pax_global_header00006660000000000000000000000064136114326330014514gustar00rootroot0000000000000052 comment=d43223773d75d2e82fb33a1281038e611c41d0f3 tempest-23.0.0/000077500000000000000000000000001361143263300132575ustar00rootroot00000000000000tempest-23.0.0/.coveragerc000066400000000000000000000001451361143263300154000ustar00rootroot00000000000000[run] branch = True source = tempest omit = tempest/tests/*,tempest/scenario/test_*.py,tempest/api/* tempest-23.0.0/.gitignore000066400000000000000000000013531361143263300152510ustar00rootroot00000000000000# Don't add patterns to exclude files created by preferred personal tools # (editors, IDEs, your operating system itself even). These should instead be # maintained outside the repository, for example in a ~/.gitignore file added # with: # # git config --global core.excludesfile '~/.gitignore' AUTHORS ChangeLog *.pyc __pycache__/ etc/accounts.yaml etc/*.conf etc/tempest.conf.sample include/swift_objects/swift_small include/swift_objects/swift_medium include/swift_objects/swift_large *.log *.swp *.swo *.egg* .tox .venv dist build .testrepository .stestr .idea .project .pydevproject .coverage* !.coveragerc cover/ doc/source/_static/tempest.conf.sample doc/source/plugin-registry.rst # Files created by releasenotes build releasenotes/build tempest-23.0.0/.gitreview000066400000000000000000000001121361143263300152570ustar00rootroot00000000000000[gerrit] host=review.opendev.org port=29418 project=openstack/tempest.git tempest-23.0.0/.mailmap000066400000000000000000000034731361143263300147070ustar00rootroot00000000000000 Adam Gandelman Andrea Frittoli Andrea Frittoli Daryl Walleck David Kranz David Kranz Ghanshyam Mann Ghanshyam Mann Ghanshyam Mann Jay Pipes Joe Gordon Ken'ichi Ohmichi Ken'ichi Ohmichi Marc Koderer Masayuki Igawa 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-23.0.0/.stestr.conf000066400000000000000000000001041361143263300155230ustar00rootroot00000000000000[DEFAULT] test_path=./tempest/test_discover group_regex=([^\.]*\.)* tempest-23.0.0/.zuul.yaml000066400000000000000000000624361361143263300152330ustar00rootroot00000000000000- job: name: devstack-tempest parent: devstack description: | Base Tempest job. This Tempest job provides the base for both the single and multi-node test setup. To run a multi-node test inherit from devstack-tempest and set the nodeset to a multi-node one. required-projects: &base_required-projects - opendev.org/openstack/tempest timeout: 7200 roles: &base_roles - zuul: opendev.org/openstack/devstack vars: &base_vars devstack_services: tempest: true devstack_local_conf: test-config: $TEMPEST_CONFIG: compute: min_compute_nodes: "{{ groups['compute'] | default(['controller']) | length }}" test_results_stage_name: test_results zuul_copy_output: '{{ devstack_base_dir }}/tempest/etc/tempest.conf': logs '{{ devstack_base_dir }}/tempest/etc/accounts.yaml': logs '{{ devstack_base_dir }}/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: true log: true yaml: true yml: true run: playbooks/devstack-tempest.yaml post-run: playbooks/post-tempest.yaml - job: name: tempest-all parent: devstack-tempest description: | Integration test that runs all tests. Former name for this job was: * legacy-periodic-tempest-dsvm-all-master vars: tox_envlist: all tempest_test_regex: tempest devstack_localrc: ENABLE_FILE_INJECTION: true - job: name: devstack-tempest-ipv6 parent: devstack-ipv6 description: | Base Tempest IPv6 job. This job is derived from 'devstack-ipv6' which set the IPv6-only setting for OpenStack services. As part of run phase, this job will verify the IPv6 setting and check the services endpoints and listen addresses are IPv6. Basically it will run the script ./tool/verify-ipv6-only-deployments.sh Child jobs of this job can run their own set of tests and can add post-run playebooks to extend the IPv6 verification specific to their deployed services. Check the wiki page for more details about project jobs setup - https://wiki.openstack.org/wiki/Goal-IPv6-only-deployments-and-testing required-projects: *base_required-projects timeout: 7200 roles: *base_roles vars: *base_vars run: playbooks/devstack-tempest-ipv6.yaml post-run: playbooks/post-tempest.yaml - job: name: tempest-ipv6-only parent: devstack-tempest-ipv6 # This currently works from stable/pike on. branches: ^(?!stable/ocata).*$ description: | Integration test of IPv6-only deployments. This job runs smoke and IPv6 relates tests only. Basic idea is to test whether OpenStack Services listen on IPv6 addrress or not. vars: tox_envlist: ipv6-only - job: name: tempest-full parent: devstack-tempest # This currently works from stable/pike on. # Before stable/pike, legacy version of tempest-full # 'legacy-tempest-dsvm-neutron-full' run. branches: ^(?!stable/ocata).*$ description: | Base integration test with Neutron networking and py27. Former names for this job where: * legacy-tempest-dsvm-neutron-full * gate-tempest-dsvm-neutron-full-ubuntu-xenial vars: tox_envlist: full devstack_localrc: ENABLE_FILE_INJECTION: true ENABLE_VOLUME_MULTIATTACH: true devstack_services: # NOTE(mriedem): Disable the cinder-backup service from tempest-full # since tempest-full is in the integrated-gate project template but # the backup tests do not really involve other services so they should # be run in some more cinder-specific job, especially because the # tests fail at a high rate (see bugs 1483434, 1813217, 1745168) c-bak: false - job: name: tempest-full-oslo-master parent: tempest-full description: | Integration test using current git of oslo libs. This ensures that when oslo libs get released that they do not break OpenStack server projects. Former name for this job was periodic-tempest-dsvm-oslo-latest-full-master. timeout: 10800 required-projects: - opendev.org/openstack/oslo.cache - opendev.org/openstack/oslo.concurrency - opendev.org/openstack/oslo.config - opendev.org/openstack/oslo.context - opendev.org/openstack/oslo.db - opendev.org/openstack/oslo.i18n - opendev.org/openstack/oslo.log - opendev.org/openstack/oslo.messaging - opendev.org/openstack/oslo.middleware - opendev.org/openstack/oslo.policy - opendev.org/openstack/oslo.privsep - opendev.org/openstack/oslo.reports - opendev.org/openstack/oslo.rootwrap - opendev.org/openstack/oslo.serialization - opendev.org/openstack/oslo.service - opendev.org/openstack/oslo.utils - opendev.org/openstack/oslo.versionedobjects - opendev.org/openstack/oslo.vmware - job: name: tempest-full-parallel parent: tempest-full voting: false branches: - master description: | Base integration test with Neutron networking and py27. It includes all scenarios as it was in the past. This job runs all scenario tests in parallel! vars: tox_envlist: full-parallel - job: name: tempest-full-py3 parent: devstack-tempest # This currently works from stable/pike on. # Before stable/pike, legacy version of tempest-full # 'legacy-tempest-dsvm-neutron-full' run. branches: ^(?!stable/ocata).*$ description: | Base integration test with Neutron networking and py3. Former names for this job where: * legacy-tempest-dsvm-py35 * gate-tempest-dsvm-py35 vars: tox_envlist: full devstack_localrc: USE_PYTHON3: true FORCE_CONFIG_DRIVE: true ENABLE_VOLUME_MULTIATTACH: true devstack_services: s-account: false s-container: false s-object: false s-proxy: false # without Swift, c-bak cannot run (in the Gate at least) # NOTE(mriedem): Disable the cinder-backup service from # tempest-full-py3 since tempest-full-py3 is in the integrated-gate-py3 # project template but the backup tests do not really involve other # services so they should be run in some more cinder-specific job, # especially because the tests fail at a high rate (see bugs 1483434, # 1813217, 1745168) c-bak: false - job: name: tempest-integrated-networking parent: devstack-tempest branches: ^(?!stable/ocata).*$ description: | This job runs integration tests for networking. This is subset of 'tempest-full' job and run only Neutron and Nova related tests. This is meant to be run on neutron gate only. vars: tox_envlist: integrated-network devstack_localrc: USE_PYTHON3: true FORCE_CONFIG_DRIVE: true devstack_services: s-account: false s-container: false s-object: false s-proxy: false c-bak: false - job: name: tempest-integrated-compute parent: devstack-tempest branches: ^(?!stable/ocata).*$ description: | This job runs integration tests for compute. This is subset of 'tempest-full' job and run Nova, Neutron, Cinder (except backup tests) and Glance related tests. This is meant to be run on Nova gate only. vars: tox_envlist: integrated-compute devstack_localrc: USE_PYTHON3: true FORCE_CONFIG_DRIVE: true ENABLE_VOLUME_MULTIATTACH: true devstack_services: s-account: false s-container: false s-object: false s-proxy: false c-bak: false - job: name: tempest-integrated-placement parent: devstack-tempest branches: ^(?!stable/ocata).*$ description: | This job runs integration tests for placement. This is subset of 'tempest-full' job and run Nova and Neutron related tests. This is meant to be run on Placement gate only. vars: tox_envlist: integrated-placement devstack_localrc: USE_PYTHON3: true FORCE_CONFIG_DRIVE: true ENABLE_VOLUME_MULTIATTACH: true devstack_services: s-account: false s-container: false s-object: false s-proxy: false c-bak: false - job: name: tempest-integrated-storage parent: devstack-tempest branches: ^(?!stable/ocata).*$ description: | This job runs integration tests for image & block storage. This is subset of 'tempest-full' job and run Cinder, Glance, Swift and Nova related tests. This is meant to be run on Cinder and Glance gate only. vars: tox_envlist: integrated-storage devstack_localrc: USE_PYTHON3: true FORCE_CONFIG_DRIVE: true ENABLE_VOLUME_MULTIATTACH: true - job: name: tempest-integrated-object-storage parent: devstack-tempest branches: ^(?!stable/ocata).*$ description: | This job runs integration tests for object storage. This is subset of 'tempest-full' job and run Swift, Cinder and Glance related tests. This is meant to be run on Swift gate only. vars: tox_envlist: integrated-object-storage devstack_localrc: # NOTE(gmann): swift is not ready on python3 yet and devstack # install it on python2.7 only. But settting the USE_PYTHON3 # for future once swift is ready on py3. USE_PYTHON3: true - job: name: tempest-full-py3-ipv6 parent: devstack-tempest-ipv6 # This currently works from stable/pike on. # Before stable/pike, legacy version of tempest-full # 'legacy-tempest-dsvm-neutron-full' run. branches: ^(?!stable/ocata).*$ description: | Base integration test with Neutron networking, IPv6 and py3. vars: tox_envlist: full devstack_localrc: USE_PYTHON3: true FORCE_CONFIG_DRIVE: true devstack_services: s-account: false s-container: false s-object: false s-proxy: false # without Swift, c-bak cannot run (in the Gate at least) c-bak: false - job: name: tempest-multinode-full-base parent: devstack-tempest description: | Base multinode integration test with Neutron networking and py27. Former names for this job were: * neutron-tempest-multinode-full * legacy-tempest-dsvm-neutron-multinode-full * gate-tempest-dsvm-neutron-multinode-full-ubuntu-xenial-nv This job includes two nodes, controller / tempest plus a subnode, but it can be used with different topologies, as long as a controller node and a tempest one exist. timeout: 10800 vars: tox_envlist: full devstack_localrc: FORCE_CONFIG_DRIVE: false NOVA_ALLOW_MOVE_TO_SAME_HOST: false LIVE_MIGRATION_AVAILABLE: true USE_BLOCK_MIGRATION_FOR_LIVE_MIGRATION: true group-vars: peers: devstack_localrc: NOVA_ALLOW_MOVE_TO_SAME_HOST: false LIVE_MIGRATION_AVAILABLE: true USE_BLOCK_MIGRATION_FOR_LIVE_MIGRATION: true - job: name: tempest-multinode-full parent: tempest-multinode-full-base nodeset: openstack-two-node-bionic # This job runs on Bionic from stable/stein on. branches: ^(?!stable/(ocata|pike|queens|rocky)).*$ - job: name: tempest-multinode-full parent: tempest-multinode-full-base nodeset: openstack-two-node-xenial # This job runs on Xenial and this is for stable/pike, stable/queens # and stable/rocky. This job is prepared to make sure all stable branches # before stable/stein will keep running on xenial. This job can be # removed once stable/rocky is EOL. branches: - stable/pike - stable/queens - stable/rocky - job: name: tempest-multinode-full-py3 parent: tempest-multinode-full vars: devstack_localrc: USE_PYTHON3: true group-vars: subnode: devstack_localrc: USE_PYTHON3: true - job: name: tempest-full-py3-opensuse15 parent: tempest-full-py3 nodeset: devstack-single-node-opensuse-15 description: | Base integration test with Neutron networking and py36 running on openSUSE Leap 15.x voting: false - job: name: tempest-slow parent: tempest-multinode-full description: | This multinode integration job will run all the tests tagged as slow. It enables the lvm multibackend setup to cover few scenario tests. This job will run only slow tests (API or Scenario) serially. Former names for this job were: * legacy-tempest-dsvm-neutron-scenario-multinode-lvm-multibackend * tempest-scenario-multinode-lvm-multibackend timeout: 10800 vars: tox_envlist: slow-serial devstack_localrc: CINDER_ENABLED_BACKENDS: lvm:lvmdriver-1,lvm:lvmdriver-2 ENABLE_VOLUME_MULTIATTACH: true devstack_plugins: neutron: https://opendev.org/openstack/neutron devstack_services: neutron-placement: true neutron-qos: true devstack_local_conf: post-config: "/$NEUTRON_CORE_PLUGIN_CONF": ovs: bridge_mappings: public:br-ex resource_provider_bandwidths: br-ex:1000000:1000000 test-config: $TEMPEST_CONFIG: network-feature-enabled: qos_placement_physnet: public tempest_concurrency: 2 group-vars: # NOTE(mriedem): The ENABLE_VOLUME_MULTIATTACH variable is used on both # the controller and subnode prior to Rocky so we have to make sure the # variable is set in both locations. subnode: devstack_localrc: ENABLE_VOLUME_MULTIATTACH: true - job: name: tempest-slow-py3 parent: tempest-slow vars: devstack_localrc: USE_PYTHON3: true devstack_services: s-account: false s-container: false s-object: false s-proxy: false # without Swift, c-bak cannot run (in the Gate at least) c-bak: false group-vars: subnode: devstack_localrc: USE_PYTHON3: true - job: name: tempest-full-train parent: tempest-full override-checkout: stable/train - job: name: tempest-full-train-py3 parent: tempest-full-py3 override-checkout: stable/train - job: name: tempest-full-stein parent: tempest-full override-checkout: stable/stein - job: name: tempest-full-stein-py3 parent: tempest-full-py3 override-checkout: stable/stein - job: name: tempest-full-rocky parent: tempest-full nodeset: openstack-single-node-xenial override-checkout: stable/rocky - job: name: tempest-full-rocky-py3 parent: tempest-full-py3 nodeset: openstack-single-node-xenial override-checkout: stable/rocky - job: name: tempest-full-queens parent: tempest-full nodeset: openstack-single-node-xenial override-checkout: stable/queens - job: name: tempest-full-queens-py3 parent: tempest-full-py3 nodeset: openstack-single-node-xenial override-checkout: stable/queens - job: name: tempest-tox-plugin-sanity-check parent: tox description: | Run tempest plugin sanity check script using tox. nodeset: ubuntu-bionic vars: tox_envlist: plugin-sanity-check timeout: 5000 - job: name: tempest-cinder-v2-api parent: devstack-tempest branches: - master description: | This job runs the cinder API test against v2 endpoint. vars: tox_envlist: all tempest_test_regex: api.*volume devstack_localrc: TEMPEST_VOLUME_TYPE: volumev2 - job: name: tempest-full-test-account-py3 parent: tempest-full-py3 description: | This job runs the full set of tempest tests using pre-provisioned credentials instead of dynamic credentials and py3. Former names for this job were: - legacy-tempest-dsvm-full-test-accounts - legacy-tempest-dsvm-neutron-full-test-accounts - legacy-tempest-dsvm-identity-v3-test-accounts vars: devstack_localrc: TEMPEST_USE_TEST_ACCOUNTS: True - job: name: tempest-full-test-account-no-admin-py3 parent: tempest-full-test-account-py3 description: | This job runs the full set of tempest tests using pre-provisioned credentials and py3 without having an admin account. Former name for this job was: - legacy-tempest-dsvm-neutron-full-non-admin vars: devstack_localrc: TEMPEST_HAS_ADMIN: False - job: name: tempest-pg-full parent: tempest-full description: | Base integration test with Neutron networking and py27 and PostgreSQL. Former name for this job was legacy-tempest-dsvm-neutron-pg-full. vars: devstack_localrc: ENABLE_FILE_INJECTION: true DATABASE_TYPE: postgresql - project-template: name: integrated-gate-networking description: | Run the python3 Tempest network integration tests (Nova and Neutron related) in check and gate for the neutron integrated gate. This is meant to be run on neutron gate only. check: jobs: - grenade-py3 - tempest-integrated-networking gate: jobs: - grenade-py3 - tempest-integrated-networking - project-template: name: integrated-gate-compute description: | Run the python3 Tempest compute integration tests (Nova, Neutron, Cinder and Glance related) in check and gate for the Nova integrated gate. This is meant to be run on Nova gate only. check: jobs: - grenade-py3 - tempest-integrated-compute gate: jobs: - grenade-py3 - tempest-integrated-compute - project-template: name: integrated-gate-placement description: | Run the python3 Tempest placement integration tests (Nova and Neutron related) in check and gate for the Placement integrated gate. This is meant to be run on Placement gate only. check: jobs: - grenade-py3 - tempest-integrated-placement gate: jobs: - grenade-py3 - tempest-integrated-placement - project-template: name: integrated-gate-storage description: | Run the python3 Tempest image & block storage integration tests (Cinder, Glance, Swift and Nova related) in check and gate for the neutron integrated gate. This is meant to be run on Cinder and Glance gate only. check: jobs: - grenade-py3 - tempest-integrated-storage gate: jobs: - grenade-py3 - tempest-integrated-storage - project-template: name: integrated-gate-object-storage description: | Run the python3 Tempest object storage integration tests (Swift, Cinder and Glance related) in check and gate for the swift integrated gate. This is meant to be run on swift gate only. check: jobs: - grenade-py3 - tempest-integrated-object-storage gate: jobs: - grenade-py3 - tempest-integrated-object-storage - project: templates: - check-requirements - integrated-gate - integrated-gate-py3 - openstack-cover-jobs - openstack-python-jobs - openstack-python35-jobs - openstack-python3-train-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 check: jobs: - devstack-tempest: files: - ^playbooks/ - ^roles/ - ^.zuul.yaml$ - devstack-tempest-ipv6: voting: false files: - ^playbooks/ - ^roles/ - ^.zuul.yaml$ - tempest-full-parallel: # Define list of irrelevant files to use everywhere else irrelevant-files: &tempest-irrelevant-files - ^.*\.rst$ - ^doc/.*$ - ^etc/.*$ - ^releasenotes/.*$ - ^setup.cfg$ - ^tempest/hacking/.*$ - ^tempest/tests/.*$ - ^tools/.*$ - ^.coveragerc$ - ^.gitignore$ - ^.gitreview$ - ^.mailmap$ - tempest-full-py3: irrelevant-files: *tempest-irrelevant-files - tempest-full-py3-ipv6: voting: false irrelevant-files: *tempest-irrelevant-files - tempest-full-train: irrelevant-files: *tempest-irrelevant-files - tempest-full-train-py3: irrelevant-files: *tempest-irrelevant-files - tempest-full-stein: irrelevant-files: *tempest-irrelevant-files - tempest-full-stein-py3: irrelevant-files: *tempest-irrelevant-files - tempest-full-rocky: irrelevant-files: *tempest-irrelevant-files - tempest-full-rocky-py3: irrelevant-files: *tempest-irrelevant-files - tempest-full-queens: irrelevant-files: *tempest-irrelevant-files - tempest-full-queens-py3: irrelevant-files: *tempest-irrelevant-files - tempest-multinode-full: irrelevant-files: *tempest-irrelevant-files - tempest-multinode-full-py3: irrelevant-files: *tempest-irrelevant-files - tempest-tox-plugin-sanity-check: irrelevant-files: &tempest-irrelevant-files-2 - ^.*\.rst$ - ^doc/.*$ - ^etc/.*$ - ^releasenotes/.*$ - ^setup.cfg$ - ^tempest/hacking/.*$ - ^tempest/tests/.*$ - ^.coveragerc$ - ^.gitignore$ - ^.gitreview$ - ^.mailmap$ # tools/ is not here since this relies on a script in tools/. - tempest-ipv6-only: irrelevant-files: *tempest-irrelevant-files-2 - tempest-slow: irrelevant-files: *tempest-irrelevant-files - tempest-slow-py3: irrelevant-files: *tempest-irrelevant-files - nova-live-migration: voting: false irrelevant-files: *tempest-irrelevant-files - neutron-grenade-multinode: irrelevant-files: *tempest-irrelevant-files - grenade-py3: irrelevant-files: *tempest-irrelevant-files - devstack-plugin-ceph-tempest: voting: false irrelevant-files: *tempest-irrelevant-files - puppet-openstack-integration-4-scenario001-tempest-centos-7: voting: false irrelevant-files: *tempest-irrelevant-files - puppet-openstack-integration-4-scenario002-tempest-centos-7: voting: false irrelevant-files: *tempest-irrelevant-files - puppet-openstack-integration-4-scenario003-tempest-centos-7: voting: false irrelevant-files: *tempest-irrelevant-files - puppet-openstack-integration-4-scenario004-tempest-centos-7: voting: false irrelevant-files: *tempest-irrelevant-files - neutron-tempest-dvr: irrelevant-files: *tempest-irrelevant-files - tempest-full: irrelevant-files: *tempest-irrelevant-files - interop-tempest-consistency: irrelevant-files: *tempest-irrelevant-files - tempest-full-test-account-py3: voting: false irrelevant-files: *tempest-irrelevant-files - tempest-full-test-account-no-admin-py3: voting: false irrelevant-files: *tempest-irrelevant-files - openstack-tox-bashate: irrelevant-files: *tempest-irrelevant-files-2 gate: jobs: - tempest-slow-py3: irrelevant-files: *tempest-irrelevant-files - neutron-grenade-multinode: irrelevant-files: *tempest-irrelevant-files - tempest-full: irrelevant-files: *tempest-irrelevant-files - grenade-py3: irrelevant-files: *tempest-irrelevant-files - tempest-ipv6-only: irrelevant-files: *tempest-irrelevant-files-2 experimental: jobs: - tempest-cinder-v2-api: irrelevant-files: *tempest-irrelevant-files - tempest-all: irrelevant-files: *tempest-irrelevant-files - legacy-tempest-dsvm-neutron-dvr-multinode-full: irrelevant-files: *tempest-irrelevant-files - neutron-tempest-dvr-ha-multinode-full: irrelevant-files: *tempest-irrelevant-files - nova-tempest-v2-api: irrelevant-files: *tempest-irrelevant-files - legacy-tempest-dsvm-lvm-multibackend: irrelevant-files: *tempest-irrelevant-files - devstack-plugin-ceph-tempest-py3: irrelevant-files: *tempest-irrelevant-files - tempest-pg-full: irrelevant-files: *tempest-irrelevant-files - tempest-full-py3-opensuse15: irrelevant-files: *tempest-irrelevant-files periodic-stable: jobs: - tempest-full-train - tempest-full-train-py3 - tempest-full-stein - tempest-full-stein-py3 - tempest-full-rocky - tempest-full-rocky-py3 - tempest-full-queens - tempest-full-queens-py3 periodic: jobs: - tempest-all - tempest-full-oslo-master tempest-23.0.0/CONTRIBUTING.rst000066400000000000000000000012151361143263300157170ustar00rootroot00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in this page: https://docs.openstack.org/infra/manual/developers.html If you already have a good understanding of how the system works and your OpenStack accounts are set up, you can skip to the development workflow section of this documentation to learn how changes to OpenStack should be submitted for review via the Gerrit tool: https://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/tempest tempest-23.0.0/HACKING.rst000066400000000000000000000527331361143263300150670ustar00rootroot00000000000000Tempest 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 - [T117] Check negative tests have ``@decorators.attr(type=['negative'])`` applied. It is recommended to use ``tox -eautopep8`` before submitting a patch. 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 Supported OpenStack Components ------------------------------ Tempest's :ref:`library` and :ref:`plugin interface ` can be leveraged to support integration testing for virtually any OpenStack component. However, Tempest only offers **in-tree** integration testing coverage for the following components: * Cinder * Glance * Keystone * Neutron * Nova * Swift Historically, Tempest offered in-tree testing for other components as well, but since the introduction of the `External Plugin Interface`_, Tempest's in-tree testing scope has been limited to the projects above. Integration tests for projects not included above should go into one of the `relevant plugin projects`_. .. _External Plugin Interface: https://specs.openstack.org/openstack/qa-specs/specs/tempest/implemented/tempest-external-plugin-interface.html .. _relevant plugin projects: https://docs.openstack.org/tempest/latest/plugin-registry.html#detected-plugins 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 Attributes --------------- Tempest leverages `test attributes`_ which are a simple but effective way of distinguishing between different "types" of API tests. A test can be "tagged" with such attributes using the ``decorators.attr`` decorator, for example:: @decorators.attr(type=['negative']) def test_aggregate_create_aggregate_name_length_less_than_1(self): [...] These test attributes can be used for test selection via regular expressions. For example, ``(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)`` runs all the tests in the ``scenario`` test module, *except* for those tagged with the ``slow`` attribute (via a negative lookahead in the regular expression). These attributes are used in Tempest's ``tox.ini`` as well as Tempest's Zuul job definitions for specifying particular batches of Tempest test suites to run. .. _test attributes: https://testtools.readthedocs.io/en/latest/for-test-authors.html?highlight=attr#test-attributes Negative Attribute ^^^^^^^^^^^^^^^^^^ The ``type='negative'`` attribute is used to signify that a test is a negative test, which is a test that handles invalid input gracefully. This attribute should be applied to all negative test scenarios. This attribute must be applied to each test that belongs to a negative test class, i.e. a test class name ending with "Negative.*" substring. Slow Attribute ^^^^^^^^^^^^^^ The ``type='slow'`` attribute is used to signify that a test takes a long time to run, relatively speaking. This attribute is usually applied to :ref:`scenario tests `, which involve a complicated series of API operations, the total runtime of which can be relatively long. This long runtime has performance implications on `Zuul`_ jobs, which is why the ``slow`` attribute is leveraged to run slow tests on a selective basis, to keep total `Zuul`_ job runtime down to a reasonable time frame. .. _Zuul: https://docs.openstack.org/infra/zuul/ Smoke Attribute ^^^^^^^^^^^^^^^ The ``type='smoke'`` attribute is used to signify that a test is a so-called smoke test, which is a type of test that tests the most vital OpenStack functionality, like listing servers or flavors or creating volumes. The attribute should be sparingly applied to only the tests that sanity-check the most essential functionality of an OpenStack cloud. 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 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 """ 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. This can be just as simple as using the ``@utils.requires_ext()`` or ``testtools.skipUnless`` decorators to check if the required extension (or discoverable optional API) or feature is enabled or can be as difficult as 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. Introduction of a new feature flag requires specifying a default value for the corresponding config option that is appropriate in the latest OpenStack release. Because Tempest is branchless, the feature flag's default value will need to be overridden to a value that is appropriate in earlier releases in which the feature isn't available. In DevStack, this can be accomplished by modifying Tempest's `lib installation script`_ for previous branches (because DevStack is branched). .. _lib installation script: https://opendev.org/openstack/devstack/src/branch/master/lib/tempest 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-23.0.0/LICENSE000066400000000000000000000236371361143263300142770ustar00rootroot00000000000000 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-23.0.0/README.rst000066400000000000000000000016341361143263300147520ustar00rootroot00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/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 ============================================== 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. * Documentation: https://docs.openstack.org/tempest/latest/ * Features: https://specs.openstack.org/openstack/qa-specs/#tempest * Bugs: https://bugs.launchpad.net/tempest/ * Release Notes: https://docs.openstack.org/releasenotes/tempest Get in touch via `email `_. Use [tempest] in your subject. tempest-23.0.0/REVIEWING.rst000066400000000000000000000176631361143263300153650ustar00rootroot00000000000000Reviewing 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. API and scenario tests aren't required to have unit tests since they should be self-verifying by running them in the gate. All service clients, on the other hand, `must have`_ unit tests, as they belong to ``tempest/lib``. .. _must have: https://docs.openstack.org/tempest/latest/library.html#testing API Stability ------------- Tests should only be added for published stable APIs. If a patch contains tests for an API which hasn't been marked as stable or for an API 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. Test Removal and Refactoring ---------------------------- Make sure that any test that is renamed, relocated (e.g. moved to another class), or removed does not belong to the `interop`_ testing suite -- which includes a select suite of Tempest tests for the purposes of validating that OpenStack vendor clouds are interoperable -- or a project's `whitelist`_ or `blacklist`_ files. It is of critical importance that no interop, whitelist or blacklist test reference be broken by a patch set introduced to Tempest that renames, relocates or removes a referenced test. Please check the existence of code which references Tempest tests with: http://codesearch.openstack.org/ Interop ^^^^^^^ Make sure that modifications to an `interop`_ test are backwards-compatible. This means that code modifications to tests should not undermine the quality of the validation currently performed by the test or significantly alter the behavior of the test. Removal ^^^^^^^ Reference the :ref:`test-removal` guidelines for understanding best practices associated with test removal. .. _interop: https://www.openstack.org/brand/interop .. _whitelist: https://docs.openstack.org/tempest/latest/run.html#test-selection .. _blacklist: https://docs.openstack.org/tempest/latest/run.html#test-selection 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 --------------- * It's OK to hold off on an approval until a subject matter expert reviews it. * Every patch needs two +2's before being approved. * However, a single Tempest core reviewer can approve patches without waiting for another +2 in the following cases: * If a patch has already been approved but requires a trivial rebase to merge, then there is no need to wait for a second +2, since the patch has already had two +2's. * If any trivial patch set fixes one of the items below: * Documentation or code comment typo * Documentation ref link * Example: `example`_ .. note:: Any other small documentation, CI job, or code change does not fall under this category. * If the patch **unblocks** a failing project gate, provided that: * the project's PTL +1's the change * the patch does not affect any other project's testing gates * the patch does not cause any negative side effects * If fixing and removing the faulty plugin (which leads to fail voting ``tempest-tox-plugin-sanity-check`` job) and unblock the tempest gate Note that such a policy should be used judiciously, as we should strive to have two +2's on each patch set, prior to approval. .. _example: https://review.opendev.org/#/c/611032/ tempest-23.0.0/bindep.txt000066400000000000000000000006021361143263300152570ustar00rootroot00000000000000# 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-23.0.0/doc/000077500000000000000000000000001361143263300140245ustar00rootroot00000000000000tempest-23.0.0/doc/requirements.txt000066400000000000000000000007021361143263300173070ustar00rootroot00000000000000# 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. openstackdocstheme>=1.20.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7' # BSD sphinx!=1.6.6,!=1.6.7,!=2.1.0,>=1.6.2;python_version>='3.4' # BSD sphinxcontrib-svg2pdfconverter>=0.1.0 # BSD tempest-23.0.0/doc/source/000077500000000000000000000000001361143263300153245ustar00rootroot00000000000000tempest-23.0.0/doc/source/HACKING.rst000077700000000000000000000000001361143263300213412../../HACKING.rstustar00rootroot00000000000000tempest-23.0.0/doc/source/REVIEWING.rst000077700000000000000000000000001361143263300221272../../REVIEWING.rstustar00rootroot00000000000000tempest-23.0.0/doc/source/_extra/000077500000000000000000000000001361143263300166065ustar00rootroot00000000000000tempest-23.0.0/doc/source/_extra/.htaccess000066400000000000000000000000761361143263300204070ustar00rootroot00000000000000redirectmatch 301 ^/developer/tempest/(.*) /tempest/latest/$1 tempest-23.0.0/doc/source/_static/000077500000000000000000000000001361143263300167525ustar00rootroot00000000000000tempest-23.0.0/doc/source/_static/.keep000066400000000000000000000000001361143263300176650ustar00rootroot00000000000000tempest-23.0.0/doc/source/account_generator.rst000066400000000000000000000002441361143263300215600ustar00rootroot00000000000000-------------------------------------- Tempest Test-Account Generator Utility -------------------------------------- .. automodule:: tempest.cmd.account_generator tempest-23.0.0/doc/source/cleanup.rst000066400000000000000000000002071361143263300175040ustar00rootroot00000000000000-------------------------------- Post Tempest Run Cleanup Utility -------------------------------- .. automodule:: tempest.cmd.cleanuptempest-23.0.0/doc/source/conf.py000066400000000000000000000157611361143263300166350ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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', 'sphinxcontrib.rsvgconverter', '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 = 'doc' # 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. 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'] # -- Options for LaTeX output ------------------------------------------------- # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', 'doc-tempest.tex', u'Tempest Testing Project', u'OpenStack Foundation', 'manual'), ] # Disable usage of xindy https://bugzilla.redhat.com/show_bug.cgi?id=1643664 latex_use_xindy = False tempest-23.0.0/doc/source/configuration.rst000066400000000000000000000511421361143263300207300ustar00rootroot00000000000000.. _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 pre-provisioned credentials (also known as 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 pre-provisioned credentials 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 the 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 are 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-23.0.0/doc/source/data/000077500000000000000000000000001361143263300162355ustar00rootroot00000000000000tempest-23.0.0/doc/source/data/tempest-blacklisted-plugins-registry.header000066400000000000000000000004561361143263300267010ustar00rootroot00000000000000Blacklisted Plugins =================== List of Tempest plugin projects that are stale or unmaintained for a long time (6 months or more). They can be moved out of blacklist state once one of the relevant patches gets merged: https://review.opendev.org/#/q/topic:tempest-sanity-gate+%28status:open%29 tempest-23.0.0/doc/source/data/tempest-plugins-registry.header000066400000000000000000000011441361143263300244150ustar00rootroot00000000000000.. 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. tempest-23.0.0/doc/source/field_guide/000077500000000000000000000000001361143263300175645ustar00rootroot00000000000000tempest-23.0.0/doc/source/field_guide/api.rst000077700000000000000000000000001361143263300256442../../../tempest/api/README.rstustar00rootroot00000000000000tempest-23.0.0/doc/source/field_guide/index.rst000077700000000000000000000000001361143263300254312../../../tempest/README.rstustar00rootroot00000000000000tempest-23.0.0/doc/source/field_guide/scenario.rst000077700000000000000000000000001361143263300277302../../../tempest/scenario/README.rstustar00rootroot00000000000000tempest-23.0.0/doc/source/field_guide/unit_tests.rst000077700000000000000000000000001361143263300276652../../../tempest/tests/README.rstustar00rootroot00000000000000tempest-23.0.0/doc/source/index.rst000066400000000000000000000025621361143263300171720ustar00rootroot00000000000000======================= 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 Support Policy -------------- .. toctree:: :maxdepth: 2 stable_branch_support_policy Search ====== .. only:: html * :ref:`Tempest document search `: Search the contents of this document. * `OpenStack wide search `_: Search the wider set of OpenStack documentation, including forums.tempest-23.0.0/doc/source/library.rst000066400000000000000000000055401361143263300175260ustar00rootroot00000000000000.. _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 change set 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 backward 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-23.0.0/doc/source/library/000077500000000000000000000000001361143263300167705ustar00rootroot00000000000000tempest-23.0.0/doc/source/library/api_microversion_testing.rst000066400000000000000000000017141361143263300246320ustar00rootroot00000000000000.. _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-23.0.0/doc/source/library/auth.rst000066400000000000000000000002511361143263300204610ustar00rootroot00000000000000.. _auth: Authentication Framework Usage ============================== --------------- The auth module --------------- .. automodule:: tempest.lib.auth :members: tempest-23.0.0/doc/source/library/cli.rst000066400000000000000000000005061361143263300202720ustar00rootroot00000000000000.. _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-23.0.0/doc/source/library/clients.rst000066400000000000000000000020311361143263300211570ustar00rootroot00000000000000.. _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-23.0.0/doc/source/library/credential_providers.rst000066400000000000000000000147761361143263300237500ustar00rootroot00000000000000.. _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='tempest', 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-23.0.0/doc/source/library/decorators.rst000066400000000000000000000002711361143263300216670ustar00rootroot00000000000000.. _decorators: Decorators Usage Guide ====================== --------------------- The decorators module --------------------- .. automodule:: tempest.lib.decorators :members: tempest-23.0.0/doc/source/library/rest_client.rst000066400000000000000000000002711361143263300220350ustar00rootroot00000000000000.. _rest_client: Rest Client Usage ================= ---------------------- The rest_client module ---------------------- .. automodule:: tempest.lib.common.rest_client :members: tempest-23.0.0/doc/source/library/service_clients/000077500000000000000000000000001361143263300221515ustar00rootroot00000000000000tempest-23.0.0/doc/source/library/service_clients/compute_clients.rst000066400000000000000000000002111361143263300260720ustar00rootroot00000000000000.. _servers_client: Compute Client Usage ==================== .. automodule:: tempest.lib.services.compute.servers_client :members: tempest-23.0.0/doc/source/library/utils.rst000066400000000000000000000002211361143263300206550ustar00rootroot00000000000000.. _utils: Utils Usage =========== --------------- The misc module --------------- .. automodule:: tempest.lib.common.utils.misc :members: tempest-23.0.0/doc/source/library/validation_resources.rst000066400000000000000000000003541361143263300237500ustar00rootroot00000000000000.. _validation_resources: Validation Resources ==================== ------------------------------- The validation_resources module ------------------------------- .. automodule:: tempest.lib.common.validation_resources :members: tempest-23.0.0/doc/source/microversion_testing.rst000066400000000000000000000363061361143263300223420ustar00rootroot00000000000000================================= 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.6`_ .. _2.6: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id5 * `2.8`_ .. _2.8: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id7 * `2.9`_ .. _2.9: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id8 * `2.10`_ .. _2.10: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id9 * `2.19`_ .. _2.19: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id17 * `2.20`_ .. _2.20: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id18 * `2.21`_ .. _2.21: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id19 * `2.25`_ .. _2.25: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-mitaka * `2.26`_ .. _2.26: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id23 * `2.28`_ .. _2.28: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id25 * `2.32`_ .. _2.32: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id29 * `2.36`_ .. _2.36: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#microversion * `2.37`_ .. _2.37: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id34 * `2.39`_ .. _2.39: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id35 * `2.41`_ .. _2.41: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id37 * `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 * `2.49`_ .. _2.49: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id44 * `2.53`_ .. _2.53: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-pike * `2.54`_ .. _2.54: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id49 * `2.55`_ .. _2.55: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id50 * `2.57`_ .. _2.57: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id52 * `2.60`_ .. _2.60: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-queens * `2.61`_ .. _2.61: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id55 * `2.63`_ .. _2.63: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id57 * `2.70`_ .. _2.70: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id63 * `2.71`_ .. _2.71: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id64 * 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.13`_ .. _3.13: https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id13 * `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-23.0.0/doc/source/overview.rst000066400000000000000000000272211361143263300177300ustar00rootroot00000000000000Tempest - 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. Team and repository tags ------------------------ .. image:: https://governance.openstack.org/tc/badges/tempest.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on 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://opendev.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 `Tempest Configuration `_ #. Once the configuration is done you're now ready to run Tempest. This can be done using the `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 `stestr`_ directly. For example, from the workspace dir run:: $ stestr run --black-regex '\[.*\bslow\b.*\]' '^tempest\.(api|scenario)' will run the same set of tests as the default gate jobs. Or you can use `unittest`_ compatible test runners such as `stestr`_, `pytest`_ etc. 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. .. _unittest: https://docs.python.org/3/library/unittest.html .. _stestr: https://stestr.readthedocs.org/en/latest/MANUAL.html .. _pytest: https://docs.pytest.org/en/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 `_ 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: https://semver.org/ Configuration ------------- Detailed configuration of Tempest is beyond the scope of this document, see `Tempest Configuration Documentation `_ 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 py36 tox jobs which will run the unit tests with the corresponding version of python. One common activity is to just run a single test, you can do this with tox simply by specifying to just run py27 or py36 tests against a single test:: $ tox -e py36 -- -n tempest.tests.test_microversions.TestMicroversionsTestsClass.test_config_version_none_23 Or all tests in the test_microversions.py file:: $ tox -e py36 -- -n tempest.tests.test_microversions You may also use regular expressions to run any matching tests:: $ tox -e py36 -- test_microversions Additionally, when running a single test, or test-file, the ``-n/--no-discover`` argument is no longer required, however it may perform faster if included. For more information on these options and details about stestr, please see the `stestr documentation `_. 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 `stestr`_ 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 ``stestr``. By default, ``stestr`` runs tests in parallel :: $ stestr run To run one single test serially :: $ stestr run --serial tempest.api.compute.servers.test_servers_negative.ServersNegativeTestJSON.test_reboot_non_existent_server tempest-23.0.0/doc/source/plugin.rst000066400000000000000000000332331361143263300173600ustar00rootroot00000000000000.. _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 whichever effort goes into ensuring backward 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://opendev.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: https://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 of 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-23.0.0/doc/source/run.rst000066400000000000000000000001271361143263300166620ustar00rootroot00000000000000.. _tempest_run: ----------- Tempest Run ----------- .. automodule:: tempest.cmd.run tempest-23.0.0/doc/source/sampleconf.rst000066400000000000000000000007431361143263300202110ustar00rootroot00000000000000.. _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-23.0.0/doc/source/stable_branch_support_policy.rst000066400000000000000000000037201361143263300240220ustar00rootroot00000000000000Stable Branch Support Policy ============================ Since the `Extended Maintenance policy`_ for stable branches was adopted OpenStack projects will keep stable branches around after a "stable" or "maintained" period for a phase of indeterminate length called "Extended Maintenance". Prior to this resolution Tempest supported all stable branches which were supported upstream. This policy does not scale under the new model as Tempest would be responsible for gating proposed changes against an ever increasing number of branches. Therefore due to resource constraints, Tempest will only provide support for branches in the "Maintained" phase from the documented `Support Phases`_. When a branch moves from the *Maintained* to the *Extended Maintenance* phase, Tempest will tag the removal of support for that branch as it has in the past when a branch goes end of life. The expectation for *Extended Maintenance* phase branches is that they will continue running Tempest during that phase of support. Since the REST APIs are stable interfaces across release boundaries, branches in these phases should run Tempest from master as long as possible. But, because we won't be actively testing branches in these phases, it's possible that we'll introduce changes to Tempest on master which will break support on *Extended Maintenance* phase branches. When this happens the expectation for those branches is to either switch to running Tempest from a tag with support for the branch, or blacklist a newly introduced test (if that is the cause of the issue). Tempest will not be creating stable branches to support *Extended Maintenance* phase branches, as the burden is on the *Extended Maintenance* phase branche maintainers, not the Tempest project, to support that branch. .. _Extended Maintenance policy: https://governance.openstack.org/tc/resolutions/20180301-stable-branch-eol.html .. _Support Phases: https://docs.openstack.org/project-team-guide/stable-branches.html#maintenance-phases tempest-23.0.0/doc/source/subunit_describe_calls.rst000066400000000000000000000002211361143263300225600ustar00rootroot00000000000000------------------------------ Subunit Describe Calls Utility ------------------------------ .. automodule:: tempest.cmd.subunit_describe_calls tempest-23.0.0/doc/source/test_removal.rst000066400000000000000000000216151361143263300205670ustar00rootroot00000000000000.. _test-removal: Tempest 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 to 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 the command: ``mysql -u query -p -h logstash.openstack.org subunit2sql`` to connect to the subunit2sql db #. run the query: .. code-block:: console 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. .. _defcore: https://wiki.openstack.org/wiki/Governance/InteropWG .. _interop: https://www.openstack.org/brand/interop 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-discuss mailing list. (for example see: http://lists.openstack.org/pipermail/openstack-dev/2016-February/086218.html or http://lists.openstack.org/pipermail/openstack-discuss/2019-March/003574.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 tests must still honor `prong #3`_. For the first exception type, the only types of testing in the 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 a 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 these criteria as a consequence of a change, the test must be removed according to the procedure discussed in this document. The API change should not be merged until all conditions required for test removal can be met. tempest-23.0.0/doc/source/workspace.rst000066400000000000000000000001351361143263300200530ustar00rootroot00000000000000----------------- Tempest Workspace ----------------- .. automodule:: tempest.cmd.workspace tempest-23.0.0/doc/source/write_tests.rst000066400000000000000000000322371361143263300204410ustar00rootroot00000000000000.. _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 addClassResourceCleanup 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-23.0.0/etc/000077500000000000000000000000001361143263300140325ustar00rootroot00000000000000tempest-23.0.0/etc/accounts.yaml.sample000066400000000000000000000037041361143263300200210ustar00rootroot00000000000000# 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-23.0.0/etc/logging.conf.sample000066400000000000000000000010701361143263300176050ustar00rootroot00000000000000[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-23.0.0/etc/whitelist.yaml000066400000000000000000000000001361143263300167200ustar00rootroot00000000000000tempest-23.0.0/playbooks/000077500000000000000000000000001361143263300152625ustar00rootroot00000000000000tempest-23.0.0/playbooks/devstack-tempest-ipv6.yaml000066400000000000000000000020351361143263300223130ustar00rootroot00000000000000# Changes that run through devstack-tempest-ipv6 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: - orchestrate-devstack # We run tests only on one node, regardless how many nodes are in the system - hosts: tempest environment: # This enviroment variable is used by the optional tempest-gabbi # job provided by the gabbi-tempest plugin. It can be safely ignored # if that plugin is not being used. GABBI_TEMPEST_PATH: "{{ gabbi_tempest_path | default('') }}" roles: - setup-tempest-run-dir - setup-tempest-data-dir - acl-devstack-files # Verify the IPv6-only deployments. This role will perform check for # IPv6 only env for example Devstack IPv6 settings and services listen # address is IPv6 etc. This is invoked before tests are run so that we can # fail early if anything missing the IPv6 settings or deployments. - ipv6-only-deployments-verification - run-tempest tempest-23.0.0/playbooks/devstack-tempest.yaml000066400000000000000000000013051361143263300214300ustar00rootroot00000000000000# 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: - orchestrate-devstack # We run tests only on one node, regardless how many nodes are in the system - hosts: tempest environment: # This enviroment variable is used by the optional tempest-gabbi # job provided by the gabbi-tempest plugin. It can be safely ignored # if that plugin is not being used. GABBI_TEMPEST_PATH: "{{ gabbi_tempest_path | default('') }}" roles: - setup-tempest-run-dir - setup-tempest-data-dir - acl-devstack-files - run-tempest tempest-23.0.0/playbooks/post-tempest.yaml000066400000000000000000000002361361143263300206130ustar00rootroot00000000000000- hosts: tempest become: true roles: - role: fetch-subunit-output zuul_work_dir: '{{ devstack_base_dir }}/tempest' - role: process-stackviz tempest-23.0.0/releasenotes/000077500000000000000000000000001361143263300157505ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/000077500000000000000000000000001361143263300171005ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/10/000077500000000000000000000000001361143263300173205ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/10/10.0-supported-openstack-releases-b88db468695348f6.yaml000066400000000000000000000007201361143263300305000ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/10/10.0.0-Tempest-library-interface-0eb680b810139a50.yaml000066400000000000000000000006301361143263300277760ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/10/10.0.0-start-using-reno-ed9518126fd0e1a3.yaml000066400000000000000000000000741361143263300263520ustar00rootroot00000000000000--- other: - Start using reno for managing release notes. tempest-23.0.0/releasenotes/notes/11/000077500000000000000000000000001361143263300173215ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/11/11.0.0-api-microversion-testing-support-2ceddd2255670932.yaml000066400000000000000000000001321361143263300315230ustar00rootroot00000000000000--- features: - Tempest library interface addition(API Microversion testing interfaces).tempest-23.0.0/releasenotes/notes/11/11.0.0-compute-microversion-support-e0b23f960f894b9b.yaml000066400000000000000000000001131361143263300310440ustar00rootroot00000000000000--- features: - Compute Microversion testing support in Service Clients. tempest-23.0.0/releasenotes/notes/11/11.0.0-supported-openstack-releases-1e5d7295d939d439.yaml000066400000000000000000000007661361143263300306440ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/12/000077500000000000000000000000001361143263300173225ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/12/12.0.0-supported-openstack-releases-f10aac381d933dd1.yaml000066400000000000000000000007611361143263300310220ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/12/12.1.0-add-network-versions-client-d90e8334e1443f5c.yaml000066400000000000000000000001601361143263300304210ustar00rootroot00000000000000--- features: - Adds a network version client for querying Neutron's API version discovery URL ("GET /"). tempest-23.0.0/releasenotes/notes/12/12.1.0-add-scope-to-auth-b5a82493ea89f41e.yaml000066400000000000000000000005271361143263300263730ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/12/12.1.0-add-tempest-run-3d0aaf69c2ca4115.yaml000066400000000000000000000002031361143263300262060ustar00rootroot00000000000000--- features: - Adds the tempest run command to the unified tempest CLI. This new command is used for running tempest tests. tempest-23.0.0/releasenotes/notes/12/12.1.0-add-tempest-workspaces-228a2ba4690b5589.yaml000066400000000000000000000002551361143263300273720ustar00rootroot00000000000000--- features: - Adds tempest workspaces command and WorkspaceManager. This is used to have a centralized repository for managing different tempest configurations. tempest-23.0.0/releasenotes/notes/12/12.1.0-add_subunit_describe_calls-5498a37e6cd66c4b.yaml000066400000000000000000000004671361143263300305020ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/12/12.1.0-bug-1486834-7ebca15836ae27a9.yaml000066400000000000000000000003021361143263300246310ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/12/12.1.0-identity-clients-as-library-e663c6132fcac6c2.yaml000066400000000000000000000005701361143263300305500ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/12/12.1.0-image-clients-as-library-86d17caa26ce3961.yaml000066400000000000000000000006711361143263300277320ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/12/12.1.0-new-test-utils-module-adf34468c4d52719.yaml000066400000000000000000000011351361143263300272560ustar00rootroot00000000000000--- 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. 12.1.0-remove-input-scenarios-functionality-01308e6d4307f580.yaml000066400000000000000000000012031361143263300321350ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/12--- 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-23.0.0/releasenotes/notes/12/12.1.0-remove-integrated-horizon-bb57551c1e5f5be3.yaml000066400000000000000000000003731361143263300303300ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/12/12.1.0-remove-legacy-credential-providers-3d653ac3ba1ada2b.yaml000066400000000000000000000003071361143263300322140ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/12/12.1.0-remove-trove-tests-666522e9113549f9.yaml000066400000000000000000000002151361143263300264470ustar00rootroot00000000000000--- upgrade: - All tests for the Trove project have been removed from tempest. They now live as a tempest plugin in the trove project. tempest-23.0.0/releasenotes/notes/12/12.1.0-routers-client-as-library-25a363379da351f6.yaml000066400000000000000000000003501361143263300300210ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/12/12.1.0-support-chunked-encoding-d71f53225f68edf3.yaml000066400000000000000000000006201361143263300300660ustar00rootroot00000000000000--- 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. 12.1.0-tempest-init-global-config-dir-location-changes-12260255871d3a2b.yaml000066400000000000000000000013751361143263300337450ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/12--- 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-23.0.0/releasenotes/notes/12/12.2.0-add-httptimeout-in-restclient-ax78061900e3f3d7.yaml000066400000000000000000000004371361143263300310030ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/12/12.2.0-add-new-identity-clients-3c3afd674a395bde.yaml000066400000000000000000000006001361143263300301040ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/12/12.2.0-clients_module-16f3025f515bf9ec.yaml000066400000000000000000000017211361143263300261510ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/12/12.2.0-nova_cert_default-90eb7c1e3cde624a.yaml000066400000000000000000000005041361143263300267600ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/12/12.2.0-plugin-service-client-registration-00b19a2dd4935ba0.yaml000066400000000000000000000010361361143263300320400ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/12/12.2.0-remove-javelin-276f62d04f7e4a1d.yaml000066400000000000000000000003151361143263300260640ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/12/12.2.0-service_client_config-8a1d7b4de769c633.yaml000066400000000000000000000004031361143263300274720ustar00rootroot00000000000000--- 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 initialization of a service client. tempest-23.0.0/releasenotes/notes/12/12.2.0-volume-clients-as-library-9a3444dd63c134b3.yaml000066400000000000000000000010101361143263300300540ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/13/000077500000000000000000000000001361143263300173235ustar00rootroot0000000000000013.0.0-add-new-identity-clients-as-library-5f7ndha733nwdsn9.yaml000066400000000000000000000006611361143263300323520ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/13--- 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-23.0.0/releasenotes/notes/13/13.0.0-add-volume-clients-as-a-library-d05b6bc35e66c6ef.yaml000066400000000000000000000006731361143263300312670ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/13/13.0.0-deprecate-get_ipv6_addr_by_EUI64-4673f07677289cf6.yaml000066400000000000000000000002041361143263300307610ustar00rootroot00000000000000--- deprecations: - Oslo.utils provides same method get_ipv6_addr_by_EUI64, so deprecate it in Newton and remove it in Ocata. 13.0.0-move-call-until-true-to-tempest-lib-c9ea70dd6fe9bd15.yaml000066400000000000000000000003171361143263300321420ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/13--- 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-23.0.0/releasenotes/notes/13/13.0.0-start-of-newton-support-3ebb274f300f28eb.yaml000066400000000000000000000007621361143263300300000ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/13/13.0.0-tempest-cleanup-nostandalone-39df2aafb2545d35.yaml000066400000000000000000000003221361143263300310050ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/13/13.0.0-volume-clients-as-library-660811011be29d1a.yaml000066400000000000000000000003261361143263300277730ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/14/000077500000000000000000000000001361143263300173245ustar00rootroot0000000000000014.0.0-add-cred-provider-abstract-class-to-lib-70ff513221f8a871.yaml000066400000000000000000000004401361143263300323050ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/14--- 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-23.0.0/releasenotes/notes/14/14.0.0-add-cred_client-to-tempest.lib-4d4af33f969c576f.yaml000066400000000000000000000003571361143263300310370ustar00rootroot00000000000000--- 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. 14.0.0-add-error-code-translation-to-versions-clients-acbc78292e24b014.yaml000066400000000000000000000004771361143263300341330ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/14--- 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-23.0.0/releasenotes/notes/14/14.0.0-add-image-clients-af94564fb34ddca6.yaml000066400000000000000000000003161361143263300265560ustar00rootroot00000000000000--- 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) 14.0.0-add-role-assignments-client-as-a-library-d34b4fdf376984ad.yaml000066400000000000000000000003741361143263300327400ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/14--- 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-23.0.0/releasenotes/notes/14/14.0.0-add-service-provider-client-cbba77d424a30dd3.yaml000066400000000000000000000001741361143263300305620ustar00rootroot00000000000000--- features: - A Neutron Service Providers client is added to deal with resources of the '/service-providers' route. tempest-23.0.0/releasenotes/notes/14/14.0.0-add-ssh-port-parameter-to-client-6d16c374ac4456c1.yaml000066400000000000000000000002501361143263300312340ustar00rootroot00000000000000--- 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. tempest-23.0.0/releasenotes/notes/14/14.0.0-deprecate-nova-api-extensions-df16b02485dae203.yaml000066400000000000000000000005601361143263300307720ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/14/14.0.0-move-cinder-v3-to-lib-service-be3ba0c20753b594.yaml000066400000000000000000000003211361143263300305040ustar00rootroot00000000000000features: - | 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-23.0.0/releasenotes/notes/14/14.0.0-new-volume-limit-client-517c17d9090f4df4.yaml000066400000000000000000000001051361143263300275530ustar00rootroot00000000000000--- features: - The volume_limits client was added to tempest.lib. tempest-23.0.0/releasenotes/notes/14/14.0.0-remo-stress-tests-81052b211ad95d2e.yaml000066400000000000000000000010551361143263300264730ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/14/14.0.0-remove-baremetal-tests-65186d9e15d5b8fb.yaml000066400000000000000000000002151361143263300275450ustar00rootroot00000000000000--- upgrade: - All tests for the Ironic project have been removed from Tempest. Those exist as a Tempest plugin in the Ironic project. tempest-23.0.0/releasenotes/notes/14/14.0.0-remove-bootable-option-024f8944c056a3e0.yaml000066400000000000000000000004331361143263300273670ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/14/14.0.0-remove-negative-test-generator-1653f4c0f86ccf75.yaml000066400000000000000000000001531361143263300312100ustar00rootroot00000000000000--- upgrade: - The Negative Tests Generator has been removed (it was not used by any Tempest tests). tempest-23.0.0/releasenotes/notes/14/14.0.0-remove-sahara-tests-1532c47c7df80e3a.yaml000066400000000000000000000002461361143263300270350ustar00rootroot00000000000000--- 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. tempest-23.0.0/releasenotes/notes/14/14.0.0-volume-clients-as-library-309030c7a16e62ab.yaml000066400000000000000000000005521361143263300300570ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/15/000077500000000000000000000000001361143263300173255ustar00rootroot0000000000000015.0.0-add-identity-v3-clients-as-a-library-d34b4fdf376984ad.yaml000066400000000000000000000003641361143263300320110ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/15--- 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. tempest-23.0.0/releasenotes/notes/15/15.0.0-add-image-clients-tests-49dbc0a0a4281a77.yaml000066400000000000000000000003531361143263300275430ustar00rootroot00000000000000--- 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) 15.0.0-add-implied-roles-to-roles-client-library-edf96408ad9ba82e.yaml000066400000000000000000000003251361143263300332130ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/15--- 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). 15.0.0-add-snapshot-manage-client-as-library-a76ffdba9d8d01cb.yaml000066400000000000000000000003641361143263300325230ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/15--- 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) 15.0.0-deprecate-allow_port_security_disabled-option-2d3d87f6bd11d03a.yaml000066400000000000000000000004621361143263300343400ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/15--- 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. 15.0.0-deprecate-identity-feature-enabled.reseller-84800a8232fe217f.yaml000066400000000000000000000004151361143263300333450ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/15--- 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. 15.0.0-deprecate-volume_feature_enabled.volume_services-dbe024ea067d5ab2.yaml000066400000000000000000000004271361143263300350520ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/15--- 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. tempest-23.0.0/releasenotes/notes/15/15.0.0-jsonschema-validator-2377ba131e12d3c7.yaml000066400000000000000000000002531361143263300271660ustar00rootroot00000000000000--- features: - Added customized JSON schema format checker for 'date-time' format. Compute response schema will be validated against customized format checker. 15.0.0-remove-deprecated-compute-microversion-config-options-eaee6a7d2f8390a8.yaml000066400000000000000000000005111361143263300357500ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/15--- 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 15.0.0-remove-deprecated-compute-validation-config-options-e3d1b89ce074d71c.yaml000066400000000000000000000023021361143263300352760ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/15--- 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. 15.0.0-remove-deprecated-input-scenario-config-options-414e0c5442e967e9.yaml000066400000000000000000000003531361143263300342250ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/15--- 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. 15.0.0-remove-deprecated-network-config-options-f9ce276231578fe6.yaml000066400000000000000000000010341361143263300330430ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/15--- 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`` tempest-23.0.0/releasenotes/notes/16/000077500000000000000000000000001361143263300173265ustar00rootroot0000000000000016.0.0-add-OAUTH-Consumer-Client-tempest-tests-db1df7aae4a9fd4e.yaml000066400000000000000000000001401361143263300326750ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/16--- features: - Add a new client to handle the OAUTH consumers feature from the identity API. 16.0.0-add-additional-methods-to-roles-client-library-178d4a6000dec72d.yaml000066400000000000000000000003161361143263300340370ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/16--- 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. 16.0.0-add-cascade-parameter-to-volumes-client-ff4f7f12795003a4.yaml000066400000000000000000000004161361143263300324670ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/16--- 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". 16.0.0-add-compute-server-evaculate-client-as-a-library-ed76baf25f02c3ca.yaml000066400000000000000000000001441361143263300345110ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/16--- features: - Define the compute server evacuate client method in the servers_client library. tempest-23.0.0/releasenotes/notes/16/16.0.0-add-content-type-without-spaces-b2c9b91b257814f3.yaml000066400000000000000000000007571361143263300312360ustar00rootroot00000000000000--- 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 current 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`` tempest-23.0.0/releasenotes/notes/16/16.0.0-add-list-auth-project-client-5905076d914a3943.yaml000066400000000000000000000003531361143263300302360ustar00rootroot00000000000000--- 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. tempest-23.0.0/releasenotes/notes/16/16.0.0-add-list-glance-api-versions-ec5fc8081fc8a0ae.yaml000066400000000000000000000002531361143263300307330ustar00rootroot00000000000000--- features: - | Add versions_client module for image service. This new module provides list_versions() method which shows API versions from Image service. 16.0.0-add-list-security-groups-by-servers-to-servers-client-library-088df48f6d81f4be.yaml000066400000000000000000000002761361143263300372040ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/16--- 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. 16.0.0-add-list-version-to-identity-client-944cb7396088a575.yaml000066400000000000000000000002611361143263300315770ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/16--- features: - | Add versions_client module for identity service. This new module provides list_versions() method which shows API versions from Identity service. tempest-23.0.0/releasenotes/notes/16/16.0.0-add-list-version-to-volume-client-4769dd1bd4ab9c5e.yaml000066400000000000000000000002551361143263300317040ustar00rootroot00000000000000--- features: - | Add versions_client module for volume service. This new module provides list_versions() method which shows API versions from Volume service. tempest-23.0.0/releasenotes/notes/16/16.0.0-add-quota-sets-detail-kwarg-74b72183295b3ce7.yaml000066400000000000000000000003451361143263300302230ustar00rootroot00000000000000--- 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. tempest-23.0.0/releasenotes/notes/16/16.0.0-add-tempest-lib-remote-client-adbeb3f42a36910b.yaml000066400000000000000000000004161361143263300310130ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/16/16.0.0-add-tempest-run-combine-option-e94c1049ba8985d5.yaml000066400000000000000000000003131361143263300310320ustar00rootroot00000000000000--- 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 16.0.0-add-update-encryption-type-to-encryption-types-client-f3093532a0bcf9a1.yaml000066400000000000000000000003111361143263300354560ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/16--- 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. 16.0.0-add-volume-manage-client-as-library-78ab198a1dc1bd41.yaml000066400000000000000000000004751361143263300317510ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/16--- 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-23.0.0/releasenotes/notes/16/16.0.0-create-server-tags-client-8c0042a77e859af6.yaml000066400000000000000000000003701361143263300300560ustar00rootroot00000000000000--- features: - | Add server tags APIs to the servers_client library. This feature enables the possibility of updating, deleting and checking existence of a tag on a server, as well as updating and deleting all tags on a server. tempest-23.0.0/releasenotes/notes/16/16.0.0-deprecate-deactivate_image-config-7a282c471937bbcb.yaml000066400000000000000000000003211361143263300316120ustar00rootroot00000000000000--- 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. 16.0.0-deprecate-dvr_extra_resources-config-8c319d6dab7f7e5c.yaml000066400000000000000000000003141361143263300325250ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/16--- 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. 16.0.0-deprecate-glance-api-version-config-options-8370b63aea8e14cf.yaml000066400000000000000000000004701361143263300335210ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/16--- 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 tempest-23.0.0/releasenotes/notes/16/16.0.0-deprecate-resources-prefix-option-ad490c0a30a0266b.yaml000066400000000000000000000006311361143263300316660ustar00rootroot00000000000000--- 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 identify resources which are created by Tempest and no projects set this option on OpenStack dev community. 16.0.0-deprecate-skip_unless_attr-decorator-450a1ed727494724.yaml000066400000000000000000000003011361143263300321470ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/16--- deprecations: - The ``skip_unless_attr`` decorator in lib/decorators.py has been deprecated, please use the standard ``testtools.skipUnless`` and ``testtools.skipIf`` decorators. 16.0.0-deprecate-skip_unless_config-decorator-64c32d588043ab12.yaml000066400000000000000000000003641361143263300325210ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/16--- 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-23.0.0/releasenotes/notes/16/16.0.0-deprecated-cinder-api-v1-option-df7d5a54d93db5cf.yaml000066400000000000000000000005641361143263300313470ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/16/16.0.0-dreprecate_client_parameters-cb8d069e62957f7e.yaml000066400000000000000000000003441361143263300310730ustar00rootroot00000000000000--- 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 16.0.0-fix-volume-v2-service-clients-bugfix-1667354-73d2c3c8fedc08bf.yaml000066400000000000000000000002171361143263300331470ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/16--- fixes: - | Fix below volume v2 service clients to make v2 API call: Bug#1667354 - SchedulerStatsClient - CapabilitiesClient tempest-23.0.0/releasenotes/notes/16/16.0.0-mitaka-eol-88ff8355fff81b55.yaml000066400000000000000000000007401361143263300252230ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/16/16.0.0-remove-call_until_true-of-test-de9c13bc8f969921.yaml000066400000000000000000000003741361143263300312320ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/16/16.0.0-remove-cinder-v1-api-tests-71e266b8d55d475f.yaml000066400000000000000000000003141361143263300300740ustar00rootroot00000000000000upgrade: - 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. 16.0.0-remove-deprecated-allow_port_security_disabled-option-d0ffaeb2e7817707.yaml000066400000000000000000000002221361143263300360070ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/16--- upgrade: - | The deprecated config option ``allow_port_security_disabled`` from ``compute_feature_enabled`` group has been removed. 16.0.0-remove-deprecated-compute-validation-config-options-part-2-5cd17b6e0e6cb8a3.yaml000066400000000000000000000010441361143263300364600ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/16--- 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``) 16.0.0-remove-deprecated-dvr_extra_resources-option-e8c441c38eab7ddd.yaml000066400000000000000000000006521361143263300343040ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/16--- 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. 16.0.0-remove-deprecated-identity-reseller-option-4411c7e3951f1094.yaml000066400000000000000000000001651361143263300332140ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/16--- upgrade: - | The deprecated config option 'reseller' from identity_feature_enabled group has been removed. tempest-23.0.0/releasenotes/notes/16/16.0.0-remove-sahara-service-available-44a642aa9c634ab4.yaml000066400000000000000000000006061361143263300312460ustar00rootroot00000000000000--- 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. 16.0.0-remove-volume_feature_enabled.volume_services-c6aa142cc1021297.yaml000066400000000000000000000002051361143263300341630ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes/16--- upgrade: - | The deprecated ``volume_services`` option in the ``volume_feature_enabled`` section has now been removed. tempest-23.0.0/releasenotes/notes/16/16.0.0-use-keystone-v3-api-935860d30ddbb8e9.yaml000066400000000000000000000002401361143263300267030ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/16/16.0.0-volume-transfers-client-e5ed3f5464c0cdc0.yaml000066400000000000000000000012761361143263300301010ustar00rootroot00000000000000--- 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) tempest-23.0.0/releasenotes/notes/Extend-cleanup-CLI-to-delete-regions-9f1dbda2c8de12e2.yaml000066400000000000000000000003371361143263300312240ustar00rootroot00000000000000--- features: - | tempest cleanup CLI is extended about region deletion. Until now, the regions have been neglected by tempest cleanup. From now on, tempest cleanup is able to delete leftover regions as well. Placement-client-for-placement-based-minimum-bw-allocation-27ed0938118752b6.yaml000066400000000000000000000012121361143263300351000ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- features: - | Add basic read-only Placement client to Tempest to make possible the testing of the placement based bandwidth allocation feature. The following API calls are available for tempest from now: * GET /allocation_candidates * GET /allocations/{consumer_uuid} Add new config group ``placement``, with the config options: * ``endpoint_type`` to use for communication with placement service. * ``catalog_type`` of the placement service. * ``region`` as the placement region name to use. * ``min_microversion`` and ``max_microversion`` as the range between placement API requests are sent. QoS-client-for-placement-based-minimum-bw-allocation-8e5854d5754cec68.yaml000066400000000000000000000014671361143263300340650ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- features: - | Add ``qos-policies`` and ``qos-minimum-bandwidth-rule`` clients to Tempest to make possible the testing of the placement based bandwidth allocation feature. The following API calls are available for tempest from now: ``QoS policies`` client: * GET /qos/policies * POST /qos/policies * GET /qos/policies/{policy_id} * PUT /qos/policies/{policy_id} * DELETE /qos/policies/{policy_id} ``QoS minimum bandwidth rules`` client: * GET qos/policies/{policy_id}/minimum_bandwidth_rules * POST /qos/policies/{policy_id}/minimum_bandwidth_rules * GET qos/policies/{policy_id}/minimum_bandwidth_rules/{rule_id} * PUT qos/policies/{policy_id}/minimum_bandwidth_rules/{rule_id} * DELETE /qos/policies/{policy_id}/minimum_bandwidth_rules/{rule_id} tempest-23.0.0/releasenotes/notes/add-OAUTH-Token-Client-tempest-tests-6351eda451b95a86.yaml000066400000000000000000000001341361143263300306400ustar00rootroot00000000000000--- features: - Add a new client to handle the OAUTH token feature from the identity API. add-additional-methods-to-policy-client-library-b8279c18335588c9.yaml000066400000000000000000000003541361143263300330620ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- features: - | Add ``v3-ext/OS-ENDPOINT-POLICY`` API calls to support creation, deletion and retrieval of associations between service endpoints and policies. Such associations enable an endpoint to request its policy. tempest-23.0.0/releasenotes/notes/add-compute-feature-serial-console-45583c4341e34fc9.yaml000066400000000000000000000004171361143263300306210ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/add-create-group-from-src-tempest-tests-9eb8b0b4b5c52055.yaml000066400000000000000000000001451361143263300316640ustar00rootroot00000000000000--- features: - | Add create_group_from_source to groups_client in the volume service library. add-domain-configuration-client-tempest-tests-e383efabdbb9ad03.yaml000066400000000000000000000001621361143263300333500ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- features: - | Add a new client to handle the domain configuration feature from the identity v3 API. tempest-23.0.0/releasenotes/notes/add-domain-param-in-cliclient-a270fcf35c8f09e6.yaml000066400000000000000000000007661361143263300277430ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/add-extra-apis-to-volume-v3-services-client-bf9b235cf5a611fe.yaml000066400000000000000000000002701361143263300325120ustar00rootroot00000000000000--- features: - | Add ``enable_service``, ``disable_service`` , ``disable_log_reason``, ``freeze_host`` and ``thaw_host`` API endpoints to volume v3 ``services_client``. tempest-23.0.0/releasenotes/notes/add-floating-ip-config-option-e5774bf77702ce9f.yaml000066400000000000000000000003131361143263300277300ustar00rootroot00000000000000--- 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. add-force-detach-volume-to-volumes-client-library-b2071f2954f8e8b1.yaml000066400000000000000000000003641361143263300334560ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- 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. add-group-type-specs-apis-to-v3-group-types-client-10390b52dedede54.yaml000066400000000000000000000004001361143263300336120ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- features: - | Add group type specs APIs to v3 group_types_client library. * create_or_update_group_type_specs * list_group_type_specs * show_group_type_specs_item * update_group_type_specs_item * delete_group_type_specs_item add-identity-v3-clients-for-os-ep-filter-api-endpoint-groups-3518a90bbb731d0f.yaml000066400000000000000000000002661361143263300354520ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- 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. add-identity-v3-clients-for-os-ep-filter-api-extensions-9cfd217fd2c6a61f.yaml000066400000000000000000000002531361143263300346650ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- features: - | Defines the identity v3 OS-EP-FILTER extension API client. This client manages associations between endpoints, projects along with groups. tempest-23.0.0/releasenotes/notes/add-immutable-user-source-support-dd17772a997075e0.yaml000066400000000000000000000010371361143263300305460ustar00rootroot00000000000000--- features: - | Add a new config setting ``immutable_user_source`` in the ``[identity-feature-enabled]`` group that defaults to false. This setting, combined with the usage of the ``@testtools.skipIf()`` decorator, will allow tests that require user creation, deletion, or modification to skip instead of failing in environments that are LDAP-backed. In such environments, the user source is read-only, so this feature flag is needed to allow such tests to gracefully skip without having to blacklist them. tempest-23.0.0/releasenotes/notes/add-ip-version-check-in-addresses-x491ac6d9abaxa12.yaml000066400000000000000000000001671361143263300307350ustar00rootroot00000000000000--- fixes: Add more accurate ip version check in addresses schema which will limit the ip version value in [4, 6]. tempest-23.0.0/releasenotes/notes/add-is-resource-deleted-sg-client-f4a7a7a54ff024d7.yaml000066400000000000000000000001731361143263300305400ustar00rootroot00000000000000--- features: - | Implement the `rest_client` method `is_resource_deleted` in the network security group client. tempest-23.0.0/releasenotes/notes/add-kwargs-to-delete-vol-of-vol-client-1ecde75beb62933c.yaml000066400000000000000000000003361361143263300315220ustar00rootroot00000000000000--- 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. add-list-volume-transfers-with-detail-to-transfers-client-80169bf78cf4fa66.yaml000066400000000000000000000002631361143263300352670ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- 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-23.0.0/releasenotes/notes/add-load-list-cmd-35a4a2e6ea0a36fd.yaml000066400000000000000000000003551361143263300255100ustar00rootroot00000000000000--- 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. tempest-23.0.0/releasenotes/notes/add-manage-snapshot-ref-config-option-67efd04897335b67.yaml000066400000000000000000000004061361143263300312210ustar00rootroot00000000000000--- 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. add-migrate-volume-and-list-hosts-to-v3-volume-client-library-ad3529260db58f00.yaml000066400000000000000000000004531361143263300355600ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- features: - | Add list host API support to the volume v3 client library. This feature enables callers to list all hosts for a given project. - | Add migrate volume API support to the volume v3 client library. This features allows callers to migrate volumes between backends. tempest-23.0.0/releasenotes/notes/add-params-to-identity-v3-list-endpoints-958a155be4e17e5b.yaml000066400000000000000000000003431361143263300317670ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/add-params-to-v2-list-backups-api-c088d2b4bfe90247.yaml000066400000000000000000000003371361143263300303320ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/add-port-profile-config-option-2610b2fa67027960.yaml000066400000000000000000000006311361143263300276730ustar00rootroot00000000000000--- features: - A new config option 'port_profile' is added to the section 'network' to specify capabilities of the port. By default this is set to {}. When using OVS HW offload feature we need to create Neutron port with a certain capability. This is done by creating Neutron port with binding profile. To be able to test this we need profile capability support in Tempest as well. tempest-23.0.0/releasenotes/notes/add-profiler-config-options-db7c4ae6d338ee5c.yaml000066400000000000000000000006731361143263300277370ustar00rootroot00000000000000--- features: - | Add support of `OSProfiler library`_ for profiling and distributed tracing of OpenStack. A new config option ``key`` in section ``profiler`` is added, the option sets the secret key used to enable profiling in OpenStack services. The value needs to correspond to the one specified in [profiler]/hmac_keys option of OpenStack services. .. _OSProfiler library: https://docs.openstack.org/osprofiler/ tempest-23.0.0/releasenotes/notes/add-redirect-param-bea1f6fbce629c70.yaml000066400000000000000000000013371361143263300260520ustar00rootroot00000000000000--- features: - | A new parameter ``follow_redirects`` has been added to the class ``RestClient``, which is passed through to ``ClosingHttp`` or ``ClosingProxyHttp`` respectively. The default value is ``True`` which corresponds to the previous behaviour of following up to five redirections before returning a response. Setting ``follow_redirects = False`` allows to disable this behaviour, so that any redirect that is received is directly returned to the caller. This allows tests to verify that an API is responding with a redirect. fixes: - | [`bug 1616892 `_] Tempest now allows tests to verify that an API responds with a redirect. add-reset-group-snapshot-status-api-to-v3-group-snapshots-client-248d41827daf2a0c.yaml000066400000000000000000000003251361143263300364360ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- 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. add-reset-group-status-api-to-v3-groups-client-9aa048617c66756a.yaml000066400000000000000000000002721361143263300326230ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- 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-23.0.0/releasenotes/notes/add-return-value-to-retype-volume-a401aa619aaa2457.yaml000066400000000000000000000003051361143263300305660ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/add-save-state-option-5ea67858cbaca969.yaml000066400000000000000000000001531361143263300264070ustar00rootroot00000000000000--- features: - | Add ``--save-state`` option to allow saving state of cloud before tempest run. tempest-23.0.0/releasenotes/notes/add-server-diagnostics-validation-schema-b5a3c55b45aa718a.yaml000066400000000000000000000001151361143263300321730ustar00rootroot00000000000000--- features: - | Add validation schema for Nova server diagnostics APIadd-show-api-v3-details-api-to-v3-versions-client-4b408427379cabfe.yaml000066400000000000000000000001771361143263300332170ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- features: - | Add show api version details function to v3 versions_client library for cinder. * show_versionadd-show-default-group-type-detail-api-to-v3-group-types-client-65f717878cf52da0.yaml000066400000000000000000000002011361143263300360350ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- features: - | Add show default group type details API to v3 group_types_client library. * show_default_group_type add-show-default-quotas-api-to-network-quotas-client-3a7c1159af9e56ff.yaml000066400000000000000000000002731361143263300343240ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- features: - | Add show default quotas API to network quotas_client library. This feature enables the possibility to show default network quotas for a specified project. add-show-encryption-specs-item-api-to-v2-encryption-types-client-290b421cd4bc0c0e.yaml000066400000000000000000000003041361143263300364420ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- features: - | Add show encryption specs item API to v2 encryption_types_client library. This feature enables the possibility to show specific encryption specs for a volume type. tempest-23.0.0/releasenotes/notes/add-show-host-to-hosts-client-library-c60c4eb49d139480.yaml000066400000000000000000000002261361143263300312770ustar00rootroot00000000000000--- features: - | Add show host API to the volume v2 hosts_client library. This feature enables the possibility to show details for a host. add-show-quota-details-api-to-network-quotas-client-3fffd302cc5d335f.yaml000066400000000000000000000004041361143263300342020ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- features: - | Add extension API show quota details to network quotas_client library. This feature enables the possibility to show a quota set for a specified project that includes the quota's used, limit and reserved counts per resource. add-show-snapshot-metadata-item-api-to-v2-snapshots-client-bd3cbab3c7f0e0b3.yaml000066400000000000000000000002731361143263300354670ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- 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. add-show-volume-image-metadata-api-to-v2-volumes-client-ee3c027f35276561.yaml000066400000000000000000000002431361143263300343140ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- features: - | Add show volume image metadata API to v2 volumes_client library. This feature enables the possibility to show volume's image metadata. add-show-volume-metadata-item-api-to-v2-volumes-client-47d59ecd999ca9df.yaml000066400000000000000000000002651361143263300345250ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- 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. add-show-volume-summary-api-to-v3-volumes-client-96e7b01abdb5c9c3.yaml000066400000000000000000000005061361143263300334500ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- 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) tempest-23.0.0/releasenotes/notes/add-storyboard-in-skip-because-decorator-3e139aa8a4f7970f.yaml000066400000000000000000000010371361143263300320640ustar00rootroot00000000000000--- features: - | Add a new parameter called ``bug_type`` to ``tempest.lib.decorators.related_bug`` and ``tempest.lib.decorators.skip_because`` decorators, which accepts 2 values: * launchpad * storyboard This offers the possibility of tracking bugs related to tests using launchpad or storyboard references. The default value is launchpad for backward compatibility. Passing in a non-digit ``bug`` value to either decorator will raise a ``InvalidParam`` exception (previously ``ValueError``). tempest-23.0.0/releasenotes/notes/add-support-args-kwargs-in-call-until-true-a91k592h5a64exf7.yaml000066400000000000000000000002631361143263300324450ustar00rootroot00000000000000--- 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 debugging. tempest-23.0.0/releasenotes/notes/add-unstable_test-decorator-a73cf97d4ffcc796.yaml000066400000000000000000000011141361143263300277440ustar00rootroot00000000000000--- features: - | New decorator ``unstable_test`` is added to ``tempest.lib.decorators``. It can be used to mark some test as unstable thus it will be still run by tempest but job will not fail if this test will fail. Such test will be skipped in case of failure. It can be used for example when there is known bug related which cause irregular tests failures. Marking such test as unstable will help other developers to get their job done and still run this test to get additional debug data or to confirm if some potential fix really solved the issue. tempest-23.0.0/releasenotes/notes/add-update-api-to-group-types-client-09c06ccdf80d5003.yaml000066400000000000000000000002031361143263300311310ustar00rootroot00000000000000--- features: - | Add update group types API to v3 ``group_types_client`` library; min_microversion of this API is 3.11. tempest-23.0.0/releasenotes/notes/add-update-backup-api-to-v3-backups-client-e8465b2b66617dc0.yaml000066400000000000000000000004771361143263300320260ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/add-update-flavor--api-to-flavors-client-a859542fe54aab7c.yaml000066400000000000000000000001211361143263300317500ustar00rootroot00000000000000--- features: - | Add update flavor API to compute flavors_client library. tempest-23.0.0/releasenotes/notes/add-update-group-tempest-tests-72f8ec19b2809849.yaml000066400000000000000000000001311361143263300300400ustar00rootroot00000000000000--- features: - | Add update_group to groups_client in the volume service library. tempest-23.0.0/releasenotes/notes/add-validation-resources-to-lib-dc2600c4324ca4d7.yaml000066400000000000000000000004761361143263300302460ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/add-volume-group-snapshots-tempest-tests-840df3da26590f5e.yaml000066400000000000000000000003351361143263300322340ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/add-volume-group-types-tempest-tests-1298ab8cb4fe8b7b.yaml000066400000000000000000000002511361143263300315150ustar00rootroot00000000000000--- 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. tempest-23.0.0/releasenotes/notes/add-volume-groups-tempest-tests-dd7b2abfe2b48427.yaml000066400000000000000000000003001361143263300305210ustar00rootroot00000000000000--- 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. tempest-23.0.0/releasenotes/notes/add-volume-quota-class-client-as-library-c4c2b22c36ff807e.yaml000066400000000000000000000003511361143263300320640ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/add_proxy_url_get_credentials-aef66b085450513f.yaml000066400000000000000000000002751361143263300302130ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/agents-client-delete-method-de1a7fb3f845999c.yaml000066400000000000000000000003151361143263300275550ustar00rootroot00000000000000--- features: - | Adds the new method to AgentsClient that implements agent deletion according to the API [0]. [0] https://developer.openstack.org/api-ref/network/v2/index.html#delete-agent tempest-23.0.0/releasenotes/notes/api_v2_admin_flag-dea5ca9bc2ce63bc.yaml000066400000000000000000000003771361143263300261020ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/bp-application-credentials-df69b1f617db1bb9.yaml000066400000000000000000000007761361143263300275540ustar00rootroot00000000000000--- features: - | [`blueprint application-credentials `_] Tempest can test keystone's application credentials interface. A new client library is added for application credentials, and a new config option, ``[identity-feature-enabled]/application_credentials``, can control whether the application credentials feature is tested (defaults to False, indicating the feature is not enabled in the cloud under test). tempest-23.0.0/releasenotes/notes/bug-1647999-7aeda50a8d082d4c.yaml000066400000000000000000000005301361143263300237570ustar00rootroot00000000000000--- features: - | A new parameter, compute/compute_volume_common_az is introduced to specify availability zone where tempest creates instances and volumes for scenario tests, to allow us to run scenario tests in the deployment which has multiple availability zones and cinder/cross_az_attach in nova.conf is set to False. tempest-23.0.0/releasenotes/notes/bug-1791007-328a8b9a43bfb157.yaml000066400000000000000000000004171361143263300236060ustar00rootroot00000000000000--- fixes: - | Fixed bug #1791007. ``tempest workspace register`` and ``tempest workspace rename`` CLI will error if None or empty string is passed in --name arguments. Earlier both CLI used to accept the None or empty string as name which was confusing. tempest-23.0.0/releasenotes/notes/bug-1799883-6ea95fc64f70c9ef.yaml000066400000000000000000000004701361143263300240120ustar00rootroot00000000000000--- fixes: - | Fixed bug #1799883. ``tempest workspace register`` and ``tempest workspace move`` CLI will now validate the value of the ``--path`` CLI argument and raise an exception if it is None or empty string. Earlier both CLI actions were accepting None or empty path which was confusing. tempest-23.0.0/releasenotes/notes/bug-1808473-54ada26ab78e7b02.yaml000066400000000000000000000005141361143263300236630ustar00rootroot00000000000000--- fixes: - | Fixed bug #1808473. ``tempest run`` CLI will error if a non-exist config file is input to parameter --config-file. Earlier non-exist config value was silently getting ignored and the default config file was used instead which used to give false behavior to the user on using the passed config file. tempest-23.0.0/releasenotes/notes/cinder-use-os-endpoint-type-c11f63fd468ceb4c.yaml000066400000000000000000000003431361143263300276140ustar00rootroot00000000000000--- upgrade: - | Cinder CLI calls have now been updated to use the ``--os-endpoint-type`` option instead of ``--endpoint-type``. The latter had been deprecated in Cinder and has been removed in the Rocky release. tempest-23.0.0/releasenotes/notes/cli-tests-v3fixes-fb38189cefd64213.yaml000066400000000000000000000005701361143263300255060ustar00rootroot00000000000000--- other: - | The CLIClient class, when it calls a command line client, uses ``--os-project-name`` instead of ``--os-tenant-name`` for the project, and passes ``--os-identity-api-version`` (default empty). All CLI clients still available in supported releases of OpenStack which are wrapped by the ``cmd_with_auth()`` method support those switches. tempest-23.0.0/releasenotes/notes/compare-header-version-func-de5139b2161b3627.yaml000066400000000000000000000012421361143263300273230ustar00rootroot00000000000000--- features: - | Add a new function called ``compare_version_header_to_response`` to ``tempest.lib.common.api_version_utils``, which compares the API microversion 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-23.0.0/releasenotes/notes/conditional-attr-a8564ec5a70ec840.yaml000066400000000000000000000003341361143263300254550ustar00rootroot00000000000000--- features: - | The ``tempest.lib.decorators.attr`` decorator now supports a ``condition`` kwarg which can be used to conditionally apply the attr to the test function if the condition evaluates to True. tempest-23.0.0/releasenotes/notes/config-image-api-v1-default-to-false-39d5f2xafc534ab1.yaml000066400000000000000000000004011361143263300311250ustar00rootroot00000000000000--- upgrade: - | Changed the default value of 'api_v1' config option in the 'image-feature-enabled' group to False from True, because glance v1 APIs are deprecated. Please set True explicitly on the option if still testing glance v1 APIs. tempest-23.0.0/releasenotes/notes/config-volume-multiattach-ea8138dfa4fd308c.yaml000066400000000000000000000010761361143263300274350ustar00rootroot00000000000000--- other: - | A new configuration option ``[compute-feature-enabled]/volume_multiattach`` has been added which defaults to False. Set this to True to enable volume multiattach testing. These tests require that compute API version 2.60 is available and block storage API version 3.44 is available. .. note:: In the Queens release, the only compute driver that supports volume multiattach is the libvirt driver, and only then when qemu<2.10 or libvirt>=3.10. The only volume backend in Queens that supports volume multiattach is lvm. tempest-23.0.0/releasenotes/notes/config_image_certificate_compute_feature-c56efb520d54aff5.yaml000066400000000000000000000005721361143263300325660ustar00rootroot00000000000000--- other: - | New configuration options ``[compute]/certified_image_ref`` and ``[compute]/certified_image_trusted_certs`` have been introduced. These are required in order to run the ``ServerShowV263Test`` test and allow a signed image with the required img_signature_* properties set along with a list of trusted certificates to be used during the test. tempest-23.0.0/releasenotes/notes/correct-port-profile-config-option-d67f5cb31f1bc34c.yaml000066400000000000000000000006071361143263300311710ustar00rootroot00000000000000--- fixes: - | Patch https://review.opendev.org/#/c/499575/ introduced support creating Neutron port with certain capabilities. Currently capabilities list interpreted as string this change fix it. tempest.conf [network] port_profile = capabilities:[switchdev] result: {'capabilities':'[switchdev]'} expected: {'capabilities': ['switchdev']} tempest-23.0.0/releasenotes/notes/create-mount-config-drive-to-lib-1a6e912b8afbcc7e.yaml000066400000000000000000000002111361143263300305520ustar00rootroot00000000000000--- features: - A function has been added to the common library to allow mounting and unmounting of the config drive consistently. tempest-23.0.0/releasenotes/notes/credentials-factory-stable-c8037bd9ae642482.yaml000066400000000000000000000007231361143263300273410ustar00rootroot00000000000000--- 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. deprecate-compute-images-client-in-volume-tests-92b6dd55fcaba620.yaml000066400000000000000000000005561361143263300334600ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- 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-23.0.0/releasenotes/notes/deprecate-config-forbid_global_implied_dsr-e64cfa66e6e3ded5.yaml000066400000000000000000000003631361143263300327720ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/deprecate-default-value-for-v3_endpoint_type-fb9e47c5ba1c719d.yaml000066400000000000000000000003211361143263300331050ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/deprecate-dns_servers-option-0xf2f297ee47a5ff.yaml000066400000000000000000000002431361143263300301740ustar00rootroot00000000000000--- deprecations: - | The config option ``CONF.network.dns_servers`` is no longer used anywhere, so it is deprecated and will be removed in the future. tempest-23.0.0/releasenotes/notes/deprecate-scheduler-available-filters-cbca2017ba3cf2aa.yaml000066400000000000000000000013751361143263300317510ustar00rootroot00000000000000--- deprecations: - | The ``scheduler_available_filters`` option is being deprecated in favor of ``scheduler_enabled_filters``. The new name is more indicative of what the option means. ``scheduler_enabled_filters``'s default value is set to the default value of Nova's ``enabled_filters``. ``scheduler_available_filters``'s default was `all`. There was confusion around this value. Sometimes it was understood to mean the default Nova filters are enabled, other times it was understood to mean all filters are enabled. While `all` is still allowed for ``scheduler_enabled_filters`` for backwards compatibility, it is strongly recommended to provide an explicit list of filters that matches what's configured in nova.conf. tempest-23.0.0/releasenotes/notes/deprecate-vnc-server-header-529f07d592aefb62.yaml000066400000000000000000000006341361143263300274650ustar00rootroot00000000000000--- deprecations: - | The config option ``CONF.compute.vnc_server_header`` is deprecated because it has become obsolete with the usage of different response header fields to accomplish the same goal in accordance with RFC7231 Section 6.2.2. fixes: - | Adequately validates WebSocket upgrade in test_novnc and removes unneeded configuration complexity. Closes bug #1838777 and #1840788. deprecate-volume-api-selection-config-options-b95c5c0ccbf38916.yaml000066400000000000000000000015401361143263300331350ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- deprecations: - | The v2 volume API has been deprecated since Pike release. Volume v3 API is current and Tempest volume tests can be run against v2 or v3 API based on config option ``CONF.volume.catalog_type``. If catalog_type is ``volumev2``, then all the volume tests will run against v2 API. If catalog_type is ``volumev3`` which is default in Tempest, then all the volume tests will run against v3 API. That makes below config options unusable in Tempest which used to select the target volume API for volume tests. * ``CONF.volume-feature-enabled.api_v2`` * ``CONF.volume-feature-enabled.api_v3`` Tempest deprecate the above two config options in Rocky release and will be removed in future. Alternatively ``CONF.volume.catalog_type`` can be used to run the Tempest against volume v2 or v3 API. tempest-23.0.0/releasenotes/notes/disable-identity-v2-testing-4ef1565d1a5aedcf.yaml000066400000000000000000000004601361143263300276510ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/drop-DEFAULT_PARAMS-bfcc2e7b74ef880b.yaml000066400000000000000000000010341361143263300255160ustar00rootroot00000000000000--- 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. tempest-23.0.0/releasenotes/notes/enable-volume-multiattach-fd5e9bf0e96b56ce.yaml000066400000000000000000000010011361143263300274770ustar00rootroot00000000000000--- upgrade: - | The ``tempest-full``, ``tempest-full-py3`` and ``tempest-slow`` zuul v3 job configurations now set ``ENABLE_VOLUME_MULTIATTACH: true`` in the ``devstack_localrc`` variables section. If you have a plugin job configuration that inherits from one of these jobs and the backend cinder volume driver or nova compute driver do not support volume multiattach then you should override and set this variable to ``ENABLE_VOLUME_MULTIATTACH: false`` in your job configuration. tempest-23.0.0/releasenotes/notes/extra-compute-services-tests-92b6c0618972e02f.yaml000066400000000000000000000003171361143263300276240ustar00rootroot00000000000000--- 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. tempest-23.0.0/releasenotes/notes/fix-1847749-2670b1d4f6097a1a.yaml000066400000000000000000000004001361143263300235370ustar00rootroot00000000000000--- fixes: - | Bug#1847749. This provides the workaround of Skip Exception raised instead of skipping the CLI tests. If you are running Tempest with stestr > 2.5.0 then use this fix. Ref- https://github.com/testing-cabal/testtools/issues/272 tempest-23.0.0/releasenotes/notes/fix-credential-logging-98089c897d801355.yaml000066400000000000000000000006231361143263300262530ustar00rootroot00000000000000--- features: - | A new kwarg, ``log_req_body``, was added to the ``tempest.lib.common.rest_client.RestClient`` method ``raw_request()``. This kwarg takes in a string which will be used in place of the request body, which is logged by default. The intent of this option is to be used for security reasons to avoid logging sensitive information that are part of request bodies. tempest-23.0.0/releasenotes/notes/fix-list-group-snapshots-api-969d9321002c566c.yaml000066400000000000000000000003151361143263300274440ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/fix-remoteclient-default-ssh-shell-prologue-33e99343d086f601.yaml000066400000000000000000000003111361143263300324140ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/fix-show-image-file-expected-code-92d97342d0f6d60e.yaml000066400000000000000000000002711361143263300304000ustar00rootroot00000000000000--- fixes: - | Fix show_image_file interface in v2 ImagesClient: Bug#1756264. The expected success code of show_image_file is changed from ``200`` to ``[200, 204, 206]``. tempest-23.0.0/releasenotes/notes/http_proxy_config-cb39b55520e84db5.yaml000066400000000000000000000007261361143263300257530ustar00rootroot00000000000000--- 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 tempest-23.0.0/releasenotes/notes/identity-tests-domain-drivers-76235f6672221e45.yaml000066400000000000000000000004061361143263300276200ustar00rootroot00000000000000--- 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. tempest-23.0.0/releasenotes/notes/identity-token-client-8aaef74b1d61090a.yaml000066400000000000000000000002271361143263300264750ustar00rootroot00000000000000--- features: - | Add additional API endpoints to the identity v2 client token API: - list_endpoints_for_token - check_token_existence tempest-23.0.0/releasenotes/notes/identity-v3-project-tags-client-36683c6a8644e54b.yaml000066400000000000000000000005111361143263300301030ustar00rootroot00000000000000--- features: - | Add ``project_tags_client`` to the identity v3 library. This feature enables the possibility of invoking the following API actions: * update_project_tag * list_project_tags * update_all_project_tags * check_project_tag_existence * delete_project_tag * delete_all_project_tags tempest-23.0.0/releasenotes/notes/identity_client-635275d43abbb807.yaml000066400000000000000000000002311361143263300253010ustar00rootroot00000000000000--- features: - | Enhances the v3 identity client with the ``check_token_existence`` endpoint, allowing users to check the existence of tokens tempest-23.0.0/releasenotes/notes/intermediate-pike-release-2ce492432ff8f012.yaml000066400000000000000000000002351361143263300271360ustar00rootroot00000000000000--- prelude: > This is an intermediate release during the Pike development cycle to make new functionality available to plugins and other consumers. tempest-23.0.0/releasenotes/notes/intermediate-queens-release-2f9f305775fca454.yaml000066400000000000000000000002371361143263300275220ustar00rootroot00000000000000--- prelude: > This is an intermediate release during the Queens development cycle to make new functionality available to plugins and other consumers. tempest-23.0.0/releasenotes/notes/intermediate-train-release-0146c624cff9a57f.yaml000066400000000000000000000005111361143263300274060ustar00rootroot00000000000000--- prelude: | This is an intermediate release during the Train development cycle to mark the end of support for Pike in Tempest. After this release, Tempest will support below OpenStack Releases: * Stein * Rocky * Queens Current development of Tempest is for OpenStack Train development cycle. tempest-23.0.0/releasenotes/notes/intermediate-ussuri-release-8aebeca312a6718c.yaml000066400000000000000000000006371361143263300277600ustar00rootroot00000000000000--- prelude: > This is an intermediate release during the Ussuri development cycle to mark the end of support for EM Queens in Tempest. After this release, Tempest will support below OpenStack Releases: * Train * Stein * Rocky Current development of Tempest is for OpenStack Ussuri development cycle. This is the last release of Tempest to officially support python2.7. tempest-23.0.0/releasenotes/notes/lib_api_microversion_fixture-f52308fc6b6b89f2.yaml000066400000000000000000000004441361143263300301610ustar00rootroot00000000000000--- features: - | New library interface to set the API microversion on Service Clients. ``APIMicroversionFixture,`` can be used to set the API microversion on multiple services. This Fixture will take care of reseting the service microversion to None once test is finished. tempest-23.0.0/releasenotes/notes/list-auth-domains-v3-endpoint-9ec60c7d3011c397.yaml000066400000000000000000000003001361143263300276200ustar00rootroot00000000000000--- 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. make-object-storage-client-as-stable-interface-d1b07c7e8f17bef6.yaml000066400000000000000000000004611361143263300331770ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- 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 tempest-23.0.0/releasenotes/notes/migrate-dynamic-creds-ecebb47528080761.yaml000066400000000000000000000002451361143263300262730ustar00rootroot00000000000000--- features: - | The tempest module tempest.common.dynamic creds which is used for dynamically allocating credentials has been migrated into tempest lib. tempest-23.0.0/releasenotes/notes/migrate-object-storage-as-stable-interface-42014c7b43ecb254.yaml000066400000000000000000000004501361143263300322500ustar00rootroot00000000000000--- 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 tempest-23.0.0/releasenotes/notes/migrate-preprov-creds-ef61a046ee1ec604.yaml000066400000000000000000000011531361143263300264740ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/move-attr-decorator-to-lib-a1e80c42ba9c5392.yaml000066400000000000000000000003551361143263300272600ustar00rootroot00000000000000--- 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. tempest-23.0.0/releasenotes/notes/move-related_bug-decorator-to-lib-dbfd5c543bbb2805.yaml000066400000000000000000000003061361143263300307160ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/move-volume-v3-base_client-to-volume-1edbz0f207c3b283.yaml000066400000000000000000000011711361143263300312670ustar00rootroot00000000000000features: - | 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-23.0.0/releasenotes/notes/network-show-version-18e1707a4df0a3d3.yaml000066400000000000000000000004151361143263300263220ustar00rootroot00000000000000--- features: - | Add ``show_version`` function to the ``NetworkVersionsClient`` client. This allows the possibility of getting details for Networking API. .. API reference: https://developer.openstack.org/api-ref/network/v2/index.html#show-api-v2-details tempest-23.0.0/releasenotes/notes/network-tag-client-f4614029af7927f0.yaml000066400000000000000000000003411361143263300255630ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/omit_X-Subject-Token_from_log-1bf5fef88c80334b.yaml000066400000000000000000000004021361143263300300610ustar00rootroot00000000000000--- security: - | The x-subject-token of a response header is ommitted from log, but clients specify the same token on a request header on Keystone API and that was not omitted. In this release, that has been omitted for a security reason. tempest-23.0.0/releasenotes/notes/pause_teardown-45c9d60ffa889f7f.yaml000066400000000000000000000005331361143263300253320ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/plugin-client-registration-enhancements-e09131742391225b.yaml000066400000000000000000000010421361143263300316160ustar00rootroot00000000000000--- 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. prevent-error-in-parse-resp-when-nullable-list-9898cd0f22180986.yaml000066400000000000000000000003031361143263300327220ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- 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. raise-exception-when-error-deleting-on-volume-18d0d0c5886212dd.yaml000066400000000000000000000005421361143263300327340ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- 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-23.0.0/releasenotes/notes/removal-deprecated-config-options-3db535b979fe3509.yaml000066400000000000000000000006251361143263300306400ustar00rootroot00000000000000--- upgrade: - | Below config options or feature flags were deprecated for removal. It's time to remove them as all supported stable branches are good to handle them. * ``[identity-feature-enabled].forbid_global_implied_dsr`` * ``[image-feature-enabled].deactivate_image`` * ``[default].resources_prefix`` * config group ``orchestration`` * ``[service_available].heat`` tempest-23.0.0/releasenotes/notes/removal-deprecated-volume-config-options-21c4412f3c600923.yaml000066400000000000000000000014061361143263300317470ustar00rootroot00000000000000--- upgrade: - | Below config option was deprecated for removal since juno release. It's time to remove it as all supported stable branches and Tempest plugins are good to handle it. * ``[volume_feature_enabled].api_v1`` Also Tempest removes the below corresponding service clients alias from client.py which were being set based on above removed config option. * self.backups_client * self.encryption_types_client * self.snapshots_client * self.volume_availability_zone_client * self.volume_hosts_client * self.volume_limits_client * self.volume_qos_client * self.volume_quotas_client * self.volume_services_client * self.volume_types_client * self.volumes_client * self.volumes_extension_client tempest-23.0.0/releasenotes/notes/remove-allow_tenant_isolation-option-03f0d998eb498d44.yaml000066400000000000000000000003101361143263300315030ustar00rootroot00000000000000--- upgrade: - | Remove deprecated config option ``allow_tenant_isolation`` from ``auth`` and ``compute`` groups. Use ``use_dynamic_credentials`` directly instead of the removed option. remove-deprecated-apis-from-v2-volumes-client-3ca4a5db5fea518f.yaml000066400000000000000000000004471361143263300331220ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- 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-23.0.0/releasenotes/notes/remove-deprecated-find-test-caller-f4525cd043bfd1b6.yaml000066400000000000000000000004001361143263300307720ustar00rootroot00000000000000--- upgrade: - | ``tempest.lib.common.utils.misc.find_test_caller`` was deprecated during Kilo release cycle in favor of ``tempest.lib.common.utils.test_utils.find_test_caller``. The deprecated version of ``find_test_caller`` is removed. tempest-23.0.0/releasenotes/notes/remove-deprecated-skip-decorators-f8b42d812d20b537.yaml000066400000000000000000000002061361143263300306150ustar00rootroot00000000000000--- upgrade: - | Remove two deprecated skip decorators in ``config`` module: ``skip_unless_config`` and ``skip_if_config``. tempest-23.0.0/releasenotes/notes/remove-deprecated-skip_unless_attr-decorator-02bde59a00328f5c.yaml000066400000000000000000000001421361143263300331270ustar00rootroot00000000000000--- upgrade: - | Remove the deprecated decorator ``skip_unless_attr`` in lib/decorators.py. remove-deprecated-volume-apis-from-v2-volumes-client-cf35e5b4cca89860.yaml000066400000000000000000000004561361143263300343020ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- 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-23.0.0/releasenotes/notes/remove-get-ipv6-addr-by-EUI64-c79972d799c7a430.yaml000066400000000000000000000002121361143263300271260ustar00rootroot00000000000000--- upgrade: - | Remove deprecated get_ipv6_addr_by_EUI64 method from data_utils. Use the same method from oslo_utils.netutils. tempest-23.0.0/releasenotes/notes/remove-heat-tests-9efb42cac3e0b306.yaml000066400000000000000000000002471361143263300257060ustar00rootroot00000000000000--- 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 remove-some-deprecated-auth-and-identity-options-xa1xd9b8fb948g4f.yaml000066400000000000000000000005161361143263300340060ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notesupgrade: - | Remove deprecated config option ``endpoint_type`` from ``identity`` group. Use ``v2_public_endpoint_type`` from ``identity`` group instead. Remove deprecated config option ``tenant_isolation_domain_name`` from ``auth`` group. Use ``default_credentials_domain_name`` from ``auth`` group instead. tempest-23.0.0/releasenotes/notes/remove-some-deprecated-identity-options-0ffxd1b8db928e43.yaml000066400000000000000000000010701361143263300322430ustar00rootroot00000000000000upgrade: - | Remove deprecated config option ``admin_username`` from ``identity`` groups. Use ``admin_username`` from ``auth`` instead. Remove deprecated config option ``admin_tenant_name`` from ``auth`` and ``identity`` groups. Use ``admin_project_name`` from ``auth`` instead. Remove deprecated config option ``admin_password`` from ``identity`` groups. Use ``admin_password`` from ``auth`` instead. Remove deprecated config option ``admin_domain_name`` from ``identity`` groups. Use ``admin_domain_name`` from ``auth`` instead.tempest-23.0.0/releasenotes/notes/remove-support-of-py34-7d59fdb431fefe24.yaml000066400000000000000000000002361361143263300265610ustar00rootroot00000000000000--- deprecations: - | Remove the support of python3.4, because in Ubuntu Xenial only python3.5 is available (python3.4 is restricted to <= Mitaka). tempest-23.0.0/releasenotes/notes/removed-tox-ostestr-8997a93d199c44f3.yaml000066400000000000000000000006651361143263300260530ustar00rootroot00000000000000--- upgrade: - | The tox ostestr job (normally invoked with ``tox -eostestr``) has been removed. This was lightly used, and in the near future ostestr will be removed from the tempest requirements file. If you were relying on this functionality you can replicate it by using the venv-tempest tox job. For example, simply running ``tox -evenv-tempest -- ostestr`` will do the same thing the old ostestr job did. tempest-23.0.0/releasenotes/notes/segments-client-866f02948f40d4ff.yaml000066400000000000000000000005241361143263300252420ustar00rootroot00000000000000--- features: - | Add ``segments`` client to Tempest to make possible the testing of the Routed Provider Networks feature. The following API calls are available for tempest from now: * POST /segments * PUT /segments/{segment_id} * GET /segment/{segment_id} * DELETE /segments/{segment_id} * GET /segments tempest-23.0.0/releasenotes/notes/set-cinder-api-v3-option-true-1b3e61e3129b7c00.yaml000066400000000000000000000002021361143263300275120ustar00rootroot00000000000000--- upgrade: - | The volume config option 'api_v3' default is changed to ``True`` because the volume v3 API is CURRENT. tempest-23.0.0/releasenotes/notes/start-of-pike-support-f2a1b7ea8e8b0311.yaml000066400000000000000000000007671361143263300264570ustar00rootroot00000000000000--- 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-23.0.0/releasenotes/notes/start-of-queens-support-fea9051ba1d85fc7.yaml000066400000000000000000000010641361143263300271110ustar00rootroot00000000000000--- prelude: > This release marks the start of Queens release support in Tempest. This release also marks the end of support for Newton in Tempest. other: - OpenStack Releases supported after this release are **Queens**, **Pike**, and **Ocata**. The release under current development of this tag is Rocky, meaning that every Tempest commit is also tested against master during the Rocky cycle. However, this does not necessarily mean that using Tempest as of this tag will work against a Rocky (or future release) cloud. tempest-23.0.0/releasenotes/notes/subunit_describe_calls-ad7df689b9d63e3f.yaml000066400000000000000000000002271361143263300270740ustar00rootroot00000000000000--- deprecations: - | Deprecated command for subunit-describe-calls features: - | Added new tempest subcommand for subunit-describe-calls tempest-23.0.0/releasenotes/notes/support-microversion-in-scenario-test-b4fbfdd3a977fc58.yaml000066400000000000000000000010031361143263300320430ustar00rootroot00000000000000--- features: - | Add microversion support for scenario tests. Scenario test calls multiple service API within same test and many services like compute, volume and placement etc support API microversion. With microversion support in scenario test, we can call different service API with different microvesion. Which means we can implement scenario tests for microversion also. Currently Scenario manager support below services microversion: * Compute * Volume * Placement tempest-23.0.0/releasenotes/notes/switch-to-stestr-8c9f834b3f5a55d8.yaml000066400000000000000000000012521361143263300254640ustar00rootroot00000000000000--- features: - The Tempest CLI commands have switched from calling testrepository internally to use stestr instead. This means that all of the features and bug fixes from moving to stestr are available to the tempest commands. upgrade: - Tempest CLI commands will no long rely on anything from testr. This means any data in existing testr internals that were being exposed are no longer present. For example things like the .testr directories will be silently ignored. There is a potential incompatibility for existing users who are relying on test results being stored by testr. Anything relying on previous testr behavior will need to be updated to handle stestr. tempest-23.0.0/releasenotes/notes/tempest-default-run_validations-9640c41b6a4a9121.yaml000066400000000000000000000006361361143263300303360ustar00rootroot00000000000000--- upgrade: - | ``CONF.validation.run_validation`` default enabled. This option required to be set ``true`` in order to run api tests stability when the guest cooperation required. For example when the guest needs react on Volume/Interface detach. The ssh test makes sure the VM is alive and ready when the detach needs to happen. The option was enabled on the gate for a long time. tempest-23.0.0/releasenotes/notes/tempest-identity-catalog-client-f5c8589a9d7c1eb5.yaml000066400000000000000000000002541361143263300304770ustar00rootroot00000000000000--- features: - Add a new identity catalog client. At this point, the new client contains a single functionality, "show_catalog", which returns a catalog object. tempest-23.0.0/releasenotes/notes/tempest-lib-compute-update-service-6019d2dcfe4a1c5d.yaml000066400000000000000000000006661361143263300311600ustar00rootroot00000000000000--- features: - | The ``update_service`` API is added to the ``services_client`` compute library. This API is introduced in microversion 2.53 and supersedes the following APIs: * ``PUT /os-services/disable`` (``disable_service``) * ``PUT /os-services/disable-log-reason`` (``disable_log_reason``) * ``PUT /os-services/enable`` (``enable_service``) * ``PUT /os-services/force-down`` (``update_forced_down``) tempest-23.0.0/releasenotes/notes/tempest-rocky-release-0fc3312053923380.yaml000066400000000000000000000010741361143263300261120ustar00rootroot00000000000000--- prelude: | This release is to tag the Tempest for OpenStack Rocky release. After this release, Tempest will support below OpenStack Releases: * Rocky * Queens * Pike * Ocata Current development of Tempest is for OpenStack Stein development cycle. Every Tempest commit is also tested against master during the Stein cycle. However, this does not necessarily mean that using Tempest as of this tag will work against a Stein (or future release) cloud. To be on safe side, use this tag to test the OpenStack Rocky release.tempest-23.0.0/releasenotes/notes/tempest-run-fix-updates-564b41706decbba1.yaml000066400000000000000000000004071361143263300267610ustar00rootroot00000000000000--- features: - | Adds a new CLI arg in tempest run, ``--black-regex``, which is a regex to exclude the tests that match it. fixes: - | Fixes tempest run CLI args mutually exclusive behavior which should not be the case anymore (Bug#1751201). tempest-23.0.0/releasenotes/notes/tempest-stein-release-18bad34136a2e6ef.yaml000066400000000000000000000012631361143263300264740ustar00rootroot00000000000000--- prelude: | This release is to tag the Tempest for OpenStack Stein release. This release marks the start of Stein release support in Tempest and the end of support for Ocata in Tempest. After this release, Tempest will support below OpenStack Releases: * Stein * Rocky * Queens * Pike Current development of Tempest is for OpenStack Train development cycle. Every Tempest commit is also tested against master during the Train cycle. However, this does not necessarily mean that using Tempest as of this tag will work against a Train (or future release) cloud. To be on safe side, use this tag to test the OpenStack Stein release. tempest-23.0.0/releasenotes/notes/tempest-train-release-a2ed0743a5eadeb6.yaml000066400000000000000000000012071361143263300266220ustar00rootroot00000000000000--- prelude: | This release is to tag the Tempest for OpenStack Train release. This release marks the start of Train release support in Tempest. After this release, Tempest will support below OpenStack Releases: * Train * Stein * Rocky * Queens Current development of Tempest is for OpenStack Ussuri development cycle. Every Tempest commit is also tested against master during the Ussuri cycle. However, this does not necessarily mean that using Tempest as of this tag will work against a Ussuri (or future release) cloud. To be on safe side, use this tag to test the OpenStack Train release. tempest-23.0.0/releasenotes/notes/tempest-workspace-delete-directory-feature-74d6d157a5a05561.yaml000066400000000000000000000002331361143263300324050ustar00rootroot00000000000000--- features: - | Added tempest workspace remove ``--name --rmdir`` feature to delete the workspace directory as well as entry. tempest-23.0.0/releasenotes/notes/test-clients-stable-for-plugin-90b1e7dc83f28ccd.yaml000066400000000000000000000005311361143263300303120ustar00rootroot00000000000000--- 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. use-cinder-v3-client-for-verify_tempest_config-2bf3d817b0070064.yaml000066400000000000000000000004071361143263300327630ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- 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-23.0.0/releasenotes/notes/vnc-hardcoded-server-name-removed-6f8d1e90a175dc08.yaml000066400000000000000000000005561361143263300305750ustar00rootroot00000000000000--- features: - | New string configuration option ``vnc_server_header`` is added to ``compute-feature-enabled`` section. It allows the expected VNC server name in the response header to be specified. For example, obvious at hand names are 'WebSockify', 'nginx'. fixes: - | Fix VNC server response header issue when it is behind reverse proxy tempest-23.0.0/releasenotes/notes/volume-backed-live-mig-5a38b496ba1ec093.yaml000066400000000000000000000004451361143263300264270ustar00rootroot00000000000000--- features: - | A new boolean configuration option ``[compute-feature-enabled]/volume_backed_live_migration`` has been added. If enabled, tests which validate the behavior of Nova's *volume-backed live migration* feature will be executed. The option defaults to ``False``. volume-service-testing-default-to-v3-endpoints-20b86895a590925d.yaml000066400000000000000000000005011361143263300327220ustar00rootroot00000000000000tempest-23.0.0/releasenotes/notes--- upgrade: - | The volume config option ``catalog_type`` default is changed to ``volumev3`` which is v3 API endpoint configured in devstack. With this change Tempest will be testing v3 API as default. User who want to test v2 API can still test by configuring the ``catalog_type`` to v2 endpoint. tempest-23.0.0/releasenotes/notes/volume-v3-service-clients-a863a6336af56cca.yaml000066400000000000000000000006471361143263300272200ustar00rootroot00000000000000--- features: - | Adds volume service clients for v3 APIs. As v3 base API should be identical to v2 APIs, we just copy all existing v2 service client for v3 API. deprecations: - | Deprecates the volume service clients for v2 APIs. Volume v2 APIs are deprecated in all supported stable branches, so it's time to deprecate the tempest service clients for v2 APIs and remove in future release. tempest-23.0.0/releasenotes/source/000077500000000000000000000000001361143263300172505ustar00rootroot00000000000000tempest-23.0.0/releasenotes/source/_static/000077500000000000000000000000001361143263300206765ustar00rootroot00000000000000tempest-23.0.0/releasenotes/source/_static/.placeholder000066400000000000000000000000001361143263300231470ustar00rootroot00000000000000tempest-23.0.0/releasenotes/source/_templates/000077500000000000000000000000001361143263300214055ustar00rootroot00000000000000tempest-23.0.0/releasenotes/source/_templates/.placeholder000066400000000000000000000000001361143263300236560ustar00rootroot00000000000000tempest-23.0.0/releasenotes/source/conf.py000066400000000000000000000207661361143263300205620ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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 = '' # 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. 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 --------------------------------------------- # 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-23.0.0/releasenotes/source/index.rst000066400000000000000000000006011361143263300211060ustar00rootroot00000000000000=========================== Tempest Release Notes =========================== .. toctree:: :maxdepth: 1 unreleased v22.1.0 v22.0.0 v21.0.0 v20.0.0 v19.0.0 v18.0.0 v17.2.0 v17.1.0 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-23.0.0/releasenotes/source/unreleased.rst000066400000000000000000000001531361143263300221300ustar00rootroot00000000000000============================ Current Series Release Notes ============================ .. release-notes:: tempest-23.0.0/releasenotes/source/v10.0.0.rst000066400000000000000000000001771361143263300207110ustar00rootroot00000000000000===================== v10.0.0 Release Notes ===================== .. release-notes:: 10.0.0 Release Notes :version: 10.0.0 tempest-23.0.0/releasenotes/source/v11.0.0.rst000066400000000000000000000001771361143263300207120ustar00rootroot00000000000000===================== v11.0.0 Release Notes ===================== .. release-notes:: 11.0.0 Release Notes :version: 11.0.0 tempest-23.0.0/releasenotes/source/v12.0.0.rst000066400000000000000000000001771361143263300207130ustar00rootroot00000000000000===================== v12.0.0 Release Notes ===================== .. release-notes:: 12.0.0 Release Notes :version: 12.0.0 tempest-23.0.0/releasenotes/source/v13.0.0.rst000066400000000000000000000001771361143263300207140ustar00rootroot00000000000000===================== v13.0.0 Release Notes ===================== .. release-notes:: 13.0.0 Release Notes :version: 13.0.0 tempest-23.0.0/releasenotes/source/v14.0.0.rst000066400000000000000000000001771361143263300207150ustar00rootroot00000000000000===================== v14.0.0 Release Notes ===================== .. release-notes:: 14.0.0 Release Notes :version: 14.0.0 tempest-23.0.0/releasenotes/source/v15.0.0.rst000066400000000000000000000001771361143263300207160ustar00rootroot00000000000000===================== v15.0.0 Release Notes ===================== .. release-notes:: 15.0.0 Release Notes :version: 15.0.0 tempest-23.0.0/releasenotes/source/v16.0.0.rst000066400000000000000000000001761361143263300207160ustar00rootroot00000000000000===================== v16.0.0 Release Notes ===================== .. release-notes:: 16.0.0 Release Notes :version: 16.0.0tempest-23.0.0/releasenotes/source/v16.1.0.rst000066400000000000000000000001771361143263300207200ustar00rootroot00000000000000===================== v16.1.0 Release Notes ===================== .. release-notes:: 16.1.0 Release Notes :version: 16.1.0 tempest-23.0.0/releasenotes/source/v17.0.0.rst000066400000000000000000000001771361143263300207200ustar00rootroot00000000000000===================== v17.0.0 Release Notes ===================== .. release-notes:: 17.0.0 Release Notes :version: 17.0.0 tempest-23.0.0/releasenotes/source/v17.1.0.rst000066400000000000000000000001771361143263300207210ustar00rootroot00000000000000===================== v17.1.0 Release Notes ===================== .. release-notes:: 17.1.0 Release Notes :version: 17.1.0 tempest-23.0.0/releasenotes/source/v17.2.0.rst000066400000000000000000000001771361143263300207220ustar00rootroot00000000000000===================== v17.2.0 Release Notes ===================== .. release-notes:: 17.2.0 Release Notes :version: 17.2.0 tempest-23.0.0/releasenotes/source/v18.0.0.rst000066400000000000000000000001771361143263300207210ustar00rootroot00000000000000===================== v18.0.0 Release Notes ===================== .. release-notes:: 18.0.0 Release Notes :version: 18.0.0 tempest-23.0.0/releasenotes/source/v19.0.0.rst000066400000000000000000000001771361143263300207220ustar00rootroot00000000000000===================== v19.0.0 Release Notes ===================== .. release-notes:: 19.0.0 Release Notes :version: 19.0.0 tempest-23.0.0/releasenotes/source/v20.0.0.rst000066400000000000000000000001771361143263300207120ustar00rootroot00000000000000===================== v20.0.0 Release Notes ===================== .. release-notes:: 20.0.0 Release Notes :version: 20.0.0 tempest-23.0.0/releasenotes/source/v21.0.0.rst000066400000000000000000000001771361143263300207130ustar00rootroot00000000000000===================== v21.0.0 Release Notes ===================== .. release-notes:: 21.0.0 Release Notes :version: 21.0.0 tempest-23.0.0/releasenotes/source/v22.0.0.rst000066400000000000000000000001771361143263300207140ustar00rootroot00000000000000===================== v22.0.0 Release Notes ===================== .. release-notes:: 22.0.0 Release Notes :version: 22.0.0 tempest-23.0.0/releasenotes/source/v22.1.0.rst000066400000000000000000000001771361143263300207150ustar00rootroot00000000000000===================== v22.1.0 Release Notes ===================== .. release-notes:: 22.1.0 Release Notes :version: 22.1.0 tempest-23.0.0/requirements.txt000066400000000000000000000014761361143263300165530ustar00rootroot00000000000000# 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>=2.6.0 # MIT testtools>=2.2.0 # MIT paramiko>=2.0.0 # LGPLv2.1+ netaddr>=0.7.18 # BSD oslo.concurrency>=3.26.0 # Apache-2.0 oslo.config>=5.2.0 # Apache-2.0 oslo.log>=3.36.0 # Apache-2.0 stestr>=1.0.0 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 six>=1.10.0 # MIT fixtures>=3.0.0 # Apache-2.0/BSD PyYAML>=3.12 # MIT python-subunit>=1.0.0 # Apache-2.0/BSD stevedore>=1.20.0 # Apache-2.0 PrettyTable<0.8,>=0.7.1 # BSD urllib3>=1.21.1 # MIT debtcollector>=1.2.0 # Apache-2.0 unittest2>=1.1.0 # BSD tempest-23.0.0/roles/000077500000000000000000000000001361143263300144035ustar00rootroot00000000000000tempest-23.0.0/roles/acl-devstack-files/000077500000000000000000000000001361143263300200445ustar00rootroot00000000000000tempest-23.0.0/roles/acl-devstack-files/README.rst000066400000000000000000000003701361143263300215330ustar00rootroot00000000000000Grant 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-23.0.0/roles/acl-devstack-files/defaults/000077500000000000000000000000001361143263300216535ustar00rootroot00000000000000tempest-23.0.0/roles/acl-devstack-files/defaults/main.yaml000066400000000000000000000000431361143263300234600ustar00rootroot00000000000000devstack_data_dir: /opt/stack/data tempest-23.0.0/roles/acl-devstack-files/tasks/000077500000000000000000000000001361143263300211715ustar00rootroot00000000000000tempest-23.0.0/roles/acl-devstack-files/tasks/main.yaml000066400000000000000000000002231361143263300227760ustar00rootroot00000000000000- name: Grant global read access to devstack files file: path: "{{devstack_data_dir}}/files" mode: "o+rx" recurse: yes become: yes tempest-23.0.0/roles/ipv6-only-deployments-verification/000077500000000000000000000000001361143263300232675ustar00rootroot00000000000000tempest-23.0.0/roles/ipv6-only-deployments-verification/README.rst000066400000000000000000000006431361143263300247610ustar00rootroot00000000000000Verify the IPv6-only deployments This role needs to be invoked from a playbook that run tests. This role verifies the IPv6 setting on devstack side and devstack deploy services on IPv6. This role is invoked before tests are run so that if any missing IPv6 setting or deployments can fail the job early. **Role Variables** .. zuul:rolevar:: devstack_base_dir :default: /opt/stack The devstack base directory. tempest-23.0.0/roles/ipv6-only-deployments-verification/defaults/000077500000000000000000000000001361143263300250765ustar00rootroot00000000000000tempest-23.0.0/roles/ipv6-only-deployments-verification/defaults/main.yaml000066400000000000000000000000361361143263300267050ustar00rootroot00000000000000devstack_base_dir: /opt/stack tempest-23.0.0/roles/ipv6-only-deployments-verification/tasks/000077500000000000000000000000001361143263300244145ustar00rootroot00000000000000tempest-23.0.0/roles/ipv6-only-deployments-verification/tasks/main.yaml000066400000000000000000000002361361143263300262250ustar00rootroot00000000000000- name: Verify the ipv6-only deployments become: true become_user: stack shell: "{{ devstack_base_dir }}/tempest/tools/verify-ipv6-only-deployments.sh" tempest-23.0.0/roles/process-stackviz/000077500000000000000000000000001361143263300177155ustar00rootroot00000000000000tempest-23.0.0/roles/process-stackviz/README.rst000066400000000000000000000010461361143263300214050ustar00rootroot00000000000000Generate 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: "{{ ansible_user_dir }}" The stage directory where the input data can be found and the output will be produced. .. zuul:rolevar:: zuul_work_dir :default: {{ devstack_base_dir }}/tempest Directory to work in. It has to be a fully qualified path. tempest-23.0.0/roles/process-stackviz/defaults/000077500000000000000000000000001361143263300215245ustar00rootroot00000000000000tempest-23.0.0/roles/process-stackviz/defaults/main.yaml000066400000000000000000000001631361143263300233340ustar00rootroot00000000000000devstack_base_dir: /opt/stack stage_dir: "{{ ansible_user_dir }}" zuul_work_dir: "{{ devstack_base_dir }}/tempest" tempest-23.0.0/roles/process-stackviz/tasks/000077500000000000000000000000001361143263300210425ustar00rootroot00000000000000tempest-23.0.0/roles/process-stackviz/tasks/main.yaml000066400000000000000000000034541361143263300226600ustar00rootroot00000000000000- 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: "{{ zuul_work_dir }}/testrepository.subunit" register: subunit_input - debug: msg: "Subunit file could not be found at {{ zuul_work_dir }}/testrepository.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 failed_when: False - 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 failed_when: False tempest-23.0.0/roles/run-tempest/000077500000000000000000000000001361143263300166665ustar00rootroot00000000000000tempest-23.0.0/roles/run-tempest/README.rst000066400000000000000000000033751361143263300203650ustar00rootroot00000000000000Run 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'.) Multi-line and commented regexs can be achieved by doing this: :: vars: tempest_test_regex: | (?x) # Ignore comments and whitespaces # Line with only a comment. (tempest\.(api|scenario|thirdparty)).*$ # Run only api scenario and third party .. zuul:rolevar:: tempest_test_blacklist Specifies a blacklist file to skip tests that are not needed. Pass a full path to the file. .. zuul:rolevar:: tox_envlist :default: smoke The Tempest tox environment to run. .. zuul:rolevar:: tempest_black_regex :default: '' A regular expression used to skip the tests. It works only when used with some specific tox environments ('all', 'all-plugin'.) Multi-line and commented regexs can be achieved by doing this: :: vars: tempest_black_regex: | (?x) # Ignore comments and whitespaces # Line with only a comment. (tempest.api.identity).*$ .. zuul:rolevar:: tox_extra_args :default: '' String of extra command line options to pass to tox. Here is an example of running tox with --sitepackages option: :: vars: tox_extra_args: --sitepackages .. zuul:rolevar:: tempest_test_timeout :default: '' The timeout (in seconds) for each test. tempest-23.0.0/roles/run-tempest/defaults/000077500000000000000000000000001361143263300204755ustar00rootroot00000000000000tempest-23.0.0/roles/run-tempest/defaults/main.yaml000066400000000000000000000002141361143263300223020ustar00rootroot00000000000000devstack_base_dir: /opt/stack tempest_test_regex: '' tox_envlist: smoke tempest_black_regex: '' tox_extra_args: '' tempest_test_timeout: '' tempest-23.0.0/roles/run-tempest/tasks/000077500000000000000000000000001361143263300200135ustar00rootroot00000000000000tempest-23.0.0/roles/run-tempest/tasks/main.yaml000066400000000000000000000030631361143263300216250ustar00rootroot00000000000000# 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 - when: - tempest_test_blacklist is defined block: - name: Check for test blacklist file stat: path: "{{ tempest_test_blacklist }}" register: blacklist_stat - name: Build blacklist option set_fact: blacklist_option: "--blacklist-file={{ tempest_test_blacklist|quote }}" when: blacklist_stat.stat.exists - name: Run Tempest command: tox -e {{tox_envlist}} {{tox_extra_args}} -- {{tempest_test_regex|quote}} {{blacklist_option|default('')}} \ --concurrency={{tempest_concurrency|default(default_concurrency)}} \ --black-regex={{tempest_black_regex|quote}} args: chdir: "{{devstack_base_dir}}/tempest" become: true become_user: tempest environment: '{{ {"OS_TEST_TIMEOUT": tempest_test_timeout} if tempest_test_timeout else {} }}' tempest-23.0.0/roles/setup-tempest-data-dir/000077500000000000000000000000001361143263300207055ustar00rootroot00000000000000tempest-23.0.0/roles/setup-tempest-data-dir/README.rst000066400000000000000000000005161361143263300223760ustar00rootroot00000000000000Setup 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-23.0.0/roles/setup-tempest-data-dir/defaults/000077500000000000000000000000001361143263300225145ustar00rootroot00000000000000tempest-23.0.0/roles/setup-tempest-data-dir/defaults/main.yaml000066400000000000000000000000431361143263300243210ustar00rootroot00000000000000devstack_data_dir: /opt/stack/data tempest-23.0.0/roles/setup-tempest-data-dir/tasks/000077500000000000000000000000001361143263300220325ustar00rootroot00000000000000tempest-23.0.0/roles/setup-tempest-data-dir/tasks/main.yaml000066400000000000000000000002511361143263300236400ustar00rootroot00000000000000- name: Set tempest as owner of Tempest data folder file: path: "{{devstack_data_dir}}/tempest" owner: tempest group: stack recurse: yes become: yes tempest-23.0.0/roles/setup-tempest-run-dir/000077500000000000000000000000001361143263300206005ustar00rootroot00000000000000tempest-23.0.0/roles/setup-tempest-run-dir/README.rst000066400000000000000000000006031361143263300222660ustar00rootroot00000000000000Setup 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-23.0.0/roles/setup-tempest-run-dir/defaults/000077500000000000000000000000001361143263300224075ustar00rootroot00000000000000tempest-23.0.0/roles/setup-tempest-run-dir/defaults/main.yaml000066400000000000000000000000361361143263300242160ustar00rootroot00000000000000devstack_base_dir: /opt/stack tempest-23.0.0/roles/setup-tempest-run-dir/tasks/000077500000000000000000000000001361143263300217255ustar00rootroot00000000000000tempest-23.0.0/roles/setup-tempest-run-dir/tasks/main.yaml000066400000000000000000000002501361143263300235320ustar00rootroot00000000000000- name: Set tempest as owner of Tempest run folder file: path: "{{devstack_base_dir}}/tempest" owner: tempest group: stack recurse: yes become: yes tempest-23.0.0/setup.cfg000066400000000000000000000040241361143263300151000ustar00rootroot00000000000000[metadata] name = tempest summary = OpenStack Integration Testing description-file = README.rst author = OpenStack author-email = openstack-discuss@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 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 [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 subunit-describe-calls = tempest.cmd.subunit_describe_calls:TempestSubunitDescribeCalls 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 [wheel] universal = 1 tempest-23.0.0/setup.py000066400000000000000000000020061361143263300147670ustar00rootroot00000000000000# 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-23.0.0/tempest/000077500000000000000000000000001361143263300147405ustar00rootroot00000000000000tempest-23.0.0/tempest/README.rst000066400000000000000000000040321361143263300164260ustar00rootroot00000000000000============================ 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 repository structure to make this clear. .. code-block:: console 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-23.0.0/tempest/__init__.py000066400000000000000000000000001361143263300170370ustar00rootroot00000000000000tempest-23.0.0/tempest/api/000077500000000000000000000000001361143263300155115ustar00rootroot00000000000000tempest-23.0.0/tempest/api/README.rst000066400000000000000000000035151361143263300172040ustar00rootroot00000000000000.. _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-23.0.0/tempest/api/__init__.py000066400000000000000000000000001361143263300176100ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/000077500000000000000000000000001361143263300171655ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/__init__.py000066400000000000000000000000001361143263300212640ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/admin/000077500000000000000000000000001361143263300202555ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/admin/__init__.py000066400000000000000000000000001361143263300223540ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/admin/test_agents.py000066400000000000000000000117151361143263300231540ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/admin/test_aggregates.py000066400000000000000000000267421361143263300240120ustar00rootroot00000000000000# 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 AggregatesAdminTestBase(base.BaseV2ComputeAdminTest): """Tests Aggregates API that require admin privileges""" @classmethod def setup_clients(cls): super(AggregatesAdminTestBase, cls).setup_clients() cls.client = cls.os_admin.aggregates_client @classmethod def resource_setup(cls): super(AggregatesAdminTestBase, 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)] cls.hosts_available = [hyper['service']['host'] for hyper in hypers if (hyper['state'] == 'up' and hyper['status'] == 'enabled')] if cls.hosts_available: cls.host = cls.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 class AggregatesAdminTestJSON(AggregatesAdminTestBase): @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) # Find a host that has not been added to other availability zone, # for one host can't be added to different availability zones. aggregates = self.client.list_aggregates()['aggregates'] hosts_in_zone = [] for agg in aggregates: if agg['availability_zone']: hosts_in_zone.extend(agg['hosts']) hosts = [v for v in self.hosts_available if v not in hosts_in_zone] if not hosts: raise self.skipException("All hosts are already in other " "availability zones, so can't add " "host to aggregate. \nAggregates list: " "%s" % aggregates) host = hosts[0] self.client.add_host(aggregate['id'], host=host) self.addCleanup(self.client.remove_host, aggregate['id'], host=host) server = self.create_test_server(availability_zone=az_name, wait_until='ACTIVE') server_host = self.get_host_for_server(server['id']) self.assertEqual(host, server_host) class AggregatesAdminTestV241(AggregatesAdminTestBase): min_microversion = '2.41' # NOTE(gmann): This test tests the Aggregate APIs response schema # for 2.41 microversion. No specific assert or behaviour verification # is needed. @decorators.idempotent_id('fdf24d9e-8afa-4700-b6aa-9c498351504f') def test_create_update_show_aggregate_add_remove_host(self): # Update and add a host to the given aggregate and get details. self.useFixture(fixtures.LockFixture('availability_zone')) # Checking create aggregate API response schema aggregate = self._create_test_aggregate() new_aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) # Checking update aggregate API response schema self.client.update_aggregate(aggregate['id'], name=new_aggregate_name) # Checking show aggregate API response schema self.client.show_aggregate(aggregate['id'])['aggregate'] # Checking add host to aggregate API response schema self.client.add_host(aggregate['id'], host=self.host) # Checking rempve host from aggregate API response schema self.client.remove_host(aggregate['id'], host=self.host) tempest-23.0.0/tempest/api/compute/admin/test_aggregates_negative.py000066400000000000000000000177231361143263300256730ustar00rootroot00000000000000# 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 cls.services_client = cls.os_admin.services_client @classmethod def resource_setup(cls): super(AggregatesAdminNegativeTestJSON, cls).resource_setup() cls.aggregate_name_prefix = 'test_aggregate' svc_list = cls.services_client.list_services( binary='nova-compute')['services'] cls.hosts = [v['host'] for v in svc_list if v['status'] == 'enabled' and v['state'] == 'up'] 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. while True: non_exist_host = data_utils.rand_name('nonexist_host') if non_exist_host not in self.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.hosts[0]) @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.hosts[0]) self.addCleanup(self.client.remove_host, aggregate['id'], host=self.hosts[0]) self.assertRaises(lib_exc.Conflict, self.client.add_host, aggregate['id'], host=self.hosts[0]) @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.hosts[0]) self.addCleanup(self.client.remove_host, aggregate['id'], host=self.hosts[0]) self.assertRaises(lib_exc.Forbidden, self.aggregates_client.remove_host, aggregate['id'], host=self.hosts[0]) @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-23.0.0/tempest/api/compute/admin/test_auto_allocate_network.py000066400000000000000000000221231361143263300262530ustar00rootroot00000000000000# 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 cls.skipException('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 = self.create_test_server(networks='none', wait_until='ACTIVE') # 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-23.0.0/tempest/api/compute/admin/test_availability_zone.py000066400000000000000000000030561361143263300253770ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/admin/test_availability_zone_negative.py000066400000000000000000000026251361143263300272620ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/admin/test_create_server.py000066400000000000000000000126431361143263300245250ustar00rootroot00000000000000# 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) # Set extra specs same as self.flavor_ref for the created flavor, # because the environment may need some special extra specs to # create server which should have been contained in # self.flavor_ref. extra_spec_keys = \ self.admin_flavors_client.list_flavor_extra_specs( self.flavor_ref)['extra_specs'] if extra_spec_keys: self.admin_flavors_client.set_flavor_extra_spec( flavor['id'], **extra_spec_keys) 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-23.0.0/tempest/api/compute/admin/test_delete_server.py000066400000000000000000000042611361143263300245210ustar00rootroot00000000000000# 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 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-23.0.0/tempest/api/compute/admin/test_fixed_ips.py000066400000000000000000000050301361143263300236360ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/admin/test_fixed_ips_negative.py000066400000000000000000000077231361143263300255330ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/admin/test_flavors.py000066400000000000000000000235131361143263300233460ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/admin/test_flavors_access.py000066400000000000000000000062761361143263300246760ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/admin/test_flavors_access_negative.py000066400000000000000000000116541361143263300265540ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/admin/test_flavors_extra_specs.py000066400000000000000000000117551361143263300257530ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/admin/test_flavors_extra_specs_negative.py000066400000000000000000000127121361143263300276270ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/admin/test_flavors_microversions.py000066400000000000000000000040401361143263300263220ustar00rootroot00000000000000# Copyright 2018 NEC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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 FlavorsV255TestJSON(base.BaseV2ComputeAdminTest): min_microversion = '2.55' max_microversion = 'latest' # NOTE(gmann): This class tests the flavors APIs # response schema for the 2.55 microversion. @decorators.idempotent_id('61976b25-488d-41dc-9dcb-cb9693a7b075') def test_crud_flavor(self): flavor_id = data_utils.rand_int_id(start=1000) # Checking create API response schema new_flavor_id = self.create_flavor(ram=512, vcpus=1, disk=10, id=flavor_id)['id'] # Checking show API response schema self.flavors_client.show_flavor(new_flavor_id) # Checking update API response schema self.admin_flavors_client.update_flavor(new_flavor_id, description='new') # Checking list details API response schema self.flavors_client.list_flavors(detail=True) # Checking list API response schema self.flavors_client.list_flavors() class FlavorsV261TestJSON(FlavorsV255TestJSON): min_microversion = '2.61' max_microversion = 'latest' # NOTE(gmann): This class tests the flavors APIs # response schema for the 2.61 microversion. tempest-23.0.0/tempest/api/compute/admin/test_floating_ips_bulk.py000066400000000000000000000072601361143263300253660ustar00rootroot00000000000000# 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 # TODO(stephenfin): Remove this test class once the nova queens branch goes # into extended maintenance mode. 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 """ max_microversion = '2.35' depends_on_nova_network = True @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-23.0.0/tempest/api/compute/admin/test_hosts.py000066400000000000000000000054511361143263300230330ustar00rootroot00000000000000# 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.""" max_microversion = '2.42' @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-23.0.0/tempest/api/compute/admin/test_hosts_negative.py000066400000000000000000000140371361143263300247150ustar00rootroot00000000000000# 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.""" max_microversion = '2.42' @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-23.0.0/tempest/api/compute/admin/test_hypervisor.py000066400000000000000000000132401361143263300241000ustar00rootroot00000000000000# 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 HypervisorAdminTestBase(base.BaseV2ComputeAdminTest): """Tests Hypervisors API that require admin privileges""" @classmethod def setup_clients(cls): super(HypervisorAdminTestBase, 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 class HypervisorAdminTestJSON(HypervisorAdminTestBase): """Tests Hypervisors API that require admin privileges""" @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('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' and details['state'] == 'up'): 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) class HypervisorAdminV228Test(HypervisorAdminTestBase): min_microversion = '2.28' @decorators.idempotent_id('d46bab64-0fbe-4eb8-9133-e6ee56188cc5') def test_get_list_hypervisor_details(self): # NOTE(zhufl): This test tests the hypervisor APIs response schema # for 2.28 microversion. No specific assert or behaviour verification # is needed. hypers = self._list_hypervisors() self.assertNotEmpty(hypers, "No hypervisors found.") self.client.show_hypervisor(hypers[0]['id']) class HypervisorAdminUnderV252Test(HypervisorAdminTestBase): max_microversion = '2.52' @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('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-23.0.0/tempest/api/compute/admin/test_hypervisor_negative.py000066400000000000000000000126611361143263300257700ustar00rootroot00000000000000# 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 HypervisorAdminNegativeTestBase(base.BaseV2ComputeAdminTest): """Tests Hypervisors API that require admin privileges""" @classmethod def setup_clients(cls): super(HypervisorAdminNegativeTestBase, 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 class HypervisorAdminNegativeTestJSON(HypervisorAdminNegativeTestBase): """Tests Hypervisors API that require admin privileges""" @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('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) class HypervisorAdminNegativeUnderV252Test(HypervisorAdminNegativeTestBase): max_microversion = '2.52' @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('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']) @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') tempest-23.0.0/tempest/api/compute/admin/test_instance_usage_audit_log.py000066400000000000000000000030441361143263300267060ustar00rootroot00000000000000# 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 self.adm_client.list_instance_usage_audit_logs() @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() self.adm_client.show_instance_usage_audit_log( urllib.quote(now.strftime("%Y-%m-%d %H:%M:%S"))) tempest-23.0.0/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py000066400000000000000000000041121361143263300305650ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/admin/test_keypairs_v210.py000066400000000000000000000067341361143263300242770ustar00rootroot00000000000000# 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, client=self.client) 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 an admin keypair admin_keypair = self.create_keypair(keypair_type='ssh', client=self.client) 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-23.0.0/tempest/api/compute/admin/test_live_migration.py000066400000000000000000000247531361143263300247110ustar00rootroot00000000000000# 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 LiveMigrationTestBase(base.BaseV2ComputeAdminTest): # 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. create_default_network = True @classmethod def skip_checks(cls): super(LiveMigrationTestBase, 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_clients(cls): super(LiveMigrationTestBase, 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: if self.is_requested_microversion_compatible('2.24'): 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) class LiveMigrationTest(LiveMigrationTestBase): max_microversion = '2.24' block_migration = None 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') @testtools.skipUnless(CONF.compute_feature_enabled. volume_backed_live_migration, 'Volume-backed live migration not available') @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') @utils.services('volume') 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._live_migrate(server_id, target_host, 'ACTIVE') server = self.admin_servers_client.show_server(server_id)['server'] volume_id2 = server["os-extended-volumes:volumes_attached"][0]["id"] self.assertEqual(volume_id1, volume_id2) class LiveMigrationRemoteConsolesV26Test(LiveMigrationTestBase): 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._live_migrate(server01_id, host02_id, 'ACTIVE') 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) received = ws.receive_frame() console_output += received 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-23.0.0/tempest/api/compute/admin/test_live_migration_negative.py000066400000000000000000000053061361143263300265640ustar00rootroot00000000000000# 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 kwargs = dict(host=dest_host, block_migration=bmflm) if self.is_requested_microversion_compatible('2.24'): kwargs['disk_over_commit'] = False self.admin_servers_client.live_migrate_server(server_id, **kwargs) @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-23.0.0/tempest/api/compute/admin/test_migrations.py000066400000000000000000000140101361143263300240360ustar00rootroot00000000000000# 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']) # Set extra specs same as self.flavor_ref for the created flavor, # because the environment may need some special extra specs to # create server which should have been contained in # self.flavor_ref. extra_spec_keys = self.admin_flavors_client.list_flavor_extra_specs( self.flavor_ref)['extra_specs'] if extra_spec_keys: self.admin_flavors_client.set_flavor_extra_spec( flavor['id'], **extra_spec_keys) # 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.assert_flavor_equal(flavor['id'], server['flavor']) 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.get_host_for_server(server['id']) 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.get_host_for_server(server['id']) 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-23.0.0/tempest/api/compute/admin/test_networks.py000066400000000000000000000054231361143263300235460ustar00rootroot00000000000000# 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: https://docs.openstack.org/api-ref/compute/#networks-os-networks-deprecated """ max_microversion = '2.35' @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-23.0.0/tempest/api/compute/admin/test_quotas.py000066400000000000000000000247061361143263300232130ustar00rootroot00000000000000# 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 QuotasAdminTestBase(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(QuotasAdminTestBase, self).setUp() @classmethod def setup_clients(cls): super(QuotasAdminTestBase, cls).setup_clients() cls.adm_client = cls.os_admin.quotas_client def _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') # Call show_quota_set with detail=true to cover the # get_quota_set_details response schema for microversion tests quota_set = self.adm_client.show_quota_set( project_id, detail=True)['quota_set'] self.assertEqual(5120, quota_set['ram']['limit']) # 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']) @classmethod def resource_setup(cls): super(QuotasAdminTestBase, 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(('metadata_items', 'ram', 'key_pairs', 'instances', 'cores', 'server_group_members', 'server_groups')) if cls.is_requested_microversion_compatible('2.35'): cls.default_quota_set = \ cls.default_quota_set | set(['fixed_ips', 'floating_ips', 'security_group_rules', 'security_groups']) if cls.is_requested_microversion_compatible('2.56'): cls.default_quota_set = \ cls.default_quota_set | set(['injected_file_content_bytes', 'injected_file_path_bytes', 'injected_files']) class QuotasAdminTestJSON(QuotasAdminTestBase): @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 = {'metadata_items': 256, 'ram': 10240, 'key_pairs': 200, 'instances': 20, 'server_groups': 20, 'server_group_members': 20, 'cores': 2} if self.is_requested_microversion_compatible('2.35'): new_quota_set.update({'fixed_ips': 10, 'floating_ips': 20, 'security_group_rules': 20, 'security_groups': 20}) if self.is_requested_microversion_compatible('2.56'): new_quota_set.update({'injected_file_content_bytes': 20480, 'injected_file_path_bytes': 512, 'injected_files': 10}) # 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): self._get_updated_quotas() @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 QuotasAdminTestV236(QuotasAdminTestBase): min_microversion = '2.36' # NOTE(gmann): This test tests the Quota APIs response schema # for 2.36 microversion. No specific assert or behaviour verification # is needed. @decorators.idempotent_id('4268b5c9-92e5-4adc-acf1-3a2798f3d803') def test_get_updated_quotas(self): # Checking Quota update, get, get details APIs response schema self._get_updated_quotas() class QuotasAdminTestV257(QuotasAdminTestBase): min_microversion = '2.57' # NOTE(gmann): This test tests the Quota APIs response schema # for 2.57 microversion. No specific assert or behaviour verification # is needed. @decorators.idempotent_id('e641e6c6-e86c-41a4-9e5c-9493c0ae47ad') def test_get_updated_quotas(self): # Checking Quota update, get, get details APIs response schema self._get_updated_quotas() 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): # 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 # update limits for the default quota class set update_body = self.adm_client.update_quota_class_set( 'default', **body)['quota_class_set'] # assert that the response has all of the changed values self.assertThat(update_body.items(), matchers.ContainsAll(body.items())) # check quota values are changed show_body = self.adm_client.show_quota_class_set( 'default')['quota_class_set'] self.assertThat(show_body.items(), matchers.ContainsAll(body.items())) tempest-23.0.0/tempest/api/compute/admin/test_quotas_negative.py000066400000000000000000000142051361143263300250660ustar00rootroot00000000000000# 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 QuotasAdminNegativeTestBase(base.BaseV2ComputeAdminTest): force_tenant_isolation = True @classmethod def setup_clients(cls): super(QuotasAdminNegativeTestBase, 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(QuotasAdminNegativeTestBase, 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}) class QuotasAdminNegativeTest(QuotasAdminNegativeTestBase): @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) class QuotasSecurityGroupAdminNegativeTest(QuotasAdminNegativeTestBase): max_microversion = '2.35' @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-23.0.0/tempest/api/compute/admin/test_security_group_default_rules.py000066400000000000000000000133461361143263300276760ustar00rootroot00000000000000# 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): max_microversion = '2.35' @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-23.0.0/tempest/api/compute/admin/test_security_groups.py000066400000000000000000000101321361143263300251310ustar00rootroot00000000000000# 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): max_microversion = '2.35' @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']) sec_group_id_list = [sg['id'] for sg in fetched_list] # Now check that 'all_tenants='true' filter for non-admin user only # provide the requested non-admin user's created security groups, # not all security groups which include security groups created by # other users. for sec_group in security_group_list: if sec_group['tenant_id'] == client_tenant_id: self.assertIn(sec_group['id'], sec_group_id_list, "Failed to get all security groups for " "non admin user.") else: self.assertNotIn(sec_group['id'], sec_group_id_list, "Non admin user shouldn't get other user's " "security groups.") tempest-23.0.0/tempest/api/compute/admin/test_server_diagnostics.py000066400000000000000000000064741361143263300255760ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/admin/test_server_diagnostics_negative.py000066400000000000000000000025411361143263300274470ustar00rootroot00000000000000# 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): @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) tempest-23.0.0/tempest/api/compute/admin/test_servers.py000066400000000000000000000222271361143263300233640ustar00rootroot00000000000000# 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 from tempest.lib import exceptions as lib_exc 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'} if self.is_requested_microversion_compatible('2.37'): body = self.client.list_servers(detail=True, **params) servers = body['servers'] self.assertEmpty(servers) else: self.assertRaises(lib_exc.BadRequest, self.client.list_servers, detail=True, **params) @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.assert_flavor_equal(self.flavor_ref, rebuilt_server['flavor']) 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-23.0.0/tempest/api/compute/admin/test_servers_negative.py000066400000000000000000000131411361143263300252410ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/admin/test_servers_on_multinodes.py000066400000000000000000000125201361143263300263160ustar00rootroot00000000000000# 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_for_server(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.") def _create_servers_with_group(self, policy): group_id = self.create_test_server_group(policy=[policy])['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_for_server(server['id']) return hosts @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_for_server(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_for_server(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_for_server(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. """ hosts = self._create_servers_with_group('anti-affinity') hostnames = list(hosts.values()) self.assertNotEqual(hostnames[0], hostnames[1], 'Servers are on the same host: %s' % hosts) @decorators.idempotent_id('9d2e924a-baf4-11e7-b856-fa163e65f5ce') @testtools.skipUnless( compute.is_scheduler_filter_enabled("ServerGroupAffinityFilter"), 'ServerGroupAffinityFilter is not available.') def test_create_server_with_scheduler_hint_group_affinity(self): """Tests the ServerGroupAffinityFilter Creates two servers in an affinity server group and asserts the servers are in the group and on same host. """ hosts = self._create_servers_with_group('affinity') hostnames = list(hosts.values()) self.assertEqual(hostnames[0], hostnames[1], 'Servers are on the different hosts: %s' % hosts) tempest-23.0.0/tempest/api/compute/admin/test_services.py000066400000000000000000000045031361143263300235130ustar00rootroot00000000000000# 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)) tempest-23.0.0/tempest/api/compute/admin/test_services_negative.py000066400000000000000000000107271361143263300254020ustar00rootroot00000000000000# 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 ServicesAdminNegativeTestJSON(base.BaseV2ComputeAdminTest): """Tests Services API. List and Enable/Disable require admin privileges.""" max_microversion = '2.52' @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): # Expect all services to be returned when the request contains invalid # parameters. 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) class ServicesAdminNegativeV253TestJSON(ServicesAdminNegativeTestJSON): min_microversion = '2.53' max_microversion = 'latest' # NOTE(felipemonteiro): This class tests the services APIs response schema # for the 2.53 microversion. Schema testing is done for `list_services` # tests. @classmethod def resource_setup(cls): super(ServicesAdminNegativeV253TestJSON, cls).resource_setup() cls.fake_service_id = data_utils.rand_uuid() @decorators.attr(type=['negative']) @decorators.idempotent_id('508671aa-c929-4479-bd10-8680d40dd0a6') def test_enable_service_with_invalid_service_id(self): self.assertRaises(lib_exc.NotFound, self.client.update_service, service_id=self.fake_service_id, status='enabled') @decorators.attr(type=['negative']) @decorators.idempotent_id('a9eeeade-42b3-419f-87aa-c9342aa068cf') def test_disable_service_with_invalid_service_id(self): self.assertRaises(lib_exc.NotFound, self.client.update_service, service_id=self.fake_service_id, status='disabled') @decorators.attr(type=['negative']) @decorators.idempotent_id('f46a9d91-1e85-4b96-8e7a-db7706fa2e9a') def test_disable_log_reason_with_invalid_service_id(self): # disabled_reason requires that status='disabled' be provided. self.assertRaises(lib_exc.NotFound, self.client.update_service, service_id=self.fake_service_id, status='disabled', disabled_reason='maintenance') tempest-23.0.0/tempest/api/compute/admin/test_simple_tenant_usage.py000066400000000000000000000070331361143263300257170ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/admin/test_simple_tenant_usage_negative.py000066400000000000000000000054251361143263300276040ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/admin/test_volume_swap.py000066400000000000000000000247411361143263300242370ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 TestVolumeSwapBase(base.BaseV2ComputeAdminTest): @classmethod def skip_checks(cls): super(TestVolumeSwapBase, 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) class TestVolumeSwap(TestVolumeSwapBase): """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. """ # NOTE(mriedem): This is an uncommon scenario to call the compute API # to swap volumes directly; swap volume is primarily only for volume # live migration and retype callbacks from the volume service, and is slow # so it's marked as such. @decorators.attr(type='slow') @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']) class TestMultiAttachVolumeSwap(TestVolumeSwapBase): min_microversion = '2.60' max_microversion = 'latest' @classmethod def skip_checks(cls): super(TestMultiAttachVolumeSwap, cls).skip_checks() if not CONF.compute_feature_enabled.volume_multiattach: raise cls.skipException('Volume multi-attach is not available.') @classmethod def setup_clients(cls): super(TestMultiAttachVolumeSwap, cls).setup_clients() # Need this to set readonly volumes. cls.admin_volumes_client = cls.os_admin.volumes_client_latest # NOTE(mriedem): This is an uncommon scenario to call the compute API # to swap volumes directly; swap volume is primarily only for volume # live migration and retype callbacks from the volume service, and is slow # so it's marked as such. @decorators.attr(type='slow') @decorators.idempotent_id('e8f8f9d1-d7b7-4cd2-8213-ab85ef697b6e') # For some reason this test intermittently fails on teardown when there are # multiple compute nodes and the servers are split across the computes. # For now, just skip this test if there are multiple computes. # Alternatively we could put the servers in an affinity group if there are # multiple computes but that would just side-step the underlying bug. @decorators.skip_because(bug='1807723', condition=CONF.compute.min_compute_nodes > 1) @utils.services('volume') def test_volume_swap_with_multiattach(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(multiattach=True) # Make volume1 read-only since you can't swap from a volume with # multiple read/write attachments, and you can't change the readonly # flag on an in-use volume so we have to do this before attaching # volume1 to anything. If the compute API ever supports per-attachment # attach modes, then we can handle this differently. self.admin_volumes_client.update_volume_readonly( volume1['id'], readonly=True) volume2 = self.create_volume(multiattach=True) # Create two servers and wait for them to be ACTIVE. reservation_id = self.create_test_server( 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( reservation_id=reservation_id)['servers'] self.assertEqual(2, len(servers)) # Attach volume1 to server1 server1 = servers[0] self.attach_volume(server1, volume1) # Attach volume1 to server2 server2 = servers[1] self.attach_volume(server2, volume1) # Swap volume1 to volume2 on server1, volume1 should remain attached # to server 2 self.admin_servers_client.update_attached_volume( server1['id'], volume1['id'], volumeId=volume2['id']) # volume1 will return to in-use after the swap waiters.wait_for_volume_resource_status(self.volumes_client, volume1['id'], 'in-use') waiters.wait_for_volume_resource_status(self.volumes_client, volume2['id'], 'in-use') self.wait_for_server_volume_swap(server1['id'], volume1['id'], volume2['id']) # Verify volume2 is attached to server1 vol_attachments = self.servers_client.list_volume_attachments( server1['id'])['volumeAttachments'] self.assertEqual(1, len(vol_attachments)) self.assertIn(volume2['id'], vol_attachments[0]['volumeId']) # Verify volume1 is still attached to server2 vol_attachments = self.servers_client.list_volume_attachments( server2['id'])['volumeAttachments'] self.assertEqual(1, len(vol_attachments)) self.assertIn(volume1['id'], vol_attachments[0]['volumeId']) tempest-23.0.0/tempest/api/compute/admin/test_volumes_negative.py000066400000000000000000000046621361143263300252520ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/api_microversion_fixture.py000066400000000000000000000021731361143263300246600ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/base.py000066400000000000000000000664231361143263300204640ustar00rootroot00000000000000# 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.common import compute from tempest.common import waiters from tempest import config from tempest import exceptions from tempest.lib.common import api_microversion_fixture 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 # Set this to True in subclasses to create a default network. See # https://bugs.launchpad.net/tempest/+bug/1844568 create_default_network = 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): # Setting network=True, subnet=True creates a default network cls.set_network_resources( network=cls.create_default_network, subnet=cls.create_default_network) 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 if CONF.service_available.glance: 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].') cls._check_depends_on_nova_network() @classmethod def _check_depends_on_nova_network(cls): # Since nova-network APIs were removed from Nova in the Rocky release, # determine, based on the max version from the version document, if # the compute API is >Queens and if so, skip tests that rely on # nova-network. if not getattr(cls, 'depends_on_nova_network', False): return versions = cls.versions_client.list_versions()['versions'] # Find the v2.1 version which will tell us our max version for the # compute API we're testing against. for version in versions: if version['id'] == 'v2.1': max_version = api_version_request.APIVersionRequest( version['version']) break else: LOG.warning( 'Unable to determine max v2.1 compute API version: %s', versions) return # The max compute API version in Queens is 2.60 so we cap # at that version. queens = api_version_request.APIVersionRequest('2.60') if max_version > queens: raise cls.skipException('nova-network is gone') @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 is_requested_microversion_compatible(cls, max_version): """Check the compatibility of selected request microversion This method will check if selected request microversion (cls.request_microversion) for test is compatible with respect to 'max_version'. Compatible means if selected request microversion is in the range(<=) of 'max_version'. :param max_version: maximum microversion to compare for compatibility. Example: '2.30' :returns: True if selected request microversion is compatible with 'max_version'. False in other case. """ try: req_version_obj = api_version_request.APIVersionRequest( cls.request_microversion) # NOTE(gmann): This is case where this method is used before calling # resource_setup(), where cls.request_microversion is set. There may # not be any such case but still we can handle this case. except AttributeError: request_microversion = ( api_version_utils.select_request_microversion( cls.min_microversion, CONF.compute.min_microversion)) req_version_obj = api_version_request.APIVersionRequest( request_microversion) max_version_obj = api_version_request.APIVersionRequest(max_version) return req_version_obj <= max_version_obj @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 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') tenant_network = cls.get_tenant_network() # NOTE(snikitin): since microversion v2.37 'networks' field is required if (request_version >= v2_37_version and 'networks' not in kwargs and not tenant_network): kwargs['networks'] = 'none' 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. If compute microversion >= 2.36, the returned image response will be from the image service API rather than the compute image proxy API. """ 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']) # The compute image proxy APIs were deprecated in 2.35 so # use the images client directly if the API microversion being # used is >=2.36. if not cls.is_requested_microversion_compatible('2.35'): client = cls.images_client else: client = cls.compute_images_client cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc, client.delete_image, image_id) if wait_until is not None: try: wait_until = wait_until.upper() if not cls.is_requested_microversion_compatible('2.35'): wait_until = wait_until.lower() waiters.wait_for_image_status(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 = client.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'] 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) def resize_server(self, server_id, new_flavor_id, **kwargs): """resize and confirm_resize an server, waits for it to be ACTIVE.""" self.servers_client.resize_server(server_id, new_flavor_id, **kwargs) waiters.wait_for_server_status(self.servers_client, server_id, 'VERIFY_RESIZE') self.servers_client.confirm_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.assert_flavor_equal(new_flavor_id, server['flavor']) @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 lib_exc.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( compute_microversion=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. :param 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 if CONF.compute.compute_volume_common_az: kwargs.setdefault('availability_zone', CONF.compute.compute_volume_common_az) 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 lib_exc.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, tag=None): """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 tag: Optional device role tag to apply to the volume. """ attach_kwargs = dict(volumeId=volume['id']) if device: attach_kwargs['device'] = device if tag: attach_kwargs['tag'] = tag 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) waiters.wait_for_volume_resource_status(self.volumes_client, volume['id'], 'in-use') return attachment def assert_flavor_equal(self, flavor_id, server_flavor): """Check whether server_flavor equals to flavor. :param flavor_id: flavor id :param server_flavor: flavor info returned by show_server. """ # Nova API > 2.46 no longer includes flavor.id, and schema check # will cover whether 'id' should be in flavor if server_flavor.get('id'): msg = ('server flavor is not same as flavor!') self.assertEqual(flavor_id, server_flavor['id'], msg) else: flavor = self.flavors_client.show_flavor(flavor_id)['flavor'] self.assertEqual(flavor['name'], server_flavor['original_name'], "original_name in server flavor is not same as " "flavor name!") for key in ['ram', 'vcpus', 'disk']: msg = ('attribute %s in server flavor is not same as ' 'flavor!' % key) self.assertEqual(flavor[key], server_flavor[key], msg) 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 @classmethod def get_host_for_server(cls, server_id): server_details = cls.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) svcs = self.os_admin.services_client.list_services( binary='nova-compute')['services'] hosts = [svc['host'] for svc in svcs if svc['state'] == 'up' and svc['status'] == 'enabled'] for target_host in hosts: if source_host != target_host: return target_host tempest-23.0.0/tempest/api/compute/certificates/000077500000000000000000000000001361143263300216325ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/certificates/__init__.py000066400000000000000000000000001361143263300237310ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/certificates/test_certificates.py000066400000000000000000000026351361143263300257160ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/flavors/000077500000000000000000000000001361143263300206415ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/flavors/__init__.py000066400000000000000000000000001361143263300227400ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/flavors/test_flavors.py000066400000000000000000000135701361143263300237340ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/flavors/test_flavors_negative.py000066400000000000000000000054041361143263300256130ustar00rootroot00000000000000# 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): @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.assertRaises(lib_exc.BadRequest, self.create_test_server, image_id=image['id'], flavor=flavor['id']) tempest-23.0.0/tempest/api/compute/floating_ips/000077500000000000000000000000001361143263300216435ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/floating_ips/__init__.py000066400000000000000000000000001361143263300237420ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/floating_ips/base.py000066400000000000000000000031661361143263300231350ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/floating_ips/test_floating_ips_actions.py000066400000000000000000000127741361143263300274650ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py000066400000000000000000000125531361143263300313420ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/floating_ips/test_list_floating_ips.py000066400000000000000000000062161361143263300267720ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py000066400000000000000000000030271361143263300306510ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/images/000077500000000000000000000000001361143263300204325ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/images/__init__.py000066400000000000000000000000001361143263300225310ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/images/test_image_metadata.py000066400000000000000000000145021361143263300247670ustar00rootroot00000000000000# 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): max_microversion = '2.38' @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-23.0.0/tempest/api/compute/images/test_image_metadata_negative.py000066400000000000000000000071301361143263300266500ustar00rootroot00000000000000# 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): max_microversion = '2.38' @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-23.0.0/tempest/api/compute/images/test_images.py000066400000000000000000000123341361143263300233130ustar00rootroot00000000000000# 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() if cls.is_requested_microversion_compatible('2.35'): cls.client = cls.compute_images_client else: cls.client = cls.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']) # wait for server active to avoid conflict when deleting server # in task_state image_snapshot self.addCleanup(waiters.wait_for_server_status, self.servers_client, server['id'], 'ACTIVE') 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-23.0.0/tempest/api/compute/images/test_images_negative.py000066400000000000000000000136321361143263300251770ustar00rootroot00000000000000# 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 ImagesNegativeTestBase(base.BaseV2ComputeTest): @classmethod def skip_checks(cls): super(ImagesNegativeTestBase, 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(ImagesNegativeTestBase, cls).setup_clients() cls.client = cls.compute_images_client class ImagesNegativeTestJSON(ImagesNegativeTestBase): @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) class ImagesDeleteNegativeTestJSON(ImagesNegativeTestBase): max_microversion = '2.35' @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-23.0.0/tempest/api/compute/images/test_images_oneserver.py000066400000000000000000000102601361143263300253770ustar00rootroot00000000000000# 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 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() if cls.is_requested_microversion_compatible('2.35'): cls.client = cls.compute_images_client else: cls.client = cls.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): if self.is_requested_microversion_compatible('2.35'): MIN_DISK = 'minDisk' MIN_RAM = 'minRam' else: MIN_DISK = 'min_disk' MIN_RAM = 'min_ram' # 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']) if self.is_requested_microversion_compatible('2.35'): self.assertEqual('test', image['metadata']['image_type']) else: self.assertEqual('test', image['image_type']) original_image = self.client.show_image(self.image_ref) if self.is_requested_microversion_compatible('2.35'): original_image = original_image['image'] # Verify minRAM is the same as the original image self.assertEqual(image[MIN_RAM], original_image[MIN_RAM]) # 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[MIN_DISK]), (str(original_image[MIN_DISK]), 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')) self.create_image_from_server(self.server_id, name=utf8_name, wait_until='ACTIVE') tempest-23.0.0/tempest/api/compute/images/test_images_oneserver_negative.py000066400000000000000000000142761361143263300272740ustar00rootroot00000000000000# 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.""" super(ImagesOneServerNegativeTestJSON, self).tearDown() # NOTE(zhufl): Because server_check_teardown will raise Exception # which will prevent other cleanup steps from being executed, so # server_check_teardown should be called after super's tearDown. self.server_check_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() if cls.is_requested_microversion_compatible('2.35'): cls.client = cls.compute_images_client else: cls.client = cls.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.compute_images_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-23.0.0/tempest/api/compute/images/test_list_image_filters.py000066400000000000000000000322421361143263300257130ustar00rootroot00000000000000# 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): max_microversion = '2.35' @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-23.0.0/tempest/api/compute/images/test_list_image_filters_negative.py000066400000000000000000000033101361143263300275670ustar00rootroot00000000000000# 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): max_microversion = '2.35' @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-23.0.0/tempest/api/compute/images/test_list_images.py000066400000000000000000000042241361143263300243450ustar00rootroot00000000000000# 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): max_microversion = '2.35' @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-23.0.0/tempest/api/compute/keypairs/000077500000000000000000000000001361143263300210145ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/keypairs/__init__.py000066400000000000000000000000001361143263300231130ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/keypairs/base.py000066400000000000000000000035371361143263300223100ustar00rootroot00000000000000# 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.""" def _delete_keypair(self, keypair_name, client=None, **params): if not client: client = self.keypairs_client client.delete_keypair(keypair_name, **params) def create_keypair(self, keypair_name=None, pub_key=None, keypair_type=None, user_id=None, client=None): if not client: client = self.keypairs_client 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 = client.create_keypair(**kwargs)['keypair'] self.addCleanup(self._delete_keypair, keypair_name, client, **delete_params) return body tempest-23.0.0/tempest/api/compute/keypairs/test_keypairs.py000066400000000000000000000101361361143263300242550ustar00rootroot00000000000000# 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.keypairs_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.keypairs_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-23.0.0/tempest/api/compute/keypairs/test_keypairs_negative.py000066400000000000000000000100441361143263300261350ustar00rootroot00000000000000# 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.keypairs_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.keypairs_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.keypairs_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 = r'key_/.\@:' self.assertRaises(lib_exc.BadRequest, self.create_keypair, k_name) tempest-23.0.0/tempest/api/compute/keypairs/test_keypairs_v22.py000066400000000000000000000044741361143263300247560ustar00rootroot00000000000000# 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.keypairs_client.show_keypair(k_name)['keypair'] self._check_keypair_type(keypair_detail, keypair_type) fetched_list = self.keypairs_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-23.0.0/tempest/api/compute/limits/000077500000000000000000000000001361143263300204665ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/limits/__init__.py000066400000000000000000000000001361143263300225650ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/limits/test_absolute_limits.py000066400000000000000000000026121361143263300252770ustar00rootroot00000000000000# 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): max_microversion = '2.56' @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 (will be checked # by schema) self.client.show_limits() class AbsoluteLimitsV257TestJSON(base.BaseV2ComputeTest): min_microversion = '2.57' max_microversion = 'latest' # NOTE(felipemonteiro): This class tests the Absolute Limits APIs # response schema for the 2.57 microversion. tempest-23.0.0/tempest/api/compute/limits/test_absolute_limits_negative.py000066400000000000000000000042771361143263300271720ustar00rootroot00000000000000# 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_metadata_exceed_limit(self): # We should not create vm with metadata over maxServerMeta limit # Get max limit value limits = self.client.show_limits()['limits'] max_meta = limits['absolute']['maxServerMeta'] # No point in running this test if there is no limit. if max_meta == -1: raise self.skipException('no limit for maxServerMeta') # 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-23.0.0/tempest/api/compute/security_groups/000077500000000000000000000000001361143263300224335ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/security_groups/__init__.py000066400000000000000000000000001361143263300245320ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/security_groups/base.py000066400000000000000000000026311361143263300237210ustar00rootroot00000000000000# 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): max_microversion = '2.35' create_default_network = True @classmethod def skip_checks(cls): super(BaseSecurityGroupsTest, cls).skip_checks() if not utils.get_service_list()['network']: raise cls.skipException("network service not enabled.") @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-23.0.0/tempest/api/compute/security_groups/test_security_group_rules.py000066400000000000000000000165521361143263300303520ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/security_groups/test_security_group_rules_negative.py000066400000000000000000000170431361143263300322300ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/security_groups/test_security_groups.py000066400000000000000000000176031361143263300273210ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/security_groups/test_security_groups_negative.py000066400000000000000000000212431361143263300311760ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/servers/000077500000000000000000000000001361143263300206565ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/servers/__init__.py000066400000000000000000000000001361143263300227550ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/servers/test_attach_interfaces.py000066400000000000000000000475611361143263300257530ustar00rootroot00000000000000# 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 from oslo_log import log 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.common.utils import data_utils from tempest.lib.common.utils.linux import remote_client from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF LOG = log.getLogger(__name__) class AttachInterfacesTestBase(base.BaseV2ComputeTest): @classmethod def skip_checks(cls): super(AttachInterfacesTestBase, 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.") if not CONF.validation.run_validation: raise cls.skipException('Validation should be enabled to ensure ' 'guest OS is running and capable of ' 'processing ACPI events.') @classmethod def setup_credentials(cls): # This test class requires network and subnet cls.set_network_resources(network=True, subnet=True, router=True, dhcp=True) super(AttachInterfacesTestBase, cls).setup_credentials() @classmethod def setup_clients(cls): super(AttachInterfacesTestBase, cls).setup_clients() cls.subnets_client = cls.os_primary.subnets_client cls.ports_client = cls.os_primary.ports_client def _wait_for_validation(self, server, validation_resources): 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() def _create_server_get_interfaces(self): validation_resources = self.get_test_validation_resources( self.os_primary) server = self.create_test_server( validatable=True, validation_resources=validation_resources, wait_until='ACTIVE') # NOTE(mgoddard): Get detailed server to ensure addresses are present # in fixed IP case. server = self.servers_client.show_server(server['id'])['server'] # NOTE(artom) self.create_test_server adds cleanups, but this is # apparently not enough? Add cleanup here. self.addCleanup(self.delete_server, server['id']) self._wait_for_validation(server, validation_resources) 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 class AttachInterfacesTestJSON(AttachInterfacesTestBase): 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, server_id=None, port_id=None, network_id=None, fixed_ip=None, mac_addr=None): if server_id: iface = waiters.wait_for_interface_status( self.interfaces_client, server_id, iface['port_id'], 'ACTIVE') 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 _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'] self._check_interface(iface, server_id=server['id'], 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, name=data_utils.rand_name(self.__class__.__name__)) 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'] self._check_interface(iface, server_id=server['id'], port_id=port_id, network_id=network_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(test_utils.call_and_ignore_notfound_exc, self.ports_client.delete_port, iface['port_id']) self._check_interface(iface, server_id=server['id'], 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_by_network_port(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) _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.idempotent_id('d290c06c-f5b3-11e7-8ec8-002293781009') @utils.services('network') def test_create_list_show_delete_interfaces_by_fixed_ip(self): # NOTE(zhufl) By default only project that is admin or network owner # or project with role advsvc is authorised to create interfaces with # fixed-ip, so if we don't create network for each project, do not # test _test_create_interface_by_fixed_ips. if not (CONF.auth.use_dynamic_credentials and CONF.auth.create_isolated_networks and not CONF.network.shared_physical_network): raise self.skipException("Only owner network supports " "creating interface by fixed ip.") server, ifs = self._create_server_get_interfaces() interface_count = len(ifs) self.assertGreater(interface_count, 0) 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.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, name=data_utils.rand_name(self.__class__.__name__)) port_id = port['port']['id'] self.addCleanup(self.ports_client.delete_port, port_id) # NOTE(artom) We create two servers one at a time because # create_test_server doesn't support multiple validatable servers. validation_resources = self.get_test_validation_resources( self.os_primary) def _create_validatable_server(): _, servers = compute.create_test_server( self.os_primary, tenant_network=network, wait_until='ACTIVE', validatable=True, validation_resources=validation_resources) return servers[0] servers = [_create_validatable_server(), _create_validatable_server()] # 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: # NOTE(mgoddard): Get detailed server to ensure addresses are # present in fixed IP case. server = self.servers_client.show_server(server['id'])['server'] self._wait_for_validation(server, validation_resources) # attach the port to the server iface = self.interfaces_client.create_interface( server['id'], port_id=port_id)['interfaceAttachment'] self._check_interface(iface, server_id=server['id'], 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) class AttachInterfacesUnderV243Test(AttachInterfacesTestBase): max_microversion = '2.43' @decorators.attr(type='smoke') @decorators.idempotent_id('c7e0e60b-ee45-43d0-abeb-8596fd42a2f9') @utils.services('network') def test_add_remove_fixed_ip(self): # NOTE(zhufl) By default only project that is admin or network owner # or project with role advsvc is authorised to add interfaces with # fixed-ip, so if we don't create network for each project, do not # test if not (CONF.auth.use_dynamic_credentials and CONF.auth.create_isolated_networks and not CONF.network.shared_physical_network): raise self.skipException("Only owner network supports " "creating interface by fixed ip.") # Add and Remove the fixed IP to server. server, ifs = self._create_server_get_interfaces() original_interface_count = len(ifs) # This is the number of ports. self.assertGreater(original_interface_count, 0) # Get the starting list of IPs on the server. addresses = self.os_primary.servers_client.list_addresses( server['id'])['addresses'] # There should be one entry for the single network mapped to a list of # addresses, which at this point should have at least one entry. # Note that we could start with two addresses depending on how tempest # is configured for using floating IPs. self.assertEqual(1, len(addresses), addresses) # number of networks # Keep track of the original addresses so we can know which IP is new. original_ips = [addr['addr'] for addr in list(addresses.values())[0]] original_ip_count = len(original_ips) self.assertGreater(original_ip_count, 0, addresses) # at least 1 network_id = ifs[0]['net_id'] # Add another fixed IP to the server. This should result in another # fixed IP on the same network (and same port since we only have one # port). self.servers_client.add_fixed_ip(server['id'], networkId=network_id) # Wait for the ips count to increase by one. def _get_server_floating_ips(): _floating_ips = [] _server = self.os_primary.servers_client.show_server( server['id'])['server'] for _ip_set in _server['addresses']: for _ip in _server['addresses'][_ip_set]: if _ip['OS-EXT-IPS:type'] == 'floating': _floating_ips.append(_ip['addr']) return _floating_ips def _wait_for_ip_increase(): _addresses = self.os_primary.servers_client.list_addresses( server['id'])['addresses'] _ips = [addr['addr'] for addr in list(_addresses.values())[0]] LOG.debug("Wait for IP increase. All IPs still associated to " "the server %(id)s: %(ips)s", {'id': server['id'], 'ips': _ips}) if len(_ips) == original_ip_count + 1: return True elif len(_ips) == original_ip_count: return False # If not, lets remove any floating IP from the list and check again _fips = _get_server_floating_ips() _ips = [_ip for _ip in _ips if _ip not in _fips] LOG.debug("Wait for IP increase. Fixed IPs still associated to " "the server %(id)s: %(ips)s", {'id': server['id'], 'ips': _ips}) return len(_ips) == original_ip_count + 1 if not test_utils.call_until_true( _wait_for_ip_increase, CONF.compute.build_timeout, CONF.compute.build_interval): raise lib_exc.TimeoutException( 'Timed out while waiting for IP count to increase.') # Remove the fixed IP that we just added. 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' and ip['addr'] not in original_ips): fixed_ip = ip['addr'] break if fixed_ip is not None: break self.servers_client.remove_fixed_ip(server['id'], address=fixed_ip) # Wait for the interface count to decrease by one. def _wait_for_ip_decrease(): _addresses = self.os_primary.servers_client.list_addresses( server['id'])['addresses'] _ips = [addr['addr'] for addr in list(_addresses.values())[0]] LOG.debug("Wait for IP decrease. All IPs still associated to " "the server %(id)s: %(ips)s", {'id': server['id'], 'ips': _ips}) if len(_ips) == original_ip_count: return True # If not, lets remove any floating IP from the list and check again _fips = _get_server_floating_ips() _ips = [_ip for _ip in _ips if _ip not in _fips] LOG.debug("Wait for IP decrease. Fixed IPs still associated to " "the server %(id)s: %(ips)s", {'id': server['id'], 'ips': _ips}) return len(_ips) == original_ip_count if not test_utils.call_until_true( _wait_for_ip_decrease, CONF.compute.build_timeout, CONF.compute.build_interval): raise lib_exc.TimeoutException( 'Timed out while waiting for IP count to decrease.') tempest-23.0.0/tempest/api/compute/servers/test_availability_zone.py000066400000000000000000000023741361143263300260020ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/servers/test_create_server.py000066400000000000000000000156451361143263300251330ustar00rootroot00000000000000# 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.assert_flavor_equal(self.flavor_ref, self.server['flavor']) 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.split(".")[0])) # NOTE(zhufl): Some images will add postfix for the hostname, e.g., # if hostname is "aaa", postfix ".novalocal" may be added to it, and # the hostname will be "aaa.novalocal" then, so we should ignore the # postfix when checking whether hostname equals self.name. self.assertEqual(self.name.lower(), hostname.split(".")[0], 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-23.0.0/tempest/api/compute/servers/test_create_server_multi_nic.py000066400000000000000000000121061361143263300271630ustar00rootroot00000000000000# 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.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 skip_checks(cls): super(ServersTestMultiNic, cls).skip_checks() if not CONF.service_available.neutron: raise cls.skipException('Neutron service must be available.') @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') 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') 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-23.0.0/tempest/api/compute/servers/test_delete_server.py000066400000000000000000000125721361143263300251260ustar00rootroot00000000000000# 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): create_default_network = True # 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 server = self.create_test_server(wait_until='ACTIVE') volume = self.create_volume() self.attach_volume(server, volume) 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-23.0.0/tempest/api/compute/servers/test_device_tagging.py000066400000000000000000000404001361143263300252240ustar00rootroot00000000000000# 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. from oslo_log import log as logging from oslo_serialization import jsonutils as json from tempest.api.compute import base 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.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 DeviceTaggingBase(base.BaseV2ComputeTest): @classmethod def skip_checks(cls): super(DeviceTaggingBase, 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(DeviceTaggingBase, 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(DeviceTaggingBase, cls).setup_credentials() def verify_metadata_from_api(self, server, ssh_client, verify_method): 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: 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 = ssh_client.exec_command(cmd) return verify_method(md_json) # NOTE(gmann) Keep refreshing the metadata info until the metadata # cache is refreshed. For safer side, we will go with wait loop of # build_interval till build_timeout. verify_method() above will return # True if all metadata verification is done as expected. 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.') def verify_metadata_on_config_drive(self, server, ssh_client, verify_method): LOG.info('Attempting to verify tagged devices in server %s via ' 'the config drive.', server['id']) ssh_client.mount_config_drive() cmd_md = 'sudo cat /mnt/openstack/latest/meta_data.json' md_json = ssh_client.exec_command(cmd_md) verify_method(md_json) ssh_client.unmount_config_drive() class TaggedBootDevicesTest(DeviceTaggingBase): 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' 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. # Some hypervisors might report devices with no tags as well. found_devices = [d['tags'][0] for d in md_dict['devices'] if d.get('tags')] try: self.assertEqual(set(found_devices), set(['port-1', 'port-2', 'net-1', 'net-2-100', 'net-2-200', 'boot', 'other'])) return True except Exception: return False # NOTE(mriedem): This is really more like a scenario test and is slow so # it's marked as such. @decorators.attr(type='slow') @decorators.idempotent_id('a2e65a6c-66f1-4442-aaa8-498c31778d96') @utils.services('network', 'volume', 'image') def test_tagged_boot_devices(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'], name=data_utils.rand_name(self.__class__.__name__), 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'], name=data_utils.rand_name(self.__class__.__name__), fixed_ips=[{'subnet_id': subnet1['id']}])['port'] self.addCleanup(self.ports_client.delete_port, self.port2['id']) # Create server 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, wait_until='ACTIVE', validation_resources=validation_resources, config_drive=config_drive_enabled, 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']) server = self.servers_client.show_server(server['id'])['server'] ssh_client = remote_client.RemoteClient( self.get_server_ip(server, validation_resources), CONF.validation.image_ssh_user, pkey=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 API if CONF.compute_feature_enabled.metadata_service: self.verify_metadata_from_api(server, ssh_client, self.verify_device_metadata) # Verify metadata on config drive if CONF.compute_feature_enabled.config_drive: self.verify_metadata_on_config_drive(server, ssh_client, self.verify_device_metadata) class TaggedBootDevicesTest_v242(TaggedBootDevicesTest): min_microversion = '2.42' max_microversion = 'latest' class TaggedAttachmentsTest(DeviceTaggingBase): min_microversion = '2.49' max_microversion = 'latest' @classmethod def skip_checks(cls): super(TaggedAttachmentsTest, cls).skip_checks() if not CONF.compute_feature_enabled.metadata_service: raise cls.skipException('Metadata API must be enabled') def verify_device_metadata(self, md_json): md_dict = json.loads(md_json) found_devices = [d['tags'][0] for d in md_dict['devices'] if d.get('tags')] try: self.assertItemsEqual(found_devices, ['nic-tag', 'volume-tag']) return True except Exception: return False def verify_empty_devices(self, md_json): md_dict = json.loads(md_json) try: self.assertEmpty(md_dict['devices']) return True except AssertionError: LOG.debug("Related bug 1775947. Devices dict is not empty: %s", md_dict['devices']) return False @decorators.idempotent_id('3e41c782-2a89-4922-a9d2-9a188c4e7c7c') @utils.services('network', 'volume', 'image') def test_tagged_attachment(self): # Create network net = self.networks_client.create_network( name=data_utils.rand_name( 'tagged-attachments-test-net'))['network'] self.addCleanup(self.networks_client.delete_network, net['id']) # Create subnet subnet = self.subnets_client.create_subnet( network_id=net['id'], cidr='10.10.10.0/24', ip_version=4)['subnet'] self.addCleanup(self.subnets_client.delete_subnet, subnet['id']) # Create volume volume = self.create_volume() # Boot test server 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, name=data_utils.rand_name('device-tagging-server'), networks=[{'uuid': self.get_tenant_network()['id']}]) self.addCleanup(self.delete_server, server['id']) # NOTE(mgoddard): Get detailed server to ensure addresses are present # in fixed IP case. server = self.servers_client.show_server(server['id'])['server'] # Attach tagged nic and volume interface = self.interfaces_client.create_interface( server['id'], net_id=net['id'], tag='nic-tag')['interfaceAttachment'] self.attach_volume(server, volume, tag='volume-tag') ssh_client = remote_client.RemoteClient( self.get_server_ip(server, validation_resources), CONF.validation.image_ssh_user, pkey=validation_resources['keypair']['private_key'], server=server, servers_client=self.servers_client) self.verify_metadata_from_api(server, ssh_client, self.verify_device_metadata) # Detach tagged nic and volume self.servers_client.detach_volume(server['id'], volume['id']) waiters.wait_for_volume_resource_status(self.volumes_client, volume['id'], 'available') self.interfaces_client.delete_interface(server['id'], interface['port_id']) waiters.wait_for_interface_detach(self.interfaces_client, server['id'], interface['port_id']) # FIXME(mriedem): The assertion that the tagged devices are removed # from the metadata for the server is being skipped until bug 1775947 # is fixed. # self.verify_metadata_from_api(server, ssh_client, # self.verify_empty_devices) tempest-23.0.0/tempest/api/compute/servers/test_disk_config.py000066400000000000000000000142651361143263300245560ustar00rootroot00000000000000# 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): create_default_network = True @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-23.0.0/tempest/api/compute/servers/test_instance_actions.py000066400000000000000000000061141361143263300256150ustar00rootroot00000000000000# 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): create_default_network = True @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): create_default_network = True 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-23.0.0/tempest/api/compute/servers/test_instance_actions_negative.py000066400000000000000000000037721361143263300275060ustar00rootroot00000000000000# 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): create_default_network = True @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-23.0.0/tempest/api/compute/servers/test_list_server_filters.py000066400000000000000000000352431361143263300263670ustar00rootroot00000000000000# 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 = [r'^.*\-instance\-[0-9]+$', r'^.*\-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-23.0.0/tempest/api/compute/servers/test_list_servers_negative.py000066400000000000000000000150411361143263300266760ustar00rootroot00000000000000# 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): create_default_network = True @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): # When invalid status is specified, up to microversion 2.37, # an empty list is returned, and starting from microversion 2.38, # a 400 error is returned in that case. if self.is_requested_microversion_compatible('2.37'): body = self.client.list_servers(status='non_existing_status') servers = body['servers'] self.assertEmpty(servers) else: self.assertRaises(lib_exc.BadRequest, self.client.list_servers, status='non_existing_status') @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. # updated_at field may haven't been set at the point in the boot # process where build_request still exists, so add # {'status': 'ACTIVE'} along with changes-since as filter. changes_since = {'changes-since': '2051-01-01T12:34:00Z', 'status': 'ACTIVE'} 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-23.0.0/tempest/api/compute/servers/test_multiple_create.py000066400000000000000000000036541361143263300254550ustar00rootroot00000000000000# 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): create_default_network = True @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-23.0.0/tempest/api/compute/servers/test_multiple_create_negative.py000066400000000000000000000045641361143263300273400ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/servers/test_novnc.py000066400000000000000000000242751361143263300234240ustar00rootroot00000000000000# 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 six.moves.urllib.parse as urlparse 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): create_default_network = True @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): super(NoVNCConsoleTestJSON, self).tearDown() if self._websocket is not None: self._websocket.close() # NOTE(zhufl): Because server_check_teardown will raise Exception # which will prevent other cleanup steps from being executed, so # server_check_teardown should be called after super's tearDown. self.server_check_teardown() @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") cls.use_get_remote_console = False if not cls.is_requested_microversion_compatible('2.5'): cls.use_get_remote_console = True 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() # This is needed in the case of example: self.assertRegex(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): """Verify that the websocket upgrade was successful. Parses response and ensures that required response fields are present and accurate. (https://tools.ietf.org/html/rfc7231#section-6.2.2) """ self.assertTrue( self._websocket.response.startswith(b'HTTP/1.1 101 Switching ' b'Protocols'), 'Incorrect HTTP return status code: {}'.format( six.text_type(self._websocket.response) ) ) _required_header = 'upgrade: websocket' _response = six.text_type(self._websocket.response).lower() self.assertIn( _required_header, _response, 'Did not get the expected WebSocket HTTP Response.' ) @decorators.idempotent_id('c640fdff-8ab4-45a4-a5d8-7e6146cbd0dc') def test_novnc(self): if self.use_get_remote_console: body = self.client.get_remote_console( self.server['id'], console_type='novnc', protocol='vnc')['remote_console'] else: 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): if self.use_get_remote_console: body = self.client.get_remote_console( self.server['id'], console_type='novnc', protocol='vnc')['remote_console'] else: 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 parts = urlparse.urlparse(body['url']) qparams = urlparse.parse_qs(parts.query) if 'path' in qparams: qparams['path'] = urlparse.unquote(qparams['path'][0]).replace( 'token=', 'token=bad') elif 'token' in qparams: qparams['token'] = 'bad' + qparams['token'][0] new_query = urlparse.urlencode(qparams) new_parts = urlparse.ParseResult(parts.scheme, parts.netloc, parts.path, parts.params, new_query, parts.fragment) url = urlparse.urlunparse(new_parts) 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-23.0.0/tempest/api/compute/servers/test_server_actions.py000066400000000000000000001032621361143263300253210ustar00rootroot00000000000000# 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): super(ServerActionsTestJSON, self).tearDown() # NOTE(zhufl): Because server_check_teardown will raise Exception # which will prevent other cleanup steps from being executed, so # server_check_teardown should be called after super's tearDown. self.server_check_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') self.addCleanup(self.delete_server, newserver['id']) # 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.assert_flavor_equal(self.flavor_ref, rebuilt_server['flavor']) # 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.assert_flavor_equal(self.flavor_ref, rebuilt_server['flavor']) # 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) # NOTE(mriedem): Marked as slow because while rebuild and volume-backed is # common, we don't actually change the image (you can't with volume-backed # rebuild) so this isn't testing much outside normal rebuild # (and it's slow). @decorators.attr(type='slow') @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']) if CONF.validation.run_validation: validation_resources = self.get_class_validation_resources( self.os_primary) linux_client = remote_client.RemoteClient( self.get_server_ip(server, validation_resources), self.ssh_user, password=None, pkey=validation_resources['keypair']['private_key'], server=server, servers_client=self.client) linux_client.validate_authentication() 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.assert_flavor_equal(self.flavor_ref_alt, server['flavor']) 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') # NOTE(mgoddard): Get detailed server to ensure addresses are present # in fixed IP case. server = self.servers_client.show_server(server['id'])['server'] self._test_resize_server_confirm(server['id']) if CONF.compute_feature_enabled.console_output: # 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']) if CONF.validation.run_validation: validation_resources = self.get_class_validation_resources( self.os_primary) linux_client = remote_client.RemoteClient( self.get_server_ip(server, validation_resources), self.ssh_user, password=None, pkey=validation_resources['keypair']['private_key'], server=server, servers_client=self.client) linux_client.validate_authentication() @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.assert_flavor_equal(self.flavor_ref, server['flavor']) @decorators.idempotent_id('fbbf075f-a812-4022-bc5c-ccb8047eef12') @decorators.related_bug('1737599') @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') @utils.services('volume') def test_resize_server_revert_with_volume_attached(self): # Tests attaching a volume to a server instance and then resizing # the instance. Once the instance is resized, revert the resize which # should move the instance and volume attachment back to the original # compute host. # Create a blank volume and attach it to the server created in setUp. volume = self.create_volume() server = self.client.show_server(self.server_id)['server'] self.attach_volume(server, volume) # Now resize the server with the blank volume attached. self.client.resize_server(self.server_id, self.flavor_ref_alt) # 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') # Now revert the resize which should move the instance and it's volume # attachment back to the original source compute host. self.client.revert_resize_server(self.server_id) waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE') # Make sure everything still looks OK. server = self.client.show_server(self.server_id)['server'] self.assert_flavor_equal(self.flavor_ref, server['flavor']) attached_volumes = server['os-extended-volumes:volumes_attached'] self.assertEqual(1, len(attached_volumes)) self.assertEqual(volume['id'], attached_volumes[0]['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) 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.response, "lt"): image1_id = resp['image_id'] else: image1_id = data_utils.parse_image_id(resp.response['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) if api_version_utils.compare_version_header_to_response( "OpenStack-API-Version", "compute 2.45", resp.response, "lt"): image2_id = resp['image_id'] else: image2_id = data_utils.parse_image_id(resp.response['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) if api_version_utils.compare_version_header_to_response( "OpenStack-API-Version", "compute 2.45", resp.response, "lt"): image3_id = resp['image_id'] else: image3_id = data_utils.parse_image_id(resp.response['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=3)['output'] self.assertTrue(output, "Console output was empty.") lines = len(output.split('\n')) self.assertEqual(lines, 3) @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, 3, "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) def _unshelve_server(): server_info = self.client.show_server(self.server_id)['server'] if 'SHELVED' in server_info['status']: self.client.unshelve_server(self.server_id) self.addCleanup(_unshelve_server) 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('8cf9f450-a871-42cf-9bef-77eba189c0b0') @decorators.related_bug('1745529') @testtools.skipUnless(CONF.compute_feature_enabled.shelve, 'Shelve is not available.') @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') def test_shelve_paused_server(self): server = self.create_test_server(wait_until='ACTIVE') self.client.pause_server(server['id']) waiters.wait_for_server_status(self.client, server['id'], 'PAUSED') # Check if Shelve operation is successful on paused server. compute.shelve_server(self.client, server['id'], force_shelve_offload=True) @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): if self.is_requested_microversion_compatible('2.5'): body = self.client.get_vnc_console( self.server_id, type='novnc')['console'] else: body = self.client.get_remote_console( self.server_id, console_type='novnc', protocol='vnc')['remote_console'] self.assertEqual('novnc', body['type']) self.assertNotEqual('', body['url']) self._validate_url(body['url']) tempest-23.0.0/tempest/api/compute/servers/test_server_addresses.py000066400000000000000000000051261361143263300256360ustar00rootroot00000000000000# 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): create_default_network = True @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-23.0.0/tempest/api/compute/servers/test_server_addresses_negative.py000066400000000000000000000037661361143263300275300ustar00rootroot00000000000000# 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): create_default_network = True @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-23.0.0/tempest/api/compute/servers/test_server_group.py000066400000000000000000000135741361143263300250230ustar00rootroot00000000000000# 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 """ create_default_network = True @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'] def setUp(self): super(ServerGroupTestJSON, self).setUp() # TODO(zhufl): After microversion 2.13 project_id and user_id are # added to the body of server_group, and microversion is not used # in resource_setup for now, so we should create server group in setUp # in order to use the same microversion as in testcases till # microversion support in resource_setup is fulfilled. if not hasattr(self, 'created_server_group'): self.__class__.created_server_group = \ self.create_test_server_group(policy=self.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-23.0.0/tempest/api/compute/servers/test_server_metadata.py000066400000000000000000000117601361143263300254420ustar00rootroot00000000000000# 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): create_default_network = True @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-23.0.0/tempest/api/compute/servers/test_server_metadata_negative.py000066400000000000000000000171521361143263300273250ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/servers/test_server_password.py000066400000000000000000000024431361143263300255220ustar00rootroot00000000000000# 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): create_default_network = True @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.servers_client.show_password(self.server['id']) @decorators.idempotent_id('f8229e8b-b625-4493-800a-bde86ac611ea') def test_delete_server_password(self): self.servers_client.delete_password(self.server['id']) tempest-23.0.0/tempest/api/compute/servers/test_server_personality.py000066400000000000000000000201601361143263300262250ustar00rootroot00000000000000# 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 # NOTE(mriedem): Marked as slow because personality (file injection) is # deprecated in nova so we don't care as much about running this all the # time (and it's slow). @decorators.attr(type='slow') @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)) # NOTE(mriedem): Marked as slow because personality (file injection) is # deprecated in nova so we don't care as much about running this all the # time (and it's slow). @decorators.attr(type='slow') @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) # NOTE(mriedem): Marked as slow because personality (file injection) is # deprecated in nova so we don't care as much about running this all the # time (and it's slow). @decorators.attr(type='slow') @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-23.0.0/tempest/api/compute/servers/test_server_rescue.py000066400000000000000000000105641361143263300251510ustar00rootroot00000000000000# 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 ServerRescueTestBase(base.BaseV2ComputeTest): @classmethod def skip_checks(cls): super(ServerRescueTestBase, 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(ServerRescueTestBase, cls).setup_credentials() @classmethod def resource_setup(cls): super(ServerRescueTestBase, 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'] class ServerRescueTestJSON(ServerRescueTestBase): @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') class ServerRescueTestJSONUnderV235(ServerRescueTestBase): max_microversion = '2.35' # TODO(zhufl): After 2.35 we should switch to neutron client to create # floating ip, but that will need admin credential, so the testcases will # have to be added in somewhere in admin directory. @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-23.0.0/tempest/api/compute/servers/test_server_rescue_negative.py000066400000000000000000000143431361143263300270320ustar00rootroot00000000000000# 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.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']) @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) # 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-23.0.0/tempest/api/compute/servers/test_server_tags.py000066400000000000000000000105401361143263300246130ustar00rootroot00000000000000# 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' create_default_network = True @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-23.0.0/tempest/api/compute/servers/test_servers.py000066400000000000000000000236721361143263300237720ustar00rootroot00000000000000# 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 import decorators CONF = config.CONF class ServersTestJSON(base.BaseV2ComputeTest): create_default_network = True @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(self.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(self.delete_server, id1) server = self.create_test_server(name=server_name, wait_until='ACTIVE') id2 = server['id'] self.addCleanup(self.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, wait_until='ACTIVE') self.addCleanup(self.delete_server, server['id']) 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(self.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(self.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', wait_until='ACTIVE') self.addCleanup(self.delete_server, server['id']) server = self.client.show_server(server['id'])['server'] self.assertEqual('2001:2001::3', server['accessIPv6']) @decorators.related_bug('1730756') @decorators.idempotent_id('defbaca5-d611-49f5-ae21-56ee25d2db49') def test_create_server_specify_multibyte_character_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 # 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')) self.create_test_server(name=utf8_name, wait_until='ACTIVE') class ServerShowV247Test(base.BaseV2ComputeTest): min_microversion = '2.47' max_microversion = 'latest' # NOTE(gmann): This test tests the server APIs response schema # Along with 2.47 microversion schema this test class tests the # other microversions 2.9, 2.19 and 2.26 server APIs response schema # also. 2.47 APIs schema are on top of 2.9->2.19->2.26 schema so # below tests cover all of the schema. @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']) @decorators.idempotent_id('8de397c2-57d0-4b90-aa30-e5d668f21a8b') def test_update_rebuild_list_server(self): server = self.create_test_server() # Checking update API response schema self.servers_client.update_server(server['id']) waiters.wait_for_server_status(self.servers_client, server['id'], 'ACTIVE') # Checking rebuild API response schema self.servers_client.rebuild_server(server['id'], self.image_ref_alt) waiters.wait_for_server_status(self.servers_client, server['id'], 'ACTIVE') # Checking list details API response schema self.servers_client.list_servers(detail=True) class ServerShowV263Test(base.BaseV2ComputeTest): min_microversion = '2.63' max_microversion = 'latest' @testtools.skipUnless(CONF.compute.certified_image_ref, '``[compute]/certified_image_ref`` required to test ' 'image certificate validation.') @testtools.skipUnless(CONF.compute.certified_image_trusted_certs, '``[compute]/certified_image_trusted_certs`` ' 'required to test image certificate validation.') @decorators.idempotent_id('71b8e3d5-11d2-494f-b917-b094a4afed3c') def test_show_update_rebuild_list_server(self): trusted_certs = CONF.compute.certified_image_trusted_certs server = self.create_test_server( image_id=CONF.compute.certified_image_ref, trusted_image_certificates=trusted_certs, wait_until='ACTIVE') # Check show API response schema self.servers_client.show_server(server['id'])['server'] # Check update API response schema self.servers_client.update_server(server['id']) waiters.wait_for_server_status(self.servers_client, server['id'], 'ACTIVE') # Check rebuild API response schema self.servers_client.rebuild_server( server['id'], CONF.compute.certified_image_ref) waiters.wait_for_server_status(self.servers_client, server['id'], 'ACTIVE') # Check list details API response schema params = {'trusted_image_certificates': trusted_certs} servers = self.servers_client.list_servers( detail=True, **params)['servers'] self.assertNotEmpty(servers) tempest-23.0.0/tempest/api/compute/servers/test_servers_microversions.py000066400000000000000000000055531361143263300267520ustar00rootroot00000000000000# Copyright 2018 NEC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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 # NOTE(gmann): This file is to write the tests which mainly # tests newly added microversion schema related to servers APIs. # As per (https://docs.openstack.org/tempest/latest/microversion_testing. # html#tempest-scope-for-microversion-testing), # we need to fill the API response schema gaps which gets modified # during microversion change. To cover the testing of such schema # we need to have operation schema test which just test # the microversion schemas. # If you are adding server APIs microversion schema file without # their integration tests, you can add tests to cover those schema # in this file. class ServerShowV254Test(base.BaseV2ComputeTest): min_microversion = '2.54' max_microversion = 'latest' @decorators.idempotent_id('09170a98-4940-4637-add7-1a35121f1a5a') def test_rebuild_server(self): server = self.create_test_server(wait_until='ACTIVE') keypair_name = data_utils.rand_name( self.__class__.__name__ + '-keypair') kwargs = {'name': keypair_name} self.keypairs_client.create_keypair(**kwargs) self.addCleanup(self.keypairs_client.delete_keypair, keypair_name) # Checking rebuild API response schema self.servers_client.rebuild_server(server['id'], self.image_ref_alt, key_name=keypair_name) waiters.wait_for_server_status(self.servers_client, server['id'], 'ACTIVE') class ServerShowV257Test(base.BaseV2ComputeTest): min_microversion = '2.57' max_microversion = 'latest' @decorators.idempotent_id('803df848-080a-4261-8f11-b020cd9b6f60') def test_rebuild_server(self): server = self.create_test_server(wait_until='ACTIVE') user_data = "ZWNobyAiaGVsbG8gd29ybGQi" # Checking rebuild API response schema self.servers_client.rebuild_server(server['id'], self.image_ref_alt, user_data=user_data) waiters.wait_for_server_status(self.servers_client, server['id'], 'ACTIVE') tempest-23.0.0/tempest/api/compute/servers/test_servers_negative.py000066400000000000000000000616041361143263300256510ustar00rootroot00000000000000# 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): create_default_network = True 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): super(ServersNegativeTestJSON, self).tearDown() # NOTE(zhufl): Because server_check_teardown will raise Exception # which will prevent other cleanup steps from being executed, so # server_check_teardown should be called after super's tearDown. self.server_check_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) def _unshelve_server(): server_info = self.client.show_server(self.server_id)['server'] if 'SHELVED' in server_info['status']: self.client.unshelve_server(self.server_id) self.addCleanup(_unshelve_server) server = self.client.show_server(self.server_id)['server'] image_name = server['name'] + '-shelved' if CONF.image_feature_enabled.api_v1: kwargs = {'name': image_name} else: kwargs = {'params': {'name': image_name}} images = self.images_client.list_images(**kwargs)['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-23.0.0/tempest/api/compute/servers/test_virtual_interfaces.py000066400000000000000000000046571361143263300261740ustar00rootroot00000000000000# 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 # TODO(mriedem): Remove this test class once the nova queens branch goes into # extended maintenance mode. class VirtualInterfacesTestJSON(base.BaseV2ComputeTest): max_microversion = '2.43' depends_on_nova_network = True create_default_network = True @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: 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-23.0.0/tempest/api/compute/servers/test_virtual_interfaces_negative.py000066400000000000000000000034231361143263300300440ustar00rootroot00000000000000# 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 # TODO(mriedem): Remove this test class once the nova queens branch goes into # extended maintenance mode. class VirtualInterfacesNegativeTestJSON(base.BaseV2ComputeTest): max_microversion = '2.43' depends_on_nova_network = True @classmethod def setup_credentials(cls): # For this test no network resources are needed cls.set_network_resources() super(VirtualInterfacesNegativeTestJSON, cls).setup_credentials() @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.servers_client.list_virtual_interfaces, invalid_server_id) tempest-23.0.0/tempest/api/compute/test_extensions.py000066400000000000000000000040621361143263300227770ustar00rootroot00000000000000# 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 = [x['alias'] for x in 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-23.0.0/tempest/api/compute/test_networks.py000066400000000000000000000025711361143263300224570ustar00rootroot00000000000000# 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): max_microversion = '2.35' @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-23.0.0/tempest/api/compute/test_quotas.py000066400000000000000000000101211361143263300221050ustar00rootroot00000000000000# 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(('metadata_items', 'ram', 'key_pairs', 'instances', 'cores', 'server_group_members', 'server_groups')) if cls.is_requested_microversion_compatible('2.35'): cls.default_quota_set = \ cls.default_quota_set | set(['fixed_ips', 'floating_ips', 'security_group_rules', 'security_groups']) if cls.is_requested_microversion_compatible('2.56'): cls.default_quota_set = \ cls.default_quota_set | set(['injected_file_content_bytes', 'injected_file_path_bytes', 'injected_files']) @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-23.0.0/tempest/api/compute/test_tenant_networks.py000066400000000000000000000034431361143263300240270ustar00rootroot00000000000000# 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): max_microversion = '2.35' @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-23.0.0/tempest/api/compute/test_versions.py000066400000000000000000000062401361143263300224500ustar00rootroot00000000000000# 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-23.0.0/tempest/api/compute/volumes/000077500000000000000000000000001361143263300206575ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/volumes/__init__.py000066400000000000000000000000001361143263300227560ustar00rootroot00000000000000tempest-23.0.0/tempest/api/compute/volumes/test_attach_volume.py000066400000000000000000000510311361143263300251230ustar00rootroot00000000000000# 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 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 import decorators CONF = config.CONF class BaseAttachVolumeTest(base.BaseV2ComputeTest): """Base class for the attach volume tests in this module.""" create_default_network = True @classmethod def skip_checks(cls): super(BaseAttachVolumeTest, 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(BaseAttachVolumeTest, cls).setup_credentials() 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 class AttachVolumeTestJSON(BaseAttachVolumeTest): @decorators.idempotent_id('52e9045a-e90d-4c0d-9087-79d657faffff') # This test is conditionally marked slow if SSH validation is enabled. @decorators.attr(type='slow', condition=CONF.validation.run_validation) 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() disks_before_attach = linux_client.count_disks() volume = self.create_volume() # NOTE: As of the 12.0.0 Liberty release, the Nova libvirt driver # no longer honors a user-supplied device name, and there can be # a mismatch between libvirt provide disk name and actual disk name # on instance, hence we no longer validate this test with the supplied # device name rather we count number of disk before attach # detach to validate the testcase. attachment = self.attach_volume(server, volume) 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_after_attach = linux_client.count_disks() self.assertGreater(disks_after_attach, disks_before_attach) 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_after_detach = linux_client.count_disks() self.assertEqual(disks_before_attach, disks_after_detach) @decorators.idempotent_id('7fa563fe-f0f7-43eb-9e22-a1ece036b513') def test_list_get_volume_attachments(self): # List volume attachment of the server server, validation_resources = self._create_server() volume_1st = self.create_volume() attachment_1st = self.attach_volume(server, volume_1st) 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)) 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() 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(BaseAttachVolumeTest): """Testing volume with shelved instance. This test checks the attaching and detaching volumes from a shelved or shelved offload instance. Note that these are uncommon scenarios until blueprint detach-boot-volume is implemented in the compute service. """ 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) # NOTE(mriedem): Marked as slow since this is an uncommon scenario until # attach/detach root volume is supported in nova, and it's slow. @decorators.attr(type='slow') @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) # 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']) # NOTE(mriedem): Marked as slow since this is an uncommon scenario until # attach/detach root volume is supported in nova, and it's slow. @decorators.attr(type='slow') @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) 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) class AttachVolumeMultiAttachTest(BaseAttachVolumeTest): min_microversion = '2.60' max_microversion = 'latest' @classmethod def skip_checks(cls): super(AttachVolumeMultiAttachTest, cls).skip_checks() if not CONF.compute_feature_enabled.volume_multiattach: raise cls.skipException('Volume multi-attach is not available.') def _attach_volume_to_servers(self, volume, servers): """Attaches the given volume to the list of servers. :param volume: The multiattach volume to use. :param servers: list of server instances on which the volume will be attached :returns: dict of server ID to volumeAttachment dict entries """ attachments = {} for server in servers: # map the server id to the volume attachment attachments[server['id']] = self.attach_volume(server, volume) # NOTE(mriedem): In the case of multi-attach, after the first # attach the volume will be in-use. On the second attach, nova will # 'reserve' the volume which puts it back into 'attaching' status # and then the volume shouldn't go back to in-use until the compute # actually attaches the server to the volume. return attachments def _detach_multiattach_volume(self, volume_id, server_id): """Detaches a multiattach volume from the given server. Depending on the number of attachments the volume has, this method will wait for the volume to go to back to 'in-use' status if there are more attachments or 'available' state if there are no more attachments. """ # Count the number of attachments before starting the detach. volume = self.volumes_client.show_volume(volume_id)['volume'] attachments = volume['attachments'] wait_status = 'in-use' if len(attachments) > 1 else 'available' # Now detach the volume from the given server. self.servers_client.detach_volume(server_id, volume_id) # Now wait for the volume status to change. waiters.wait_for_volume_resource_status( self.volumes_client, volume_id, wait_status) def _create_multiattach_volume(self, bootable=False): kwargs = {} if bootable: kwargs['image_ref'] = CONF.compute.image_ref return self.create_volume(multiattach=True, **kwargs) def _create_and_multiattach(self): """Creates two server instances and a volume and attaches to both. :returns: A three-item tuple of the list of created servers, the created volume, and dict of server ID to volumeAttachment dict entries """ servers = [] for x in range(2): name = 'multiattach-server-%i' % x servers.append(self.create_test_server(name=name)) # Now wait for the servers to be ACTIVE. for server in servers: waiters.wait_for_server_status(self.servers_client, server['id'], 'ACTIVE') volume = self._create_multiattach_volume() # Attach the volume to the servers attachments = self._attach_volume_to_servers(volume, servers) return servers, volume, attachments @decorators.idempotent_id('8d5853f7-56e7-4988-9b0c-48cea3c7049a') def test_list_get_volume_attachments_multiattach(self): # Attach a single volume to two servers. servers, volume, attachments = self._create_and_multiattach() # List attachments from the volume and make sure the server uuids # are in that list. vol_attachments = self.volumes_client.show_volume( volume['id'])['volume']['attachments'] attached_server_ids = [attachment['server_id'] for attachment in vol_attachments] self.assertEqual(2, len(attached_server_ids)) # List Volume attachment of the servers for server in servers: self.assertIn(server['id'], attached_server_ids) vol_attachments = self.servers_client.list_volume_attachments( server['id'])['volumeAttachments'] self.assertEqual(1, len(vol_attachments)) attachment = attachments[server['id']] self.assertDictEqual(attachment, vol_attachments[0]) # Detach the volume from this server. self._detach_multiattach_volume(volume['id'], server['id']) def _boot_from_multiattach_volume(self): """Boots a server from a multiattach volume. The volume will not be deleted when the server is deleted. :returns: 2-item tuple of (server, volume) """ volume = self._create_multiattach_volume(bootable=True) # Now create a server from the bootable volume. bdm = [{ 'uuid': volume['id'], 'source_type': 'volume', 'destination_type': 'volume', 'boot_index': 0, 'delete_on_termination': False}] server = self.create_test_server( image_id='', block_device_mapping_v2=bdm, wait_until='ACTIVE') # Assert the volume is attached to the server. attachments = self.servers_client.list_volume_attachments( server['id'])['volumeAttachments'] self.assertEqual(1, len(attachments)) self.assertEqual(volume['id'], attachments[0]['volumeId']) return server, volume @decorators.idempotent_id('65e33aa2-185b-44c8-b22e-e524973ed625') def test_boot_from_multiattach_volume(self): """Simple test to boot an instance from a multiattach volume.""" self._boot_from_multiattach_volume() @utils.services('image') @decorators.idempotent_id('885ac48a-2d7a-40c5-ae8b-1993882d724c') def test_snapshot_volume_backed_multiattach(self): """Boots a server from a multiattach volume and snapshots the server. Creating the snapshot of the server will also create a snapshot of the volume. """ server, volume = self._boot_from_multiattach_volume() # Create a snapshot of the server (and volume implicitly). self.create_image_from_server( server['id'], name='multiattach-snapshot', wait_until='active', wait_for_server=True) # TODO(mriedem): Make sure the volume snapshot exists. This requires # adding the volume snapshots client to BaseV2ComputeTest. # Delete the server, wait for it to be gone, and make sure the volume # still exists. self.servers_client.delete_server(server['id']) waiters.wait_for_server_termination(self.servers_client, server['id']) # Delete the volume and cascade the delete of the volume snapshot. self.volumes_client.delete_volume(volume['id'], cascade=True) # Now we have to wait for the volume to be gone otherwise the normal # teardown will fail since it will race with our call and the snapshot # might still exist. self.volumes_client.wait_for_resource_deletion(volume['id']) @decorators.idempotent_id('f01c7169-a124-4fc7-ae60-5e380e247c9c') @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') def test_resize_server_with_multiattached_volume(self): # Attach a single volume to multiple servers, then resize the servers servers, volume, _ = self._create_and_multiattach() for server in servers: self.resize_server(server['id'], self.flavor_ref_alt) for server in servers: self._detach_multiattach_volume(volume['id'], server['id']) # TODO(mriedem): Might be interesting to create a bootable multiattach # volume with delete_on_termination=True, create server1 from the # volume, then attach it to server2, and then delete server1 in which # case the volume won't be deleted because it's still attached to # server2 and make sure the volume is still attached to server2. tempest-23.0.0/tempest/api/compute/volumes/test_attach_volume_negative.py000066400000000000000000000054351361143263300270140ustar00rootroot00000000000000# 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): create_default_network = True @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() self.attach_volume(server, volume) self.assertRaises(lib_exc.BadRequest, self.delete_volume, volume['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('aab919e2-d992-4cbb-a4ed-745c2475398c') def test_attach_attached_volume_to_same_server(self): # Test attaching the same volume to the same instance once # it's already attached. The nova/cinder validation for this differs # depending on whether or not cinder v3.27 is being used to attach # the volume to the instance. server = self.create_test_server(wait_until='ACTIVE') volume = self.create_volume() self.attach_volume(server, volume) self.assertRaises(lib_exc.BadRequest, self.attach_volume, server, volume) @decorators.attr(type=['negative']) @decorators.idempotent_id('ee37a796-2afb-11e7-bc0f-fa163e65f5ce') def test_attach_attached_volume_to_different_server(self): server1 = self.create_test_server(wait_until='ACTIVE') volume = self.create_volume() self.attach_volume(server1, volume) # Create server2 and attach in-use volume server2 = self.create_test_server(wait_until='ACTIVE') self.assertRaises(lib_exc.BadRequest, self.attach_volume, server2, volume) tempest-23.0.0/tempest/api/compute/volumes/test_volume_snapshots.py000066400000000000000000000062571361143263300257130ustar00rootroot00000000000000# 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://docs.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-23.0.0/tempest/api/compute/volumes/test_volumes_get.py000066400000000000000000000062201361143263300246210ustar00rootroot00000000000000# 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://docs.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-23.0.0/tempest/api/compute/volumes/test_volumes_list.py000066400000000000000000000142641361143263300250240ustar00rootroot00000000000000# 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://docs.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-23.0.0/tempest/api/compute/volumes/test_volumes_negative.py000066400000000000000000000116571361143263300256560ustar00rootroot00000000000000# 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://docs.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-23.0.0/tempest/api/identity/000077500000000000000000000000001361143263300173425ustar00rootroot00000000000000tempest-23.0.0/tempest/api/identity/__init__.py000066400000000000000000000015051361143263300214540ustar00rootroot00000000000000# 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-23.0.0/tempest/api/identity/admin/000077500000000000000000000000001361143263300204325ustar00rootroot00000000000000tempest-23.0.0/tempest/api/identity/admin/__init__.py000066400000000000000000000000001361143263300225310ustar00rootroot00000000000000tempest-23.0.0/tempest/api/identity/admin/v2/000077500000000000000000000000001361143263300207615ustar00rootroot00000000000000tempest-23.0.0/tempest/api/identity/admin/v2/__init__.py000066400000000000000000000000001361143263300230600ustar00rootroot00000000000000tempest-23.0.0/tempest/api/identity/admin/v2/test_endpoints.py000066400000000000000000000076131361143263300244040ustar00rootroot00000000000000# 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-23.0.0/tempest/api/identity/admin/v2/test_roles.py000066400000000000000000000120071361143263300235160ustar00rootroot00000000000000# 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-23.0.0/tempest/api/identity/admin/v2/test_roles_negative.py000066400000000000000000000305141361143263300254030ustar00rootroot00000000000000# 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-23.0.0/tempest/api/identity/admin/v2/test_services.py000066400000000000000000000106161361143263300242210ustar00rootroot00000000000000# 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'] self.addCleanup(self.services_client.delete_service, service['id']) services.append(service) service_ids = [svc['id'] for svc in 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-23.0.0/tempest/api/identity/admin/v2/test_tenant_negative.py000066400000000000000000000150051361143263300255460ustar00rootroot00000000000000# 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-23.0.0/tempest/api/identity/admin/v2/test_tenants.py000066400000000000000000000136631361143263300240570ustar00rootroot00000000000000# 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'] self.assertTrue(tenant['enabled'], 'Enable should be True in response') body = self.tenants_client.show_tenant(tenant_id)['tenant'] self.assertTrue(body['enabled'], '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'] self.assertFalse(tenant['enabled'], 'Enable should be False in response') body = self.tenants_client.show_tenant(tenant_id)['tenant'] self.assertFalse(body['enabled'], '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.assertFalse(tenant['enabled']) self.assertEqual(resp2_en, resp3_en) self.tenants_client.delete_tenant(t_id) tempest-23.0.0/tempest/api/identity/admin/v2/test_tokens.py000066400000000000000000000132611361143263300237000ustar00rootroot00000000000000# 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): tempest_services = ['keystone', 'nova', 'neutron', 'swift', 'cinder', 'neutron'] # 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. Keystone is always available. available_services = [s[0] for s in list( CONF.service_available.items()) if s[1] is True] + ['keystone'] # Verify that all available services are present. for service in tempest_services: if service in available_services: self.assertIn(service, service_names) tempest-23.0.0/tempest/api/identity/admin/v2/test_tokens_negative.py000066400000000000000000000030131361143263300255540ustar00rootroot00000000000000# 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-23.0.0/tempest/api/identity/admin/v2/test_users.py000066400000000000000000000211141361143263300235320ustar00rootroot00000000000000# 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-23.0.0/tempest/api/identity/admin/v2/test_users_negative.py000066400000000000000000000306431361143263300254230ustar00rootroot00000000000000# 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-23.0.0/tempest/api/identity/admin/v3/000077500000000000000000000000001361143263300207625ustar00rootroot00000000000000tempest-23.0.0/tempest/api/identity/admin/v3/__init__.py000066400000000000000000000000001361143263300230610ustar00rootroot00000000000000tempest-23.0.0/tempest/api/identity/admin/v3/test_application_credentials.py000066400000000000000000000032121361143263300272510ustar00rootroot00000000000000# Copyright 2018 SUSE Linux GmbH # # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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 ApplicationCredentialsV3AdminTest(base.BaseApplicationCredentialsV3Test, base.BaseIdentityV3AdminTest): @decorators.idempotent_id('3b3dd48f-3388-406a-a9e6-4d078a552d0e') def test_create_application_credential_with_roles(self): role = self.setup_test_role() self.os_admin.roles_v3_client.create_user_role_on_project( self.project_id, self.user_id, role['id'] ) app_cred = self.create_application_credential( roles=[{'id': role['id']}]) secret = app_cred['secret'] # Check that the application credential is functional token_id, resp = self.non_admin_token.get_token( app_cred_id=app_cred['id'], app_cred_secret=secret, auth_data=True ) self.assertEqual(resp['project']['id'], self.project_id) self.assertEqual(resp['roles'][0]['id'], role['id']) tempest-23.0.0/tempest/api/identity/admin/v3/test_credentials.py000066400000000000000000000113711361143263300246730ustar00rootroot00000000000000# 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): # NOTE: force_tenant_isolation is true in the base class by default but # overridden to false here to allow test execution for clouds using the # pre-provisioned credentials provider. force_tenant_isolation = False @classmethod def resource_setup(cls): super(CredentialsTestJSON, cls).resource_setup() cls.projects = list() cls.creds_list = [['project_id', 'user_id', 'id'], ['access', 'secret']] for _ in range(2): project = cls.projects_client.create_project( data_utils.rand_name('project'), description=data_utils.rand_name('project-desc'))['project'] cls.addClassResourceCleanup( cls.projects_client.delete_project, project['id']) cls.projects.append(project['id']) cls.user_body = cls.users_client.show_user( cls.os_primary.credentials.user_id)['user'] 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-23.0.0/tempest/api/identity/admin/v3/test_default_project_id.py000066400000000000000000000076601361143263300262320ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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.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) @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source, 'Skipped because environment has an ' 'immutable user source and solely ' 'provides read-only access to users.') @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_pass = data_utils.rand_password() user_body = self.users_client.create_user( name=user_name, password=user_pass, 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_pass, 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-23.0.0/tempest/api/identity/admin/v3/test_domain_configuration.py000066400000000000000000000172161361143263300266000ustar00rootroot00000000000000# 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): # NOTE: force_tenant_isolation is true in the base class by default but # overridden to false here to allow test execution for clouds using the # pre-provisioned credentials provider. force_tenant_isolation = False 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 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-23.0.0/tempest/api/identity/admin/v3/test_domains.py000066400000000000000000000156611361143263300240360ustar00rootroot00000000000000# 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 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('d8d318b7-d1b3-4c37-94c5-3c5ba0b121ea') def test_domain_delete_cascades_content(self): # Create a domain with a user and a group in it domain = self.setup_test_domain() user = self.create_test_user(domain_id=domain['id']) group = self.setup_test_group(domain_id=domain['id']) # Delete the domain self.delete_domain(domain['id']) # Check the domain, its users and groups are gone self.assertRaises(exceptions.NotFound, self.domains_client.show_domain, domain['id']) self.assertRaises(exceptions.NotFound, self.users_client.show_user, user['id']) self.assertRaises(exceptions.NotFound, self.groups_client.show_group, group['id']) @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) tempest-23.0.0/tempest/api/identity/admin/v3/test_domains_negative.py000066400000000000000000000061431361143263300257130ustar00rootroot00000000000000# 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): # NOTE: force_tenant_isolation is true in the base class by default but # overridden to false here to allow test execution for clouds using the # pre-provisioned credentials provider. force_tenant_isolation = False @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-23.0.0/tempest/api/identity/admin/v3/test_endpoint_groups.py000066400000000000000000000152171361143263300256200ustar00rootroot00000000000000# 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 class EndPointGroupsTest(base.BaseIdentityV3AdminTest): # NOTE: force_tenant_isolation is true in the base class by default but # overridden to false here to allow test execution for clouds using the # pre-provisioned credentials provider. force_tenant_isolation = False @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.endpoint_groups = list() # Create endpoint group so as to use it for LIST test service_id = cls._create_service() cls.addClassResourceCleanup( cls.services_client.delete_service, service_id) 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.addClassResourceCleanup( cls.client.delete_endpoint_group, endpoint_group['id']) cls.endpoint_groups.append(endpoint_group) @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'] 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() self.addCleanup(self.services_client.delete_service, service_id) 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.addCleanup( test_utils.call_and_ignore_notfound_exc, self.client.delete_endpoint_group, endpoint_group['id']) 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']) # 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() self.addCleanup(self.services_client.delete_service, service1_id) 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.addCleanup(self.client.delete_endpoint_group, endpoint_group['id']) # Creating new attr values to update endpoint group service2_id = self._create_service() self.addCleanup(self.services_client.delete_service, service2_id) 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-23.0.0/tempest/api/identity/admin/v3/test_endpoints.py000066400000000000000000000230361361143263300244020ustar00rootroot00000000000000# 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.common.utils import test_utils from tempest.lib import decorators class EndPointsTestJSON(base.BaseIdentityV3AdminTest): # NOTE: force_tenant_isolation is true in the base class by default but # overridden to false here to allow test execution for clouds using the # pre-provisioned credentials provider. force_tenant_isolation = False @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): service = cls._create_service() cls.service_ids.append(service['id']) cls.addClassResourceCleanup( cls.services_client.delete_service, service['id']) region_name = 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_name, enabled=True)['endpoint'] region = cls.regions_client.show_region(region_name)['region'] cls.addClassResourceCleanup( cls.regions_client.delete_region, region['id']) cls.addClassResourceCleanup( cls.client.delete_endpoint, endpoint['id']) 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)) return service_data['service'] @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_name = 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_name, enabled=True)['endpoint'] region = self.regions_client.show_region(region_name)['region'] self.addCleanup(self.regions_client.delete_region, region['id']) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.client.delete_endpoint, endpoint['id']) # Asserting Create Endpoint response body self.assertEqual(region_name, 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_name, fetched_endpoint['region']) self.assertEqual(True, fetched_endpoint['enabled']) # Deleting the endpoint created in this method self.client.delete_endpoint(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): # NOTE(zhufl) Service2 should be created before endpoint_for_update # is created, because Service2 must be deleted after # endpoint_for_update is deleted, otherwise we will get a 404 error # when deleting endpoint_for_update if endpoint's service is deleted. # Creating service for updating 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) self.addCleanup(self.services_client.delete_service, service2['id']) # Creating an endpoint so as to check update endpoint with new values region1_name = 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_name, enabled=True)['endpoint']) region1 = self.regions_client.show_region(region1_name)['region'] self.addCleanup(self.regions_client.delete_region, region1['id']) # Updating endpoint with new values region2_name = 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_name, enabled=False)['endpoint'] region2 = self.regions_client.show_region(region2_name)['region'] self.addCleanup(self.regions_client.delete_region, region2['id']) self.addCleanup(self.client.delete_endpoint, endpoint_for_update['id']) # 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_name, endpoint['region']) self.assertEqual(False, endpoint['enabled']) tempest-23.0.0/tempest/api/identity/admin/v3/test_endpoints_negative.py000066400000000000000000000104731361143263300262650ustar00rootroot00000000000000# 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): # NOTE: force_tenant_isolation is true in the base class by default but # overridden to false here to allow test execution for clouds using the # pre-provisioned credentials provider. force_tenant_isolation = False @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() 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.addClassResourceCleanup(cls.services_client.delete_service, service_data['id']) cls.service_id = service_data['id'] @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_name = 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_name, enabled=True)['endpoint']) region1 = self.regions_client.show_region(region1_name)['region'] self.addCleanup(self.regions_client.delete_region, region1['id']) 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-23.0.0/tempest/api/identity/admin/v3/test_groups.py000066400000000000000000000146741361143263300237260ustar00rootroot00000000000000# 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 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 GroupsV3TestJSON(base.BaseIdentityV3AdminTest): # NOTE: force_tenant_isolation is true in the base class by default but # overridden to false here to allow test execution for clouds using the # pre-provisioned credentials provider. force_tenant_isolation = False @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): # Verify group creation works. name = data_utils.rand_name('Group') description = data_utils.rand_name('Description') group = self.setup_test_group(name=name, domain_id=self.domain['id'], description=description) self.assertEqual(group['name'], name) self.assertEqual(group['description'], description) self.assertEqual(self.domain['id'], group['domain_id']) # Verify updating name and description works. first_name_update = data_utils.rand_name('UpdateGroup') first_desc_update = data_utils.rand_name('UpdateDescription') updated_group = self.groups_client.update_group( group['id'], name=first_name_update, description=first_desc_update)['group'] self.assertEqual(updated_group['name'], first_name_update) self.assertEqual(updated_group['description'], first_desc_update) # Verify that the updated values are reflected after performing show. new_group = self.groups_client.show_group(group['id'])['group'] self.assertEqual(group['id'], new_group['id']) self.assertEqual(first_name_update, new_group['name']) self.assertEqual(first_desc_update, new_group['description']) # Verify that updating a single field for a group (name) leaves the # other fields (description, domain_id) unchanged. second_name_update = data_utils.rand_name( self.__class__.__name__ + 'UpdateGroup') updated_group = self.groups_client.update_group( group['id'], name=second_name_update)['group'] self.assertEqual(second_name_update, updated_group['name']) # Verify that 'description' and 'domain_id' were not updated or # deleted. self.assertEqual(first_desc_update, updated_group['description']) self.assertEqual(self.domain['id'], updated_group['domain_id']) @decorators.attr(type='smoke') @decorators.idempotent_id('1598521a-2f36-4606-8df9-30772bd51339') @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source, 'Skipped because environment has an ' 'immutable user source and solely ' 'provides read-only access to users.') def test_group_users_add_list_delete(self): group = self.setup_test_group(domain_id=self.domain['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') @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source, 'Skipped because environment has an ' 'immutable user source and solely ' 'provides read-only access to users.') 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): group = self.setup_test_group(domain_id=self.domain['id']) groups.append(group) 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): group = self.setup_test_group(domain_id=self.domain['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-23.0.0/tempest/api/identity/admin/v3/test_inherits.py000066400000000000000000000255441361143263300242320ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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.identity 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 InheritsV3TestJSON(base.BaseIdentityV3AdminTest): # NOTE: force_tenant_isolation is true in the base class by default but # overridden to false here to allow test execution for clouds using the # pre-provisioned credentials provider. force_tenant_isolation = False @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_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.addClassResourceCleanup(cls.projects_client.delete_project, cls.project['id']) 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.addClassResourceCleanup(cls.groups_client.delete_group, cls.group['id']) if not CONF.identity_feature_enabled.immutable_user_source: 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'] cls.addClassResourceCleanup(cls.users_client.delete_user, cls.user['id']) 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') @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source, 'Skipped because environment has an immutable user ' 'source and solely provides read-only access to users.') 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') @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source, 'Skipped because environment has an immutable user ' 'source and solely provides read-only access to users.') 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') @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source, 'Skipped because environment has an immutable user ' 'source and solely provides read-only access to users.') 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') @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source, 'Skipped because environment has an immutable user ' 'source and solely provides read-only access to users.') 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-23.0.0/tempest/api/identity/admin/v3/test_list_projects.py000066400000000000000000000132211361143263300252560ustar00rootroot00000000000000# 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 oslo_log import log as logging from tempest.api.identity import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators LOG = logging.getLogger(__name__) CONF = config.CONF class BaseListProjectsTestJSON(base.BaseIdentityV3AdminTest): def _list_projects_with_params(self, included, excluded, params, key): # Validate that projects in ``included`` belongs to the projects # returned that match ``params`` but not projects in ``excluded`` all_projects = self.projects_client.list_projects()['projects'] LOG.debug("Complete list of projects available in keystone: %s", all_projects) body = self.projects_client.list_projects(params)['projects'] for p in included: self.assertIn(p[key], map(lambda x: x[key], body)) for p in excluded: self.assertNotIn(p[key], map(lambda x: x[key], body)) class ListProjectsTestJSON(BaseListProjectsTestJSON): @classmethod def resource_setup(cls): super(ListProjectsTestJSON, cls).resource_setup() domain_id = cls.os_admin.credentials.domain_id # Create project with domain p1_name = data_utils.rand_name(cls.__name__) cls.p1 = cls.projects_client.create_project( p1_name, enabled=False, domain_id=domain_id)['project'] cls.addClassResourceCleanup(cls.projects_client.delete_project, cls.p1['id']) # Create default project p2_name = data_utils.rand_name(cls.__name__) cls.p2 = cls.projects_client.create_project(p2_name)['project'] cls.addClassResourceCleanup(cls.projects_client.delete_project, cls.p2['id']) # Create a new project (p3) using p2 as parent project p3_name = data_utils.rand_name(cls.__name__) cls.p3 = cls.projects_client.create_project( p3_name, parent_id=cls.p2['id'])['project'] cls.addClassResourceCleanup(cls.projects_client.delete_project, cls.p3['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( [self.p1], [self.p2, self.p3], {'enabled': False}, 'enabled') @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']) class ListProjectsStaticTestJSON(BaseListProjectsTestJSON): # NOTE: force_tenant_isolation is true in the base class by default but # overridden to false here to allow test execution for clouds using the # pre-provisioned credentials provider. force_tenant_isolation = False @classmethod def resource_setup(cls): super(ListProjectsStaticTestJSON, cls).resource_setup() # Fetch an existing project from os_primary cls.p1 = cls.projects_client.show_project( cls.os_primary.credentials.project_id)['project'] # Create a test project p2_name = data_utils.rand_name(cls.__name__) p2_domain_id = CONF.identity.default_domain_id cls.p2 = cls.projects_client.create_project( p2_name, domain_id=p2_domain_id)['project'] cls.addClassResourceCleanup(cls.projects_client.delete_project, cls.p2['id']) @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.p1, self.p2]: show_project = self.projects_client.show_project(p['id'])[ 'project'] self.assertIn(show_project, list_projects) @decorators.idempotent_id('fa178524-4e6d-4925-907c-7ab9f42c7e26') def test_list_projects_with_name(self): # List projects with name self._list_projects_with_params( [self.p1], [self.p2], {'name': self.p1['name']}, 'name') @decorators.idempotent_id('fab13f3c-f6a6-4b9f-829b-d32fd44fdf10') def test_list_projects_with_domains(self): # Verify project list filtered by domain key = 'domain_id' for p in [self.p1, self.p2]: params = {key: p[key]} # Verify filter shows both projects in their respective domains self._list_projects_with_params([p], [], params, key) # Verify filter excludes projects that are filtered out if self.p1[key] != self.p2[key]: exclude = [self.p2] params = {key: self.p1[key]} self._list_projects_with_params([self.p1], exclude, params, key) tempest-23.0.0/tempest/api/identity/admin/v3/test_list_users.py000066400000000000000000000137551361143263300246020ustar00rootroot00000000000000# 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 skip_checks(cls): super(UsersV3TestJSON, cls).skip_checks() if CONF.identity_feature_enabled.immutable_user_source: raise cls.skipException('Skipped because environment has an ' 'immutable user source and solely ' 'provides read-only access to users.') @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.addClassResourceCleanup(cls.users_client.delete_user, cls.domain_enabled_user['id']) 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.addClassResourceCleanup(cls.users_client.delete_user, cls.non_domain_enabled_user['id']) cls.users.append(cls.non_domain_enabled_user) @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-23.0.0/tempest/api/identity/admin/v3/test_oauth_consumers.py000066400000000000000000000105431361143263300256140ustar00rootroot00000000000000# 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): # NOTE: force_tenant_isolation is true in the base class by default but # overridden to false here to allow test execution for clouds using the # pre-provisioned credentials provider. force_tenant_isolation = False 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-23.0.0/tempest/api/identity/admin/v3/test_policies.py000066400000000000000000000062121361143263300242030ustar00rootroot00000000000000# 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-23.0.0/tempest/api/identity/admin/v3/test_project_tags.py000066400000000000000000000056401361143263300250640ustar00rootroot00000000000000# Copyright 2018 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 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 as lib_exc CONF = config.CONF class IdentityV3ProjectTagsTest(base.BaseIdentityV3AdminTest): # NOTE: force_tenant_isolation is true in the base class by default but # overridden to false here to allow test execution for clouds using the # pre-provisioned credentials provider. force_tenant_isolation = False @decorators.idempotent_id('7c123aac-999d-416a-a0fb-84b915ab10de') @testtools.skipUnless(CONF.identity_feature_enabled.project_tags, 'Project tags not available.') def test_list_update_delete_project_tags(self): project = self.setup_test_project() # Create a tag for testing. tag = data_utils.rand_name('tag') # NOTE(felipemonteiro): The response body for create is empty. self.project_tags_client.update_project_tag(project['id'], tag) # Verify that the tag was created. self.project_tags_client.check_project_tag_existence( project['id'], tag) # Verify that updating the project tags works. tags_to_update = [data_utils.rand_name('tag') for _ in range(3)] updated_tags = self.project_tags_client.update_all_project_tags( project['id'], tags_to_update)['tags'] self.assertEqual(sorted(tags_to_update), sorted(updated_tags)) # Verify that listing project tags works. retrieved_tags = self.project_tags_client.list_project_tags( project['id'])['tags'] self.assertEqual(sorted(tags_to_update), sorted(retrieved_tags)) # Verify that deleting a project tag works. self.project_tags_client.delete_project_tag( project['id'], tags_to_update[0]) self.assertRaises(lib_exc.NotFound, self.project_tags_client.check_project_tag_existence, project['id'], tags_to_update[0]) # Verify that deleting all project tags works. self.project_tags_client.delete_all_project_tags(project['id']) retrieved_tags = self.project_tags_client.list_project_tags( project['id'])['tags'] self.assertEmpty(retrieved_tags) tempest-23.0.0/tempest/api/identity/admin/v3/test_projects.py000066400000000000000000000256061361143263300242350ustar00rootroot00000000000000# 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.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): # NOTE: force_tenant_isolation is true in the base class by default but # overridden to false here to allow test execution for clouds using the # pre-provisioned credentials provider. force_tenant_isolation = False @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'] self.assertTrue(project['enabled'], 'Enable should be True in response') body = self.projects_client.show_project(project_id)['project'] self.assertTrue(body['enabled'], '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) self.assertFalse(project['enabled'], 'Enable should be False in response') body = self.projects_client.show_project(project['id'])['project'] self.assertFalse(body['enabled'], '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.assertFalse(project['enabled']) self.assertEqual(resp2_en, resp3_en) @decorators.idempotent_id('59398d4a-5dc5-4f86-9a4c-c26cc804d6c6') @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source, 'Skipped because environment has an ' 'immutable user source and solely ' 'provides read-only access to users.') 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 expected fields exist. More fields than expected may # be in this list. This is for future proofind as keystone does not # and has no plans to support microservices. Any fields in the future # that are added to the response of the API should eventually be added # to the expected fields. The expected fields must be a subset of # the project_get fields (all keys in fields must exist in project_get, # but project_get.keys() may have additional fields) self.assertTrue(set(fields).issubset(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-23.0.0/tempest/api/identity/admin/v3/test_projects_negative.py000066400000000000000000000074361361143263300261200ustar00rootroot00000000000000# 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('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']) class ProjectsNegativeStaticTestJSON(base.BaseIdentityV3AdminTest): # NOTE: force_tenant_isolation is true in the base class by default but # overridden to false here to allow test execution for clouds using the # pre-provisioned credentials provider. force_tenant_isolation = False @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('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-23.0.0/tempest/api/identity/admin/v3/test_regions.py000066400000000000000000000131121361143263300240370ustar00rootroot00000000000000# 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): # NOTE: force_tenant_isolation is true in the base class by default but # overridden to false here to allow test execution for clouds using the # pre-provisioned credentials provider. force_tenant_isolation = False @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.addClassResourceCleanup( cls.client.delete_region, region['id']) cls.setup_regions.append(region) @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-23.0.0/tempest/api/identity/admin/v3/test_roles.py000066400000000000000000000507511361143263300235270ustar00rootroot00000000000000# 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.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): # NOTE: force_tenant_isolation is true in the base class by default but # overridden to false here to allow test execution for clouds using the # pre-provisioned credentials provider. force_tenant_isolation = False @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.addClassResourceCleanup(cls.roles_client.delete_role, role['id']) 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.addClassResourceCleanup(cls.projects_client.delete_project, cls.project['id']) 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.addClassResourceCleanup(cls.groups_client.delete_group, cls.group_body['id']) cls.role = cls.roles_client.create_role( name=data_utils.rand_name('Role'))['role'] cls.addClassResourceCleanup(cls.roles_client.delete_role, cls.role['id']) if not CONF.identity_feature_enabled.immutable_user_source: cls.user_body = cls.users_client.create_user( name=u_name, description=u_desc, email=u_email, password=cls.u_password, domain_id=cls.domain['id'], project_id=cls.project['id'] )['user'] cls.addClassResourceCleanup(cls.users_client.delete_user, cls.user_body['id']) @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') @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source, 'Skipped because environment has an immutable user ' 'source and solely provides read-only access to users.') 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') @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source, 'Skipped because environment has an immutable user ' 'source and solely provides read-only access to users.') 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') @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source, 'Skipped because environment has an immutable user ' 'source and solely provides read-only access to users.') 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') @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source, 'Skipped because environment has an immutable user ' 'source and solely provides read-only access to users.') 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']) # 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') @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source, 'Skipped because environment has an immutable user ' 'source and solely provides read-only access to users.') 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'] # NOTE(jaosorior): With the work related to the define-default-roles # blueprint, we now have 'admin', 'member' and 'reader' by default. So # we filter every other implied role to only take into account the ones # relates to this test. relevant_roles = (self.roles[0]['id'], self.roles[1]['id'], self.roles[2]['id'], self.role['id']) def is_implied_role_relevant(rule): return any(r for r in rule['implies'] if r['id'] in relevant_roles) relevant_rules = filter(is_implied_role_relevant, rules) # 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(relevant_rules, key=lambda r: len(r['implies'])) 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-23.0.0/tempest/api/identity/admin/v3/test_services.py000066400000000000000000000106771361143263300242310ustar00rootroot00000000000000# 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-23.0.0/tempest/api/identity/admin/v3/test_tokens.py000066400000000000000000000224641361143263300237060ustar00rootroot00000000000000# 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 CONF = config.CONF class TokensV3TestJSON(base.BaseIdentityV3AdminTest): credentials = ['primary', 'admin', 'alt'] @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=self.__class__.__name__) project1 = self.setup_test_project(name=project1_name) project2_name = data_utils.rand_name(name=self.__class__.__name__) 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_id = self.setup_test_group(domain_id=domain_id)['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-23.0.0/tempest/api/identity/admin/v3/test_trusts.py000066400000000000000000000274651361143263300237550ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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") if CONF.identity_feature_enabled.immutable_user_source: raise cls.skipException('Skipped because environment has an ' 'immutable user source and solely ' 'provides read-only access to users.') def setUp(self): super(TrustsV3TestJSON, self).setUp() # Use alt_username as the trustee self.trust_id = None self.create_trustor_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=self.__class__.__name__) project = self.projects_client.create_project( trustor_project_name, domain_id=CONF.identity.default_domain_id)['project'] self.trustor_project_id = project['id'] self.addCleanup(self.projects_client.delete_project, 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.addCleanup(self.users_client.delete_user, user['id']) 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.addCleanup(self.roles_client.delete_role, role['id']) self.delegated_role_id = role['id'] role = self.roles_client.create_role( name=self.not_delegated_role)['role'] self.addCleanup(self.roles_client.delete_role, role['id']) 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 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-23.0.0/tempest/api/identity/admin/v3/test_users.py000066400000000000000000000174301361143263300235410ustar00rootroot00000000000000# 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): @classmethod def skip_checks(cls): super(UsersV3TestJSON, cls).skip_checks() if CONF.identity_feature_enabled.immutable_user_source: raise cls.skipException('Skipped because environment has an ' 'immutable user source and solely ' 'provides read-only access to users.') @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-23.0.0/tempest/api/identity/admin/v3/test_users_negative.py000066400000000000000000000041261361143263300254210ustar00rootroot00000000000000# 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-23.0.0/tempest/api/identity/base.py000066400000000000000000000330271361143263300206330ustar00rootroot00000000000000# 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 cls.non_admin_app_creds_client = \ cls.os_primary.application_credentials_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 cls.project_tags_client = cls.os_admin.project_tags_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 def setup_test_group(self, **kwargs): """Set up a test group.""" if 'name' not in kwargs: kwargs['name'] = data_utils.rand_name( self.__class__.__name__ + '_test_project') if 'description' not in kwargs: kwargs['description'] = data_utils.rand_name( self.__class__.__name__ + '_test_description') group = self.groups_client.create_group(**kwargs)['group'] self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.groups_client.delete_group, group['id']) return group class BaseApplicationCredentialsV3Test(BaseIdentityV3Test): @classmethod def skip_checks(cls): super(BaseApplicationCredentialsV3Test, cls).skip_checks() if not CONF.identity_feature_enabled.application_credentials: raise cls.skipException("Application credentials are not available" " in this environment") @classmethod def resource_setup(cls): super(BaseApplicationCredentialsV3Test, cls).resource_setup() cls.user_id = cls.os_primary.credentials.user_id cls.project_id = cls.os_primary.credentials.project_id def create_application_credential(self, name=None, **kwargs): name = name or data_utils.rand_name('application_credential') application_credential = ( self.non_admin_app_creds_client.create_application_credential( self.user_id, name=name, **kwargs))['application_credential'] self.addCleanup( self.non_admin_app_creds_client.delete_application_credential, self.user_id, application_credential['id']) return application_credential tempest-23.0.0/tempest/api/identity/v2/000077500000000000000000000000001361143263300176715ustar00rootroot00000000000000tempest-23.0.0/tempest/api/identity/v2/__init__.py000066400000000000000000000000001361143263300217700ustar00rootroot00000000000000tempest-23.0.0/tempest/api/identity/v2/test_api_discovery.py000066400000000000000000000045271361143263300241520ustar00rootroot00000000000000# 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-23.0.0/tempest/api/identity/v2/test_ec2_credentials.py000066400000000000000000000113511361143263300243310ustar00rootroot00000000000000# 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']) self.addCleanup( self.non_admin_users_client.delete_user_ec2_credential, self.creds.user_id, 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']) 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-23.0.0/tempest/api/identity/v2/test_extension.py000066400000000000000000000022651361143263300233230ustar00rootroot00000000000000# 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-23.0.0/tempest/api/identity/v2/test_tenants.py000066400000000000000000000041331361143263300227570ustar00rootroot00000000000000# 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-23.0.0/tempest/api/identity/v2/test_tokens.py000066400000000000000000000033541361143263300226120ustar00rootroot00000000000000# 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-23.0.0/tempest/api/identity/v2/test_users.py000066400000000000000000000107411361143263300224460ustar00rootroot00000000000000# 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 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') @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source, 'Skipped because environment has an ' 'immutable user source and solely ' 'provides read-only access to users.') 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 use_dynamic_credentials=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-23.0.0/tempest/api/identity/v3/000077500000000000000000000000001361143263300176725ustar00rootroot00000000000000tempest-23.0.0/tempest/api/identity/v3/__init__.py000066400000000000000000000000001361143263300217710ustar00rootroot00000000000000tempest-23.0.0/tempest/api/identity/v3/test_api_discovery.py000066400000000000000000000066401361143263300241510ustar00rootroot00000000000000# 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 import config from tempest.lib import decorators CONF = config.CONF class TestApiDiscovery(base.BaseIdentityV3Test): """Tests for API discovery features.""" @decorators.idempotent_id('79aec9ae-710f-4c54-a4fc-3aa25b4feac3') def test_identity_v3_existence(self): versions = self.non_admin_versions_client.list_versions() found = any( "v3" in version.get('id') for version in versions['versions']['values']) self.assertEqual(CONF.identity_feature_enabled.api_v3, found) @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-23.0.0/tempest/api/identity/v3/test_application_credentials.py000066400000000000000000000060731361143263300261710ustar00rootroot00000000000000# Copyright 2018 SUSE Linux GmbH # # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 oslo_utils import timeutils from tempest.api.identity import base from tempest.lib import decorators class ApplicationCredentialsV3Test(base.BaseApplicationCredentialsV3Test): def _list_app_creds(self, name=None): kwargs = dict(user_id=self.user_id) if name: kwargs.update(name=name) return self.non_admin_app_creds_client.list_application_credentials( **kwargs)['application_credentials'] @decorators.idempotent_id('8080c75c-eddc-4786-941a-c2da7039ae61') def test_create_application_credential(self): app_cred = self.create_application_credential() # Check that the secret appears in the create response secret = app_cred['secret'] # Check that the secret is not retrievable after initial create app_cred = self.non_admin_app_creds_client.show_application_credential( user_id=self.user_id, application_credential_id=app_cred['id'] )['application_credential'] self.assertNotIn('secret', app_cred) # Check that the application credential is functional token_id, resp = self.non_admin_token.get_token( app_cred_id=app_cred['id'], app_cred_secret=secret, auth_data=True ) self.assertEqual(resp['project']['id'], self.project_id) @decorators.idempotent_id('852daf0c-42b5-4239-8466-d193d0543ed3') def test_create_application_credential_expires(self): expires_at = timeutils.utcnow() + datetime.timedelta(hours=1) app_cred = self.create_application_credential(expires_at=expires_at) expires_str = expires_at.isoformat() self.assertEqual(expires_str, app_cred['expires_at']) @decorators.idempotent_id('ff0cd457-6224-46e7-b79e-0ada4964a8a6') def test_list_application_credentials(self): self.create_application_credential() self.create_application_credential() app_creds = self._list_app_creds() self.assertEqual(2, len(app_creds)) @decorators.idempotent_id('9bb5e5cc-5250-493a-8869-8b665f6aa5f6') def test_query_application_credentials(self): self.create_application_credential() app_cred_two = self.create_application_credential() app_cred_two_name = app_cred_two['name'] app_creds = self._list_app_creds(name=app_cred_two_name) self.assertEqual(1, len(app_creds)) self.assertEqual(app_cred_two_name, app_creds[0]['name']) tempest-23.0.0/tempest/api/identity/v3/test_catalog.py000066400000000000000000000035511361143263300227210ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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): # https://opendev.org/openstack/service-types-authority # /src/branch/master/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-23.0.0/tempest/api/identity/v3/test_domains.py000066400000000000000000000025731361143263300227440ustar00rootroot00000000000000# 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 import decorators CONF = config.CONF class DefaultDomainTestJSON(base.BaseIdentityV3Test): @classmethod def setup_clients(cls): super(DefaultDomainTestJSON, cls).setup_clients() cls.domains_client = cls.os_primary.domains_client @classmethod def resource_setup(cls): super(DefaultDomainTestJSON, cls).resource_setup() cls.domain_id = CONF.identity.default_domain_id @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-23.0.0/tempest/api/identity/v3/test_projects.py000066400000000000000000000051331361143263300231360ustar00rootroot00000000000000# 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-23.0.0/tempest/api/identity/v3/test_tokens.py000066400000000000000000000136301361143263300226110ustar00rootroot00000000000000# 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) # NOTE: resource name that are case-sensitive in keystone # depends on backends such as MySQL or LDAP which are # case-insensitive, case-preserving. Resource name is # returned as it is stored in the backend, not as it is # requested. Verifying the username with both lower-case to # avoid failure on different backends self.assertEqual( authenticated_token['user']['name'].lower(), username.lower()) 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: # NOTE: resource name that are case-sensitive in keystone # depends on backends such as MySQL or LDAP which are # case-insensitive, case-preserving. Resource name is # returned as it is stored in the backend, not as it is # requested. Verifying the username with both lower-case to # avoid failure on different backends self.assertEqual(subject_name.lower(), username.lower()) 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') @decorators.idempotent_id('0f9f5a5f-d5cd-4a86-8a5b-c5ded151f212') def test_token_auth_creation_existence_deletion(self): # Tests basic token auth functionality in a way that is compatible with # pre-provisioned credentials. The default user is used for token # authentication. # Valid user's token is authenticated user = self.os_primary.credentials # Perform Authentication resp = self.non_admin_token.auth( user_id=user.user_id, password=user.password).response subject_token = resp['x-subject-token'] self.non_admin_client.check_token_existence(subject_token) # Perform GET Token token_details = self.non_admin_client.show_token( subject_token)['token'] self.assertEqual(resp['x-subject-token'], subject_token) self.assertEqual(token_details['user']['id'], user.user_id) # NOTE: resource name that are case-sensitive in keystone # depends on backends such as MySQL or LDAP which are # case-insensitive, case-preserving. Resource name is # returned as it is stored in the backend, not as it is # requested. Verifying the username with both lower-case to # avoid failure on different backends self.assertEqual( token_details['user']['name'].lower(), user.username.lower()) # Perform Delete Token self.non_admin_client.delete_token(subject_token) self.assertRaises(lib_exc.NotFound, self.non_admin_client.check_token_existence, subject_token) tempest-23.0.0/tempest/api/identity/v3/test_users.py000066400000000000000000000167131361143263300224540ustar00rootroot00000000000000# 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') @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source, 'Skipped because environment has an ' 'immutable user source and solely ' 'provides read-only access to users.') 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 use_dynamic_credentials=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') @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source, 'Skipped because environment has an ' 'immutable user source and solely ' 'provides read-only access to users.') 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): if (CONF.identity.user_lockout_failure_attempts <= 0 or CONF.identity.user_lockout_duration <= 0): raise self.skipException( "Both CONF.identity.user_lockout_failure_attempts and " "CONF.identity.user_lockout_duration should be greater than " "zero to test this feature") 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-23.0.0/tempest/api/image/000077500000000000000000000000001361143263300165735ustar00rootroot00000000000000tempest-23.0.0/tempest/api/image/__init__.py000066400000000000000000000000001361143263300206720ustar00rootroot00000000000000tempest-23.0.0/tempest/api/image/base.py000066400000000000000000000155501361143263300200650ustar00rootroot00000000000000# 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(self, 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 = self.namespaces_client.create_namespace( namespace=namespace_name, visibility=visibility, description=description, protected=protected, **kwargs) self.addCleanup(self.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 BaseV2ImageAdminTest(BaseV2ImageTest): credentials = ['admin', 'primary'] @classmethod def setup_clients(cls): super(BaseV2ImageAdminTest, cls).setup_clients() cls.admin_client = cls.os_admin.image_client_v2 tempest-23.0.0/tempest/api/image/v1/000077500000000000000000000000001361143263300171215ustar00rootroot00000000000000tempest-23.0.0/tempest/api/image/v1/__init__.py000066400000000000000000000000001361143263300212200ustar00rootroot00000000000000tempest-23.0.0/tempest/api/image/v1/test_image_members.py000066400000000000000000000050611361143263300233300ustar00rootroot00000000000000# 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-23.0.0/tempest/api/image/v1/test_image_members_negative.py000066400000000000000000000050261361143263300252130ustar00rootroot00000000000000# 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-23.0.0/tempest/api/image/v1/test_images.py000066400000000000000000000361641361143263300220110ustar00rootroot00000000000000# 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-23.0.0/tempest/api/image/v1/test_images_negative.py000066400000000000000000000072311361143263300236640ustar00rootroot00000000000000# 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-23.0.0/tempest/api/image/v2/000077500000000000000000000000001361143263300171225ustar00rootroot00000000000000tempest-23.0.0/tempest/api/image/v2/__init__.py000066400000000000000000000000001361143263300212210ustar00rootroot00000000000000tempest-23.0.0/tempest/api/image/v2/admin/000077500000000000000000000000001361143263300202125ustar00rootroot00000000000000tempest-23.0.0/tempest/api/image/v2/admin/__init__.py000066400000000000000000000000001361143263300223110ustar00rootroot00000000000000tempest-23.0.0/tempest/api/image/v2/admin/test_images.py000066400000000000000000000044451361143263300230770ustar00rootroot00000000000000# Copyright 2018 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 import decorators class BasicOperationsImagesAdminTest(base.BaseV2ImageAdminTest): @decorators.related_bug('1420008') @decorators.idempotent_id('646a6eaa-135f-4493-a0af-12583021224e') def test_create_image_owner_param(self): # NOTE: Create image with owner different from tenant owner by # using "owner" parameter requires an admin privileges. random_id = data_utils.rand_uuid_hex() image = self.admin_client.create_image( container_format='bare', disk_format='raw', owner=random_id) self.addCleanup(self.admin_client.delete_image, image['id']) image_info = self.admin_client.show_image(image['id']) self.assertEqual(random_id, image_info['owner']) @decorators.related_bug('1420008') @decorators.idempotent_id('525ba546-10ef-4aad-bba1-1858095ce553') def test_update_image_owner_param(self): random_id_1 = data_utils.rand_uuid_hex() image = self.admin_client.create_image( container_format='bare', disk_format='raw', owner=random_id_1) self.addCleanup(self.admin_client.delete_image, image['id']) created_image_info = self.admin_client.show_image(image['id']) random_id_2 = data_utils.rand_uuid_hex() self.admin_client.update_image( image['id'], [dict(replace="/owner", value=random_id_2)]) updated_image_info = self.admin_client.show_image(image['id']) self.assertEqual(random_id_2, updated_image_info['owner']) self.assertNotEqual(created_image_info['owner'], updated_image_info['owner']) tempest-23.0.0/tempest/api/image/v2/test_images.py000066400000000000000000000407311361143263300220050ustar00rootroot00000000000000# 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 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']) # NOTE: This Glance API returns different status codes for image # condition. In this empty data case, Glance should return 204, # so here should check the status code. image_file = self.client.show_image_file(image['id']) self.assertEqual(0, len(image_file.data)) self.assertEqual(204, image_file.response.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 # NOTE: This Glance API returns different status codes for image # condition. In this non-empty data case, Glance should return 200, # so here should check the status code. body = self.client.show_image_file(image['id']) self.assertEqual(file_content, body.data) self.assertEqual(200, body.response.status) @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']) # 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']) @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-23.0.0/tempest/api/image/v2/test_images_member.py000066400000000000000000000116161361143263300233340ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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'] self.assertEqual(len(members), 1, str(members)) member = members[0] 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-23.0.0/tempest/api/image/v2/test_images_member_negative.py000066400000000000000000000037661361143263300252250ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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-23.0.0/tempest/api/image/v2/test_images_metadefs_namespace_objects.py000066400000000000000000000067321361143263300274050ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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-23.0.0/tempest/api/image/v2/test_images_metadefs_namespace_properties.py000066400000000000000000000055601361143263300301460ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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-23.0.0/tempest/api/image/v2/test_images_metadefs_namespace_tags.py000066400000000000000000000073041361143263300267060ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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.assertEqual(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-23.0.0/tempest/api/image/v2/test_images_metadefs_namespaces.py000066400000000000000000000065761361143263300260650ustar00rootroot00000000000000# 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-23.0.0/tempest/api/image/v2/test_images_metadefs_resource_types.py000066400000000000000000000052651361143263300270130ustar00rootroot00000000000000# 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-23.0.0/tempest/api/image/v2/test_images_metadefs_schema.py000066400000000000000000000070131361143263300251710ustar00rootroot00000000000000# 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-23.0.0/tempest/api/image/v2/test_images_negative.py000066400000000000000000000110351361143263300236620ustar00rootroot00000000000000# 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-23.0.0/tempest/api/image/v2/test_images_tags.py000066400000000000000000000027431361143263300230240ustar00rootroot00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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-23.0.0/tempest/api/image/v2/test_images_tags_negative.py000066400000000000000000000034501361143263300247020ustar00rootroot00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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-23.0.0/tempest/api/image/v2/test_versions.py000066400000000000000000000021111361143263300223760ustar00rootroot00000000000000# 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-23.0.0/tempest/api/network/000077500000000000000000000000001361143263300172025ustar00rootroot00000000000000tempest-23.0.0/tempest/api/network/__init__.py000066400000000000000000000000001361143263300213010ustar00rootroot00000000000000tempest-23.0.0/tempest/api/network/admin/000077500000000000000000000000001361143263300202725ustar00rootroot00000000000000tempest-23.0.0/tempest/api/network/admin/__init__.py000066400000000000000000000000001361143263300223710ustar00rootroot00000000000000tempest-23.0.0/tempest/api/network/admin/test_dhcp_agent_scheduler.py000066400000000000000000000100471361143263300260370ustar00rootroot00000000000000# 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']) 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-23.0.0/tempest/api/network/admin/test_external_network_extension.py000066400000000000000000000152671361143263300274050ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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.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( test_utils.call_and_ignore_notfound_exc, 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']) if utils.is_extension_enabled('filter-validation', 'network'): floatingip_list = self.admin_floating_ips_client.list_floatingips( floating_network_id=external_network['id']) else: # NOTE(hongbin): This is for testing the backward-compatibility # of neutron API although the parameter is a wrong filter # for listing floating IPs. floatingip_list = self.admin_floating_ips_client.list_floatingips( invalid_filter=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)) tempest-23.0.0/tempest/api/network/admin/test_external_networks_negative.py000066400000000000000000000047461361143263300273560ustar00rootroot00000000000000# 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 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 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, name=data_utils.rand_name(self.__class__.__name__), network_id=CONF.network.public_network_id, fixed_ips=fixed_ips) tempest-23.0.0/tempest/api/network/admin/test_floating_ips_admin_actions.py000066400000000000000000000140411361143263300272510ustar00rootroot00000000000000# 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.common.utils import test_utils from tempest.lib import decorators CONF = config.CONF class FloatingIPAdminTestJSON(base.BaseAdminNetworkTest): 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( test_utils.call_and_ignore_notfound_exc, 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( test_utils.call_and_ignore_notfound_exc, 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( test_utils.call_and_ignore_notfound_exc, 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-23.0.0/tempest/api/network/admin/test_metering_extensions.py000066400000000000000000000200161361143263300257730ustar00rootroot00000000000000# 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-23.0.0/tempest/api/network/admin/test_negative_quotas.py000066400000000000000000000063721361143263300251110ustar00rootroot00000000000000# 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 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 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.driver.DbQuotaDriver """ @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) def setUp(self): super(QuotasNegativeTest, self).setUp() name = data_utils.rand_name('test_project_') description = data_utils.rand_name('desc_') self.project = identity.identity_utils(self.os_admin).create_project( name=name, description=description) self.addCleanup(identity.identity_utils(self.os_admin).delete_project, self.project['id']) @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.project['id'], network=2) # Create two networks n1 = self.admin_networks_client.create_network( tenant_id=self.project['id']) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.admin_networks_client.delete_network, n1['network']['id']) n2 = self.admin_networks_client.create_network( tenant_id=self.project['id']) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.admin_networks_client.delete_network, n2['network']['id']) # Try to create a third network while the quota is two with self.assertRaisesRegex( lib_exc.Conflict, r"Quota exceeded for resources: \['network'\].*"): n3 = self.admin_networks_client.create_network( tenant_id=self.project['id']) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.admin_networks_client.delete_network, n3['network']['id']) tempest-23.0.0/tempest/api/network/admin/test_ports.py000066400000000000000000000123111361143263300230500ustar00rootroot00000000000000# 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.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators CONF = config.CONF class PortsAdminExtendedAttrsTestJSON(base.BaseAdminNetworkTest): @classmethod def setup_clients(cls): super(PortsAdminExtendedAttrsTestJSON, cls).setup_clients() cls.hyper_client = cls.os_admin.hypervisor_client @classmethod def resource_setup(cls): super(PortsAdminExtendedAttrsTestJSON, cls).resource_setup() cls.network = cls.create_network() if CONF.service_available.nova: hyper_list = cls.hyper_client.list_hypervisors() cls.host_id = hyper_list['hypervisors'][0]['hypervisor_hostname'] @decorators.idempotent_id('8e8569c1-9ac7-44db-8bc1-f5fb2814f29b') @utils.services('compute') def test_create_port_binding_ext_attr(self): post_body = {"network_id": self.network['id'], "binding:host_id": self.host_id, "name": data_utils.rand_name(self.__class__.__name__)} body = self.admin_ports_client.create_port(**post_body) port = body['port'] self.addCleanup( test_utils.call_and_ignore_notfound_exc, 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') @utils.services('compute') def test_update_port_binding_ext_attr(self): post_body = {"network_id": self.network['id'], "name": data_utils.rand_name(self.__class__.__name__)} body = self.admin_ports_client.create_port(**post_body) port = body['port'] self.addCleanup( test_utils.call_and_ignore_notfound_exc, 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') @utils.services('compute') def test_list_ports_binding_ext_attr(self): # Create a new port post_body = {"network_id": self.network['id'], "name": data_utils.rand_name(self.__class__.__name__)} body = self.admin_ports_client.create_port(**post_body) port = body['port'] self.addCleanup( test_utils.call_and_ignore_notfound_exc, 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( name=data_utils.rand_name(self.__class__.__name__), network_id=self.network['id']) port = body['port'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, 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-23.0.0/tempest/api/network/admin/test_quotas.py000066400000000000000000000105531361143263300232230ustar00rootroot00000000000000# 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.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.driver.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']) quota_set = self.admin_quotas_client.show_quotas(project_id)['quota'] default_quotas = self.admin_quotas_client.show_default_quotas( project_id)['quota'] self.assertEqual(default_quotas, quota_set) @decorators.idempotent_id('2390f766-836d-40ef-9aeb-e810d78207fb') def test_quotas(self): new_quotas = {'network': 0, 'port': 0} self._check_quotas(new_quotas) @testtools.skipUnless(utils.is_extension_enabled( 'quota_details', 'network'), 'Quota details extension not enabled.') @decorators.idempotent_id('7b05ec5f-bf44-43cb-b28f-ddd72a824288') def test_show_quota_details(self): # Show quota details for an existing project quota_details = self.admin_quotas_client.show_quota_details( self.admin_quotas_client.tenant_id)['quota'] expected_keys = ['used', 'limit', 'reserved'] for resource_type in quota_details: for key in expected_keys: self.assertIn(key, quota_details[resource_type]) tempest-23.0.0/tempest/api/network/admin/test_routers.py000066400000000000000000000235051361143263300234130ustar00rootroot00000000000000# 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.common.utils import test_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) 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(test_utils.call_and_ignore_notfound_exc, 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(test_utils.call_and_ignore_notfound_exc, 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(test_utils.call_and_ignore_notfound_exc, 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, device_owner="network:router_gateway") 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-23.0.0/tempest/api/network/admin/test_routers_dvr.py000066400000000000000000000133151361143263300242640ustar00rootroot00000000000000# 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.common.utils import test_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(test_utils.call_and_ignore_notfound_exc, 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(test_utils.call_and_ignore_notfound_exc, 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(test_utils.call_and_ignore_notfound_exc, 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-23.0.0/tempest/api/network/admin/test_routers_negative.py000066400000000000000000000052441361143263300252750ustar00rootroot00000000000000# 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.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 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( name=data_utils.rand_name(self.__class__.__name__), network_id=CONF.network.public_network_id)['port'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, 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-23.0.0/tempest/api/network/base.py000066400000000000000000000240321361143263300204670ustar00rootroot00000000000000# 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.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 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.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc, cls.networks_client.delete_network, network['id']) 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.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc, cls.subnets_client.delete_subnet, subnet['id']) return subnet @classmethod def create_port(cls, network, **kwargs): if 'name' not in kwargs: kwargs['name'] = data_utils.rand_name(cls.__name__) """Wrapper utility that returns a test port.""" body = cls.ports_client.create_port(network_id=network['id'], **kwargs) port = body['port'] cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc, cls.ports_client.delete_port, port['id']) 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.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc, cls.delete_router, 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.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc, cls.floating_ips_client.delete_floatingip, fip['id']) 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-23.0.0/tempest/api/network/base_security_groups.py000066400000000000000000000044051361143263300240170ustar00rootroot00000000000000# 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 from tempest.lib.common.utils import test_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(test_utils.call_and_ignore_notfound_exc, 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-23.0.0/tempest/api/network/test_agent_management_negative.py000066400000000000000000000021521361143263300257670ustar00rootroot00000000000000# Copyright 2018 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 import decorators class AgentManagementNegativeTest(base.BaseNetworkTest): @decorators.idempotent_id('e335be47-b9a1-46fd-be30-0874c0b751e6') @decorators.attr(type=['negative']) def test_list_agents_non_admin(self): """Validate that non-admin user cannot list agents.""" # Listing agents requires admin_only permissions. body = self.agents_client.list_agents() self.assertEmpty(body["agents"]) tempest-23.0.0/tempest/api/network/test_allowed_address_pair.py000066400000000000000000000136231361143263300247670ustar00rootroot00000000000000# 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.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators 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'], name=data_utils.rand_name(self.__class__.__name__), allowed_address_pairs=allowed_address_pairs) port_id = body['port']['id'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, 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'], name=data_utils.rand_name(self.__class__.__name__)) port_id = body['port']['id'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, 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'], name=data_utils.rand_name(self.__class__.__name__)) newportid = resp['port']['id'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, 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-23.0.0/tempest/api/network/test_dhcp_ipv6.py000066400000000000000000000460121361143263300225000ustar00rootroot00000000000000# 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() cls.ports = [] cls.subnets = [] cls.routers = [] 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) self.subnets.append(subnet) port_mac = data_utils.rand_mac_address() port = self.create_port(self.network, mac_address=port_mac) self.ports.append(port) 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) self.subnets.append(subnet_slaac) subnet_dhcp = self.create_subnet( self.network, **kwargs_dhcp) self.subnets.append(subnet_dhcp) else: subnet_dhcp = self.create_subnet( self.network, **kwargs_dhcp) self.subnets.append(subnet_dhcp) subnet_slaac = self.create_subnet(self.network, **kwargs) self.subnets.append(subnet_slaac) 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) self.ports.append(port) 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) self.subnets.append(subnet_slaac) subnet_dhcp = self.create_subnet( self.network, ip_version=4) self.subnets.append(subnet_dhcp) else: subnet_dhcp = self.create_subnet( self.network, ip_version=4) self.subnets.append(subnet_dhcp) subnet_slaac = self.create_subnet(self.network, **kwargs) self.subnets.append(subnet_slaac) 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) self.ports.append(port) 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) self.subnets.append(subnet) port = self.create_port(self.network) self.ports.append(port) 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) self.subnets.append(subnet) 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}]) self.ports.append(port) 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) self.subnets.append(subnet) 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) self.subnets.append(subnet) 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}]) self.ports.append(port) 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) self.subnets.append(subnet) router = self.create_router(admin_state_up=True) self.routers.append(router) 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-23.0.0/tempest/api/network/test_extensions.py000066400000000000000000000060411361143263300230130ustar00rootroot00000000000000# 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-23.0.0/tempest/api/network/test_extra_dhcp_options.py000066400000000000000000000102311361143263300245040ustar00rootroot00000000000000# 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.common.utils import test_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'], name=data_utils.rand_name(self.__class__.__name__), extra_dhcp_opts=self.extra_dhcp_opts) port_id = body['port']['id'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, 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-23.0.0/tempest/api/network/test_floating_ips.py000066400000000000000000000304051361143263300232730ustar00rootroot00000000000000# 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 data_utils from tempest.common.utils import net_utils from tempest import config from tempest.lib.common.utils import test_utils 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 cls.ports = [] for i in range(2): port = cls.create_port(cls.network) cls.ports.append(port) @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( test_utils.call_and_ignore_notfound_exc, 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']) # Explicity test deletion of floating IP self.floating_ips_client.delete_floatingip(created_floating_ip['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( test_utils.call_and_ignore_notfound_exc, self.floating_ips_client.delete_floatingip, created_floating_ip['id']) # Create a port port = self.ports_client.create_port( network_id=self.network['id'], name=data_utils.rand_name(self.__class__.__name__)) 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( test_utils.call_and_ignore_notfound_exc, self.floating_ips_client.delete_floatingip, created_floating_ip['id']) self.assertEqual(created_floating_ip['router_id'], self.router['id']) network_name = data_utils.rand_name(self.__class__.__name__) network2 = self.networks_client.create_network( name=network_name)['network'] self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.networks_client.delete_network, network2['id']) subnet2 = self.create_subnet(network2) self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.subnets_client.delete_subnet, subnet2['id']) router2 = self.create_router(external_network_id=self.ext_net_id) self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.routers_client.delete_router, router2['id']) self.create_router_interface(router2['id'], subnet2['id']) self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.routers_client.remove_router_interface, router2['id'], subnet_id=subnet2['id']) port_other_router = self.create_port(network2) self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.ports_client.delete_port, port_other_router['id']) # 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( test_utils.call_and_ignore_notfound_exc, 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'], name=data_utils.rand_name(self.__class__.__name__), fixed_ips=fixed_ips) port = body['port'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, 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(test_utils.call_and_ignore_notfound_exc, 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-23.0.0/tempest/api/network/test_floating_ips_negative.py000066400000000000000000000077531361143263300251670ustar00rootroot00000000000000# 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.common.utils import test_utils 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( test_utils.call_and_ignore_notfound_exc, 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-23.0.0/tempest/api/network/test_networks.py000066400000000000000000000720411361143263300224730ustar00rootroot00000000000000# 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 _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) 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(test_utils.call_and_ignore_notfound_exc, self.networks_client.delete_network, network['id']) 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) # Verify network delete self.networks_client.delete_network(network['id']) @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.networks_client.delete_network, network['id']) # 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(test_utils.call_and_ignore_notfound_exc, self.networks_client.delete_network, network['id']) 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']) 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-23.0.0/tempest/api/network/test_networks_negative.py000066400000000000000000000103611361143263300243520ustar00rootroot00000000000000# 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, name=data_utils.rand_name(self.__class__.__name__)) @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-23.0.0/tempest/api/network/test_ports.py000066400000000000000000000521071361143263300217670ustar00rootroot00000000000000# 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 ipaddress import netaddr import six 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.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 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]) def _create_subnet(self, network, gateway='', cidr=None, mask_bits=None, **kwargs): subnet = self.create_subnet(network, gateway, cidr, mask_bits) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.subnets_client.delete_subnet, subnet['id']) return subnet def _create_network(self, network_name=None, **kwargs): network_name = network_name or data_utils.rand_name( self.__class__.__name__) network = self.networks_client.create_network( name=network_name, **kwargs)['network'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.networks_client.delete_network, network['id']) return network @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'], name=data_utils.rand_name(self.__class__.__name__)) 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])}]} self._create_subnet(network, cidr=address, mask_bits=address.prefixlen, **allocation_pools) body = self.ports_client.create_port( network_id=net_id, name=data_utils.rand_name(self.__class__.__name__)) self.addCleanup(test_utils.call_and_ignore_notfound_exc, 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) self.ports_client.delete_port(port['id']) @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() self._create_subnet(network) # Create two ports port_1 = self.ports_client.create_port( network_id=network['id'], name=data_utils.rand_name(self.__class__.__name__)) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.ports_client.delete_port, port_1['port']['id']) port_2 = self.ports_client.create_port( network_id=network['id'], name=data_utils.rand_name(self.__class__.__name__)) self.addCleanup(test_utils.call_and_ignore_notfound_exc, 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_net_ids = [port['network_id'] for port in ports] self.assertIn(port_1['port']['id'], port_ids) self.assertIn(network['id'], port_net_ids) # Check that every port has a fixed_ip that matches the query for addr in fixed_ips: port_ips = [port['ip_address'] for port in addr] self.assertIn(port_1_fixed_ip, port_ips, 'Port not matching IP filter found') @decorators.idempotent_id('79895408-85d5-460d-94e7-9531c5fd9123') @testtools.skipUnless( utils.is_extension_enabled('ip-substring-filtering', 'network'), 'ip-substring-filtering extension not enabled.') def test_port_list_filter_by_ip_substr(self): # Create network and subnet network = self._create_network() subnet = self._create_subnet(network) # Get two IP addresses ip_address_1 = None ip_address_2 = None ip_network = ipaddress.ip_network(six.text_type(subnet['cidr'])) for ip in ip_network: if ip == ip_network.network_address: continue if ip_address_1 is None: ip_address_1 = six.text_type(ip) else: ip_address_2 = ip_address_1 ip_address_1 = six.text_type(ip) # Make sure these two IP addresses have different substring if ip_address_1[:-1] != ip_address_2[:-1]: break # Create two ports fixed_ips = [{'subnet_id': subnet['id'], 'ip_address': ip_address_1}] port_1 = self.ports_client.create_port( network_id=network['id'], name=data_utils.rand_name(self.__class__.__name__), fixed_ips=fixed_ips) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.ports_client.delete_port, port_1['port']['id']) fixed_ips = [{'subnet_id': subnet['id'], 'ip_address': ip_address_2}] port_2 = self.ports_client.create_port( network_id=network['id'], name=data_utils.rand_name(self.__class__.__name__), fixed_ips=fixed_ips) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.ports_client.delete_port, port_2['port']['id']) # Scenario 1: List port1 (port2 is filtered out) if ip_address_1[:-1] != ip_address_2[:-1]: ips_filter = 'ip_address_substr=' + ip_address_1[:-1] else: ips_filter = 'ip_address_substr=' + ip_address_1 ports = self.ports_client.list_ports(fixed_ips=ips_filter)['ports'] # Check that we got the desired port 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([a['ip_address'] for a in addr]) port_net_ids = [port['network_id'] for port in ports] self.assertIn(network['id'], port_net_ids) self.assertIn(port_1['port']['id'], port_ids) self.assertIn(port_1['port']['fixed_ips'][0]['ip_address'], port_ips) self.assertNotIn(port_2['port']['id'], port_ids) self.assertNotIn( port_2['port']['fixed_ips'][0]['ip_address'], port_ips) # Scenario 2: List both port1 and port2 substr = ip_address_1 while substr not in ip_address_2: substr = substr[:-1] ips_filter = 'ip_address_substr=' + substr ports = self.ports_client.list_ports(fixed_ips=ips_filter)['ports'] # Check that we got both port 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([a['ip_address'] for a in addr]) port_net_ids = [port['network_id'] for port in ports] self.assertIn(network['id'], port_net_ids) self.assertIn(port_1['port']['id'], port_ids) self.assertIn(port_1['port']['fixed_ips'][0]['ip_address'], port_ips) self.assertIn(port_2['port']['id'], port_ids) self.assertIn(port_2['port']['fixed_ips'][0]['ip_address'], port_ips) @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._create_subnet(network) router = self.create_router() self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.routers_client.delete_router, router['id']) port = self.ports_client.create_port( network_id=network['id'], name=data_utils.rand_name(self.__class__.__name__)) # Add router interface to port created above self.routers_client.add_router_interface(router['id'], port_id=port['port']['id']) self.addCleanup(test_utils.call_and_ignore_notfound_exc, 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() subnet_1 = self._create_subnet(network) subnet_2 = self._create_subnet(network) 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(test_utils.call_and_ignore_notfound_exc, 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) 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(test_utils.call_and_ignore_notfound_exc, 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(test_utils.call_and_ignore_notfound_exc, self.security_groups_client.delete_security_group, security_group['security_group']['id']) post_body = { "name": data_utils.rand_name(self.__class__.__name__), "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(test_utils.call_and_ignore_notfound_exc, 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(self.__class__.__name__), "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'], name=data_utils.rand_name(self.__class__.__name__)) 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, name=data_utils.rand_name(self.__class__.__name__)) self.addCleanup(test_utils.call_and_ignore_notfound_exc, 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._create_subnet(network) port = self.create_port(network, security_groups=[]) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.ports_client.delete_port, port['id']) self.assertIsNotNone(port['security_groups']) self.assertEmpty(port['security_groups']) class PortsIpV6TestJSON(PortsTestJSON): _ip_version = 6 tempest-23.0.0/tempest/api/network/test_routers.py000066400000000000000000000372611361143263300223270ustar00rootroot00000000000000# 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.common.utils import test_utils from tempest.lib import decorators CONF = config.CONF class RoutersTest(base.BaseNetworkTest): 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 router_name = data_utils.rand_name(self.__class__.__name__ + '-router') router = self.create_router( router_name, admin_state_up=False, external_network_id=CONF.network.public_network_id) self.addCleanup(self.delete_router, router) self.assertEqual(router['name'], router_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_name = data_utils.rand_name(self.__class__.__name__) network = self.networks_client.create_network( name=network_name)['network'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.networks_client.delete_network, network['id']) subnet = self.create_subnet(network) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.subnets_client.delete_subnet, subnet['id']) router = self.create_router() self.addCleanup(self.delete_router, 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_name = data_utils.rand_name(self.__class__.__name__) network = self.networks_client.create_network( name=network_name)['network'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, 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.delete_router, router) port_body = self.ports_client.create_port( network_id=network['id'], name=data_utils.rand_name(self.__class__.__name__)) # add router interface to port created above interface = self.routers_client.add_router_interface( router['id'], port_id=port_body['port']['id']) self.addCleanup(test_utils.call_and_ignore_notfound_exc, 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']) self.routers_client.remove_router_interface( router['id'], port_id=port_body['port']['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( name=data_utils.rand_name(self.__class__.__name__), 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(test_utils.call_and_ignore_notfound_exc, 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_router, router) 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_name = data_utils.rand_name(self.__class__.__name__) network = self.networks_client.create_network( name=network_name)['network'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.networks_client.delete_network, network['id']) subnet = self.create_subnet(network, cidr=next_cidr) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.subnets_client.delete_subnet, subnet['id']) next_cidr = next_cidr.next() # Add router interface with subnet id self.create_router_interface(router['id'], subnet['id']) self.addCleanup(self._remove_router_interface_with_subnet_id, 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.addCleanup(self.delete_router, 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): network_name = data_utils.rand_name(self.__class__.__name__) network01 = self.networks_client.create_network( name=network_name)['network'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.networks_client.delete_network, network01['id']) network_name = data_utils.rand_name(self.__class__.__name__) network02 = self.networks_client.create_network( name=network_name)['network'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.networks_client.delete_network, network02['id']) subnet01 = self.create_subnet(network01) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.subnets_client.delete_subnet, subnet01['id']) sub02_cidr = self.cidr.next() subnet02 = self.create_subnet(network02, cidr=sub02_cidr) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.subnets_client.delete_subnet, subnet02['id']) router = self.create_router() self.addCleanup(self.delete_router, 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_name = data_utils.rand_name(self.__class__.__name__) network = self.networks_client.create_network( name=network_name)['network'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.networks_client.delete_network, network['id']) subnet = self.create_subnet(network) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.subnets_client.delete_subnet, subnet['id']) router = self.create_router() self.addCleanup(self.delete_router, 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-23.0.0/tempest/api/network/test_routers_negative.py000066400000000000000000000131721361143263300242040ustar00rootroot00000000000000# 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 from tempest.lib import exceptions as lib_exc 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.addCleanup(self.routers_client.remove_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-23.0.0/tempest/api/network/test_security_groups.py000066400000000000000000000267521361143263300240750ustar00rootroot00000000000000# 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.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators 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(test_utils.call_and_ignore_notfound_exc, 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) # Delete security group self._delete_security_group(group_create_body['security_group']['id']) @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) self._delete_security_group_rule( rule_create_body['security_group_rule']['id']) @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' # The Neutron API accepts 'icmp', 'icmpv6' and 'ipv6-icmp' for # IPv6 ICMP protocol names, but the latter is preferred and the # others considered "legacy". Use 'ipv6-icmp' as the API could # change to return only that value, see # https://review.opendev.org/#/c/453346/ # The neutron-tempest-plugin API tests pass all three and verify # the output, so there is no need to duplicate that here. protocol = 'ipv6-icmp' if self._ip_version == 6 else '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-23.0.0/tempest/api/network/test_security_groups_negative.py000066400000000000000000000260171361143263300257510ustar00rootroot00000000000000# 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-23.0.0/tempest/api/network/test_service_providers.py000066400000000000000000000022351361143263300243520ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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-23.0.0/tempest/api/network/test_subnetpools_extensions.py000066400000000000000000000061461361143263300254560ustar00rootroot00000000000000# 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-23.0.0/tempest/api/network/test_tags.py000066400000000000000000000201721361143263300215530ustar00rootroot00000000000000# 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-23.0.0/tempest/api/network/test_versions.py000066400000000000000000000041021361143263300224600ustar00rootroot00000000000000# 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) @decorators.attr(type='smoke') @decorators.idempotent_id('e64b7216-3178-4263-967c-d389290988bf') def test_show_api_v2_details(self): """Test that GET /v2.0/ returns expected resources.""" current_version = 'v2.0' expected_resources = ('subnet', 'network', 'port') result = self.network_versions_client.show_version(current_version) actual_resources = [r['name'] for r in result['resources']] for resource in expected_resources: self.assertIn(resource, actual_resources) tempest-23.0.0/tempest/api/object_storage/000077500000000000000000000000001361143263300205035ustar00rootroot00000000000000tempest-23.0.0/tempest/api/object_storage/__init__.py000066400000000000000000000000001361143263300226020ustar00rootroot00000000000000tempest-23.0.0/tempest/api/object_storage/base.py000066400000000000000000000130431361143263300217700ustar00rootroot00000000000000# 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(or string of a container) to be deleted :param container_client: Client to be used to delete containers :param object_client: Client to be used to delete objects """ if isinstance(containers, str): containers = [containers] 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-23.0.0/tempest/api/object_storage/test_account_bulk.py000066400000000000000000000144271361143263300245750ustar00rootroot00000000000000# 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-23.0.0/tempest/api/object_storage/test_account_quotas.py000066400000000000000000000104271361143263300251500ustar00rootroot00000000000000# 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-23.0.0/tempest/api/object_storage/test_account_quotas_negative.py000066400000000000000000000067121361143263300270340ustar00rootroot00000000000000# 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-23.0.0/tempest/api/object_storage/test_account_services.py000066400000000000000000000411351361143263300254570ustar00rootroot00000000000000# 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.addClassResourceCleanup(base.delete_containers, [name], cls.container_client, cls.object_client) cls.containers.append(name) cls.containers_count = len(cls.containers) @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 = 'tempest-a' 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-23.0.0/tempest/api/object_storage/test_account_services_negative.py000066400000000000000000000041151361143263300273360ustar00rootroot00000000000000# 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-23.0.0/tempest/api/object_storage/test_container_acl.py000066400000000000000000000100311361143263300247100ustar00rootroot00000000000000# 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_id = self.os_roles_operator_alt.credentials.tenant_id user_id = self.os_roles_operator_alt.credentials.user_id cont_headers = {'X-Container-Read': tenant_id + ':' + user_id} 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_id = self.os_roles_operator_alt.credentials.tenant_id user_id = self.os_roles_operator_alt.credentials.user_id cont_headers = {'X-Container-Write': tenant_id + ':' + user_id} 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-23.0.0/tempest/api/object_storage/test_container_acl_negative.py000066400000000000000000000251141361143263300266020ustar00rootroot00000000000000# 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-23.0.0/tempest/api/object_storage/test_container_quotas.py000066400000000000000000000107171361143263300255000ustar00rootroot00000000000000# 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-23.0.0/tempest/api/object_storage/test_container_services.py000066400000000000000000000424741361143263300260140ustar00rootroot00000000000000# 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-23.0.0/tempest/api/object_storage/test_container_services_negative.py000066400000000000000000000203131361143263300276620ustar00rootroot00000000000000# 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-23.0.0/tempest/api/object_storage/test_container_staticweb.py000066400000000000000000000150531361143263300261470ustar00rootroot00000000000000# 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-23.0.0/tempest/api/object_storage/test_container_sync.py000066400000000000000000000137741361143263300251460ustar00rootroot00000000000000# 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): 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 = [] cls.clients = {} # 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.addClassResourceCleanup(base.delete_containers, cont_name, client[0], client[1]) cls.containers.append(cont_name) 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-23.0.0/tempest/api/object_storage/test_container_sync_middleware.py000066400000000000000000000040711361143263300273310ustar00rootroot00000000000000# 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-23.0.0/tempest/api/object_storage/test_crossdomain.py000066400000000000000000000042671361143263300244460ustar00rootroot00000000000000# 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-23.0.0/tempest/api/object_storage/test_healthcheck.py000066400000000000000000000030421361143263300243560ustar00rootroot00000000000000# 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-23.0.0/tempest/api/object_storage/test_object_expiry.py000066400000000000000000000072711361143263300247710ustar00rootroot00000000000000# 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-23.0.0/tempest/api/object_storage/test_object_formpost.py000066400000000000000000000106701361143263300253170ustar00rootroot00000000000000# 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-23.0.0/tempest/api/object_storage/test_object_formpost_negative.py000066400000000000000000000121161361143263300271760ustar00rootroot00000000000000# 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-23.0.0/tempest/api/object_storage/test_object_services.py000066400000000000000000001353271361143263300253000ustar00rootroot00000000000000# 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-23.0.0/tempest/api/object_storage/test_object_slo.py000066400000000000000000000160501361143263300242410ustar00rootroot00000000000000# 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-23.0.0/tempest/api/object_storage/test_object_temp_url.py000066400000000000000000000161041361143263300252730ustar00rootroot00000000000000# 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-23.0.0/tempest/api/object_storage/test_object_temp_url_negative.py000066400000000000000000000075071361143263300271640ustar00rootroot00000000000000# 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-23.0.0/tempest/api/object_storage/test_object_version.py000066400000000000000000000103231361143263300251260ustar00rootroot00000000000000# 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): 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.addCleanup(base.delete_containers, [vers_container_name], self.container_client, self.object_client) 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.addCleanup(base.delete_containers, [base_container_name], self.container_client, self.object_client) 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-23.0.0/tempest/api/volume/000077500000000000000000000000001361143263300170205ustar00rootroot00000000000000tempest-23.0.0/tempest/api/volume/__init__.py000066400000000000000000000000001361143263300211170ustar00rootroot00000000000000tempest-23.0.0/tempest/api/volume/admin/000077500000000000000000000000001361143263300201105ustar00rootroot00000000000000tempest-23.0.0/tempest/api/volume/admin/__init__.py000066400000000000000000000000001361143263300222070ustar00rootroot00000000000000tempest-23.0.0/tempest/api/volume/admin/test_backends_capabilities.py000066400000000000000000000047301361143263300260100ustar00rootroot00000000000000# 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): @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 # Check response schema self.admin_capabilities_client.show_backend_capabilities(self.hosts[0]) @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 = sorted(list(map(operator.itemgetter(*VOLUME_STATS), cinder_pools))) observed_list = sorted(list(map(operator.itemgetter(*VOLUME_STATS), capabilities))) self.assertEqual(expected_list, observed_list) tempest-23.0.0/tempest/api/volume/admin/test_group_snapshots.py000066400000000000000000000251631361143263300247660ustar00rootroot00000000000000# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD # 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 BaseGroupSnapshotsTest(base.BaseVolumeAdminTest): @classmethod def skip_checks(cls): super(BaseGroupSnapshotsTest, cls).skip_checks() if not CONF.volume_feature_enabled.snapshot: raise cls.skipException("Cinder volume snapshots are disabled") def _create_group_snapshot(self, **kwargs): if 'name' not in kwargs: kwargs['name'] = data_utils.rand_name( self.__class__.__name__ + '-Group_Snapshot') group_snapshot = self.group_snapshots_client.create_group_snapshot( **kwargs)['group_snapshot'] group_snapshot['group_id'] = kwargs['group_id'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self._delete_group_snapshot, group_snapshot) waiters.wait_for_volume_resource_status( self.group_snapshots_client, group_snapshot['id'], 'available') return group_snapshot def _delete_group_snapshot(self, group_snapshot): 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'] == group_snapshot['group_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']) class GroupSnapshotsTest(BaseGroupSnapshotsTest): _api_version = 3 min_microversion = '3.14' max_microversion = 'latest' @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=group_type['id'], volume_types=[volume_type['id']]) # 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._create_group_snapshot( group_id=grp['id'], 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') 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) 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=group_type['id'], volume_types=[volume_type['id']]) # 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._create_group_snapshot( group_id=grp['id'], name=group_snapshot_name) 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') # 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('7d7fc000-0b4c-4376-a372-544116d2e127') @decorators.related_bug('1739031') def test_delete_group_snapshots_following_updated_volumes(self): volume_type = self.create_volume_type() group_type = self.create_group_type() # Create a volume group grp = self.create_group(group_type=group_type['id'], volume_types=[volume_type['id']]) # Note: When dealing with consistency groups all volumes must # reside on the same backend. Adding volumes to the same consistency # group from multiple backends isn't supported. In order to ensure all # volumes share the same backend, all volumes must share same # volume-type and group id. volume_list = [] for _ in range(2): volume = self.create_volume(volume_type=volume_type['id'], group_id=grp['id']) volume_list.append(volume['id']) for vol in volume_list: self.groups_client.update_group(grp['id'], remove_volumes=vol) waiters.wait_for_volume_resource_status( self.groups_client, grp['id'], 'available') self.groups_client.update_group(grp['id'], add_volumes=vol) waiters.wait_for_volume_resource_status( self.groups_client, grp['id'], 'available') # Verify the created volumes are associated with consistency 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)) # Create a snapshot group group_snapshot = self._create_group_snapshot(group_id=grp['id']) snapshots = self.snapshots_client.list_snapshots( detail=True)['snapshots'] for snap in snapshots: if snap['volume_id'] in volume_list: waiters.wait_for_volume_resource_status( self.snapshots_client, snap['id'], 'available') # Delete a snapshot group self._delete_group_snapshot(group_snapshot) class GroupSnapshotsV319Test(BaseGroupSnapshotsTest): _api_version = 3 min_microversion = '3.19' max_microversion = 'latest' @decorators.idempotent_id('3b42c9b9-c984-4444-816e-ca2e1ed30b40') @decorators.skip_because(bug='1770179') 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=group_type['id'], volume_types=[volume_type['id']]) # Create volume volume = self.create_volume(volume_type=volume_type['id'], group_id=group['id']) # Create group snapshot group_snapshot = self._create_group_snapshot(group_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') # 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) tempest-23.0.0/tempest/api/volume/admin/test_group_type_specs.py000066400000000000000000000061551361143263300251220ustar00rootroot00000000000000# 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.lib import decorators from tempest.lib import exceptions as lib_exc class GroupTypeSpecsTest(base.BaseVolumeAdminTest): _api_version = 3 min_microversion = '3.11' max_microversion = 'latest' @decorators.idempotent_id('bb4e30d0-de6e-4f4d-866c-dcc48d023b4e') def test_group_type_specs_create_show_update_list_delete(self): # Create new group type group_type = self.create_group_type() # Create new group type specs create_specs = { "key1": "value1", "key2": "value2" } body = self.admin_group_types_client.create_or_update_group_type_specs( group_type['id'], create_specs)['group_specs'] self.assertEqual(create_specs, body) # Create a new group type spec and update an existing group type spec update_specs = { "key2": "value2-updated", "key3": "value3" } body = self.admin_group_types_client.create_or_update_group_type_specs( group_type['id'], update_specs)['group_specs'] self.assertEqual(update_specs, body) # Show specified item of group type specs spec_keys = ['key2', 'key3'] for key in spec_keys: body = self.admin_group_types_client.show_group_type_specs_item( group_type['id'], key) self.assertIn(key, body) self.assertEqual(update_specs[key], body[key]) # Update specified item of group type specs update_key = 'key3' update_spec = {update_key: "value3-updated"} body = self.admin_group_types_client.update_group_type_specs_item( group_type['id'], update_key, update_spec) self.assertEqual(update_spec, body) # List all group type specs that created or updated above list_specs = {} list_specs.update(create_specs) list_specs.update(update_specs) list_specs.update(update_spec) body = self.admin_group_types_client.list_group_type_specs( group_type['id'])['group_specs'] self.assertEqual(list_specs, body) # Delete specified item of group type specs delete_key = 'key1' self.admin_group_types_client.delete_group_type_specs_item( group_type['id'], delete_key) self.assertRaises( lib_exc.NotFound, self.admin_group_types_client.show_group_type_specs_item, group_type['id'], delete_key) tempest-23.0.0/tempest/api/volume/admin/test_group_types.py000066400000000000000000000055421361143263300241070ustar00rootroot00000000000000# 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_update_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) update_params = { 'name': data_utils.rand_name( self.__class__.__name__ + '-updated-group-type'), 'description': 'updated-group-type-desc' } updated_group_type = self.admin_group_types_client.update_group_type( body['id'], **update_params)['group_type'] for key, expected_val in update_params.items(): self.assertEqual(expected_val, updated_group_type[key]) fetched_group_type = self.admin_group_types_client.show_group_type( body['id'])['group_type'] params.update(update_params) # Add updated params to original params. 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-23.0.0/tempest/api/volume/admin/test_groups.py000066400000000000000000000202321361143263300230370ustar00rootroot00000000000000# 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 import decorators CONF = config.CONF class GroupsTest(base.BaseVolumeAdminTest): _api_version = 3 min_microversion = '3.13' 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=group_type['id'], volume_types=[volume_type['id']], name=grp1_name) grp1_id = grp1['id'] grp2_name = data_utils.rand_name('Group2') grp2 = self.create_group(group_type=group_type['id'], volume_types=[volume_type['id']], 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('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=group_type['id'], volume_types=[volume_type['id']]) # 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 GroupsV314Test(base.BaseVolumeAdminTest): _api_version = 3 min_microversion = '3.14' max_microversion = 'latest' @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=group_type['id'], volume_types=[volume_type['id']]) # 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') class GroupsV320Test(base.BaseVolumeAdminTest): _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=group_type['id'], volume_types=[volume_type['id']]) # 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-23.0.0/tempest/api/volume/admin/test_multi_backend.py000066400000000000000000000127721361143263300243330ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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") if len(set(CONF.volume.backend_names)) < 2: raise cls.skipException("Requires at least two different " "backend names") @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 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-23.0.0/tempest/api/volume/admin/test_qos.py000066400000000000000000000146421361143263300223320ustar00rootroot00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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-23.0.0/tempest/api/volume/admin/test_snapshot_manage.py000066400000000000000000000104111361143263300246650ustar00rootroot00000000000000# 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.snapshot: raise cls.skipException("Cinder volume snapshots are disabled") 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)['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-23.0.0/tempest/api/volume/admin/test_snapshots_actions.py000066400000000000000000000120641361143263300252660ustar00rootroot00000000000000# 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-23.0.0/tempest/api/volume/admin/test_user_messages.py000066400000000000000000000053071361143263300243730ustar00rootroot00000000000000# 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 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_show_messages(self): message_id = self._create_user_message() self.addCleanup(self.messages_client.delete_message, message_id) # show message, check response schema self.messages_client.show_message(message_id) # list messages, check response schema self.messages_client.list_messages() @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-23.0.0/tempest/api/volume/admin/test_volume_hosts.py000066400000000000000000000045561361143263300242620ustar00rootroot00000000000000# 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) @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. for host in c_vol_hosts: host_details = self.admin_hosts_client.show_host(host)['host'] self.assertNotEmpty(host_details) tempest-23.0.0/tempest/api/volume/admin/test_volume_manage.py000066400000000000000000000066711361143263300243520ustar00rootroot00000000000000# 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-23.0.0/tempest/api/volume/admin/test_volume_pools.py000066400000000000000000000030621361143263300242450ustar00rootroot00000000000000# 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.assertNotEmpty(cinder_pools, "no cinder pools listed.") 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-23.0.0/tempest/api/volume/admin/test_volume_quota_classes.py000066400000000000000000000111611361143263300257560ustar00rootroot00000000000000# 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-23.0.0/tempest/api/volume/admin/test_volume_quotas.py000066400000000000000000000171361361143263300244340ustar00rootroot00000000000000# 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 tempest_fixtures as fixtures from tempest.common import waiters from tempest.lib import decorators QUOTA_KEYS = ['gigabytes', 'snapshots', 'volumes', 'backups', 'backup_gigabytes', 'per_volume_gigabytes'] class VolumeQuotasAdminTestJSON(base.BaseVolumeAdminTest): credentials = ['primary', 'alt', 'admin'] def setUp(self): # NOTE(jeremy.zhang): Avoid conflicts with volume quota class tests. self.useFixture(fixtures.LockFixture('volume_quotas')) super(VolumeQuotasAdminTestJSON, self).setUp() @classmethod def setup_credentials(cls): super(VolumeQuotasAdminTestJSON, cls).setup_credentials() cls.demo_tenant_id = cls.os_primary.credentials.tenant_id @classmethod def setup_clients(cls): super(VolumeQuotasAdminTestJSON, cls).setup_clients() cls.transfer_client = cls.os_primary.volume_transfers_client_latest cls.alt_transfer_client = cls.os_alt.volume_transfers_client_latest @classmethod def resource_setup(cls): super(VolumeQuotasAdminTestJSON, cls).resource_setup() # Save the current set of quotas so that some tests may use it # to restore the quotas to their original values after they are # done. original_quota_set = (cls.admin_quotas_client.show_quota_set( cls.demo_tenant_id)['quota_set']) cls.cleanup_quota_set = dict( (k, v) for k, v in original_quota_set.items() if k in QUOTA_KEYS) @decorators.idempotent_id('59eada70-403c-4cef-a2a3-a8ce2f1b07a0') def test_list_quotas(self): # Check response schema self.admin_quotas_client.show_quota_set(self.demo_tenant_id) @decorators.idempotent_id('2be020a2-5fdd-423d-8d35-a7ffbc36e9f7') def test_list_default_quotas(self): # Check response schema self.admin_quotas_client.show_default_quota_set(self.demo_tenant_id) @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 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'] self.addCleanup(self.admin_quotas_client.update_quota_set, self.demo_tenant_id, **self.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): # Check response schema self.admin_quotas_client.show_quota_set( self.os_admin.credentials.tenant_id, params={'usage': True}) @decorators.idempotent_id('874b35a9-51f1-4258-bec5-cd561b6690d3') def test_delete_quota(self): # Admin can delete the resource quota set for a project self.addCleanup(self.admin_quotas_client.update_quota_set, self.demo_tenant_id, **self.cleanup_quota_set) quota_set_default = self.admin_quotas_client.show_default_quota_set( self.demo_tenant_id)['quota_set'] volume_default = quota_set_default['volumes'] self.admin_quotas_client.update_quota_set( self.demo_tenant_id, volumes=(volume_default + 5)) self.admin_quotas_client.delete_quota_set(self.demo_tenant_id) quota_set_new = (self.admin_quotas_client.show_quota_set( self.demo_tenant_id)['quota_set']) self.assertEqual(volume_default, quota_set_new['volumes']) @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('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.os_alt.volumes_client_latest.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.os_alt.volumes_client_latest, 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.os_alt.volumes_client_latest.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-23.0.0/tempest/api/volume/admin/test_volume_quotas_negative.py000066400000000000000000000066771361143263300263260ustar00rootroot00000000000000# 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 QUOTA_KEYS = ['gigabytes', 'snapshots', 'volumes', 'backups', 'backup_gigabytes', 'per_volume_gigabytes'] class VolumeQuotasNegativeTestJSON(base.BaseVolumeAdminTest): @classmethod def setup_credentials(cls): super(VolumeQuotasNegativeTestJSON, cls).setup_credentials() cls.demo_tenant_id = cls.os_primary.credentials.tenant_id @classmethod def resource_setup(cls): super(VolumeQuotasNegativeTestJSON, cls).resource_setup() # Save the current set of quotas, then set up the cleanup method # to restore the quotas to their original values after the tests # from this class are done. This is needed just in case Tempest is # configured to use pre-provisioned projects/user accounts. original_quota_set = (cls.admin_quotas_client.show_quota_set( cls.demo_tenant_id)['quota_set']) cleanup_quota_set = dict( (k, v) for k, v in original_quota_set.items() if k in QUOTA_KEYS) cls.addClassResourceCleanup(cls.admin_quotas_client.update_quota_set, cls.demo_tenant_id, **cleanup_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.admin_quotas_client.update_quota_set(self.demo_tenant_id, volumes=1, gigabytes=-1) 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): self.admin_quotas_client.update_quota_set( self.demo_tenant_id, gigabytes=CONF.volume.volume_size, volumes=-1) self.assertRaises(lib_exc.OverLimit, self.volumes_client.create_volume, size=CONF.volume.volume_size * 2) @decorators.attr(type=['negative']) @decorators.idempotent_id('d321dc21-d8c6-401f-95fe-49f4845f1a6d') def test_volume_extend_gigabytes_quota_deviation(self): self.admin_quotas_client.update_quota_set( self.demo_tenant_id, gigabytes=CONF.volume.volume_size) self.assertRaises(lib_exc.OverLimit, self.volumes_client.extend_volume, self.volume['id'], new_size=CONF.volume.volume_size * 2) tempest-23.0.0/tempest/api/volume/admin/test_volume_retype.py000066400000000000000000000156311361143263300244260ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 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 VolumeRetypeTest(base.BaseVolumeAdminTest): def _wait_for_internal_volume_cleanup(self, vol): # 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 = self.admin_volume_client.list_volumes( params={'all_tenants': True, 'name': vol['name']})['volumes'] for fetched_vol in fetched_list: if fetched_vol['id'] != vol['id']: # This is the Cinder internal volume LOG.debug('Waiting for internal volume %s deletion', fetched_vol['id']) self.admin_volume_client.wait_for_resource_deletion( fetched_vol['id']) break @abc.abstractmethod def _verify_migration(self, source_vol, dest_vol): pass def _create_volume_from_snapshot(self): # Create a volume in the first backend src_vol = self.create_volume(volume_type=self.src_vol_type['name']) # Create a volume snapshot snapshot = self.create_snapshot(src_vol['id']) # Create a volume from the snapshot src_vol = self.create_volume(volume_type=self.src_vol_type['name'], snapshot_id=snapshot['id']) # Delete the snapshot self.snapshots_client.delete_snapshot(snapshot['id']) self.snapshots_client.wait_for_resource_deletion(snapshot['id']) return src_vol def _retype_volume(self, volume, migration_policy): volume_source = self.admin_volume_client.show_volume( volume['id'])['volume'] self.volumes_client.retype_volume( volume['id'], new_type=self.dst_vol_type['name'], migration_policy=migration_policy) self.addCleanup(self._wait_for_internal_volume_cleanup, volume) waiters.wait_for_volume_retype(self.volumes_client, volume['id'], self.dst_vol_type['name']) volume_dest = self.admin_volume_client.show_volume( volume['id'])['volume'] self._verify_migration(volume_source, volume_dest) class VolumeRetypeWithMigrationTest(VolumeRetypeTest): @classmethod def skip_checks(cls): super(VolumeRetypeTest, 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} cls.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) def _verify_migration(self, volume_source, volume_dest): 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') # 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]) self.assertEqual(volume_dest['volume_type'], self.dst_vol_type['name']) @decorators.idempotent_id('a1a41f3f-9dad-493e-9f09-3ff197d477cd') def test_available_volume_retype_with_migration(self): src_vol = self.create_volume(volume_type=self.src_vol_type['name']) self._retype_volume(src_vol, migration_policy='on-demand') @decorators.idempotent_id('d0d9554f-e7a5-4104-8973-f35b27ccb60d') def test_volume_from_snapshot_retype_with_migration(self): src_vol = self._create_volume_from_snapshot() # Migrate the volume from snapshot to the second backend self._retype_volume(src_vol, migration_policy='on-demand') class VolumeRetypeWithoutMigrationTest(VolumeRetypeTest): @classmethod def resource_setup(cls): super(VolumeRetypeWithoutMigrationTest, cls).resource_setup() cls.src_vol_type = cls.create_volume_type('volume-type-1') cls.dst_vol_type = cls.create_volume_type('volume-type-2') def _verify_migration(self, volume_source, volume_dest): keys_with_no_change = ('id', 'size', 'description', 'name', 'user_id', 'os-vol-tenant-attr:tenant_id', 'os-vol-host-attr:host') keys_with_change = ('volume_type',) # Check the volume information after the retype self.assertIsNone(volume_dest['os-vol-mig-status-attr:migstat']) self.assertIsNone(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]) self.assertEqual(volume_dest['volume_type'], self.dst_vol_type['name']) @decorators.idempotent_id('b90412ee-465d-46e9-b249-ec84a47d5f25') def test_available_volume_retype(self): src_vol = self.create_volume(volume_type=self.src_vol_type['name']) # Retype the volume from snapshot self._retype_volume(src_vol, migration_policy='never') tempest-23.0.0/tempest/api/volume/admin/test_volume_services.py000066400000000000000000000075271361143263300247460ustar00rootroot00000000000000# 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-23.0.0/tempest/api/volume/admin/test_volume_services_negative.py000066400000000000000000000054571361143263300266300ustar00rootroot00000000000000# Copyright 2018 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.lib import decorators from tempest.lib import exceptions as lib_exc class VolumeServicesNegativeTest(base.BaseVolumeAdminTest): @classmethod def resource_setup(cls): super(VolumeServicesNegativeTest, cls).resource_setup() services = cls.admin_volume_services_client.list_services()['services'] cls.host = services[0]['host'] cls.binary = services[0]['binary'] @decorators.attr(type='negative') @decorators.idempotent_id('3246ce65-ba70-4159-aa3b-082c28e4b484') def test_enable_service_with_invalid_host(self): self.assertRaises(lib_exc.NotFound, self.admin_volume_services_client.enable_service, host='invalid_host', binary=self.binary) @decorators.attr(type='negative') @decorators.idempotent_id('c571f179-c6e6-4c50-a0ab-368b628a8ac1') def test_disable_service_with_invalid_binary(self): self.assertRaises(lib_exc.NotFound, self.admin_volume_services_client.disable_service, host=self.host, binary='invalid_binary') @decorators.attr(type='negative') @decorators.idempotent_id('77767b36-5e8f-4c68-a0b5-2308cc21ec64') def test_disable_log_reason_with_no_reason(self): self.assertRaises(lib_exc.BadRequest, self.admin_volume_services_client.disable_log_reason, host=self.host, binary=self.binary, disabled_reason=None) @decorators.attr(type='negative') @decorators.idempotent_id('712bfab8-1f44-4eb5-a632-fa70bf78f05e') def test_freeze_host_with_invalid_host(self): self.assertRaises(lib_exc.BadRequest, self.admin_volume_services_client.freeze_host, host='invalid_host') @decorators.attr(type='negative') @decorators.idempotent_id('7c6287c9-d655-47e1-9a11-76f6657a6dce') def test_thaw_host_with_invalid_host(self): self.assertRaises(lib_exc.BadRequest, self.admin_volume_services_client.thaw_host, host='invalid_host') tempest-23.0.0/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py000066400000000000000000000073401361143263300302310ustar00rootroot00000000000000# 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 QUOTA_KEYS = ['gigabytes', 'snapshots', 'volumes', 'backups', 'backup_gigabytes', 'per_volume_gigabytes'] class VolumeSnapshotQuotasNegativeTestJSON(base.BaseVolumeAdminTest): @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() # Save the current set of quotas, then set up the cleanup method # to restore the quotas to their original values after the tests # from this class are done. This is needed just in case Tempest is # configured to use pre-provisioned projects/user accounts. original_quota_set = (cls.admin_quotas_client.show_quota_set( cls.demo_tenant_id)['quota_set']) cleanup_quota_set = dict( (k, v) for k, v in original_quota_set.items() if k in QUOTA_KEYS) cls.addClassResourceCleanup(cls.admin_quotas_client.update_quota_set, cls.demo_tenant_id, **cleanup_quota_set) cls.default_volume_size = CONF.volume.volume_size cls.shared_quota_set = {'gigabytes': 3 * cls.default_volume_size, 'volumes': 1, 'snapshots': 1} 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-23.0.0/tempest/api/volume/admin/test_volume_type_access.py000066400000000000000000000074571361143263300254270ustar00rootroot00000000000000# 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'] @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.os_alt.volumes_client_latest.tenant_id) self.addCleanup(self.admin_volume_types_client.remove_type_access, volume_type['id'], project=self.os_alt.volumes_client_latest.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.os_alt.volumes_client_latest.tenant_id, map(operator.itemgetter('project_id'), type_access_list)) tempest-23.0.0/tempest/api/volume/admin/test_volume_types.py000066400000000000000000000227411361143263300242620ustar00rootroot00000000000000# 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) # Get encryption specs item key = 'cipher' item = self.admin_encryption_types_client.show_encryption_specs_item( encrypt_type_id, key) self.assertEqual(update_kwargs[key], item[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'] self.assertEqual(name, updated_vol_type['name']) self.assertEqual(description, updated_vol_type['description']) self.assertEqual(is_public, updated_vol_type['is_public']) # Verify volume type details were updated fetched_volume_type = self.admin_volume_types_client.show_volume_type( volume_type['id'])['volume_type'] self.assertEqual(name, fetched_volume_type['name']) self.assertEqual(description, fetched_volume_type['description']) self.assertEqual(is_public, fetched_volume_type['is_public']) tempest-23.0.0/tempest/api/volume/admin/test_volume_types_extra_specs.py000066400000000000000000000105331361143263300266560ustar00rootroot00000000000000# 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-23.0.0/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py000066400000000000000000000134401361143263300305400ustar00rootroot00000000000000# 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-23.0.0/tempest/api/volume/admin/test_volume_types_negative.py000066400000000000000000000047031361143263300261420ustar00rootroot00000000000000# 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-23.0.0/tempest/api/volume/admin/test_volumes_actions.py000066400000000000000000000115401361143263300247340ustar00rootroot00000000000000# 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-23.0.0/tempest/api/volume/admin/test_volumes_backup.py000066400000000000000000000140161361143263300245420ustar00rootroot00000000000000# 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(volume_id=volume['id'], name=backup_name) waiters.wait_for_volume_resource_status(self.volumes_client, volume['id'], 'available') 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.backups_client.restore_backup(backup['id'])['restore'] self.addCleanup(self.volumes_client.delete_volume, restore['volume_id']) self.assertEqual(backup['id'], restore['backup_id']) # When restore operation is performed then, backup['id'] # goes to 'restoring' state so we need to wait for # backup['id'] to become 'available'. waiters.wait_for_volume_resource_status( self.backups_client, backup['id'], 'available') waiters.wait_for_volume_resource_status( self.volumes_client, restore['volume_id'], 'available') # Verify if restored volume is there in volume list volumes = self.volumes_client.list_volumes()['volumes'] self.assertIn(restore['volume_id'], [v['id'] for v in volumes]) @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(volume_id=volume['id'], name=backup_name) waiters.wait_for_volume_resource_status(self.volumes_client, volume['id'], 'available') 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.backups_client, backup['id'], 'error') tempest-23.0.0/tempest/api/volume/admin/test_volumes_list.py000066400000000000000000000056371361143263300242610ustar00rootroot00000000000000# 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-23.0.0/tempest/api/volume/api_microversion_fixture.py000066400000000000000000000020421361143263300245060ustar00rootroot00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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-23.0.0/tempest/api/volume/base.py000066400000000000000000000327351361143263300203160ustar00rootroot00000000000000# 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 # if api_v2 is not enabled while api_v3 is enabled, the volume v2 classes # should be transferred to volume v3 classes. if (not CONF.volume_feature_enabled.api_v2 and CONF.volume_feature_enabled.api_v3): _api_version = 3 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 cls.backups_client = cls.os_primary.backups_client_latest cls.volumes_client = cls.os_primary.volumes_client_latest cls.messages_client = cls.os_primary.volume_messages_client_latest cls.versions_client = cls.os_primary.volume_versions_client_latest cls.groups_client = cls.os_primary.groups_client_latest cls.group_snapshots_client = ( cls.os_primary.group_snapshots_client_latest) cls.snapshots_client = cls.os_primary.snapshots_client_latest cls.volumes_extension_client =\ cls.os_primary.volumes_extension_client_latest cls.availability_zone_client = ( cls.os_primary.volume_availability_zone_client_latest) cls.volume_limits_client = cls.os_primary.volume_limits_client_latest 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.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 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 if CONF.compute.compute_volume_common_az: kwargs.setdefault('availability_zone', CONF.compute.compute_volume_common_az) volume = cls.volumes_client.create_volume(**kwargs)['volume'] cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc, cls.delete_volume, cls.volumes_client, volume['id']) 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.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc, cls.delete_snapshot, 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) @classmethod def delete_snapshot(cls, snapshot_id, snapshots_client=None): """Delete snapshot by the given client""" if snapshots_client is None: snapshots_client = cls.snapshots_client snapshots_client.delete_snapshot(snapshot_id) snapshots_client.wait_for_resource_deletion(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) 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 def create_group(self, **kwargs): if 'name' not in kwargs: kwargs['name'] = data_utils.rand_name( self.__class__.__name__ + '-Group') group = self.groups_client.create_group(**kwargs)['group'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.delete_group, group['id']) waiters.wait_for_volume_resource_status( self.groups_client, group['id'], 'available') return group def delete_group(self, group_id, delete_volumes=True): self.groups_client.delete_group(group_id, delete_volumes) if delete_volumes: vols = self.volumes_client.list_volumes(detail=True)['volumes'] for vol in vols: if vol['group_id'] == group_id: self.volumes_client.wait_for_resource_deletion(vol['id']) self.groups_client.wait_for_resource_deletion(group_id) 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_client_latest cls.admin_volume_services_client = \ cls.os_admin.volume_services_client_latest cls.admin_volume_types_client = cls.os_admin.volume_types_client_latest cls.admin_volume_manage_client = ( cls.os_admin.volume_manage_client_latest) cls.admin_volume_client = cls.os_admin.volumes_client_latest cls.admin_groups_client = cls.os_admin.groups_client_latest cls.admin_messages_client = cls.os_admin.volume_messages_client_latest cls.admin_group_snapshots_client = \ cls.os_admin.group_snapshots_client_latest cls.admin_group_types_client = cls.os_admin.group_types_client_latest cls.admin_hosts_client = cls.os_admin.volume_hosts_client_latest cls.admin_snapshot_manage_client = \ cls.os_admin.snapshot_manage_client_latest cls.admin_snapshots_client = cls.os_admin.snapshots_client_latest cls.admin_backups_client = cls.os_admin.backups_client_latest cls.admin_encryption_types_client = \ cls.os_admin.encryption_types_client_latest cls.admin_quota_classes_client = \ cls.os_admin.volume_quota_classes_client_latest cls.admin_quotas_client = cls.os_admin.volume_quotas_client_latest cls.admin_volume_limits_client = ( cls.os_admin.volume_limits_client_latest) cls.admin_capabilities_client = \ cls.os_admin.volume_capabilities_client_latest cls.admin_scheduler_stats_client = \ cls.os_admin.volume_scheduler_stats_client_latest @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.addClassResourceCleanup(cls.clear_qos_spec, 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.addClassResourceCleanup(cls.clear_volume_type, 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_spec(cls, qos_id): test_utils.call_and_ignore_notfound_exc( cls.admin_volume_qos_client.delete_qos, qos_id) test_utils.call_and_ignore_notfound_exc( cls.admin_volume_qos_client.wait_for_resource_deletion, qos_id) @classmethod def clear_volume_type(cls, vol_type_id): test_utils.call_and_ignore_notfound_exc( cls.admin_volume_types_client.delete_volume_type, vol_type_id) test_utils.call_and_ignore_notfound_exc( cls.admin_volume_types_client.wait_for_resource_deletion, vol_type_id) tempest-23.0.0/tempest/api/volume/test_availability_zone.py000066400000000000000000000021611361143263300241360ustar00rootroot00000000000000# 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-23.0.0/tempest/api/volume/test_extensions.py000066400000000000000000000033131361143263300226300ustar00rootroot00000000000000# 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-23.0.0/tempest/api/volume/test_image_metadata.py000066400000000000000000000057041361143263300233610ustar00rootroot00000000000000# 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-23.0.0/tempest/api/volume/test_snapshot_metadata.py000066400000000000000000000112421361143263300241300ustar00rootroot00000000000000# 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-23.0.0/tempest/api/volume/test_versions.py000066400000000000000000000032521361143263300223030ustar00rootroot00000000000000# 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() @decorators.idempotent_id('7f755ae2-caa9-4049-988c-331d8f7a579f') def test_show_version(self): # NOTE: The version data is checked on service client side # with JSON-Schema validation. So we will loop through each # version and call show version. versions = self.versions_client.list_versions()['versions'] for version_dict in versions: version = version_dict['id'] major_version = version.split('.')[0] response = self.versions_client.show_version(major_version) self.assertEqual(version, response['versions'][0]['id']) tempest-23.0.0/tempest/api/volume/test_volume_absolute_limits.py000066400000000000000000000042301361143263300252160ustar00rootroot00000000000000# 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() @classmethod def skip_checks(cls): super(AbsoluteLimitsTests, cls).skip_checks() if not CONF.auth.use_dynamic_credentials: raise cls.skipException("Must use dynamic credentials.") @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-23.0.0/tempest/api/volume/test_volume_delete_cascade.py000066400000000000000000000071661361143263300247370ustar00rootroot00000000000000# 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-23.0.0/tempest/api/volume/test_volume_metadata.py000066400000000000000000000102031361143263300235740ustar00rootroot00000000000000# 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-23.0.0/tempest/api/volume/test_volume_transfers.py000066400000000000000000000106161361143263300240330ustar00rootroot00000000000000# 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.common.utils import test_utils 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_client_latest cls.alt_client = cls.os_alt.volume_transfers_client_latest cls.alt_volumes_client = cls.os_alt.volumes_client_latest cls.adm_volumes_client = cls.os_admin.volumes_client_latest @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'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.client.delete_volume_transfer, 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'] 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'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.client.delete_volume_transfer, 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) 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-23.0.0/tempest/api/volume/test_volumes_actions.py000066400000000000000000000160051361143263300236450ustar00rootroot00000000000000# 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-23.0.0/tempest/api/volume/test_volumes_backup.py000066400000000000000000000172151361143263300234560ustar00rootroot00000000000000# 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.delete_volume, self.volumes_client, 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.delete_volume, self.volumes_client, 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.delete_volume, self.volumes_client, 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) waiters.wait_for_volume_resource_status(self.volumes_client, volume['id'], 'in-use') 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.assertTrue(volume_details['bootable']) # Create a backup backup = self.create_backup(volume_id=volume['id']) waiters.wait_for_volume_resource_status(self.volumes_client, volume['id'], 'available') # 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.assertTrue(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']) waiters.wait_for_volume_resource_status(self.volumes_client, volume['id'], 'available') # 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-23.0.0/tempest/api/volume/test_volumes_clone.py000066400000000000000000000050461361143263300233100ustar00rootroot00000000000000# 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-23.0.0/tempest/api/volume/test_volumes_clone_negative.py000066400000000000000000000032441361143263300251700ustar00rootroot00000000000000# 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-23.0.0/tempest/api/volume/test_volumes_extend.py000066400000000000000000000211521361143263300234730ustar00rootroot00000000000000# 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 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 VolumesExtendTest(base.BaseVolumeTest): @decorators.idempotent_id('9a36df71-a257-43a5-9555-dc7c88e66e0e') def test_volume_extend(self): # Extend Volume Test. volume = self.create_volume(imageRef=self.image_ref) extend_size = volume['size'] * 2 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") def test_volume_extend_when_volume_has_snapshot(self): volume = self.create_volume() self.create_snapshot(volume['id']) extend_size = volume['size'] * 2 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' 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.os_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.") @utils.services('compute') 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-23.0.0/tempest/api/volume/test_volumes_get.py000066400000000000000000000153021361143263300227630ustar00rootroot00000000000000# 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-23.0.0/tempest/api/volume/test_volumes_list.py000066400000000000000000000433401361143263300231620ustar00rootroot00000000000000# 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.parse import urlparse 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 it successfully, # ensure that the backing file for the volume group that Cinder 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') @classmethod def _remove_volatile_fields(cls, fetched_list): """Remove fields that should not be compared. This method makes sure that Tempest does not compare e.g. the volume's "updated_at" field that may change for any reason internal to the operation of Cinder. """ for volume in fetched_list: for field in ('updated_at',): if field in volume: del volume[field] def _assert_volumes_in(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] # Hopefully the expected_list has already been cleaned. self._remove_volatile_fields(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']) cls._remove_volatile_fields(cls.volume_list) 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._assert_volumes_in(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._assert_volumes_in(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._assert_volumes_in(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._assert_volumes_in(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._assert_volumes_in(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._assert_volumes_in(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._assert_volumes_in(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._assert_volumes_in(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: curr = response.response['content-location'] currparsed = urlparse(curr) nextparsed = urlparse(next) # Depending on the environment, certain fields are omitted # from url (ie port). The fields to check are defined here. fieldscheck = ['scheme', 'hostname', 'path', 'query', 'params', 'fragment'] for field in fieldscheck: self.assertEqual(getattr(currparsed, field), getattr(nextparsed, field), 'Incorrect link to next page. URLs do ' 'not match at %s:\n%s\n%s' % (field, curr, next)) # 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 = 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-23.0.0/tempest/api/volume/test_volumes_negative.py000066400000000000000000000351651361143263300240170ustar00rootroot00000000000000# 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=CONF.volume.volume_size, 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=CONF.volume.volume_size, 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=CONF.volume.volume_size, 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-23.0.0/tempest/api/volume/test_volumes_snapshots.py000066400000000000000000000177701361143263300242410ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 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() # NOTE(zhufl) Here we create volume from self.image_ref for adding # coverage for "creating snapshot from non-blank volume". volume = self.create_volume(imageRef=self.image_ref) self.attach_volume(server['id'], volume['id']) # Snapshot a volume which attached to an instance with force=False self.assertRaises(lib_exc.BadRequest, self.create_snapshot, volume['id'], force=False) # Snapshot a volume attached to an instance snapshot1 = self.create_snapshot(volume['id'], force=True) snapshot2 = self.create_snapshot(volume['id'], force=True) snapshot3 = self.create_snapshot(volume['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']) def _create_volume_from_snapshot(self, extra_size=0): src_size = CONF.volume.volume_size size = src_size + extra_size src_vol = self.create_volume(size=src_size) src_snap = self.create_snapshot(src_vol['id']) dst_vol = self.create_volume(snapshot_id=src_snap['id'], size=size) # 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'], size) @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 self._create_volume_from_snapshot(extra_size=1) @decorators.idempotent_id('053d8870-8282-4fff-9dbb-99cb58bb5e0a') def test_volume_from_snapshot_no_size(self): # Creates a volume from a snapshot defaulting to original size self._create_volume_from_snapshot() @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']) waiters.wait_for_volume_resource_status(self.snapshots_client, snapshot['id'], 'available') 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-23.0.0/tempest/api/volume/test_volumes_snapshots_list.py000066400000000000000000000175431361143263300252720ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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() volume_origin = cls.create_volume() # Create snapshots with params for _ in range(3): snapshot = cls.create_snapshot(volume_origin['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 snap_list = self.snapshots_client.list_snapshots()['snapshots'] # list_snapshots will take the reverse order as they are created. snapshot_id_list = [snap['id'] for snap in snap_list][::-1] params = {'marker': 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(snapshot_id_list[:1], fetched_list_id) @decorators.idempotent_id('ca96d551-17c6-4e11-b0e8-52d3bb8a63c7') def test_snapshot_list_param_offset(self): params = {'offset': 2, 'limit': 3} snap_list = self.snapshots_client.list_snapshots(**params)['snapshots'] # Verify the list of snapshots skip offset=2 from the first element # (total 3 elements), therefore only one snapshot should display self.assertEqual(1, len(snap_list)) tempest-23.0.0/tempest/api/volume/test_volumes_snapshots_negative.py000066400000000000000000000072041361143263300261120ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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 * 2 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=CONF.volume.volume_size, 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-23.0.0/tempest/clients.py000066400000000000000000000425451361143263300167650ustar00rootroot00000000000000# 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() self.placement_client = self.placement.PlacementClient() # 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() self.qos_client = self.network.QosClient() self.qos_min_bw_client = self.network.QosMinimumBandwidthRulesClient() self.segments_client = self.network.SegmentsClient() 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) self.project_tags_client = self.identity_v3.ProjectTagsClient( **params_v3) self.application_credentials_client = \ self.identity_v3.ApplicationCredentialsClient(**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 only api_v3 is enabled, all these clients should be available if (CONF.volume_feature_enabled.api_v2 or CONF.volume_feature_enabled.api_v3): self.backups_client_latest = self.volume_v3.BackupsClient() self.encryption_types_client_latest = \ self.volume_v3.EncryptionTypesClient() self.snapshot_manage_client_latest = \ self.volume_v3.SnapshotManageClient() self.snapshots_client_latest = self.volume_v3.SnapshotsClient() self.volume_capabilities_client_latest = \ self.volume_v3.CapabilitiesClient() self.volume_manage_client_latest = ( self.volume_v3.VolumeManageClient()) self.volume_qos_client_latest = self.volume_v3.QosSpecsClient() self.volume_services_client_latest = ( self.volume_v3.ServicesClient()) self.volume_types_client_latest = self.volume_v3.TypesClient() self.volume_hosts_client_latest = self.volume_v3.HostsClient() self.volume_quotas_client_latest = self.volume_v3.QuotasClient() self.volume_quota_classes_client_latest = \ self.volume_v3.QuotaClassesClient() self.volume_scheduler_stats_client_latest = \ self.volume_v3.SchedulerStatsClient() self.volume_transfers_client_latest = \ self.volume_v3.TransfersClient() self.volume_availability_zone_client_latest = \ self.volume_v3.AvailabilityZoneClient() self.volume_limits_client_latest = self.volume_v3.LimitsClient() self.volumes_client_latest = self.volume_v3.VolumesClient() self.volumes_extension_client_latest = \ self.volume_v3.ExtensionsClient() self.group_types_client_latest = self.volume_v3.GroupTypesClient() self.groups_client_latest = self.volume_v3.GroupsClient() self.group_snapshots_client_latest = \ self.volume_v3.GroupSnapshotsClient() self.volume_messages_client_latest = ( self.volume_v3.MessagesClient()) self.volume_versions_client_latest = ( self.volume_v3.VersionsClient()) # TODO(gmann): Below alias for service clients have been # deprecated and will be removed in future. Start using the alias # defined above with suffix _latest. # ****************Deprecated alias start from here*************** self.backups_v2_client = self.volume_v3.BackupsClient() self.encryption_types_v2_client = \ self.volume_v3.EncryptionTypesClient() self.snapshot_manage_v2_client = \ self.volume_v3.SnapshotManageClient() self.snapshots_v2_client = self.volume_v3.SnapshotsClient() self.volume_capabilities_v2_client = \ self.volume_v3.CapabilitiesClient() self.volume_manage_v2_client = self.volume_v3.VolumeManageClient() self.volume_qos_v2_client = self.volume_v3.QosSpecsClient() self.volume_services_v2_client = self.volume_v3.ServicesClient() self.volume_types_v2_client = self.volume_v3.TypesClient() self.volume_hosts_v2_client = self.volume_v3.HostsClient() self.volume_quotas_v2_client = self.volume_v3.QuotasClient() self.volume_quota_classes_v2_client = \ self.volume_v3.QuotaClassesClient() self.volume_scheduler_stats_v2_client = \ self.volume_v3.SchedulerStatsClient() self.volume_transfers_v2_client = self.volume_v3.TransfersClient() self.volume_v2_availability_zone_client = \ self.volume_v3.AvailabilityZoneClient() self.volume_v2_limits_client = self.volume_v3.LimitsClient() self.volumes_v2_client = self.volume_v3.VolumesClient() self.volumes_v2_extension_client = \ self.volume_v3.ExtensionsClient() 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() # ****************Deprecated alias end here*********************** 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-23.0.0/tempest/cmd/000077500000000000000000000000001361143263300155035ustar00rootroot00000000000000tempest-23.0.0/tempest/cmd/__init__.py000066400000000000000000000000001361143263300176020ustar00rootroot00000000000000tempest-23.0.0/tempest/cmd/account_generator.py000077500000000000000000000303501361143263300215630ustar00rootroot00000000000000#!/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, --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`` (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, --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 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'] = {} 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): log_warning = False if not opts: log_warning = True opts = get_options() if opts.config_file: config.CONF.set_config_path(opts.config_file) setup_logging() if log_warning: LOG.warning("Use of: 'tempest-account-generator' is deprecated, " "please use: 'tempest account-generator'") 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-23.0.0/tempest/cmd/cleanup.py000066400000000000000000000277531361143263300175220ustar00rootroot00000000000000# 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. Examples:: $ 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. .. note:: If during execution of ``tempest cleanup`` NotImplemented exception occurres, ``tempest cleanup`` won't fail on that, it will be logged only. NotImplemented errors are ignored because they are an outcome of some extensions being disabled and ``tempest cleanup`` is not checking their availability as it tries to clean up as much as possible without any complicated logic. """ 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 from tempest.lib import exceptions SAVED_STATE_JSON = "saved_state.json" DRY_RUN_JSON = "dry_run.json" LOG = logging.getLogger(__name__) CONF = config.CONF class TempestCleanup(command.Command): GOT_EXCEPTIONS = [] 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 # ignore NotImplemented errors as those are an outcome of some # extensions being disabled and cleanup is not checking their # availability as it tries to clean up as much as possible without # any complicated logic critical_exceptions = [ex for ex in self.GOT_EXCEPTIONS if not isinstance(ex, exceptions.NotImplemented)] if critical_exceptions: raise Exception(self.GOT_EXCEPTIONS) 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() # available services self.project_associated_services = ( cleanup_service.get_project_associated_cleanup_services()) self.resource_cleanup_services = ( cleanup_service.get_resource_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._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, 'got_exceptions': self.GOT_EXCEPTIONS} for service in self.global_services: svc = service(admin_mgr, **kwargs) svc.run() for service in self.resource_cleanup_services: svc = service(self.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=(',', ': '))) 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 = {'data': project_data, 'is_dry_run': is_dry_run, 'saved_state_json': self.json_data, 'is_preserve': is_preserve, 'is_save_state': False, 'project_id': project_id, 'got_exceptions': self.GOT_EXCEPTIONS} for service in self.project_associated_services: svc = service(self.admin_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 _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, 'got_exceptions': self.GOT_EXCEPTIONS} for service in self.global_services: svc = service(admin_mgr, **kwargs) svc.run() for service in self.project_associated_services: svc = service(admin_mgr, **kwargs) svc.run() for service in self.resource_cleanup_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, saved_state_json=SAVED_STATE_JSON): try: with open(saved_state_json, 'rb') 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-23.0.0/tempest/cmd/cleanup_service.py000066400000000000000000001062451361143263300212340ustar00rootroot00000000000000# 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 from tempest.lib import exceptions 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_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_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['project_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): try: if self.is_dry_run: self.dry_run() elif self.is_save_state: self.save_state() else: self.delete() except exceptions.NotImplemented as exc: # Many OpenStack services use extensions logic to implement the # features or resources. Tempest cleanup tries to clean up the test # resources without having much logic of extensions checks etc. # If any of the extension is missing then, service will return # NotImplemented error. msg = ("Got NotImplemented error in %s, full exception: %s" % (str(self.__class__), str(exc))) LOG.exception(msg) self.got_exceptions.append(exc) 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'] if not self.is_save_state: # recreate list removing saved snapshots snaps = [snap for snap in snaps if snap['id'] not in self.saved_state_json['snapshots'].keys()] 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 %s exception.", snap['id']) def dry_run(self): snaps = self.list() self.data['snapshots'] = snaps def save_state(self): snaps = self.list() self.data['snapshots'] = {} for snap in snaps: self.data['snapshots'][snap['id']] = snap['name'] 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'] if not self.is_save_state: # recreate list removing saved servers servers = [server for server in servers if server['id'] not in self.saved_state_json['servers'].keys()] 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 %s exception.", server['id']) def dry_run(self): servers = self.list() self.data['servers'] = servers def save_state(self): servers = self.list() self.data['servers'] = {} for server in servers: self.data['servers'][server['id']] = server['name'] class ServerGroupService(ServerService): def list(self): client = self.server_groups_client sgs = client.list_server_groups()['server_groups'] if not self.is_save_state: # recreate list removing saved server_groups sgs = [sg for sg in sgs if sg['id'] not in self.saved_state_json['server_groups'].keys()] LOG.debug("List count, %s Server Groups", len(sgs)) return sgs def delete(self): client = self.server_groups_client sgs = self.list() for sg in sgs: try: client.delete_server_group(sg['id']) except Exception: LOG.exception("Delete Server Group %s exception.", sg['id']) def dry_run(self): sgs = self.list() self.data['server_groups'] = sgs def save_state(self): sgs = self.list() self.data['server_groups'] = {} for sg in sgs: self.data['server_groups'][sg['id']] = sg['name'] 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'] if not self.is_save_state: # recreate list removing saved keypairs keypairs = [keypair for keypair in keypairs if keypair['keypair']['name'] not in self.saved_state_json['keypairs'].keys()] LOG.debug("List count, %s Keypairs", len(keypairs)) return keypairs def delete(self): client = self.client keypairs = self.list() for k in keypairs: name = k['keypair']['name'] try: client.delete_keypair(name) except Exception: LOG.exception("Delete Keypair %s exception.", name) def dry_run(self): keypairs = self.list() self.data['keypairs'] = keypairs def save_state(self): keypairs = self.list() self.data['keypairs'] = {} for keypair in keypairs: keypair = keypair['keypair'] self.data['keypairs'][keypair['name']] = keypair 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'] if not self.is_save_state: # recreate list removing saved volumes vols = [vol for vol in vols if vol['id'] not in self.saved_state_json['volumes'].keys()] 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 %s exception.", v['id']) def dry_run(self): vols = self.list() self.data['volumes'] = vols def save_state(self): vols = self.list() self.data['volumes'] = {} for vol in vols: self.data['volumes'][vol['id']] = vol['name'] class VolumeQuotaService(BaseService): def __init__(self, manager, **kwargs): super(VolumeQuotaService, self).__init__(kwargs) self.client = manager.volume_quotas_client_latest def delete(self): client = self.client try: client.delete_quota_set(self.project_id) except Exception: LOG.exception("Delete Volume Quotas exception for 'project %s'.", self.project_id) def dry_run(self): quotas = self.client.show_quota_set( self.project_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.project_id) except Exception: LOG.exception("Delete Quotas exception for 'project %s'.", self.project_id) def dry_run(self): client = self.limits_client quotas = client.show_limits()['limits'] self.data['compute_quotas'] = quotas['absolute'] # Begin network service classes class BaseNetworkService(BaseService): def __init__(self, manager, **kwargs): super(BaseNetworkService, 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 self.subnetpools_client = manager.subnetpools_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] class NetworkService(BaseNetworkService): def list(self): client = self.networks_client networks = client.list_networks(**self.tenant_filter) networks = networks['networks'] if not self.is_save_state: # recreate list removing saved networks networks = [network for network in networks if network['id'] not in self.saved_state_json['networks'].keys()] # 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 %s exception.", n['id']) def dry_run(self): networks = self.list() self.data['networks'] = networks def save_state(self): networks = self.list() self.data['networks'] = {} for network in networks: self.data['networks'][network['id']] = network class NetworkFloatingIpService(BaseNetworkService): def list(self): client = self.floating_ips_client flips = client.list_floatingips(**self.tenant_filter) flips = flips['floatingips'] if not self.is_save_state: # recreate list removing saved flips flips = [flip for flip in flips if flip['id'] not in self.saved_state_json['floatingips'].keys()] LOG.debug("List count, %s Network Floating IPs", len(flips)) return flips def delete(self): client = self.floating_ips_client flips = self.list() for flip in flips: try: client.delete_floatingip(flip['id']) except Exception: LOG.exception("Delete Network Floating IP %s exception.", flip['id']) def dry_run(self): flips = self.list() self.data['floatingips'] = flips def save_state(self): flips = self.list() self.data['floatingips'] = {} for flip in flips: self.data['floatingips'][flip['id']] = flip class NetworkRouterService(BaseNetworkService): def list(self): client = self.routers_client routers = client.list_routers(**self.tenant_filter) routers = routers['routers'] if not self.is_save_state: # recreate list removing saved routers routers = [router for router in routers if router['id'] not in self.saved_state_json['routers'].keys()] 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: 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: try: client.remove_router_interface(rid, port_id=port['id']) except Exception: LOG.exception("Delete Router Interface exception for " "'port %s' of 'router %s'.", port['id'], rid) try: client.delete_router(rid) except Exception: LOG.exception("Delete Router %s exception.", rid) def dry_run(self): routers = self.list() self.data['routers'] = routers def save_state(self): routers = self.list() self.data['routers'] = {} for router in routers: self.data['routers'][router['id']] = router['name'] 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) if not self.is_save_state: saved_rules = self.saved_state_json['metering_label_rules'].keys() # recreate list removing saved rules rules = [rule for rule in rules if rule['id'] not in saved_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 %s exception.", rule['id']) def dry_run(self): rules = self.list() self.data['metering_label_rules'] = rules def save_state(self): rules = self.list() self.data['metering_label_rules'] = {} for rule in rules: self.data['metering_label_rules'][rule['id']] = rule class NetworkMeteringLabelService(BaseNetworkService): def list(self): client = self.metering_labels_client labels = client.list_metering_labels() labels = labels['metering_labels'] labels = self._filter_by_tenant_id(labels) if not self.is_save_state: # recreate list removing saved labels labels = [label for label in labels if label['id'] not in self.saved_state_json['metering_labels'].keys()] 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 %s exception.", label['id']) def dry_run(self): labels = self.list() self.data['metering_labels'] = labels def save_state(self): labels = self.list() self.data['metering_labels'] = {} for label in labels: self.data['metering_labels'][label['id']] = label['name'] class NetworkPortService(BaseNetworkService): 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 not self.is_save_state: # recreate list removing saved ports ports = [port for port in ports if port['id'] not in self.saved_state_json['ports'].keys()] 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 %s exception.", port['id']) def dry_run(self): ports = self.list() self.data['ports'] = ports def save_state(self): ports = self.list() self.data['ports'] = {} for port in ports: self.data['ports'][port['id']] = port['name'] class NetworkSecGroupService(BaseNetworkService): 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 not self.is_save_state: # recreate list removing saved security_groups secgroups = [secgroup for secgroup in secgroups if secgroup['id'] not in self.saved_state_json['security_groups'].keys() ] if self.is_preserve: secgroups = [secgroup for secgroup in secgroups if secgroup['security_group_rules'][0]['project_id'] not in CONF_PROJECTS] LOG.debug("List count, %s security_groups", len(secgroups)) return secgroups def delete(self): client = self.security_groups_client secgroups = self.list() for secgroup in secgroups: try: client.delete_security_group(secgroup['id']) except Exception: LOG.exception("Delete security_group %s exception.", secgroup['id']) def dry_run(self): secgroups = self.list() self.data['security_groups'] = secgroups def save_state(self): secgroups = self.list() self.data['security_groups'] = {} for secgroup in secgroups: self.data['security_groups'][secgroup['id']] = secgroup['name'] class NetworkSubnetService(BaseNetworkService): def list(self): client = self.subnets_client subnets = client.list_subnets(**self.tenant_filter) subnets = subnets['subnets'] if not self.is_save_state: # recreate list removing saved subnets subnets = [subnet for subnet in subnets if subnet['id'] not in self.saved_state_json['subnets'].keys()] 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 %s exception.", subnet['id']) def dry_run(self): subnets = self.list() self.data['subnets'] = subnets def save_state(self): subnets = self.list() self.data['subnets'] = {} for subnet in subnets: self.data['subnets'][subnet['id']] = subnet['name'] class NetworkSubnetPoolsService(BaseNetworkService): def list(self): client = self.subnetpools_client pools = client.list_subnetpools(**self.tenant_filter)['subnetpools'] if not self.is_save_state: # recreate list removing saved subnet pools pools = [pool for pool in pools if pool['id'] not in self.saved_state_json['subnetpools'].keys()] if self.is_preserve: pools = [pool for pool in pools if pool['project_id'] not in CONF_PROJECTS] LOG.debug("List count, %s Subnet Pools", len(pools)) return pools def delete(self): client = self.subnetpools_client pools = self.list() for pool in pools: try: client.delete_subnetpool(pool['id']) except Exception: LOG.exception("Delete Subnet Pool %s exception.", pool['id']) def dry_run(self): pools = self.list() self.data['subnetpools'] = pools def save_state(self): pools = self.list() self.data['subnetpools'] = {} for pool in pools: self.data['subnetpools'][pool['id']] = pool['name'] # begin global services class RegionService(BaseService): def __init__(self, manager, **kwargs): super(RegionService, self).__init__(kwargs) self.client = manager.regions_client def list(self): client = self.client regions = client.list_regions() if not self.is_save_state: regions = [region for region in regions['regions'] if region['id'] not in self.saved_state_json['regions'].keys()] return regions else: return regions['regions'] def delete(self): client = self.client regions = self.list() for region in regions: try: client.delete_region(region['id']) except Exception: LOG.exception("Delete Region %s exception.", region['id']) def dry_run(self): regions = self.list() self.data['regions'] = {} for region in regions: self.data['regions'][region['id']] = region def save_state(self): regions = self.list() self.data['regions'] = {} for region in regions: self.data['regions'][region['id']] = region 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 %s exception.", flavor['id']) 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.image_client_v2 def list(self): client = self.client images = client.list_images(params={"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 %s exception.", image['id']) 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 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 %s exception.", user['id']) 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_v3_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 %s exception.", role['id']) 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: project_ids = self.saved_state_json['projects'] projects = [project for project in projects if (project['id'] not in project_ids 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 %s exception.", project['id']) 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 %s exception.", domain['id']) 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_associated_cleanup_services(): """Returns list of project service classes. The list contains services whose resources need to be deleted prior, the project they are associated with, deletion. The resources cannot be most likely deleted after the project is deleted first. """ project_associated_services = [] # TODO(gmann): Tempest should provide some plugin hook for cleanup # script extension to plugin tests also. if IS_NOVA: project_associated_services.append(NovaQuotaService) if IS_CINDER: project_associated_services.append(VolumeQuotaService) return project_associated_services def get_resource_cleanup_services(): """Returns list of project related classes. The list contains services whose resources are associated with a project, however, their deletion is possible also after the project is deleted first. """ resource_cleanup_services = [] # TODO(gmann): Tempest should provide some plugin hook for cleanup # script extension to plugin tests also. if IS_NOVA: resource_cleanup_services.append(ServerService) resource_cleanup_services.append(KeyPairService) resource_cleanup_services.append(ServerGroupService) if IS_NEUTRON: resource_cleanup_services.append(NetworkFloatingIpService) if utils.is_extension_enabled('metering', 'network'): resource_cleanup_services.append(NetworkMeteringLabelRuleService) resource_cleanup_services.append(NetworkMeteringLabelService) resource_cleanup_services.append(NetworkRouterService) resource_cleanup_services.append(NetworkPortService) resource_cleanup_services.append(NetworkSubnetService) resource_cleanup_services.append(NetworkService) resource_cleanup_services.append(NetworkSecGroupService) resource_cleanup_services.append(NetworkSubnetPoolsService) if IS_CINDER: resource_cleanup_services.append(SnapshotService) resource_cleanup_services.append(VolumeService) return resource_cleanup_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) global_services.append(RegionService) return global_services tempest-23.0.0/tempest/cmd/config-generator.tempest.conf000066400000000000000000000001751361143263300232660ustar00rootroot00000000000000[DEFAULT] output_file = etc/tempest.conf.sample namespace = tempest.config namespace = oslo.concurrency namespace = oslo.log tempest-23.0.0/tempest/cmd/init.py000066400000000000000000000167361361143263300170350ustar00rootroot00000000000000# 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 stestr import commands from tempest.cmd import workspace LOG = logging.getLogger(__name__) STESTR_CONF = r"""[DEFAULT] test_path=%s top_dir=%s 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_stestr_conf(self, local_path): stestr_conf_path = os.path.join(local_path, '.stestr.conf') top_level_path = os.path.dirname(os.path.dirname(__file__)) discover_path = os.path.join(top_level_path, 'test_discover') stestr_conf = STESTR_CONF % (discover_path, top_level_path) with open(stestr_conf_path, 'w+') as stestr_conf_file: stestr_conf_file.write(stestr_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 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') stestr_dir = os.path.join(local_dir, '.stestr') # 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 stestr conf file self.generate_stestr_conf(local_dir) # setup local stestr working dir if not os.path.isdir(stestr_dir): commands.init_command(repo_url=local_dir) 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-23.0.0/tempest/cmd/list_plugins.py000066400000000000000000000023621361143263300205740ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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-23.0.0/tempest/cmd/main.py000066400000000000000000000031121361143263300167760ustar00rootroot00000000000000# 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-23.0.0/tempest/cmd/run.py000066400000000000000000000311411361143263300166610ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 stestr uses. It will run any tests that match on re.match() with the regex * ``--smoke/-s``: Run all the tests tagged as smoke * ``--black-regex``: It allows to do simple test exclusion via passing a rejection/black regexp 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 These arguments are just passed into stestr, you can refer to the stestr selection docs for more details on how these operate: http://stestr.readthedocs.io/en/latest/MANUAL.html#test-selection 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 .stestr directory and a .stestr.conf file in your current working directory. This way you can use stestr 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 os import sys from cliff import command from oslo_serialization import jsonutils as json import six from stestr import commands 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 if six.PY2: # Python 2 has not FileNotFoundError exception FileNotFoundError = IOError CONF = config.CONF SAVED_STATE_JSON = "saved_state.json" class TempestRun(command.Command): def _set_env(self, config_file=None): if config_file: if os.path.exists(os.path.abspath(config_file)): CONF.set_config_path(os.path.abspath(config_file)) else: raise FileNotFoundError( "Config file: %s doesn't exist" % config_file) # NOTE(mtreinish): This is needed so that stestr 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 .stestr.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_stestr_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.STESTR_CONF % (discover_path, top_level_path) with open('.stestr.conf', 'w+') as stestr_conf_file: stestr_conf_file.write(file_contents) def take_action(self, parsed_args): 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) if not os.path.isfile('.stestr.conf'): self._create_stestr_conf() # local execution with config file mode elif parsed_args.config_file and not os.path.isfile('.stestr.conf'): self._create_stestr_conf() elif not os.path.isfile('.stestr.conf'): print("No .stestr.conf file was found for local execution") sys.exit(2) if parsed_args.state: self._init_state() regex = self._build_regex(parsed_args) return_code = 0 if parsed_args.list_tests: return_code = commands.list_command( filters=regex, whitelist_file=parsed_args.whitelist_file, blacklist_file=parsed_args.blacklist_file, black_regex=parsed_args.black_regex) else: serial = not parsed_args.parallel return_code = commands.run_command( filters=regex, subunit_out=parsed_args.subunit, serial=serial, concurrency=parsed_args.concurrency, blacklist_file=parsed_args.blacklist_file, whitelist_file=parsed_args.whitelist_file, black_regex=parsed_args.black_regex, load_list=parsed_args.load_list, combine=parsed_args.combine) if return_code > 0: sys.exit(return_code) return return_code 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 stestr selection regex used to ' 'specify a subset of tests to run') parser.add_argument('--black-regex', dest='black_regex', help='A regex to exclude tests that match it') parser.add_argument('--whitelist-file', '--whitelist_file', help="Path to a whitelist file, this file " "contains a separate regex on each " "newline.") parser.add_argument('--blacklist-file', '--blacklist_file', help='Path to a blacklist file, this file ' 'contains a separate regex exclude on ' 'each newline') parser.add_argument('--load-list', '--load_list', help='Path to a non-regex whitelist file, ' 'this file contains a separate 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', type=int, default=0, 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 " "stestr repository after it finish") parser.set_defaults(parallel=True) return parser def _build_regex(self, parsed_args): regex = None if parsed_args.smoke: regex = ['smoke'] elif parsed_args.regex: regex = parsed_args.regex.split() return regex tempest-23.0.0/tempest/cmd/subunit_describe_calls.py000066400000000000000000000313701361143263300225700ustar00rootroot00000000000000# 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 * ``--verbose, -v``: (Optional) Print Request and Response Headers and Body data to stdout 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 os import re import sys import traceback from cliff.command import Command from oslo_serialization import jsonutils as json import subunit import testtools DESCRIPTION = "Outputs all HTTP calls a given test made that were logged." 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}) ' r'(?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 = DESCRIPTION super(ArgumentParser, self).__init__(description=desc) self.prog = "subunit-describe-calls" _parser_add_args(self) 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, verbose): 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'))) if verbose: sys.stdout.write('\t\t- request headers: {0}\n'.format( item.get('request_headers'))) sys.stdout.write('\t\t- request body: {0}\n'.format( item.get('request_body'))) sys.stdout.write('\t\t- response headers: {0}\n'.format( item.get('response_headers'))) sys.stdout.write('\t\t- response body: {0}\n'.format( item.get('response_body'))) sys.stdout.write('\n') def entry_point(cl_args=None): print('Running subunit_describe_calls ...') if not cl_args: print("Use of: 'subunit-describe-calls' is deprecated, " "please use: 'tempest subunit-describe-calls'") cl_args = ArgumentParser().parse_args() parser = parse(cl_args.subunit, cl_args.non_subunit_name, cl_args.ports) output(parser, cl_args.output_file, cl_args.verbose) def _parser_add_args(parser): parser.add_argument( "-s", "--subunit", metavar="", nargs="?", type=argparse.FileType('rb'), default=sys.stdin, help="The path to the subunit output file(default:stdin v1/v2 stream)" ) parser.add_argument( "-n", "--non-subunit-name", metavar="", default="pythonlogging", help="The name used in subunit to describe the file contents." ) parser.add_argument( "-o", "--output-file", metavar="", default=None, help="The output file name for the json." ) parser.add_argument( "-p", "--ports", metavar="", default=None, help="A JSON file describing the ports for each service." ) parser.add_argument( "-v", "--verbose", action='store_true', default=False, help="Add Request and Response header and body data to stdout." ) class TempestSubunitDescribeCalls(Command): def get_parser(self, prog_name): parser = super(TempestSubunitDescribeCalls, self).get_parser(prog_name) _parser_add_args(parser) return parser def take_action(self, parsed_args): try: entry_point(parsed_args) except Exception: traceback.print_exc() raise def get_description(self): return DESCRIPTION if __name__ == "__main__": entry_point() tempest-23.0.0/tempest/cmd/verify_tempest_config.py000066400000000000000000000451551361143263300224610ustar00rootroot00000000000000#!/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_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 not extensions_opt: LOG.info("'%s' has no api_extensions set.", service) return results 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', '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 if cfgname == 'identity': # Keystone is a required service for tempest continue if catalog_type not in services: try: 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) except KeyError: print('%s is a third party plugin, cannot be verified ' 'automatically, but it is suggested that it is set to ' 'False because %s service is not available ' % ( cfgname, catalog_type)) else: try: 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]) except KeyError: print('%s is a third party plugin, cannot be verified ' 'automatically, but it is suggested that it is set to ' 'True because %s service is available ' % ( cfgname, catalog_type)) 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-23.0.0/tempest/cmd/workspace.py000066400000000000000000000216171361143263300200620ustar00rootroot00000000000000# 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._invalid_name_check(new_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)) if path else 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): self._validate_path(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 _invalid_name_check(self, name): if not name: print("None or empty name is specified." " Please specify correct name for workspace.") sys.exit(1) def _validate_path(self, path): if not path: print("None or empty path is specified for workspace." " Please specify correct workspace path.") sys.exit(1) 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)) if path else path # This only happens when register is called from outside of init if not init: self._validate_path(path) self._invalid_name_check(name) 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-23.0.0/tempest/common/000077500000000000000000000000001361143263300162305ustar00rootroot00000000000000tempest-23.0.0/tempest/common/__init__.py000066400000000000000000000000001361143263300203270ustar00rootroot00000000000000tempest-23.0.0/tempest/common/compute.py000066400000000000000000000447261361143263300202730ustar00rootroot00000000000000# 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 enabled in the nova config file. If the scheduler_enabled_filters option is set to 'all' in tempest.conf then, this function returns True with assumption that requested filter 'filter_name' is one of the enabled filters in nova ("nova.scheduler.filters.all_filters"). """ filters = CONF.compute_feature_enabled.scheduler_enabled_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: .. code-block:: python 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_client_latest params = {'name': volume_name, 'imageRef': image_id, 'size': CONF.volume.volume_size} if CONF.compute.compute_volume_common_az: params.setdefault('availability_zone', CONF.compute.compute_volume_common_az) volume = volumes_client.create_volume(**params) try: waiters.wait_for_volume_resource_status(volumes_client, volume['volume']['id'], 'available') except Exception: with excutils.save_and_reraise_exception(): try: volumes_client.delete_volume(volume['volume']['id']) volumes_client.wait_for_resource_deletion( volume['volume']['id']) except Exception as exc: LOG.exception("Deleting volume %s failed, exception %s", volume['volume']['id'], exc) 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 = '' if CONF.compute.compute_volume_common_az: kwargs.setdefault('availability_zone', CONF.compute.compute_volume_common_az) 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. 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) # NOTE(mnaser): It is possible that there is no port specified, so fall # back to the default port based on the scheme. port = url.port or (443 if url.scheme == 'https' else 80) for res in socket.getaddrinfo(url.hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM): af, socktype, proto, _, sa = res client_socket = socket.socket(af, socktype, proto) if url.scheme == 'https': client_socket = ssl.wrap_socket(client_socket) client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: client_socket.connect(sa) except socket.error: client_socket.close() continue break else: raise socket.error('WebSocket creation failed') # 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.""" # It is possible to pass the path as a query parameter in the request, # so use it if present qparams = urlparse.parse_qs(url.query) path = qparams['path'][0] if 'path' in qparams else '/websockify' reqdata = 'GET %s HTTP/1.1\r\n' % path reqdata += 'Host: %s' % url.hostname # Add port only if we have one specified if url.port: reqdata += ':%s' % url.port # Line-ending for Host header reqdata += '\r\n' # 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 for noVNC < v1.1.0 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-23.0.0/tempest/common/credentials_factory.py000066400000000000000000000314721361143263300226350ustar00rootroot00000000000000# 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', 'tempest'), ('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-23.0.0/tempest/common/custom_matchers.py000066400000000000000000000314401361143263300220040ustar00rootroot00000000000000# 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(r"^\d+\.?\d*\Z", value): return InvalidFormat(key, value) elif key == 'x-copied-from' and not re.match(r"\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-23.0.0/tempest/common/identity.py000066400000000000000000000066501361143263300204420ustar00rootroot00000000000000# 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: v2 or v3 of CredsClient :rtype: V2CredsClient or V3CredsClient """ 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-23.0.0/tempest/common/image.py000066400000000000000000000040741361143263300176710ustar00rootroot00000000000000# 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-23.0.0/tempest/common/tempest_fixtures.py000066400000000000000000000014461361143263300222210ustar00rootroot00000000000000# 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-23.0.0/tempest/common/utils/000077500000000000000000000000001361143263300173705ustar00rootroot00000000000000tempest-23.0.0/tempest/common/utils/__init__.py000066400000000000000000000105751361143263300215110ustar00rootroot00000000000000# 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 'tempest' attr_obj = partial(lib_data_utils.rand_name, prefix='tempest') 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(*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(*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-23.0.0/tempest/common/utils/linux/000077500000000000000000000000001361143263300205275ustar00rootroot00000000000000tempest-23.0.0/tempest/common/utils/linux/__init__.py000066400000000000000000000000001361143263300226260ustar00rootroot00000000000000tempest-23.0.0/tempest/common/utils/linux/remote_client.py000066400000000000000000000212261361143263300237350ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 count_disks(self): disks_list = self.get_disks() disks_list = [line[0] for line in [device_name.split() for device_name in disks_list.splitlines()][1:]] return len(disks_list) 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) LOG.debug('(get_nic_name_by_ip) Command result: %s', nic) 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 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 def nc_listen_host(self, port=80, protocol='tcp'): """Creates persistent nc server listening on the given TCP / UDP port :port: the port to start listening on. :protocol: the protocol used by the server. TCP by default. """ udp = '-u' if protocol.lower() == 'udp' else '' cmd = "sudo nc %(udp)s -p %(port)s -lk -e echo foolish &" % { 'udp': udp, 'port': port} return self.exec_command(cmd) def nc_host(self, host, port=80, protocol='tcp', expected_response=None): """Check connectivity to TCP / UDP port at host via nc :host: an IP against which the connectivity will be tested. :port: the port to check connectivity against. :protocol: the protocol used by nc to send packets. TCP by default. :expected_response: string representing the expected response from server. :raises SSHExecCommandFailed: if an expected response is given and it does not match the actual server response. """ udp = '-u' if protocol.lower() == 'udp' else '' cmd = 'echo "bar" | nc -w 1 %(udp)s %(host)s %(port)s' % { 'udp': udp, 'host': host, 'port': port} response = self.exec_command(cmd) # sending an UDP packet will always succeed. we need to check # the response. if (expected_response is not None and expected_response != response.strip()): raise tempest.lib.exceptions.SSHExecCommandFailed( command=cmd, exit_status=0, stdout=response, stderr='') return response def icmp_check(self, host, nic=None): """Wrapper for icmp connectivity checks""" return self.ping_host(host, nic=nic) def udp_check(self, host, **kwargs): """Wrapper for udp connectivity checks.""" kwargs.pop('nic', None) return self.nc_host(host, protocol='udp', expected_response='foolish', **kwargs) def tcp_check(self, host, **kwargs): """Wrapper for tcp connectivity checks.""" kwargs.pop('nic', None) return self.nc_host(host, **kwargs) tempest-23.0.0/tempest/common/utils/net_info.py000066400000000000000000000016271361143263300215510ustar00rootroot00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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-23.0.0/tempest/common/utils/net_utils.py000066400000000000000000000046571361143263300217640ustar00rootroot00000000000000# 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-23.0.0/tempest/common/waiters.py000066400000000000000000000344061361143263300202670ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 = body['status'] old_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( "Server %s failed to delete and is in ERROR status" % 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, status): """Waits for a volume resource to reach a given status. This function is a common function for volume, snapshot and backup resources. The function extracts the name of the desired resource from the client class name of the resource. """ resource_name = re.findall( r'(volume|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 != status: time.sleep(client.build_interval) resource_status = show_resource(resource_id)[ '{}'.format(resource_name)]['status'] if resource_status == 'error' and resource_status != status: 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 resource_status == 'error_extending' and resource_status != status: raise exceptions.VolumeExtendErrorException(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, status, 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, status, time.time() - start) def wait_for_volume_migration(client, volume_id, new_host): """Waits for a Volume to move to a new host.""" body = client.show_volume(volume_id)['volume'] host = body['os-vol-host-attr:host'] migration_status = body['migration_status'] start = int(time.time()) # new_host is hostname@backend while current_host is hostname@backend#type while migration_status != 'success' or new_host not in host: time.sleep(client.build_interval) body = client.show_volume(volume_id)['volume'] host = body['os-vol-host-attr:host'] migration_status = body['migration_status'] if migration_status == 'error': message = ('volume %s failed to migrate.' % (volume_id)) raise lib_exc.TempestException(message) if int(time.time()) - start >= client.build_timeout: message = ('Volume %s failed to migrate to %s (current %s) ' 'within the required time (%s s).' % (volume_id, new_host, host, client.build_timeout)) raise lib_exc.TimeoutException(message) 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 def wait_for_interface_detach(client, server_id, port_id): """Waits for an interface to be detached from a server.""" body = client.list_interfaces(server_id)['interfaceAttachments'] ports = [iface['port_id'] for iface in body] start = int(time.time()) while port_id in ports: time.sleep(client.build_interval) body = client.list_interfaces(server_id)['interfaceAttachments'] ports = [iface['port_id'] for iface in body] if port_id not in ports: return body timed_out = int(time.time()) - start >= client.build_timeout if timed_out: message = ('Interface %s failed to detach from server %s within ' 'the required time (%s s)' % (port_id, server_id, client.build_timeout)) raise lib_exc.TimeoutException(message) tempest-23.0.0/tempest/config.py000066400000000000000000002074541361143263300165730ustar00rootroot00000000000000# 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_config import types 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"), 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."), 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"), 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"), 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), cfg.StrOpt('admin_domain_name', default='Default', help="Admin domain name for authentication (Keystone V3). " "The same domain applies to user and project"), ] 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"), 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. This only " "takes effect when identity-feature-enabled." "security_compliance is set to 'True'. For more details, " "refer to keystone config options keystone.conf:" "security_compliance.lockout_failure_attempts. " "This feature is disabled by default in keystone."), cfg.IntOpt('user_lockout_duration', default=5, help="The number of seconds a user account will remain " "locked. This only takes " "effect when identity-feature-enabled.security_compliance " "is set to 'True'. For more details, refer to " "keystone config options " "keystone.conf:security_compliance.lockout_duration. " "Setting this option will have no effect unless you also " "set identity.user_lockout_failure_attempts."), 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. This only takes " "effect when identity-feature-enabled.security_compliance " "is set to 'True'. " "This config option corresponds to keystone.conf: " "security_compliance.unique_last_password_count, whose " "default value is 0 meaning disabling this feature. " "NOTE: This config option value must be same as " "keystone.conf: security_compliance.unique_last_password_" "count otherwise test might fail" ), ] 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'"), 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?'), cfg.BoolOpt('project_tags', default=False, help='Is the project tags identity v3 API available?'), # Application credentials is a default feature in Queens. This config # option can removed once Pike is EOL. cfg.BoolOpt('application_credentials', default=False, help='Does the environment have application credentials ' 'enabled?'), cfg.BoolOpt('immutable_user_source', default=False, help='Set to True if the environment has a read-only ' 'user source. This will skip all tests that attempt to ' 'create, delete, or modify users. This should not be set ' 'to True if using dynamic credentials') ] 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('certified_image_ref', help="Valid image reference to be used in image certificate " "validation tests when enabled. This image must also " "have the required img_signature_* properties set. " "Additional details available within the following Nova " "documentation: https://docs.openstack.org/nova/latest/" "user/certificate-validation.html"), cfg.ListOpt('certified_image_trusted_certs', help="A list of trusted certificates to be used when the " "image certificate validation compute feature is " "enabled."), 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 configuration value should be same as ' 'nova.conf: DEFAULT.shelved_offload_time, 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'"), cfg.StrOpt('compute_volume_common_az', default=None, help='AZ to be used for Cinder and Nova. Set this parameter ' 'when the cloud has nova.conf: cinder.cross_az_attach ' 'set to false. Which means volumes attached to an ' 'instance must be in the same availability zone in Cinder ' 'as the instance availability zone in Nova. Set the ' 'common availability zone in this config which will be ' 'used to boot an instance as well as creating a volume. ' 'NOTE: If that AZ is not in Cinder (or ' 'allow_availability_zone_fallback=False in cinder.conf), ' 'the volume create request will fail and the instance ' 'will fail the build request.'), ] placement_group = cfg.OptGroup(name='placement', title='Placement Service Options') PlacementGroup = [ cfg.StrOpt('endpoint_type', default='public', choices=['public', 'admin', 'internal'], help="The endpoint type to use for the placement service."), cfg.StrOpt('catalog_type', default='placement', help="Catalog type of the Placement service."), cfg.StrOpt('region', default='RegionOne', help="The placement 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 region found " "is used."), 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. " "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. " "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.conf: vnc.enabled'), cfg.StrOpt('vnc_server_header', default='WebSockify', help='Expected VNC server name (WebSockify, nginx, etc) ' 'in response header.', deprecated_for_removal=True, deprecated_reason='This option will be ignored because the ' 'usage of different response header fields ' 'to accomplish the same goal (in accordance ' 'with RFC7231 S6.2.2) makes it obsolete.'), cfg.BoolOpt('spice_console', default=False, help='Enable Spice console. This configuration value should ' 'be same as nova.conf: spice.enabled'), cfg.BoolOpt('rdp_console', default=False, help='Enable RDP console. This configuration value should ' 'be same as nova.conf: rdp.enabled'), cfg.BoolOpt('serial_console', default=False, help='Enable serial console. This configuration value ' 'should be the same as ' 'nova.conf: serial_console.enabled'), 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_enabled_filters', default=["AvailabilityZoneFilter", "ComputeFilter", "ComputeCapabilitiesFilter", "ImagePropertiesFilter", "ServerGroupAntiAffinityFilter", "ServerGroupAffinityFilter"], help="A list of enabled filters that Nova will accept as " "hints to the scheduler when creating a server. 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. A special entry 'all' indicates all " "filters that are included with Nova are enabled. If " "using 'all', be sure to enable all filters in " "nova.conf, as tests can fail in unpredictable ways if " "Nova's and Tempest's enabled filters don't match. " "Empty list indicates all filters are disabled. The " "full list of enabled filters is in nova.conf: " "filter_scheduler.enabled_filters.", deprecated_opts=[cfg.DeprecatedOpt( 'scheduler_available_filters', group='compute-feature-enabled')]), cfg.BoolOpt('swap_volume', default=False, help='Does the test environment support in-place swapping of ' 'volumes attached to a server instance?'), cfg.BoolOpt('volume_backed_live_migration', default=False, help='Does the test environment support volume-backed live ' 'migration?'), cfg.BoolOpt('volume_multiattach', default=False, help='Does the test environment support attaching a volume to ' 'more than one instance? This depends on hypervisor and ' 'volume backend/type and compute API version 2.60.'), ] 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=False, 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.'), ] network_group = cfg.OptGroup(name='network', title='Network Service Options') ProfileType = types.Dict(types.List(types.String(), bounds=True)) 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="2001:db8::/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", deprecated_for_removal=True, deprecated_reason="This config option is no longer " "used anywhere, so it can be removed."), 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.Opt('port_profile', type=ProfileType, default={}, help="port profile to use when launching instances" " with pre-configured ports."), 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'), cfg.StrOpt('qos_placement_physnet', default=None, help='Name of the physnet for placement based minimum ' 'bandwidth allocation.'), cfg.StrOpt('provider_net_base_segmentation_id', default=3000, help='Base segmentation ID to create provider networks. ' 'This value will be increased in case of conflict.') ] validation_group = cfg.OptGroup(name='validation', title='SSH Validation options') ValidationGroup = [ cfg.BoolOpt('run_validation', default=True, help='Enable ssh on created servers and creation of additional' ' validation resources to enable remote access.' ' In case the guest does not support ssh set it' ' to false'), 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:/usr/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='volumev3', 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_v2', default=True, help="Is the v2 volume API enabled", deprecated_for_removal=True, deprecated_reason="The v2 volume API has been deprecated " "since Pike release. Now Tempest run all " "the volume tests against v2 or v3 API " "based on CONF.volume.catalog_type which " "makes this config option unusable. If " "catalog_type is volumev2, then all the " "volume tests will run against v2 API. " "Use ``CONF.volume.catalog_type`` to run " "the Tempest against volume v2 or v3 API"), cfg.BoolOpt('api_v3', default=True, help="Is the v3 volume API enabled", deprecated_for_removal=True, deprecated_reason="Tempest run all the volume tests against " "v2 or v3 API based on " "CONF.volume.catalog_type which makes this " "config option unusable. If catalog_type is " "volumev3 which is default, then all the " "volume tests will run against v3 API. " "Use ``CONF.volume.catalog_type`` to run " "the Tempest against volume v2 or v3 API"), 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"), ] 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"'), cfg.StrOpt('protocol', default='icmp', choices=('icmp', 'tcp', 'udp'), help='The protocol used in security groups tests to check ' 'connectivity.'), ] 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"), ] 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. """) ] profiler_group = cfg.OptGroup(name="profiler", title="OpenStack Profiler") ProfilerGroup = [ cfg.StrOpt('key', help="The secret key to enable OpenStack Profiler. The value " "should match the one configured in OpenStack services " "under `[profiler]/hmac_keys` property. The default empty " "value keeps profiling disabled"), ] DefaultGroup = [ 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), (scenario_group, ScenarioGroup), (service_available_group, ServiceAvailableGroup), (debug_group, DebugGroup), (placement_group, PlacementGroup), (profiler_group, ProfilerGroup), (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 module 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.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 # FIXME(masayukig): bug#1783751 To pass the config file path to child # processes, we need to set the environment variables here as a # workaround. os.environ['TEMPEST_CONFIG_DIR'] = os.path.dirname(path) os.environ['TEMPEST_CONFIG'] = os.path.basename(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-23.0.0/tempest/exceptions.py000077500000000000000000000041311361143263300174750ustar00rootroot00000000000000# 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 VolumeExtendErrorException(exceptions.TempestException): message = ("Volume %(volume_id)s failed to extend and " "is in error_extending 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-23.0.0/tempest/hacking/000077500000000000000000000000001361143263300163445ustar00rootroot00000000000000tempest-23.0.0/tempest/hacking/__init__.py000066400000000000000000000000001361143263300204430ustar00rootroot00000000000000tempest-23.0.0/tempest/hacking/checks.py000066400000000000000000000266211361143263300201650ustar00rootroot00000000000000# 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 pycodestyle 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+|\))') NEGATIVE_TEST_DECORATOR = re.compile( r'\s*@decorators\.attr\(type=.*negative.*\)') _HAVE_NEGATIVE_DECORATOR = False 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 pycodestyle.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 pycodestyle.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 pycodestyle.noqa(physical_line): return if not re.match(r'class .*Test.*\(.*Admin.*\):', logical_line): return if not re.match(r'.\/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 negative_test_attribute_always_applied_to_negative_tests(physical_line, filename): """Check ``@decorators.attr(type=['negative'])`` applied to negative tests. T117 """ global _HAVE_NEGATIVE_DECORATOR if re.match(r'.\/tempest\/api\/.*_negative.*', filename): if NEGATIVE_TEST_DECORATOR.match(physical_line): _HAVE_NEGATIVE_DECORATOR = True return if TEST_DEFINITION.match(physical_line): if not _HAVE_NEGATIVE_DECORATOR: return ( 0, "T117: Must apply `@decorators.attr(type=['negative'])`" " to all negative API tests" ) _HAVE_NEGATIVE_DECORATOR = False 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) register(negative_test_attribute_always_applied_to_negative_tests) tempest-23.0.0/tempest/lib/000077500000000000000000000000001361143263300155065ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/__init__.py000066400000000000000000000000001361143263300176050ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/000077500000000000000000000000001361143263300175775ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/__init__.py000066400000000000000000000000001361143263300216760ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/000077500000000000000000000000001361143263300214355ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/__init__.py000066400000000000000000000000001361143263300235340ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/000077500000000000000000000000001361143263300231115ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/__init__.py000066400000000000000000000000001361143263300252100ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_1/000077500000000000000000000000001361143263300236605ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_1/__init__.py000066400000000000000000000000001361143263300257570ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_1/agents.py000066400000000000000000000046021361143263300255150ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/aggregates.py000066400000000000000000000054011361143263300263430ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/availability_zone.py000066400000000000000000000054271361143263300277470ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/baremetal_nodes.py000066400000000000000000000037101361143263300273570ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/certificates.py000066400000000000000000000026001361143263300266750ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/extensions.py000066400000000000000000000033251361143263300264340ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/fixed_ips.py000066400000000000000000000026111361143263300262040ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/flavors.py000066400000000000000000000063771361143263300257230ustar00rootroot00000000000000# 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'] } } create_update_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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/flavors_access.py000066400000000000000000000024311361143263300272270ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py000066400000000000000000000024601361143263300303100ustar00rootroot00000000000000# 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': { r'^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'} } } }, 'additionalProperties': False, 'required': ['extra_specs'] } } unset_flavor_extra_specs = { 'status_code': [200] } set_get_flavor_extra_specs_key = { 'status_code': [200], 'response_body': { 'type': 'object', 'patternProperties': { r'^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'} } } } tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_1/floating_ips.py000066400000000000000000000111051361143263300267060ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/hosts.py000066400000000000000000000072761361143263300254060ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/hypervisors.py000066400000000000000000000156721361143263300266420ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/images.py000066400000000000000000000110721361143263300255000ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/instance_usage_audit_logs.py000066400000000000000000000053621361143263300314420ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/interfaces.py000066400000000000000000000043441361143263300263620ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/keypairs.py000066400000000000000000000073261361143263300260710ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/limits.py000066400000000000000000000123151361143263300255350ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/migrations.py000066400000000000000000000044521361143263300264130ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py000066400000000000000000000061271361143263300274440ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/quota_classes.py000066400000000000000000000027261361143263300271070ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/quotas.py000066400000000000000000000135161361143263300255540ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/security_group_default_rule.py000066400000000000000000000040001361143263300320420ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/security_groups.py000066400000000000000000000066211361143263300275050ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/server_groups.py000066400000000000000000000035541361143263300271460ustar00rootroot00000000000000# 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. 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'] } } tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_1/servers.py000066400000000000000000000404561361143263300257340ustar00rootroot00000000000000# 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'] } } 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, # NOTE(zhufl): events.traceback can only be seen by admin users # with default policy.json, so it shouldn't be a required field. 'required': ['event', 'start_time', 'finish_time', 'result'] } } 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/services.py000066400000000000000000000060651361143263300260640ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/snapshots.py000066400000000000000000000035461361143263300262640ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/tenant_networks.py000066400000000000000000000026771361143263300274730ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/tenant_usages.py000066400000000000000000000057641361143263300271060ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/versions.py000066400000000000000000000062401361143263300261040ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_1/volumes.py000066400000000000000000000127151361143263300257320ustar00rootroot00000000000000# 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'. } }, 'os-vol-host-attr:host': {'type': 'string'}, }, '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-23.0.0/tempest/lib/api_schema/response/compute/v2_11/000077500000000000000000000000001361143263300237415ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_11/__init__.py000066400000000000000000000000001361143263300260400ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_11/services.py000066400000000000000000000037701361143263300261450ustar00rootroot00000000000000# 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'] } } # **** Schemas unchanged in microversion 2.11 since microversion 2.1 **** # Note(felipemonteiro): Below are the unchanged schema in this microversion. We # need to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. enable_disable_service = copy.deepcopy(services.enable_disable_service) disable_log_reason = copy.deepcopy(services.disable_log_reason) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_13/000077500000000000000000000000001361143263300237435ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_13/__init__.py000066400000000000000000000000001361143263300260420ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_13/server_groups.py000066400000000000000000000030301361143263300272160ustar00rootroot00000000000000# 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 server_groups # Compute microversion 2.13: # 1. New attributes in 'server_group' dict. # 'project_id', 'user_id' common_server_group = copy.deepcopy(server_groups.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( server_groups.create_show_server_group) create_show_server_group['response_body']['properties'][ 'server_group'] = common_server_group delete_server_group = copy.deepcopy(server_groups.delete_server_group) list_server_groups = copy.deepcopy(server_groups.list_server_groups) list_server_groups['response_body']['properties']['server_groups'][ 'items'] = common_server_group tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_16/000077500000000000000000000000001361143263300237465ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_16/__init__.py000066400000000000000000000000001361143263300260450ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_16/servers.py000066400000000000000000000157751361143263300260300ustar00rootroot00000000000000# 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): 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'] } } # NOTE(gmann): Below are the unchanged schema in this microversion. We need # to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. # ****** Schemas unchanged since microversion 2.9 ****** list_servers = copy.deepcopy(servers.list_servers) update_server = copy.deepcopy(servers.update_server) rebuild_server = copy.deepcopy(servers.rebuild_server) rebuild_server_with_admin_pass = copy.deepcopy( servers.rebuild_server_with_admin_pass) show_server_diagnostics = copy.deepcopy(servers.show_server_diagnostics) get_remote_consoles = copy.deepcopy(servers.get_remote_consoles) attach_volume = copy.deepcopy(servers.attach_volume) show_volume_attachment = copy.deepcopy(servers.show_volume_attachment) list_volume_attachments = copy.deepcopy(servers.list_volume_attachments) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_19/000077500000000000000000000000001361143263300237515ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_19/__init__.py000066400000000000000000000000001361143263300260500ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_19/servers.py000066400000000000000000000057171361143263300260260ustar00rootroot00000000000000# 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_16 import servers \ as serversv216 # Compute microversion 2.19: # 1. New attributes in 'server' dict. # 'description' 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(serversv216.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(serversv216.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( serversv216.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') # NOTE(gmann): Below are the unchanged schema in this microversion. We need # to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. # ****** Schemas unchanged since microversion 2.16 ****** list_servers = copy.deepcopy(serversv216.list_servers) show_server_diagnostics = copy.deepcopy(serversv216.show_server_diagnostics) get_remote_consoles = copy.deepcopy(serversv216.get_remote_consoles) attach_volume = copy.deepcopy(serversv216.attach_volume) show_volume_attachment = copy.deepcopy(serversv216.show_volume_attachment) list_volume_attachments = copy.deepcopy(serversv216.list_volume_attachments) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_2/000077500000000000000000000000001361143263300236615ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_2/__init__.py000066400000000000000000000000001361143263300257600ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_2/keypairs.py000066400000000000000000000031401361143263300260600ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_23/000077500000000000000000000000001361143263300237445ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_23/__init__.py000066400000000000000000000000001361143263300260430ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_23/migrations.py000066400000000000000000000051511361143263300264740ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/api_schema/response/compute/v2_26/000077500000000000000000000000001361143263300237475ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_26/__init__.py000066400000000000000000000000001361143263300260460ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_26/servers.py000066400000000000000000000071651361143263300260230ustar00rootroot00000000000000# 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_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') update_server = copy.deepcopy(servers219.update_server) update_server['response_body']['properties']['server'][ 'properties'].update({'tags': tag_items}) update_server['response_body']['properties']['server'][ 'required'].append('tags') rebuild_server = copy.deepcopy(servers219.rebuild_server) rebuild_server['response_body']['properties']['server'][ 'properties'].update({'tags': tag_items}) rebuild_server['response_body']['properties']['server'][ 'required'].append('tags') rebuild_server_with_admin_pass = copy.deepcopy( servers219.rebuild_server_with_admin_pass) rebuild_server_with_admin_pass['response_body']['properties']['server'][ 'properties'].update({'tags': tag_items}) rebuild_server_with_admin_pass['response_body']['properties']['server'][ 'required'].append('tags') 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]} # NOTE(gmann): Below are the unchanged schema in this microversion. We need # to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. # ****** Schemas unchanged since microversion 2.19 ****** list_servers = copy.deepcopy(servers219.list_servers) show_server_diagnostics = copy.deepcopy(servers219.show_server_diagnostics) get_remote_consoles = copy.deepcopy(servers219.get_remote_consoles) attach_volume = copy.deepcopy(servers219.attach_volume) show_volume_attachment = copy.deepcopy(servers219.show_volume_attachment) list_volume_attachments = copy.deepcopy(servers219.list_volume_attachments) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_28/000077500000000000000000000000001361143263300237515ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_28/__init__.py000066400000000000000000000000001361143263300260500ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_28/hypervisors.py000066400000000000000000000035441361143263300267260ustar00rootroot00000000000000# Copyright 2018 ZTE Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 hypervisors as hypervisorsv21 # hypervisor.cpu_info change from string to JSON object. hypervisor_detail = copy.deepcopy(hypervisorsv21.hypervisor_detail) hypervisor_detail['properties'].update({'cpu_info': {'type': 'object'}}) list_hypervisors_detail = copy.deepcopy(hypervisorsv21.list_hypervisors_detail) list_hypervisors_detail['response_body']['properties']['hypervisors'].update( {'items': hypervisor_detail}) get_hypervisor = copy.deepcopy(hypervisorsv21.get_hypervisor) get_hypervisor['response_body']['properties'].update( {'hypervisor': hypervisor_detail}) # NOTE(zhufl): Below are the unchanged schema in this microversion. We need # to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. # ****** Schemas unchanged since microversion 2.1 *** get_hypervisor_statistics = \ copy.deepcopy(hypervisorsv21.get_hypervisor_statistics) list_search_hypervisors = copy.deepcopy(hypervisorsv21.list_search_hypervisors) get_hypervisor_uptime = copy.deepcopy(hypervisorsv21.get_hypervisor_uptime) get_hypervisors_servers = copy.deepcopy(hypervisorsv21.get_hypervisors_servers) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_3/000077500000000000000000000000001361143263300236625ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_3/__init__.py000066400000000000000000000000001361143263300257610ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_3/servers.py000066400000000000000000000162611361143263300257330ustar00rootroot00000000000000# 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): 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'] } } # NOTE: Below are the unchanged schema in this microversion. We need # to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. # ****** Schemas unchanged since microversion 2.1 *** list_servers = copy.deepcopy(servers.list_servers) update_server = copy.deepcopy(servers.update_server) rebuild_server = copy.deepcopy(servers.rebuild_server) rebuild_server_with_admin_pass = copy.deepcopy( servers.rebuild_server_with_admin_pass) show_server_diagnostics = copy.deepcopy(servers.show_server_diagnostics) attach_volume = copy.deepcopy(servers.attach_volume) show_volume_attachment = copy.deepcopy(servers.show_volume_attachment) list_volume_attachments = copy.deepcopy(servers.list_volume_attachments) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_36/000077500000000000000000000000001361143263300237505ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_36/__init__.py000066400000000000000000000000001361143263300260470ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_36/limits.py000066400000000000000000000025251361143263300256270ustar00rootroot00000000000000# Copyright 2018 ZTE Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 limits as limitv21 # Compute microversion 2.36: # remove attributes in get_limit: # 'maxSecurityGroupRules', # 'maxSecurityGroups', # 'maxTotalFloatingIps', # 'totalFloatingIpsUsed', # 'totalSecurityGroupsUsed' get_limit = copy.deepcopy(limitv21.get_limit) for item in ['maxSecurityGroupRules', 'maxSecurityGroups', 'maxTotalFloatingIps', 'totalFloatingIpsUsed', 'totalSecurityGroupsUsed']: get_limit['response_body']['properties']['limits']['properties'][ 'absolute']['properties'].pop(item) get_limit['response_body']['properties']['limits']['properties'][ 'absolute']['required'].remove(item) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_36/quotas.py000066400000000000000000000042261361143263300256420ustar00rootroot00000000000000# Copyright 2018 ZTE Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 as quotasv21 # Compute microversion 2.36: # remove attributes in quota_set: # 'fixed_ips', # 'floating_ips', # 'security_group_rules', # 'security_groups' remove_item_list = ['fixed_ips', 'floating_ips', 'security_group_rules', 'security_groups'] update_quota_set = copy.deepcopy(quotasv21.update_quota_set) for item in remove_item_list: update_quota_set['response_body']['properties']['quota_set'][ 'properties'].pop(item) update_quota_set['response_body']['properties']['quota_set'][ 'required'].remove(item) get_quota_set = copy.deepcopy(quotasv21.get_quota_set) for item in remove_item_list: get_quota_set['response_body']['properties']['quota_set'][ 'properties'].pop(item) get_quota_set['response_body']['properties']['quota_set'][ 'required'].remove(item) get_quota_set_details = copy.deepcopy(quotasv21.get_quota_set_details) for item in remove_item_list: get_quota_set_details['response_body']['properties']['quota_set'][ 'properties'].pop(item) get_quota_set_details['response_body']['properties']['quota_set'][ 'required'].remove(item) # NOTE(zhufl): Below are the unchanged schema in this microversion. We need # to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. # ****** Schemas unchanged since microversion 2.1 *** delete_quota = copy.deepcopy(quotasv21.delete_quota) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_39/000077500000000000000000000000001361143263300237535ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_39/__init__.py000066400000000000000000000000001361143263300260520ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_39/limits.py000066400000000000000000000021011361143263300256200ustar00rootroot00000000000000# Copyright 2018 ZTE Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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_36 import limits as limitv236 # Compute microversion 2.39: # remove attributes in get_limit: # 'maxImageMeta' get_limit = copy.deepcopy(limitv236.get_limit) get_limit['response_body']['properties']['limits']['properties']['absolute'][ 'properties'].pop('maxImageMeta') get_limit['response_body']['properties']['limits']['properties']['absolute'][ 'required'].remove('maxImageMeta') tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_41/000077500000000000000000000000001361143263300237445ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_41/__init__.py000066400000000000000000000000001361143263300260430ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_41/aggregates.py000066400000000000000000000044741361143263300264400ustar00rootroot00000000000000# Copyright 2018 ZTE Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 aggregates # 'uuid' of an aggregate is returned in microversion 2.41 aggregate_for_create = copy.deepcopy(aggregates.aggregate_for_create) aggregate_for_create['properties'].update({'uuid': {'type': 'string', 'format': 'uuid'}}) aggregate_for_create['required'].append('uuid') common_aggregate_info = copy.deepcopy(aggregates.common_aggregate_info) common_aggregate_info['properties'].update({'uuid': {'type': 'string', 'format': 'uuid'}}) common_aggregate_info['required'].append('uuid') list_aggregates = copy.deepcopy(aggregates.list_aggregates) list_aggregates['response_body']['properties']['aggregates'].update( {'items': common_aggregate_info}) get_aggregate = copy.deepcopy(aggregates.get_aggregate) get_aggregate['response_body']['properties'].update( {'aggregate': common_aggregate_info}) aggregate_set_metadata = get_aggregate update_aggregate = copy.deepcopy(aggregates.update_aggregate) update_aggregate['response_body']['properties'].update( {'aggregate': common_aggregate_info}) create_aggregate = copy.deepcopy(aggregates.create_aggregate) create_aggregate['response_body']['properties'].update( {'aggregate': aggregate_for_create}) aggregate_add_remove_host = get_aggregate # NOTE(zhufl): Below are the unchanged schema in this microversion. We need # to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. # ****** Schemas unchanged since microversion 2.1 *** delete_aggregate = copy.deepcopy(aggregates.delete_aggregate) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_45/000077500000000000000000000000001361143263300237505ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_45/__init__.py000066400000000000000000000000001361143263300260470ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_45/images.py000066400000000000000000000024441361143263300255730ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # The 2.45 microversion removes the "location" header and adds "image_id" # to the response body. create_image = { 'status_code': [202], 'response_body': { 'type': 'object', 'properties': { 'image_id': {'type': 'string'} }, 'additionalProperties': False, 'required': ['image_id'] } } # NOTE(mriedem): The compute proxy APIs for showing/listing and deleting # images were deprecated in microversion 2.35, and the compute proxy APIs for # working with image metadata were deprecated in microversion 2.39. Therefore, # client-side code shouldn't rely on those APIs in the compute images client # past those microversions and should instead use the Glance images client # directly. tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_47/000077500000000000000000000000001361143263300237525ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_47/__init__.py000066400000000000000000000000001361143263300260510ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_47/servers.py000066400000000000000000000060171361143263300260210ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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': { r'^[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}) list_servers_detail = copy.deepcopy(servers226.list_servers_detail) list_servers_detail['response_body']['properties']['servers']['items'][ 'properties'].update({'flavor': flavor}) update_server = copy.deepcopy(servers226.update_server) update_server['response_body']['properties']['server'][ 'properties'].update({'flavor': flavor}) rebuild_server = copy.deepcopy(servers226.rebuild_server) rebuild_server['response_body']['properties']['server'][ 'properties'].update({'flavor': flavor}) rebuild_server_with_admin_pass = copy.deepcopy( servers226.rebuild_server_with_admin_pass) rebuild_server_with_admin_pass['response_body']['properties']['server'][ 'properties'].update({'flavor': flavor}) # NOTE(zhufl): Below are the unchanged schema in this microversion. We need # to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. show_server_diagnostics = copy.deepcopy(servers226.show_server_diagnostics) get_remote_consoles = copy.deepcopy(servers226.get_remote_consoles) list_tags = copy.deepcopy(servers226.list_tags) update_all_tags = copy.deepcopy(servers226.update_all_tags) delete_all_tags = copy.deepcopy(servers226.delete_all_tags) check_tag_existence = copy.deepcopy(servers226.check_tag_existence) update_tag = copy.deepcopy(servers226.update_tag) delete_tag = copy.deepcopy(servers226.delete_tag) list_servers = copy.deepcopy(servers226.list_servers) attach_volume = copy.deepcopy(servers226.attach_volume) show_volume_attachment = copy.deepcopy(servers226.show_volume_attachment) list_volume_attachments = copy.deepcopy(servers226.list_volume_attachments) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_48/000077500000000000000000000000001361143263300237535ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_48/__init__.py000066400000000000000000000000001361143263300260520ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_48/servers.py000066400000000000000000000140071361143263300260200ustar00rootroot00000000000000# 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'], } } # NOTE(zhufl): Below are the unchanged schema in this microversion. We need # to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. list_servers = copy.deepcopy(servers247.list_servers) get_remote_consoles = copy.deepcopy(servers247.get_remote_consoles) list_tags = copy.deepcopy(servers247.list_tags) update_all_tags = copy.deepcopy(servers247.update_all_tags) delete_all_tags = copy.deepcopy(servers247.delete_all_tags) check_tag_existence = copy.deepcopy(servers247.check_tag_existence) update_tag = copy.deepcopy(servers247.update_tag) delete_tag = copy.deepcopy(servers247.delete_tag) get_server = copy.deepcopy(servers247.get_server) list_servers_detail = copy.deepcopy(servers247.list_servers_detail) update_server = copy.deepcopy(servers247.update_server) rebuild_server = copy.deepcopy(servers247.rebuild_server) rebuild_server_with_admin_pass = copy.deepcopy( servers247.rebuild_server_with_admin_pass) attach_volume = copy.deepcopy(servers247.attach_volume) show_volume_attachment = copy.deepcopy(servers247.show_volume_attachment) list_volume_attachments = copy.deepcopy(servers247.list_volume_attachments) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_53/000077500000000000000000000000001361143263300237475ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_53/__init__.py000066400000000000000000000000001361143263300260460ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_53/services.py000066400000000000000000000057511361143263300261540ustar00rootroot00000000000000# Copyright 2018 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 parameter_types from tempest.lib.api_schema.response.compute.v2_11 import services \ as servicesv211 # ***************** Schemas changed in microversion 2.53 ***************** # NOTE(felipemonteiro): This is schema for microversion 2.53 which includes: # # * changing the service 'id' to 'string' type only # * adding update_service which supersedes enable_service, disable_service, # disable_log_reason, update_forced_down. list_services = copy.deepcopy(servicesv211.list_services) # The ID of the service is a uuid, so v2.1 pattern does not apply. list_services['response_body']['properties']['services']['items'][ 'properties']['id'] = {'type': 'string', 'format': 'uuid'} update_service = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'service': { 'type': 'object', 'properties': { 'id': {'type': 'string', 'format': 'uuid'}, 'binary': {'type': 'string'}, # disabled_reason can be null when status is enabled. 'disabled_reason': {'type': ['string', 'null']}, 'host': {'type': 'string'}, 'state': {'type': 'string'}, 'status': {'type': 'string'}, 'updated_at': parameter_types.date_time, 'zone': {'type': 'string'}, 'forced_down': {'type': 'boolean'} }, 'additionalProperties': False, 'required': ['id', 'binary', 'disabled_reason', 'host', 'state', 'status', 'updated_at', 'zone', 'forced_down'] } }, 'additionalProperties': False, 'required': ['service'] } } # **** Schemas unchanged in microversion 2.53 since microversion 2.11 **** # Note(felipemonteiro): Below are the unchanged schema in this microversion. We # need to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. enable_disable_service = copy.deepcopy(servicesv211.enable_disable_service) update_forced_down = copy.deepcopy(servicesv211.update_forced_down) disable_log_reason = copy.deepcopy(servicesv211.disable_log_reason) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_54/000077500000000000000000000000001361143263300237505ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_54/__init__.py000066400000000000000000000000001361143263300260470ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_54/servers.py000066400000000000000000000054271361143263300260230ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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_48 import servers as servers248 # ****** Schemas changed in microversion 2.54 ***************** # Note(gmann): This is schema for microversion 2.54 which includes the # 'key_name' in the Response body of the following APIs: # - ``POST '/servers/{server_id}/action (rebuild)`` key_name = { 'oneOf': [ {'type': 'string', 'minLength': 1, 'maxLength': 255}, {'type': 'null'}, ] } rebuild_server = copy.deepcopy(servers248.rebuild_server) rebuild_server['response_body']['properties']['server'][ 'properties'].update({'key_name': key_name}) rebuild_server['response_body']['properties']['server'][ 'required'].append('key_name') rebuild_server_with_admin_pass = copy.deepcopy( servers248.rebuild_server_with_admin_pass) rebuild_server_with_admin_pass['response_body']['properties']['server'][ 'properties'].update({'key_name': key_name}) rebuild_server_with_admin_pass['response_body']['properties']['server'][ 'required'].append('key_name') # NOTE(gmann): Below are the unchanged schema in this microversion. We need # to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. # ****** Schemas unchanged in microversion 2.54 since microversion 2.48 *** get_server = copy.deepcopy(servers248.get_server) list_servers_detail = copy.deepcopy(servers248.list_servers_detail) update_server = copy.deepcopy(servers248.update_server) list_servers = copy.deepcopy(servers248.list_servers) show_server_diagnostics = copy.deepcopy(servers248.show_server_diagnostics) get_remote_consoles = copy.deepcopy(servers248.get_remote_consoles) list_tags = copy.deepcopy(servers248.list_tags) update_all_tags = copy.deepcopy(servers248.update_all_tags) delete_all_tags = copy.deepcopy(servers248.delete_all_tags) check_tag_existence = copy.deepcopy(servers248.check_tag_existence) update_tag = copy.deepcopy(servers248.update_tag) delete_tag = copy.deepcopy(servers248.delete_tag) attach_volume = copy.deepcopy(servers248.attach_volume) show_volume_attachment = copy.deepcopy(servers248.show_volume_attachment) list_volume_attachments = copy.deepcopy(servers248.list_volume_attachments) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_55/000077500000000000000000000000001361143263300237515ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_55/__init__.py000066400000000000000000000000001361143263300260500ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_55/flavors.py000066400000000000000000000102251361143263300257770ustar00rootroot00000000000000# Copyright 2018 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 flavors as flavorsv21 from tempest.lib.api_schema.response.compute.v2_1 import parameter_types # Note(gmann): This is schema for microversion 2.55 which includes the # following changes: # Add new PUT API # Adds a ``description`` field to the following APIs response: # - ``GET /flavors`` # - ``GET /flavors/detail`` # - ``GET /flavors/{flavor_id}`` # - ``POST /flavors`` flavor_description = { 'type': ['string', 'null'], 'minLength': 0, 'maxLength': 65535 } 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'}, 'description': flavor_description }, 'additionalProperties': False, 'required': ['name', 'links', 'id', 'description'] } }, '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'}, 'description': flavor_description }, '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', 'description'] } 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'] } } create_update_get_flavor_details = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'flavor': common_flavor_info }, 'additionalProperties': False, 'required': ['flavor'] } } # Note(zhufl): Below are the unchanged schema in this microversion. We need # to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. # ****** Schemas unchanged since microversion 2.1 *** delete_flavor = copy.deepcopy(flavorsv21.delete_flavor) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_57/000077500000000000000000000000001361143263300237535ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_57/__init__.py000066400000000000000000000000001361143263300260520ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_57/limits.py000066400000000000000000000022171361143263300256300ustar00rootroot00000000000000# Copyright 2018 ZTE Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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_39 import limits as limitv239 # Compute microversion 2.57: # remove attributes in get_limit: # 'maxPersonality', # 'maxPersonalitySize' get_limit = copy.deepcopy(limitv239.get_limit) for item in ['maxPersonality', 'maxPersonalitySize']: get_limit['response_body']['properties']['limits']['properties'][ 'absolute']['properties'].pop(item) get_limit['response_body']['properties']['limits']['properties'][ 'absolute']['required'].remove(item) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_57/quotas.py000066400000000000000000000042411361143263300256420ustar00rootroot00000000000000# Copyright 2018 ZTE Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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_36 import quotas as quotasv236 # Compute microversion 2.57: # remove attributes in quota_set: # 'injected_file_content_bytes', # 'injected_file_path_bytes', # 'injected_files' remove_item_list = ['injected_file_content_bytes', 'injected_file_path_bytes', 'injected_files'] update_quota_set = copy.deepcopy(quotasv236.update_quota_set) for item in remove_item_list: update_quota_set['response_body']['properties']['quota_set'][ 'properties'].pop(item) update_quota_set['response_body']['properties']['quota_set'][ 'required'].remove(item) get_quota_set = copy.deepcopy(quotasv236.get_quota_set) for item in remove_item_list: get_quota_set['response_body']['properties']['quota_set'][ 'properties'].pop(item) get_quota_set['response_body']['properties']['quota_set'][ 'required'].remove(item) get_quota_set_details = copy.deepcopy(quotasv236.get_quota_set_details) for item in remove_item_list: get_quota_set_details['response_body']['properties']['quota_set'][ 'properties'].pop(item) get_quota_set_details['response_body']['properties']['quota_set'][ 'required'].remove(item) # NOTE(zhufl): Below are the unchanged schema in this microversion. We need # to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. # ****** Schemas unchanged since microversion 2.1 *** delete_quota = copy.deepcopy(quotasv236.delete_quota) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_57/servers.py000066400000000000000000000055221361143263300260220ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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_54 import servers as servers254 # ****** Schemas changed in microversion 2.57 ***************** # Note(gmann): This is schema for microversion 2.57 which includes the # 'user_data' in the Response body of the following APIs: # - ``POST '/servers/{server_id}/action (rebuild)`` user_data = { 'oneOf': [ { 'type': 'string', 'format': 'base64', 'maxLength': 65535 }, {'type': 'null'} ] } rebuild_server = copy.deepcopy(servers254.rebuild_server) rebuild_server['response_body']['properties']['server'][ 'properties'].update({'user_data': user_data}) rebuild_server['response_body']['properties']['server'][ 'required'].append('user_data') rebuild_server_with_admin_pass = copy.deepcopy( servers254.rebuild_server_with_admin_pass) rebuild_server_with_admin_pass['response_body']['properties']['server'][ 'properties'].update({'user_data': user_data}) rebuild_server_with_admin_pass['response_body']['properties']['server'][ 'required'].append('user_data') # NOTE(gmann): Below are the unchanged schema in this microversion. We need # to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. # ****** Schemas unchanged in microversion 2.57 since microversion 2.54 *** get_server = copy.deepcopy(servers254.get_server) list_servers_detail = copy.deepcopy(servers254.list_servers_detail) update_server = copy.deepcopy(servers254.update_server) list_servers = copy.deepcopy(servers254.list_servers) show_server_diagnostics = copy.deepcopy(servers254.show_server_diagnostics) get_remote_consoles = copy.deepcopy(servers254.get_remote_consoles) list_tags = copy.deepcopy(servers254.list_tags) update_all_tags = copy.deepcopy(servers254.update_all_tags) delete_all_tags = copy.deepcopy(servers254.delete_all_tags) check_tag_existence = copy.deepcopy(servers254.check_tag_existence) update_tag = copy.deepcopy(servers254.update_tag) delete_tag = copy.deepcopy(servers254.delete_tag) attach_volume = copy.deepcopy(servers254.attach_volume) show_volume_attachment = copy.deepcopy(servers254.show_volume_attachment) list_volume_attachments = copy.deepcopy(servers254.list_volume_attachments) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_6/000077500000000000000000000000001361143263300236655ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_6/__init__.py000066400000000000000000000000001361143263300257640ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_6/servers.py000066400000000000000000000047301361143263300257340ustar00rootroot00000000000000# 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 # NOTE: Below are the unchanged schema in this microversion. We need # to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. # ****** Schemas unchanged since microversion 2.3 ****** list_servers = copy.deepcopy(servers.list_servers) get_server = copy.deepcopy(servers.get_server) list_servers_detail = copy.deepcopy(servers.list_servers_detail) update_server = copy.deepcopy(servers.update_server) rebuild_server = copy.deepcopy(servers.rebuild_server) rebuild_server_with_admin_pass = copy.deepcopy( servers.rebuild_server_with_admin_pass) show_server_diagnostics = copy.deepcopy(servers.show_server_diagnostics) attach_volume = copy.deepcopy(servers.attach_volume) show_volume_attachment = copy.deepcopy(servers.show_volume_attachment) list_volume_attachments = copy.deepcopy(servers.list_volume_attachments) # 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', 'xvpvnc', 'rdp-html5', 'spice-html5', 'serial']}, 'url': { 'type': 'string', 'format': 'uri' } }, 'additionalProperties': False, 'required': ['protocol', 'type', 'url'] } }, 'additionalProperties': False, 'required': ['remote_console'] } } tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_61/000077500000000000000000000000001361143263300237465ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_61/__init__.py000066400000000000000000000000001361143263300260450ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_61/flavors.py000066400000000000000000000072231361143263300260000ustar00rootroot00000000000000# Copyright 2018 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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_55 import flavors \ as flavorsv255 # ****** Schemas changed in microversion 2.61 ***************** # Note(gmann): This is schema for microversion 2.61 which includes the # Flavor extra_specs in the Response body of the following APIs: # - ``PUT /flavors/{flavor_id}`` # - ``GET /flavors/detail`` # - ``GET /flavors/{flavor_id}`` # - ``POST /flavors`` flavor_description = { 'type': ['string', 'null'], 'minLength': 0, 'maxLength': 65535 } flavor_extra_specs = { 'type': 'object', 'patternProperties': { '^[a-zA-Z0-9-_:. ]{1,255}$': {'type': 'string'} } } 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'}, 'description': flavor_description, 'extra_specs': flavor_extra_specs }, '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', 'description'] } 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'] } } create_update_get_flavor_details = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'flavor': common_flavor_info }, 'additionalProperties': False, 'required': ['flavor'] } } # ****** Schemas unchanged in microversion 2.61 since microversion 2.55 *** # Note(gmann): Below are the unchanged schema in this microversion. We need # to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. # ****** Schemas unchanged since microversion 2.55 *** list_flavors = copy.deepcopy(flavorsv255.list_flavors) # ****** Schemas unchanged since microversion 2.1 *** delete_flavor = copy.deepcopy(flavorsv255.delete_flavor) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_63/000077500000000000000000000000001361143263300237505ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_63/__init__.py000066400000000000000000000000001361143263300260470ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_63/servers.py000066400000000000000000000070151361143263300260160ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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_57 import servers as servers257 # Nova microversion 2.63 adds 'trusted_image_certificates' (a list of # certificate IDs) to the server rebuild and servers details responses. trusted_certs = { 'type': ['array', 'null'], 'minItems': 1, 'maxItems': 50, 'uniqueItems': True, 'items': { 'type': 'string', 'minLength': 1 } } list_servers_detail = copy.deepcopy(servers257.list_servers_detail) list_servers_detail['response_body']['properties']['servers']['items'][ 'properties'].update({'trusted_image_certificates': trusted_certs}) list_servers_detail['response_body']['properties']['servers']['items'][ 'required'].append('trusted_image_certificates') rebuild_server = copy.deepcopy(servers257.rebuild_server) rebuild_server['response_body']['properties']['server'][ 'properties'].update({'trusted_image_certificates': trusted_certs}) rebuild_server['response_body']['properties']['server'][ 'required'].append('trusted_image_certificates') rebuild_server_with_admin_pass = copy.deepcopy( servers257.rebuild_server_with_admin_pass) rebuild_server_with_admin_pass['response_body']['properties']['server'][ 'properties'].update({'trusted_image_certificates': trusted_certs}) rebuild_server_with_admin_pass['response_body']['properties']['server'][ 'required'].append('trusted_image_certificates') update_server = copy.deepcopy(servers257.update_server) update_server['response_body']['properties']['server'][ 'properties'].update({'trusted_image_certificates': trusted_certs}) update_server['response_body']['properties']['server'][ 'required'].append('trusted_image_certificates') get_server = copy.deepcopy(servers257.get_server) get_server['response_body']['properties']['server'][ 'properties'].update({'trusted_image_certificates': trusted_certs}) get_server['response_body']['properties']['server'][ 'required'].append('trusted_image_certificates') # NOTE(zhufl): Below are the unchanged schema in this microversion. We need # to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. # ****** Schemas unchanged since microversion 2.57 *** list_servers = copy.deepcopy(servers257.list_servers) show_server_diagnostics = copy.deepcopy(servers257.show_server_diagnostics) get_remote_consoles = copy.deepcopy(servers257.get_remote_consoles) list_tags = copy.deepcopy(servers257.list_tags) update_all_tags = copy.deepcopy(servers257.update_all_tags) delete_all_tags = copy.deepcopy(servers257.delete_all_tags) check_tag_existence = copy.deepcopy(servers257.check_tag_existence) update_tag = copy.deepcopy(servers257.update_tag) delete_tag = copy.deepcopy(servers257.delete_tag) attach_volume = copy.deepcopy(servers257.attach_volume) show_volume_attachment = copy.deepcopy(servers257.show_volume_attachment) list_volume_attachments = copy.deepcopy(servers257.list_volume_attachments) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_70/000077500000000000000000000000001361143263300237465ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_70/__init__.py000066400000000000000000000000001361143263300260450ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_70/servers.py000066400000000000000000000071151361143263300260150ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 servers2_1 from tempest.lib.api_schema.response.compute.v2_63 import servers as servers263 ########################################################################### # # 2.70: # # Exposes virtual device tags for volume attachments and virtual interfaces # (ports). A tag parameter is added to the response body for the following # APIs: # # Volumes # # - GET /servers/{server_id}/os-volume_attachments (list) # - GET /servers/{server_id}/os-volume_attachments/{volume_id} (show) # - POST /servers/{server_id}/os-volume_attachments (attach) # # Ports # # - GET /servers/{server_id}/os-interface (list) # - GET /servers/{server_id}/os-interface/{port_id} (show) # - POST /servers/{server_id}/os-interface (attach) # ########################################################################### attach_volume = copy.deepcopy(servers2_1.attach_volume) attach_volume['response_body']['properties']['volumeAttachment'][ 'properties'].update({'tag': {'type': ['string', 'null']}}) attach_volume['response_body']['properties']['volumeAttachment'][ 'required'].append('tag') show_volume_attachment = copy.deepcopy(servers2_1.show_volume_attachment) show_volume_attachment['response_body']['properties']['volumeAttachment'][ 'properties'].update({'tag': {'type': ['string', 'null']}}) show_volume_attachment['response_body']['properties'][ 'volumeAttachment']['required'].append('tag') list_volume_attachments = copy.deepcopy(servers2_1.list_volume_attachments) list_volume_attachments['response_body']['properties']['volumeAttachments'][ 'items']['properties'].update({'tag': {'type': ['string', 'null']}}) list_volume_attachments['response_body']['properties'][ 'volumeAttachments']['items']['required'].append('tag') # TODO(mriedem): Handle the os-interface changes when there is a test that # needs them from this microversion onward. # NOTE(lajoskatona): Below are the unchanged schema in this microversion. We # need to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. # ****** Schemas unchanged since microversion 2.63 *** list_servers_detail = copy.deepcopy(servers263.list_servers_detail) rebuild_server = copy.deepcopy(servers263.rebuild_server) rebuild_server_with_admin_pass = copy.deepcopy( servers263.rebuild_server_with_admin_pass) update_server = copy.deepcopy(servers263.update_server) get_server = copy.deepcopy(servers263.get_server) list_servers = copy.deepcopy(servers263.list_servers) show_server_diagnostics = copy.deepcopy(servers263.show_server_diagnostics) get_remote_consoles = copy.deepcopy(servers263.get_remote_consoles) list_tags = copy.deepcopy(servers263.list_tags) update_all_tags = copy.deepcopy(servers263.update_all_tags) delete_all_tags = copy.deepcopy(servers263.delete_all_tags) check_tag_existence = copy.deepcopy(servers263.check_tag_existence) update_tag = copy.deepcopy(servers263.update_tag) delete_tag = copy.deepcopy(servers263.delete_tag) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_71/000077500000000000000000000000001361143263300237475ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_71/__init__.py000066400000000000000000000000001361143263300260460ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_71/servers.py000066400000000000000000000064471361143263300260250ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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_70 import servers as servers270 ########################################################################### # # 2.71: # # The server_groups parameter will be in the response body of the following # APIs to list the server groups to which the server belongs: # # - GET /servers/{server_id} (show) # - PUT /servers/{server_id} (update) # - POST /servers/{server_id}/action (rebuild) # ########################################################################### # The "server_groups" parameter will always be present and contain at most one # UUID entry. server_groups = { 'type': 'array', 'minItems': 0, 'maxItems': 1, 'items': { 'type': 'string', 'format': 'uuid' } } rebuild_server = copy.deepcopy(servers270.rebuild_server) rebuild_server['response_body']['properties']['server'][ 'properties'].update({'server_groups': server_groups}) rebuild_server['response_body']['properties']['server'][ 'required'].append('server_groups') rebuild_server_with_admin_pass = copy.deepcopy( servers270.rebuild_server_with_admin_pass) rebuild_server_with_admin_pass['response_body']['properties']['server'][ 'properties'].update({'server_groups': server_groups}) rebuild_server_with_admin_pass['response_body']['properties']['server'][ 'required'].append('server_groups') update_server = copy.deepcopy(servers270.update_server) update_server['response_body']['properties']['server'][ 'properties'].update({'server_groups': server_groups}) update_server['response_body']['properties']['server'][ 'required'].append('server_groups') get_server = copy.deepcopy(servers270.get_server) get_server['response_body']['properties']['server'][ 'properties'].update({'server_groups': server_groups}) get_server['response_body']['properties']['server'][ 'required'].append('server_groups') # NOTE(lajoskatona): Below are the unchanged schema in this microversion. We # need to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. # ****** Schemas unchanged since microversion 2.70 *** list_servers_detail = copy.deepcopy(servers270.list_servers_detail) list_servers = copy.deepcopy(servers270.list_servers) show_server_diagnostics = copy.deepcopy(servers270.show_server_diagnostics) get_remote_consoles = copy.deepcopy(servers270.get_remote_consoles) list_tags = copy.deepcopy(servers270.list_tags) update_all_tags = copy.deepcopy(servers270.update_all_tags) delete_all_tags = copy.deepcopy(servers270.delete_all_tags) check_tag_existence = copy.deepcopy(servers270.check_tag_existence) update_tag = copy.deepcopy(servers270.update_tag) delete_tag = copy.deepcopy(servers270.delete_tag) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_8/000077500000000000000000000000001361143263300236675ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_8/__init__.py000066400000000000000000000000001361143263300257660ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_8/servers.py000066400000000000000000000037121361143263300257350ustar00rootroot00000000000000# Copyright 2018 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_6 import servers # 2.8: Add 'mks' protocol and 'webmks' type for remote consoles. get_remote_consoles = copy.deepcopy(servers.get_remote_consoles) get_remote_consoles['response_body']['properties']['remote_console'][ 'properties']['protocol']['enum'].append('mks') get_remote_consoles['response_body']['properties']['remote_console'][ 'properties']['type']['enum'].append('webmks') # NOTE: Below are the unchanged schema in this microversion. We need # to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. # ****** Schemas unchanged since microversion 2.6 ****** list_servers = copy.deepcopy(servers.list_servers) get_server = copy.deepcopy(servers.get_server) list_servers_detail = copy.deepcopy(servers.list_servers_detail) update_server = copy.deepcopy(servers.update_server) rebuild_server = copy.deepcopy(servers.rebuild_server) rebuild_server_with_admin_pass = copy.deepcopy( servers.rebuild_server_with_admin_pass) show_server_diagnostics = copy.deepcopy(servers.show_server_diagnostics) attach_volume = copy.deepcopy(servers.attach_volume) show_volume_attachment = copy.deepcopy(servers.show_volume_attachment) list_volume_attachments = copy.deepcopy(servers.list_volume_attachments) tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_9/000077500000000000000000000000001361143263300236705ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_9/__init__.py000066400000000000000000000000001361143263300257670ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/compute/v2_9/servers.py000066400000000000000000000053161361143263300257400ustar00rootroot00000000000000# 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_8 import 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') update_server = copy.deepcopy(servers.update_server) update_server['response_body']['properties']['server'][ 'properties'].update({'locked': {'type': 'boolean'}}) update_server['response_body']['properties']['server'][ 'required'].append('locked') rebuild_server = copy.deepcopy(servers.rebuild_server) rebuild_server['response_body']['properties']['server'][ 'properties'].update({'locked': {'type': 'boolean'}}) rebuild_server['response_body']['properties']['server'][ 'required'].append('locked') rebuild_server_with_admin_pass = copy.deepcopy( servers.rebuild_server_with_admin_pass) rebuild_server_with_admin_pass['response_body']['properties']['server'][ 'properties'].update({'locked': {'type': 'boolean'}}) rebuild_server_with_admin_pass['response_body']['properties']['server'][ 'required'].append('locked') # NOTE: Below are the unchanged schema in this microversion. We need # to keep this schema in this file to have the generic way to select the # right schema based on self.schema_versions_info mapping in service client. # ****** Schemas unchanged since microversion 2.8 ****** list_servers = copy.deepcopy(servers.list_servers) show_server_diagnostics = copy.deepcopy(servers.show_server_diagnostics) get_remote_consoles = copy.deepcopy(servers.get_remote_consoles) attach_volume = copy.deepcopy(servers.attach_volume) show_volume_attachment = copy.deepcopy(servers.show_volume_attachment) list_volume_attachments = copy.deepcopy(servers.list_volume_attachments) tempest-23.0.0/tempest/lib/api_schema/response/volume/000077500000000000000000000000001361143263300227445ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/volume/__init__.py000066400000000000000000000000001361143263300250430ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/api_schema/response/volume/capabilities.py000066400000000000000000000041151361143263300257500ustar00rootroot00000000000000# Copyright 2018 ZTE Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. property_info = { 'type': 'object', 'properties': { 'type': {'type': 'string'}, 'description': {'type': 'string'}, 'title': {'type': 'string'} }, 'additionalProperties': False, 'required': ['type', 'description', 'title'] } show_backend_capabilities = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'pool_name': {'type': ['string', 'null']}, 'description': {'type': ['string', 'null']}, 'volume_backend_name': {'type': 'string'}, 'namespace': {'type': 'string', 'pattern': '^OS::Storage::Capabilities::.+$'}, 'visibility': {'type': ['string', 'null']}, 'driver_version': {'type': 'string'}, 'vendor_name': {'type': 'string'}, 'properties': { 'type': 'object', 'properties': { '^.+$': property_info }, }, 'storage_protocol': {'type': 'string'}, 'replication_targets': {'type': 'array'}, 'display_name': {'type': ['string', 'null']} }, 'additionalProperties': False, 'required': ['pool_name', 'volume_backend_name', 'namespace', 'visibility', 'driver_version', 'vendor_name', 'properties', 'storage_protocol', 'replication_targets', 'display_name', 'description'] } } tempest-23.0.0/tempest/lib/api_schema/response/volume/extensions.py000066400000000000000000000031461361143263300255210ustar00rootroot00000000000000# Copyright 2018 ZTE Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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, 'description': {'type': 'string'}, 'links': {'type': 'array'}, 'namespace': {'type': 'string'}, 'alias': {'type': 'string'}, 'name': {'type': 'string'} }, 'additionalProperties': False, 'required': ['updated', 'links', 'alias', 'name', 'description'] } } }, 'additionalProperties': False, 'required': ['extensions'], } } tempest-23.0.0/tempest/lib/api_schema/response/volume/hosts.py000066400000000000000000000061111361143263300244550ustar00rootroot00000000000000# Copyright 2018 ZTE Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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 show_host = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'host': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'resource': { 'type': 'object', 'properties': { 'volume_count': {'type': 'string'}, 'total_volume_gb': {'type': 'string'}, 'total_snapshot_gb': {'type': 'string'}, 'project': {'type': 'string'}, 'host': {'type': 'string'}, 'snapshot_count': {'type': 'string'}, }, 'additionalProperties': False, 'required': ['volume_count', 'total_volume_gb', 'total_snapshot_gb', 'project', 'host', 'snapshot_count'], } }, 'additionalProperties': False, 'required': ['resource'] } } }, 'additionalProperties': False, 'required': ['host'] } } list_hosts = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'hosts': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'service-status': { 'enum': ['available', 'unavailable']}, 'service': {'type': 'string'}, 'zone': {'type': 'string'}, 'service-state': { 'enum': ['enabled', 'disabled']}, 'host_name': {'type': 'string'}, 'last-update': parameter_types.date_time_or_null }, 'additionalProperties': False, 'required': ['service-status', 'service', 'zone', 'service-state', 'host_name', 'last-update'] } } }, 'additionalProperties': False, 'required': ['hosts'] } } tempest-23.0.0/tempest/lib/api_schema/response/volume/manage_snapshot.py000066400000000000000000000036231361143263300264710ustar00rootroot00000000000000# 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.api_schema.response.compute.v2_1 import parameter_types manage_snapshot = { 'status_code': [202], 'response_body': { 'type': 'object', 'properties': { 'snapshot': { 'type': 'object', 'properties': { 'status': {'type': 'string'}, 'size': {'type': 'integer'}, 'metadata': { 'type': 'object', 'patternProperties': { '^.+$': {'type': 'string'} } }, 'name': {'type': ['string', 'null']}, 'volume_id': {'type': 'string', 'format': 'uuid'}, 'created_at': parameter_types.date_time, 'description': {'type': ['string', 'null']}, 'id': {'type': 'string', 'format': 'uuid'}, 'updated_at': parameter_types.date_time_or_null }, 'additionalProperties': False, 'required': ['status', 'size', 'volume_id', 'created_at', 'description', 'id', 'updated_at'] } }, 'additionalProperties': False, 'required': ['snapshot'] } } tempest-23.0.0/tempest/lib/api_schema/response/volume/messages.py000066400000000000000000000037501361143263300251320ustar00rootroot00000000000000# Copyright 2018 ZTE Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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 delete_message = { 'status_code': [204], } common_show_message = { 'type': 'object', 'properties': { 'request_id': {'type': 'string'}, 'message_level': {'type': 'string'}, 'links': parameter_types.links, 'event_id': {'type': 'string'}, 'created_at': parameter_types.date_time, 'guaranteed_until': parameter_types.date_time, 'resource_uuid': {'type': 'string', 'format': 'uuid'}, 'id': {'type': 'string', 'format': 'uuid'}, 'resource_type': {'type': 'string'}, 'user_message': {'type': 'string'}}, 'additionalProperties': False, 'required': ['request_id', 'message_level', 'event_id', 'created_at', 'id', 'user_message'], } show_message = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'message': common_show_message }, 'additionalProperties': False, 'required': ['message'] } } list_messages = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'messages': { 'type': 'array', 'items': common_show_message }, }, 'additionalProperties': False, 'required': ['messages'] } } tempest-23.0.0/tempest/lib/api_schema/response/volume/qos.py000066400000000000000000000075561361143263300241350ustar00rootroot00000000000000# Copyright 2018 ZTE Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. show_qos = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'qos_specs': { 'type': 'object', 'properties': { 'name': {'type': 'string'}, 'id': {'type': 'string', 'format': 'uuid'}, 'consumer': {'type': 'string'}, 'specs': {'type': ['object', 'null']}, }, 'additionalProperties': False, 'required': ['name', 'id', 'specs'] }, 'links': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'href': {'type': 'string', 'format': 'uri'}, 'rel': {'type': 'string'}, }, 'additionalProperties': False, 'required': ['href', 'rel'] } } }, 'additionalProperties': False, 'required': ['qos_specs', 'links'] } } delete_qos = {'status_code': [202]} list_qos = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'qos_specs': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'specs': { 'type': 'object', 'patternProperties': {'^.+$': {'type': 'string'}} }, 'consumer': {'type': 'string'}, 'id': {'type': 'string', 'format': 'uuid'}, 'name': {'type': 'string'} }, 'additionalProperties': False, 'required': ['specs', 'id', 'name'] } } }, 'additionalProperties': False, 'required': ['qos_specs'] } } set_qos_key = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'qos_specs': { 'type': 'object', 'patternProperties': {'^.+$': {'type': 'string'}} }, }, 'additionalProperties': False, 'required': ['qos_specs'] } } unset_qos_key = {'status_code': [202]} associate_qos = {'status_code': [202]} show_association_qos = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'qos_associations': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'association_type': {'type': 'string'}, 'id': {'type': 'string', 'format': 'uuid'}, 'name': {'type': 'string'} }, 'additionalProperties': False, 'required': ['association_type', 'id', 'name'] } }, }, 'additionalProperties': False, 'required': ['qos_associations'] } } disassociate_qos = {'status_code': [202]} disassociate_all_qos = {'status_code': [202]} tempest-23.0.0/tempest/lib/api_schema/response/volume/quotas.py000066400000000000000000000063721361143263300246420ustar00rootroot00000000000000# Copyright 2019 ZTE Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 delete_quota_set = { 'status_code': [200], } quota_usage_info = { 'type': 'object', 'properties': { 'reserved': {'type': 'integer'}, 'allocated': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'in_use': {'type': 'integer'} }, 'additionalProperties': False, # 'allocated' attribute is available only when nested quota is enabled. 'required': ['reserved', 'limit', 'in_use'], } show_quota_set = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'quota_set': { 'type': 'object', 'properties': { 'id': {'type': 'string', 'format': 'uuid'}, 'volumes': {'type': 'integer'}, 'snapshots': {'type': 'integer'}, 'backups': {'type': 'integer'}, 'groups': {'type': 'integer'}, 'per_volume_gigabytes': {'type': 'integer'}, 'gigabytes': {'type': 'integer'}, 'backup_gigabytes': {'type': 'integer'}, }, # for volumes_{volume_type}, etc "additionalProperties": {'type': 'integer'}, 'required': ['id', 'volumes', 'snapshots', 'backups', 'per_volume_gigabytes', 'gigabytes', 'backup_gigabytes', 'groups'], } }, 'required': ['quota_set'] } } update_quota_set = copy.deepcopy(show_quota_set) update_quota_set['response_body']['properties']['quota_set'][ 'required'].remove('id') show_quota_set_usage = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'quota_set': { 'type': 'object', 'properties': { 'id': {'type': 'string', 'format': 'uuid'}, 'volumes': quota_usage_info, 'snapshots': quota_usage_info, 'backups': quota_usage_info, 'groups': quota_usage_info, 'per_volume_gigabytes': quota_usage_info, 'gigabytes': quota_usage_info, 'backup_gigabytes': quota_usage_info, }, # for volumes_{volume_type}, etc "additionalProperties": quota_usage_info, 'required': ['id', 'volumes', 'snapshots', 'backups', 'per_volume_gigabytes', 'gigabytes', 'backup_gigabytes', 'groups'], } }, 'required': ['quota_set'] } } tempest-23.0.0/tempest/lib/api_schema/response/volume/services.py000066400000000000000000000066021361143263300251450ustar00rootroot00000000000000# Copyright 2018 ZTE Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 list_services = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'services': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'binary': {'type': 'string'}, 'disabled_reason': {'type': ['string', 'null']}, 'host': {'type': 'string'}, 'state': {'enum': ['up', 'down']}, 'status': {'enum': ['enabled', 'disabled']}, 'frozen': {'type': 'boolean'}, 'updated_at': parameter_types.date_time, 'zone': {'type': 'string'}, # TODO(zhufl): cluster is added in 3.7, we should move # it to the 3.7 schema file when microversion is # supported in volume interfaces 'cluster': {'type': 'string'}, 'replication_status': {'type': 'string'}, 'active_backend_id': {'type': ['string', 'null']}, 'backend_state': {'type': 'string'}, }, 'additionalProperties': False, 'required': ['binary', 'disabled_reason', 'host', 'state', 'status', 'updated_at', 'zone'] } } }, 'additionalProperties': False, 'required': ['services'] } } enable_service = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'disabled': {'type': 'boolean'}, 'status': {'enum': ['enabled', 'disabled']}, 'host': {'type': 'string'}, 'service': {'type': 'string'}, 'binary': {'type': 'string'}, 'disabled_reason': {'type': ['string', 'null']} }, 'additionalProperties': False, 'required': ['disabled', 'status', 'host', 'service', 'binary', 'disabled_reason'] } } disable_service = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'disabled': {'type': 'boolean'}, 'status': {'enum': ['enabled', 'disabled']}, 'host': {'type': 'string'}, 'service': {'type': 'string'}, 'binary': {'type': 'string'}, }, 'additionalProperties': False, 'required': ['disabled', 'status', 'host', 'service', 'binary'] } } disable_log_reason = copy.deepcopy(enable_service) freeze_host = {'status_code': [200]} thaw_host = {'status_code': [200]} tempest-23.0.0/tempest/lib/api_schema/response/volume/transfers.py000066400000000000000000000101511361143263300253230ustar00rootroot00000000000000# 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.api_schema.response.compute.v2_1 import parameter_types create_volume_transfer = { 'status_code': [202], 'response_body': { 'type': 'object', 'properties': { 'transfer': { 'type': 'object', 'properties': { 'auth_key': {'type': 'string'}, 'links': parameter_types.links, 'created_at': parameter_types.date_time, 'volume_id': {'type': 'string', 'format': 'uuid'}, 'id': {'type': 'string', 'format': 'uuid'}, 'name': {'type': ['string', 'null']} }, 'additionalProperties': False, 'required': ['auth_key', 'links', 'created_at', 'volume_id', 'id', 'name'] } }, 'additionalProperties': False, 'required': ['transfer'] } } common_show_volume_transfer = { 'type': 'object', 'properties': { 'links': parameter_types.links, 'created_at': parameter_types.date_time, 'volume_id': {'type': 'string', 'format': 'uuid'}, 'id': {'type': 'string', 'format': 'uuid'}, 'name': {'type': ['string', 'null']} }, 'additionalProperties': False, 'required': ['links', 'created_at', 'volume_id', 'id', 'name'] } show_volume_transfer = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'transfer': common_show_volume_transfer }, 'additionalProperties': False, 'required': ['transfer'] } } list_volume_transfers_no_detail = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'transfers': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'volume_id': {'type': 'string', 'format': 'uuid'}, 'id': {'type': 'string', 'format': 'uuid'}, 'links': parameter_types.links, 'name': {'type': ['string', 'null']} }, 'additionalProperties': False, 'required': ['volume_id', 'id', 'links', 'name'] } } }, 'additionalProperties': False, 'required': ['transfers'], } } list_volume_transfers_with_detail = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'transfers': { 'type': 'array', 'items': common_show_volume_transfer } }, 'additionalProperties': False, 'required': ['transfers'], } } delete_volume_transfer = {'status_code': [202]} accept_volume_transfer = { 'status_code': [202], 'response_body': { 'type': 'object', 'properties': { 'transfer': { 'type': 'object', 'properties': { 'links': parameter_types.links, 'volume_id': {'type': 'string', 'format': 'uuid'}, 'id': {'type': 'string', 'format': 'uuid'}, 'name': {'type': ['string', 'null']} }, 'additionalProperties': False, 'required': ['links', 'volume_id', 'id', 'name'] } }, 'additionalProperties': False, 'required': ['transfer'] } } tempest-23.0.0/tempest/lib/api_schema/response/volume/versions.py000077500000000000000000000101721361143263300251720ustar00rootroot00000000000000# 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'], } } volume_api_version_details = { 'status_code': [200], '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-23.0.0/tempest/lib/auth.py000066400000000000000000001015001361143263300170160ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/base.py000066400000000000000000000104131361143263300167710ustar00rootroot00000000000000# 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 sys import fixtures import pkg_resources import testtools def _handle_skip_exception(): try: stestr_version = pkg_resources.parse_version( pkg_resources.get_distribution("stestr").version) stestr_min = pkg_resources.parse_version('2.5.0') new_stestr = (stestr_version >= stestr_min) import unittest import unittest2 if sys.version_info >= (3, 5) and new_stestr: testtools.TestCase.skipException = unittest.case.SkipTest else: testtools.TestCase.skipException = unittest2.case.SkipTest except Exception: pass 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 # TODO(gmann): cls.handle_skip_exception is really workaround for # testtools bug- https://github.com/testing-cabal/testtools/issues/272 # stestr which is used by Tempest internally to run the test switch # the customize test runner(which use stdlib unittest) for >=py3.5 # else testtools.run.- https://github.com/mtreinish/stestr/pull/265 # These two test runner are not compatible due to skip exception # handling(due to unittest2). testtools.run treat unittestt.SkipTest # as error and stdlib unittest treat unittest2.case.SkipTest raised # by testtools.TestCase.skipException. # The below workaround can be removed once testtools fix issue# 272. cls.orig_skip_exception = testtools.TestCase.skipException _handle_skip_exception() @classmethod def tearDownClass(cls): if hasattr(super(BaseTestCase, cls), 'tearDownClass'): super(BaseTestCase, cls).tearDownClass() def setUp(self): testtools.TestCase.skipException = self.orig_skip_exception super(BaseTestCase, self).setUp() if not self.setUpClassCalled: raise RuntimeError("setUpClass does not calls the super's " "setUpClass in {!r}".format(type(self))) 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-23.0.0/tempest/lib/cli/000077500000000000000000000000001361143263300162555ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/cli/__init__.py000066400000000000000000000000001361143263300203540ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/cli/base.py000066400000000000000000000440541361143263300175500ustar00rootroot00000000000000# 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) 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 :param identity_api_version: Version of the Identity API :type identity_api_version: 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, identity_api_version=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 self.identity_api_version = identity_api_version 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 += ' --os-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-project-name %s --os-password %s ' '--os-auth-url %s' % (self.username, self.tenant_name, self.password, self.uri)) if self.identity_api_version: creds += ' --os-identity-api-version %s' % ( self.identity_api_version) 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-23.0.0/tempest/lib/cli/output_parser.py000066400000000000000000000113241361143263300215440ustar00rootroot00000000000000# 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(r'^\+\-[\+\-]+\-\+$') 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-23.0.0/tempest/lib/cmd/000077500000000000000000000000001361143263300162515ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/cmd/__init__.py000066400000000000000000000000001361143263300203500ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/cmd/check_uuid.py000077500000000000000000000331151361143263300207340ustar00rootroot00000000000000#!/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, _, 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-23.0.0/tempest/lib/cmd/skip_tracker.py000077500000000000000000000130751361143263300213150ustar00rootroot00000000000000#!/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-23.0.0/tempest/lib/common/000077500000000000000000000000001361143263300167765ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/common/__init__.py000066400000000000000000000000001361143263300210750ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/common/api_microversion_fixture.py000066400000000000000000000066631361143263300245010ustar00rootroot00000000000000# Copyright 2019 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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.placement import base_placement_client from tempest.lib.services.volume import base_client as base_volume_client class APIMicroversionFixture(fixtures.Fixture): """API Microversion Fixture to set service microversion. This class provides the fixture to set and reset the microversion on service client. Service client has global variable to set the microversion for that service API request. For example: base_compute_client.COMPUTE_MICROVERSION Global variable is always risky to set directly which can affect the other test's API request also. This class provides a way to reset the service microversion once test finish the API request. This class can be used with useFixture: Example:: def setUp(self): super(BaseV2ComputeTest, self).setUp() self.useFixture(api_microversion_fixture.APIMicroversionFixture( compute_microversion=self.compute_request_microversion)) Or you can set microversion on multiple services together:: def setUp(self): super(ScenarioTest, self).setUp() self.useFixture(api_microversion_fixture.APIMicroversionFixture( compute_microversion=self.compute_request_microversion, volume_microversion=self.volume_request_microversion)) Current supported services: - Compute - Volume - Placement :param str compute_microversion: microvesion to be set on compute service clients :param str volume_microversion: microvesion to be set on volume service clients :param str placement_microversion: microvesion to be set on placement service clients """ def __init__(self, compute_microversion=None, volume_microversion=None, placement_microversion=None): self.compute_microversion = compute_microversion self.volume_microversion = volume_microversion self.placement_microversion = placement_microversion def _setUp(self): super(APIMicroversionFixture, self)._setUp() if self.compute_microversion: base_compute_client.COMPUTE_MICROVERSION = ( self.compute_microversion) if self.volume_microversion: base_volume_client.VOLUME_MICROVERSION = self.volume_microversion if self.placement_microversion: base_placement_client.PLACEMENT_MICROVERSION = ( self.placement_microversion) self.addCleanup(self._reset_microversion) def _reset_microversion(self): base_compute_client.COMPUTE_MICROVERSION = None base_volume_client.VOLUME_MICROVERSION = None base_placement_client.PLACEMENT_MICROVERSION = None tempest-23.0.0/tempest/lib/common/api_version_request.py000066400000000000000000000132271361143263300234430ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/common/api_version_utils.py000066400000000000000000000177151361143263300231210ustar00rootroot00000000000000# 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_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", type str. :param response_header: Response header where microversion is expected to be present. """ if not isinstance(api_microversion, six.string_types): raise TypeError('api_microversion must be a string') 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-23.0.0/tempest/lib/common/cred_client.py000066400000000000000000000203371361143263300216300ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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-23.0.0/tempest/lib/common/cred_provider.py000066400000000000000000000067111361143263300222040ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/common/dynamic_creds.py000066400000000000000000000540261361143263300221630ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/common/fixed_network.py000066400000000000000000000124671361143263300222320ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 network.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'] # NOTE(zhufl) compute networks_client uses 'label' as network name field, # while neutron networks_client uses 'name' as network name field. try: networks = [n for n in networks if n['label'] == name] except KeyError: networks = [n for n in networks if n['name'] == 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-23.0.0/tempest/lib/common/http.py000066400000000000000000000110121361143263300203220ustar00rootroot00000000000000# 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, follow_redirects=True): self.follow_redirects = follow_redirects 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) if self.follow_redirects: # 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) else: # Do not follow redirections. Don't raise an exception if # a redirect is found, but return the HTTP 3XX response instead. retry = urllib3.util.Retry(redirect=False) 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, follow_redirects=True): self.follow_redirects = follow_redirects 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) if self.follow_redirects: # 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) else: # Do not follow redirections. Don't raise an exception if # a redirect is found, but return the HTTP 3XX response instead. retry = urllib3.util.Retry(redirect=False) r = super(ClosingHttp, self).request(method, url, retries=retry, *args, **new_kwargs) return Response(r), r.data tempest-23.0.0/tempest/lib/common/jsonschema_validator.py000066400000000000000000000037261361143263300235570ustar00rootroot00000000000000# 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_serialization import base64 from oslo_utils import timeutils import six # 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 @jsonschema.FormatChecker.cls_checks('base64') def _validate_base64_format(instance): try: if isinstance(instance, six.text_type): instance = instance.encode('utf-8') base64.decode_as_bytes(instance) except TypeError: # The name must be string type. If instance isn't string type, the # TypeError will be raised at here. return False return True tempest-23.0.0/tempest/lib/common/preprov_creds.py000066400000000000000000000402431361143263300222300ustar00rootroot00000000000000# 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) networks_client = net_clients.network.NetworksClient() net_name = self.hash_dict['networks'].get(hash, None) try: network = fixed_network.get_network_from_name( net_name, networks_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-23.0.0/tempest/lib/common/profiler.py000066400000000000000000000035461361143263300212020ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 hashlib import hmac import json from oslo_utils import encodeutils from oslo_utils import uuidutils _profiler = {} def enable(profiler_key, trace_id=None): """Enable global profiler instance :param profiler_key: the secret key used to enable profiling in services :param trace_id: unique id of the trace, if empty the id is generated automatically """ _profiler['key'] = profiler_key _profiler['uuid'] = trace_id or uuidutils.generate_uuid() def disable(): """Disable global profiler instance""" _profiler.clear() def serialize_as_http_headers(): """Serialize profiler state as HTTP headers This function corresponds to the one from osprofiler library. :return: dictionary with 2 keys `X-Trace-Info` and `X-Trace-HMAC`. """ p = _profiler if not p: # profiler is not enabled return {} info = {'base_id': p['uuid'], 'parent_id': p['uuid']} trace_info = base64.urlsafe_b64encode( encodeutils.to_utf8(json.dumps(info))) trace_hmac = _sign(trace_info, p['key']) return { 'X-Trace-Info': trace_info, 'X-Trace-HMAC': trace_hmac, } def _sign(trace_info, key): h = hmac.new(encodeutils.to_utf8(key), digestmod=hashlib.sha1) h.update(trace_info) return h.hexdigest() tempest-23.0.0/tempest/lib/common/rest_client.py000066400000000000000000001253261361143263300216740ustar00rootroot00000000000000# 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 import profiler 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. :param bool follow_redirects: Set to false to stop following redirects. """ # 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, follow_redirects=True): 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, follow_redirects=follow_redirects) else: self.http_obj = http.ClosingHttp( disable_ssl_certificate_validation=dscv, ca_certs=ca_certs, timeout=http_timeout, follow_redirects=follow_redirects) 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' headers = {'Content-Type': 'application/%s' % send_type, 'Accept': 'application/%s' % accept_type} headers.update(profiler.serialize_as_http_headers()) return headers 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 get 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 delete 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 patch 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 put 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 head 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 copy 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 an 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'] = '' if 'X-Subject-Token' in req_headers: req_headers['X-Subject-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) resp, resp_body = self.raw_request( req_url, method, headers=req_headers, body=req_body, chunked=chunked ) # 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, log_req_body=None): """Send a raw HTTP request without the keystone catalog or auth This method sends a HTTP request in the same manner as the request() method, however it does so without using keystone auth or the catalog to determine the base url. Additionally no response handling is done the results from the request are just returned. :param str url: Full url to send the request :param str method: The HTTP verb to use for the request :param dict headers: Headers to use for the request. If none are specified, then 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 :param str log_req_body: Whether to log the request body or not. It is default to None which means request body is safe to log otherwise pass any string you want to log in place of request body. For example: '' :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() # Do the actual request, and time it start = time.time() self._log_request_start(method, url) resp, resp_body = self.http_obj.request( url, method, headers=headers, body=body, chunked=chunked) end = time.time() req_body = body if log_req_body is None else log_req_body self._log_request(method, url, resp, secs=(end - start), req_headers=headers, req_body=req_body, resp_body=resp_body) return resp, resp_body 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 specified, then 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 retry-after is not in the response body or its retry operation exceeds the limits defined by the server :raises RateLimitExceeded: If a 413 response code is received and retry-after is in the response body and its retry operation does not exceeds the limits defined by the server :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-23.0.0/tempest/lib/common/ssh.py000066400000000000000000000216511361143263300201520ustar00rootroot00000000000000# 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() ssh.close() 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-23.0.0/tempest/lib/common/thread.py000066400000000000000000000017431361143263300206240ustar00rootroot00000000000000# Copyright 2018 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 make disable relative module import from __future__ import absolute_import import six if six.PY2: # module thread is removed in Python 3 from thread import get_ident # noqa: H237,F401 else: # On Python3 thread module has been deprecated and get_ident has been moved # to threading module from threading import get_ident # noqa: F401 tempest-23.0.0/tempest/lib/common/utils/000077500000000000000000000000001361143263300201365ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/common/utils/__init__.py000066400000000000000000000000001361143263300222350ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/common/utils/data_utils.py000066400000000000000000000127541361143263300226520ustar00rootroot00000000000000# 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 """ rand_name = str(random.randint(1, 0x7fffffff)) 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-23.0.0/tempest/lib/common/utils/linux/000077500000000000000000000000001361143263300212755ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/common/utils/linux/__init__.py000066400000000000000000000000001361143263300233740ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/common/utils/linux/remote_client.py000066400000000000000000000143441361143263300245060ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 re 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""" @functools.wraps(function) 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 = ('Executing command on %(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) def mount_config_drive(self): """Mount the config drive inside a virtual machine This method will not unmount the config drive, so unmount_config_drive must be used for cleanup. """ cmd_blkid = 'blkid | grep -i config-2' result = self.exec_command(cmd_blkid) dev_name = re.match('([^:]+)', result).group() try: self.exec_command('sudo mount %s /mnt' % dev_name) except tempest.lib.exceptions.SSHExecCommandFailed: # So the command failed, let's try to know why and print some # useful information. lsblk = self.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 def unmount_config_drive(self): self.exec_command('sudo umount /mnt') tempest-23.0.0/tempest/lib/common/utils/misc.py000066400000000000000000000016031361143263300214430ustar00rootroot00000000000000# 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. 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 tempest-23.0.0/tempest/lib/common/utils/test_utils.py000066400000000000000000000101351361143263300227070ustar00rootroot00000000000000# 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 func_name = getattr(func, '__name__', getattr(func.__class__, '__name__')) while now < timeout: if func(*args, **kwargs): LOG.debug("Call %s returns true in %f seconds", func_name, time.time() - begin_time) return True time.sleep(sleep_for) now = time.time() LOG.debug("Call %s returns false in %f seconds", func_name, duration) return False tempest-23.0.0/tempest/lib/common/validation_resources.py000066400000000000000000000530301361143263300235750ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/decorators.py000066400000000000000000000167711361143263300202410ustar00rootroot00000000000000# 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 from oslo_log import log as logging import six import testtools from tempest.lib import exceptions as lib_exc LOG = logging.getLogger(__name__) _SUPPORTED_BUG_TYPES = { 'launchpad': 'https://launchpad.net/bugs/%s', 'storyboard': 'https://storyboard.openstack.org/#!/story/%s', } def _validate_bug_and_bug_type(bug, bug_type): """Validates ``bug`` and ``bug_type`` values. :param bug: bug number causing the test to skip (launchpad or storyboard) :param bug_type: 'launchpad' or 'storyboard', default 'launchpad' :raises: InvalidParam if ``bug`` is not a digit or ``bug_type`` is not a valid value """ if not bug.isdigit(): invalid_param = '%s must be a valid %s number' % (bug, bug_type) raise lib_exc.InvalidParam(invalid_param=invalid_param) if bug_type not in _SUPPORTED_BUG_TYPES: invalid_param = 'bug_type "%s" must be one of: %s' % ( bug_type, ', '.join(_SUPPORTED_BUG_TYPES.keys())) raise lib_exc.InvalidParam(invalid_param=invalid_param) def _get_bug_url(bug, bug_type='launchpad'): """Get the bug URL based on the ``bug_type`` and ``bug`` :param bug: The launchpad/storyboard bug number causing the test :param bug_type: 'launchpad' or 'storyboard', default 'launchpad' :returns: Bug URL corresponding to ``bug_type`` value """ _validate_bug_and_bug_type(bug, bug_type) return _SUPPORTED_BUG_TYPES[bug_type] % bug def skip_because(*args, **kwargs): """A decorator useful to skip tests hitting known bugs ``bug`` must be a number and ``condition`` must be true for the test to skip. :param bug: bug number causing the test to skip (launchpad or storyboard) :param bug_type: 'launchpad' or 'storyboard', default 'launchpad' :param condition: optional condition to be True for the skip to have place :raises: testtools.TestCase.skipException if ``condition`` is True and ``bug`` is included """ def decorator(f): @functools.wraps(f) def wrapper(*func_args, **func_kwargs): skip = False msg = '' if "condition" in kwargs: if kwargs["condition"] is True: skip = True else: skip = True if "bug" in kwargs and skip is True: bug = kwargs['bug'] bug_type = kwargs.get('bug_type', 'launchpad') bug_url = _get_bug_url(bug, bug_type) msg = "Skipped until bug: %s is resolved." % bug_url raise testtools.TestCase.skipException(msg) return f(*func_args, **func_kwargs) return wrapper return decorator def related_bug(bug, status_code=None, bug_type='launchpad'): """A decorator useful to know solutions from launchpad/storyboard reports :param bug: The launchpad/storyboard bug number causing the test bug :param bug_type: 'launchpad' or 'storyboard', default 'launchpad' :param status_code: The status code related to the bug report """ def decorator(f): @functools.wraps(f) def wrapper(*func_args, **func_kwargs): try: return f(*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: if bug: LOG.error('Hints: This test was made for the bug_type ' '%s. The failure could be related to ' '%s', bug, _get_bug_url(bug, bug_type)) 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 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. :param condition: Optional condition which if true will apply the attr. If a condition is specified which is false the attr will not be applied to the test function. If not specified, the attr is always applied. """ def decorator(f): # Check to see if the attr should be conditional applied. if 'condition' in kwargs and not kwargs.get('condition'): return f if 'type' in kwargs and isinstance(kwargs['type'], six.string_types): 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 def unstable_test(*args, **kwargs): """A decorator useful to run tests hitting known bugs and skip it if fails This decorator can be used in cases like: * We have skipped tests with some bug and now bug is claimed to be fixed. Now we want to check the test stability so we use this decorator. The number of skipped cases with that bug can be counted to mark test stable again. * There is test which is failing often, but not always. If there is known bug related to it, and someone is working on fix, this decorator can be used instead of "skip_because". That will ensure that test is still run so new debug data can be collected from jobs' logs but it will not make life of other developers harder by forcing them to recheck jobs more often. ``bug`` must be a number for the test to skip. :param bug: bug number causing the test to skip (launchpad or storyboard) :param bug_type: 'launchpad' or 'storyboard', default 'launchpad' :raises: testtools.TestCase.skipException if test actually fails, and ``bug`` is included """ def decor(f): @functools.wraps(f) def inner(self, *func_args, **func_kwargs): try: return f(self, *func_args, **func_kwargs) except Exception as e: if "bug" in kwargs: bug = kwargs['bug'] bug_type = kwargs.get('bug_type', 'launchpad') bug_url = _get_bug_url(bug, bug_type) msg = ("Marked as unstable and skipped because of bug: " "%s, failure was: %s") % (bug_url, e) raise testtools.TestCase.skipException(msg) else: raise e return inner return decor tempest-23.0.0/tempest/lib/exceptions.py000066400000000000000000000201101361143263300202330ustar00rootroot00000000000000# 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 = "Conflict with state of target resource" 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-23.0.0/tempest/lib/services/000077500000000000000000000000001361143263300173315ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/services/__init__.py000066400000000000000000000000001361143263300214300ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/services/clients.py000066400000000000000000000515761361143263300213620ustar00rootroot00000000000000# 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 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 placement from tempest.lib.services import volume 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, 'placement': placement, '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(credentials) # 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: msg = "Zero or %d ambiguous auth provider found. identity: %s, " \ "credentials: %s" % (len(identity), identity, credentials) raise exceptions.InvalidCredentials(msg) 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-23.0.0/tempest/lib/services/compute/000077500000000000000000000000001361143263300210055ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/services/compute/__init__.py000066400000000000000000000100501361143263300231120ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/compute/agents_client.py000066400000000000000000000057041361143263300242040ustar00rootroot00000000000000# 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://docs.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://docs.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://docs.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://docs.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-23.0.0/tempest/lib/services/compute/aggregates_client.py000066400000000000000000000132571361143263300250360ustar00rootroot00000000000000# 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.api_schema.response.compute.v2_41 \ import aggregates as schemav241 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): schema_versions_info = [ {'min': None, 'max': '2.40', 'schema': schema}, {'min': '2.41', 'max': None, 'schema': schemav241}] def list_aggregates(self): """Get aggregate list.""" resp, body = self.get("os-aggregates") body = json.loads(body) schema = self.get_schema(self.schema_versions_info) 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) schema = self.get_schema(self.schema_versions_info) 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://docs.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) schema = self.get_schema(self.schema_versions_info) 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://docs.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) schema = self.get_schema(self.schema_versions_info) 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) schema = self.get_schema(self.schema_versions_info) 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://docs.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) schema = self.get_schema(self.schema_versions_info) 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://docs.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) schema = self.get_schema(self.schema_versions_info) 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://docs.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) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.aggregate_set_metadata, resp, body) return rest_client.ResponseBody(resp, body) tempest-23.0.0/tempest/lib/services/compute/availability_zone_client.py000066400000000000000000000025541361143263300264300ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/compute/baremetal_nodes_client.py000066400000000000000000000041011361143263300260350ustar00rootroot00000000000000# 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://docs.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://docs.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-23.0.0/tempest/lib/services/compute/base_compute_client.py000066400000000000000000000073671361143263300254000ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/compute/certificates_client.py000066400000000000000000000027471361143263300253740ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/compute/extensions_client.py000066400000000000000000000025371361143263300251230ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/compute/fixed_ips_client.py000066400000000000000000000032561361143263300246750ustar00rootroot00000000000000# 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://docs.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-23.0.0/tempest/lib/services/compute/flavors_client.py000066400000000000000000000252761361143263300244050ustar00rootroot00000000000000# 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.api_schema.response.compute.v2_55 import flavors \ as schemav255 from tempest.lib.api_schema.response.compute.v2_61 import flavors \ as schemav261 from tempest.lib.common import rest_client from tempest.lib.services.compute import base_compute_client class FlavorsClient(base_compute_client.BaseComputeClient): schema_versions_info = [ {'min': None, 'max': '2.54', 'schema': schema}, {'min': '2.55', 'max': '2.60', 'schema': schemav255}, {'min': '2.61', 'max': None, 'schema': schemav261}] def list_flavors(self, detail=False, **params): """Lists flavors. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/compute/#list-flavors https://docs.openstack.org/api-ref/compute/#list-flavors-with-details """ url = 'flavors' schema = self.get_schema(self.schema_versions_info) if detail: url += '/detail' _schema = schema.list_flavors_details else: _schema = schema.list_flavors 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://docs.openstack.org/api-ref/compute/#show-flavor-details """ resp, body = self.get("flavors/%s" % flavor_id) body = json.loads(body) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.create_update_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://docs.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) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.create_update_get_flavor_details, resp, body) return rest_client.ResponseBody(resp, body) def update_flavor(self, flavor_id, **kwargs): """Uodate the flavor or instance type. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/compute/#update-flavor-description """ put_body = json.dumps({'flavor': kwargs}) resp, body = self.put("flavors/%s" % flavor_id, put_body) body = json.loads(body) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.create_update_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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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_extra_specs.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://docs.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://docs.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://docs.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-23.0.0/tempest/lib/services/compute/floating_ip_pools_client.py000066400000000000000000000025521361143263300264300ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/compute/floating_ips_bulk_client.py000066400000000000000000000041711361143263300264130ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/compute/floating_ips_client.py000066400000000000000000000110501361143263300253700ustar00rootroot00000000000000# 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://docs.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://docs.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://docs.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://docs.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-23.0.0/tempest/lib/services/compute/hosts_client.py000066400000000000000000000100651361143263300240570ustar00rootroot00000000000000# 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://docs.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://docs.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-23.0.0/tempest/lib/services/compute/hypervisor_client.py000066400000000000000000000073361361143263300251400ustar00rootroot00000000000000# 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 schemav21 from tempest.lib.api_schema.response.compute.v2_28 \ import hypervisors as schemav228 from tempest.lib.common import rest_client from tempest.lib.services.compute import base_compute_client class HypervisorClient(base_compute_client.BaseComputeClient): schema_versions_info = [ {'min': None, 'max': '2.27', 'schema': schemav21}, {'min': '2.28', 'max': None, 'schema': schemav228}] def list_hypervisors(self, detail=False): """List hypervisors information.""" url = 'os-hypervisors' schema = self.get_schema(self.schema_versions_info) _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) schema = self.get_schema(self.schema_versions_info) 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) schema = self.get_schema(self.schema_versions_info) 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) schema = self.get_schema(self.schema_versions_info) 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) schema = self.get_schema(self.schema_versions_info) 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) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.list_search_hypervisors, resp, body) return rest_client.ResponseBody(resp, body) tempest-23.0.0/tempest/lib/services/compute/images_client.py000066400000000000000000000144551361143263300241730ustar00rootroot00000000000000# 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.api_schema.response.compute.v2_45 import images as schemav245 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): schema_versions_info = [ {'min': None, 'max': '2.44', 'schema': schema}, {'min': '2.45', 'max': None, 'schema': schemav245}] 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://docs.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) _schema = self.get_schema(self.schema_versions_info) if body: body = json.loads(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://docs.openstack.org/api-ref/compute/#list-images https://docs.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://docs.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://docs.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://docs.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-23.0.0/tempest/lib/services/compute/instance_usage_audit_log_client.py000066400000000000000000000031021361143263300277300ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/compute/interfaces_client.py000066400000000000000000000047031361143263300250440ustar00rootroot00000000000000# 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://docs.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-23.0.0/tempest/lib/services/compute/keypairs_client.py000066400000000000000000000071631361143263300245530ustar00rootroot00000000000000# 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://docs.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://docs.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://docs.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://docs.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-23.0.0/tempest/lib/services/compute/limits_client.py000066400000000000000000000032571361143263300242250ustar00rootroot00000000000000# 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 schemav21 from tempest.lib.api_schema.response.compute.v2_36 import limits as schemav236 from tempest.lib.api_schema.response.compute.v2_39 import limits as schemav239 from tempest.lib.api_schema.response.compute.v2_57 import limits as schemav257 from tempest.lib.common import rest_client from tempest.lib.services.compute import base_compute_client class LimitsClient(base_compute_client.BaseComputeClient): schema_versions_info = [ {'min': None, 'max': '2.35', 'schema': schemav21}, {'min': '2.36', 'max': '2.38', 'schema': schemav236}, {'min': '2.39', 'max': '2.56', 'schema': schemav239}, {'min': '2.57', 'max': None, 'schema': schemav257}] def show_limits(self): resp, body = self.get("limits") body = json.loads(body) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.get_limit, resp, body) return rest_client.ResponseBody(resp, body) tempest-23.0.0/tempest/lib/services/compute/migrations_client.py000066400000000000000000000033761361143263300251020ustar00rootroot00000000000000# 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://docs.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-23.0.0/tempest/lib/services/compute/networks_client.py000066400000000000000000000024161361143263300245740ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/compute/quota_classes_client.py000066400000000000000000000037431361143263300255720ustar00rootroot00000000000000# 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. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/compute/#create-or-update-quotas-for-quota-class """ 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-23.0.0/tempest/lib/services/compute/quotas_client.py000066400000000000000000000077671361143263300242520ustar00rootroot00000000000000# 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.api_schema.response.compute.v2_36 import quotas as schemav236 from tempest.lib.api_schema.response.compute.v2_57 import quotas as schemav257 from tempest.lib.common import rest_client from tempest.lib.services.compute import base_compute_client class QuotasClient(base_compute_client.BaseComputeClient): schema_versions_info = [ {'min': None, 'max': '2.35', 'schema': schema}, {'min': '2.36', 'max': '2.56', 'schema': schemav236}, {'min': '2.57', 'max': None, 'schema': schemav257}] 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: https://docs.openstack.org/api-ref/compute/#show-a-quota https://docs.openstack.org/api-ref/compute/#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) schema = self.get_schema(self.schema_versions_info) 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. https://docs.openstack.org/api-ref/compute/#list-default-quotas-for-tenant """ url = 'os-quota-sets/%s/defaults' % tenant_id resp, body = self.get(url) body = json.loads(body) schema = self.get_schema(self.schema_versions_info) 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://docs.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) schema = self.get_schema(self.schema_versions_info) 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. https://docs.openstack.org/api-ref/compute/#revert-quotas-to-defaults """ resp, body = self.delete('os-quota-sets/%s' % tenant_id) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.delete_quota, resp, body) return rest_client.ResponseBody(resp, body) tempest-23.0.0/tempest/lib/services/compute/security_group_default_rules_client.py000066400000000000000000000056501361143263300307240ustar00rootroot00000000000000# 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://docs.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-23.0.0/tempest/lib/services/compute/security_group_rules_client.py000066400000000000000000000035701361143263300272170ustar00rootroot00000000000000# 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://docs.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-23.0.0/tempest/lib/services/compute/security_groups_client.py000066400000000000000000000101121361143263300261560ustar00rootroot00000000000000# 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://docs.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://docs.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://docs.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://docs.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://docs.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-23.0.0/tempest/lib/services/compute/server_groups_client.py000066400000000000000000000055701361143263300256310ustar00rootroot00000000000000# 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 server_groups \ as schema from tempest.lib.api_schema.response.compute.v2_13 import server_groups \ 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://docs.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-23.0.0/tempest/lib/services/compute/servers_client.py000066400000000000000000001112211361143263300244040ustar00rootroot00000000000000# 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_54 import servers as schemav254 from tempest.lib.api_schema.response.compute.v2_57 import servers as schemav257 from tempest.lib.api_schema.response.compute.v2_6 import servers as schemav26 from tempest.lib.api_schema.response.compute.v2_63 import servers as schemav263 from tempest.lib.api_schema.response.compute.v2_70 import servers as schemav270 from tempest.lib.api_schema.response.compute.v2_71 import servers as schemav271 from tempest.lib.api_schema.response.compute.v2_8 import servers as schemav28 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.7', 'schema': schemav26}, {'min': '2.8', 'max': '2.8', 'schema': schemav28}, {'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': '2.53', 'schema': schemav248}, {'min': '2.54', 'max': '2.56', 'schema': schemav254}, {'min': '2.57', 'max': '2.62', 'schema': schemav257}, {'min': '2.63', 'max': '2.69', 'schema': schemav263}, {'min': '2.70', 'max': '2.70', 'schema': schemav270}, {'min': '2.71', 'max': None, 'schema': schemav271}] 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: https://docs.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://docs.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: https://docs.openstack.org/api-ref/compute/#show-server-details """ 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://docs.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://docs.openstack.org/api-ref/compute/#list-servers https://docs.openstack.org/api-ref/compute/#list-servers-detailed """ url = 'servers' schema = self.get_schema(self.schema_versions_info) if detail: url += '/detail' _schema = schema.list_servers_detail else: _schema = schema.list_servers 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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.openstack.org/api-ref/compute/#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://docs.openstack.org/api-ref/compute/#create-or-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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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) schema = self.get_schema(self.schema_versions_info) 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 For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/compute/#update-a-volume-attachment """ 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://docs.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://docs.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) schema = self.get_schema(self.schema_versions_info) 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://docs.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) schema = self.get_schema(self.schema_versions_info) 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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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: https://docs.openstack.org/api-ref/compute/#create-remote-console """ 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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.openstack.org/api-ref/compute/#get-vnc-console-os-getvncconsole-action-deprecated """ 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://docs.openstack.org/api-ref/compute/#add-associate-fixed-ip-addfixedip-action-deprecated """ 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://docs.openstack.org/api-ref/compute/#remove-disassociate-fixed-ip-removefixedip-action-deprecated """ 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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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-23.0.0/tempest/lib/services/compute/services_client.py000066400000000000000000000126501361143263300245440ustar00rootroot00000000000000# 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.api_schema.response.compute.v2_53 import services \ as schemav253 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': '2.52', 'schema': schemav211}, {'min': '2.53', 'max': None, 'schema': schemav253}] 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://docs.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 update_service(self, service_id, **kwargs): """Update a compute service. Update a compute service to enable or disable scheduling, including recording a reason why a compute service was disabled from scheduling. This API is available starting with microversion 2.53. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/compute/#update-compute-service """ put_body = json.dumps(kwargs) resp, body = self.put('os-services/%s' % service_id, put_body) body = json.loads(body) _schema = self.get_schema(self.schema_versions_info) self.validate_response(_schema.update_service, resp, body) return rest_client.ResponseBody(resp, body) def enable_service(self, **kwargs): """Enable service on a host. ``update_service`` supersedes this API starting with microversion 2.53. For a full list of available parameters, please refer to the official API reference: https://docs.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. ``update_service`` supersedes this API starting with microversion 2.53. For a full list of available parameters, please refer to the official API reference: https://docs.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. ``update_service`` supersedes this API starting with microversion 2.53. For a full list of available parameters, please refer to the official API reference: https://docs.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. ``update_service`` supersedes this API starting with microversion 2.53. For a full list of available parameters, please refer to the official API reference: https://docs.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-23.0.0/tempest/lib/services/compute/snapshots_client.py000066400000000000000000000060101361143263300247340ustar00rootroot00000000000000# 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://docs.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://docs.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-23.0.0/tempest/lib/services/compute/tenant_networks_client.py000066400000000000000000000027221361143263300261450ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/compute/tenant_usages_client.py000066400000000000000000000042201361143263300255530ustar00rootroot00000000000000# 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://docs.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://docs.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-23.0.0/tempest/lib/services/compute/versions_client.py000066400000000000000000000036371361143263300245760ustar00rootroot00000000000000# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils as json from 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() resp, body = self.raw_request(version_url, 'GET') 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-23.0.0/tempest/lib/services/compute/volumes_client.py000066400000000000000000000066071361143263300244200ustar00rootroot00000000000000# 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://docs.openstack.org/api-ref/compute/#list-volumes https://docs.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://docs.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://docs.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://docs.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-23.0.0/tempest/lib/services/identity/000077500000000000000000000000001361143263300211625ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/services/identity/__init__.py000066400000000000000000000013361361143263300232760ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/identity/v2/000077500000000000000000000000001361143263300215115ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/services/identity/v2/__init__.py000066400000000000000000000023721361143263300236260ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/identity/v2/endpoints_client.py000066400000000000000000000034071361143263300254300ustar00rootroot00000000000000# 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://docs.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-23.0.0/tempest/lib/services/identity/v2/identity_client.py000066400000000000000000000051341361143263300252550ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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://docs.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-23.0.0/tempest/lib/services/identity/v2/roles_client.py000066400000000000000000000106741361143263300245550ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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://docs.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://docs.openstack.org/api-ref/identity/v2-ext/index.html#show-a-role OR https://docs.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://docs.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://docs.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://docs.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://docs.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-23.0.0/tempest/lib/services/identity/v2/services_client.py000066400000000000000000000045741361143263300252560ustar00rootroot00000000000000# 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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/identity/v2/tenants_client.py000066400000000000000000000075231361143263300251040ustar00rootroot00000000000000# 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: https://docs.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://docs.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://docs.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: https://docs.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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/identity/v2/token_client.py000066400000000000000000000120611361143263300245410ustar00rootroot00000000000000# 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, log_req_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-23.0.0/tempest/lib/services/identity/v2/users_client.py000066400000000000000000000153111361143263300245630ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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: https://docs.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: https://docs.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://docs.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://docs.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: https://docs.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://docs.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-23.0.0/tempest/lib/services/identity/v3/000077500000000000000000000000001361143263300215125ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/services/identity/v3/__init__.py000066400000000000000000000061671361143263300236350ustar00rootroot00000000000000# 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.application_credentials_client import \ ApplicationCredentialsClient 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.project_tags_client import \ ProjectTagsClient 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__ = ['ApplicationCredentialsClient', 'CatalogClient', 'CredentialsClient', 'DomainsClient', 'DomainConfigurationClient', 'EndPointGroupsClient', 'EndPointsClient', 'EndPointsFilterClient', 'GroupsClient', 'IdentityClient', 'InheritedRolesClient', 'OAUTHConsumerClient', 'OAUTHTokenClient', 'PoliciesClient', 'ProjectsClient', 'ProjectTagsClient', 'RegionsClient', 'RoleAssignmentsClient', 'RolesClient', 'ServicesClient', 'V3TokenClient', 'TrustsClient', 'UsersClient', 'VersionsClient'] tempest-23.0.0/tempest/lib/services/identity/v3/application_credentials_client.py000066400000000000000000000066021361143263300303060ustar00rootroot00000000000000# Copyright 2018 SUSE Linux GmbH # # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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://docs.openstack.org/api-ref/identity/v3/index.html#application-credentials """ from oslo_serialization import jsonutils as json from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client class ApplicationCredentialsClient(rest_client.RestClient): api_version = "v3" def create_application_credential(self, user_id, **kwargs): """Creates an application credential. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/identity/v3/index.html#create-application-credential """ post_body = json.dumps({'application_credential': kwargs}) resp, body = self.post('users/%s/application_credentials' % user_id, post_body) self.expected_success(201, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_application_credential(self, user_id, application_credential_id): """Gets details of an application credential. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/identity/v3/index.html#show-application-credential-details """ resp, body = self.get('users/%s/application_credentials/%s' % (user_id, application_credential_id)) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_application_credentials(self, user_id, **params): """Lists out all of a user's application credentials. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/identity/v3/index.html#list-application-credentials """ url = 'users/%s/application_credentials' % 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_application_credential(self, user_id, application_credential_id): """Deletes an application credential. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/identity/v3/index.html#delete-application-credential """ resp, body = self.delete('users/%s/application_credentials/%s' % (user_id, application_credential_id)) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) tempest-23.0.0/tempest/lib/services/identity/v3/catalog_client.py000066400000000000000000000020061361143263300250320ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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://docs.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-23.0.0/tempest/lib/services/identity/v3/credentials_client.py000066400000000000000000000066231361143263300257260ustar00rootroot00000000000000# 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://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/identity/v3/domain_configuration_client.py000066400000000000000000000173511361143263300276270ustar00rootroot00000000000000# 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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/identity/v3/domains_client.py000066400000000000000000000061111361143263300250530ustar00rootroot00000000000000# 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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/identity/v3/endpoint_filter_client.py000066400000000000000000000052711361143263300266140ustar00rootroot00000000000000# 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://docs.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-23.0.0/tempest/lib/services/identity/v3/endpoint_groups_client.py000066400000000000000000000062741361143263300266520ustar00rootroot00000000000000# 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://docs.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://docs.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-23.0.0/tempest/lib/services/identity/v3/endpoints_client.py000066400000000000000000000060371361143263300254330ustar00rootroot00000000000000# 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://docs.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: https://docs.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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/identity/v3/groups_client.py000066400000000000000000000103211361143263300247360ustar00rootroot00000000000000# 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://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/identity/v3/identity_client.py000066400000000000000000000046661361143263300252670ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/identity/v3/inherited_roles_client.py000066400000000000000000000151471361143263300266110ustar00rootroot00000000000000# 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): """Check if user has an inherited project role on 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): """Check if group has an inherited project role on 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-23.0.0/tempest/lib/services/identity/v3/oauth_consumers_client.py000066400000000000000000000071061361143263300266440ustar00rootroot00000000000000# 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: https://docs.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: https://docs.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://docs.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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/identity/v3/oauth_token_client.py000066400000000000000000000232411361143263300257440ustar00rootroot00000000000000# 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() path = path.replace('//', '/') 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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/identity/v3/policies_client.py000066400000000000000000000170641361143263300252410ustar00rootroot00000000000000# 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://docs.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: https://docs.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: https://docs.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) def update_policy_association_for_endpoint(self, policy_id, endpoint_id): """Create policy association with endpoint. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/identity/v3-ext/index.html#associate-policy-and-endpoint """ url = "policies/{0}/OS-ENDPOINT-POLICY/endpoints/{1}"\ .format(policy_id, endpoint_id) resp, body = self.put(url, '{}') self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def show_policy_association_for_endpoint(self, policy_id, endpoint_id): """Get policy association of endpoint. API reference: https://docs.openstack.org/api-ref/identity/v3-ext/index.html#verify-a-policy-and-endpoint-association """ url = "policies/{0}/OS-ENDPOINT-POLICY/endpoints/{1}"\ .format(policy_id, endpoint_id) resp, body = self.get(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def delete_policy_association_for_endpoint(self, policy_id, endpoint_id): """Delete policy association with endpoint. API reference: https://docs.openstack.org/api-ref/identity/v3-ext/index.html#delete-a-policy-and-endpoint-association """ url = "policies/{0}/OS-ENDPOINT-POLICY/endpoints/{1}"\ .format(policy_id, endpoint_id) resp, body = self.delete(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def update_policy_association_for_service(self, policy_id, service_id): """Create policy association with service. API reference: https://docs.openstack.org/api-ref/identity/v3-ext/index.html#associate-policy-and-service-type-endpoint """ url = "policies/{0}/OS-ENDPOINT-POLICY/services/{1}"\ .format(policy_id, service_id) resp, body = self.put(url, '{}') self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def show_policy_association_for_service(self, policy_id, service_id): """Get policy association of service. API Reference: https://docs.openstack.org/api-ref/identity/v3-ext/index.html#verify-a-policy-and-service-type-endpoint-association """ url = "policies/{0}/OS-ENDPOINT-POLICY/services/{1}"\ .format(policy_id, service_id) resp, body = self.get(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def delete_policy_association_for_service(self, policy_id, service_id): """Delete policy association with service. API reference: https://docs.openstack.org/api-ref/identity/v3-ext/index.html#delete-a-policy-and-service-type-endpoint-association """ url = "policies/{0}/OS-ENDPOINT-POLICY/services/{1}"\ .format(policy_id, service_id) resp, body = self.delete(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def update_policy_association_for_region_and_service( self, policy_id, service_id, region_id): """Create policy association with service and region. API reference: https://docs.openstack.org/api-ref/identity/v3-ext/index.html#associate-policy-and-service-type-endpoint-in-a-region """ url = "policies/{0}/OS-ENDPOINT-POLICY/services/{1}/regions/{2}"\ .format(policy_id, service_id, region_id) resp, body = self.put(url, '{}') self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def show_policy_association_for_region_and_service( self, policy_id, service_id, region_id): """Get policy association of service and region. API reference: https://docs.openstack.org/api-ref/identity/v3-ext/index.html#verify-a-policy-and-service-type-endpoint-in-a-region-association """ url = "policies/{0}/OS-ENDPOINT-POLICY/services/{1}/regions/{2}"\ .format(policy_id, service_id, region_id) resp, body = self.get(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def delete_policy_association_for_region_and_service( self, policy_id, service_id, region_id): """Delete policy association with service and region. API reference: https://docs.openstack.org/api-ref/identity/v3-ext/index.html#delete-a-policy-and-service-type-endpoint-in-a-region-association """ url = "policies/{0}/OS-ENDPOINT-POLICY/services/{1}/regions/{2}"\ .format(policy_id, service_id, region_id) resp, body = self.delete(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) tempest-23.0.0/tempest/lib/services/identity/v3/project_tags_client.py000066400000000000000000000062241361143263300261120ustar00rootroot00000000000000# Copyright 2018 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 ProjectTagsClient(rest_client.RestClient): api_version = "v3" def update_project_tag(self, project_id, tag): """Updates the specified tag and adds it to the project's list of tags. """ url = 'projects/%s/tags/%s' % (project_id, tag) resp, body = self.put(url, '{}') # NOTE(felipemonteiro): This API endpoint returns 201 AND an empty # response body, which is consistent with the spec: # https://specs.openstack.org/openstack/api-wg/guidelines/tags.html#addressing-individual-tags self.expected_success(201, resp.status) return rest_client.ResponseBody(resp, body) def list_project_tags(self, project_id): """List tags for a project.""" url = "projects/%s/tags" % project_id resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_all_project_tags(self, project_id, tags, **kwargs): """Updates all the tags for a project. Any existing tags not specified will be deleted. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/identity/v3/#modify-tag-list-for-a-project """ body = {'tags': tags} if kwargs: body.update(kwargs) put_body = json.dumps(body) resp, body = self.put('projects/%s/tags' % project_id, put_body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def check_project_tag_existence(self, project_id, tag): """Check if a project contains a tag.""" url = 'projects/%s/tags/%s' % (project_id, tag) resp, body = self.get(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def delete_project_tag(self, project_id, tag): """Delete a project tag.""" url = 'projects/%s/tags/%s' % (project_id, tag) resp, body = self.delete(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def delete_all_project_tags(self, project_id): """Delete all tags from a project.""" resp, body = self.delete('projects/%s/tags' % project_id) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) tempest-23.0.0/tempest/lib/services/identity/v3/projects_client.py000066400000000000000000000053341361143263300252600ustar00rootroot00000000000000# 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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/identity/v3/regions_client.py000066400000000000000000000056421361143263300250770ustar00rootroot00000000000000# 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://docs.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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/identity/v3/role_assignments_client.py000066400000000000000000000034121361143263300267760ustar00rootroot00000000000000# 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: https://docs.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-23.0.0/tempest/lib/services/identity/v3/roles_client.py000066400000000000000000000251071361143263300245530ustar00rootroot00000000000000# 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: https://docs.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. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/identity/v3/index.html#list-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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/identity/v3/services_client.py000066400000000000000000000056441361143263300252560ustar00rootroot00000000000000# 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://docs.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: https://docs.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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/identity/v3/token_client.py000066400000000000000000000174161361143263300245530ustar00rootroot00000000000000# 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, app_cred_id=None, app_cred_secret=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 app_cred_id and app_cred_secret: id_obj['methods'].append('application_credential') id_obj['application_credential'] = { 'id': app_cred_id, 'secret': app_cred_secret, } 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, log_req_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-23.0.0/tempest/lib/services/identity/v3/trusts_client.py000066400000000000000000000063061361143263300247730ustar00rootroot00000000000000# 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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/identity/v3/users_client.py000066400000000000000000000106261361143263300245700ustar00rootroot00000000000000# 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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/identity/v3/versions_client.py000066400000000000000000000021641361143263300252750ustar00rootroot00000000000000# 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 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() resp, body = self.raw_request(version_url, 'GET') self._error_checker(resp, body) self.expected_success(300, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) tempest-23.0.0/tempest/lib/services/image/000077500000000000000000000000001361143263300204135ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/services/image/__init__.py000066400000000000000000000013301361143263300225210ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/image/v1/000077500000000000000000000000001361143263300207415ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/services/image/v1/__init__.py000066400000000000000000000014731361143263300230570ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/image/v1/image_members_client.py000066400000000000000000000047671361143263300254630ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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: https://docs.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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/image/v1/images_client.py000066400000000000000000000125071361143263300241230ustar00rootroot00000000000000# 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://docs.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://docs.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: https://docs.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-23.0.0/tempest/lib/services/image/v2/000077500000000000000000000000001361143263300207425ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/services/image/v2/__init__.py000066400000000000000000000031171361143263300230550ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/image/v2/image_members_client.py000066400000000000000000000062751361143263300254600ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/image/v2/images_client.py000066400000000000000000000151051361143263300241210ustar00rootroot00000000000000# 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: https://docs.openstack.org/api-ref/image/v2/#update-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: https://docs.openstack.org/api-ref/image/v2/#create-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: https://docs.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: https://docs.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: https://docs.openstack.org/api-ref/image/v2/#delete-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: https://docs.openstack.org/api-ref/image/v2/#list-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: https://docs.openstack.org/api-ref/image/v2/#show-image """ 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: https://docs.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: https://docs.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, 204, 206], 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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/image/v2/namespace_objects_client.py000066400000000000000000000070751361143263300263300ustar00rootroot00000000000000# 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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/image/v2/namespace_properties_client.py000066400000000000000000000074121361143263300270660ustar00rootroot00000000000000# 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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/image/v2/namespace_tags_client.py000066400000000000000000000120551361143263300256270ustar00rootroot00000000000000# 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: https://docs.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://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/image/v2/namespaces_client.py000066400000000000000000000065611361143263300250010ustar00rootroot00000000000000# 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: https://docs.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: https://docs.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: https://docs.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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/image/v2/resource_types_client.py000066400000000000000000000060771361143263300257370ustar00rootroot00000000000000# 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: https://docs.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: https://docs.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: https://docs.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: https://docs.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-23.0.0/tempest/lib/services/image/v2/schemas_client.py000066400000000000000000000020031361143263300242700ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/image/v2/versions_client.py000066400000000000000000000021641361143263300245250ustar00rootroot00000000000000# 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 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() resp, body = self.raw_request(version_url, 'GET') self._error_checker(resp, body) self.expected_success(300, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) tempest-23.0.0/tempest/lib/services/network/000077500000000000000000000000001361143263300210225ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/services/network/__init__.py000066400000000000000000000050571361143263300231420ustar00rootroot00000000000000# 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.qos_client import QosClient from tempest.lib.services.network.qos_minimum_bandwidth_rules_client import \ QosMinimumBandwidthRulesClient 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.segments_client import SegmentsClient 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', 'QosClient', 'QosMinimumBandwidthRulesClient', 'QuotasClient', 'RoutersClient', 'SecurityGroupRulesClient', 'SecurityGroupsClient', 'SegmentsClient', 'ServiceProvidersClient', 'SubnetpoolsClient', 'SubnetsClient', 'TagsClient'] tempest-23.0.0/tempest/lib/services/network/agents_client.py000066400000000000000000000100611361143263300242110ustar00rootroot00000000000000# 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 an agent. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/#update-agent """ uri = '/agents/%s' % agent_id return self.update_resource(uri, kwargs) def show_agent(self, agent_id, **fields): """Show details for an agent. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/#show-agent-details """ uri = '/agents/%s' % agent_id return self.show_resource(uri, **fields) def delete_agent(self, agent_id): """Delete agent. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#delete-agent """ uri = '/agents/%s' % agent_id return self.delete_resource(uri) def list_agents(self, **filters): """List all agents. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/#list-all-agents """ uri = '/agents' return self.list_resources(uri, **filters) def list_routers_on_l3_agent(self, agent_id): """List routers that an l3 agent hosts. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/#list-routers-hosted-by-an-l3-agent """ uri = '/agents/%s/l3-routers' % agent_id return self.list_resources(uri) def create_router_on_l3_agent(self, agent_id, **kwargs): """Add a router to an l3 agent. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/#schedule-router-to-an-l3-agent """ 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): """Remove a router to an l3 agent. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/#remove-l3-router-from-an-l3-agent """ 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): """Schedule a network to a DHCP agent. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/#schedule-a-network-to-a-dhcp-agent """ uri = '/agents/%s/dhcp-networks' % agent_id return self.create_resource(uri, kwargs, expect_empty_body=True) tempest-23.0.0/tempest/lib/services/network/base.py000066400000000000000000000065341361143263300223160ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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-23.0.0/tempest/lib/services/network/extensions_client.py000066400000000000000000000025631361143263300251370ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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): """Show extension details. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#show-extension-details """ uri = '/extensions/%s' % ext_alias return self.show_resource(uri, **fields) def list_extensions(self, **filters): """List extensions. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#list-extensions """ uri = '/extensions' return self.list_resources(uri, **filters) tempest-23.0.0/tempest/lib/services/network/floating_ips_client.py000066400000000000000000000047521361143263300254200ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/v2/index.html#list-floating-ips """ uri = '/floatingips' return self.list_resources(uri, **filters) tempest-23.0.0/tempest/lib/services/network/metering_label_rules_client.py000066400000000000000000000042211361143263300271140ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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): """Create metering label rule. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#create-metering-label-rule """ 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): """Show metering label rule. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#show-metering-label-rule-details """ 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): """Delete metering label rule.""" uri = '/metering/metering-label-rules/%s' % metering_label_rule_id return self.delete_resource(uri) def list_metering_label_rules(self, **filters): """List metering label rules. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#list-metering-label-rules """ uri = '/metering/metering-label-rules' return self.list_resources(uri, **filters) tempest-23.0.0/tempest/lib/services/network/metering_labels_client.py000066400000000000000000000044561361143263300260770ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/v2/index.html#list-metering-labels """ uri = '/metering/metering-labels' return self.list_resources(uri, **filters) tempest-23.0.0/tempest/lib/services/network/networks_client.py000066400000000000000000000054731361143263300246170ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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-23.0.0/tempest/lib/services/network/ports_client.py000066400000000000000000000057671361143263300241200ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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-23.0.0/tempest/lib/services/network/qos_client.py000066400000000000000000000051061361143263300235360ustar00rootroot00000000000000# Copyright (c) 2019 Ericsson # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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 QosClient(base.BaseNetworkClient): def create_qos_policy(self, **kwargs): """Creates a QoS policy. For full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#create-qos-policy """ uri = '/qos/policies' post_data = {'policy': kwargs} return self.create_resource(uri, post_data) def update_qos_policy(self, qos_policy_id, **kwargs): """Updates a QoS policy. For full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#update-qos-policy """ uri = '/qos/policies/%s' % qos_policy_id post_data = {'policy': kwargs} return self.update_resource(uri, post_data) def show_qos_policy(self, qos_policy_id, **fields): """Show details of a QoS policy. For full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#show-qos-policy-details """ uri = '/qos/policies/%s' % qos_policy_id return self.show_resource(uri, **fields) def delete_qos_policy(self, qos_policy_id): """Deletes a QoS policy. For full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#delete-qos-policy """ uri = '/qos/policies/%s' % qos_policy_id return self.delete_resource(uri) def list_qos_policies(self, **filters): """Lists QoS policies. For full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#list-qos-policies """ uri = '/qos/policies' return self.list_resources(uri, **filters) tempest-23.0.0/tempest/lib/services/network/qos_minimum_bandwidth_rules_client.py000066400000000000000000000062321361143263300305300ustar00rootroot00000000000000# Copyright (c) 2019 Ericsson # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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 QosMinimumBandwidthRulesClient(base.BaseNetworkClient): def create_minimum_bandwidth_rule(self, qos_policy_id, **kwargs): """Creates a minimum bandwidth rule for a QoS policy. For full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#create-minimum-bandwidth-rule """ uri = '/qos/policies/%s/minimum_bandwidth_rules' % qos_policy_id post_data = {'minimum_bandwidth_rule': kwargs} return self.create_resource(uri, post_data) def update_minimum_bandwidth_rule(self, qos_policy_id, rule_id, **kwargs): """Updates a minimum bandwidth rule. For full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#update-minimum-bandwidth-rule """ uri = '/qos/policies/%s/minimum_bandwidth_rules/%s' % ( qos_policy_id, rule_id) post_data = {'minimum_bandwidth_rule': kwargs} return self.update_resource(uri, post_data) def show_minimum_bandwidth_rule(self, qos_policy_id, rule_id, **fields): """Show details of a minimum bandwidth rule. For full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#show-minimum-bandwidth-rule-details """ uri = '/qos/policies/%s/minimum_bandwidth_rules/%s' % ( qos_policy_id, rule_id) return self.show_resource(uri, **fields) def delete_minimum_bandwidth_rule(self, qos_policy_id, rule_id): """Deletes a minimum bandwidth rule for a QoS policy. For full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#delete-minimum-bandwidth-rule """ uri = '/qos/policies/%s/minimum_bandwidth_rules/%s' % ( qos_policy_id, rule_id) return self.delete_resource(uri) def list_minimum_bandwidth_rules(self, qos_policy_id, **filters): """Lists all minimum bandwidth rules for a QoS policy. For full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#list-minimum-bandwidth-rules-for-qos-policy """ uri = '/qos/policies/%s/minimum_bandwidth_rules' % qos_policy_id return self.list_resources(uri, **filters) tempest-23.0.0/tempest/lib/services/network/quotas_client.py000066400000000000000000000047751361143263300242630ustar00rootroot00000000000000# 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): """Update quota for a project. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#update-quota-for-a-project """ 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): """Show quota for a project. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#list-quotas-for-a-project """ uri = '/quotas/%s' % tenant_id return self.show_resource(uri, **fields) def list_quotas(self, **filters): """List quotas for projects with non default quota values. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#list-quotas-for-projects-with-non-default-quota-values """ uri = '/quotas' return self.list_resources(uri, **filters) def show_default_quotas(self, tenant_id): """List default quotas for a project.""" uri = '/quotas/%s/default' % tenant_id return self.show_resource(uri) def show_quota_details(self, tenant_id): """Show quota details for a project.""" uri = '/quotas/%s/details.json' % tenant_id return self.show_resource(uri) tempest-23.0.0/tempest/lib/services/network/routers_client.py000066400000000000000000000063041361143263300244400ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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-23.0.0/tempest/lib/services/network/security_group_rules_client.py000066400000000000000000000042271361143263300272340ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/v2/index.html#list-security-group-rules """ uri = '/security-group-rules' return self.list_resources(uri, **filters) tempest-23.0.0/tempest/lib/services/network/security_groups_client.py000066400000000000000000000057001361143263300262020ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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-23.0.0/tempest/lib/services/network/segments_client.py000066400000000000000000000044421361143263300245630ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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 SegmentsClient(base.BaseNetworkClient): def create_segment(self, **kwargs): """Creates a segment. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#create-segment """ uri = '/segments' post_data = {'segment': kwargs} return self.create_resource(uri, post_data) def update_segment(self, segment_id, **kwargs): """Updates a segment. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#update-segment """ uri = '/segments/%s' % segment_id post_data = {'segment': kwargs} return self.update_resource(uri, post_data) def show_segment(self, segment_id, **fields): """Shows details of a segment. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#show-segment-details """ uri = '/segments/%s' % segment_id return self.show_resource(uri, **fields) def delete_segment(self, segment_id): """Deletes a segment""" uri = '/segments/%s' % segment_id return self.delete_resource(uri) def list_segments(self, **filters): """Lists segments. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#list-segments """ uri = '/segments' return self.list_resources(uri, **filters) tempest-23.0.0/tempest/lib/services/network/service_providers_client.py000066400000000000000000000020311361143263300264630ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#list-service-providers """ uri = '/service-providers' return self.list_resources(uri, **filters) tempest-23.0.0/tempest/lib/services/network/subnetpools_client.py000066400000000000000000000046611361143263300253160ustar00rootroot00000000000000# 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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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-23.0.0/tempest/lib/services/network/subnets_client.py000066400000000000000000000052121361143263300244150ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/v2/index.html#bulk-create-subnet """ uri = '/subnets' return self.create_resource(uri, kwargs) tempest-23.0.0/tempest/lib/services/network/tags_client.py000066400000000000000000000066101361143263300236730ustar00rootroot00000000000000# 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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/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: https://docs.openstack.org/api-ref/network/v2/index.html#obtain-tag-list """ uri = '/%s/%s/tags' % (resource_type, resource_id) return self.list_resources(uri) tempest-23.0.0/tempest/lib/services/network/versions_client.py000066400000000000000000000034371361143263300246110ustar00rootroot00000000000000# 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. from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client from tempest.lib.services.network import base class NetworkVersionsClient(base.BaseNetworkClient): def list_versions(self): """Do a GET / to fetch available API version information. For more information, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#list-api-versions """ # Note: we do a self.get('/') here because we want to use # an unversioned URL, not "v2/$project_id/". resp, body = self.get('/') body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def show_version(self, version): """Do a GET / to fetch available resources. For more information, please refer to the official API reference: https://docs.openstack.org/api-ref/network/v2/index.html#show-api-v2-details """ resp, body = self.get(version + '/') body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) tempest-23.0.0/tempest/lib/services/object_storage/000077500000000000000000000000001361143263300223235ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/services/object_storage/__init__.py000066400000000000000000000022231361143263300244330ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/object_storage/account_client.py000066400000000000000000000057651361143263300257040ustar00rootroot00000000000000# 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: https://docs.openstack.org/api-ref/object-store/#create-update-or-delete-account-metadata """ 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://docs.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-23.0.0/tempest/lib/services/object_storage/bulk_middleware_client.py000066400000000000000000000046631361143263300273760ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/object_storage/capabilities_client.py000066400000000000000000000020621361143263300266640ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/object_storage/container_client.py000066400000000000000000000114141361143263300262160ustar00rootroot00000000000000# 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://docs.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://docs.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://docs.openstack.org/api-ref/object-store/#show-container-details-and-list-objects """ 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-23.0.0/tempest/lib/services/object_storage/object_client.py000066400000000000000000000135701361143263300255070ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/placement/000077500000000000000000000000001361143263300213015ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/services/placement/__init__.py000066400000000000000000000013161361143263300234130ustar00rootroot00000000000000# Copyright (c) 2019 Ericsson # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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.placement.placement_client import \ PlacementClient __all__ = ['PlacementClient'] tempest-23.0.0/tempest/lib/services/placement/base_placement_client.py000066400000000000000000000033441361143263300261570ustar00rootroot00000000000000# Copyright (c) 2019 Ericsson # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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 PLACEMENT_MICROVERSION = None class BasePlacementClient(rest_client.RestClient): api_microversion_header_name = 'OpenStack-API-Version' version_header_value = 'placement %s' def get_headers(self): headers = super(BasePlacementClient, self).get_headers() if PLACEMENT_MICROVERSION: headers[self.api_microversion_header_name] = \ self.version_header_value % PLACEMENT_MICROVERSION return headers def request(self, method, url, extra_headers=False, headers=None, body=None, chunked=False): resp, resp_body = super(BasePlacementClient, self).request( method, url, extra_headers, headers, body, chunked) if (PLACEMENT_MICROVERSION and PLACEMENT_MICROVERSION != api_version_utils.LATEST_MICROVERSION): api_version_utils.assert_version_header_matches_request( self.api_microversion_header_name, self.version_header_value % PLACEMENT_MICROVERSION, resp) return resp, resp_body tempest-23.0.0/tempest/lib/services/placement/placement_client.py000066400000000000000000000036251361143263300251670ustar00rootroot00000000000000# Copyright (c) 2019 Ericsson # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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.placement import base_placement_client class PlacementClient(base_placement_client.BasePlacementClient): def list_allocation_candidates(self, **params): """List allocation candidates. For full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/placement/#list-allocation-candidates """ url = '/allocation_candidates' 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_allocations(self, consumer_uuid): """List all allocation records for the consumer. For full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/placement/#list-allocations """ url = '/allocations/%s' % consumer_uuid resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) tempest-23.0.0/tempest/lib/services/volume/000077500000000000000000000000001361143263300206405ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/services/volume/__init__.py000066400000000000000000000014131361143263300227500ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/volume/base_client.py000066400000000000000000000035211361143263300234630ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/volume/v1/000077500000000000000000000000001361143263300211665ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/services/volume/v1/__init__.py000066400000000000000000000034041361143263300233000ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/volume/v1/availability_zone_client.py000066400000000000000000000020351361143263300266030ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/volume/v1/backups_client.py000066400000000000000000000103131361143263300245240ustar00rootroot00000000000000# 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: https://docs.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: https://docs.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.""" # TODO(linanbj): 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({'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-23.0.0/tempest/lib/services/volume/v1/encryption_types_client.py000066400000000000000000000047601361143263300265230ustar00rootroot00000000000000# 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. :param 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://docs.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-23.0.0/tempest/lib/services/volume/v1/extensions_client.py000066400000000000000000000020241361143263300252730ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/volume/v1/hosts_client.py000066400000000000000000000025721361143263300242440ustar00rootroot00000000000000# 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://docs.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-23.0.0/tempest/lib/services/volume/v1/limits_client.py000066400000000000000000000021241361143263300243760ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/volume/v1/qos_client.py000066400000000000000000000117341361143263300237060ustar00rootroot00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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: https://docs.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: https://docs.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://docs.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-23.0.0/tempest/lib/services/volume/v1/quotas_client.py000066400000000000000000000045401361143263300244150ustar00rootroot00000000000000# 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://docs.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-23.0.0/tempest/lib/services/volume/v1/services_client.py000066400000000000000000000022171361143263300247230ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/volume/v1/snapshots_client.py000066400000000000000000000167011361143263300251250ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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://docs.openstack.org/api-ref/block-storage/v2/#list-snapshots https://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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-23.0.0/tempest/lib/services/volume/v1/types_client.py000066400000000000000000000150431361143263300242450ustar00rootroot00000000000000# 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://docs.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://docs.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://docs.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://docs.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://docs.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. :param volume_type_id: Id of volume_type. :param extra_spec_name: Name of the extra spec to be updated. :param extra_specs: 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://docs.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-23.0.0/tempest/lib/services/volume/v1/volumes_client.py000066400000000000000000000300371361143263300245730ustar00rootroot00000000000000# 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://docs.openstack.org/api-ref/block-storage/v2/#list-volumes https://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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://docs.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-23.0.0/tempest/lib/services/volume/v2/000077500000000000000000000000001361143263300211675ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/services/volume/v2/__init__.py000066400000000000000000000046571361143263300233140ustar00rootroot00000000000000# 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-23.0.0/tempest/lib/services/volume/v2/availability_zone_client.py000066400000000000000000000016071361143263300266100ustar00rootroot00000000000000# 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 debtcollector import moves from tempest.lib.services.volume.v3 import availability_zone_client AvailabilityZoneClient = moves.moved_class( availability_zone_client.AvailabilityZoneClient, 'AvailabilityZoneClient', __name__, version="Rocky", removal_version='?') tempest-23.0.0/tempest/lib/services/volume/v2/backups_client.py000066400000000000000000000015421361143263300245310ustar00rootroot00000000000000# 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 debtcollector import moves from tempest.lib.services.volume.v3 import backups_client BackupsClient = moves.moved_class( backups_client.BackupsClient, 'BackupsClient', __name__, version="Rocky", removal_version='?') tempest-23.0.0/tempest/lib/services/volume/v2/capabilities_client.py000066400000000000000000000015661361143263300255400ustar00rootroot00000000000000# 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 debtcollector import moves from tempest.lib.services.volume.v3 import capabilities_client CapabilitiesClient = moves.moved_class( capabilities_client.CapabilitiesClient, 'CapabilitiesClient', __name__, version="Queens", removal_version='?') tempest-23.0.0/tempest/lib/services/volume/v2/encryption_types_client.py000066400000000000000000000016151361143263300265200ustar00rootroot00000000000000# 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 debtcollector import moves from tempest.lib.services.volume.v3 import encryption_types_client EncryptionTypesClient = moves.moved_class( encryption_types_client.EncryptionTypesClient, 'EncryptionTypesClient', __name__, version="Rocky", removal_version='?') tempest-23.0.0/tempest/lib/services/volume/v2/extensions_client.py000066400000000000000000000015461361143263300253040ustar00rootroot00000000000000# 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 debtcollector import moves from tempest.lib.services.volume.v3 import extensions_client ExtensionsClient = moves.moved_class( extensions_client.ExtensionsClient, 'ExtensionsClient', __name__, version="Rocky", removal_version='?') tempest-23.0.0/tempest/lib/services/volume/v2/hosts_client.py000066400000000000000000000015301361143263300242360ustar00rootroot00000000000000# 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 debtcollector import moves from tempest.lib.services.volume.v3 import hosts_client HostsClient = moves.moved_class( hosts_client.HostsClient, 'HostsClient', __name__, version="Rocky", removal_version='?') tempest-23.0.0/tempest/lib/services/volume/v2/limits_client.py000066400000000000000000000015261361143263300244040ustar00rootroot00000000000000# 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 debtcollector import moves from tempest.lib.services.volume.v3 import limits_client LimitsClient = moves.moved_class( limits_client.LimitsClient, 'LimitsClient', __name__, version="Rocky", removal_version='?') tempest-23.0.0/tempest/lib/services/volume/v2/qos_client.py000066400000000000000000000014671361143263300237110ustar00rootroot00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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.v3 import qos_client QosSpecsClient = moves.moved_class( qos_client.QosSpecsClient, 'QosSpecsClient', __name__, version="Rocky", removal_version='?') tempest-23.0.0/tempest/lib/services/volume/v2/quota_classes_client.py000066400000000000000000000016311361143263300257460ustar00rootroot00000000000000# 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 debtcollector import moves from tempest.lib.services.volume.v3 import quota_classes_client QuotaClassesClient = moves.moved_class( quota_classes_client.QuotaClassesClient, 'QuotaClassesClient', __name__, version="Rocky", removal_version='?') tempest-23.0.0/tempest/lib/services/volume/v2/quotas_client.py000066400000000000000000000015351361143263300244170ustar00rootroot00000000000000# 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 debtcollector import moves from tempest.lib.services.volume.v3 import quotas_client QuotasClient = moves.moved_class( quotas_client.QuotasClient, 'QuotasClient', __name__, version="Rocky", removal_version='?') tempest-23.0.0/tempest/lib/services/volume/v2/scheduler_stats_client.py000066400000000000000000000016001361143263300262700ustar00rootroot00000000000000# 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 debtcollector import moves from tempest.lib.services.volume.v3 import scheduler_stats_client SchedulerStatsClient = moves.moved_class( scheduler_stats_client.SchedulerStatsClient, 'SchedulerStatsClient', __name__, version="Rocky", removal_version='?') tempest-23.0.0/tempest/lib/services/volume/v2/services_client.py000066400000000000000000000015471361143263300247310ustar00rootroot00000000000000# 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 debtcollector import moves from tempest.lib.services.volume.v3 import services_client ServicesClient = moves.moved_class( services_client.ServicesClient, 'ServicesClient', __name__, version="Rocky", removal_version='?') tempest-23.0.0/tempest/lib/services/volume/v2/snapshot_manage_client.py000066400000000000000000000016001361143263300262430ustar00rootroot00000000000000# 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 debtcollector import moves from tempest.lib.services.volume.v3 import snapshot_manage_client SnapshotManageClient = moves.moved_class( snapshot_manage_client.SnapshotManageClient, 'SnapshotManageClient', __name__, version="Rocky", removal_version='?') tempest-23.0.0/tempest/lib/services/volume/v2/snapshots_client.py000066400000000000000000000014551361143263300251260ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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.v3 import snapshots_client SnapshotsClient = moves.moved_class( snapshots_client.SnapshotsClient, 'SnapshotsClient', __name__, version="Rocky", removal_version='?') tempest-23.0.0/tempest/lib/services/volume/v2/transfers_client.py000066400000000000000000000015541361143263300251130ustar00rootroot00000000000000# 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 debtcollector import moves from tempest.lib.services.volume.v3 import transfers_client TransfersClient = moves.moved_class( transfers_client.TransfersClient, 'TransfersClient', __name__, version="Rocky", removal_version='?') tempest-23.0.0/tempest/lib/services/volume/v2/types_client.py000066400000000000000000000015301361143263300242420ustar00rootroot00000000000000# 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 debtcollector import moves from tempest.lib.services.volume.v3 import types_client TypesClient = moves.moved_class( types_client.TypesClient, 'TypesClient', __name__, version="Rocky", removal_version='?') tempest-23.0.0/tempest/lib/services/volume/v2/volume_manage_client.py000066400000000000000000000016311361143263300257170ustar00rootroot00000000000000# 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 debtcollector import moves from tempest.lib.services.volume.v3 import volume_manage_client VolumeManageClient = moves.moved_class( volume_manage_client.VolumeManageClient, 'VolumeManageClient', __name__, version="Rocky", removal_version='?') tempest-23.0.0/tempest/lib/services/volume/v2/volumes_client.py000066400000000000000000000015421361143263300245730ustar00rootroot00000000000000# 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 debtcollector import moves from tempest.lib.services.volume.v3 import volumes_client VolumesClient = moves.moved_class( volumes_client.VolumesClient, 'VolumesClient', __name__, version="Rocky", removal_version='?') tempest-23.0.0/tempest/lib/services/volume/v3/000077500000000000000000000000001361143263300211705ustar00rootroot00000000000000tempest-23.0.0/tempest/lib/services/volume/v3/__init__.py000066400000000000000000000057721361143263300233140ustar00rootroot00000000000000# 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.availability_zone_client \ import AvailabilityZoneClient 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.capabilities_client import \ CapabilitiesClient from tempest.lib.services.volume.v3.encryption_types_client import \ EncryptionTypesClient from tempest.lib.services.volume.v3.extensions_client import ExtensionsClient 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.hosts_client import HostsClient from tempest.lib.services.volume.v3.limits_client import LimitsClient from tempest.lib.services.volume.v3.messages_client import MessagesClient from tempest.lib.services.volume.v3.qos_client import QosSpecsClient from tempest.lib.services.volume.v3.quota_classes_client import \ QuotaClassesClient from tempest.lib.services.volume.v3.quotas_client import QuotasClient from tempest.lib.services.volume.v3.scheduler_stats_client import \ SchedulerStatsClient from tempest.lib.services.volume.v3.services_client import ServicesClient from tempest.lib.services.volume.v3.snapshot_manage_client import \ SnapshotManageClient from tempest.lib.services.volume.v3.snapshots_client import SnapshotsClient from tempest.lib.services.volume.v3.transfers_client import TransfersClient from tempest.lib.services.volume.v3.types_client import TypesClient from tempest.lib.services.volume.v3.versions_client import VersionsClient from tempest.lib.services.volume.v3.volume_manage_client import \ VolumeManageClient from tempest.lib.services.volume.v3.volumes_client import VolumesClient __all__ = ['AvailabilityZoneClient', 'BackupsClient', 'BaseClient', 'CapabilitiesClient', 'EncryptionTypesClient', 'ExtensionsClient', 'GroupSnapshotsClient', 'GroupTypesClient', 'GroupsClient', 'HostsClient', 'LimitsClient', 'MessagesClient', 'QosSpecsClient', 'QuotaClassesClient', 'QuotasClient', 'SchedulerStatsClient', 'ServicesClient', 'SnapshotManageClient', 'SnapshotsClient', 'TransfersClient', 'TypesClient', 'VersionsClient', 'VolumeManageClient', 'VolumesClient'] tempest-23.0.0/tempest/lib/services/volume/v3/availability_zone_client.py000066400000000000000000000017501361143263300266100ustar00rootroot00000000000000# 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): 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-23.0.0/tempest/lib/services/volume/v3/backups_client.py000066400000000000000000000123441361143263300245340ustar00rootroot00000000000000# 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 import exceptions as lib_exc from tempest.lib.services.volume import base_client class BackupsClient(base_client.BaseClient): """Volume V3 Backups client""" 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://docs.openstack.org/api-ref/block-storage/v3/index.html#create-a-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 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://docs.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) 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://docs.openstack.org/api-ref/block-storage/v3/index.html#restore-a-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: https://docs.openstack.org/api-ref/block-storage/v3/index.html#list-backups-for-project https://docs.openstack.org/api-ref/block-storage/v3/index.html#list-backups-with-detail """ 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. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/index.html#import-a-backup """ 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-23.0.0/tempest/lib/services/volume/v3/base_client.py000066400000000000000000000015351361143263300240160ustar00rootroot00000000000000# 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='?') tempest-23.0.0/tempest/lib/services/volume/v3/capabilities_client.py000066400000000000000000000025651361143263300255410ustar00rootroot00000000000000# 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.api_schema.response.volume import capabilities as schema from tempest.lib.common import rest_client class CapabilitiesClient(rest_client.RestClient): 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://docs.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.validate_response(schema.show_backend_capabilities, resp, body) return rest_client.ResponseBody(resp, body) tempest-23.0.0/tempest/lib/services/volume/v3/encryption_types_client.py000066400000000000000000000067411361143263300265260ustar00rootroot00000000000000# 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. :param 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 show_encryption_specs_item(self, volume_type_id, key): """Get the encryption specs item for the specified volume type.""" url = "/types/%s/encryption/%s" % (volume_type_id, key) 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://docs.openstack.org/api-ref/block-storage/v3/index.html#create-an-encryption-type """ 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://docs.openstack.org/api-ref/block-storage/v3/index.html#update-an-encryption-type """ 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-23.0.0/tempest/lib/services/volume/v3/extensions_client.py000066400000000000000000000021411361143263300252750ustar00rootroot00000000000000# 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.api_schema.response.volume import extensions as schema from tempest.lib.common import rest_client class ExtensionsClient(rest_client.RestClient): """Volume extensions client.""" 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) tempest-23.0.0/tempest/lib/services/volume/v3/group_snapshots_client.py000066400000000000000000000100311361143263300263310ustar00rootroot00000000000000# 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""" 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://docs.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://docs.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://docs.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://docs.openstack.org/api-ref/block-storage/v3/#list-group-snapshots https://docs.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://docs.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-23.0.0/tempest/lib/services/volume/v3/group_types_client.py000066400000000000000000000143001361143263300254560ustar00rootroot00000000000000# 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""" @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://docs.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://docs.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_default_group_type(self): """Returns the details of default group_type. For more information, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/#show-default-group-type-details """ url = 'group_types/default' 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://docs.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) def update_group_type(self, group_type_id, **kwargs): """Updates a group type. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/#update-group-type """ post_body = json.dumps({'group_type': kwargs}) resp, body = self.put('group_types/%s' % group_type_id, post_body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def create_or_update_group_type_specs(self, group_type_id, group_specs): """Creates new group specs or updates existing group specs. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/#create-or-update-group-specs-for-a-group-type """ url = "group_types/%s/group_specs" % group_type_id post_body = json.dumps({'group_specs': group_specs}) resp, body = self.post(url, post_body) body = json.loads(body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def list_group_type_specs(self, group_type_id): """Lists all group specs for a given group type.""" url = 'group_types/%s/group_specs' % group_type_id 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_specs_item(self, group_type_id, spec_id): """Shows specified item of group specs for a given group type.""" url = "group_types/%s/group_specs/%s" % (group_type_id, spec_id) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def update_group_type_specs_item(self, group_type_id, spec_id, spec): """Updates specified item of group specs for a given group type. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/#update-one-specific-group-spec-for-a-group-type """ url = "group_types/%s/group_specs/%s" % (group_type_id, spec_id) put_body = json.dumps(spec) 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_group_type_specs_item(self, group_type_id, spec_id): """Deletes specified item of group specs for a given group type.""" resp, body = self.delete("group_types/%s/group_specs/%s" % ( group_type_id, spec_id)) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) tempest-23.0.0/tempest/lib/services/volume/v3/groups_client.py000066400000000000000000000120621361143263300244200ustar00rootroot00000000000000# 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""" 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://docs.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://docs.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://docs.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://docs.openstack.org/api-ref/block-storage/v3/#list-groups https://docs.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://docs.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://docs.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://docs.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-23.0.0/tempest/lib/services/volume/v3/hosts_client.py000066400000000000000000000033751361143263300242500ustar00rootroot00000000000000# 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.api_schema.response.volume import hosts as schema from tempest.lib.common import rest_client class HostsClient(rest_client.RestClient): """Client class to send CRUD Volume API requests""" def list_hosts(self, **params): """Lists all hosts. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/index.html#list-all-hosts-for-a-project """ 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, host_name): """Show host details.""" url = 'os-hosts/%s' % host_name resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.show_host, resp, body) return rest_client.ResponseBody(resp, body) tempest-23.0.0/tempest/lib/services/volume/v3/limits_client.py000066400000000000000000000020711361143263300244010ustar00rootroot00000000000000# 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 limits client.""" 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-23.0.0/tempest/lib/services/volume/v3/messages_client.py000066400000000000000000000042031361143263300247060ustar00rootroot00000000000000# 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.api_schema.response.volume import messages as schema 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.""" 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.validate_response(schema.show_message, resp, body) 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.validate_response(schema.list_messages, resp, body) 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.validate_response(schema.delete_message, resp, body) 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-23.0.0/tempest/lib/services/volume/v3/qos_client.py000066400000000000000000000123151361143263300237040ustar00rootroot00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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.volume import qos as schema from tempest.lib.common import rest_client from tempest.lib import exceptions as lib_exc class QosSpecsClient(rest_client.RestClient): """Volume QoS client. Client class to send CRUD QoS API requests """ 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: https://docs.openstack.org/api-ref/block-storage/v3/index.html#create-a-qos-specification """ post_body = json.dumps({'qos_specs': kwargs}) resp, body = self.post('qos-specs', post_body) body = json.loads(body) self.validate_response(schema.show_qos, resp, body) return rest_client.ResponseBody(resp, body) def delete_qos(self, qos_id, force=False): """Delete the specified QoS specification.""" resp, body = self.delete( "qos-specs/%s?force=%s" % (qos_id, force)) self.validate_response(schema.delete_qos, resp, body) 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.validate_response(schema.list_qos, resp, body) 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.validate_response(schema.show_qos, resp, body) 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: https://docs.openstack.org/api-ref/block-storage/v3/index.html#set-keys-in-a-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.validate_response(schema.set_qos_key, resp, body) 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://docs.openstack.org/api-ref/block-storage/v3/index.html#unset-keys-in-a-qos-specification """ put_body = json.dumps({'keys': keys}) resp, body = self.put('qos-specs/%s/delete_keys' % qos_id, put_body) self.validate_response(schema.unset_qos_key, resp, body) return rest_client.ResponseBody(resp, body) def associate_qos(self, qos_id, vol_type_id): """Associate the specified QoS with specified volume-type.""" url = "qos-specs/%s/associate" % qos_id url += "?vol_type_id=%s" % vol_type_id resp, body = self.get(url) self.validate_response(schema.associate_qos, resp, body) 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.validate_response(schema.show_association_qos, resp, body) return rest_client.ResponseBody(resp, body) def disassociate_qos(self, qos_id, vol_type_id): """Disassociate the specified QoS with specified volume-type.""" url = "qos-specs/%s/disassociate" % qos_id url += "?vol_type_id=%s" % vol_type_id resp, body = self.get(url) self.validate_response(schema.disassociate_qos, resp, body) return rest_client.ResponseBody(resp, body) def disassociate_all_qos(self, qos_id): """Disassociate the specified QoS with all associations.""" url = "qos-specs/%s/disassociate_all" % qos_id resp, body = self.get(url) self.validate_response(schema.disassociate_all_qos, resp, body) return rest_client.ResponseBody(resp, body) tempest-23.0.0/tempest/lib/services/volume/v3/quota_classes_client.py000066400000000000000000000037231361143263300257530ustar00rootroot00000000000000# 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 client.""" def show_quota_class_set(self, quota_class_id): """List quotas for a quota class. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/index.html#show-quota-classes-for-a-project """ 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. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/index.html#update-quota-classes-for-a-project """ 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-23.0.0/tempest/lib/services/volume/v3/quotas_client.py000066400000000000000000000052271361143263300244220ustar00rootroot00000000000000# 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.api_schema.response.volume import quotas as schema from tempest.lib.common import rest_client class QuotasClient(rest_client.RestClient): """Client class to send CRUD Volume Quotas API 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) body = jsonutils.loads(body) self.validate_response(schema.show_quota_set, resp, 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) body = jsonutils.loads(body) if params and params.get('usage', False): self.validate_response(schema.show_quota_set_usage, resp, body) else: self.validate_response(schema.show_quota_set, resp, 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://docs.openstack.org/api-ref/block-storage/v3/index.html#update-quotas-for-a-project """ put_body = jsonutils.dumps({'quota_set': kwargs}) resp, body = self.put('os-quota-sets/%s' % tenant_id, put_body) body = jsonutils.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_set, resp, body) return rest_client.ResponseBody(resp, body) tempest-23.0.0/tempest/lib/services/volume/v3/scheduler_stats_client.py000066400000000000000000000024771361143263300263060ustar00rootroot00000000000000# 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): 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://docs.openstack.org/api-ref/block-storage/v3/index.html#list-all-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-23.0.0/tempest/lib/services/volume/v3/services_client.py000066400000000000000000000100711361143263300247220ustar00rootroot00000000000000# 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.api_schema.response.volume import services as schema from tempest.lib.common import rest_client class ServicesClient(rest_client.RestClient): """Client class to send CRUD Volume Services API requests""" def list_services(self, **params): """List all Cinder services. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/#list-all-cinder-services """ url = 'os-services' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.list_services, resp, body) return rest_client.ResponseBody(resp, body) def enable_service(self, **kwargs): """Enable service on a host. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/#enable-a-cinder-service """ put_body = json.dumps(kwargs) resp, body = self.put('os-services/enable', put_body) body = json.loads(body) self.validate_response(schema.enable_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://docs.openstack.org/api-ref/block-storage/v3/#disable-a-cinder-service """ put_body = json.dumps(kwargs) resp, body = self.put('os-services/disable', put_body) body = json.loads(body) self.validate_response(schema.disable_service, resp, body) return rest_client.ResponseBody(resp, body) def disable_log_reason(self, **kwargs): """Disable scheduling for a volume service and log disabled reason. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/#log-disabled-cinder-service-information """ put_body = json.dumps(kwargs) resp, body = self.put('os-services/disable-log-reason', put_body) body = json.loads(body) self.validate_response(schema.disable_log_reason, resp, body) return rest_client.ResponseBody(resp, body) def freeze_host(self, **kwargs): """Freeze a Cinder backend host. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/#freeze-a-cinder-backend-host """ put_body = json.dumps(kwargs) resp, body = self.put('os-services/freeze', put_body) self.validate_response(schema.freeze_host, resp, body) return rest_client.ResponseBody(resp) def thaw_host(self, **kwargs): """Thaw a Cinder backend host. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/#thaw-a-cinder-backend-host """ put_body = json.dumps(kwargs) resp, body = self.put('os-services/thaw', put_body) self.validate_response(schema.thaw_host, resp, body) return rest_client.ResponseBody(resp) tempest-23.0.0/tempest/lib/services/volume/v3/snapshot_manage_client.py000066400000000000000000000026621361143263300262550ustar00rootroot00000000000000# 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.api_schema.response.volume import manage_snapshot as schema from tempest.lib.common import rest_client class SnapshotManageClient(rest_client.RestClient): """Snapshot manage client.""" def manage_snapshot(self, **kwargs): """Manage a snapshot. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/index.html#manage-an-existing-snapshot """ post_body = json.dumps({'snapshot': kwargs}) url = 'os-snapshot-manage' resp, body = self.post(url, post_body) body = json.loads(body) self.validate_response(schema.manage_snapshot, resp, body) return rest_client.ResponseBody(resp, body) tempest-23.0.0/tempest/lib/services/volume/v3/snapshots_client.py000066400000000000000000000206331361143263300251260ustar00rootroot00000000000000# 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 class SnapshotsClient(rest_client.RestClient): """Client class to send CRUD Volume Snapshot V3 API requests.""" 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://docs.openstack.org/api-ref/block-storage/v3/index.html#list-accessible-snapshots https://docs.openstack.org/api-ref/block-storage/v3/index.html#list-snapshots-and-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://docs.openstack.org/api-ref/block-storage/v3/index.html#show-a-snapshot-s-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://docs.openstack.org/api-ref/block-storage/v3/index.html#create-a-snapshot """ post_body = json.dumps({'snapshot': kwargs}) resp, body = self.post('snapshots', post_body) body = json.loads(body) self.expected_success(202, 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://docs.openstack.org/api-ref/block-storage/v3/index.html#update-a-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: https://docs.openstack.org/api-ref/block-storage/v3/index.html#delete-a-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 status of a snapshot. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/#update-status-of-a-snapshot """ 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: https://docs.openstack.org/api-ref/block-storage/v3/index.html#create-a-snapshot-s-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: https://docs.openstack.org/api-ref/block-storage/v3/index.html#show-a-snapshot-s-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://docs.openstack.org/api-ref/block-storage/v3/index.html#update-a-snapshot-s-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 for the snapshot for a specific key. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/#update-a-snapshot-s-metadata-for-a-specific-key """ 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-23.0.0/tempest/lib/services/volume/v3/transfers_client.py000066400000000000000000000072061361143263300251140ustar00rootroot00000000000000# 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.volume import transfers as schema from tempest.lib.common import rest_client class TransfersClient(rest_client.RestClient): """Client class to send CRUD Volume Transfer API requests""" 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://docs.openstack.org/api-ref/block-storage/v3/index.html#create-a-volume-transfer """ post_body = json.dumps({'transfer': kwargs}) resp, body = self.post('os-volume-transfer', post_body) body = json.loads(body) self.validate_response(schema.create_volume_transfer, resp, body) 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.validate_response(schema.show_volume_transfer, resp, body) 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://docs.openstack.org/api-ref/block-storage/v3/index.html#list-volume-transfers-for-a-project https://docs.openstack.org/api-ref/block-storage/v3/index.html#list-volume-transfers-and-details """ url = 'os-volume-transfer' schema_list_transfers = schema.list_volume_transfers_no_detail if detail: url += '/detail' schema_list_transfers = schema.list_volume_transfers_with_detail if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema_list_transfers, resp, body) return rest_client.ResponseBody(resp, body) def delete_volume_transfer(self, transfer_id): """Delete a volume transfer.""" resp, body = self.delete("os-volume-transfer/%s" % transfer_id) self.validate_response(schema.delete_volume_transfer, resp, body) 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://docs.openstack.org/api-ref/block-storage/v3/index.html#accept-a-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.validate_response(schema.accept_volume_transfer, resp, body) return rest_client.ResponseBody(resp, body) tempest-23.0.0/tempest/lib/services/volume/v3/types_client.py000066400000000000000000000205751361143263300242550ustar00rootroot00000000000000# 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://docs.openstack.org/api-ref/block-storage/v3/index.html#list-all-volume-types """ 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://docs.openstack.org/api-ref/block-storage/v3/index.html#show-volume-type-detail """ 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://docs.openstack.org/api-ref/block-storage/v3/index.html#create-a-volume-type """ 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://docs.openstack.org/api-ref/block-storage/v3/index.html#delete-a-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 type extra specs created. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/#show-all-extra-specifications-for-volume-type """ 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 new volume type extra specs. :param volume_type_id: Id of volume type. :param 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://docs.openstack.org/api-ref/block-storage/v3/index.html#update-a-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. :param volume_type_id: Id of volume type. :param extra_spec_name: Name of the extra spec to be updated. :param extra_specs: 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://docs.openstack.org/api-ref/block-storage/v3/index.html#update-extra-specification-for-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: https://docs.openstack.org/api-ref/block-storage/v3/index.html#add-private-volume-type-access-to-project """ 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: https://docs.openstack.org/api-ref/block-storage/v3/index.html#remove-private-volume-type-access-from-project """ 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: https://docs.openstack.org/api-ref/block-storage/v3/index.html#list-private-volume-type-access-detail """ 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-23.0.0/tempest/lib/services/volume/v3/versions_client.py000066400000000000000000000047071361143263300247600ustar00rootroot00000000000000# 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 six.moves.urllib.parse import urljoin 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): def list_versions(self): """List API versions For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/#list-all-api-versions """ version_url = self._get_base_version_url() resp, body = self.raw_request(version_url, 'GET') # 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._error_checker(resp, body) body = json.loads(body) self.validate_response(schema.list_versions, resp, body) return rest_client.ResponseBody(resp, body) def show_version(self, version): """Show API version details Use raw_request in order to have access to the endpoints minus version and project in order to add version only back. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/#show-api-v3-details """ version_url = urljoin(self._get_base_version_url(), version + '/') 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.volume_api_version_details, resp, body) return rest_client.ResponseBody(resp, body) tempest-23.0.0/tempest/lib/services/volume/v3/volume_manage_client.py000066400000000000000000000025341361143263300257230ustar00rootroot00000000000000# 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 client.""" def manage_volume(self, **kwargs): """Manage existing volume. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/index.html#manage-an-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-23.0.0/tempest/lib/services/volume/v3/volumes_client.py000066400000000000000000000402621361143263300245760ustar00rootroot00000000000000# 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 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 V3 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_hosts(self): """Lists all hosts summary info that is not disabled. https://docs.openstack.org/api-ref/block-storage/v3/index.html#list-all-hosts-for-a-project """ resp, body = self.get('os-hosts') body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) 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://docs.openstack.org/api-ref/block-storage/v3/index.html#list-accessible-volumes-with-details https://docs.openstack.org/api-ref/block-storage/v3/index.html#list-accessible-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 migrate_volume(self, volume_id, **kwargs): """Migrate a volume to a new backend For a full list of available parameters please refer to the offical API reference: https://docs.openstack.org/api-ref/block-storage/v3/index.html#migrate-a-volume """ post_body = json.dumps({'os-migrate_volume': 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 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://docs.openstack.org/api-ref/block-storage/v3/index.html#create-a-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: https://docs.openstack.org/api-ref/block-storage/v3/index.html#update-a-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://docs.openstack.org/api-ref/block-storage/v3/index.html#delete-a-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 show_volume_summary(self, **params): """Get volumes summary. For a full list of available parameters, please refer to the official API reference: https://docs.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) def upload_volume(self, volume_id, **kwargs): """Uploads a volume in Glance. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/index.html#upload-volume-to-image """ 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://docs.openstack.org/api-ref/block-storage/v3/index.html#attach-volume-to-a-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: https://docs.openstack.org/api-ref/block-storage/v3/index.html#update-a-volume-s-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( "Volume %s failed to delete and is in error_deleting status" % volume['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: https://docs.openstack.org/api-ref/block-storage/v3/index.html#extend-a-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://docs.openstack.org/api-ref/block-storage/v3/index.html#reset-a-volume-s-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. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/index.html#updates-volume-read-only-access-mode-flag """ 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: https://docs.openstack.org/api-ref/block-storage/v3/index.html#create-metadata-for-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. For a full list of available parameters, please refer to the official API reference: https://docs.openstack.org/api-ref/block-storage/v3/index.html#update-a-volume-s-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://docs.openstack.org/api-ref/block-storage/v3/index.html#retype-a-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://docs.openstack.org/api-ref/block-storage/v3/index.html#force-detach-a-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: https://docs.openstack.org/api-ref/block-storage/v3/index.html#set-image-metadata-for-a-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://docs.openstack.org/api-ref/block-storage/v3/index.html#unmanage-a-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-23.0.0/tempest/manager.py000066400000000000000000000051731361143263300167320ustar00rootroot00000000000000# 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-23.0.0/tempest/scenario/000077500000000000000000000000001361143263300165435ustar00rootroot00000000000000tempest-23.0.0/tempest/scenario/README.rst000066400000000000000000000030361361143263300202340ustar00rootroot00000000000000.. _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-23.0.0/tempest/scenario/__init__.py000066400000000000000000000000001361143263300206420ustar00rootroot00000000000000tempest-23.0.0/tempest/scenario/manager.py000066400000000000000000001756701361143263300205470ustar00rootroot00000000000000# 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 import api_microversion_fixture 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 = log.getLogger(__name__) LATEST_MICROVERSION = 'latest' class ScenarioTest(tempest.test.BaseTestCase): """Base class for scenario tests. Uses tempest own clients. """ credentials = ['primary'] compute_min_microversion = None compute_max_microversion = LATEST_MICROVERSION volume_min_microversion = None volume_max_microversion = LATEST_MICROVERSION placement_min_microversion = None placement_max_microversion = LATEST_MICROVERSION @classmethod def skip_checks(cls): super(ScenarioTest, cls).skip_checks() api_version_utils.check_skip_with_microversion( cls.compute_min_microversion, cls.compute_max_microversion, CONF.compute.min_microversion, CONF.compute.max_microversion) api_version_utils.check_skip_with_microversion( cls.volume_min_microversion, cls.volume_max_microversion, CONF.volume.min_microversion, CONF.volume.max_microversion) api_version_utils.check_skip_with_microversion( cls.placement_min_microversion, cls.placement_max_microversion, CONF.placement.min_microversion, CONF.placement.max_microversion) @classmethod def resource_setup(cls): super(ScenarioTest, cls).resource_setup() cls.compute_request_microversion = ( api_version_utils.select_request_microversion( cls.compute_min_microversion, CONF.compute.min_microversion)) cls.volume_request_microversion = ( api_version_utils.select_request_microversion( cls.volume_min_microversion, CONF.volume.min_microversion)) cls.placement_request_microversion = ( api_version_utils.select_request_microversion( cls.placement_min_microversion, CONF.placement.min_microversion)) def setUp(self): super(ScenarioTest, self).setUp() self.useFixture(api_microversion_fixture.APIMicroversionFixture( compute_microversion=self.compute_request_microversion, volume_microversion=self.volume_request_microversion, placement_microversion=self.placement_request_microversion)) @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 cls.backups_client = cls.os_primary.backups_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__) if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs: kwargs['binding:vnic_type'] = CONF.network.port_vnic_type if CONF.network.port_profile and 'binding:profile' not in kwargs: kwargs['binding:profile'] = CONF.network.port_profile 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. :param **kwargs: See extra parameters below :Keyword Arguments: * *vnic_type* (``string``) -- used when launching instances with pre-configured ports. Examples: normal: a traditional virtual port that is either attached to a linux bridge or an openvswitch bridge on a compute node. direct: an SR-IOV port that is directly attached to a VM macvtap: an SR-IOV port that is attached to a VM via a macvtap device. Defaults to ``CONF.network.port_vnic_type``. * *port_profile* (``dict``) -- This attribute is a dictionary that can be used (with admin credentials) to supply information influencing the binding of the port. example: port_profile = "capabilities:[switchdev]" Defaults to ``CONF.network.port_profile``. """ # 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 = kwargs.pop('vnic_type', CONF.network.port_vnic_type) profile = kwargs.pop('port_profile', CONF.network.port_profile) # If vnic_type or profile are configured create port for # every network if vnic_type or profile: ports = [] create_port_body = {} if vnic_type: create_port_body['binding:vnic_type'] = vnic_type if profile: create_port_body['binding:profile'] = profile 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() if CONF.compute.compute_volume_common_az: kwargs.setdefault('availability_zone', CONF.compute.compute_volume_common_az) 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: if CONF.image_feature_enabled.api_v1: resp = self.image_client.check_image(imageRef) image = common_image.get_image_meta_from_headers(resp) else: image = self.image_client.show_image(imageRef) min_disk = image.get('min_disk') 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} if CONF.compute.compute_volume_common_az: kwargs.setdefault('availability_zone', CONF.compute.compute_volume_common_az) 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_backup(self, volume_id, name=None, description=None, force=False, snapshot_id=None, incremental=False, container=None): name = name or data_utils.rand_name( self.__class__.__name__ + "-backup") kwargs = {'name': name, 'description': description, 'force': force, 'snapshot_id': snapshot_id, 'incremental': incremental, 'container': container} backup = self.backups_client.create_backup(volume_id=volume_id, **kwargs)['backup'] self.addCleanup(self.backups_client.delete_backup, backup['id']) waiters.wait_for_volume_resource_status(self.backups_client, backup['id'], 'available') return backup def restore_backup(self, backup_id): restore = self.backups_client.restore_backup(backup_id)['restore'] self.addCleanup(self.volumes_client.delete_volume, restore['volume_id']) waiters.wait_for_volume_resource_status(self.backups_client, backup_id, 'available') waiters.wait_for_volume_resource_status(self.volumes_client, restore['volume_id'], 'available') self.assertEqual(backup_id, restore['backup_id']) return restore 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') snapshot = self.snapshots_client.show_snapshot( snapshot['id'])['snapshot'] return snapshot def _cleanup_volume_type(self, volume_type): """Clean up a given volume type. Ensuring all volumes associated to a type are first removed before attempting to remove the type itself. This includes any image volume cache volumes stored in a separate tenant to the original volumes created from the type. """ admin_volume_type_client = self.os_admin.volume_types_client_latest admin_volumes_client = self.os_admin.volumes_client_latest volumes = admin_volumes_client.list_volumes( detail=True, params={'all_tenants': 1})['volumes'] type_name = volume_type['name'] for volume in [v for v in volumes if v['volume_type'] == type_name]: test_utils.call_and_ignore_notfound_exc( admin_volumes_client.delete_volume, volume['id']) admin_volumes_client.wait_for_resource_deletion(volume['id']) admin_volume_type_client.delete_volume_type(volume_type['id']) def create_volume_type(self, client=None, name=None, backend_name=None): if not client: client = self.os_admin.volume_types_client_latest 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(self._cleanup_volume_type, volume_type) 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.warning( "A(n) %s image was not found. Retrying with uec image.", img_disk_format) 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, server=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' }) if server: self._log_console_output([server]) return result def check_vm_connectivity(self, ip_address, username=None, private_key=None, should_connect=True, extra_msg="", server=None, 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 extra_msg: Message to help with debugging if ``ping_ip_address`` fails :param server: The server whose console to log for debugging :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 """ LOG.debug('checking network connections to IP %s with user: %s', ip_address, username) if should_connect: msg = "Timed out waiting for %s to become reachable" % ip_address else: msg = "ip address %s is reachable" % ip_address if extra_msg: msg = "%s\n%s" % (extra_msg, msg) self.assertTrue(self.ping_ip_address(ip_address, should_succeed=should_connect, mtu=mtu, server=server), msg=msg) if should_connect: # no need to check ssh for negative connectivity try: self.get_remote_client(ip_address, username, private_key, server=server) except Exception: if not extra_msg: extra_msg = 'Failed to ssh to %s' % ip_address LOG.exception(extra_msg) 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, server=None): ssh_client = self.get_remote_client(ip_address, private_key=private_key, server=server) 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, server=None): ssh_client = self.get_remote_client(ip_address, private_key=private_key, server=server) 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 # noqa address['OS-EXT-IPS:type'] == 'fixed'): return address['addr'] raise exceptions.ServerUnreachable(server_id=server['id']) else: raise lib_exc.InvalidConfiguration() @classmethod def get_host_for_server(cls, server_id): server_details = cls.os_admin.servers_client.show_server(server_id) return server_details['server']['OS-EXT-SRV-ATTR:host'] 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, **net_dict): 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) if net_dict: network_kwargs.update(net_dict) # 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): if ip_addr: ports = self.os_admin.ports_client.list_ports( device_id=server['id'], fixed_ips='ip_address=%s' % ip_addr)['ports'] else: ports = self.os_admin.ports_client.list_ports( device_id=server['id'])['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, protocol='icmp'): """check server connectivity via source ssh connection :param source: RemoteClient: an ssh connection from which to execute the check :param dest: an IP to check connectivity against :param should_succeed: boolean should connection succeed or not :param nic: specific network interface to test connectivity from :param protocol: the protocol used to test connectivity with. :returns: True, if the connection succeeded and it was expected to succeed. False otherwise. """ method_name = '%s_check' % protocol connectivity_checker = getattr(source, method_name) def connect_remote(): try: connectivity_checker(dest, nic=nic) except lib_exc.SSHExecCommandFailed: LOG.warning('Failed to check %(protocol)s connectivity for ' 'IP %(dest)s via a ssh connection from: %(src)s.', dict(protocol=protocol, dest=dest, src=source.ssh_client.host)) return not should_succeed return should_succeed result = test_utils.call_until_true(connect_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, **net_dict): """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. :param port_security_enabled: whether or not port_security is enabled :param net_dict: a dict containing experimental network information in a form like this: {'provider:network_type': 'vlan', 'provider:physical_network': 'foo', 'provider:segmentation_id': '42'} :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, **net_dict) 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_client_latest cls.admin_encryption_types_client =\ cls.os_admin.encryption_types_client_latest 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) 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-23.0.0/tempest/scenario/test_aggregates_basic_ops.py000066400000000000000000000122531361143263300243120ustar00rootroot00000000000000# 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.services_client = cls.os_admin.services_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): svc_list = self.services_client.list_services( binary='nova-compute')['services'] self.assertNotEmpty(svc_list) return svc_list[0]['host'] 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-23.0.0/tempest/scenario/test_encrypted_cinder_volumes.py000066400000000000000000000054261361143263300252560ustar00rootroot00000000000000# 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('luks', 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('plain', volume_type='cryptsetup') self.attach_detach_volume(server, volume) tempest-23.0.0/tempest/scenario/test_minbw_allocation_placement.py000066400000000000000000000205611361143263300255310ustar00rootroot00000000000000# Copyright (c) 2019 Ericsson # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.scenario import manager LOG = logging.getLogger(__name__) CONF = config.CONF class MinBwAllocationPlacementTest(manager.NetworkScenarioTest): credentials = ['primary', 'admin'] required_extensions = ['port-resource-request', 'qos', 'qos-bw-minimum-ingress'] # The feature QoS minimum bandwidth allocation in Placement API depends on # Granular resource requests to GET /allocation_candidates and Support # allocation candidates with nested resource providers features in # Placement (see: https://specs.openstack.org/openstack/nova-specs/specs/ # stein/approved/bandwidth-resource-provider.html#rest-api-impact) and this # means that the minimum placement microversion is 1.29 placement_min_microversion = '1.29' placement_max_microversion = 'latest' # Nova rejects to boot VM with port which has resource_request field, below # microversion 2.72 compute_min_microversion = '2.72' compute_max_microversion = 'latest' INGRESS_RESOURCE_CLASS = "NET_BW_IGR_KILOBIT_PER_SEC" INGRESS_DIRECTION = 'ingress' SMALLEST_POSSIBLE_BW = 1 # For any realistic inventory value (that is inventory != MAX_INT) an # allocation candidate request of MAX_INT is expected to be rejected, see: # https://github.com/openstack/placement/blob/master/placement/ # db/constants.py#L16 PLACEMENT_MAX_INT = 0x7FFFFFFF @classmethod def setup_clients(cls): super(MinBwAllocationPlacementTest, cls).setup_clients() cls.placement_client = cls.os_admin.placement_client cls.networks_client = cls.os_admin.networks_client cls.subnets_client = cls.os_admin.subnets_client cls.routers_client = cls.os_adm.routers_client cls.qos_client = cls.os_admin.qos_client cls.qos_min_bw_client = cls.os_admin.qos_min_bw_client @classmethod def skip_checks(cls): super(MinBwAllocationPlacementTest, cls).skip_checks() if not CONF.network_feature_enabled.qos_placement_physnet: msg = "Skipped as no physnet is available in config for " \ "placement based QoS allocation." raise cls.skipException(msg) def _create_policy_and_min_bw_rule(self, name_prefix, min_kbps): policy = self.qos_client.create_qos_policy( name=data_utils.rand_name(name_prefix), shared=True)['policy'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.qos_client.delete_qos_policy, policy['id']) rule = self.qos_min_bw_client.create_minimum_bandwidth_rule( policy['id'], **{ 'min_kbps': min_kbps, 'direction': self.INGRESS_DIRECTION })['minimum_bandwidth_rule'] self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.qos_min_bw_client.delete_minimum_bandwidth_rule, policy['id'], rule['id']) return policy def _create_qos_policies(self): self.qos_policy_valid = self._create_policy_and_min_bw_rule( name_prefix='test_policy_valid', min_kbps=self.SMALLEST_POSSIBLE_BW) self.qos_policy_not_valid = self._create_policy_and_min_bw_rule( name_prefix='test_policy_not_valid', min_kbps=self.PLACEMENT_MAX_INT) def _create_network_and_qos_policies(self): physnet_name = CONF.network_feature_enabled.qos_placement_physnet base_segm = \ CONF.network_feature_enabled.provider_net_base_segmentation_id self.prov_network, _, _ = self.create_networks( networks_client=self.networks_client, routers_client=self.routers_client, subnets_client=self.subnets_client, **{ 'shared': True, 'provider:network_type': 'vlan', 'provider:physical_network': physnet_name, 'provider:segmentation_id': base_segm }) self._create_qos_policies() def _check_if_allocation_is_possible(self): alloc_candidates = self.placement_client.list_allocation_candidates( resources1='%s:%s' % (self.INGRESS_RESOURCE_CLASS, self.SMALLEST_POSSIBLE_BW)) if len(alloc_candidates['provider_summaries']) == 0: self.fail('No allocation candidates are available for %s:%s' % (self.INGRESS_RESOURCE_CLASS, self.SMALLEST_POSSIBLE_BW)) # Just to be sure check with impossible high (placement max_int), # allocation alloc_candidates = self.placement_client.list_allocation_candidates( resources1='%s:%s' % (self.INGRESS_RESOURCE_CLASS, self.PLACEMENT_MAX_INT)) if len(alloc_candidates['provider_summaries']) != 0: self.fail('For %s:%s there should be no available candidate!' % (self.INGRESS_RESOURCE_CLASS, self.PLACEMENT_MAX_INT)) @decorators.idempotent_id('78625d92-212c-400e-8695-dd51706858b8') @decorators.attr(type='slow') @utils.services('compute', 'network') def test_qos_min_bw_allocation_basic(self): """"Basic scenario with QoS min bw allocation in placement. Steps: * Create prerequisites: ** VLAN type provider network with subnet. ** valid QoS policy with minimum bandwidth rule with min_kbps=1 (This is a simplification to skip the checks in placement for detecting the resource provider tree and inventories, as if bandwidth resource is available 1 kbs will be available). ** invalid QoS policy with minimum bandwidth rule with min_kbs=max integer from placement (this is a simplification again to avoid detection of RP tress and inventories, as placement will reject such big allocation). * Create port with valid QoS policy, and boot VM with that, it should pass. * Create port with invalid QoS policy, and try to boot VM with that, it should fail. """ self._check_if_allocation_is_possible() self._create_network_and_qos_policies() valid_port = self.create_port( self.prov_network['id'], qos_policy_id=self.qos_policy_valid['id']) server1 = self.create_server( networks=[{'port': valid_port['id']}]) allocations = self.placement_client.list_allocations(server1['id']) self.assertGreater(len(allocations['allocations']), 0) bw_resource_in_alloc = False for rp, resources in allocations['allocations'].items(): if self.INGRESS_RESOURCE_CLASS in resources['resources']: bw_resource_in_alloc = True self.assertTrue(bw_resource_in_alloc) # boot another vm with max int bandwidth not_valid_port = self.create_port( self.prov_network['id'], qos_policy_id=self.qos_policy_not_valid['id']) server2 = self.create_server( wait_until=None, networks=[{'port': not_valid_port['id']}]) waiters.wait_for_server_status( client=self.os_primary.servers_client, server_id=server2['id'], status='ERROR', ready_wait=False, raise_on_error=False) allocations = self.placement_client.list_allocations(server2['id']) self.assertEqual(0, len(allocations['allocations'])) server2 = self.servers_client.show_server(server2['id']) self.assertIn('fault', server2['server']) self.assertIn('No valid host', server2['server']['fault']['message']) tempest-23.0.0/tempest/scenario/test_minimum_basic.py000066400000000000000000000160341361143263300227740ustar00rootroot00000000000000# 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.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') @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 = None server = self.servers_client.show_server(server['id'])['server'] if (CONF.network_feature_enabled.floating_ips and CONF.network.floating_network_name): 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'])) ssh_ip = floating_ip['ip'] else: ssh_ip = self.get_server_ip(server) 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( ssh_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( ssh_ip, private_key=keypair['private_key'], server=server) self.check_disks() if floating_ip: # 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-23.0.0/tempest/scenario/test_network_advanced_server_ops.py000066400000000000000000000325341361143263300257500ustar00rootroot00000000000000# 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_vm_connectivity(floating_ip_addr, username, private_key, should_connect, 'Public network connectivity check failed', 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) @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') @decorators.attr(type='slow') @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']) server = self.servers_client.show_server(server['id'])['server'] # Nova API > 2.46 no longer includes flavor.id, and schema check # will cover whether 'id' should be in flavor if server['flavor'].get('id'): self.assertEqual(resize_flavor, server['flavor']['id']) else: flavor = self.flavors_client.show_flavor(resize_flavor)['flavor'] self.assertEqual(flavor['name'], server['original_name']) for key in ['ram', 'vcpus', 'disk']: self.assertEqual(flavor[key], server['flavor'][key]) 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('03fd1562-faad-11e7-9ea0-fa163e65f5ce') @testtools.skipUnless(CONF.compute_feature_enabled.live_migration, 'Live 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_live_migration(self): keypair = self.create_keypair() server = self._setup_server(keypair) floating_ip = self._setup_network(server, keypair) self._wait_server_status_and_check_network_connectivity( server, keypair, floating_ip) block_migration = (CONF.compute_feature_enabled. block_migration_for_live_migration) old_host = self.get_host_for_server(server['id']) self.admin_servers_client.live_migrate_server( server['id'], host=None, block_migration=block_migration, disk_over_commit=False) waiters.wait_for_server_status(self.servers_client, server['id'], 'ACTIVE') new_host = self.get_host_for_server(server['id']) self.assertNotEqual(old_host, new_host, 'Server did not migrate') self._wait_server_status_and_check_network_connectivity( server, keypair, floating_ip) @decorators.skip_because(bug='1836595') @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-23.0.0/tempest/scenario/test_network_basic_ops.py000066400000000000000000001174571361143263300237060ustar00rootroot00000000000000# 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) message = 'Public network connectivity check failed' if msg: message += '. Reason: %s' % msg self.check_vm_connectivity( ip_address, ssh_login, private_key, should_connect, message, server, 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] ip_output = ssh_client.exec_command('ip a') ip_address = new_port['fixed_ips'][0]['ip_address'] ip_mask = CONF.network.project_network_mask_bits # check if the address is not already in use, if not, set it if ' ' + ip_address + '/' + str(ip_mask) not in ip_output: ssh_client.exec_command("sudo ip addr add %s/%s dev %s" % ( ip_address, ip_mask, 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 - ping internal compute port, implying connectivity to other VMs on this network """ floating_ip, server = self.floating_ip_tuple # get internal ports' ips: # get all network and compute 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') or p['device_owner'].startswith('compute') ) 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() # first check that connectivity works in general for the instance self._check_public_network_connectivity(should_connect=True) # now that we checked general connectivity, test that full size frames # can also pass between nodes 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.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) new_server = self._create_server(self.new_net) new_server_ips = [addr['addr'] for addr in new_server['addresses'][self.new_net['name']]] # Assert that pinging the new VM fails since the new network is not # connected to a router self._check_server_connectivity(self.floating_ip_tuple.floating_ip, new_server_ips, 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_server_connectivity(self.floating_ip_tuple.floating_ip, new_server_ips, 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' # Original timeouts are suggested by salvatore-orlando in # https://bugs.launchpad.net/neutron/+bug/1412325/comments/3 # # Compared to that renew_delay was increased, because # busybox's udhcpc accepts SIGUSR1 as a renew request. Internally # it goes into RENEW_REQUESTED state. If it receives a 2nd SIGUSR1 # signal while in that state then it calls the deconfig script # ("/sbin/cirros-dhcpc deconfig" in sufficiently new cirros versions) # which leads to the address being transiently deconfigured which # for our case is unwanted. renew_delay = 3 * 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-23.0.0/tempest/scenario/test_network_v6.py000066400000000000000000000303221361143263300222600ustar00rootroot00000000000000# 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 oslo_log import log as logging 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 from tempest.scenario import manager CONF = config.CONF LOG = logging.getLogger(__name__) 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) # NOTE(slaweq): on RHEL based OS ifcfg file for new interface is # needed to make IPv6 working on it, so if # /etc/sysconfig/network-scripts directory exists ifcfg-%(nic)s file # should be added in it if self._sysconfig_network_scripts_dir_exists(ssh): try: ssh.exec_command( 'echo -e "DEVICE=%(nic)s\\nNAME=%(nic)s\\nIPV6INIT=yes" | ' 'sudo tee /etc/sysconfig/network-scripts/ifcfg-%(nic)s; ' 'sudo nmcli connection reload' % {'nic': nic}) ssh.exec_command('sudo nmcli connection up %s' % nic) except exceptions.SSHExecCommandFailed as e: # NOTE(slaweq): Sometimes it can happen that this SSH command # will fail because of some error from network manager in # guest os. # But even then doing ip link set up below is fine and # IP address should be configured properly. LOG.debug("Error during restarting %(nic)s interface on " "instance. Error message: %(error)s", {'nic': nic, 'error': e}) ssh.exec_command("sudo ip link set %s up" % nic) def _sysconfig_network_scripts_dir_exists(self, ssh): return "False" not in ssh.exec_command( 'test -d /etc/sysconfig/network-scripts/ || echo "False"') 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') @decorators.attr(type='slow') @utils.services('compute', 'network') def test_dualnet_multi_prefix_slaac(self): self._prepare_and_test(address6_mode='slaac', n_subnets6=2, dualnet=True) tempest-23.0.0/tempest/scenario/test_object_storage_basic_ops.py000066400000000000000000000064751361143263300252040ustar00rootroot00000000000000# 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-23.0.0/tempest/scenario/test_security_groups_basic_ops.py000066400000000000000000000704421361143263300254530ustar00rootroot00000000000000# 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: new_host = self.get_host_for_server(server["id"]) host_list = [self.get_host_for_server(s) 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, server=tenant.access_point) 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, ruleset): # if public router isn't defined, then dest_tenant access is via # floating-ip protocol = ruleset['protocol'] 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, protocol=protocol) def _test_cross_tenant_allow(self, source_tenant, dest_tenant, ruleset): """check for each direction: creating rule for tenant incoming traffic enables only 1way traffic """ protocol = ruleset['protocol'] 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, protocol=protocol) # test that reverse traffic is still blocked self._test_cross_tenant_block(dest_tenant, source_tenant, ruleset) # 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, protocol=protocol) 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) def _create_protocol_ruleset(self, protocol, port=80): if protocol == 'icmp': ruleset = dict(protocol='icmp', direction='ingress') else: ruleset = dict(protocol=protocol, port_range_min=port, port_range_max=port, direction='ingress') return ruleset @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 protocol = CONF.scenario.protocol LOG.debug("Testing cross tenant traffic for %s protocol", protocol) if protocol in ['udp', 'tcp']: for tenant in [source_tenant, dest_tenant]: access_point = self._connect_to_access_point(tenant) access_point.nc_listen_host(protocol=protocol) ruleset = self._create_protocol_ruleset(protocol) self._test_cross_tenant_block(source_tenant, dest_tenant, ruleset) self._test_cross_tenant_allow(source_tenant, dest_tenant, ruleset) 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-23.0.0/tempest/scenario/test_server_advanced_ops.py000066400000000000000000000042501361143263300241710ustar00rootroot00000000000000# 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: * 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-23.0.0/tempest/scenario/test_server_basic_ops.py000066400000000000000000000142121361143263300235040ustar00rootroot00000000000000# 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.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 """ 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 if floating_ips is enabled if (CONF.network_feature_enabled.floating_ips and CONF.network.floating_network_name): self.ip = self.create_floating_ip(self.instance)['ip'] else: server = self.servers_client.show_server( self.instance['id'])['server'] self.ip = self.get_server_ip(server) # Check ssh self.ssh_client = self.get_remote_client( ip_address=self.ip, 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.ip)) self.assertEqual(self.ip, 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 verify_metadata_on_config_drive(self): if self.run_ssh and CONF.compute_feature_enabled.config_drive: # Verify metadata on config_drive self.ssh_client.mount_config_drive() cmd_md = 'sudo cat /mnt/openstack/latest/meta_data.json' result = self.ssh_client.exec_command(cmd_md) self.ssh_client.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.ssh_client.mount_config_drive() cmd_md = 'sudo cat /mnt/openstack/latest/network_data.json' result = self.ssh_client.exec_command(cmd_md) self.ssh_client.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-23.0.0/tempest/scenario/test_server_multinode.py000066400000000000000000000071651361143263300235530ustar00rootroot00000000000000# 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-23.0.0/tempest/scenario/test_shelve_instance.py000066400000000000000000000071331361143263300233320ustar00rootroot00000000000000# 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'], server=server) # 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'], server=server) 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-23.0.0/tempest/scenario/test_snapshot_pattern.py000066400000000000000000000057741361143263300235650ustar00rootroot00000000000000# 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'], server=server) # 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'], server=server_from_snapshot) self.assertEqual(timestamp, timestamp2) tempest-23.0.0/tempest/scenario/test_stamp_pattern.py000066400000000000000000000137731361143263300230500ustar00rootroot00000000000000# 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.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) # Make sure the machine ssh-able before attaching the volume self.get_remote_client(ip_for_server, private_key=keypair['private_key'], server=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'], server=server) 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) # Make sure the machine ssh-able before attaching the volume # Just a live machine is responding # for device attache/detach as expected self.get_remote_client(ip_for_snapshot, private_key=keypair['private_key'], server=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'], server=server_from_snapshot) self.assertEqual(timestamp, timestamp2) tempest-23.0.0/tempest/scenario/test_volume_backup_restore.py000066400000000000000000000071121361143263300245540ustar00rootroot00000000000000# Copyright 2018 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.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 TestVolumeBackupRestore(manager.ScenarioTest): """Test cinder backup and restore This testcase verifies content preservation after backup and restore operations by booting a server from a restored backup and check the connectivity to it. The following is the scenario outline: 1. Create volume from image. 2. Create a backup for the volume. 3. Restore the backup. 4. Boot a server from the restored backup. 5. Create a floating ip. 6. Check server connectivity. """ @classmethod def skip_checks(cls): super(TestVolumeBackupRestore, cls).skip_checks() if not CONF.volume_feature_enabled.backup: raise cls.skipException('Backup is not enable.') @decorators.idempotent_id('2ce5e55c-4085-43c1-98c6-582525334ad7') @decorators.attr(type='slow') @utils.services('compute', 'volume', 'image') def test_volume_backup_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']) waiters.wait_for_volume_resource_status(self.volumes_client, volume['id'], 'available') # 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']) # Create keypair and security group keypair = self.create_keypair() security_group = self._create_security_group() # Boot a server from the restored backup bd_map_v2 = [{ 'uuid': restored_volume_id, 'source_type': 'volume', 'destination_type': 'volume', 'boot_index': 0}] server = self.create_server(image_id='', block_device_mapping_v2=bd_map_v2, key_name=keypair['name'], security_groups=[ {'name': security_group['name']}]) # Create a floating ip floating_ip = self.create_floating_ip(server) # Check server connectivity self.check_vm_connectivity(floating_ip['ip'], username=CONF.validation.image_ssh_user, private_key=keypair['private_key'], should_connect=True) tempest-23.0.0/tempest/scenario/test_volume_boot_pattern.py000066400000000000000000000330031361143263300242420ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 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 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, name=None): 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)) if name: create_kwargs['name'] = name 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']) def _delete_snapshot(self, snapshot_id): self.snapshots_client.delete_snapshot(snapshot_id) self.snapshots_client.wait_for_resource_deletion(snapshot_id) @decorators.idempotent_id('557cd2c2-4eb8-4dce-98be-f86765ff311b') @decorators.attr(type='slow') # Note: This test is being skipped based on 'public_network_id'. # It is being used in create_floating_ip() method which gets called # from get_server_ip() method @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') @testtools.skipUnless(CONF.volume_feature_enabled.snapshot, 'Cinder volume snapshots are disabled') @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'], server=instance_1st) # 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'], server=instance_2nd) 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'], server=server_from_snapshot) self.assertEqual(timestamp, timestamp3) @decorators.idempotent_id('05795fb2-b2a7-4c9f-8fac-ff25aedb1489') @decorators.attr(type='slow') @testtools.skipUnless(CONF.volume_feature_enabled.snapshot, 'Cinder volume snapshots are disabled') @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') @testtools.skipUnless(CONF.volume_feature_enabled.snapshot, 'Cinder volume snapshots are disabled') @utils.services('compute', 'volume', 'image') def test_image_defined_boot_from_volume(self): # create an instance from image-backed volume volume_origin = self._create_volume_from_image() name = data_utils.rand_name(self.__class__.__name__ + '-volume-backed-server') instance1 = self._boot_instance_from_resource( source_id=volume_origin['id'], source_type='volume', delete_on_termination=True, name=name) # Create a snapshot image from the volume-backed server. # The compute service will have the block service create a snapshot of # the root volume and store its metadata in the image. image = self.create_server_snapshot(instance1) # Create a server from the image snapshot which has an # "image-defined block device mapping (BDM)" in it, i.e. the metadata # about the volume snapshot. The compute service will use this to # create a volume from the volume snapshot and use that as the root # disk for the server. name = data_utils.rand_name(self.__class__.__name__ + '-image-snapshot-server') instance2 = self.create_server(image_id=image['id'], name=name) # Verify the server was created from the image-defined BDM. volume_attachments = instance2['os-extended-volumes:volumes_attached'] self.assertEqual(1, len(volume_attachments), "No volume attachment found.") created_volume = self.volumes_client.show_volume( volume_attachments[0]['id'])['volume'] # Assert that the volume service also shows the server attachment. self.assertEqual(1, len(created_volume['attachments']), "No server attachment found for volume: %s" % created_volume) self.assertEqual(instance2['id'], created_volume['attachments'][0]['server_id']) self.assertEqual(volume_attachments[0]['id'], created_volume['attachments'][0]['volume_id']) self.assertEqual( volume_origin['volume_image_metadata']['image_id'], created_volume['volume_image_metadata']['image_id']) # Delete the second server which should also delete the second volume # created from the volume snapshot. self._delete_server(instance2) # Assert that the underlying volume is gone. self.volumes_client.wait_for_resource_deletion(created_volume['id']) # Delete the volume snapshot. We must do this before deleting the first # server created in this test because the snapshot depends on the first # instance's underlying volume (volume_origin). # In glance v2, the image properties are flattened and in glance v1, # the image properties are under the 'properties' key. bdms = image.get('block_device_mapping') if not bdms: bdms = image['properties']['block_device_mapping'] bdms = json.loads(bdms) snapshot_id = bdms[0]['snapshot_id'] self._delete_snapshot(snapshot_id) # Now, delete the first server which will also delete the first # image-backed volume. self._delete_server(instance1) # Assert that the underlying volume is gone. self.volumes_client.wait_for_resource_deletion(volume_origin['id']) @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-23.0.0/tempest/scenario/test_volume_migrate_attached.py000066400000000000000000000234071361143263300250360ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 * Check the type of the volume has been updated. * Check the volume is still in-use and the migration was successful. * Check that the same volume is attached to the instance. """ credentials = ['primary', 'admin'] @classmethod def setup_clients(cls): super(TestVolumeMigrateRetypeAttached, cls).setup_clients() cls.admin_volumes_client = cls.os_admin.volumes_client_latest @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 ({'name': source_body['name'], 'host': backend_source}, {'name': dest_body['name'], 'host': backend_dest}) def _volume_retype_with_migration(self, volume_id, new_volume_type): # NOTE: The 'on-demand' migration requires admin operation, so # admin_volumes_client() should be used here. migration_policy = 'on-demand' self.admin_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_retype_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_id = self.create_volume(imageRef=CONF.compute.image_ref, volume_type=source_type['name'])['id'] instance = self._boot_instance_from_volume(volume_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'], server=instance) # retype volume with migration from backend #1 to backend #2 LOG.info("Retyping Volume %s to new type %s", volume_id, dest_type['name']) # This method calls for the retype of the volume before calling a # waiter that asserts that the volume type has changed successfully. self._volume_retype_with_migration(volume_id, dest_type['name']) # 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'], server=instance) self.assertEqual(timestamp, timestamp2) # Assert that the volume is on the new host, is still in-use and has a # migration_status of success volume = self.admin_volumes_client.show_volume(volume_id)['volume'] # dest_type is host@backend, os-vol-host-attr:host is host@backend#type self.assertIn(dest_type['host'], volume['os-vol-host-attr:host']) self.assertEqual('in-use', volume['status']) self.assertEqual('success', volume['migration_status']) # Assert that the same volume id is attached to the instance, ensuring # the os-migrate_volume_completion Cinder API has been called. attached_volumes = self.servers_client.list_volume_attachments( instance['id'])['volumeAttachments'] self.assertEqual(volume_id, attached_volumes[0]['id']) @decorators.attr(type='slow') @decorators.idempotent_id('fe47b1ed-640e-4e3b-a090-200e25607362') @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() LOG.info("Creating volume") # Create a unique volume type to avoid using the backend default migratable_type = self.create_volume_type()['name'] volume_id = self.create_volume(imageRef=CONF.compute.image_ref, volume_type=migratable_type)['id'] volume = self.admin_volumes_client.show_volume(volume_id) LOG.info("Booting instance from volume") instance = self._boot_instance_from_volume(volume_id, keypair, security_group) # Identify the source and destination hosts for the migration src_host = volume['volume']['os-vol-host-attr:host'] # Select the first c-vol host that isn't hosting the volume as the dest # host['host_name'] should take the format of host@backend. # src_host should take the format of host@backend#type hosts = self.admin_volumes_client.list_hosts()['hosts'] for host in hosts: if (host['service'] == 'cinder-volume' and not src_host.startswith(host['host_name'])): dest_host = host['host_name'] break ip_instance = self.get_server_ip(instance) timestamp = self.create_timestamp(ip_instance, private_key=keypair['private_key'], server=instance) LOG.info("Migrating Volume %s from host %s to host %s", volume_id, src_host, dest_host) self.admin_volumes_client.migrate_volume(volume_id, host=dest_host) # This waiter asserts that the migration_status is success and that # the volume has moved to the dest_host waiters.wait_for_volume_migration(self.admin_volumes_client, volume_id, dest_host) # 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'], server=instance) self.assertEqual(timestamp, timestamp2) # Assert that the volume is in-use volume = self.admin_volumes_client.show_volume(volume_id)['volume'] self.assertEqual('in-use', volume['status']) # Assert that the same volume id is attached to the instance, ensuring # the os-migrate_volume_completion Cinder API has been called attached_volumes = self.servers_client.list_volume_attachments( instance['id'])['volumeAttachments'] attached_volume_id = attached_volumes[0]['id'] self.assertEqual(volume_id, attached_volume_id) tempest-23.0.0/tempest/services/000077500000000000000000000000001361143263300165635ustar00rootroot00000000000000tempest-23.0.0/tempest/services/__init__.py000066400000000000000000000000001361143263300206620ustar00rootroot00000000000000tempest-23.0.0/tempest/services/orchestration/000077500000000000000000000000001361143263300214475ustar00rootroot00000000000000tempest-23.0.0/tempest/services/orchestration/__init__.py000066400000000000000000000013561361143263300235650ustar00rootroot00000000000000# 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-23.0.0/tempest/services/orchestration/json/000077500000000000000000000000001361143263300224205ustar00rootroot00000000000000tempest-23.0.0/tempest/services/orchestration/json/__init__.py000066400000000000000000000000001361143263300245170ustar00rootroot00000000000000tempest-23.0.0/tempest/services/orchestration/json/orchestration_client.py000066400000000000000000000407111361143263300272170ustar00rootroot00000000000000# 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-23.0.0/tempest/test.py000066400000000000000000001145101361143263300162730ustar00rootroot00000000000000# 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 import base as lib_base from tempest.lib.common import fixed_network from tempest.lib.common import profiler 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" " %s", 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 # TODO(gmann): cls.handle_skip_exception is really workaround for # testtools bug- https://github.com/testing-cabal/testtools/issues/272 # stestr which is used by Tempest internally to run the test switch # the customize test runner(which use stdlib unittest) for >=py3.5 # else testtools.run.- https://github.com/mtreinish/stestr/pull/265 # These two test runner are not compatible due to skip exception # handling(due to unittest2). testtools.run treat unittestt.SkipTest # as error and stdlib unittest treat unittest2.case.SkipTest raised # by testtools.TestCase.skipException. # The below workaround can be removed once testtools fix issue# 272. orig_skip_exception = testtools.TestCase.skipException lib_base._handle_skip_exception() try: 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__) # 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 finally: testtools.TestCase.skipException = orig_skip_exception @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() if CONF.profiler.key: profiler.disable() @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)) if CONF.profiler.key: profiler.enable(CONF.profiler.key) @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.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-23.0.0/tempest/test_discover/000077500000000000000000000000001361143263300176155ustar00rootroot00000000000000tempest-23.0.0/tempest/test_discover/__init__.py000066400000000000000000000000001361143263300217140ustar00rootroot00000000000000tempest-23.0.0/tempest/test_discover/plugins.py000066400000000000000000000204461361143263300216560ustar00rootroot00000000000000# 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-23.0.0/tempest/test_discover/test_discover.py000066400000000000000000000036761361143263300230600ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/000077500000000000000000000000001361143263300161025ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/README.rst000066400000000000000000000020371361143263300175730ustar00rootroot00000000000000.. _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-23.0.0/tempest/tests/__init__.py000066400000000000000000000000001361143263300202010ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/api/000077500000000000000000000000001361143263300166535ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/api/__init__.py000066400000000000000000000000001361143263300207520ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/api/compute/000077500000000000000000000000001361143263300203275ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/api/compute/__init__.py000066400000000000000000000000001361143263300224260ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/api/compute/test_base.py000066400000000000000000000225601361143263300226570ustar00rootroot00000000000000# 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)) if compute_base.BaseV2ComputeTest.is_requested_microversion_compatible( '2.35'): status = 'ACTIVE' else: status = 'active' wait_for_image_status.assert_called_once_with( compute_images_client, image_id, status) 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') def _test_version_compatible(self, max_version, expected=True): actual = (compute_base.BaseV2ComputeTest. is_requested_microversion_compatible(max_version)) self.assertEqual(expected, actual) def test_check_lower_version(self): compute_base.BaseV2ComputeTest.request_microversion = '2.8' self._test_version_compatible('2.40') def test_check_euqal_version(self): compute_base.BaseV2ComputeTest.request_microversion = '2.40' self._test_version_compatible('2.40') def test_check_higher_version(self): compute_base.BaseV2ComputeTest.request_microversion = '2.41' self._test_version_compatible('2.40', expected=False) tempest-23.0.0/tempest/tests/base.py000066400000000000000000000043251361143263300173720ustar00rootroot00000000000000# 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, *args, **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 *args: passed as-is to `mock.patch`. :param **kwargs: passed as-is to `mock.patch`. See mock documentation for more details: https://docs.python.org/3.5/library/unittest.mock.html#unittest.mock.patch """ p = mock.patch(target, *args, **kwargs) m = p.start() self.addCleanup(p.stop) return m def patchobject(self, target, attribute, *args, **kwargs): """Convenient wrapper around `mock.patch.object` Returns a started mock that will be automatically stopped after the test ran. :param target: object to have the attribute patched :param attribute: name of the attribute to be patched :param *args: passed as-is to `mock.patch.object`. :param **kwargs: passed as-is to `mock.patch.object`. See mock documentation for more details: https://docs.python.org/3.5/library/unittest.mock.html#unittest.mock.patch.object """ p = mock.patch.object(target, attribute, *args, **kwargs) m = p.start() self.addCleanup(p.stop) return m tempest-23.0.0/tempest/tests/cmd/000077500000000000000000000000001361143263300166455ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/cmd/__init__.py000066400000000000000000000000001361143263300207440ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/cmd/sample_streams/000077500000000000000000000000001361143263300216645ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/cmd/sample_streams/calls.subunit000066400000000000000000000161431361143263300244020ustar00rootroot00000000000000³+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-23.0.0/tempest/tests/cmd/test_account_generator.py000066400000000000000000000364251361143263300237720ustar00rootroot00000000000000# 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) self.assertEqual(self.opts.os_username, admin_creds.username) self.assertEqual(self.opts.os_password, admin_creds.password) 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('operator_role', 'fake_operator', group='object-storage') cfg.CONF.set_default('reseller_admin_role', 'fake_reseller', group='object-storage') resources = account_generator.generate_resources( self.cred_provider, admin=False) resource_types = [k for k, _ in resources] # No admin, 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('operator_role', 'fake_operator', group='object-storage') cfg.CONF.set_default('reseller_admin_role', 'fake_reseller', group='object-storage') resources = account_generator.generate_resources( self.cred_provider, admin=True) resource_types = [k for k, _ in resources] # Admin, 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_admin(self): cfg.CONF.set_default('swift', 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') resources = account_generator.generate_resources( self.cred_provider, admin=True) resource_types = [k for k, _ in resources] # all options on, expect five credentials self.assertEqual(5, len(resources)) # Ensure create_user was invoked 5 times (5 distinct users) self.assertEqual(5, 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) for resource in resources: self.assertIsNotNone(resource[1].network) self.assertIsNotNone(resource[1].router) self.assertIsNotNone(resource[1].subnet) def test_generate_resources_swift_no_admin(self): cfg.CONF.set_default('swift', 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') resources = account_generator.generate_resources( self.cred_provider, admin=False) resource_types = [k for k, _ in resources] # No Admin, swift, expect four credentials only self.assertEqual(4, len(resources)) # Ensure create_user was invoked 4 times (4 distinct users) self.assertEqual(4, self.user_create_fixture.mock.call_count) self.assertIn('primary', resource_types) self.assertIn('alt', resource_types) self.assertNotIn('admin', resource_types) self.assertIn(['fake_operator'], resource_types) self.assertIn(['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) 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') 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(5, 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(2, 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(5, 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(2, 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-23.0.0/tempest/tests/cmd/test_cleanup.py000066400000000000000000000030301361143263300217010ustar00rootroot00000000000000# Copyright 2018 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 mock from tempest.cmd import cleanup from tempest.tests import base class TestTempestCleanup(base.TestCase): def test_load_json(self): # instantiate "empty" TempestCleanup c = cleanup.TempestCleanup(None, None, 'test') test_saved_json = 'tempest/tests/cmd/test_saved_state_json.json' # test if the file is loaded without any issues/exceptions c._load_json(test_saved_json) @mock.patch('tempest.cmd.cleanup.TempestCleanup.init') @mock.patch('tempest.cmd.cleanup.TempestCleanup._cleanup') def test_take_action_got_exception(self, mock_cleanup, mock_init): c = cleanup.TempestCleanup(None, None, 'test') c.GOT_EXCEPTIONS.append('exception') mock_cleanup.return_value = True mock_init.return_value = True try: c.take_action(mock.Mock()) except Exception as exc: self.assertEqual(str(exc), '[\'exception\']') return assert False tempest-23.0.0/tempest/tests/cmd/test_cleanup_services.py000066400000000000000000001676531361143263300236320ustar00rootroot00000000000000# Copyright 2018 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 oslo_serialization import jsonutils as json from tempest import clients from tempest.cmd import cleanup_service from tempest import config from tempest.lib import exceptions from tempest.tests import base from tempest.tests import fake_config from tempest.tests.lib import fake_credentials from tempest.tests.lib import fake_http class TestBaseService(base.TestCase): class TestException(cleanup_service.BaseService): def delete(self): raise exceptions.NotImplemented def dry_run(self): raise exceptions.NotImplemented def save_state(self): raise exceptions.NotImplemented def test_base_service_init(self): kwargs = {'data': {'data': 'test'}, 'is_dry_run': False, 'saved_state_json': {'saved': 'data'}, 'is_preserve': False, 'is_save_state': True, 'tenant_id': 'project_id', 'got_exceptions': []} base = cleanup_service.BaseService(kwargs) self.assertEqual(base.data, kwargs['data']) self.assertFalse(base.is_dry_run) self.assertEqual(base.saved_state_json, kwargs['saved_state_json']) self.assertFalse(base.is_preserve) self.assertTrue(base.is_save_state) self.assertEqual(base.tenant_filter['project_id'], kwargs['tenant_id']) self.assertEqual(base.got_exceptions, kwargs['got_exceptions']) def test_not_implemented_ex(self): kwargs = {'data': {'data': 'test'}, 'is_dry_run': False, 'saved_state_json': {'saved': 'data'}, 'is_preserve': False, 'is_save_state': False, 'tenant_id': 'project_id', 'got_exceptions': []} base = self.TestException(kwargs) # delete base.run() self.assertEqual(len(base.got_exceptions), 1) # save_state base.save_state = True base.run() self.assertEqual(len(base.got_exceptions), 2) # dry_run base.is_dry_run = True base.run() self.assertEqual(len(base.got_exceptions), 3) class MockFunctionsBase(base.TestCase): def _create_response(self, body, status, headers): if status: if body: body = json.dumps(body) resp = fake_http.fake_http_response(headers, status=status), body return resp else: return body def _create_fixtures(self, fixtures_to_make): mocked_fixtures = [] for fixture in fixtures_to_make: func, body, status = fixture mocked_response = self._create_response(body, status, None) if mocked_response == 'error': mocked_func = self.useFixture(fixtures.MockPatch( func, side_effect=Exception("error"))) else: mocked_func = self.useFixture(fixtures.MockPatch( func, return_value=mocked_response)) mocked_fixtures.append(mocked_func) return mocked_fixtures def run_function_with_mocks(self, function_to_run, functions_to_mock): """Mock a service client function for testing. :param function_to_run: The service client function to call. :param functions_to_mock: a list of tuples containing the function to mock, the response body, and the response status. EX: ('tempest.lib.common.rest_client.RestClient.get', {'users': ['']}, 200) """ mocked_fixtures = self._create_fixtures(functions_to_mock) func_return = function_to_run() return func_return, mocked_fixtures class BaseCmdServiceTests(MockFunctionsBase): def setUp(self): super(BaseCmdServiceTests, self).setUp() self.useFixture(fake_config.ConfigFixture()) self.patchobject(config, 'TempestConfigPrivate', fake_config.FakePrivate) self.useFixture(fixtures.MockPatch( 'tempest.cmd.cleanup_service._get_network_id', return_value='')) cleanup_service.init_conf() self.conf_values = {"flavors": cleanup_service.CONF_FLAVORS[0], "images": cleanup_service.CONF_IMAGES[0], "projects": cleanup_service.CONF_PROJECTS[0], "users": cleanup_service.CONF_USERS[0], "networks": cleanup_service.CONF_PUB_NETWORK, "security_groups": cleanup_service.CONF_PROJECTS[0], "ports": cleanup_service.CONF_PUB_NETWORK, "routers": cleanup_service.CONF_PUB_ROUTER, "subnetpools": cleanup_service.CONF_PROJECTS[0], } saved_state = { # Static list to ensure global service saved items are not deleted "users": {u'32rwef64245tgr20121qw324bgg': u'Lightning'}, "flavors": {u'42': u'm1.tiny'}, "images": {u'34yhwr-4t3q': u'stratus-0.3.2-x86_64-disk'}, "roles": {u'3efrt74r45hn': u'president'}, "projects": {u'f38ohgp93jj032': u'manhattan'}, "domains": {u'default': u'Default'}, # Static list to ensure project service saved items are not deleted "snapshots": {u'1ad4c789-7e8w-4dwg-afc5': u'saved-snapshot'}, "servers": {u'7a6d4v7w-36ds-4216': u'saved-server'}, "server_groups": {u'as6d5f7g-46ca-475e': u'saved-server-group'}, "keypairs": {u'saved-key-pair': { u'fingerprint': u'7e:eb:ab:24', u'name': u'saved-key-pair' }}, "volumes": {u'aa77asdf-1234': u'saved-volume'}, "networks": {u'6722fc13-4319': { u'id': u'6722fc13-4319', u'name': u'saved-network' }}, "floatingips": {u'9e82d248-408a': { u'id': u'9e82d248-408a', u'status': u'ACTIVE' }}, "routers": {u'4s5w34hj-id44': u'saved-router'}, "metering_label_rules": {u'93a973ce-4dc5': { u'direction': u'ingress', u'id': u'93a973ce-4dc5' }}, "metering_labels": {u'723b346ce866-4c7q': u'saved-label'}, "ports": {u'aa74aa4v-741a': u'saved-port'}, "security_groups": {u'7q844add-3697': u'saved-sec-group'}, "subnets": {u'55ttda4a-2584': u'saved-subnet'}, "subnetpools": {u'8acf64c1-43fc': u'saved-subnet-pool'}, "regions": {u'RegionOne': {}} } # Mocked methods get_method = 'tempest.lib.common.rest_client.RestClient.get' delete_method = 'tempest.lib.common.rest_client.RestClient.delete' log_method = 'tempest.cmd.cleanup_service.LOG.exception' # Override parameters service_class = 'BaseService' response = None service_name = 'default' def _create_cmd_service(self, service_type, is_save_state=False, is_preserve=False, is_dry_run=False): creds = fake_credentials.FakeKeystoneV3Credentials() os = clients.Manager(creds) return getattr(cleanup_service, service_type)( os, is_save_state=is_save_state, is_preserve=is_preserve, is_dry_run=is_dry_run, data={}, saved_state_json=self.saved_state ) def _test_delete(self, mocked_fixture_tuple_list, fail=False): serv = self._create_cmd_service(self.service_class) resp, fixtures = self.run_function_with_mocks( serv.run, mocked_fixture_tuple_list, ) for fixture in fixtures: if fixture.mock.return_value == 'validate': fixture.mock.assert_called() elif fail is False and fixture.mock.return_value == 'exception': fixture.mock.assert_not_called() elif self.service_name in self.saved_state.keys(): fixture.mock.assert_called_once() for key in self.saved_state[self.service_name].keys(): self.assertNotIn(key, fixture.mock.call_args[0][0]) else: fixture.mock.assert_called_once() self.assertFalse(serv.data) def _test_dry_run_true(self, mocked_fixture_tuple_list): serv = self._create_cmd_service(self.service_class, is_dry_run=True) _, fixtures = self.run_function_with_mocks( serv.run, mocked_fixture_tuple_list ) for fixture in fixtures: if fixture.mock.return_value == 'delete': fixture.mock.assert_not_called() elif self.service_name in self.saved_state.keys(): fixture.mock.assert_called_once() for key in self.saved_state[self.service_name].keys(): self.assertNotIn(key, fixture.mock.call_args[0][0]) else: fixture.mock.assert_called_once() def _test_saved_state_true(self, mocked_fixture_tuple_list): serv = self._create_cmd_service(self.service_class, is_save_state=True) _, fixtures = self.run_function_with_mocks( serv.run, mocked_fixture_tuple_list ) for item in self.response[self.service_name]: self.assertIn(item['id'], serv.data[self.service_name]) for fixture in fixtures: fixture.mock.assert_called_once() def _test_is_preserve_true(self, mocked_fixture_tuple_list): serv = self._create_cmd_service(self.service_class, is_preserve=True) resp, fixtures = self.run_function_with_mocks( serv.list, mocked_fixture_tuple_list ) for fixture in fixtures: fixture.mock.assert_called_once() self.assertIn(resp[0], self.response[self.service_name]) for rsp in resp: self.assertNotIn(rsp['id'], self.conf_values.values()) self.assertNotIn(rsp['name'], self.conf_values.values()) class TestSnapshotService(BaseCmdServiceTests): service_class = 'SnapshotService' service_name = 'snapshots' response = { "snapshots": [ { "status": "available", "metadata": { "name": "test" }, "name": "test-volume-snapshot", "user_id": "40c2102f4a554b848d96b14f3eec39ed", "volume_id": "173f7b48-c4c1-4e70-9acc-086b39073506", "created_at": "2015-11-29T02:25:51.000000", "size": 1, "updated_at": "2015-11-20T05:36:40.000000", "os-extended-snapshot-attributes:progress": "100%", "id": "b1323cda-8e4b-41c1-afc5-2fc791809c8c", "description": "volume snapshot" }, { "status": "available", "name": "saved-snapshot", "id": "1ad4c789-7e8w-4dwg-afc5", "description": "snapshot in saved state" } ] } def test_delete_fail(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, 'error', None), (self.log_method, 'exception', None)] self._test_delete(delete_mock, fail=True) def test_delete_pass(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, None, 202), (self.log_method, 'exception', None)] self._test_delete(delete_mock) def test_dry_run(self): dry_mock = [(self.get_method, self.response, 200), (self.delete_method, "delete", None)] self._test_dry_run_true(dry_mock) def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) class TestServerService(BaseCmdServiceTests): service_class = 'ServerService' service_name = 'servers' response = { "servers": [ { "id": "22c91117-08de-4894-9aa9-6ef382400985", "links": [ { "href": "http://openstack.example.com/v2/6f70-6ef0985", "rel": "self" }, { "href": "http://openstack.example.com/6f70656e7-6ef35", "rel": "bookmark" } ], "name": "new-server-test" }, { "id": "7a6d4v7w-36ds-4216", "links": [ { "href": "http://openstack.example.com/v2/6f70-6ef0985", "rel": "self" }, { "href": "http://openstack.example.com/6f70656e7-6ef35", "rel": "bookmark" } ], "name": "saved-server" } ] } def test_delete_fail(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, 'error', None), (self.log_method, 'exception', None)] self._test_delete(delete_mock, fail=True) def test_delete_pass(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, None, 204), (self.log_method, 'exception', None)] self._test_delete(delete_mock) def test_dry_run(self): dry_mock = [(self.get_method, self.response, 200), (self.delete_method, "delete", None)] self._test_dry_run_true(dry_mock) def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) class TestServerGroupService(BaseCmdServiceTests): service_class = 'ServerGroupService' service_name = 'server_groups' validate_response = ('tempest.lib.services.compute.server_groups_client' '.ServerGroupsClient.validate_response') response = { "server_groups": [ { "id": "616fb98f-46ca-475e-917e-2563e5a8cd19", "name": "test", "policy": "anti-affinity", "rules": {"max_server_per_host": 3}, "members": [], "project_id": "6f70656e737461636b20342065766572", "user_id": "fake" }, { "id": "as6d5f7g-46ca-475e", "name": "saved-server-group" } ] } def test_delete_fail(self): delete_mock = [(self.get_method, self.response, 200), (self.validate_response, 'validate', None), (self.delete_method, 'error', None), (self.log_method, 'exception', None)] self._test_delete(delete_mock, fail=True) def test_delete_pass(self): delete_mock = [(self.get_method, self.response, 200), (self.validate_response, 'validate', None), (self.delete_method, None, 204), (self.log_method, 'exception', None)] self._test_delete(delete_mock) def test_dry_run(self): dry_mock = [(self.get_method, self.response, 200), (self.validate_response, 'validate', None), (self.delete_method, "delete", None)] self._test_dry_run_true(dry_mock) def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200), (self.validate_response, 'validate', None) ]) class TestKeyPairService(BaseCmdServiceTests): service_class = 'KeyPairService' service_name = 'keypairs' validate_response = ('tempest.lib.services.compute.keypairs_client' '.KeyPairsClient.validate_response') response = { "keypairs": [ { "keypair": { "fingerprint": "7e:eb:ab:24:ba:d1:e1:88:ae:9a:fb:66:53:bd", "name": "keypair-5d935425-31d5-48a7-a0f1-e76e9813f2c3", "type": "ssh", "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkF\n" } }, { "keypair": { "fingerprint": "7e:eb:ab:24", "name": "saved-key-pair" } } ] } def _test_saved_state_true(self, mocked_fixture_tuple_list): serv = self._create_cmd_service(self.service_class, is_save_state=True) _, fixtures = self.run_function_with_mocks( serv.run, mocked_fixture_tuple_list ) for item in self.response[self.service_name]: self.assertTrue(item['keypair']['name'], serv.data[self.service_name]) for fixture in fixtures: fixture.mock.assert_called_once() def test_delete_fail(self): delete_mock = [(self.get_method, self.response, 200), (self.validate_response, 'validate', None), (self.delete_method, 'error', None), (self.log_method, 'exception', None)] self._test_delete(delete_mock, fail=True) def test_delete_pass(self): delete_mock = [(self.get_method, self.response, 200), (self.validate_response, 'validate', None), (self.delete_method, None, 204), (self.log_method, 'exception', None)] self._test_delete(delete_mock) def test_dry_run(self): dry_mock = [(self.get_method, self.response, 200), (self.validate_response, 'validate', None), (self.delete_method, "delete", None)] self._test_dry_run_true(dry_mock) def test_save_state(self): self._test_saved_state_true([ (self.get_method, self.response, 200), (self.validate_response, 'validate', None) ]) class TestVolumeService(BaseCmdServiceTests): service_class = 'VolumeService' service_name = 'volumes' response = { "volumes": [ { "id": "efa54464-8fab-47cd-a05a-be3e6b396188", "links": [ { "href": "http://127.0.0.1:37097/v3/89af/volumes/efa54", "rel": "self" }, { "href": "http://127.0.0.1:37097/89af/volumes/efa54464", "rel": "bookmark" } ], "name": "volume-name" }, { "id": "aa77asdf-1234", "name": "saved-volume" } ] } def test_delete_fail(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, 'error', None), (self.log_method, 'exception', None)] self._test_delete(delete_mock, fail=True) def test_delete_pass(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, None, 202), (self.log_method, 'exception', None)] self._test_delete(delete_mock) def test_dry_run(self): dry_mock = [(self.get_method, self.response, 200), (self.delete_method, "delete", None)] self._test_dry_run_true(dry_mock) def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) # Begin network service classes class TestNetworkService(BaseCmdServiceTests): service_class = 'NetworkService' service_name = 'networks' response = { "networks": [ { "admin_state_up": True, "availability_zone_hints": [], "availability_zones": [ "nova" ], "created_at": "2016-03-08T20:19:41", "dns_domain": "my-domain.org.", "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", "l2_adjacency": False, "mtu": 1500, "name": "net1", "port_security_enabled": True, "project_id": "4fd44f30292945e481c7b8a0c8908869", "qos_policy_id": "6a8454ade84346f59e8d40665f878b2e", "revision_number": 1, "router:external": False, "shared": False, "status": "ACTIVE", "subnets": [ "54d6f61d-db07-451c-9ab3-b9609b6b6f0b" ], "tenant_id": "4fd44f30292945e481c7b8a0c8908869", "updated_at": "2016-03-08T20:19:41", "vlan_transparent": True, "description": "", "is_default": False }, { "id": "6722fc13-4319", "name": "saved-network" } ] } def test_delete_fail(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, 'error', None), (self.log_method, 'exception', None)] self._test_delete(delete_mock, fail=True) def test_delete_pass(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, None, 204), (self.log_method, 'exception', None)] self._test_delete(delete_mock) def test_dry_run(self): dry_mock = [(self.get_method, self.response, 200), (self.delete_method, "delete", None)] self._test_dry_run_true(dry_mock) def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) def test_preserve_list(self): self.response['networks'].append( { "admin_state_up": True, "availability_zone_hints": [], "availability_zones": [ "nova" ], "created_at": "2017-03-08T20:19:41", "dns_domain": "my-domain.org.", "id": cleanup_service.CONF_PUB_NETWORK, "name": "net2", "port_security_enabled": True, "project_id": "4fd44f30292945e481c7b8a0c8908869", "qos_policy_id": "6a8454ade84346f59e8d40665f878b2e", "revision_number": 1, "status": "ACTIVE", "subnets": [ "54d6f61d-db07-451c-9ab3-b9609b6b6f0b" ], "tenant_id": "4fd44f30292945e481c7b8a0c8908869", "updated_at": "2018-03-08T20:19:41", "vlan_transparent": True, "is_default": False }) self._test_is_preserve_true([(self.get_method, self.response, 200)]) class TestNetworkFloatingIpService(BaseCmdServiceTests): service_class = 'NetworkFloatingIpService' service_name = 'floatingips' response = { "floatingips": [ { "router_id": "d23abc8d-2991-4a55-ba98-2aaea84cc72f", "description": "for test", "dns_domain": "my-domain.org.", "dns_name": "myfip", "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", "port_details": { "status": "ACTIVE", "name": "", "admin_state_up": True, "network_id": "02dd8479-ef26-4398-a102-d19d0a7b3a1f", "device_owner": "compute:nova", "mac_address": "fa:16:3e:b1:3b:30", "device_id": "8e3941b4-a6e9-499f-a1ac-2a4662025cba" }, "tags": ["tag1,tag2"], "port_forwardings": [] }, { "id": "9e82d248-408a", "status": "ACTIVE" } ] } def test_delete_fail(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, 'error', None), (self.log_method, 'exception', None)] self._test_delete(delete_mock, fail=True) def test_delete_pass(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, None, 204), (self.log_method, 'exception', None)] self._test_delete(delete_mock) def test_dry_run(self): dry_mock = [(self.get_method, self.response, 200), (self.delete_method, "delete", None)] self._test_dry_run_true(dry_mock) def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) class TestNetworkRouterService(BaseCmdServiceTests): service_class = 'NetworkRouterService' service_name = 'routers' validate_response = ('tempest.lib.services.network.routers_client' '.RoutersClient.validate_response') response = { "routers": [ { "admin_state_up": True, "availability_zone_hints": [], "availability_zones": [ "nova" ], "created_at": "2018-03-19T19:17:04Z", "description": "", "distributed": False, "external_gateway_info": { "enable_snat": True, "external_fixed_ips": [ { "ip_address": "172.24.4.3", "subnet_id": "b930d7f6-ceb7-40a0-8b81-a425dd994ccf" }, { "ip_address": "2001:db8::c", "subnet_id": "0c56df5d-ace5-46c8-8f4c-45fa4e334d18" } ], "network_id": "ae34051f-aa6c-4c75-abf5-50dc9ac99ef3" }, "flavor_id": "f7b14d9a-b0dc-4fbe-bb14-a0f4970a69e0", "ha": False, "id": "915a14a6-867b-4af7-83d1-70efceb146f9", "name": "router2", "revision_number": 1, "routes": [ { "destination": "179.24.1.0/24", "nexthop": "172.24.3.99" } ], "status": "ACTIVE", "updated_at": "2018-03-19T19:17:22Z", "project_id": "0bd18306d801447bb457a46252d82d13", "tenant_id": "0bd18306d801447bb457a46252d82d13", "tags": ["tag1,tag2"] }, { "id": "4s5w34hj-id44", "name": "saved-router" } ], # "ports" key is added to the response in order to simplify unit # testing - it's because NetworkRouterService's delete method lists # ports before deleting any router "ports": [] } def _test_delete(self, mocked_fixture_tuple_list, fail=False): serv = self._create_cmd_service(self.service_class) resp, fixtures = self.run_function_with_mocks( serv.run, mocked_fixture_tuple_list, ) for fixture in fixtures: if fail is False and fixture.mock.return_value == 'exception': fixture.mock.assert_not_called() elif self.service_name in self.saved_state.keys(): fixture.mock.assert_called() for key in self.saved_state[self.service_name].keys(): self.assertNotIn(key, fixture.mock.call_args[0][0]) self.assertFalse(serv.data) def test_delete_fail(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, 'error', None), (self.log_method, 'exception', None)] self._test_delete(delete_mock, fail=True) def test_delete_pass(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, None, 204), (self.log_method, 'exception', None)] self._test_delete(delete_mock) def test_dry_run(self): dry_mock = [(self.get_method, self.response, 200), (self.delete_method, "delete", None)] self._test_dry_run_true(dry_mock) def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) def test_preserve_list(self): self.response['routers'].append( { "admin_state_up": True, "availability_zone_hints": [], "availability_zones": [ "nova" ], "created_at": "2018-03-19T19:17:04Z", "id": cleanup_service.CONF_PUB_ROUTER, "name": "router-preserve", "status": "ACTIVE", "updated_at": "2018-03-19T19:17:22Z", "project_id": "0bd18306d801447bb457a46252d82d13", "tenant_id": "0bd18306d801447bb457a46252d82d13", "tags": ["tag1,tag2"] }) self._test_is_preserve_true([(self.get_method, self.response, 200)]) class TestNetworkMeteringLabelRuleService(BaseCmdServiceTests): service_class = 'NetworkMeteringLabelRuleService' service_name = 'metering_label_rules' response = { "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 }, { "direction": "ingress", "id": "93a973ce-4dc5" } ] } def test_delete_fail(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, 'error', None), (self.log_method, 'exception', None)] self._test_delete(delete_mock, fail=True) def test_delete_pass(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, None, 204), (self.log_method, 'exception', None)] self._test_delete(delete_mock) def test_dry_run(self): dry_mock = [(self.get_method, self.response, 200), (self.delete_method, "delete", None)] self._test_dry_run_true(dry_mock) def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) class TestNetworkMeteringLabelService(BaseCmdServiceTests): service_class = 'NetworkMeteringLabelService' service_name = 'metering_labels' response = { "metering_labels": [ { "project_id": "45345b0ee1ea477fac0f541b2cb79cd4", "tenant_id": "45345b0ee1ea477fac0f541b2cb79cd4", "description": "label1 description", "name": "label1", "id": "a6700594-5b7a-4105-8bfe-723b346ce866", "shared": False }, { "name": "saved-label", "id": "723b346ce866-4c7q", } ] } def test_delete_fail(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, 'error', None), (self.log_method, 'exception', None)] self._test_delete(delete_mock, fail=True) def test_delete_pass(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, None, 204), (self.log_method, 'exception', None)] self._test_delete(delete_mock) def test_dry_run(self): dry_mock = [(self.get_method, self.response, 200), (self.delete_method, "delete", None)] self._test_dry_run_true(dry_mock) def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) class TestNetworkPortService(BaseCmdServiceTests): service_class = 'NetworkPortService' service_name = 'ports' response = { "ports": [ { "admin_state_up": True, "allowed_address_pairs": [], "created_at": "2016-03-08T20:19:41", "description": "", "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824", "device_owner": "", "dns_assignment": { "hostname": "myport", "ip_address": "172.24.4.2", "fqdn": "myport.my-domain.org" }, "dns_domain": "my-domain.org.", "dns_name": "myport", "extra_dhcp_opts": [ { "opt_value": "pxelinux.0", "ip_version": 4, "opt_name": "bootfile-name" } ], "fixed_ips": [ { "ip_address": "172.24.4.2", "subnet_id": "008ba151-0b8c-4a67-98b5-0d2b87666062" } ], "id": "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b", "ip_allocation": "immediate", "mac_address": "fa:16:3e:58:42:ed", "name": "test_port", "network_id": "70c1db1f-b701-45bd-96e0-a313ee3430b3", "project_id": "", "revision_number": 1, "security_groups": [], "status": "ACTIVE", "tags": ["tag1,tag2"], "tenant_id": "", "updated_at": "2016-03-08T20:19:41", "qos_policy_id": "29d5e02e-d5ab-4929-bee4-4a9fc12e22ae", "port_security_enabled": False }, { "id": "aa74aa4v-741a", "name": "saved-port", "device_owner": "" } ] } def test_delete_fail(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, 'error', None), (self.log_method, 'exception', None)] self._test_delete(delete_mock, fail=True) def test_delete_pass(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, None, 204), (self.log_method, 'exception', None)] self._test_delete(delete_mock) def test_dry_run(self): dry_mock = [(self.get_method, self.response, 200), (self.delete_method, "delete", None)] self._test_dry_run_true(dry_mock) def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) def test_preserve_list(self): self.response['ports'].append( { "created_at": "2018-03-08T20:19:41", "description": "", "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824", "device_owner": "compute:router_gateway", "id": "d80b1a3b-4fc1-49f3-952e-1fdy1ws542", "ip_allocation": "immediate", "mac_address": "fa:16:3e:58:42:ed", "name": "preserve_port", "network_id": cleanup_service.CONF_PUB_NETWORK, "project_id": "", "security_groups": [], "status": "ACTIVE", "tags": ["tag1,tag2"], "tenant_id": "", "updated_at": "2018-03-08T20:19:41", }) self._test_is_preserve_true([(self.get_method, self.response, 200)]) class TestNetworkSecGroupService(BaseCmdServiceTests): service_class = 'NetworkSecGroupService' service_name = 'security_groups' response = { "security_groups": [ { "description": "default", "id": "85cc3048-abc3-43cc-89b3-377341426ac5", "name": "test", "security_group_rules": [ { "direction": "egress", "ethertype": "IPv6", "id": "3c0e45ff-adaf-4124-b083-bf390e5482ff", "security_group_id": "85cc3048-abc3-43cc-89b3-3773414", "project_id": "e4f50856753b4dc6afee5fa6b9b6c550", "revision_number": 1, "tags": ["tag1,tag2"], "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550", "created_at": "2018-03-19T19:16:56Z", "updated_at": "2018-03-19T19:16:56Z", "description": "" } ] }, { "id": "7q844add-3697", "name": "saved-sec-group" } ] } def test_delete_fail(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, 'error', None), (self.log_method, 'exception', None)] self._test_delete(delete_mock, fail=True) def test_delete_pass(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, None, 204), (self.log_method, 'exception', None)] self._test_delete(delete_mock) def test_dry_run(self): dry_mock = [(self.get_method, self.response, 200), (self.delete_method, "delete", None)] self._test_dry_run_true(dry_mock) def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) def test_preserve_list(self): self.response['security_groups'].append( { "description": "default", "id": "85cc3048-abc3-43cc-89b3-377341426ac5", "name": "test", "security_group_rules": [ { "direction": "egress", "ethertype": "IPv6", "id": "3c0e45ff-adaf-4124-b083-bf390e5482ff", "security_group_id": "85cc3048-abc3-43cc-89b3-3773414", "project_id": cleanup_service.CONF_PROJECTS[0], "revision_number": 1, "tags": ["tag1,tag2"], "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550", "created_at": "2018-03-19T19:16:56Z", "updated_at": "2018-03-19T19:16:56Z", "description": "" } ] }) self._test_is_preserve_true([(self.get_method, self.response, 200)]) class TestNetworkSubnetService(BaseCmdServiceTests): service_class = 'NetworkSubnetService' service_name = 'subnets' response = { "subnets": [ { "name": "private-subnet", "enable_dhcp": True, "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", "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", "revision_number": 2, "service_types": [], "tags": ["tag1,tag2"], "updated_at": "2016-10-10T14:35:34Z" }, { "id": "55ttda4a-2584", "name": "saved-subnet" } ] } def test_delete_fail(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, 'error', None), (self.log_method, 'exception', None)] self._test_delete(delete_mock, fail=True) def test_delete_pass(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, None, 204), (self.log_method, 'exception', None)] self._test_delete(delete_mock) def test_dry_run(self): dry_mock = [(self.get_method, self.response, 200), (self.delete_method, "delete", None)] self._test_dry_run_true(dry_mock) def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) def test_preserve_list(self): self.response['subnets'].append( { "name": "public-subnet", "network_id": cleanup_service.CONF_PUB_NETWORK, "project_id": "26a7980765d0414dbc1fc1f88cdb7e6e", "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", "ip_version": 4, "gateway_ip": "10.0.0.1", "cidr": "10.0.0.0/24", "id": "08eae331-0402-425a-923c-34f7cfe39c1b", "created_at": "2018-10-10T14:35:34Z", "service_types": [], "tags": ["tag1,tag2"], "updated_at": "2018-10-10T14:35:34Z" }) self._test_is_preserve_true([(self.get_method, self.response, 200)]) class TestNetworkSubnetPoolsService(BaseCmdServiceTests): service_class = 'NetworkSubnetPoolsService' service_name = 'subnetpools' response = { "subnetpools": [ { "min_prefixlen": "64", "default_prefixlen": "64", "id": "03f761e6-eee0-43fc-a921-8acf64c14988", "max_prefixlen": "64", "name": "my-subnet-pool-ipv6", "is_default": False, "project_id": "9fadcee8aa7c40cdb2114fff7d569c08", "tenant_id": "9fadcee8aa7c40cdb2114fff7d569c08", "prefixes": [ "2001:db8:0:2::/64", "2001:db8::/63" ], "ip_version": 6, "shared": False, "description": "", "created_at": "2016-03-08T20:19:41", "updated_at": "2016-03-08T20:19:41", "revision_number": 2, "tags": ["tag1,tag2"] }, { "id": "8acf64c1-43fc", "name": "saved-subnet-pool" } ] } def test_delete_fail(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, 'error', None), (self.log_method, 'exception', None)] self._test_delete(delete_mock, fail=True) def test_delete_pass(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, None, 204), (self.log_method, 'exception', None)] self._test_delete(delete_mock) def test_dry_run(self): dry_mock = [(self.get_method, self.response, 200), (self.delete_method, "delete", None)] self._test_dry_run_true(dry_mock) def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) def test_preserve_list(self): self.response['subnetpools'].append( { "min_prefixlen": "64", "default_prefixlen": "64", "id": "9acf64c1-43fc", "name": "preserve-pool", "project_id": cleanup_service.CONF_PROJECTS[0], "created_at": "2016-03-08T20:19:41", "updated_at": "2016-03-08T20:19:41" }) self._test_is_preserve_true([(self.get_method, self.response, 200)]) # begin global services class TestRegionService(BaseCmdServiceTests): service_class = 'RegionService' service_name = 'regions' response = { "regions": [{ "parent_region_id": None, "id": "RegionOne", "links": { "self": "http://10.0.145.61:5000/v3/regions/RegionOne" }, "description": "" }, { "parent_region_id": None, "id": "RegionTwo", "links": { "self": "http://10.0.145.61:5000/v3/regions/RegionTwo" }, "description": "" }], "links": { "self": "http://10.0.145.61:5000/v3/regions", "next": None, "previous": None } } def test_delete_pass(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, None, 204), (self.log_method, "exception", None)] self._test_delete(delete_mock) def test_delete_fail(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, 'error', None), (self.log_method, "exception", None)] self._test_delete(delete_mock, fail=True) def test_dry_run(self): dry_mock = [(self.get_method, self.response, 200), (self.delete_method, "delete", None)] self._test_dry_run_true(dry_mock) def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) class TestDomainService(BaseCmdServiceTests): service_class = 'DomainService' service_name = 'domains' response = { "domains": [ { "description": "Destroy all humans", "enabled": True, "id": "5a75994a3", "links": { "self": "http://example.com/identity/v3/domains/5a75994a3" }, "name": "Sky_net" }, { "description": "Owns users and tenants on Identity API", "enabled": False, "id": "default", "links": { "self": "http://example.com/identity/v3/domains/default" }, "name": "Default" } ] } mock_update = ("tempest.lib.services.identity.v3." "domains_client.DomainsClient.update_domain") def test_delete_fail(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, 'error', None), (self.log_method, 'exception', None), (self.mock_update, 'update', None)] self._test_delete(delete_mock, fail=True) def test_delete_pass(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, None, 204), (self.log_method, 'exception', None), (self.mock_update, 'update', None)] self._test_delete(delete_mock) def test_dry_run(self): dry_mock = [(self.get_method, self.response, 200), (self.delete_method, "delete", None)] self._test_dry_run_true(dry_mock) def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) class TestProjectsService(BaseCmdServiceTests): service_class = 'ProjectService' service_name = 'projects' response = { "projects": [ { "is_domain": False, "description": None, "domain_id": "default", "enabled": True, "id": "f38ohgp93jj032", "links": { "self": "http://example.com/identity/v3/projects" "/f38ohgp93jj032" }, "name": "manhattan", "parent_id": None }, { "is_domain": False, "description": None, "domain_id": "default", "enabled": True, "id": "098f89d3292ri4jf4", "links": { "self": "http://example.com/identity/v3/projects" "/098f89d3292ri4jf4" }, "name": "Apollo", "parent_id": None } ] } def test_delete_fail(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, 'error', None), (self.log_method, 'exception', None)] self._test_delete(delete_mock, fail=True) def test_delete_pass(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, None, 204), (self.log_method, 'exception', None)] self._test_delete(delete_mock) def test_dry_run(self): dry_mock = [(self.get_method, self.response, 200), (self.delete_method, "delete", None)] self._test_dry_run_true(dry_mock) def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) def test_preserve_list(self): self.response['projects'].append( { "is_domain": False, "description": None, "domain_id": "default", "enabled": True, "id": "r343q98h09f3092", "links": { "self": "http://example.com/identity/v3/projects" "/r343q98h09f3092" }, "name": cleanup_service.CONF_PROJECTS[0], "parent_id": None }) self._test_is_preserve_true([(self.get_method, self.response, 200)]) class TestImagesService(BaseCmdServiceTests): service_class = 'ImageService' service_name = 'images' response = { "images": [ { "status": "ACTIVE", "name": "stratus-0.3.2-x86_64-disk", "id": "34yhwr-4t3q", "updated": "2014-11-03T16:40:10Z", "links": [{ "href": "http://openstack.ex.com/v2/openstack/images/" "34yhwr-4t3q", "rel": "self"}], "created": "2014-10-30T08:23:39Z", "minDisk": 0, "minRam": 0, "progress": 0, "metadata": {}, }, { "status": "ACTIVE", "name": "cirros-0.3.2-x86_64-disk", "id": "1bea47ed-f6a9", "updated": "2014-11-03T16:40:10Z", "links": [{ "href": "http://openstack.ex.com/v2/openstack/images/" "1bea47ed-f6a9", "rel": "self"}], "created": "2014-10-30T08:23:39Z", "minDisk": 0, "minRam": 0, "progress": 0, "metadata": {}, } ] } def test_delete_fail(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, 'error', None), (self.log_method, 'exception', None)] self._test_delete(delete_mock, fail=True) def test_delete_pass(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, None, 204), (self.log_method, 'exception', None)] self._test_delete(delete_mock) def test_dry_run(self): dry_mock = [(self.get_method, self.response, 200), (self.delete_method, "delete", None)] self._test_dry_run_true(dry_mock) def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) def test_preserve_list(self): self.response['images'].append( { "status": "ACTIVE", "name": "cirros-0.3.2-x86_64-disk", "id": cleanup_service.CONF_IMAGES[0], "updated": "2014-11-03T16:40:10Z", "links": [{ "href": "http://openstack.ex.com/v2/openstack/images/" "None", "rel": "self"}], "created": "2014-10-30T08:23:39Z", "minDisk": 0, "minRam": 0, "progress": 0, "metadata": {}, }) self._test_is_preserve_true([(self.get_method, self.response, 200)]) class TestFlavorService(BaseCmdServiceTests): service_class = 'FlavorService' service_name = 'flavors' response = { "flavors": [ { "disk": 1, "id": "42", "links": [{ "href": "http://openstack.ex.com/v2/openstack/flavors/1", "rel": "self"}, { "href": "http://openstack.ex.com/openstack/flavors/1", "rel": "bookmark"}], "name": "m1.tiny", "ram": 512, "swap": 1, "vcpus": 1 }, { "disk": 2, "id": "13", "links": [{ "href": "http://openstack.ex.com/v2/openstack/flavors/2", "rel": "self"}, { "href": "http://openstack.ex.com/openstack/flavors/2", "rel": "bookmark"}], "name": "m1.tiny", "ram": 512, "swap": 1, "vcpus": 1 } ] } def test_delete_fail(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, 'error', None), (self.log_method, 'exception', None)] self._test_delete(delete_mock, fail=True) def test_delete_pass(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, None, 202), (self.log_method, 'exception', None)] self._test_delete(delete_mock) def test_dry_run(self): dry_mock = [(self.get_method, self.response, 200), (self.delete_method, "delete", None)] self._test_dry_run_true(dry_mock) def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) def test_preserve_list(self): self.response['flavors'].append( { "disk": 3, "id": cleanup_service.CONF_FLAVORS[0], "links": [{ "href": "http://openstack.ex.com/v2/openstack/flavors/3", "rel": "self"}, { "href": "http://openstack.ex.com/openstack/flavors/3", "rel": "bookmark"}], "name": "m1.tiny", "ram": 512, "swap": 1, "vcpus": 1 }) self._test_is_preserve_true([(self.get_method, self.response, 200)]) class TestRoleService(BaseCmdServiceTests): service_class = 'RoleService' service_name = 'roles' response = { "roles": [ { "domain_id": "FakeDomain", "id": "3efrt74r45hn", "name": "president", "links": { "self": "http://ex.com/identity/v3/roles/3efrt74r45hn" } }, { "domain_id": 'FakeDomain', "id": "39ruo5sdk040", "name": "vice-p", "links": { "self": "http://ex.com/identity/v3/roles/39ruo5sdk040" } } ] } def test_delete_fail(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, 'error', None), (self.log_method, 'exception', None)] self._test_delete(delete_mock, fail=True) def test_delete_pass(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, None, 204), (self.log_method, 'exception', None)] self._test_delete(delete_mock) def test_dry_run(self): dry_mock = [(self.get_method, self.response, 200), (self.delete_method, "delete", None)] self._test_dry_run_true(dry_mock) def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) class TestUserService(BaseCmdServiceTests): service_class = 'UserService' service_name = 'users' response = { "users": [ { "domain_id": "TempestDomain", "enabled": True, "id": "e812fb332456423fdv1b1320121qwe2", "links": { "self": "http://example.com/identity/v3/users/" "e812fb332456423fdv1b1320121qwe2", }, "name": "Thunder", "password_expires_at": "3102-11-06T15:32:17.000000", }, { "domain_id": "TempestDomain", "enabled": True, "id": "32rwef64245tgr20121qw324bgg", "links": { "self": "http://example.com/identity/v3/users/" "32rwef64245tgr20121qw324bgg", }, "name": "Lightning", "password_expires_at": "1893-11-06T15:32:17.000000", } ] } def test_delete_fail(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, 'error', None), (self.log_method, 'exception', None)] self._test_delete(delete_mock, fail=True) def test_delete_pass(self): delete_mock = [(self.get_method, self.response, 200), (self.delete_method, None, 204), (self.log_method, 'exception', None)] self._test_delete(delete_mock) def test_dry_run(self): dry_mock = [(self.get_method, self.response, 200), (self.delete_method, "delete", None)] self._test_dry_run_true(dry_mock) def test_save_state(self): self._test_saved_state_true([(self.get_method, self.response, 200)]) def test_preserve_list(self): self.response['users'].append( { "domain_id": "TempestDomain", "enabled": True, "id": "23ads5tg3rtrhe30121qwhyth", "links": { "self": "http://example.com/identity/v3/users/" "23ads5tg3rtrhe30121qwhyth", }, "name": cleanup_service.CONF_USERS[0], "password_expires_at": "1893-11-06T15:32:17.000000", }) self._test_is_preserve_true([(self.get_method, self.response, 200)]) tempest-23.0.0/tempest/tests/cmd/test_list_plugins.py000066400000000000000000000015661361143263300230020ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/cmd/test_run.py000066400000000000000000000433331361143263300210700ustar00rootroot00000000000000# 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 import six from tempest.cmd import run from tempest.cmd import workspace from tempest import config from tempest.lib.common.utils import data_utils from tempest.tests import base if six.PY2: # Python 2 has not FileNotFoundError exception FileNotFoundError = IOError DEVNULL = open(os.devnull, 'wb') atexit.register(DEVNULL.close) CONF = config.CONF class TestTempestRun(base.TestCase): def setUp(self): super(TestTempestRun, self).setUp() self.run_cmd = run.TempestRun(None, None) def test__build_regex_default(self): args = mock.Mock(spec=argparse.Namespace) setattr(args, 'smoke', False) setattr(args, 'regex', '') self.assertIsNone(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', '') 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') self.assertEqual(['i_am_a_fun_little_regex'], self.run_cmd._build_regex(args)) def test__build_regex_smoke_regex(self): args = mock.Mock(spec=argparse.Namespace) setattr(args, "smoke", True) setattr(args, 'regex', 'i_am_a_fun_little_regex') self.assertEqual(['smoke'], self.run_cmd._build_regex(args)) 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.stestr_conf_file = os.path.join(self.directory, '.stestr.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.stestr_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) return out, err def test_tempest_run_passes(self): self.assertRunExit(['tempest', 'run', '--regex', 'passing'], 0) def test_tempest_run_passes_with_stestr_repository(self): subprocess.call(['stestr', 'init']) self.assertRunExit(['tempest', 'run', '--regex', 'passing'], 0) def test_tempest_run_failing(self): self.assertRunExit(['tempest', 'run', '--regex', 'failing'], 1) def test_tempest_run_failing_with_stestr_repository(self): subprocess.call(['stestr', 'init']) self.assertRunExit(['tempest', 'run', '--regex', 'failing'], 1) def test_tempest_run_blackregex_failing(self): self.assertRunExit(['tempest', 'run', '--black-regex', 'failing'], 0) def test_tempest_run_blackregex_failing_with_stestr_repository(self): subprocess.call(['stestr', 'init']) self.assertRunExit(['tempest', 'run', '--black-regex', 'failing'], 0) def test_tempest_run_blackregex_passing(self): self.assertRunExit(['tempest', 'run', '--black-regex', 'passing'], 1) def test_tempest_run_blackregex_passing_with_stestr_repository(self): subprocess.call(['stestr', 'init']) self.assertRunExit(['tempest', 'run', '--black-regex', 'passing'], 1) def test_tempest_run_fails(self): self.assertRunExit(['tempest', 'run'], 1) def test_run_list(self): subprocess.call(['stestr', 'init']) out, err = self.assertRunExit(['tempest', 'run', '-l'], 0) tests = out.split() tests = sorted([six.text_type(x.rstrip()) for x in tests if x]) result = [ six.text_type('tests.test_failing.FakeTestClass.test_pass'), six.text_type('tests.test_failing.FakeTestClass.test_pass_list'), six.text_type('tests.test_passing.FakeTestClass.test_pass'), six.text_type('tests.test_passing.FakeTestClass.test_pass_list'), ] # NOTE(mtreinish): on python 3 the subprocess prints b'' around # stdout. if six.PY3: result = ["b\'" + x + "\'" for x in result] self.assertEqual(result, tests) def test_tempest_run_with_whitelist(self): fd, path = tempfile.mkstemp() self.addCleanup(os.remove, path) whitelist_file = os.fdopen(fd, 'wb', 0) self.addCleanup(whitelist_file.close) whitelist_file.write('passing'.encode('utf-8')) self.assertRunExit(['tempest', 'run', '--whitelist-file=%s' % path], 0) def test_tempest_run_with_whitelist_regex_include_pass_check_fail(self): fd, path = tempfile.mkstemp() self.addCleanup(os.remove, path) whitelist_file = os.fdopen(fd, 'wb', 0) self.addCleanup(whitelist_file.close) whitelist_file.write('passing'.encode('utf-8')) self.assertRunExit(['tempest', 'run', '--whitelist-file=%s' % path, '--regex', 'fail'], 1) def test_tempest_run_with_whitelist_regex_include_pass_check_pass(self): fd, path = tempfile.mkstemp() self.addCleanup(os.remove, path) whitelist_file = os.fdopen(fd, 'wb', 0) self.addCleanup(whitelist_file.close) whitelist_file.write('passing'.encode('utf-8')) self.assertRunExit(['tempest', 'run', '--whitelist-file=%s' % path, '--regex', 'passing'], 0) def test_tempest_run_with_whitelist_regex_include_fail_check_pass(self): fd, path = tempfile.mkstemp() self.addCleanup(os.remove, path) whitelist_file = os.fdopen(fd, 'wb', 0) self.addCleanup(whitelist_file.close) whitelist_file.write('failing'.encode('utf-8')) self.assertRunExit(['tempest', 'run', '--whitelist-file=%s' % path, '--regex', 'pass'], 1) def test_tempest_run_passes_with_config_file(self): self.assertRunExit(['tempest', 'run', '--config-file', self.stestr_conf_file, '--regex', 'passing'], 0) def test_tempest_run_with_blacklist_failing(self): fd, path = tempfile.mkstemp() self.addCleanup(os.remove, path) blacklist_file = os.fdopen(fd, 'wb', 0) self.addCleanup(blacklist_file.close) blacklist_file.write('failing'.encode('utf-8')) self.assertRunExit(['tempest', 'run', '--blacklist-file=%s' % path], 0) def test_tempest_run_with_blacklist_passing(self): fd, path = tempfile.mkstemp() self.addCleanup(os.remove, path) blacklist_file = os.fdopen(fd, 'wb', 0) self.addCleanup(blacklist_file.close) blacklist_file.write('passing'.encode('utf-8')) self.assertRunExit(['tempest', 'run', '--blacklist-file=%s' % path], 1) def test_tempest_run_with_blacklist_regex_exclude_fail_check_pass(self): fd, path = tempfile.mkstemp() self.addCleanup(os.remove, path) blacklist_file = os.fdopen(fd, 'wb', 0) self.addCleanup(blacklist_file.close) blacklist_file.write('failing'.encode('utf-8')) self.assertRunExit(['tempest', 'run', '--blacklist-file=%s' % path, '--regex', 'pass'], 0) def test_tempest_run_with_blacklist_regex_exclude_pass_check_pass(self): fd, path = tempfile.mkstemp() self.addCleanup(os.remove, path) blacklist_file = os.fdopen(fd, 'wb', 0) self.addCleanup(blacklist_file.close) blacklist_file.write('passing'.encode('utf-8')) self.assertRunExit(['tempest', 'run', '--blacklist-file=%s' % path, '--regex', 'pass'], 1) def test_tempest_run_with_blacklist_regex_exclude_pass_check_fail(self): fd, path = tempfile.mkstemp() self.addCleanup(os.remove, path) blacklist_file = os.fdopen(fd, 'wb', 0) self.addCleanup(blacklist_file.close) blacklist_file.write('passing'.encode('utf-8')) self.assertRunExit(['tempest', 'run', '--blacklist-file=%s' % path, '--regex', 'fail'], 1) class TestConfigPathCheck(base.TestCase): def setUp(self): super(TestConfigPathCheck, self).setUp() self.run_cmd = run.TempestRun(None, None) def test_tempest_run_set_config_path(self): # Note: (mbindlish) This test is created for the bug id: 1783751 # Checking TEMPEST_CONFIG_DIR and TEMPEST_CONFIG is actually # getting set in os environment when some data has passed to # set the environment. _, path = tempfile.mkstemp() self.addCleanup(os.remove, path) self.run_cmd._set_env(path) self.assertEqual(path, CONF._path) self.assertIn('TEMPEST_CONFIG_DIR', os.environ) self.assertEqual(path, os.path.join(os.environ['TEMPEST_CONFIG_DIR'], os.environ['TEMPEST_CONFIG'])) def test_tempest_run_set_config_no_exist_path(self): path = "fake/path" self.assertRaisesRegex(FileNotFoundError, 'Config file: .* doesn\'t exist', self.run_cmd._set_env, path) def test_tempest_run_no_config_path(self): # Note: (mbindlish) This test is created for the bug id: 1783751 # Checking TEMPEST_CONFIG_DIR and TEMPEST_CONFIG should have no value # in os environment when no data has passed to set the environment. self.run_cmd._set_env("") self.assertFalse(CONF._path) self.assertNotIn('TEMPEST_CONFIG_DIR', os.environ) self.assertNotIn('TEMPEST_CONFIG', os.environ) class TestTakeAction(base.TestCase): def setUp(self): super(TestTakeAction, self).setUp() self.name = data_utils.rand_name('workspace') 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 _setup_test_dirs(self): self.directory = tempfile.mkdtemp(prefix='tempest-unit') self.addCleanup(shutil.rmtree, self.directory, ignore_errors=True) self.test_dir = os.path.join(self.directory, 'tests') os.mkdir(self.test_dir) # Change directory, run wrapper and check result self.addCleanup(os.chdir, os.path.abspath(os.curdir)) os.chdir(self.directory) 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) def test_config_file_specified(self): self._setup_test_dirs() _, path = tempfile.mkstemp() self.addCleanup(os.remove, path) tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock()) parsed_args = mock.Mock() parsed_args.workspace = None parsed_args.state = None parsed_args.list_tests = False parsed_args.config_file = path with mock.patch('stestr.commands.run_command') as m: m.return_value = 0 self.assertEqual(0, tempest_run.take_action(parsed_args)) m.assert_called() def test_no_config_file_no_workspace_no_state(self): self._setup_test_dirs() tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock()) parsed_args = mock.Mock() parsed_args.workspace = None parsed_args.state = None parsed_args.list_tests = False parsed_args.config_file = '' with mock.patch('stestr.commands.run_command'): self.assertRaises(SystemExit, tempest_run.take_action, parsed_args) def test_config_file_workspace_registered(self): self._setup_test_dirs() _, path = tempfile.mkstemp() self.addCleanup(os.remove, path) tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock()) parsed_args = mock.Mock() parsed_args.workspace = self.name parsed_args.workspace_path = self.store_file parsed_args.state = None parsed_args.list_tests = False parsed_args.config_file = path with mock.patch('stestr.commands.run_command') as m: m.return_value = 0 self.assertEqual(0, tempest_run.take_action(parsed_args)) m.assert_called() @mock.patch('tempest.cmd.run.TempestRun._init_state') def test_workspace_registered_no_config_no_state(self, mock_init_state): self._setup_test_dirs() tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock()) parsed_args = mock.Mock() parsed_args.workspace = self.name parsed_args.workspace_path = self.store_file parsed_args.state = None parsed_args.list_tests = False parsed_args.config_file = '' with mock.patch('stestr.commands.run_command') as m: m.return_value = 0 self.assertEqual(0, tempest_run.take_action(parsed_args)) m.assert_called() mock_init_state.assert_not_called() @mock.patch('tempest.cmd.run.TempestRun._init_state') def test_no_config_file_no_workspace_state_true(self, mock_init_state): self._setup_test_dirs() tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock()) parsed_args = mock.Mock() parsed_args.workspace = None parsed_args.state = True parsed_args.list_tests = False parsed_args.config_file = '' with mock.patch('stestr.commands.run_command'): self.assertRaises(SystemExit, tempest_run.take_action, parsed_args) mock_init_state.assert_not_called() @mock.patch('tempest.cmd.run.TempestRun._init_state') def test_workspace_registered_no_config_state_true(self, mock_init_state): self._setup_test_dirs() tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock()) parsed_args = mock.Mock() parsed_args.workspace = self.name parsed_args.workspace_path = self.store_file parsed_args.state = True parsed_args.list_tests = False parsed_args.config_file = '' with mock.patch('stestr.commands.run_command') as m: m.return_value = 0 self.assertEqual(0, tempest_run.take_action(parsed_args)) m.assert_called() mock_init_state.assert_called() @mock.patch('tempest.cmd.run.TempestRun._init_state') def test_no_workspace_config_file_state_true(self, mock_init_state): self._setup_test_dirs() _, path = tempfile.mkstemp() self.addCleanup(os.remove, path) tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock()) parsed_args = mock.Mock() parsed_args.workspace = None parsed_args.workspace_path = self.store_file parsed_args.state = True parsed_args.list_tests = False parsed_args.config_file = path with mock.patch('stestr.commands.run_command') as m: m.return_value = 0 self.assertEqual(0, tempest_run.take_action(parsed_args)) m.assert_called() mock_init_state.assert_called() tempest-23.0.0/tempest/tests/cmd/test_saved_state_json.json000066400000000000000000000003731361143263300241350ustar00rootroot00000000000000{ "domains": { "default": "Default" }, "flavors": { "1": "m1.tiny" }, "images": {}, "projects": { "268bcb63488b4aa2942ecaac0f85ed62": "demo" }, "roles": {}, "users": { "023e65a5922a454585a91c6af8310968": "demo" } } tempest-23.0.0/tempest/tests/cmd/test_subunit_describe_calls.py000066400000000000000000000273461361143263300250010ustar00rootroot00000000000000# 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_verbose(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, '-v'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) stdout = p.communicate() self.assertEqual(0, p.returncode) self.assertIn(b'- request headers:', stdout[0]) self.assertIn(b'- request body:', stdout[0]) self.assertIn(b'- response headers:', stdout[0]) self.assertIn(b'- response body:', stdout[0]) 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, stdout=subprocess.PIPE) stdout = p.communicate() self.assertEqual(0, p.returncode) self.assertIn(b'foo', stdout[0]) self.assertIn(b'- 200 POST request for Nova to v2.1//', stdout[0]) self.assertIn(b'- 200 DELETE request for Nova to v2.1//', stdout[0]) self.assertIn(b'- 200 GET request for Nova to v2.1//', stdout[0]) self.assertIn(b'- 404 DELETE request for Nova to v2.1//', stdout[0]) self.assertNotIn(b'- request headers:', stdout[0]) self.assertNotIn(b'- request body:', stdout[0]) self.assertNotIn(b'- response headers:', stdout[0]) self.assertNotIn(b'- response body:', stdout[0]) 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-23.0.0/tempest/tests/cmd/test_tempest_init.py000066400000000000000000000154521361143263300227710ustar00rootroot00000000000000# 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_stestr_conf(self): # Create fake conf dir conf_dir = self.useFixture(fixtures.TempDir()) init_cmd = init.TempestInit(None, None) init_cmd.generate_stestr_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') stestr_conf_file = init.STESTR_CONF % (discover_path, top_level_path) conf_path = conf_dir.join('.stestr.conf') with open(conf_path, 'r') as conf_file: self.assertEqual(conf_file.read(), stestr_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') stestr_dir = os.path.join(fake_local_dir.path, '.stestr') 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(stestr_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_stestr_conf = os.path.join(fake_local_dir.path, '.stestr.conf') self.assertTrue(os.path.isfile(fake_file_moved)) self.assertTrue(os.path.isfile(local_conf_file)) self.assertTrue(os.path.isfile(local_stestr_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-23.0.0/tempest/tests/cmd/test_verify_tempest_config.py000066400000000000000000000714421361143263300246600ustar00rootroot00000000000000# 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 os import fixtures import mock from oslo_serialization import jsonutils as json from tempest import clients from tempest.cmd import init 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.1']) 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_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_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_not_called() 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_neutron_none(self): def fake_list_extensions(): return {'extensions': []} 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([], 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_cinder_none(self): def fake_list_extensions(): return {'extensions': []} 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([], 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_nova_none(self): def fake_list_extensions(): return ({'extensions': []}) 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([], 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_verify_extensions_swift_none(self): def fake_list_extensions(): return {'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([], 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) def test_get_extension_client_sysexit(self): creds = credentials_factory.get_credentials( fill_in=False, username='fake_user', project_name='fake_project', password='fake_password') os = clients.Manager(creds) self.assertRaises(SystemExit, verify_tempest_config.get_extension_client, os, 'fakeservice') def test_get_config_file(self): conf_dir = os.path.join(os.getcwd(), 'etc/') conf_file = "tempest.conf.sample" local_sample_conf_file = os.path.join(conf_dir, conf_file) def fake_environ_get(key, default=None): if key == 'TEMPEST_CONFIG_DIR': return conf_dir elif key == 'TEMPEST_CONFIG': return 'tempest.conf.sample' return default with mock.patch('os.environ.get', side_effect=fake_environ_get, autospec=True): init_cmd = init.TempestInit(None, None) init_cmd.generate_sample_config(os.path.join(conf_dir, os.pardir)) self.assertTrue(os.path.isfile(local_sample_conf_file), local_sample_conf_file) file_pointer = verify_tempest_config._get_config_file() self.assertEqual(local_sample_conf_file, file_pointer.name) with open(local_sample_conf_file, 'r+') as f: local_sample_conf_contents = f.read() self.assertEqual(local_sample_conf_contents, file_pointer.read()) if file_pointer: file_pointer.close() def test_print_and_or_update_true(self): with mock.patch.object( verify_tempest_config, 'change_option') as test_mock: verify_tempest_config.print_and_or_update( 'fakeservice', 'fake-service-available', False, True) test_mock.assert_called_once_with( 'fakeservice', 'fake-service-available', False) def test_print_and_or_update_false(self): with mock.patch.object( verify_tempest_config, 'change_option') as test_mock: verify_tempest_config.print_and_or_update( 'fakeservice', 'fake-service-available', False, False) test_mock.assert_not_called() def test_contains_version_positive_data(self): self.assertTrue( verify_tempest_config.contains_version('v1.', ['v1.0', 'v2.0'])) def test_contains_version_negative_data(self): self.assertFalse( verify_tempest_config.contains_version('v5.', ['v1.0', 'v2.0'])) tempest-23.0.0/tempest/tests/cmd/test_workspace.py000066400000000000000000000415611361143263300222630ustar00rootroot00000000000000# 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 mock import patch try: from StringIO import StringIO except ImportError: from io import StringIO 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_rename_no_name_exist(self): no_name = "" with patch('sys.stdout', new_callable=StringIO) as mock_stdout: ex = self.assertRaises(SystemExit, self.workspace_manager.rename_workspace, self.name, no_name) self.assertEqual(1, ex.code) self.assertEqual(mock_stdout.getvalue(), "None or empty name is specified." " Please specify correct name for workspace.\n") def test_workspace_manager_rename_with_existing_name(self): new_name = self.name with patch('sys.stdout', new_callable=StringIO) as mock_stdout: ex = self.assertRaises(SystemExit, self.workspace_manager.rename_workspace, self.name, new_name) self.assertEqual(1, ex.code) self.assertEqual(mock_stdout.getvalue(), "A workspace already exists with name: %s.\n" % new_name) def test_workspace_manager_rename_no_exist_old_name(self): old_name = "" new_name = data_utils.rand_uuid() with patch('sys.stdout', new_callable=StringIO) as mock_stdout: ex = self.assertRaises(SystemExit, self.workspace_manager.rename_workspace, old_name, new_name) self.assertEqual(1, ex.code) self.assertEqual(mock_stdout.getvalue(), "A workspace was not found with name: %s\n" % old_name) def test_workspace_manager_rename_integer_data(self): old_name = self.name new_name = 12345 self.workspace_manager.rename_workspace(old_name, new_name) self.assertIsNone(self.workspace_manager.get_workspace(old_name)) self.assertIsNotNone(self.workspace_manager.get_workspace(new_name)) def test_workspace_manager_rename_alphanumeric_data(self): old_name = self.name new_name = 'abc123' self.workspace_manager.rename_workspace(old_name, new_name) self.assertIsNone(self.workspace_manager.get_workspace(old_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) # NOTE(mbindlish): Also checking for the workspace that it # shouldn't exist in old path self.assertNotEqual( self.workspace_manager.get_workspace(self.name), self.path) def test_workspace_manager_move_wrong_path(self): new_path = 'wrong/path' with patch('sys.stdout', new_callable=StringIO) as mock_stdout: ex = self.assertRaises(SystemExit, self.workspace_manager.move_workspace, self.name, new_path) self.assertEqual(1, ex.code) self.assertEqual(mock_stdout.getvalue(), "Path does not exist.\n") def test_workspace_manager_move_wrong_workspace(self): workspace_name = "wrong_workspace_name" new_path = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, new_path, ignore_errors=True) with patch('sys.stdout', new_callable=StringIO) as mock_stdout: ex = self.assertRaises(SystemExit, self.workspace_manager.move_workspace, workspace_name, new_path) self.assertEqual(1, ex.code) self.assertEqual(mock_stdout.getvalue(), "A workspace was not found with name: %s\n" % workspace_name) def test_workspace_manager_move_no_workspace_name(self): workspace_name = "" new_path = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, new_path, ignore_errors=True) with patch('sys.stdout', new_callable=StringIO) as mock_stdout: ex = self.assertRaises(SystemExit, self.workspace_manager.move_workspace, workspace_name, new_path) self.assertEqual(1, ex.code) self.assertEqual(mock_stdout.getvalue(), "A workspace was not found with name: %s\n" % workspace_name) def test_workspace_manager_move_no_workspace_path(self): new_path = "" with patch('sys.stdout', new_callable=StringIO) as mock_stdout: ex = self.assertRaises(SystemExit, self.workspace_manager.move_workspace, self.name, new_path) self.assertEqual(1, ex.code) self.assertEqual(mock_stdout.getvalue(), "None or empty path is specified for workspace." " Please specify correct workspace path.\n") 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_entry_no_name(self): no_name = "" with patch('sys.stdout', new_callable=StringIO) as mock_stdout: ex = self.assertRaises(SystemExit, self.workspace_manager. remove_workspace_entry, no_name) self.assertEqual(1, ex.code) self.assertEqual(mock_stdout.getvalue(), "A workspace was not found with name: %s\n" % no_name) def test_workspace_manager_remove_entry_wrong_name(self): wrong_name = "wrong_name" with patch('sys.stdout', new_callable=StringIO) as mock_stdout: ex = self.assertRaises(SystemExit, self.workspace_manager. remove_workspace_entry, wrong_name) self.assertEqual(1, ex.code) self.assertEqual(mock_stdout.getvalue(), "A workspace was not found with name: %s\n" % wrong_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_workspace_manager_remove_directory_no_path(self): no_path = "" with patch('sys.stdout', new_callable=StringIO) as mock_stdout: ex = self.assertRaises(SystemExit, self.workspace_manager. remove_workspace_directory, no_path) self.assertEqual(1, ex.code) self.assertEqual(mock_stdout.getvalue(), "None or empty path is specified for workspace." " Please specify correct workspace path.\n") 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)) def test_workspace_name_not_exists(self): nonexistent_name = data_utils.rand_uuid() with patch('sys.stdout', new_callable=StringIO) as mock_stdout: ex = self.assertRaises(SystemExit, self.workspace_manager._name_exists, nonexistent_name) self.assertEqual(1, ex.code) self.assertEqual(mock_stdout.getvalue(), "A workspace was not found with name: %s\n" % nonexistent_name) def test_workspace_name_exists(self): self.assertIsNone(self.workspace_manager._name_exists(self.name)) def test_workspace_name_already_exists(self): duplicate_name = self.name with patch('sys.stdout', new_callable=StringIO) as mock_stdout: ex = self.assertRaises(SystemExit, self.workspace_manager. _workspace_name_exists, duplicate_name) self.assertEqual(1, ex.code) self.assertEqual(mock_stdout.getvalue(), "A workspace already exists with name: %s.\n" % duplicate_name) def test_workspace_name_exists_check_new_name(self): new_name = "fake_name" self.assertIsNone(self.workspace_manager. _workspace_name_exists(new_name)) def test_workspace_manager_path_not_exist(self): fake_path = "fake_path" with patch('sys.stdout', new_callable=StringIO) as mock_stdout: ex = self.assertRaises(SystemExit, self.workspace_manager._validate_path, fake_path) self.assertEqual(1, ex.code) self.assertEqual(mock_stdout.getvalue(), "Path does not exist.\n") def test_validate_path_exists(self): new_path = self.path self.assertIsNone(self.workspace_manager. _validate_path(new_path)) def test_workspace_manager_list_workspaces(self): listed = self.workspace_manager.list_workspaces() self.assertEqual(1, len(listed)) self.assertIn(self.name, listed) self.assertEqual(self.path, listed.get(self.name)) def test_register_new_workspace_no_name(self): no_name = "" with patch('sys.stdout', new_callable=StringIO) as mock_stdout: ex = self.assertRaises(SystemExit, self.workspace_manager. register_new_workspace, no_name, self.path) self.assertEqual(1, ex.code) self.assertEqual(mock_stdout.getvalue(), "None or empty name is specified." " Please specify correct name for workspace.\n") def test_register_new_workspace_no_path(self): no_path = "" with patch('sys.stdout', new_callable=StringIO) as mock_stdout: ex = self.assertRaises(SystemExit, self.workspace_manager. register_new_workspace, self.name, no_path) self.assertEqual(1, ex.code) self.assertEqual(mock_stdout.getvalue(), "None or empty path is specified for workspace." " Please specify correct workspace path.\n") def test_register_new_workspace_integer_data(self): workspace_name = 12345 self.workspace_manager.register_new_workspace( workspace_name, self.path) self.assertIsNotNone( self.workspace_manager.get_workspace(workspace_name)) self.assertEqual( self.workspace_manager.get_workspace(workspace_name), self.path) def test_register_new_workspace_alphanumeric_data(self): workspace_name = 'abc123' self.workspace_manager.register_new_workspace( workspace_name, self.path) self.assertIsNotNone( self.workspace_manager.get_workspace(workspace_name)) self.assertEqual( self.workspace_manager.get_workspace(workspace_name), self.path) tempest-23.0.0/tempest/tests/common/000077500000000000000000000000001361143263300173725ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/common/__init__.py000066400000000000000000000000001361143263300214710ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/common/test_admin_available.py000066400000000000000000000112641361143263300240770ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/common/test_alt_available.py000066400000000000000000000061331361143263300235660ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/common/test_compute.py000066400000000000000000000101341361143263300224560ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/common/test_credentials_factory.py000066400000000000000000000325441361143263300250370ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/common/test_custom_matchers.py000066400000000000000000000067211361143263300242110ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/common/test_image.py000066400000000000000000000043231361143263300220670ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/common/test_waiters.py000077500000000000000000000247251361143263300224760ustar00rootroot00000000000000# 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') class TestInterfaceWaiters(base.TestCase): build_timeout = 1. build_interval = 1 port_down = {'interfaceAttachment': {'port_state': 'DOWN'}} port_active = {'interfaceAttachment': {'port_state': 'ACTIVE'}} def mock_client(self, **kwargs): return mock.MagicMock( build_timeout=self.build_timeout, build_interval=self.build_interval, **kwargs) def test_wait_for_interface_status(self): show_interface = mock.Mock( side_effect=[self.port_down, self.port_active]) client = self.mock_client(show_interface=show_interface) self.patch('time.time', return_value=0.) sleep = self.patch('time.sleep') result = waiters.wait_for_interface_status( client, 'server_id', 'port_id', 'ACTIVE') self.assertIs(self.port_active['interfaceAttachment'], result) show_interface.assert_has_calls([mock.call('server_id', 'port_id'), mock.call('server_id', 'port_id')]) sleep.assert_called_once_with(client.build_interval) def test_wait_for_interface_status_timeout(self): show_interface = mock.MagicMock(return_value=self.port_down) client = self.mock_client(show_interface=show_interface) self.patch('time.time', side_effect=[0., client.build_timeout + 1.]) sleep = self.patch('time.sleep') self.assertRaises(lib_exc.TimeoutException, waiters.wait_for_interface_status, client, 'server_id', 'port_id', 'ACTIVE') show_interface.assert_has_calls([mock.call('server_id', 'port_id'), mock.call('server_id', 'port_id')]) sleep.assert_called_once_with(client.build_interval) one_interface = {'interfaceAttachments': [{'port_id': 'port_one'}]} two_interfaces = {'interfaceAttachments': [{'port_id': 'port_one'}, {'port_id': 'port_two'}]} def test_wait_for_interface_detach(self): list_interfaces = mock.MagicMock( side_effect=[self.two_interfaces, self.one_interface]) client = self.mock_client(list_interfaces=list_interfaces) self.patch('time.time', return_value=0.) sleep = self.patch('time.sleep') result = waiters.wait_for_interface_detach( client, 'server_id', 'port_two') self.assertIs(self.one_interface['interfaceAttachments'], result) list_interfaces.assert_has_calls([mock.call('server_id'), mock.call('server_id')]) sleep.assert_called_once_with(client.build_interval) def test_wait_for_interface_detach_timeout(self): list_interfaces = mock.MagicMock(return_value=self.one_interface) client = self.mock_client(list_interfaces=list_interfaces) self.patch('time.time', side_effect=[0., client.build_timeout + 1.]) sleep = self.patch('time.sleep') self.assertRaises(lib_exc.TimeoutException, waiters.wait_for_interface_detach, client, 'server_id', 'port_one') list_interfaces.assert_has_calls([mock.call('server_id'), mock.call('server_id')]) sleep.assert_called_once_with(client.build_interval) class TestVolumeWaiters(base.TestCase): vol_migrating_src_host = { 'volume': {'migration_status': 'migrating', 'os-vol-host-attr:host': 'src_host@backend#type'}} vol_migrating_dst_host = { 'volume': {'migration_status': 'migrating', 'os-vol-host-attr:host': 'dst_host@backend#type'}} vol_migration_success = { 'volume': {'migration_status': 'success', 'os-vol-host-attr:host': 'dst_host@backend#type'}} vol_migration_error = { 'volume': {'migration_status': 'error', 'os-vol-host-attr:host': 'src_host@backend#type'}} def test_wait_for_volume_migration_timeout(self): show_volume = mock.MagicMock(return_value=self.vol_migrating_src_host) client = mock.Mock(spec=volumes_client.VolumesClient, resource_type="volume", build_interval=1, build_timeout=1, show_volume=show_volume) self.patch('time.time', side_effect=[0., client.build_timeout + 1.]) self.patch('time.sleep') self.assertRaises(lib_exc.TimeoutException, waiters.wait_for_volume_migration, client, mock.sentinel.volume_id, 'dst_host') def test_wait_for_volume_migration_error(self): show_volume = mock.MagicMock(side_effect=[ self.vol_migrating_src_host, self.vol_migrating_src_host, self.vol_migration_error]) client = mock.Mock(spec=volumes_client.VolumesClient, resource_type="volume", build_interval=1, build_timeout=1, show_volume=show_volume) self.patch('time.time', return_value=0.) self.patch('time.sleep') self.assertRaises(lib_exc.TempestException, waiters.wait_for_volume_migration, client, mock.sentinel.volume_id, 'dst_host') def test_wait_for_volume_migration_success_and_dst(self): show_volume = mock.MagicMock(side_effect=[ self.vol_migrating_src_host, self.vol_migrating_dst_host, self.vol_migration_success]) client = mock.Mock(spec=volumes_client.VolumesClient, resource_type="volume", build_interval=1, build_timeout=1, show_volume=show_volume) self.patch('time.time', return_value=0.) self.patch('time.sleep') waiters.wait_for_volume_migration( client, mock.sentinel.volume_id, 'dst_host') # Assert that we wait until migration_status is success and dst_host is # part of the returned os-vol-host-attr:host. show_volume.assert_has_calls([mock.call(mock.sentinel.volume_id), mock.call(mock.sentinel.volume_id), mock.call(mock.sentinel.volume_id)]) @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) @mock.patch.object(time, 'sleep') def test_wait_for_volume_status_error_extending(self, mock_sleep): # Tests that the wait method raises VolumeExtendErrorException if # the volume status is 'error_extending'. client = mock.Mock(spec=volumes_client.VolumesClient, resource_type="volume", build_interval=1) volume1 = {'volume': {'status': 'extending'}} volume2 = {'volume': {'status': 'error_extending'}} mock_show = mock.Mock(side_effect=(volume1, volume2)) client.show_volume = mock_show volume_id = '7532b91e-aa0a-4e06-b3e5-20c0c5ee1caa' self.assertRaises(exceptions.VolumeExtendErrorException, 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-23.0.0/tempest/tests/common/utils/000077500000000000000000000000001361143263300205325ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/common/utils/__init__.py000066400000000000000000000000001361143263300226310ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/common/utils/linux/000077500000000000000000000000001361143263300216715ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/common/utils/linux/__init__.py000066400000000000000000000000001361143263300237700ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/common/utils/linux/test_remote_client.py000066400000000000000000000215261361143263300261410ustar00rootroot00000000000000# 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( r'\`', '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:/usr/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_count_disk(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""" self.ssh_mock.mock.exec_command.return_value = output_lsblk self.assertEqual(self.conn.count_disks(), 2) 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-23.0.0/tempest/tests/common/utils/test_net_utils.py000066400000000000000000000022431361143263300241520ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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-23.0.0/tempest/tests/fake_config.py000066400000000000000000000105071361143263300207120ustar00rootroot00000000000000# 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('image_ref', 'fake_image_id', 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') lock_path = str(os.environ.get('OS_TEST_LOCK_PATH', os.environ.get('TMPDIR', '/tmp'))) if not os.path.exists(lock_path): os.mkdir(lock_path) lockutils.set_defaults( lock_path=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-23.0.0/tempest/tests/fake_tempest_plugin.py000066400000000000000000000032121361143263300224770ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/files/000077500000000000000000000000001361143263300172045ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/files/__init__.py000066400000000000000000000000001361143263300213030ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/files/failing-tests000066400000000000000000000014741361143263300217060ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/files/passing-tests000066400000000000000000000014731361143263300217400ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/files/setup.cfg000066400000000000000000000011671361143263300210320ustar00rootroot00000000000000[metadata] name = tempest_unit_tests version = 1 summary = Fake Project for testing wrapper scripts author = OpenStack author-email = openstack-discuss@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 [global] setup-hooks = pbr.hooks.setup_hook tempest-23.0.0/tempest/tests/files/testr-conf000066400000000000000000000000641361143263300212130ustar00rootroot00000000000000[DEFAULT] test_path=./tests group_regex=([^\.]*\.)* tempest-23.0.0/tempest/tests/lib/000077500000000000000000000000001361143263300166505ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/__init__.py000066400000000000000000000000001361143263300207470ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/cli/000077500000000000000000000000001361143263300174175ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/cli/__init__.py000066400000000000000000000000001361143263300215160ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/cli/test_command_failed.py000066400000000000000000000021511361143263300237510ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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-23.0.0/tempest/tests/lib/cli/test_execute.py000066400000000000000000000143741361143263300225030ustar00rootroot00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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]) @mock.patch.object(cli_base, 'execute') def test_execute_with_default_api_version(self, mock_execute): cli = cli_base.CLIClient() cli.openstack('action') self.assertEqual(mock_execute.call_count, 1) self.assertNotIn('--os-identity-api-version ', mock_execute.call_args[0][2]) @mock.patch.object(cli_base, 'execute') def test_execute_with_empty_api_version(self, mock_execute): cli = cli_base.CLIClient(identity_api_version='') cli.openstack('action') self.assertEqual(mock_execute.call_count, 1) self.assertNotIn('--os-identity-api-version ', mock_execute.call_args[0][2]) @mock.patch.object(cli_base, 'execute') def test_execute_with_explicit_api_version(self, mock_execute): cli = cli_base.CLIClient(identity_api_version='0.0') cli.openstack('action') self.assertEqual(mock_execute.call_count, 1) self.assertIn('--os-identity-api-version 0.0 ', mock_execute.call_args[0][2]) tempest-23.0.0/tempest/tests/lib/cli/test_output_parser.py000066400000000000000000000151141361143263300237460ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/common/000077500000000000000000000000001361143263300201405ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/common/__init__.py000066400000000000000000000000001361143263300222370ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/common/test_api_version_request.py000066400000000000000000000147461361143263300256530ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/common/test_api_version_utils.py000066400000000000000000000203061361143263300253100ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/common/test_cred_client.py000066400000000000000000000063551361143263300240350ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/common/test_dynamic_creds.py000066400000000000000000000734531361143263300243710ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/common/test_http.py000066400000000000000000000151151361143263300225330ustar00rootroot00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import urllib3 from tempest.lib.common import http from tempest.tests import base CERT_NONE = 'CERT_NONE' CERT_REQUIRED = 'CERT_REQUIRED' CERT_LOCATION = '/etc/ssl/certs/ca-certificates.crt' PROXY_URL = 'http://myproxy:3128' REQUEST_URL = 'http://10.0.0.107:5000/v2.0' REQUEST_METHOD = 'GET' class TestClosingHttp(base.TestCase): def closing_http(self, **kwargs): return http.ClosingHttp(**kwargs) def test_closing_http(self): connection = self.closing_http() self.assertNotIn('cert_reqs', connection.connection_pool_kw) self.assertNotIn('ca_certs', connection.connection_pool_kw) self.assertNotIn('timeout', connection.connection_pool_kw) def test_closing_http_with_ca_certs(self): connection = self.closing_http(ca_certs=CERT_LOCATION) self.assertEqual(CERT_REQUIRED, connection.connection_pool_kw['cert_reqs']) self.assertEqual(CERT_LOCATION, connection.connection_pool_kw['ca_certs']) def test_closing_http_with_dscv(self): connection = self.closing_http( disable_ssl_certificate_validation=True) self.assertEqual(CERT_NONE, connection.connection_pool_kw['cert_reqs']) self.assertNotIn('ca_certs', connection.connection_pool_kw) def test_closing_http_with_ca_certs_and_dscv(self): connection = self.closing_http( disable_ssl_certificate_validation=True, ca_certs=CERT_LOCATION) self.assertEqual(CERT_NONE, connection.connection_pool_kw['cert_reqs']) self.assertNotIn('ca_certs', connection.connection_pool_kw) def test_closing_http_with_timeout(self): timeout = 30 connection = self.closing_http(timeout=timeout) self.assertEqual(timeout, connection.connection_pool_kw['timeout']) def test_request(self): # Given connection = self.closing_http() http_response = urllib3.HTTPResponse() request = self.patch('urllib3.PoolManager.request', return_value=http_response) retry = self.patch('urllib3.util.Retry') # When response, data = connection.request( method=REQUEST_METHOD, url=REQUEST_URL) # Then request.assert_called_once_with( REQUEST_METHOD, REQUEST_URL, headers={'connection': 'close'}, retries=retry(raise_on_redirect=False, redirect=5)) self.assertEqual( {'content-location': REQUEST_URL, 'status': str(http_response.status)}, response) self.assertEqual(http_response.status, response.status) self.assertEqual(http_response.reason, response.reason) self.assertEqual(http_response.version, response.version) self.assertEqual(http_response.data, data) def test_request_with_fields(self): # Given connection = self.closing_http() http_response = urllib3.HTTPResponse() request = self.patch('urllib3.PoolManager.request', return_value=http_response) retry = self.patch('urllib3.util.Retry') fields = object() # When connection.request( method=REQUEST_METHOD, url=REQUEST_URL, fields=fields) # Then request.assert_called_once_with( REQUEST_METHOD, REQUEST_URL, fields=fields, headers=dict(connection='close'), retries=retry(raise_on_redirect=False, redirect=5)) def test_request_with_headers(self): # Given connection = self.closing_http() headers = {'Xtra Key': 'Xtra Value'} http_response = urllib3.HTTPResponse(headers=headers) request = self.patch('urllib3.PoolManager.request', return_value=http_response) retry = self.patch('urllib3.util.Retry') # When response, _ = connection.request( method=REQUEST_METHOD, url=REQUEST_URL, headers=headers) # Then request.assert_called_once_with( REQUEST_METHOD, REQUEST_URL, headers=dict(headers, connection='close'), retries=retry(raise_on_redirect=False, redirect=5)) self.assertEqual( {'content-location': REQUEST_URL, 'status': str(http_response.status), 'xtra key': 'Xtra Value'}, response) class TestClosingProxyHttp(TestClosingHttp): def closing_http(self, proxy_url=PROXY_URL, **kwargs): connection = http.ClosingProxyHttp(proxy_url=proxy_url, **kwargs) self.assertHasProxy(connection, proxy_url) return connection def test_class_without_proxy_url(self): self.assertRaises(ValueError, http.ClosingProxyHttp, None) def assertHasProxy(self, connection, proxy_url): self.assertIsInstance(connection, http.ClosingProxyHttp) proxy = connection.proxy self.assertEqual(proxy_url, '%s://%s:%i' % (proxy.scheme, proxy.host, proxy.port)) class TestClosingHttpRedirects(base.TestCase): def test_redirect_default(self): connection = http.ClosingHttp() self.assertTrue(connection.follow_redirects) def test_redirect_off(self): connection = http.ClosingHttp(follow_redirects=False) self.assertFalse(connection.follow_redirects) class TestClosingProxyHttpRedirects(base.TestCase): def test_redirect_default(self): connection = http.ClosingProxyHttp(proxy_url=PROXY_URL) self.assertTrue(connection.follow_redirects) def test_redirect_off(self): connection = http.ClosingProxyHttp(follow_redirects=False, proxy_url=PROXY_URL) self.assertFalse(connection.follow_redirects) tempest-23.0.0/tempest/tests/lib/common/test_jsonschema_validator.py000066400000000000000000000066101361143263300257530ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/common/test_preprov_creds.py000066400000000000000000000563131361143263300244360ustar00rootroot00000000000000# 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.network.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-23.0.0/tempest/tests/lib/common/test_profiler.py000066400000000000000000000041301361143263300233710ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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.common import profiler class TestProfiler(testtools.TestCase): def test_serialize(self): key = 'SECRET_KEY' pm = {'key': key, 'uuid': 'ID'} with mock.patch('tempest.lib.common.profiler._profiler', pm): with mock.patch('json.dumps') as jdm: jdm.return_value = '{"base_id": "ID", "parent_id": "ID"}' expected = { 'X-Trace-HMAC': '887292df9f13b8b5ecd6bbbd2e16bfaaa4d914b0', 'X-Trace-Info': b'eyJiYXNlX2lkIjogIklEIiwgInBhcmVudF9pZCI6ICJJRCJ9' } self.assertEqual(expected, profiler.serialize_as_http_headers()) def test_profiler_lifecycle(self): key = 'SECRET_KEY' uuid = 'ID' self.assertEqual({}, profiler._profiler) profiler.enable(key, uuid) self.assertEqual({'key': key, 'uuid': uuid}, profiler._profiler) profiler.disable() self.assertEqual({}, profiler._profiler) @mock.patch('oslo_utils.uuidutils.generate_uuid') def test_profiler_lifecycle_generate_trace_id(self, generate_uuid_mock): key = 'SECRET_KEY' uuid = 'ID' generate_uuid_mock.return_value = uuid self.assertEqual({}, profiler._profiler) profiler.enable(key) self.assertEqual({'key': key, 'uuid': uuid}, profiler._profiler) profiler.disable() self.assertEqual({}, profiler._profiler) tempest-23.0.0/tempest/tests/lib/common/test_rest_client.py000066400000000000000000001200531361143263300240650ustar00rootroot00000000000000# 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 fixtures import jsonschema from oslo_serialization import jsonutils as json 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-23.0.0/tempest/tests/lib/common/test_validation_resources.py000066400000000000000000000441151361143263300260020ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/common/utils/000077500000000000000000000000001361143263300213005ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/common/utils/__init__.py000066400000000000000000000000001361143263300233770ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/common/utils/linux/000077500000000000000000000000001361143263300224375ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/common/utils/linux/__init__.py000066400000000000000000000000001361143263300245360ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/common/utils/linux/test_remote_client.py000066400000000000000000000056051361143263300267070ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/common/utils/test_data_utils.py000066400000000000000000000132571361143263300250520ustar00rootroot00000000000000# 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, r"^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-23.0.0/tempest/tests/lib/common/utils/test_misc.py000066400000000000000000000025151361143263300236470ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/common/utils/test_test_utils.py000066400000000000000000000202061361143263300251100ustar00rootroot00000000000000# 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 time import mock from tempest.lib.common import thread from tempest.lib.common.utils import test_utils from tempest.lib import exceptions from tempest.tests import base 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) class TestCallUntilTrue(base.TestCase): def test_call_until_true_when_true_at_first_call(self): """func returns True at first call """ self._test_call_until_true(return_values=[True], duration=30., time_sequence=[10., 60.]) def test_call_until_true_when_true_before_timeout(self): """func returns false at first call, then True before timeout """ self._test_call_until_true(return_values=[False, True], duration=30., time_sequence=[10., 39., 41.]) def test_call_until_true_when_never_true_before_timeout(self): """func returns false, then false, just before timeout """ self._test_call_until_true(return_values=[False, False], duration=30., time_sequence=[10., 39., 41.]) def test_call_until_true_with_params(self): """func is called using given parameters """ self._test_call_until_true(return_values=[False, True], duration=30., time_sequence=[10., 30., 60.], args=(1, 2), kwargs=dict(foo='bar', bar='foo')) def _test_call_until_true(self, return_values, duration, time_sequence, args=None, kwargs=None): """Test call_until_true function :param return_values: list of booleans values to be returned each time given function is called. If any of these values is not consumed by calling the function the test fails. The list must contain a sequence of False items terminated by a single True or False :param duration: parameter passed to call_until_true function (a floating point value). :param time_sequence: sequence of time values returned by mocked time.time function used to trigger call_until_true behavior when handling timeout condition. The sequence must contain the exact number of values expected to be consumed by each time call_until_true calls time.time function. :param args: sequence of positional arguments to be passed to call_until_true function. :param kwargs: sequence of named arguments to be passed to call_until_true function. """ # all values except the last are False self.assertEqual([False] * len(return_values[:-1]), return_values[:-1]) # last value can be True or False self.assertIn(return_values[-1], [True, False]) # GIVEN func = mock.Mock(side_effect=return_values) sleep = 10. # this value has no effect as time.sleep is being mocked sleep_func = self.patch('time.sleep') time_func = self._patch_time(time_sequence) args = args or tuple() kwargs = kwargs or dict() # WHEN result = test_utils.call_until_true(func, duration, sleep, *args, **kwargs) # THEN # It must return last returned value self.assertIs(return_values[-1], result) self._test_func_calls(func, return_values, *args, **kwargs) self._test_sleep_calls(sleep_func, return_values, sleep) # The number of times time.time is called is not relevant as a # requirement of call_until_true. What is instead relevant is that # call_until_true use a mocked function to make the test reliable # and the test actually provide the right sequence of numbers to # reproduce the behavior has to be tested self._assert_called_n_times(time_func, len(time_sequence)) def _patch_time(self, time_sequence): # Iterator over time sequence time_iterator = iter(time_sequence) # Preserve original time.time() behavior for other threads original_time = time.time thread_id = thread.get_ident() def mocked_time(): if thread.get_ident() == thread_id: # Test thread => return time sequence values return next(time_iterator) else: # Other threads => call original time function return original_time() return self.patch('time.time', side_effect=mocked_time) def _test_func_calls(self, func, return_values, *args, **kwargs): self._assert_called_n_times(func, len(return_values), *args, **kwargs) def _test_sleep_calls(self, sleep_func, return_values, sleep): # count first consecutive False expected_count = 0 for value in return_values: if value: break expected_count += 1 self._assert_called_n_times(sleep_func, expected_count, sleep) def _assert_called_n_times(self, mock_func, expected_count, *args, **kwargs): calls = [mock.call(*args, **kwargs)] * expected_count self.assertEqual(expected_count, mock_func.call_count) mock_func.assert_has_calls(calls) tempest-23.0.0/tempest/tests/lib/fake_auth_provider.py000066400000000000000000000024421361143263300230650ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/fake_credentials.py000066400000000000000000000046601361143263300225130ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/fake_http.py000066400000000000000000000051111361143263300211650ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/fake_identity.py000066400000000000000000000156741361143263300220560ustar00rootroot00000000000000# 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, log_req_body=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, log_req_body=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, log_req_body=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, log_req_body=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-23.0.0/tempest/tests/lib/services/000077500000000000000000000000001361143263300204735ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/__init__.py000066400000000000000000000000001361143263300225720ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/base.py000066400000000000000000000065561361143263300217730ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/000077500000000000000000000000001361143263300221475ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/compute/__init__.py000066400000000000000000000000001361143263300242460ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/compute/test_agents_client.py000066400000000000000000000067411361143263300264070ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_aggregates_client.py000066400000000000000000000140751361143263300272360ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_availability_zone_client.py000066400000000000000000000035711361143263300306310ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_baremetal_nodes_client.py000066400000000000000000000055401361143263300302460ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_base_compute_client.py000066400000000000000000000170061361143263300275700ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_certificates_client.py000066400000000000000000000044431361143263300275700ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_extensions_client.py000066400000000000000000000044711361143263300273230ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_fixedIPs_client.py000066400000000000000000000044331361143263300266350ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_flavors_client.py000066400000000000000000000251261361143263300266000ustar00rootroot00000000000000# 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.api.compute import api_microversion_fixture 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 } FAKE_FLAVOR_UPDATE = { "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, "description": 'new' } 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_update_flavor(self, bytes_body=False): self.useFixture(api_microversion_fixture.APIMicroversionFixture( '2.55')) expected = {"flavor": TestFlavorsClient.FAKE_FLAVOR_UPDATE} request = {"flavor": {"description": "updated description"}} self.check_service_client_function( self.client.update_flavor, 'tempest.lib.common.rest_client.RestClient.put', expected, bytes_body, flavor_id='8c7aae5a-d315-4216-875b-ed9b6a5bcfc6', **request) def test_update_flavor_str_body(self): self._test_update_flavor(bytes_body=False) def test_update_flavor__byte_body(self): self._test_update_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-23.0.0/tempest/tests/lib/services/compute/test_floating_ip_pools_client.py000066400000000000000000000033521361143263300306300ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_floating_ips_bulk_client.py000066400000000000000000000064311361143263300306150ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_floating_ips_client.py000066400000000000000000000110341361143263300275730ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_hosts_client.py000066400000000000000000000120311361143263300262530ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_hypervisor_client.py000066400000000000000000000142241361143263300273330ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_images_client.py000066400000000000000000000232731361143263300263720ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_instance_usage_audit_log_client.py000066400000000000000000000055421361143263300321430ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_interfaces_client.py000066400000000000000000000075311361143263300272470ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_keypairs_client.py000066400000000000000000000064231361143263300267520ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_limits_client.py000066400000000000000000000046451361143263300264300ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_migrations_client.py000066400000000000000000000037411361143263300272770ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_networks_client.py000066400000000000000000000057631361143263300270050ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_quota_classes_client.py000066400000000000000000000050611361143263300277660ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_quotas_client.py000066400000000000000000000126741361143263300264440ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_security_group_default_rules_client.py000066400000000000000000000071701361143263300331240ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_security_group_rules_client.py000066400000000000000000000047771361143263300314320ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_security_groups_client.py000066400000000000000000000110221361143263300303600ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_server_groups_client.py000066400000000000000000000074111361143263300300260ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_servers_client.py000066400000000000000000001066271361143263300266230ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_services_client.py000066400000000000000000000176241361143263300267530ustar00rootroot00000000000000# 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" } } FAKE_UPDATE_SERVICE = { "service": { "id": "e81d66a4-ddd3-4aba-8a84-171d1cb4d339", "binary": "nova-compute", "disabled_reason": "test2", "host": "host1", "state": "down", "status": "disabled", "updated_at": "2012-10-29T13:42:05.000000", "forced_down": False, "zone": "nova" } } 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_update_service(self, bytes_body=False, status=None, disabled_reason=None, forced_down=None): resp_body = copy.deepcopy(self.FAKE_UPDATE_SERVICE) kwargs = {} if status is not None: kwargs['status'] = status if disabled_reason is not None: kwargs['disabled_reason'] = disabled_reason if forced_down is not None: kwargs['forced_down'] = forced_down resp_body['service'].update(kwargs) self.check_service_client_function( self.client.update_service, 'tempest.lib.common.rest_client.RestClient.put', resp_body, bytes_body, service_id=resp_body['service']['id'], **kwargs) 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) @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', new_callable=mock.PropertyMock(return_value='2.53')) def test_update_service_disable_scheduling_with_str_body(self, _): self._test_update_service(status='disabled', disabled_reason='maintenance') @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', new_callable=mock.PropertyMock(return_value='2.53')) def test_update_service_disable_scheduling_with_bytes_body(self, _): self._test_update_service(status='disabled', disabled_reason='maintenance', bytes_body=True) @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', new_callable=mock.PropertyMock(return_value='2.53')) def test_update_service_enable_scheduling_with_str_body(self, _): self._test_update_service(status='enabled') @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', new_callable=mock.PropertyMock(return_value='2.53')) def test_update_service_enable_scheduling_with_bytes_body(self, _): self._test_update_service(status='enabled', bytes_body=True) @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', new_callable=mock.PropertyMock(return_value='2.53')) def test_update_service_forced_down_with_str_body(self, _): self._test_update_service(forced_down=True) @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', new_callable=mock.PropertyMock(return_value='2.53')) def test_update_service_forced_down_with_bytes_body(self, _): self._test_update_service(forced_down=True, bytes_body=True) tempest-23.0.0/tempest/tests/lib/services/compute/test_snapshots_client.py000066400000000000000000000075321361143263300271470ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_tenant_networks_client.py000066400000000000000000000044241361143263300303470ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_tenant_usages_client.py000066400000000000000000000054731361143263300277670ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_versions_client.py000066400000000000000000000130001361143263300267600ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/compute/test_volumes_client.py000066400000000000000000000101351361143263300266100ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/000077500000000000000000000000001361143263300223245ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/identity/__init__.py000066400000000000000000000000001361143263300244230ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/identity/v2/000077500000000000000000000000001361143263300226535ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/identity/v2/__init__.py000066400000000000000000000000001361143263300247520ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/identity/v2/test_endpoints_client.py000066400000000000000000000072231361143263300276310ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v2/test_identity_client.py000066400000000000000000000214641361143263300274620ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v2/test_roles_client.py000066400000000000000000000113221361143263300267450ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v2/test_services_client.py000066400000000000000000000062521361143263300274520ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v2/test_tenants_client.py000066400000000000000000000104001361143263300272710ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v2/test_token_client.py000066400000000000000000000077361361143263300267570ustar00rootroot00000000000000# 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 from oslo_serialization import jsonutils as json 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') mock_raw_r.assert_called_once_with('fake_uri', 'GET', headers=mock.ANY, body=None, log_req_body='') 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-23.0.0/tempest/tests/lib/services/identity/v2/test_users_client.py000066400000000000000000000203401361143263300267620ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v3/000077500000000000000000000000001361143263300226545ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/identity/v3/__init__.py000066400000000000000000000000001361143263300247530ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/identity/v3/test_application_credentials_client.py000066400000000000000000000123061361143263300325050ustar00rootroot00000000000000# Copyright 2018 SUSE Linux GmbH # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR 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 application_credentials_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestApplicationCredentialsClient(base.BaseServiceTest): FAKE_CREATE_APP_CRED = { "application_credential": { "description": "fake application credential", "roles": [ { "id": "c60fdd45", "domain_id": None, "name": "Member" } ], "expires_at": "2019-02-27T18:30:59.999999Z", "secret": "_BVq0xU5L", "unrestricted": None, "project_id": "ddef321", "id": "5499a186", "name": "one" } } FAKE_LIST_APP_CREDS = { "application_credentials": [ { "description": "fake application credential", "roles": [ { "domain_id": None, "name": "Member", "id": "c60fdd45", } ], "expires_at": "2018-02-27T18:30:59.999999Z", "unrestricted": None, "project_id": "ddef321", "id": "5499a186", "name": "one" }, { "description": None, "roles": [ { "id": "0f1837c8", "domain_id": None, "name": "anotherrole" }, { "id": "c60fdd45", "domain_id": None, "name": "Member" } ], "expires_at": None, "unrestricted": None, "project_id": "c5403d938", "id": "d441c904f", "name": "two" } ] } FAKE_APP_CRED_INFO = { "application_credential": { "description": None, "roles": [ { "domain_id": None, "name": "Member", "id": "c60fdd45", } ], "expires_at": None, "unrestricted": None, "project_id": "ddef321", "id": "5499a186", "name": "one" } } def setUp(self): super(TestApplicationCredentialsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = \ application_credentials_client.ApplicationCredentialsClient( fake_auth, 'identity', 'regionOne') def _test_create_app_cred(self, bytes_body=False): self.check_service_client_function( self.client.create_application_credential, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_APP_CRED, bytes_body, status=201, user_id="123456") def _test_show_app_cred(self, bytes_body=False): self.check_service_client_function( self.client.show_application_credential, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_APP_CRED_INFO, bytes_body, user_id="123456", application_credential_id="5499a186") def _test_list_app_creds(self, bytes_body=False): self.check_service_client_function( self.client.list_application_credentials, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_APP_CREDS, bytes_body, user_id="123456") def test_create_application_credential_with_str_body(self): self._test_create_app_cred() def test_create_application_credential_with_bytes_body(self): self._test_create_app_cred(bytes_body=True) def test_show_application_credential_with_str_body(self): self._test_show_app_cred() def test_show_application_credential_with_bytes_body(self): self._test_show_app_cred(bytes_body=True) def test_list_application_credential_with_str_body(self): self._test_list_app_creds() def test_list_application_credential_with_bytes_body(self): self._test_list_app_creds(bytes_body=True) def test_delete_application_credential(self): self.check_service_client_function( self.client.delete_application_credential, 'tempest.lib.common.rest_client.RestClient.delete', {}, user_id="123456", application_credential_id="5499a186", status=204) tempest-23.0.0/tempest/tests/lib/services/identity/v3/test_catalog_client.py000066400000000000000000000057511361143263300272450ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR 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-23.0.0/tempest/tests/lib/services/identity/v3/test_credentials_client.py000066400000000000000000000166001361143263300301230ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v3/test_domain_configuration_client.py000066400000000000000000000201471361143263300320250ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v3/test_domains_client.py000066400000000000000000000110031361143263300272500ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v3/test_endpoint_filter_client.py000066400000000000000000000133731361143263300310170ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v3/test_endpoint_groups_client.py000066400000000000000000000130611361143263300310430ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v3/test_endpoints_client.py000066400000000000000000000104561361143263300276340ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v3/test_groups_client.py000066400000000000000000000162771361143263300271570ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v3/test_identity_client.py000066400000000000000000000125741361143263300274650ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v3/test_inherited_roles_client.py000066400000000000000000000207241361143263300310070ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v3/test_oauth_consumers_client.py000066400000000000000000000131361361143263300310450ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v3/test_oauth_token_client.py000066400000000000000000000175151361143263300301540ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v3/test_policies_client.py000066400000000000000000000206021361143263300274320ustar00rootroot00000000000000# 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" } ] } FAKE_ENDPOINT_ID = "234789" FAKE_SERVICE_ID = "556782" FAKE_POLICY_ID = "717273" FAKE_REGION_ID = "73" 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) def test_update_policy_association_for_endpoint(self): self.check_service_client_function( self.client.update_policy_association_for_endpoint, 'tempest.lib.common.rest_client.RestClient.put', {}, policy_id=self.FAKE_POLICY_ID, endpoint_id=self.FAKE_ENDPOINT_ID, status=204) def test_show_policy_association_for_endpoint(self): self.check_service_client_function( self.client.show_policy_association_for_endpoint, 'tempest.lib.common.rest_client.RestClient.get', {}, policy_id=self.FAKE_POLICY_ID, endpoint_id=self.FAKE_ENDPOINT_ID, status=204) def test_delete_policy_association_for_endpoint(self): self.check_service_client_function( self.client.delete_policy_association_for_endpoint, 'tempest.lib.common.rest_client.RestClient.delete', {}, policy_id=self.FAKE_POLICY_ID, endpoint_id=self.FAKE_ENDPOINT_ID, status=204) def test_update_policy_association_for_service(self): self.check_service_client_function( self.client.update_policy_association_for_service, 'tempest.lib.common.rest_client.RestClient.put', {}, policy_id=self.FAKE_POLICY_ID, service_id=self.FAKE_SERVICE_ID, status=204) def test_show_policy_association_for_service(self): self.check_service_client_function( self.client.show_policy_association_for_service, 'tempest.lib.common.rest_client.RestClient.get', {}, policy_id=self.FAKE_POLICY_ID, service_id=self.FAKE_SERVICE_ID, status=204) def test_delete_policy_association_for_service(self): self.check_service_client_function( self.client.delete_policy_association_for_service, 'tempest.lib.common.rest_client.RestClient.delete', {}, policy_id=self.FAKE_POLICY_ID, service_id=self.FAKE_SERVICE_ID, status=204) def test_update_policy_association_for_region_and_service(self): self.check_service_client_function( self.client.update_policy_association_for_region_and_service, 'tempest.lib.common.rest_client.RestClient.put', {}, policy_id=self.FAKE_POLICY_ID, service_id=self.FAKE_SERVICE_ID, region_id=self.FAKE_REGION_ID, status=204) def test_show_policy_association_for_region_and_service(self): self.check_service_client_function( self.client.show_policy_association_for_region_and_service, 'tempest.lib.common.rest_client.RestClient.get', {}, policy_id=self.FAKE_POLICY_ID, service_id=self.FAKE_SERVICE_ID, region_id=self.FAKE_REGION_ID, status=204) def test_delete_policy_association_for_region_and_service(self): self.check_service_client_function( self.client.delete_policy_association_for_region_and_service, 'tempest.lib.common.rest_client.RestClient.delete', {}, policy_id=self.FAKE_POLICY_ID, service_id=self.FAKE_SERVICE_ID, region_id=self.FAKE_REGION_ID, status=204) tempest-23.0.0/tempest/tests/lib/services/identity/v3/test_project_tags_client.py000066400000000000000000000074621361143263300303200ustar00rootroot00000000000000# Copyright 2018 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 tempest.lib.services.identity.v3 import project_tags_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestProjectTagsClient(base.BaseServiceTest): FAKE_PROJECT_ID = "0c4e939acacf4376bdcd1129f1a054ad" FAKE_PROJECT_TAG = "foo" FAKE_PROJECT_TAGS = ["foo", "bar"] def setUp(self): super(TestProjectTagsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = project_tags_client.ProjectTagsClient(fake_auth, 'identity', 'regionOne') def _test_update_project_tag(self, bytes_body=False): self.check_service_client_function( self.client.update_project_tag, 'tempest.lib.common.rest_client.RestClient.put', {}, bytes_body, project_id=self.FAKE_PROJECT_ID, tag=self.FAKE_PROJECT_TAG, status=201) def _test_list_project_tags(self, bytes_body=False): self.check_service_client_function( self.client.list_project_tags, 'tempest.lib.common.rest_client.RestClient.get', {"tags": self.FAKE_PROJECT_TAGS}, bytes_body, project_id=self.FAKE_PROJECT_ID) def _test_update_all_project_tags(self, bytes_body=False): self.check_service_client_function( self.client.update_all_project_tags, 'tempest.lib.common.rest_client.RestClient.put', {"tags": self.FAKE_PROJECT_TAGS}, bytes_body, project_id=self.FAKE_PROJECT_ID, tags=self.FAKE_PROJECT_TAGS) def test_update_project_tag_with_str_body(self): self._test_update_project_tag() def test_update_project_tag_with_bytes_body(self): self._test_update_project_tag(bytes_body=True) def test_list_project_tags_with_str_body(self): self._test_list_project_tags() def test_list_project_tags_with_bytes_body(self): self._test_list_project_tags(bytes_body=True) def test_update_all_project_tags_with_str_body(self): self._test_update_all_project_tags() def test_update_all_project_tags_with_bytes_body(self): self._test_update_all_project_tags(bytes_body=True) def test_check_project_project_tag_existence(self): self.check_service_client_function( self.client.check_project_tag_existence, 'tempest.lib.common.rest_client.RestClient.get', {}, project_id=self.FAKE_PROJECT_ID, tag=self.FAKE_PROJECT_TAG, status=204) def test_delete_project_tag(self): self.check_service_client_function( self.client.delete_project_tag, 'tempest.lib.common.rest_client.RestClient.delete', {}, project_id=self.FAKE_PROJECT_ID, tag=self.FAKE_PROJECT_TAG, status=204) def test_delete_all_project_tags(self): self.check_service_client_function( self.client.delete_all_project_tags, 'tempest.lib.common.rest_client.RestClient.delete', {}, project_id=self.FAKE_PROJECT_ID, status=204) tempest-23.0.0/tempest/tests/lib/services/identity/v3/test_projects_client.py000066400000000000000000000143271361143263300274630ustar00rootroot00000000000000# 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, "tags": [] }, { "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, "tags": [] }, { "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, "tags": [] }, { "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, "tags": [] } ] } 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-23.0.0/tempest/tests/lib/services/identity/v3/test_regions_client.py000066400000000000000000000101001361143263300272610ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v3/test_role_assignments_client.py000066400000000000000000000156201361143263300312030ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v3/test_roles_client.py000066400000000000000000000432211361143263300267510ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v3/test_services_client.py000066400000000000000000000122061361143263300274470ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v3/test_token_client.py000066400000000000000000000137621361143263300267540ustar00rootroot00000000000000# 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 from oslo_serialization import jsonutils as json 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') mock_raw_r.assert_called_once_with('fake_uri', 'GET', headers=mock.ANY, body=None, log_req_body='') 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-23.0.0/tempest/tests/lib/services/identity/v3/test_trusts_client.py000066400000000000000000000115451361143263300271750ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/identity/v3/test_users_client.py000066400000000000000000000211251361143263300267650ustar00rootroot00000000000000# 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', "description": "Tempest User", "email": "TempestUser@example.com", "options": { "ignore_password_expiry": True } } } 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', }, ] } FAKE_PROJECT_LIST = { "links": { "self": "http://example.com/identity/v3/users/313233/projects", "previous": None, "next": None }, "projects": [ { "description": "description of this project", "domain_id": "161718", "enabled": True, "id": "456788", "links": { "self": "http://example.com/identity/v3/projects/456788" }, "name": "a project name", "parent_id": "212223" }, { "description": "description of this project", "domain_id": "161718", "enabled": True, "id": "456789", "links": { "self": "http://example.com/identity/v3/projects/456789" }, "name": "another domain", "parent_id": "212223" }, ] } 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_list_user_projects(self, bytes_body=False): self.check_service_client_function( self.client.list_user_projects, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_PROJECT_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_list_user_projects_with_string_body(self): self._test_list_user_projects() def test_list_user_projects_with_bytes_body(self): self._test_list_user_projects(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-23.0.0/tempest/tests/lib/services/identity/v3/test_versions_client.py000066400000000000000000000052431361143263300274770ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/image/000077500000000000000000000000001361143263300215555ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/image/__init__.py000066400000000000000000000000001361143263300236540ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/image/v1/000077500000000000000000000000001361143263300221035ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/image/v1/__init__.py000066400000000000000000000000001361143263300242020ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/image/v1/test_image_members_client.py000066400000000000000000000063721361143263300276560ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/image/v2/000077500000000000000000000000001361143263300221045ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/image/v2/__init__.py000066400000000000000000000000001361143263300242030ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/image/v2/test_image_members_client.py000066400000000000000000000117141361143263300276530ustar00rootroot00000000000000# 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" } FAKE_LIST_IMAGE_MEMBERS = { "members": [ { "created_at": "2013-10-07T17:58:03Z", "image_id": "dbc999e3-c52f-4200-bedd-3b18fe7f87fe", "member_id": "123456789", "schema": "/v2/schemas/member", "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", "schema": "/v2/schemas/member", "status": "accepted", "updated_at": "2013-10-08T12:08:55Z" } ], "schema": "/v2/schemas/members" } 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="dbc999e3-c52f-4200-bedd-3b18fe7f87fe") 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_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_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-23.0.0/tempest/tests/lib/services/image/v2/test_images_client.py000066400000000000000000000203541361143263300263240ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/image/v2/test_namespace_object_client.py000066400000000000000000000203271361143263300303410ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/image/v2/test_namespace_properties_client.py000066400000000000000000000161371361143263300312730ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/image/v2/test_namespace_tags_client.py000066400000000000000000000111151361143263300300240ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/image/v2/test_namespaces_client.py000066400000000000000000000134171361143263300272000ustar00rootroot00000000000000# 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_LIST_NAMESPACES = { "first": "/v2/metadefs/namespaces?sort_key=created_at&sort_dir=asc", "namespaces": [ { "created_at": "2014-08-28T17:13:06Z", "description": "OS::Compute::Libvirt", "display_name": "libvirt Driver Options", "namespace": "OS::Compute::Libvirt", "owner": "admin", "protected": True, "resource_type_associations": [ { "created_at": "2014-08-28T17:13:06Z", "name": "OS::Glance::Image", "updated_at": "2014-08-28T17:13:06Z" } ], "schema": "/v2/schemas/metadefs/namespace", "self": "/v2/metadefs/namespaces/OS::Compute::Libvirt", "updated_at": "2014-08-28T17:13:06Z", "visibility": "public" }, { "created_at": "2014-08-28T17:13:06Z", "description": "OS::Compute::Quota", "display_name": "Flavor Quota", "namespace": "OS::Compute::Quota", "owner": "admin", "protected": True, "resource_type_associations": [ { "created_at": "2014-08-28T17:13:06Z", "name": "OS::Nova::Flavor", "updated_at": "2014-08-28T17:13:06Z" } ], "schema": "/v2/schemas/metadefs/namespace", "self": "/v2/metadefs/namespaces/OS::Compute::Quota", "updated_at": "2014-08-28T17:13:06Z", "visibility": "public" } ], "schema": "/v2/schemas/metadefs/namespaces" } 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_list_namespaces(self, bytes_body=False): self.check_service_client_function( self.client.list_namespaces, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_NAMESPACES, bytes_body) 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_list_namespaces_with_str_body(self): self._test_list_namespaces() def test_list_namespaces_with_bytes_body(self): self._test_list_namespaces(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-23.0.0/tempest/tests/lib/services/image/v2/test_resource_types_client.py000066400000000000000000000051671361143263300301370ustar00rootroot00000000000000# 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 TestResourceTypesClient(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(TestResourceTypesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = resource_types_client.ResourceTypesClient(fake_auth, 'image', 'regionOne') def _test_list_resource_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_resource_types_with_str_body(self): self._test_list_resource_types() def test_list_resource_types_with_bytes_body(self): self._test_list_resource_types(bytes_body=True) tempest-23.0.0/tempest/tests/lib/services/image/v2/test_schemas_client.py000066400000000000000000000071751361143263300265100ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/image/v2/test_versions_client.py000066400000000000000000000063221361143263300267260ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/network/000077500000000000000000000000001361143263300221645ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/network/__init__.py000066400000000000000000000000001361143263300242630ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/network/test_agents_client.py000066400000000000000000000025531361143263300264210ustar00rootroot00000000000000# Copyright 2018 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 agents_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestAgentsClient(base.BaseServiceTest): FAKE_AGENT_ID = "d32019d3-bc6e-4319-9c1d-6123f4135a88" def setUp(self): super(TestAgentsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.agents_client = agents_client.AgentsClient( fake_auth, "network", "regionOne") def test_delete_agent(self): self.check_service_client_function( self.agents_client.delete_agent, "tempest.lib.common.rest_client.RestClient.delete", {}, status=204, agent_id=self.FAKE_AGENT_ID) tempest-23.0.0/tempest/tests/lib/services/network/test_base_network_client.py000066400000000000000000000077261361143263300276320ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/network/test_extensions_client.py000066400000000000000000000171401361143263300273350ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/network/test_floating_ips_client.py000066400000000000000000000125641361143263300276210ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/network/test_metering_label_rules_client.py000066400000000000000000000105101361143263300313130ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/network/test_metering_labels_client.py000066400000000000000000000100121361143263300302610ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/network/test_networks_client.py000066400000000000000000000204321361143263300270100ustar00rootroot00000000000000# 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 networks_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestNetworksClient(base.BaseServiceTest): FAKE_NETWORKS = { "networks": [ { "admin_state_up": True, "availability_zone_hints": [], "availability_zones": [ "nova" ], "created_at": "2016-03-08T20:19:41", "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", "mtu": 0, "name": "net1", "port_security_enabled": True, "project_id": "4fd44f30292945e481c7b8a0c8908869", "qos_policy_id": "6a8454ade84346f59e8d40665f878b2e", "router:external": False, "shared": False, "status": "ACTIVE", "subnets": [ "54d6f61d-db07-451c-9ab3-b9609b6b6f0b" ], "tenant_id": "4fd44f30292945e481c7b8a0c8908869", "updated_at": "2016-03-08T20:19:41", "vlan_transparent": True, "description": "" }, { "admin_state_up": True, "availability_zone_hints": [], "availability_zones": [ "nova" ], "id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", "mtu": 0, "name": "net2", "port_security_enabled": True, "project_id": "26a7980765d0414dbc1fc1f88cdb7e6e", "qos_policy_id": "bfdb6c39f71e4d44b1dfbda245c50819", "router:external": False, "shared": False, "status": "ACTIVE", "subnets": [ "08eae331-0402-425a-923c-34f7cfe39c1b" ], "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", "updated_at": "2016-03-08T20:19:41", "vlan_transparent": False, "description": "" } ] } FAKE_NETWORK_ID = "d32019d3-bc6e-4319-9c1d-6722fc136a22" FAKE_NETWORK1 = { "name": "net1", "admin_state_up": True, "qos_policy_id": "6a8454ade84346f59e8d40665f878b2e" } FAKE_NETWORK2 = { "name": "net2", "admin_state_up": True, "qos_policy_id": "bfdb6c39f71e4d44b1dfbda245c50819" } FAKE_NETWORKS_REQ = { "networks": [ FAKE_NETWORK1, FAKE_NETWORK2 ] } FAKE_DHCP_AGENT_NETWORK_ID = "80515c45-651f-4f9a-b82b-2ca8a7301a8d" FAKE_DHCP_AGENTS = { "agents": [ { "binary": "neutron-dhcp-agent", "description": None, "admin_state_up": True, "heartbeat_timestamp": "2017-06-22 18:29:50", "availability_zone": "nova", "alive": True, "topic": "dhcp_agent", "host": "osboxes", "agent_type": "DHCP agent", "resource_versions": {}, "created_at": "2017-06-19 21:39:51", "started_at": "2017-06-19 21:39:51", "id": "b6cfb7a1-6ac4-4980-993c-9d295d37062e", "configurations": { "subnets": 2, "dhcp_lease_duration": 86400, "dhcp_driver": "neutron.agent.linux.dhcp.Dnsmasq", "networks": 1, "log_agent_heartbeats": False, "ports": 3 } } ] } def setUp(self): super(TestNetworksClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.networks_client = networks_client.NetworksClient( fake_auth, "network", "regionOne") def _test_list_networks(self, bytes_body=False): self.check_service_client_function( self.networks_client.list_networks, "tempest.lib.common.rest_client.RestClient.get", self.FAKE_NETWORKS, bytes_body, 200) def _test_create_network(self, bytes_body=False): self.check_service_client_function( self.networks_client.create_network, "tempest.lib.common.rest_client.RestClient.post", {"network": self.FAKE_NETWORKS["networks"][0]}, bytes_body, 201, **self.FAKE_NETWORK1) def _test_create_bulk_networks(self, bytes_body=False): self.check_service_client_function( self.networks_client.create_bulk_networks, "tempest.lib.common.rest_client.RestClient.post", self.FAKE_NETWORKS, bytes_body, 201, networks=self.FAKE_NETWORKS_REQ) def _test_show_network(self, bytes_body=False): self.check_service_client_function( self.networks_client.show_network, "tempest.lib.common.rest_client.RestClient.get", {"network": self.FAKE_NETWORKS["networks"][0]}, bytes_body, 200, network_id=self.FAKE_NETWORK_ID) def _test_update_network(self, bytes_body=False): update_kwargs = { "name": "sample_network_5_updated", "qos_policy_id": "6a8454ade84346f59e8d40665f878b2e" } resp_body = { "network": copy.deepcopy( self.FAKE_NETWORKS["networks"][0] ) } resp_body["network"].update(update_kwargs) self.check_service_client_function( self.networks_client.update_network, "tempest.lib.common.rest_client.RestClient.put", resp_body, bytes_body, 200, network_id=self.FAKE_NETWORK_ID, **update_kwargs) def _test_list_dhcp_agents_on_hosting_network(self, bytes_body=False): self.check_service_client_function( self.networks_client.list_dhcp_agents_on_hosting_network, "tempest.lib.common.rest_client.RestClient.get", self.FAKE_DHCP_AGENTS, bytes_body, 200, network_id=self.FAKE_DHCP_AGENT_NETWORK_ID) def test_delete_network(self): self.check_service_client_function( self.networks_client.delete_network, "tempest.lib.common.rest_client.RestClient.delete", {}, status=204, network_id=self.FAKE_NETWORK_ID) 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_create_network_with_str_body(self): self._test_create_network() def test_create_network_with_bytes_body(self): self._test_create_network(bytes_body=True) def test_create_bulk_network_with_str_body(self): self._test_create_bulk_networks() def test_create_bulk_network_with_bytes_body(self): self._test_create_bulk_networks(bytes_body=True) 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) def test_update_network_with_str_body(self): self._test_update_network() def test_update_network_with_bytes_body(self): self._test_update_network(bytes_body=True) def test_list_dhcp_agents_on_hosting_network_with_str_body(self): self._test_list_dhcp_agents_on_hosting_network() def test_list_dhcp_agents_on_hosting_network_with_bytes_body(self): self._test_list_dhcp_agents_on_hosting_network(bytes_body=True) tempest-23.0.0/tempest/tests/lib/services/network/test_ports_client.py000066400000000000000000000147371361143263300263160ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/network/test_qos_client.py000066400000000000000000000110351361143263300257350ustar00rootroot00000000000000# Copyright (c) 2019 Ericsson # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 qos_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestQosClient(base.BaseServiceTest): FAKE_QOS_POLICY_ID = "f1011b08-1297-11e9-a1e7-c7e6825a2616" FAKE_QOS_POLICY_REQUEST = { 'name': 'foo', 'shared': True } FAKE_QOS_POLICY_RESPONSE = { 'policy': { "name": "10Mbit", "description": "This policy limits the ports to 10Mbit max.", "rules": [], "id": FAKE_QOS_POLICY_ID, "is_default": False, "project_id": "8d4c70a21fed4aeba121a1a429ba0d04", "revision_number": 1, "tenant_id": "8d4c70a21fed4aeba121a1a429ba0d04", "created_at": "2018-04-03T21:26:39Z", "updated_at": "2018-04-03T21:26:39Z", "shared": False, "tags": ["tag1,tag2"] } } FAKE_QOS_POLICIES = { 'policies': [ FAKE_QOS_POLICY_RESPONSE['policy'] ] } def setUp(self): super(TestQosClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.qos_client = qos_client.QosClient( fake_auth, "network", "regionOne") def _test_create_qos_policy(self, bytes_body=False): self.check_service_client_function( self.qos_client.create_qos_policy, "tempest.lib.common.rest_client.RestClient.post", self.FAKE_QOS_POLICY_RESPONSE, bytes_body, 201, **self.FAKE_QOS_POLICY_REQUEST) def _test_list_qos_policies(self, bytes_body=False): self.check_service_client_function( self.qos_client.list_qos_policies, "tempest.lib.common.rest_client.RestClient.get", self.FAKE_QOS_POLICIES, bytes_body, 200) def _test_show_qos_policy(self, bytes_body=False): self.check_service_client_function( self.qos_client.show_qos_policy, "tempest.lib.common.rest_client.RestClient.get", self.FAKE_QOS_POLICY_RESPONSE, bytes_body, 200, qos_policy_id=self.FAKE_QOS_POLICY_ID) def _test_update_qos_polcy(self, bytes_body=False): update_kwargs = { "name": "100Mbit", "description": "This policy limits the ports to 100Mbit max.", "shared": True } resp_body = { "policy": copy.deepcopy( self.FAKE_QOS_POLICY_RESPONSE['policy'] ) } resp_body["policy"].update(update_kwargs) self.check_service_client_function( self.qos_client.update_qos_policy, "tempest.lib.common.rest_client.RestClient.put", resp_body, bytes_body, 200, qos_policy_id=self.FAKE_QOS_POLICY_ID, **update_kwargs) def test_create_qos_policy_with_str_body(self): self._test_create_qos_policy() def test_create_qos_policy_with_bytes_body(self): self._test_create_qos_policy(bytes_body=True) def test_update_qos_policy_with_str_body(self): self._test_update_qos_polcy() def test_update_qos_policy_with_bytes_body(self): self._test_update_qos_polcy(bytes_body=True) def test_show_qos_policy_with_str_body(self): self._test_show_qos_policy() def test_show_qos_policy_with_bytes_body(self): self._test_show_qos_policy(bytes_body=True) def test_delete_qos_policy(self): self.check_service_client_function( self.qos_client.delete_qos_policy, "tempest.lib.common.rest_client.RestClient.delete", {}, status=204, qos_policy_id=self.FAKE_QOS_POLICY_ID) def test_list_qos_policies_with_str_body(self): self._test_list_qos_policies() def test_list_qos_policies_with_bytes_body(self): self._test_list_qos_policies(bytes_body=True) tempest-23.0.0/tempest/tests/lib/services/network/test_qos_minimum_bandwidth_rules_client.py000066400000000000000000000115631361143263300327340ustar00rootroot00000000000000# Copyright (c) 2019 Ericsson # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 qos_minimum_bandwidth_rules_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestQosMinimumBandwidthRulesClient(base.BaseServiceTest): FAKE_QOS_POLICY_ID = "f1011b08-1297-11e9-a1e7-c7e6825a2616" FAKE_MIN_BW_RULE_ID = "e758c89e-1297-11e9-a6cf-cf46a71e6699" FAKE_MIN_BW_RULE_REQUEST = { 'qos_policy_id': FAKE_QOS_POLICY_ID, 'min_kbps': 1000, 'direction': 'ingress' } FAKE_MIN_BW_RULE_RESPONSE = { 'minimum_bandwidth_rule': { 'id': FAKE_MIN_BW_RULE_ID, 'min_kbps': 10000, 'direction': 'egress' } } FAKE_MIN_BW_RULES = { 'bandwidth_limit_rules': [ FAKE_MIN_BW_RULE_RESPONSE['minimum_bandwidth_rule'] ] } def setUp(self): super(TestQosMinimumBandwidthRulesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.qos_min_bw_client = qos_minimum_bandwidth_rules_client.\ QosMinimumBandwidthRulesClient(fake_auth, "network", "regionOne") def _test_create_minimum_bandwidth_rule(self, bytes_body=False): self.check_service_client_function( self.qos_min_bw_client.create_minimum_bandwidth_rule, "tempest.lib.common.rest_client.RestClient.post", self.FAKE_MIN_BW_RULE_RESPONSE, bytes_body, 201, **self.FAKE_MIN_BW_RULE_REQUEST ) def _test_list_minimum_bandwidth_rules(self, bytes_body=False): self.check_service_client_function( self.qos_min_bw_client.list_minimum_bandwidth_rules, "tempest.lib.common.rest_client.RestClient.get", self.FAKE_MIN_BW_RULES, bytes_body, 200, qos_policy_id=self.FAKE_QOS_POLICY_ID ) def _test_show_minimum_bandwidth_rule(self, bytes_body=False): self.check_service_client_function( self.qos_min_bw_client.show_minimum_bandwidth_rule, "tempest.lib.common.rest_client.RestClient.get", self.FAKE_MIN_BW_RULE_RESPONSE, bytes_body, 200, qos_policy_id=self.FAKE_QOS_POLICY_ID, rule_id=self.FAKE_MIN_BW_RULE_ID ) def _test_update_qos_polcy(self, bytes_body=False): update_kwargs = { "min_kbps": "20000" } resp_body = { "minimum_bandwidth_rule": copy.deepcopy( self.FAKE_MIN_BW_RULE_RESPONSE['minimum_bandwidth_rule'] ) } resp_body["minimum_bandwidth_rule"].update(update_kwargs) self.check_service_client_function( self.qos_min_bw_client.update_minimum_bandwidth_rule, "tempest.lib.common.rest_client.RestClient.put", resp_body, bytes_body, 200, qos_policy_id=self.FAKE_QOS_POLICY_ID, rule_id=self.FAKE_MIN_BW_RULE_ID, **update_kwargs) def test_create_minimum_bandwidth_rule_with_str_body(self): self._test_create_minimum_bandwidth_rule() def test_create_minimum_bandwidth_rule_with_bytes_body(self): self._test_create_minimum_bandwidth_rule(bytes_body=True) def test_update_minimum_bandwidth_rule_with_str_body(self): self._test_update_qos_polcy() def test_update_minimum_bandwidth_rule_with_bytes_body(self): self._test_update_qos_polcy(bytes_body=True) def test_show_minimum_bandwidth_rule_with_str_body(self): self._test_show_minimum_bandwidth_rule() def test_show_minimum_bandwidth_rule_with_bytes_body(self): self._test_show_minimum_bandwidth_rule(bytes_body=True) def test_delete_minimum_bandwidth_rule(self): self.check_service_client_function( self.qos_min_bw_client.delete_minimum_bandwidth_rule, "tempest.lib.common.rest_client.RestClient.delete", {}, status=204, qos_policy_id=self.FAKE_QOS_POLICY_ID, rule_id=self.FAKE_MIN_BW_RULE_ID) def test_list_minimum_bandwidth_rule_with_str_body(self): self._test_list_minimum_bandwidth_rules() def test_list_minimum_bandwidth_rule_with_bytes_body(self): self._test_list_minimum_bandwidth_rules(bytes_body=True) tempest-23.0.0/tempest/tests/lib/services/network/test_quotas_client.py000066400000000000000000000132421361143263300264510ustar00rootroot00000000000000# 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_PROJECT_QUOTAS = { "quota": { "floatingip": 50, "network": 10, "port": 50, "rbac_policy": -1, "router": 10, "security_group": 10, "security_group_rule": 100, "subnet": 10, "subnetpool": -1 } } FAKE_QUOTA_TENANT_ID = "bab7d5c60cd041a0a36f7c4b6e1dd978" FAKE_QUOTA_DETAILS = { "quota": { "rbac_policy": { "used": 4, "limit": 10, "reserved": 0 }, "subnetpool": { "used": 2, "limit": -1, "reserved": 0 }, "security_group_rule": { "used": 10, "limit": 100, "reserved": 1 }, "security_group": { "used": 3, "limit": 10, "reserved": 0 }, "subnet": { "used": 3, "limit": 100, "reserved": 0 }, "port": { "used": 21, "limit": 500, "reserved": 3 }, "network": { "used": 9, "limit": 100, "reserved": 2 } } } 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", self.FAKE_PROJECT_QUOTAS, bytes_body, 200, tenant_id=self.FAKE_QUOTA_TENANT_ID) def _test_show_default_quotas(self, bytes_body=False): self.check_service_client_function( self.quotas_client.show_default_quotas, "tempest.lib.common.rest_client.RestClient.get", self.FAKE_PROJECT_QUOTAS, 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", self.FAKE_PROJECT_QUOTAS, bytes_body, 200, tenant_id=self.FAKE_QUOTA_TENANT_ID) def _test_show_quota_details(self, bytes_body=False): self.check_service_client_function( self.quotas_client.show_quota_details, "tempest.lib.common.rest_client.RestClient.get", self.FAKE_QUOTA_DETAILS, 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_show_default_quotas_with_str_body(self): self._test_show_default_quotas() def test_show_default_quotas_with_bytes_body(self): self._test_show_default_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) def test_show_quota_details_with_str_body(self): self._test_show_quota_details() def test_show_quota_details_with_bytes_body(self): self._test_show_quota_details(bytes_body=True) tempest-23.0.0/tempest/tests/lib/services/network/test_routers_client.py000066400000000000000000000075511361143263300266460ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/network/test_security_group_rules_client.py000066400000000000000000000130141361143263300314270ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/network/test_security_groups_client.py000066400000000000000000000157671361143263300304210ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/network/test_segments_client.py000066400000000000000000000106511361143263300267630ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES 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 segments_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestSegmentsClient(base.BaseServiceTest): FAKE_SEGMENT_ID = '83a59912-a473-11e9-a012-af494c35c9c2' FAKE_NETWORK_ID = '913ab0e4-a473-11e9-84a3-af1c16fc05de' FAKE_SEGMENT_REQUEST = { 'segment': { 'network_id': FAKE_NETWORK_ID, 'segmentation_id': 2000, 'network_type': 'vlan', 'physical_network': 'segment-1' } } FAKE_SEGMENT_RESPONSE = { 'segment': { 'name': 'foo', 'network_id': FAKE_NETWORK_ID, 'segmentation_id': 2000, 'network_type': 'vlan', 'physical_network': 'segment-1', 'revision_number': 1, 'id': FAKE_SEGMENT_ID, 'created_at': '2019-07-12T09:13:56Z', 'updated_at': '2019-07-12T09:13:56Z', 'description': 'bar' } } FAKE_SEGMENTS = { 'segments': [ FAKE_SEGMENT_RESPONSE['segment'] ] } def setUp(self): super(TestSegmentsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.segments_client = segments_client.SegmentsClient( fake_auth, 'compute', 'regionOne') def _test_create_segment(self, bytes_body=False): self.check_service_client_function( self.segments_client.create_segment, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_SEGMENT_RESPONSE, bytes_body, 201, **self.FAKE_SEGMENT_REQUEST['segment'] ) def _test_list_segments(self, bytes_body=False): self.check_service_client_function( self.segments_client.list_segments, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_SEGMENTS, bytes_body, 200 ) def _test_show_segment(self, bytes_body=False): self.check_service_client_function( self.segments_client.show_segment, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_SEGMENT_RESPONSE, bytes_body, 200, segment_id=self.FAKE_SEGMENT_ID ) def _test_update_segment(self, bytes_body=False): update_kwargs = { 'name': 'notfoo' } resp_body = { 'segment': copy.deepcopy(self.FAKE_SEGMENT_RESPONSE['segment']) } resp_body['segment'].update(update_kwargs) self.check_service_client_function( self.segments_client.update_segment, 'tempest.lib.common.rest_client.RestClient.put', resp_body, bytes_body, 200, segment_id=self.FAKE_SEGMENT_ID, **update_kwargs ) def test_create_segment_with_str_body(self): self._test_create_segment() def test_create_segment_with_bytes_body(self): self._test_create_segment(bytes_body=True) def test_update_segment_with_str_body(self): self._test_update_segment() def test_update_segment_with_bytes_body(self): self._test_update_segment(bytes_body=True) def test_show_segment_with_str_body(self): self._test_show_segment() def test_show_segment_with_bytes_body(self): self._test_show_segment(bytes_body=True) def test_delete_segment(self): self.check_service_client_function( self.segments_client.delete_segment, 'tempest.lib.common.rest_client.RestClient.delete', {}, status=204, segment_id=self.FAKE_SEGMENT_ID) def test_list_segment_with_str_body(self): self._test_list_segments() def test_list_segment_with_bytes_body(self): self._test_list_segments(bytes_body=True) tempest-23.0.0/tempest/tests/lib/services/network/test_service_providers_client.py000066400000000000000000000030101361143263300306620ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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-23.0.0/tempest/tests/lib/services/network/test_subnetpools_client.py000066400000000000000000000132661361143263300275200ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/network/test_subnets_client.py000066400000000000000000000210071361143263300266160ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/network/test_tags_client.py000066400000000000000000000101431361143263300260700ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/network/test_versions_client.py000066400000000000000000000072441361143263300270120ustar00rootroot00000000000000# 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. 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): VERSION = "v2.0" FAKE_VERSIONS_INFO = { "versions": [ { "id": "v2.0", "links": [ { "href": "http://openstack.example.com/%s/" % VERSION, "rel": "self" } ], "status": "CURRENT" } ] } FAKE_VERSION_DETAILS = { "resources": [ { "collection": "subnets", "links": [ { "href": "http://openstack.example.com:9696/" "%s/subnets" % VERSION, "rel": "self" } ], "name": "subnet" }, { "collection": "networks", "links": [ { "href": "http://openstack.example.com:9696/" "%s/networks" % VERSION, "rel": "self" } ], "name": "network" }, { "collection": "ports", "links": [ { "href": "http://openstack.example.com:9696/" "%s/ports" % VERSION, "rel": "self" } ], "name": "port" } ] } 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, func, body, bytes_body=False, **kwargs): self.check_service_client_function( func, 'tempest.lib.common.rest_client.RestClient.raw_request', body, bytes_body, 200, **kwargs) def test_list_versions_client_with_str_body(self): self._test_versions_client(self.versions_client.list_versions, self.FAKE_VERSIONS_INFO) def test_list_versions_client_with_bytes_body(self): self._test_versions_client(self.versions_client.list_versions, self.FAKE_VERSIONS_INFO, bytes_body=True) def test_show_version_client_with_str_body(self): self._test_versions_client(self.versions_client.show_version, self.FAKE_VERSION_DETAILS, version=self.VERSION) def test_show_version_client_with_bytes_body(self): self._test_versions_client(self.versions_client.show_version, self.FAKE_VERSION_DETAILS, bytes_body=True, version=self.VERSION) tempest-23.0.0/tempest/tests/lib/services/object_storage/000077500000000000000000000000001361143263300234655ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/object_storage/__init__.py000066400000000000000000000000001361143263300255640ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/object_storage/test_bulk_middleware_client.py000066400000000000000000000046751361143263300316020ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/object_storage/test_capabilities_client.py000066400000000000000000000035521361143263300310720ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/object_storage/test_object_client.py000066400000000000000000000106631361143263300277100ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/placement/000077500000000000000000000000001361143263300224435ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/placement/__init__.py000066400000000000000000000000001361143263300245420ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/placement/test_placement_client.py000066400000000000000000000060721361143263300273670ustar00rootroot00000000000000# Copyright (c) 2019 Ericsson # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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.placement import placement_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestPlacementClient(base.BaseServiceTest): FAKE_ALLOCATION_CANDIDATES = { 'allocation_requests': [ {'allocations': { 'rp-uuid': {'resources': {'VCPU': 42}} }} ], 'provider_summaries': { 'rp-uuid': { 'resources': { 'VCPU': {'used': 0, 'capacity': 64}, 'MEMORY_MB': {'capacity': 11196, 'used': 0}, 'DISK_GB': {'capacity': 19, 'used': 0} }, 'traits': ["HW_CPU_X86_SVM"], } } } FAKE_ALLOCATIONS = { 'allocations': { 'rp-uuid-1': { 'resources': { 'NET_BW_IGR_KILOBIT_PER_SEC': 1 }, 'generation': 14 }, 'rp-uuid2': { 'resources': { 'MEMORY_MB': 256, 'VCPU': 1 }, 'generation': 9 } } } def setUp(self): super(TestPlacementClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = placement_client.PlacementClient( fake_auth, 'placement', 'regionOne') def _test_list_allocation_candidates(self, bytes_body=False): self.check_service_client_function( self.client.list_allocation_candidates, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_ALLOCATION_CANDIDATES, to_utf=bytes_body, **{'resources1': 'NET_BW_IGR_KILOBIT_PER_SEC:1'}) def test_list_allocation_candidates_with_str_body(self): self._test_list_allocation_candidates() def test_list_allocation_candidates_with_bytes_body(self): self._test_list_allocation_candidates(bytes_body=True) def _test_list_allocations(self, bytes_body=False): self.check_service_client_function( self.client.list_allocations, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_ALLOCATIONS, to_utf=bytes_body, **{'consumer_uuid': 'foo-bar'}) def test_list_allocations_with_str_body(self): self._test_list_allocations() def test_list_allocations_with_bytes_body(self): self._test_list_allocations(bytes_body=True) tempest-23.0.0/tempest/tests/lib/services/registry_fixture.py000066400000000000000000000051051361143263300244640ustar00rootroot00000000000000# 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', 'placement', '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-23.0.0/tempest/tests/lib/services/test_clients.py000066400000000000000000000470521361143263300235550ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/volume/000077500000000000000000000000001361143263300220025ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/volume/__init__.py000066400000000000000000000000001361143263300241010ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/volume/v1/000077500000000000000000000000001361143263300223305ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/volume/v1/__init__.py000066400000000000000000000000001361143263300244270ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/volume/v1/test_encryption_types_client.py000066400000000000000000000064321361143263300307220ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/volume/v1/test_quotas_client.py000066400000000000000000000065551361143263300266260ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/volume/v1/test_snapshots_client.py000066400000000000000000000164301361143263300273250ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/volume/v3/000077500000000000000000000000001361143263300223325ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/volume/v3/__init__.py000066400000000000000000000000001361143263300244310ustar00rootroot00000000000000tempest-23.0.0/tempest/tests/lib/services/volume/v3/test_availability_zone_client.py000066400000000000000000000035011361143263300310050ustar00rootroot00000000000000# 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 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-23.0.0/tempest/tests/lib/services/volume/v3/test_backups_client.py000066400000000000000000000123571361143263300267410ustar00rootroot00000000000000# 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_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, "os-backup-project-attr:project_id": "2c67a14be9314c5dae2ee6", "user_id": "515ba0dd59f84f25a6a084a45d8d93b2", "size": 1, "status": "available", "updated_at": "2013-04-02T10:35:27.000000", "volume_id": "e5185058-943a-4cb4-96d9-72c184c337d6", "is_incremental": True, "has_dependent_backups": False } ] } FAKE_BACKUP_UPDATE = { "backup": { "id": "4c65c15f-a5c5-464b-b92a-90e4c04636a7", "name": "fake-backup-name", "links": [ { "href": "fake-url-1", "rel": "self" }, { "href": "fake-url-2", "rel": "bookmark" } ] } } 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) 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-23.0.0/tempest/tests/lib/services/volume/v3/test_capabilities_client.py000066400000000000000000000054601361143263300277370ustar00rootroot00000000000000# 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.v3 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-23.0.0/tempest/tests/lib/services/volume/v3/test_encryption_types_client.py000066400000000000000000000111141361143263300307150ustar00rootroot00000000000000# 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.v3 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": { "volume_type_id": "cbc36478b0bd8e67e89", "control_location": "front-end", "encryption_id": "81e069c6-7394-4856-8df7-3b237ca61f74", "key_size": 128, "provider": "LuksEncryptor", "cipher": "aes-xts-plain64" } } FAKE_UPDATE_ENCRYPTION_TYPE = { "encryption": { "key_size": 64, "provider": "LuksEncryptor", "control_location": "front-end", "cipher": "aes-xts-plain64" } } FAKE_INFO_ENCRYPTION_TYPE = { "volume_type_id": "cbc36478b0bd8e67e89", "control_location": "front-end", "deleted": False, "created_at": "2015-08-27T09:49:58-05:00", "updated_at": "2015-08-29T09:49:58-05:00", "encryption_id": "81e069c6-7394-4856-8df7-3b237ca61f74", "key_size": 128, "provider": "LuksEncryptor", "deleted_at": "2015-08-30T09:49:58-05:00", "cipher": "aes-xts-plain64" } FAKE_ENCRYPTION_SPECS_ITEM = { "cipher": "aes-xts-plain64" } 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_show_encryption_specs_item(self, bytes_body=False): self.check_service_client_function( self.client.show_encryption_specs_item, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_ENCRYPTION_SPECS_ITEM, bytes_body, volume_type_id="cbc36478b0bd8e67e89", key="cipher") 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_show_encryption_specs_item_with_str_body(self): self._test_show_encryption_specs_item() def test_show_encryption_specs_item_with_bytes_body(self): self._test_show_encryption_specs_item(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) def test_update_encryption_type_with_str_body(self): self._test_update_encryption_type() def test_update_encryption_type_with_bytes_body(self): self._test_update_encryption_type(bytes_body=True) def _test_update_encryption_type(self, bytes_body=False): self.check_service_client_function( self.client.update_encryption_type, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_UPDATE_ENCRYPTION_TYPE, bytes_body, volume_type_id="cbc36478b0bd8e67e89") tempest-23.0.0/tempest/tests/lib/services/volume/v3/test_extensions_client.py000066400000000000000000000052051361143263300275020ustar00rootroot00000000000000# 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 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-23.0.0/tempest/tests/lib/services/volume/v3/test_group_snapshots_client.py000066400000000000000000000154201361143263300305410ustar00rootroot00000000000000# 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": { "id": "6f519a48-3183-46cf-a32f-41815f816666", "name": "first_group_snapshot", "group_type_id": "58737af7-786b-48b7-ab7c-2447e74b0ef4" } } 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": "2017-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']} 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-23.0.0/tempest/tests/lib/services/volume/v3/test_group_types_client.py000066400000000000000000000224461361143263300276710ustar00rootroot00000000000000# 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. import copy 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": { "id": "6685584b-1eac-4da6-b5c3-555430cf68ff", "name": "grp-type-001", "description": "group type 001", "is_public": True, "group_specs": { "consistent_group_snapshot_enabled": " False" } } } FAKE_INFO_GROUP_TYPE = { "group_type": { "id": "0e701ab8-1bec-4b9f-b026-a7ba4af13578", "name": "group-type-001", "description": "Test group type 1", "is_public": True, "group_specs": {}, } } FAKE_INFO_DEFAULT_GROUP_TYPE = { "group_type": { "id": "7270c56e-6354-4528-8e8b-f54dee2232c8", "name": "group-type-default", "description": "default group type", "is_public": True, "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, "group_specs": { "consistent_group_snapshot_enabled": " False" } }, { "id": "e479997c-650b-40a4-9dfe-77655818b0d2", "name": "group-type-002", "description": "Test group type 2", "is_public": True, "group_specs": { "consistent_group_snapshot_enabled": " False" } }, { "id": "c5c4769e-213c-40a6-a568-8e797bb691d4", "name": "group-type-003", "description": "Test group type 3", "is_public": True, "group_specs": { "consistent_group_snapshot_enabled": " False" } } ] } FAKE_CREATE_GROUP_TYPE_SPECS = { "group_specs": { "key1": "value1", "key2": "value2" } } FAKE_LIST_GROUP_TYPE_SPECS = { "group_specs": { "key1": "value1", "key2": "value2" } } FAKE_SHOW_GROUP_TYPE_SPECS_ITEM = { "key1": "value1" } FAKE_UPDATE_GROUP_TYPE_SPECS_ITEM = { "key2": "value2-updated" } 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_show_default_group_type(self, bytes_body=False): self.check_service_client_function( self.client.show_default_group_type, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_INFO_DEFAULT_GROUP_TYPE, bytes_body) 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_update_group_types(self, bytes_body=False): resp_body = copy.deepcopy(self.FAKE_INFO_GROUP_TYPE) self.check_service_client_function( self.client.update_group_type, 'tempest.lib.common.rest_client.RestClient.put', resp_body, bytes_body, group_type_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5") def _test_create_or_update_group_type_specs(self, bytes_body=False): group_specs = self.FAKE_CREATE_GROUP_TYPE_SPECS['group_specs'] self.check_service_client_function( self.client.create_or_update_group_type_specs, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_GROUP_TYPE_SPECS, bytes_body, group_type_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5", group_specs=group_specs, status=202) def _test_list_group_type_specs(self, bytes_body=False): self.check_service_client_function( self.client.list_group_type_specs, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_GROUP_TYPE_SPECS, bytes_body, group_type_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5") def _test_show_group_type_specs_item(self, bytes_body=False): self.check_service_client_function( self.client.show_group_type_specs_item, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_SHOW_GROUP_TYPE_SPECS_ITEM, bytes_body, group_type_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5", spec_id="key1") def _test_update_group_type_specs_item(self, bytes_body=False): spec = self.FAKE_UPDATE_GROUP_TYPE_SPECS_ITEM self.check_service_client_function( self.client.update_group_type_specs_item, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_UPDATE_GROUP_TYPE_SPECS_ITEM, bytes_body, group_type_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5", spec_id="key2", spec=spec) 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_show_default_group_type_with_str_body(self): self._test_show_default_group_type() def test_show_default_group_type_with_bytes_body(self): self._test_show_default_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) def test_update_group_types_with_str_body(self): self._test_update_group_types() def test_update_group_types_with_bytes_body(self): self._test_update_group_types(bytes_body=True) def test_create_or_update_group_type_specs_with_str_body(self): self._test_create_or_update_group_type_specs() def test_create_or_update_group_type_specs_with_bytes_body(self): self._test_create_or_update_group_type_specs(bytes_body=True) def test_list_group_type_specs_with_str_body(self): self._test_list_group_type_specs() def test_list_group_type_specs_with_bytes_body(self): self._test_list_group_type_specs(bytes_body=True) def test_show_group_type_specs_item_with_str_body(self): self._test_show_group_type_specs_item() def test_show_group_type_specs_item_with_bytes_body(self): self._test_show_group_type_specs_item(bytes_body=True) def test_update_group_type_specs_item_with_str_body(self): self._test_update_group_type_specs_item() def test_update_group_type_specs_item_with_bytes_body(self): self._test_update_group_type_specs_item(bytes_body=True) def test_delete_group_type_specs_item(self): self.check_service_client_function( self.client.delete_group_type_specs_item, 'tempest.lib.common.rest_client.RestClient.delete', {}, group_type_id='0e58433f-d108-4bf3-a22c-34e6b71ef86b', spec_id='key1', status=202) tempest-23.0.0/tempest/tests/lib/services/volume/v3/test_groups_client.py000066400000000000000000000171171361143263300266270ustar00rootroot00000000000000# 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": { "id": "6f519a48-3183-46cf-a32f-41815f816666", "name": "first_group" } } FAKE_CREATE_GROUP_FROM_GROUP_SNAPSHOT = { "group": { "id": "6f519a48-3183-46cf-a32f-41815f816668", "name": "first_group" } } FAKE_CREATE_GROUP_FROM_GROUP = { "group": { "id": "6f519a48-3183-46cf-a32f-41815f816667", "name": "other_group" } } 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": "2017-06-20T03:50:07Z" } } FAKE_LIST_GROUP_DETAILS = { "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", } ] } FAKE_LIST_GROUPS = { "groups": [ { "id": "0e701ab8-1bec-4b9f-b026-a7ba4af13578", "name": "group-001", }, { "id": "e479997c-650b-40a4-9dfe-77655818b0d2", "name": "group-002", } ] } 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_group_details(self, bytes_body=False): self.check_service_client_function( self.client.list_groups, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_GROUP_DETAILS, bytes_body, detail=True) 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=False) 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_group_details_with_str_body(self): self._test_list_group_details() def test_list_group_details_with_bytes_body(self): self._test_list_group_details(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-23.0.0/tempest/tests/lib/services/volume/v3/test_hosts_client.py000066400000000000000000000065271361143263300264530ustar00rootroot00000000000000# 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 hosts_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestHostsClient(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@rbd", "snapshot_count": "0" } }, { "resource": { "volume_count": "2", "total_volume_gb": "2", "total_snapshot_gb": "0", "project": "f21a9c86d7114bf99c711f4874d80474", "host": "fake-host@lvm", "snapshot_count": "0" } } ] } def setUp(self): super(TestHostsClient, 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-23.0.0/tempest/tests/lib/services/volume/v3/test_limits_client.py000066400000000000000000000041421361143263300266030ustar00rootroot00000000000000# 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 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-23.0.0/tempest/tests/lib/services/volume/v3/test_quota_classes_client.py000066400000000000000000000050761361143263300301570ustar00rootroot00000000000000# 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.v3 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-23.0.0/tempest/tests/lib/services/volume/v3/test_quotas_client.py000066400000000000000000000065761361143263300266330ustar00rootroot00000000000000# 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.v3 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": { "id": '730a1cbd-68ca-4d68-8e09-d603f2dfa72b', "gigabytes": 5, "snapshots": 10, "volumes": 20, 'backups': 10, 'groups': 10, 'per_volume_gigabytes': 1000, 'backup_gigabytes': 2000 } } FAKE_UPDATE_QUOTAS_RESPONSE = { "quota_set": { "gigabytes": 6, "snapshots": 11, "volumes": 21, 'backups': 11, 'groups': 11, 'per_volume_gigabytes': 1001, 'backup_gigabytes': 2001 } } 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_RESPONSE, 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-23.0.0/tempest/tests/lib/services/volume/v3/test_scheduler_stats_client.py000066400000000000000000000057521361143263300305060ustar00rootroot00000000000000# 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.v3 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-23.0.0/tempest/tests/lib/services/volume/v3/test_services_client.py000066400000000000000000000173111361143263300271270ustar00rootroot00000000000000# Copyright 2018 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.v3 import services_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestServicesClient(base.BaseServiceTest): FAKE_SERVICE_LIST = { "services": [ { "status": "enabled", "binary": "cinder-backup", "zone": "nova", "state": "up", "updated_at": "2017-07-20T07:20:17.000000", "host": "fake-host", "disabled_reason": None }, { "status": "enabled", "binary": "cinder-scheduler", "zone": "nova", "state": "up", "updated_at": "2017-07-20T07:20:24.000000", "host": "fake-host", "disabled_reason": None }, { "status": "enabled", "binary": "cinder-volume", "zone": "nova", "frozen": False, "state": "up", "updated_at": "2017-07-20T07:20:20.000000", "host": "fake-host@lvm", "replication_status": "disabled", "active_backend_id": None, "disabled_reason": None } ] } FAKE_SERVICE_REQUEST = { "host": "fake-host", "binary": "cinder-volume" } FAKE_SERVICE_RESPONSE = { "disabled": False, "status": "enabled", "host": "fake-host@lvm", "service": "", "binary": "cinder-volume", "disabled_reason": None } def setUp(self): super(TestServicesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = services_client.ServicesClient(fake_auth, 'volume', 'regionOne') def _test_list_services(self, bytes_body=False, mock_args='os-services', **params): self.check_service_client_function( self.client.list_services, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_SERVICE_LIST, to_utf=bytes_body, mock_args=[mock_args], **params) def _test_enable_service(self, bytes_body=False): resp_body = self.FAKE_SERVICE_RESPONSE kwargs = self.FAKE_SERVICE_REQUEST payload = json.dumps(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(services_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.enable_service, 'tempest.lib.common.rest_client.RestClient.put', resp_body, to_utf=bytes_body, mock_args=['os-services/enable', payload], **kwargs) def _test_disable_service(self, bytes_body=False): resp_body = copy.deepcopy(self.FAKE_SERVICE_RESPONSE) resp_body.pop('disabled_reason') resp_body['disabled'] = True resp_body['status'] = 'disabled' kwargs = self.FAKE_SERVICE_REQUEST payload = json.dumps(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(services_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.disable_service, 'tempest.lib.common.rest_client.RestClient.put', resp_body, to_utf=bytes_body, mock_args=['os-services/disable', payload], **kwargs) def _test_disable_log_reason(self, bytes_body=False): resp_body = copy.deepcopy(self.FAKE_SERVICE_RESPONSE) resp_body['disabled_reason'] = "disabled for test" resp_body['disabled'] = True resp_body['status'] = 'disabled' kwargs = copy.deepcopy(self.FAKE_SERVICE_REQUEST) kwargs.update({"disabled_reason": "disabled for test"}) payload = json.dumps(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(services_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.disable_log_reason, 'tempest.lib.common.rest_client.RestClient.put', resp_body, to_utf=bytes_body, mock_args=['os-services/disable-log-reason', payload], **kwargs) def _test_freeze_host(self, bytes_body=False): kwargs = {'host': 'host1@lvm'} self.check_service_client_function( self.client.freeze_host, 'tempest.lib.common.rest_client.RestClient.put', {}, bytes_body, **kwargs) def _test_thaw_host(self, bytes_body=False): kwargs = {'host': 'host1@lvm'} self.check_service_client_function( self.client.thaw_host, 'tempest.lib.common.rest_client.RestClient.put', {}, bytes_body, **kwargs) 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): mock_args = 'os-services?host=fake-host' self._test_list_services(mock_args=mock_args, host='fake-host') 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_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_disable_log_reason_with_str_body(self): self._test_disable_log_reason() def test_disable_log_reason_with_bytes_body(self): self._test_disable_log_reason(bytes_body=True) def test_freeze_host_with_str_body(self): self._test_freeze_host() def test_freeze_host_with_bytes_body(self): self._test_freeze_host(bytes_body=True) def test_thaw_host_with_str_body(self): self._test_thaw_host() def test_thaw_host_with_bytes_body(self): self._test_thaw_host(bytes_body=True) tempest-23.0.0/tempest/tests/lib/services/volume/v3/test_snapshot_manage_client.py000066400000000000000000000062551361143263300304600ustar00rootroot00000000000000# 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.v3 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-23.0.0/tempest/tests/lib/services/volume/v3/test_snapshots_client.py000066400000000000000000000200041361143263300273170ustar00rootroot00000000000000# 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.v3 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-23.0.0/tempest/tests/lib/services/volume/v3/test_transfers_client.py000066400000000000000000000143651361143263300273210ustar00rootroot00000000000000# 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.v3 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-23.0.0/tempest/tests/lib/services/volume/v3/test_types_client.py000066400000000000000000000244441361143263300264550ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR 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 types_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestTypesClient(base.BaseServiceTest): FAKE_CREATE_VOLUME_TYPE = { 'volume_type': { 'id': '6685584b-1eac-4da6-b5c3-555430cf68ff', 'name': 'vol-type-001', 'description': 'volume type 0001', 'is_public': True, 'os-volume-type-access:is_public': True, 'extra_specs': { 'volume_backend_name': 'rbd' } } } FAKE_DEFAULT_VOLUME_TYPE_INFO = { 'volume_type': { 'id': '6685584b-1eac-4da6-b5c3-555430cf68ff', 'qos_specs_id': None, 'name': 'volume-type-test', 'description': 'default volume type', 'is_public': True, 'os-volume-type-access:is_public': True, 'extra_specs': { 'volume_backend_name': 'rbd' } } } FAKE_UPDATE_VOLUME_TYPE = { 'volume_type': { 'id': '6685584b-1eac-4da6-b5c3-555430cf68ff', 'name': 'volume-type-test', 'description': 'default volume type', 'is_public': True, 'extra_specs': { 'volume_backend_name': 'rbd' } } } FAKE_VOLUME_TYPES = { 'volume_types': [ { 'name': 'volume_type01', 'qos_specs_id': None, 'extra_specs': { 'volume_backend_name': 'lvmdriver-1' }, 'os-volume-type-access:is_public': True, 'is_public': True, 'id': '6685584b-1eac-4da6-b5c3-555430cf68ff', 'description': None }, { 'name': 'volume_type02', 'qos_specs_id': None, 'extra_specs': { 'volume_backend_name': 'lvmdriver-1' }, 'os-volume-type-access:is_public': True, 'is_public': True, 'id': '8eb69a46-df97-4e41-9586-9a40a7533803', 'description': None } ] } FAKE_VOLUME_TYPE_EXTRA_SPECS = { 'extra_specs': { 'capabilities': 'gpu' } } FAKE_SHOW_VOLUME_TYPE_EXTRA_SPECS = { 'capabilities': 'gpu' } FAKE_VOLUME_TYPE_ACCESS = { 'volume_type_access': [{ 'volume_type_id': '3c67e124-39ad-4ace-a507-8bb7bf510c26', 'project_id': 'f270b245cb11498ca4031deb7e141cfa' }] } def setUp(self): super(TestTypesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = types_client.TypesClient(fake_auth, 'volume', 'regionOne') def _test_list_volume_types(self, bytes_body=False): self.check_service_client_function( self.client.list_volume_types, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_VOLUME_TYPES, bytes_body) def _test_show_volume_type(self, bytes_body=False): self.check_service_client_function( self.client.show_volume_type, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_DEFAULT_VOLUME_TYPE_INFO, to_utf=bytes_body, volume_type_id="6685584b-1eac-4da6-b5c3-555430cf68ff") def _test_create_volume_type(self, bytes_body=False): self.check_service_client_function( self.client.create_volume_type, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_VOLUME_TYPE, to_utf=bytes_body, name='volume-type-test') def _test_delete_volume_type(self): self.check_service_client_function( self.client.delete_volume_type, 'tempest.lib.common.rest_client.RestClient.delete', {}, status=202, volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff') def _test_list_volume_types_extra_specs(self, bytes_body=False): self.check_service_client_function( self.client.list_volume_types_extra_specs, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_VOLUME_TYPE_EXTRA_SPECS, to_utf=bytes_body, volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff') def _test_show_volume_type_extra_specs(self, bytes_body=False): self.check_service_client_function( self.client.show_volume_type_extra_specs, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_SHOW_VOLUME_TYPE_EXTRA_SPECS, volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff', extra_specs_name='capabilities', to_utf=bytes_body) def _test_create_volume_type_extra_specs(self, bytes_body=False): self.check_service_client_function( self.client.create_volume_type_extra_specs, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_VOLUME_TYPE_EXTRA_SPECS, volume_type_id="6685584b-1eac-4da6-b5c3-555430cf68ff", extra_specs=self.FAKE_VOLUME_TYPE_EXTRA_SPECS, to_utf=bytes_body) def _test_delete_volume_type_extra_specs(self): self.check_service_client_function( self.client.delete_volume_type_extra_specs, 'tempest.lib.common.rest_client.RestClient.delete', {}, status=202, volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff', extra_spec_name='volume_backend_name') def _test_update_volume_type(self, bytes_body=False): self.check_service_client_function( self.client.update_volume_type, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_UPDATE_VOLUME_TYPE, volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff', to_utf=bytes_body, name='update-volume-type-test', description='test update volume type description') def _test_update_volume_type_extra_specs(self, bytes_body=False): self.check_service_client_function( self.client.update_volume_type_extra_specs, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_SHOW_VOLUME_TYPE_EXTRA_SPECS, extra_spec_name='capabilities', volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff', extra_specs=self.FAKE_SHOW_VOLUME_TYPE_EXTRA_SPECS, to_utf=bytes_body) def _test_add_type_access(self): self.check_service_client_function( self.client.add_type_access, 'tempest.lib.common.rest_client.RestClient.post', {}, status=202, volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff') def _test_remove_type_access(self): self.check_service_client_function( self.client.remove_type_access, 'tempest.lib.common.rest_client.RestClient.post', {}, status=202, volume_type_id='6685584b-1eac-4da6-b5c3-555430cf68ff') def _test_list_type_access(self, bytes_body=False): self.check_service_client_function( self.client.list_type_access, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_VOLUME_TYPE_ACCESS, volume_type_id='3c67e124-39ad-4ace-a507-8bb7bf510c26', to_utf=bytes_body) def test_list_volume_types_with_str_body(self): self._test_list_volume_types() def test_list_volume_types_with_bytes_body(self): self._test_list_volume_types(bytes_body=True) def test_show_volume_type_with_str_body(self): self._test_show_volume_type() def test_show_volume_type_with_bytes_body(self): self._test_show_volume_type(bytes_body=True) def test_create_volume_type_str_body(self): self._test_create_volume_type() def test_create_volume_type_with_bytes_body(self): self._test_create_volume_type(bytes_body=True) def test_list_volume_types_extra_specs_with_str_body(self): self._test_list_volume_types_extra_specs() def test_list_volume_types_extra_specs_with_bytes_body(self): self._test_list_volume_types_extra_specs(bytes_body=True) def test_show_volume_type_extra_specs_with_str_body(self): self._test_show_volume_type_extra_specs() def test_show_volume_type_extra_specs_with_bytes_body(self): self._test_show_volume_type_extra_specs(bytes_body=True) def test_create_volume_type_extra_specs_with_str_body(self): self._test_create_volume_type_extra_specs() def test_create_volume_type_extra_specs_with_bytes_body(self): self._test_create_volume_type_extra_specs(bytes_body=True) def test_delete_volume_type_extra_specs(self): self._test_delete_volume_type_extra_specs() def test_update_volume_type_with_str_body(self): self._test_update_volume_type() def test_update_volume_type_with_bytes_body(self): self._test_update_volume_type(bytes_body=True) def test_delete_volume_type(self): self._test_delete_volume_type() def test_update_volume_type_extra_specs_with_str_body(self): self._test_update_volume_type_extra_specs() def test_update_volume_type_extra_specs_with_bytes_body(self): self._test_update_volume_type_extra_specs(bytes_body=True) def test_add_type_access(self): self._test_add_type_access() def test_remove_type_access(self): self._test_remove_type_access() def test_list_type_access_with_str_body(self): self._test_list_type_access() def test_list_type_access_with_bytes_body(self): self._test_list_type_access(bytes_body=True) tempest-23.0.0/tempest/tests/lib/services/volume/v3/test_user_messages_client.py000066400000000000000000000072211361143263300301500ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/services/volume/v3/test_versions_client.py000066400000000000000000000130421361143263300271510ustar00rootroot00000000000000# 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" } ] } FAKE_VERSION_DETAILS = { "versions": [ { "id": "v3.0", "links": [ {"href": "https://docs.openstack.org/", "type": "text/html", "rel": "describedby"}, {"href": "http://127.0.0.1:44895/v3/", "rel": "self"} ], "media-types": [ {"base": "application/json", "type": "application/vnd.openstack.volume+json;version=3"} ], "min_version": "3.0", "status": "CURRENT", "updated": "2018-07-17T00:00:00Z", "version": "3.59" } ] } def setUp(self): super(TestVersionsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = versions_client.VersionsClient(fake_auth, 'volume', 'regionOne') def _test_get_base_version_url(self, url, expected_base_url): fake_auth = fake_auth_provider.FakeAuthProvider(fake_base_url=url) client = versions_client.VersionsClient(fake_auth, 'volume', 'regionOne') self.assertEqual(expected_base_url, client._get_base_version_url()) 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_show_version(self, version, bytes_body=False): self.check_service_client_function( self.client.show_version, 'tempest.lib.common.rest_client.RestClient.raw_request', self.FAKE_VERSION_DETAILS, bytes_body, 200, version=version) 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) def test_show_version_details_with_str_body(self): self._test_show_version('v3') def test_show_version_details_with_bytes_body(self): self._test_show_version('v3', bytes_body=True) def test_get_base_version_url_app_name(self): self._test_get_base_version_url('https://bar.org/volume/v1/123', 'https://bar.org/volume/') self._test_get_base_version_url('https://bar.org/volume/v2/123', 'https://bar.org/volume/') self._test_get_base_version_url('https://bar.org/volume/v3/123', 'https://bar.org/volume/') tempest-23.0.0/tempest/tests/lib/services/volume/v3/test_volume_manage_client.py000066400000000000000000000101021361143263300301120ustar00rootroot00000000000000# 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.v3 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-23.0.0/tempest/tests/lib/services/volume/v3/test_volumes_client.py000066400000000000000000000116431361143263300270000ustar00rootroot00000000000000# 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.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": 4, "total_count": 4, "metadata": { "key1": ["value1", "value2"], "key2": ["value2"] } } } FAKE_VOLUME_METADATA_ITEM = { "meta": { "name": "metadata1" } } FAKE_VOLUME_IMAGE_METADATA = { "metadata": { "key1": "value1", "key2": "value2" } } 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_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_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) 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-23.0.0/tempest/tests/lib/test_api_microversion_fixture.py000066400000000000000000000054251361143263300254050ustar00rootroot00000000000000# Copyright 2019 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 tempest.lib.common import api_microversion_fixture from tempest.lib.services.compute import base_compute_client from tempest.lib.services.placement import base_placement_client from tempest.lib.services.volume import base_client from tempest.tests import base class TestAPIMicroversionFixture(base.TestCase): def setUp(self): super(TestAPIMicroversionFixture, self).setUp() # Verify that all the microversion are reset back to None # by Fixture. self.assertIsNone(base_compute_client.COMPUTE_MICROVERSION) self.assertIsNone(base_client.VOLUME_MICROVERSION) self.assertIsNone(base_placement_client.PLACEMENT_MICROVERSION) def test_compute_microversion(self): self.useFixture(api_microversion_fixture.APIMicroversionFixture( compute_microversion='2.10')) self.assertEqual('2.10', base_compute_client.COMPUTE_MICROVERSION) self.assertIsNone(base_client.VOLUME_MICROVERSION) self.assertIsNone(base_placement_client.PLACEMENT_MICROVERSION) def test_volume_microversion(self): self.useFixture(api_microversion_fixture.APIMicroversionFixture( volume_microversion='3.10')) self.assertIsNone(base_compute_client.COMPUTE_MICROVERSION) self.assertEqual('3.10', base_client.VOLUME_MICROVERSION) self.assertIsNone(base_placement_client.PLACEMENT_MICROVERSION) def test_placement_microversion(self): self.useFixture(api_microversion_fixture.APIMicroversionFixture( placement_microversion='1.10')) self.assertIsNone(base_compute_client.COMPUTE_MICROVERSION) self.assertIsNone(base_client.VOLUME_MICROVERSION) self.assertEqual('1.10', base_placement_client.PLACEMENT_MICROVERSION) def test_multiple_service_microversion(self): self.useFixture(api_microversion_fixture.APIMicroversionFixture( compute_microversion='2.10', volume_microversion='3.10', placement_microversion='1.10')) self.assertEqual('2.10', base_compute_client.COMPUTE_MICROVERSION) self.assertEqual('3.10', base_client.VOLUME_MICROVERSION) self.assertEqual('1.10', base_placement_client.PLACEMENT_MICROVERSION) tempest-23.0.0/tempest/tests/lib/test_auth.py000066400000000000000000001036231361143263300212270ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/test_base.py000066400000000000000000000037551361143263300212050ustar00rootroot00000000000000# 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.""" cls.orig_skip_exception = cls.skipException 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-23.0.0/tempest/tests/lib/test_credentials.py000066400000000000000000000164741361143263300225720ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/lib/test_decorators.py000066400000000000000000000247621361143263300224410ustar00rootroot00000000000000# 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 abc import mock import six import testtools from tempest.lib import base as test from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc 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: if 'condition' in decorator_args: if decorator_args['condition']: # The expected attrs should be in the function. self.assertEqual(set(expected_attrs), getattr(foo, '__testtools_attrs')) else: # The expected attrs should not be in the function. self.assertNotIn('__testtools_attrs', foo) else: self.assertEqual(set(expected_attrs), getattr(foo, '__testtools_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']) def test_attr_decorator_condition_false(self): self._test_attr_helper(None, type='slow', condition=False) def test_attr_decorator_condition_true(self): self._test_attr_helper(expected_attrs=['slow'], type='slow', condition=True) @six.add_metaclass(abc.ABCMeta) class BaseSkipDecoratorTests(object): @abc.abstractmethod def _test_skip_helper(self, raise_exception=True, expected_to_skip=True, **decorator_args): return def test_skip_launchpad_bug(self): self._test_skip_helper(bug='12345') def test_skip_storyboard_bug(self): self._test_skip_helper(bug='1992', bug_type='storyboard') def test_skip_bug_without_bug_never_skips(self): """Never skip without a bug parameter.""" self._test_skip_helper( raise_exception=False, expected_to_skip=False, condition=True) self._test_skip_helper( raise_exception=False, expected_to_skip=False) def test_skip_invalid_bug_number(self): """Raise InvalidParam if with an invalid bug number""" self.assertRaises(lib_exc.InvalidParam, self._test_skip_helper, bug='critical_bug') class TestSkipBecauseDecorator(base.TestCase, BaseSkipDecoratorTests): def _test_skip_helper(self, raise_exception=True, 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: e = self.assertRaises(testtools.TestCase.skipException, t.test_bar) bug = decorator_args['bug'] bug_type = decorator_args.get('bug_type', 'launchpad') self.assertRegex( str(e), r'Skipped until bug\: %s.*' % decorators._get_bug_url( bug, bug_type) ) else: # assert that test_bar returned 0 self.assertEqual(TestFoo('test_bar').test_bar(), 0) def test_skip_because_launchpad_bug_and_condition_true(self): self._test_skip_helper(bug='12348', condition=True) def test_skip_because_launchpad_bug_and_condition_false(self): self._test_skip_helper(expected_to_skip=False, bug='12349', condition=False) def test_skip_because_storyboard_bug_and_condition_false(self): self._test_skip_helper(expected_to_skip=False, bug='1992', bug_type='storyboard', condition=False) def test_skip_because_storyboard_bug_and_condition_true(self): self._test_skip_helper(bug='1992', bug_type='storyboard', condition=True) class TestUnstableTestDecorator(base.TestCase, BaseSkipDecoratorTests): def _test_skip_helper(self, raise_exception=True, expected_to_skip=True, **decorator_args): fail_test_reason = "test_bar failed" class TestFoo(test.BaseTestCase): @decorators.unstable_test(**decorator_args) def test_bar(self): if raise_exception: raise Exception(fail_test_reason) else: return 0 t = TestFoo('test_bar') if expected_to_skip: e = self.assertRaises(testtools.TestCase.skipException, t.test_bar) bug = decorator_args['bug'] bug_type = decorator_args.get('bug_type', 'launchpad') self.assertRegex( str(e), r'Marked as unstable and skipped because of bug\: %s.*, ' 'failure was: %s' % (decorators._get_bug_url(bug, bug_type), fail_test_reason) ) else: # assert that test_bar returned 0 self.assertEqual(TestFoo('test_bar').test_bar(), 0) def test_skip_bug_given_exception_not_raised(self): self._test_skip_helper(raise_exception=False, expected_to_skip=False, bug='1234') 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 TestRelatedBugDecorator(base.TestCase): def _get_my_exception(self): class MyException(Exception): def __init__(self, status_code): self.status_code = status_code return MyException 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_with_launchpad_bug_type(self): """Validate related_bug decorator with bug_type == 'launchpad'""" MyException = self._get_my_exception() 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', 'https://launchpad.net/bugs/1234') def test_relatedbug_when_exception_with_storyboard_bug_type(self): """Validate related_bug decorator with bug_type == 'storyboard'""" MyException = self._get_my_exception() def f(self): raise MyException(status_code=500) @decorators.related_bug(bug="1234", status_code=500, bug_type='storyboard') 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', 'https://storyboard.openstack.org/#!/story/1234') def test_relatedbug_when_exception_invalid_bug_type(self): """Check related_bug decorator raises exc when bug_type is not valid""" MyException = self._get_my_exception() def f(self): raise MyException(status_code=500) @decorators.related_bug(bug="1234", status_code=500, bug_type=mock.sentinel.invalid) def test_foo(self): f(self) with mock.patch.object(decorators.LOG, 'error'): self.assertRaises(lib_exc.InvalidParam, test_foo, object()) def test_relatedbug_when_exception_invalid_bug_number(self): """Check related_bug decorator raises exc when bug_number != digit""" MyException = self._get_my_exception() def f(self): raise MyException(status_code=500) @decorators.related_bug(bug="not a digit", status_code=500, bug_type='launchpad') def test_foo(self): f(self) with mock.patch.object(decorators.LOG, 'error'): self.assertRaises(lib_exc.InvalidParam, test_foo, object()) tempest-23.0.0/tempest/tests/lib/test_ssh.py000066400000000000000000000274451361143263300210720ustar00rootroot00000000000000# 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, client_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() client_mock.close.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, client_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-23.0.0/tempest/tests/lib/test_tempest_lib.py000066400000000000000000000013631361143263300225730ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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-23.0.0/tempest/tests/test_base_test.py000066400000000000000000000116541361143263300214730ustar00rootroot00000000000000# 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.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.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.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-23.0.0/tempest/tests/test_config.py000066400000000000000000000100331361143263300207550ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/test_decorators.py000066400000000000000000000157411361143263300216700ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/test_hacking.py000066400000000000000000000320231361143263300211170ustar00rootroot00000000000000# 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) def _test_no_negatve_test_attribute_applied_to_negative_test( self, filename, with_other_decorators=False, with_negative_decorator=True, expected_success=True): check = checks.negative_test_attribute_always_applied_to_negative_tests other_decorators = [ "@decorators.idempotent_id(123)", "@utils.requires_ext(extension='ext', service='svc')" ] if with_other_decorators: # Include multiple decorators to verify that this check works with # arbitrarily many decorators. These insert decorators above the # @decorators.attr(type=['negative']) decorator. for decorator in other_decorators: self.assertIsNone(check(" %s" % decorator, filename)) if with_negative_decorator: self.assertIsNone( check("@decorators.attr(type=['negative'])", filename)) if with_other_decorators: # Include multiple decorators to verify that this check works with # arbitrarily many decorators. These insert decorators between # the test and the @decorators.attr(type=['negative']) decorator. for decorator in other_decorators: self.assertIsNone(check(" %s" % decorator, filename)) final_result = check(" def test_some_negative_case", filename) if expected_success: self.assertIsNone(final_result) else: self.assertIsInstance(final_result, tuple) self.assertFalse(final_result[0]) def test_no_negatve_test_attribute_applied_to_negative_test(self): # Check negative filename, negative decorator passes self._test_no_negatve_test_attribute_applied_to_negative_test( "./tempest/api/test_something_negative.py") # Check negative filename, negative decorator, other decorators passes self._test_no_negatve_test_attribute_applied_to_negative_test( "./tempest/api/test_something_negative.py", with_other_decorators=True) # Check non-negative filename skips check, causing pass self._test_no_negatve_test_attribute_applied_to_negative_test( "./tempest/api/test_something.py") # Check negative filename, no negative decorator fails self._test_no_negatve_test_attribute_applied_to_negative_test( "./tempest/api/test_something_negative.py", with_negative_decorator=False, expected_success=False) # Check negative filename, no negative decorator, other decorators # fails self._test_no_negatve_test_attribute_applied_to_negative_test( "./tempest/api/test_something_negative.py", with_other_decorators=True, with_negative_decorator=False, expected_success=False) tempest-23.0.0/tempest/tests/test_imports.py000066400000000000000000000044411361143263300212130ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/test_list_tests.py000066400000000000000000000032531361143263300217130ustar00rootroot00000000000000# 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(r'(\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-23.0.0/tempest/tests/test_microversions.py000066400000000000000000000142051361143263300224170ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/test_tempest_plugin.py000066400000000000000000000075361361143263300225650ustar00rootroot00000000000000# 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-23.0.0/tempest/tests/test_test.py000066400000000000000000000576451361143263300205130ustar00rootroot00000000000000# 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.skipException()) 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.TEARDOWN_FIXTURES[0]]), 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-23.0.0/tempest/tests/utils.py000066400000000000000000000021231361143263300176120ustar00rootroot00000000000000# 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-23.0.0/tempest/version.py000066400000000000000000000012631361143263300170010ustar00rootroot00000000000000# 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-23.0.0/test-requirements.txt000066400000000000000000000005501361143263300175200ustar00rootroot00000000000000# 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>=1.1.0,<1.2.0 # Apache-2.0 mock>=2.0.0 # BSD coverage!=4.4,>=4.0 # Apache-2.0 oslotest>=3.2.0 # Apache-2.0 flake8-import-order==0.11 # LGPLv3 tempest-23.0.0/tools/000077500000000000000000000000001361143263300144175ustar00rootroot00000000000000tempest-23.0.0/tools/check_logs.py000077500000000000000000000140511361143263300170760ustar00rootroot00000000000000#!/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(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(f.read().splitlines(), regexp, whitelist): logs_with_errors.append(name) return logs_with_errors def scan_content(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(r'(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-23.0.0/tools/format.sh000077500000000000000000000012301361143263300162420ustar00rootroot00000000000000#!/bin/bash cd $(dirname "$(readlink -f "$0")") AUTOPEP8=`which autopep8 2>/dev/null` if [[ -z "$AUTOPEP8" ]]; then AUTOPEP8=`which autopep8-3` fi if [[ -z "$AUTOPEP8" ]]; then echo "Unable to locate autopep8" >&2 exit 2 fi # isort is not compatible with the default flake8 (H306), maybe flake8-isort # isort -rc -sl -fss ../tempest ../setup.py $AUTOPEP8 --exit-code --max-line-length=79 --experimental --in-place \ -r ../tempest ../setup.py ERROR=$? if [[ $ERROR -eq 0 ]]; then echo "Formatting was not needed." >&2 exit 0 elif [[ $ERROR -eq 1 ]]; then echo "Formatting failed.." >&2 exit 1 else echo "done" >&2 fi tempest-23.0.0/tools/generate-tempest-plugins-list.py000066400000000000000000000106401361143263300226730ustar00rootroot00000000000000#! /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.opendev.org Gerrit API # working directory # * network access to https://opendev.org/openstack import json import re import sys import urllib3 from urllib3.util import retry # List of projects having tempest plugin stale or unmaintained for a long time # (6 months or more) # TODO(masayukig): Some of these can be removed from BLACKLIST in the future # when the patches are merged. BLACKLIST = [ 'x/gce-api', # It looks gce-api doesn't support python3 yet. 'x/group-based-policy', # It looks this doesn't support python3 yet. 'x/intel-nfv-ci-tests', # https://review.opendev.org/#/c/634640/ 'openstack/networking-generic-switch', # https://review.opendev.org/#/c/634846/ 'openstack/networking-l2gw-tempest-plugin', # https://review.opendev.org/#/c/635093/ 'openstack/networking-midonet', # https://review.opendev.org/#/c/635096/ 'x/networking-plumgrid', # https://review.opendev.org/#/c/635096/ 'x/networking-spp', # https://review.opendev.org/#/c/635098/ 'openstack/neutron-dynamic-routing', # https://review.opendev.org/#/c/637718/ 'openstack/neutron-vpnaas', # https://review.opendev.org/#/c/637719/ 'x/valet', # https://review.opendev.org/#/c/638339/ ] url = 'https://review.opendev.org/projects/' # This is what a project looks like ''' "openstack-attic/akanda": { "id": "openstack-attic%2Fakanda", "state": "READ_ONLY" }, ''' http = urllib3.PoolManager(cert_reqs='CERT_REQUIRED') retries = retry.Retry(status_forcelist=[500], backoff_factor=1.0) def has_tempest_plugin(proj): try: r = http.request('GET', "https://opendev.org/%s/raw/branch/" "master/setup.cfg" % proj, retries=retries) if r.status == 404: return False except urllib3.exceptions.MaxRetryError as err: # We should not ignore non 404 errors. raise err p = re.compile(r'^tempest\.test_plugins', re.M) if p.findall(r.data.decode('utf-8')): return True else: False if len(sys.argv) > 1 and sys.argv[1] == 'blacklist': for black_plugin in BLACKLIST: print(black_plugin) # We just need BLACKLIST when we use this `blacklist` option. # So, this exits here. sys.exit() r = http.request('GET', url, retries=retries) # 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. content = r.data.decode('utf-8')[4:] projects = sorted(json.loads(content)) # Retrieve projects having no deployment tool repo (such as deb, # puppet, ansible, etc.), infra repos, ui or spec namespace as those # namespaces do not contains tempest plugins. projects_list = [i for i in projects if not ( i.startswith('openstack-dev/') or i.startswith('openstack-infra/') or i.startswith('openstack/ansible-') or i.startswith('openstack/charm-') or i.startswith('openstack/cookbook-openstack-') or i.startswith('openstack/devstack-') or i.startswith('openstack/fuel-') or i.startswith('openstack/deb-') or i.startswith('openstack/puppet-') or i.startswith('openstack/openstack-ansible-') or i.startswith('x/deb-') or i.startswith('x/fuel-') or i.startswith('x/python-') or i.startswith('zuul/') or i.endswith('-ui') or i.endswith('-specs'))] found_plugins = list(filter(has_tempest_plugin, projects_list)) # We have tempest plugins not only in 'openstack/' namespace but also the # other name spaces such as 'airship/', 'x/', etc. # So, we print all of them here. for project in found_plugins: print(project) tempest-23.0.0/tools/generate-tempest-plugins-list.sh000077500000000000000000000062131361143263300226610ustar00rootroot00000000000000#!/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.opendev.org Gerrit API # working directory # * network access to https://opendev.org/openstack # )) # # 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 ( 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) name_col_len=$(echo "${sorted_plugins}" | wc -L) name_col_len=$(( name_col_len + 20 )) # Print the title underline for a RST table. function title_underline { printf "== " local len=$1 while [[ $len -gt 0 ]]; do printf "=" len=$(( len - 1)) done printf " ===\n" } function print_plugin_table { title_underline ${name_col_len} printf "%-3s %-${name_col_len}s %s\n" "SR" "Plugin Name" "URL" title_underline ${name_col_len} i=0 for plugin in $1; do i=$((i+1)) giturl="https://opendev.org/${plugin}" printf "%-3s %-${name_col_len}s %s\n" "$i" "${plugin}" "${giturl}" done title_underline ${name_col_len} } printf "\n\n" print_plugin_table "${sorted_plugins}" printf "\n\n" # Print BLACKLIST if [[ -r doc/source/data/tempest-blacklisted-plugins-registry.header ]]; then cat doc/source/data/tempest-blacklisted-plugins-registry.header fi blacklist=$(python tools/generate-tempest-plugins-list.py blacklist) name_col_len=$(echo "${blacklist}" | wc -L) name_col_len=$(( name_col_len + 20 )) printf "\n\n" print_plugin_table "${blacklist}" printf "\n\n" 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-23.0.0/tools/skip_tracker.py000077500000000000000000000017431361143263300174620ustar00rootroot00000000000000#!/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-23.0.0/tools/tempest-integrated-gate-compute-blacklist.txt000066400000000000000000000012451361143263300253250ustar00rootroot00000000000000# This file includes the backlist of tests which need to be # skipped for Integrated-gate-compute template. Integrated-gate-compute template # needs to run only Nova, Neutron, Cinder and Glance related tests and rest all # tests will be skipped by below list. # Skip keystone and Swift API tests. tempest.api.identity tempest.api.object_storage # Skip Swift only scenario tests. tempest.scenario.test_object_storage_basic_ops.TestObjectStorageBasicOps.test_swift_basic_ops tempest.scenario.test_object_storage_basic_ops.TestObjectStorageBasicOps.test_swift_acl_anonymous_download tempest.scenario.test_volume_backup_restore.TestVolumeBackupRestore.test_volume_backup_restore tempest-23.0.0/tools/tempest-integrated-gate-networking-blacklist.txt000066400000000000000000000021101361143263300260300ustar00rootroot00000000000000# This file includes the backlist of tests which need to be # skipped for Integrated-gate-networking template. # Skip Cinder, Glance, keystone and Swift API tests. tempest.api.volume tempest.api.compute.volumes tempest.api.image tempest.api.compute.images tempest.api.object_storage tempest.api.identity # Skip Cinder, Glance and Swift only scenario tests. tempest.scenario.test_encrypted_cinder_volumes.TestEncryptedCinderVolumes.test_encrypted_cinder_volumes_luks tempest.scenario.test_encrypted_cinder_volumes.TestEncryptedCinderVolumes.test_encrypted_cinder_volumes_cryptsetup tempest.scenario.test_object_storage_basic_ops.TestObjectStorageBasicOps.test_swift_basic_ops tempest.scenario.test_object_storage_basic_ops.TestObjectStorageBasicOps.test_swift_acl_anonymous_download tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_boot_server_from_encrypted_volume_luks tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_image_defined_boot_from_volume tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_create_server_from_volume_snapshot tempest-23.0.0/tools/tempest-integrated-gate-object-storage-blacklist.txt000066400000000000000000000017141361143263300265620ustar00rootroot00000000000000# This file includes the backlist of tests which need to be # skipped for Integrated-gate-object-storage template. Integrated-gate-object-storage template # needs to run only Swift, Cinder and Glance related tests and rest all # tests will be skipped by below list. # Skip network, compute, keystone API tests. tempest.api.network tempest.api.compute tempest.api.identity # Skip network, compute, keystone only scenario tests tempest.scenario.test_network_advanced_server_ops.TestNetworkAdvancedServerOps.test_network_advanced_server_ops tempest.scenario.test_network_basic_ops.TestNetworkBasicOps.test_network_basic_ops tempest.scenario.test_network_v6.TestGettingAddress.test_security_groups_basic_ops tempest.scenario.test_server_advanced_ops.TestServerAdvancedOps.test_server_sequence_suspend_resume tempest.scenario.test_server_basic_ops.TestServerBasicOps.test_server_basic_ops tempest.scenario.test_server_multinode.TestServerMultinode.test_schedule_to_all_nodes tempest-23.0.0/tools/tempest-integrated-gate-placement-blacklist.txt000066400000000000000000000022321361143263300256160ustar00rootroot00000000000000# This file includes the backlist of tests which need to be # skipped for Integrated-gate-placement template. Integrated-gate-placement template # needs to run only Nova and Neutron related tests and rest all # tests will be skipped by below list. # Skip Cinder, Glance, keystone and Swift API tests. tempest.api.volume tempest.api.image tempest.api.identity tempest.api.object_storage # Skip Cinder, Glance and Swift only scenario tests. tempest.scenario.test_encrypted_cinder_volumes.TestEncryptedCinderVolumes.test_encrypted_cinder_volumes_luks tempest.scenario.test_encrypted_cinder_volumes.TestEncryptedCinderVolumes.test_encrypted_cinder_volumes_cryptsetup tempest.scenario.test_object_storage_basic_ops.TestObjectStorageBasicOps.test_swift_basic_ops tempest.scenario.test_object_storage_basic_ops.TestObjectStorageBasicOps.test_swift_acl_anonymous_download tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_boot_server_from_encrypted_volume_luks tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_image_defined_boot_from_volume tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_create_server_from_volume_snapshot tempest-23.0.0/tools/tempest-integrated-gate-storage-blacklist.txt000066400000000000000000000012131361143263300253100ustar00rootroot00000000000000# This file includes the backlist of tests which need to be # skipped for Integrated-gate-storage template. Integrated-gate-storage template # needs to run only Cinder, Glance, Swift and Nova related tests and rest all # tests will be skipped by below list. # Skip network, keystone API tests. tempest.api.network tempest.api.identity # Skip network only scenario tests. tempest.scenario.test_network_advanced_server_ops.TestNetworkAdvancedServerOps.test_network_advanced_server_ops tempest.scenario.test_network_basic_ops.TestNetworkBasicOps.test_network_basic_ops tempest.scenario.test_network_v6.TestGettingAddress.test_security_groups_basic_ops tempest-23.0.0/tools/tempest-plugin-sanity.sh000066400000000000000000000102011361143263300212270ustar00rootroot00000000000000#!/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: # * Retrieve the project lists having tempest plugin if project name is # given. # * For each project in a list, it does: # * Create virtualenv and install tempest in it # * Clone the Project # * Install the Project and also installs dependencies from # test-requirements.txt. # * Create Tempest workspace # * List tempest plugins # * List tempest plugins tests # * Delete virtualenv and project repo # # 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)" BLACKLIST="$(python tools/generate-tempest-plugins-list.py blacklist)" # 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 \ https://opendev.org \ "$1" elif [ -e /usr/bin/git ]; then /usr/bin/git clone https://opendev.org/"$1" \ "$1" fi } : ${UPPER_CONSTRAINTS_FILE:="https://releases.openstack.org/constraints/upper/master"} DEPS="-c${UPPER_CONSTRAINTS_FILE}" # function to create virtualenv to perform sanity operation function prepare_workspace { SANITY_DIR=$(pwd) virtualenv -p python3 --clear "$SANITY_DIR"/.venv export TVENV="$SANITY_DIR/tools/with_venv.sh" cd "$SANITY_DIR" # Install tempest with test dependencies in a venv "$TVENV" pip install -e . -r test-requirements.txt } # Function to install project function install_project { "$TVENV" pip install $DEPS "$SANITY_DIR"/"$1" # Check for test-requirements.txt file in a project then install it. if [ -e "$SANITY_DIR"/"$1"/test-requirements.txt ]; then "$TVENV" pip install $DEPS -r "$SANITY_DIR"/"$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 retval=$? # Delete tempest workspace # NOTE: Cleaning should be done even if an error occurs. "$TVENV" tempest workspace remove --name tempest_sanity --rmdir cd "$SANITY_DIR" # Remove the sanity workspace in case of remaining rm -fr "$SANITY_DIR"/tempest_sanity # Remove the project directory after sanity run rm -fr "$SANITY_DIR"/"$1" return $retval } # Function to run sanity check on each project function plugin_sanity_check { prepare_workspace && \ clone_project "$1" && \ install_project "$1" && \ tempest_sanity "$1" return $? } # 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, " > $SANITY_DIR/$project.txt fi done echo "Passed Plugins: $passed_plugin" echo "Failed Plugins: $failed_plugin" # Check for failed status if [[ -n $failed_plugin ]]; then exit 1 fi tempest-23.0.0/tools/verify-ipv6-only-deployments.sh000077500000000000000000000107231361143263300224670ustar00rootroot00000000000000#!/bin/bash # # # NOTE(gmann): This script is used in 'devstack-tempest-ipv6' zuul job to verify that # services are deployed on IPv6 properly or not. This will capture if any devstck or devstack # plugins are missing the required setting to listen on IPv6 address. This is run as part of # run phase of zuul job and before test run. Child job of 'devstack-tempest-ipv6' # can expand the IPv6 verification specific to project by defining the new post-run script which # will run along with this base script. # If there are more common verification for IPv6 then we can always extent this script. # Keep track of the DevStack directory TOP_DIR=$(cd $(dirname "$0")/../../devstack && pwd) source $TOP_DIR/stackrc source $TOP_DIR/openrc admin admin function verify_devstack_ipv6_setting { local _service_host='' _service_host=$(echo $SERVICE_HOST | tr -d []) local _host_ipv6='' _host_ipv6=$(echo $HOST_IPV6 | tr -d []) local _service_listen_address='' _service_listen_address=$(echo $SERVICE_LISTEN_ADDRESS | tr -d []) local _service_local_host='' _service_local_host=$(echo $SERVICE_LOCAL_HOST | tr -d []) if [[ "$SERVICE_IP_VERSION" != 6 ]]; then echo $SERVICE_IP_VERSION "SERVICE_IP_VERSION is not set to 6 which is must for devstack to deploy services with IPv6 address." exit 1 fi is_service_host_ipv6=$(python3 -c 'import oslo_utils.netutils as nutils; print(nutils.is_valid_ipv6("'$_service_host'"))') if [[ "$is_service_host_ipv6" != "True" ]]; then echo $SERVICE_HOST "SERVICE_HOST is not ipv6 which means devstack cannot deploy services on IPv6 address." exit 1 fi is_host_ipv6=$(python3 -c 'import oslo_utils.netutils as nutils; print(nutils.is_valid_ipv6("'$_host_ipv6'"))') if [[ "$is_host_ipv6" != "True" ]]; then echo $HOST_IPV6 "HOST_IPV6 is not ipv6 which means devstack cannot deploy services on IPv6 address." exit 1 fi is_service_listen_address=$(python3 -c 'import oslo_utils.netutils as nutils; print(nutils.is_valid_ipv6("'$_service_listen_address'"))') if [[ "$is_service_listen_address" != "True" ]]; then echo $SERVICE_LISTEN_ADDRESS "SERVICE_LISTEN_ADDRESS is not ipv6 which means devstack cannot deploy services on IPv6 address." exit 1 fi is_service_local_host=$(python3 -c 'import oslo_utils.netutils as nutils; print(nutils.is_valid_ipv6("'$_service_local_host'"))') if [[ "$is_service_local_host" != "True" ]]; then echo $SERVICE_LOCAL_HOST "SERVICE_LOCAL_HOST is not ipv6 which means devstack cannot deploy services on IPv6 address." exit 1 fi echo "Devstack is properly configured with IPv6" echo "SERVICE_IP_VERSION: " $SERVICE_IP_VERSION "HOST_IPV6: " $HOST_IPV6 "SERVICE_HOST: " $SERVICE_HOST "SERVICE_LISTEN_ADDRESS: " $SERVICE_LISTEN_ADDRESS "SERVICE_LOCAL_HOST: " $SERVICE_LOCAL_HOST } function sanity_check_system_ipv6_enabled { system_ipv6_enabled=$(python3 -c 'import oslo_utils.netutils as nutils; print(nutils.is_ipv6_enabled())') if [[ $system_ipv6_enabled != "True" ]]; then echo "IPv6 is disabled in system" exit 1 fi echo "IPv6 is enabled in system" } function verify_service_listen_address_is_ipv6 { local endpoints_verified=False local all_ipv6=True endpoints=$(openstack endpoint list -f value -c URL) for endpoint in ${endpoints}; do local endpoint_address='' endpoint_address=$(echo "$endpoint" | awk -F/ '{print $3}' | awk -F] '{print $1}') endpoint_address=$(echo $endpoint_address | tr -d []) local is_endpoint_ipv6='' is_endpoint_ipv6=$(python3 -c 'import oslo_utils.netutils as nutils; print(nutils.is_valid_ipv6("'$endpoint_address'"))') if [[ "$is_endpoint_ipv6" != "True" ]]; then all_ipv6=False echo $endpoint ": This is not ipv6 endpoint which means corresponding service is not listening on IPv6 address." continue fi endpoints_verified=True done if [[ "$all_ipv6" == "False" ]] || [[ "$endpoints_verified" == "False" ]]; then exit 1 fi echo "All services deployed by devstack is on IPv6 endpoints" echo $endpoints } #First thing to verify if system has IPv6 enabled or not sanity_check_system_ipv6_enabled #Verify whether devstack is configured properly with IPv6 setting verify_devstack_ipv6_setting #Get all registrfed endpoints by devstack in keystone and verify that each endpoints address is IPv6. verify_service_listen_address_is_ipv6 tempest-23.0.0/tools/with_venv.sh000077500000000000000000000003161361143263300167670ustar00rootroot00000000000000#!/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-23.0.0/tox.ini000066400000000000000000000320251361143263300145740ustar00rootroot00000000000000[tox] envlist = pep8,py36,py37,py27,bashate,pip-check-reqs minversion = 2.3.1 skipsdist = True [tempestenv] sitepackages = False setenv = VIRTUAL_ENV={envdir} OS_TEST_PATH=./tempest/test_discover deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt [testenv] setenv = VIRTUAL_ENV={envdir} OS_LOG_CAPTURE=1 OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=160 PYTHONWARNINGS=default::DeprecationWarning,ignore::DeprecationWarning:distutils,ignore::DeprecationWarning:site 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 GABBI_TEMPEST_PATH usedevelop = True install_command = pip install {opts} {packages} whitelist_externals = * deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -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] setenv = {[testenv]setenv} PYTHON=coverage run --source tempest --parallel-mode commands = coverage erase find . -type f -name "*.pyc" -delete stestr --test-path ./tempest/tests run {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml coverage report [testenv:debug] basepython = python3 commands = oslo_debug_helper -t 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:all-plugin] # DEPRECATED # NOTE(andreaf) The all-plugin tox env uses sitepackages # so that plugins installed outsite of Tempest virtual environment # can be discovered. After the implementation during the Queens # release cycle of the goal of moving Tempest plugins in dedicated # git repos, this environment should not be used anymore. "all" # should be used instead with the appropriate regex filtering. sitepackages = True # 'all' includes slow tests setenv = {[tempestenv]setenv} OS_TEST_TIMEOUT={env:OS_TEST_TIMEOUT:1200} deps = {[tempestenv]deps} commands = echo "WARNING: The all-plugin env is deprecated and will be removed" echo "WARNING Please use the 'all' environment for Tempest plugins." find . -type f -name "*.pyc" -delete tempest run --regex {posargs:''} [testenv:all-site-packages] 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 # FIXME: We can replace it with the `--black-regex` option to exclude tests now. 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-parallel] envdir = .tox/tempest sitepackages = {[tempestenv]sitepackages} setenv = {[tempestenv]setenv} deps = {[tempestenv]deps} # The regex below is used to select all tempest scenario and including the non slow api tests commands = find . -type f -name "*.pyc" -delete tempest run --regex '(^tempest\.scenario.*)|(?!.*\[.*\bslow\b.*\])(^tempest\.api)' {posargs} [testenv:integrated-network] 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 and # tests listed in blacklist file: commands = find . -type f -name "*.pyc" -delete tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --blacklist_file ./tools/tempest-integrated-gate-networking-blacklist.txt {posargs} tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --blacklist_file ./tools/tempest-integrated-gate-networking-blacklist.txt {posargs} [testenv:integrated-compute] 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 and # tests listed in blacklist file: commands = find . -type f -name "*.pyc" -delete tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --blacklist_file ./tools/tempest-integrated-gate-compute-blacklist.txt {posargs} tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --blacklist_file ./tools/tempest-integrated-gate-compute-blacklist.txt {posargs} [testenv:integrated-placement] 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 and # tests listed in blacklist file: commands = find . -type f -name "*.pyc" -delete tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --blacklist_file ./tools/tempest-integrated-gate-placement-blacklist.txt {posargs} tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --blacklist_file ./tools/tempest-integrated-gate-placement-blacklist.txt {posargs} [testenv:integrated-storage] 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 and # tests listed in blacklist file: commands = find . -type f -name "*.pyc" -delete tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --blacklist_file ./tools/tempest-integrated-gate-storage-blacklist.txt {posargs} tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --blacklist_file ./tools/tempest-integrated-gate-storage-blacklist.txt {posargs} [testenv:integrated-object-storage] 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 and # tests listed in blacklist file: commands = find . -type f -name "*.pyc" -delete tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --blacklist_file ./tools/tempest-integrated-gate-object-storage-blacklist.txt {posargs} tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --blacklist_file ./tools/tempest-integrated-gate-object-storage-blacklist.txt {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 # FIXME: We can replace it with the `--black-regex` option to exclude tests now. 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:slow-serial] envdir = .tox/tempest sitepackages = {[tempestenv]sitepackages} setenv = {[tempestenv]setenv} deps = {[tempestenv]deps} # The regex below is used to select the slow tagged tests to run serially: commands = find . -type f -name "*.pyc" -delete tempest run --serial --regex '\[.*\bslow\b.*\]' {posargs} [testenv:ipv6-only] envdir = .tox/tempest sitepackages = {[tempestenv]sitepackages} setenv = {[tempestenv]setenv} deps = {[tempestenv]deps} # Run only smoke and ipv6 tests. This env is used to tests # the ipv6 deployments and basic tests run fine so that we can # verify that services listen on IPv6 address. commands = find . -type f -name "*.pyc" -delete tempest run --regex '\[.*\bsmoke|ipv6|test_network_v6\b.*\]' {posargs} [testenv:venv] deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = {posargs} [testenv:venv-tempest] envdir = .tox/tempest sitepackages = {[tempestenv]sitepackages} setenv = {[tempestenv]setenv} deps = {[tempestenv]deps} commands = {posargs} [testenv:docs] basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = rm -rf doc/build sphinx-build -W -b html doc/source doc/build/html whitelist_externals = rm [testenv:pdf-docs] basepython = python3 deps = {[testenv:docs]deps} whitelist_externals = make commands = sphinx-build -W -b latex doc/source doc/build/pdf make -C doc/build/pdf [testenv:pep8] deps = -r{toxinidir}/test-requirements.txt autopep8 basepython = python3 commands = autopep8 --exit-code --max-line-length=79 --experimental --diff -r tempest setup.py flake8 {posargs} check-uuid [testenv:autopep8] deps = autopep8 basepython = python3 commands = {toxinidir}/tools/format.sh [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.opendev.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 # W504 skipped because it is overeager and unnecessary ignore = E125,E123,E129,W504 show-source = True exclude = .git,.venv,.tox,dist,doc,*egg,build enable-extensions = H106,H203,H904 import-order-style = pep8 [testenv:releasenotes] basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = rm -rf releasenotes/build sphinx-build -a -E -W -d releasenotes/build/doctrees \ -b html releasenotes/source releasenotes/build/html whitelist_externals = rm [testenv:bashate] basepython = python3 # if you want to test out some changes you have made to bashate # against tempest, just set BASHATE_INSTALL_PATH=/path/... to your # modified bashate tree deps = {env:BASHATE_INSTALL_PATH:bashate} whitelist_externals = bash commands = bash -c "find {toxinidir}/tools \ -not \( -type d -name .?\* -prune \) \ -type f \ -name \*.sh \ -print0 | xargs -0 bashate -v -eE005,E042 -i E006" [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 basepython = python3 whitelist_externals = bash commands = bash tools/tempest-plugin-sanity.sh