Merge "Remove deprecated find_test_caller function"
diff --git a/.gitignore b/.gitignore
index 7cb052f..06a2281 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,9 +3,8 @@
*.pyc
__pycache__/
etc/accounts.yaml
-etc/tempest.conf
+etc/*.conf
etc/tempest.conf.sample
-etc/logging.conf
include/swift_objects/swift_small
include/swift_objects/swift_medium
include/swift_objects/swift_large
diff --git a/.mailmap b/.mailmap
index 3ea6ab0..fc74aa9 100644
--- a/.mailmap
+++ b/.mailmap
@@ -6,16 +6,18 @@
Andrea Frittoli <andrea.frittoli@gmail.com> <andrea.frittoli@hpe.com>
Daryl Walleck <daryl.walleck@rackspace.com> <daryl.walleck@rackspace.com>
David Kranz <dkranz@redhat.com> David Kranz <david.kranz@qrclab.com>
-Ghanshyam <ghanshyam.mann@nectechnologies.in> <ghanshyam.mann@nectechnologies.in>
-Ghanshyam <ghanshyam.mann@nectechnologies.in> <ghanshyam.mann@nectechnologies.in>
+Ghanshyam Mann <gmann@ghanshyammann.com> <ghanshyam.mann@nectechnologies.in>
+Ghanshyam Mann <gmann@ghanshyammann.com> <ghanshyam.mann@india.nec.com>
+Ghanshyam Mann <gmann@ghanshyammann.com> <ghanshyammann@gmail.com>
Jay Pipes <jaypipes@gmail.com> <jpipes@librebox.gateway.2wire.net>
Joe Gordon <joe.gordon0@gmail.com> <jogo@cloudscaling.com>
Ken'ichi Ohmichi <ken-oomichi@wx.jp.nec.com> <oomichi@mxs.nes.nec.co.jp>
Ken'ichi Ohmichi <ken-oomichi@wx.jp.nec.com> <ken1ohmichi@gmail.com>
Marc Koderer <marc@koderer.com> <m.koderer@telekom.de>
-Masayuki Igawa <masayuki@igawa.me> <igawa@mxs.nes.nec.co.jp>
-Masayuki Igawa <masayuki@igawa.me> <mas-igawa@ut.jp.nec.com>
-Masayuki Igawa <masayuki@igawa.me> <masayuki.igawa@gmail.com>
+Masayuki Igawa <masayuki@igawa.io> <igawa@mxs.nes.nec.co.jp>
+Masayuki Igawa <masayuki@igawa.io> <mas-igawa@ut.jp.nec.com>
+Masayuki Igawa <masayuki@igawa.io> <masayuki.igawa@gmail.com>
+Masayuki Igawa <masayuki@igawa.io> <masayuki@igawa.me>
Matthew Treinish <mtreinish@kortar.org> <treinish@linux.vnet.ibm.com>
Nayna Patel <nayna.patel@hp.com> <nayna.patel@hp.com>
ravikumar-venkatesan <ravikumar.venkatesan@hp.com> <ravikumar.venkatesan@hp.com>
diff --git a/.zuul.yaml b/.zuul.yaml
index 8ab3028..48d14ff 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -9,10 +9,10 @@
test setup. To run a multi-node test inherit from devstack-tempest and
set the nodeset to a multi-node one.
required-projects:
- - openstack/tempest
+ - git.openstack.org/openstack/tempest
timeout: 7200
roles:
- - zuul: openstack-dev/devstack
+ - zuul: git.openstack.org/openstack-dev/devstack
vars:
devstack_services:
tempest: true
@@ -21,30 +21,42 @@
$TEMPEST_CONFIG:
compute:
min_compute_nodes: "{{ groups['compute'] | default(['controller']) | length }}"
- test_results_stage_name: 'test_results'
+ 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'
+ '{{ 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
+ 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: tempest-full
parent: devstack-tempest
# This currently works from stable/pike on.
- branches:
- - master
- - stable/queens
- - stable/pike
+ # 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:
@@ -56,6 +68,37 @@
ENABLE_FILE_INJECTION: true
- 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:
+ - git.openstack.org/openstack/oslo.cache
+ - git.openstack.org/openstack/oslo.concurrency
+ - git.openstack.org/openstack/oslo.config
+ - git.openstack.org/openstack/oslo.context
+ - git.openstack.org/openstack/oslo.db
+ - git.openstack.org/openstack/oslo.i18n
+ - git.openstack.org/openstack/oslo.log
+ - git.openstack.org/openstack/oslo.messaging
+ - git.openstack.org/openstack/oslo.middleware
+ - git.openstack.org/openstack/oslo.policy
+ - git.openstack.org/openstack/oslo.privsep
+ - git.openstack.org/openstack/oslo.reports
+ - git.openstack.org/openstack/oslo.rootwrap
+ - git.openstack.org/openstack/oslo.serialization
+ - git.openstack.org/openstack/oslo.service
+ - git.openstack.org/openstack/oslo.utils
+ - git.openstack.org/openstack/oslo.versionedobjects
+ - git.openstack.org/openstack/oslo.vmware
+
+- job:
name: tempest-full-parallel
parent: tempest-full
voting: false
@@ -71,9 +114,10 @@
- job:
name: tempest-full-py3
parent: devstack-tempest
- branches:
- - master
- - stable/queens
+ # 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:
@@ -82,8 +126,8 @@
vars:
tox_envlist: full
devstack_localrc:
- USE_PYTHON3: True
- FORCE_CONFIG_DRIVE: True
+ USE_PYTHON3: true
+ FORCE_CONFIG_DRIVE: true
devstack_services:
s-account: false
s-container: false
@@ -101,17 +145,18 @@
- master
description: |
Base multinode integration test with Neutron networking and py27.
- Former names for this job where:
+ 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
+ FORCE_CONFIG_DRIVE: false
NOVA_ALLOW_MOVE_TO_SAME_HOST: false
LIVE_MIGRATION_AVAILABLE: true
USE_BLOCK_MIGRATION_FOR_LIVE_MIGRATION: true
@@ -142,18 +187,54 @@
Base integration test with Neutron networking and py36.
voting: false
-# TODO(gmann): needs to migrate this to zuulv3
+- nodeset:
+ name: openstack-opensuse150-node
+ nodes:
+ - name: controller
+ label: opensuse-150
+ groups:
+ - name: tempest
+ nodes:
+ - controller
+
- job:
- name: tempest-scenario-multinode-lvm-multibackend
- parent: legacy-dsvm-base-multinode
- run: playbooks/tempest-scenario-multinode-lvm-multibackend/run.yaml
- post-run: playbooks/tempest-scenario-multinode-lvm-multibackend/post.yaml
+ name: tempest-full-py3-opensuse150
+ parent: tempest-full-py3
+ nodeset: openstack-opensuse150-node
+ description: |
+ Base integration test with Neutron networking and py36 running
+ on openSUSE Leap 15.0
+ voting: false
+
+- job:
+ name: tempest-slow
+ parent: tempest-multinode-full
+ branches:
+ - master
+ 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
- required-projects:
- - openstack-infra/devstack-gate
- - openstack/neutron
- - openstack/tempest
- nodeset: ubuntu-xenial-2-node
+ vars:
+ tox_envlist: slow-serial
+ devstack_localrc:
+ CINDER_ENABLED_BACKENDS: lvm:lvmdriver-1,lvm:lvmdriver-2
+ tempest_concurrency: 2
+
+- job:
+ name: tempest-full-rocky
+ parent: tempest-full
+ override-checkout: stable/rocky
+
+- job:
+ name: tempest-full-rocky-py3
+ parent: tempest-full-py3
+ override-checkout: stable/rocky
- job:
name: tempest-full-queens
@@ -188,70 +269,70 @@
- ^tempest/hacking/.*$
- ^tempest/tests/.*$
required-projects:
- - openstack/almanach
- - openstack/aodh
- - openstack/barbican-tempest-plugin
- - openstack/ceilometer
- - openstack/cinder
- - openstack/congress
- - openstack/designate-tempest-plugin
- - openstack/ec2-api
- - openstack/freezer
- - openstack/freezer-api
- - openstack/freezer-tempest-plugin
- - openstack/gce-api
- - openstack/glare
- - openstack/heat
- - openstack/intel-nfv-ci-tests
- - openstack/ironic
- - openstack/ironic-inspector
- - openstack/keystone-tempest-plugin
- - openstack/kingbird
- - openstack/kuryr-tempest-plugin
- - openstack/magnum
- - openstack/magnum-tempest-plugin
- - openstack/manila
- - openstack/manila-tempest-plugin
- - openstack/mistral
- - openstack/mogan
- - openstack/monasca-api
- - openstack/monasca-log-api
- - openstack/murano
- - openstack/networking-bgpvpn
- - openstack/networking-cisco
- - openstack/networking-fortinet
- - openstack/networking-generic-switch
- - openstack/networking-l2gw
- - openstack/networking-midonet
- - openstack/networking-plumgrid
- - openstack/networking-sfc
- - openstack/neutron
- - openstack/neutron-dynamic-routing
- - openstack/neutron-fwaas
- - openstack/neutron-lbaas
- - openstack/neutron-tempest-plugin
- - openstack/neutron-vpnaas
- - openstack/nova-lxd
- - openstack/novajoin-tempest-plugin
- - openstack/octavia-tempest-plugin
- - openstack/oswin-tempest-plugin
- - openstack/panko
- - openstack/patrole
- - openstack/qinling
- - openstack/requirements
- - openstack/sahara-tests
- - openstack/senlin
- - openstack/senlin-tempest-plugin
- - openstack/tap-as-a-service
- - openstack/tempest-horizon
- - openstack/trio2o
- - openstack/trove
- - openstack/valet
- - openstack/vitrage
- - openstack/vmware-nsx-tempest-plugin
- - openstack/watcher-tempest-plugin
- - openstack/zaqar-tempest-plugin
- - openstack/zun-tempest-plugin
+ - git.openstack.org/openstack/almanach
+ - git.openstack.org/openstack/aodh
+ - git.openstack.org/openstack/barbican-tempest-plugin
+ - git.openstack.org/openstack/ceilometer
+ - git.openstack.org/openstack/cinder
+ - git.openstack.org/openstack/congress
+ - git.openstack.org/openstack/designate-tempest-plugin
+ - git.openstack.org/openstack/ec2-api
+ - git.openstack.org/openstack/freezer
+ - git.openstack.org/openstack/freezer-api
+ - git.openstack.org/openstack/freezer-tempest-plugin
+ - git.openstack.org/openstack/gce-api
+ - git.openstack.org/openstack/glare
+ - git.openstack.org/openstack/heat
+ - git.openstack.org/openstack/intel-nfv-ci-tests
+ - git.openstack.org/openstack/ironic
+ - git.openstack.org/openstack/ironic-inspector
+ - git.openstack.org/openstack/keystone-tempest-plugin
+ - git.openstack.org/openstack/kingbird
+ - git.openstack.org/openstack/kuryr-tempest-plugin
+ - git.openstack.org/openstack/magnum
+ - git.openstack.org/openstack/magnum-tempest-plugin
+ - git.openstack.org/openstack/manila
+ - git.openstack.org/openstack/manila-tempest-plugin
+ - git.openstack.org/openstack/mistral
+ - git.openstack.org/openstack/mogan
+ - git.openstack.org/openstack/monasca-api
+ - git.openstack.org/openstack/monasca-log-api
+ - git.openstack.org/openstack/murano
+ - git.openstack.org/openstack/networking-bgpvpn
+ - git.openstack.org/openstack/networking-cisco
+ - git.openstack.org/openstack/networking-fortinet
+ - git.openstack.org/openstack/networking-generic-switch
+ - git.openstack.org/openstack/networking-l2gw
+ - git.openstack.org/openstack/networking-midonet
+ - git.openstack.org/openstack/networking-plumgrid
+ - git.openstack.org/openstack/networking-sfc
+ - git.openstack.org/openstack/neutron
+ - git.openstack.org/openstack/neutron-dynamic-routing
+ - git.openstack.org/openstack/neutron-fwaas
+ - git.openstack.org/openstack/neutron-lbaas
+ - git.openstack.org/openstack/neutron-tempest-plugin
+ - git.openstack.org/openstack/neutron-vpnaas
+ - git.openstack.org/openstack/nova-lxd
+ - git.openstack.org/openstack/novajoin-tempest-plugin
+ - git.openstack.org/openstack/octavia-tempest-plugin
+ - git.openstack.org/openstack/oswin-tempest-plugin
+ - git.openstack.org/openstack/panko
+ - git.openstack.org/openstack/patrole
+ - git.openstack.org/openstack/qinling
+ - git.openstack.org/openstack/requirements
+ - git.openstack.org/openstack/sahara-tests
+ - git.openstack.org/openstack/senlin
+ - git.openstack.org/openstack/senlin-tempest-plugin
+ - git.openstack.org/openstack/tap-as-a-service
+ - git.openstack.org/openstack/tempest-horizon
+ - git.openstack.org/openstack/trio2o
+ - git.openstack.org/openstack/trove
+ - git.openstack.org/openstack/valet
+ - git.openstack.org/openstack/vitrage
+ - git.openstack.org/openstack/vmware-nsx-tempest-plugin
+ - git.openstack.org/openstack/watcher-tempest-plugin
+ - git.openstack.org/openstack/zaqar-tempest-plugin
+ - git.openstack.org/openstack/zun-tempest-plugin
- job:
name: tempest-cinder-v2-api
@@ -266,7 +347,55 @@
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:
+ templates:
+ - check-requirements
+ - integrated-gate
+ - integrated-gate-py35
+ - openstack-cover-jobs
+ - openstack-python-jobs
+ - openstack-python35-jobs
+ - openstack-python36-jobs
+ - publish-openstack-docs-pti
+ - release-notes-jobs-python3
check:
jobs:
- devstack-tempest:
@@ -274,116 +403,118 @@
- ^playbooks/
- ^roles/
- ^.zuul.yaml$
- - nova-multiattach
+ - nova-multiattach:
+ # Define list of irrelevant files to use everywhere else
+ irrelevant-files: &tempest-irrelevant-files
+ - ^(test-|)requirements.txt$
+ - ^.*\.rst$
+ - ^doc/.*$
+ - ^etc/.*$
+ - ^releasenotes/.*$
+ - ^setup.cfg$
+ - ^tempest/hacking/.*$
+ - ^tempest/tests/.*$
- tempest-full-parallel:
- irrelevant-files:
- - ^(test-|)requirements.txt$
- - ^.*\.rst$
- - ^doc/.*$
- - ^etc/.*$
- - ^releasenotes/.*$
- - ^setup.cfg$
- - ^tempest/hacking/.*$
- - ^tempest/tests/.*$
+ irrelevant-files: *tempest-irrelevant-files
+ - tempest-full-py3:
+ irrelevant-files: *tempest-irrelevant-files
- tempest-full-py36:
- irrelevant-files:
- - ^(test-|)requirements.txt$
- - ^.*\.rst$
- - ^doc/.*$
- - ^etc/.*$
- - ^releasenotes/.*$
- - ^setup.cfg$
- - ^tempest/hacking/.*$
- - ^tempest/tests/.*$
+ 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:
- - ^(test-|)requirements.txt$
- - ^.*\.rst$
- - ^doc/.*$
- - ^etc/.*$
- - ^releasenotes/.*$
- - ^setup.cfg$
- - ^tempest/hacking/.*$
- - ^tempest/tests/.*$
+ irrelevant-files: *tempest-irrelevant-files
- tempest-full-queens-py3:
- irrelevant-files:
- - ^(test-|)requirements.txt$
- - ^.*\.rst$
- - ^doc/.*$
- - ^etc/.*$
- - ^releasenotes/.*$
- - ^setup.cfg$
- - ^tempest/hacking/.*$
- - ^tempest/tests/.*$
+ irrelevant-files: *tempest-irrelevant-files
- tempest-full-pike:
- irrelevant-files:
- - ^(test-|)requirements.txt$
- - ^.*\.rst$
- - ^doc/.*$
- - ^etc/.*$
- - ^releasenotes/.*$
- - ^setup.cfg$
- - ^tempest/hacking/.*$
- - ^tempest/tests/.*$
+ irrelevant-files: *tempest-irrelevant-files
- tempest-multinode-full:
- irrelevant-files:
- - ^(test-|)requirements.txt$
- - ^.*\.rst$
- - ^doc/.*$
- - ^etc/.*$
- - ^releasenotes/.*$
- - ^setup.cfg$
- - ^tempest/hacking/.*$
- - ^tempest/tests/.*$
+ irrelevant-files: *tempest-irrelevant-files
- tempest-tox-plugin-sanity-check
- - tempest-scenario-multinode-lvm-multibackend:
- voting: false
- irrelevant-files:
- - ^(test-|)requirements.txt$
- - ^.*\.rst$
- - ^doc/.*$
- - ^etc/.*$
- - ^releasenotes/.*$
- - ^setup.cfg$
- - ^tempest/hacking/.*$
- - ^tempest/tests/.*$
+ - tempest-slow:
+ irrelevant-files: *tempest-irrelevant-files
- nova-cells-v1:
- irrelevant-files:
- - ^(test-|)requirements.txt$
- - ^.*\.rst$
- - ^doc/.*$
- - ^etc/.*$
- - ^releasenotes/.*$
- - ^setup.cfg$
- - ^tempest/hacking/.*$
- - ^tempest/tests/.*$
+ irrelevant-files: *tempest-irrelevant-files
+ - nova-live-migration:
+ voting: false
+ irrelevant-files: *tempest-irrelevant-files
+ - neutron-grenade-multinode:
+ irrelevant-files: *tempest-irrelevant-files
+ - neutron-grenade:
+ 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
+ - legacy-tempest-dsvm-neutron-full-ocata:
+ irrelevant-files: *tempest-irrelevant-files
+ - tempest-full:
+ irrelevant-files: *tempest-irrelevant-files
+ - interop-tempest-consistency:
+ voting: false
+ 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
gate:
jobs:
- - nova-multiattach
+ - nova-multiattach:
+ irrelevant-files: *tempest-irrelevant-files
+ - tempest-slow:
+ irrelevant-files: *tempest-irrelevant-files
+ - neutron-grenade-multinode:
+ irrelevant-files: *tempest-irrelevant-files
+ - tempest-full:
+ irrelevant-files: *tempest-irrelevant-files
+ - neutron-grenade:
+ irrelevant-files: *tempest-irrelevant-files
experimental:
jobs:
- - nova-live-migration:
- irrelevant-files:
- - ^(test-|)requirements.txt$
- - ^.*\.rst$
- - ^doc/.*$
- - ^etc/.*$
- - ^releasenotes/.*$
- - ^setup.cfg$
- - ^tempest/hacking/.*$
- - ^tempest/tests/.*$
- tempest-cinder-v2-api:
- irrelevant-files:
- - ^(test-|)requirements.txt$
- - ^.*\.rst$
- - ^doc/.*$
- - ^etc/.*$
- - ^releasenotes/.*$
- - ^setup.cfg$
- - ^tempest/hacking/.*$
- - ^tempest/tests/.*$
+ 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
+ - legacy-tempest-dsvm-nova-v20-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-opensuse150:
+ irrelevant-files: *tempest-irrelevant-files
periodic-stable:
jobs:
+ - tempest-full-rocky
+ - tempest-full-rocky-py3
- tempest-full-queens
- tempest-full-queens-py3
- tempest-full-pike
+ - legacy-periodic-tempest-dsvm-neutron-full-ocata
+ periodic:
+ jobs:
+ - tempest-all
+ - tempest-full-oslo-master
diff --git a/HACKING.rst b/HACKING.rst
index 1c084f8..e767b25 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -25,6 +25,8 @@
- [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.
Test Data/Configuration
-----------------------
@@ -106,7 +108,7 @@
test method. You specify the services with the ``tempest.common.utils.services``
decorator. For example:
-@utils.services('compute', 'image')
+``@utils.services('compute', 'image')``
Valid service tag names are the same as the list of directories in tempest.api
that have tests.
@@ -118,6 +120,54 @@
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 <scenario_field_guide>`, 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
@@ -299,18 +349,19 @@
docstrings for the workflow in each test methods can be used instead. A good
example of this would be::
- class TestVolumeBootPattern(manager.ScenarioTest):
- """
- This test case attempts to reproduce the following steps:
+ class TestServerBasicOps(manager.ScenarioTest):
- * 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
+ """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
@@ -419,34 +470,3 @@
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.
-
-Stable 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
diff --git a/README.rst b/README.rst
index 2243536..307ceb3 100644
--- a/README.rst
+++ b/README.rst
@@ -95,10 +95,12 @@
command. Tempest is expecting a ``tempest.conf`` file in etc/ so if only a
sample exists you must rename or copy it to tempest.conf before making
any changes to it otherwise Tempest will not know how to load it. For
- details on configuring Tempest refer to the :ref:`tempest-configuration`.
+ details on configuring Tempest refer to the
+ `Tempest Configuration <https://docs.openstack.org/tempest/latest/configuration.html#tempest-configuration>`_
#. Once the configuration is done you're now ready to run Tempest. This can
- be done using the :ref:`tempest_run` command. This can be done by either
+ be done using the `Tempest Run <https://docs.openstack.org/tempest/latest/run.html#tempest-run>`_
+ command. This can be done by either
running::
$ tempest run
@@ -109,15 +111,18 @@
$ tempest run --workspace cloud-01
- There is also the option to use testr directly, or any `testr`_ based test
- runner, like `ostestr`_. For example, from the workspace dir run::
+ There is also the option to use `stestr`_ directly. For example, from
+ the workspace dir run::
- $ ostestr --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario))'
+ $ stestr run --black-regex '\[.*\bslow\b.*\]' '^tempest\.(api|scenario)'
- will run the same set of tests as the default gate jobs.
+ will run the same set of tests as the default gate jobs. Or you can
+ use `unittest`_ compatible test runners such as `testr`_, `pytest`_ etc.
+.. _unittest: https://docs.python.org/3/library/unittest.html
.. _testr: https://testrepository.readthedocs.org/en/latest/MANUAL.html
-.. _ostestr: https://docs.openstack.org/os-testr/latest/
+.. _stestr: https://stestr.readthedocs.org/en/latest/MANUAL.html
+.. _pytest: https://docs.pytest.org/en/latest/
Library
-------
@@ -129,7 +134,8 @@
stable interface and there are no guarantees on the Python API unless otherwise
stated.
-For more details refer to the library documentation here: :ref:`library`
+For more details refer to the `library documentation
+<https://docs.openstack.org/tempest/latest/library.html#library>`_
Release Versioning
------------------
@@ -165,8 +171,10 @@
-------------
Detailed configuration of Tempest is beyond the scope of this
-document see :ref:`tempest-configuration` for more details on configuring
-Tempest. The ``etc/tempest.conf.sample`` attempts to be a self-documenting
+document, see `Tempest Configuration Documentation
+<https://docs.openstack.org/tempest/latest/configuration.html#tempest-configuration>`_
+for more details on configuring Tempest.
+The ``etc/tempest.conf.sample`` attempts to be a self-documenting
version of the configuration.
You can generate a new sample tempest.conf file, run the following
@@ -190,21 +198,21 @@
is ``test_path=./tempest/test_discover`` which will only run test discover on the
Tempest suite.
-Alternatively, there are the py27 and py35 tox jobs which will run the unit
+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 py35 tests against a single test::
+simply by specifying to just run py27 or py36 tests against a single test::
- $ tox -e py27 -- -n tempest.tests.test_microversions.TestMicroversionsTestsClass.test_config_version_none_23
+ $ 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 py27 -- -n tempest.tests.test_microversions
+ $ tox -e py36 -- -n tempest.tests.test_microversions
You may also use regular expressions to run any matching tests::
- $ tox -e py27 -- test_microversions
+ $ 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.
diff --git a/REVIEWING.rst b/REVIEWING.rst
index a880181..bf63ed2 100644
--- a/REVIEWING.rst
+++ b/REVIEWING.rst
@@ -36,8 +36,11 @@
For any change that adds new functionality to either common functionality or an
out-of-band tool unit tests are required. This is to ensure we don't introduce
future regressions and to test conditions which we may not hit in the gate runs.
-Tests, and service clients aren't required to have unit tests since they should
-be self verifying by running them in the gate.
+API 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
@@ -99,6 +102,39 @@
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
@@ -113,16 +149,18 @@
.. _reno: https://docs.openstack.org/reno/latest/
+
Deprecated Code
---------------
Sometimes we have some bugs in deprecated code. Basically, we leave it. Because
we don't need to maintain it. However, if the bug is critical, we might need to
fix it. When it will happen, we will deal with it on a case-by-case basis.
+
When to approve
---------------
* Every patch needs two +2s before being approved.
-* Its ok to hold off on an approval until a subject matter expert reviews it
+* It's ok to hold off on an approval until a subject matter expert reviews it
* If a patch has already been approved but requires a trivial rebase to merge,
you do not have to wait for a second +2, since the patch has already had
two +2s.
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index d0d7320..2e5f706 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -172,7 +172,7 @@
resize test).
Using a smaller flavor is generally recommended. When larger flavors are used,
-the extra time required to bring up servers will likely affect total run time
+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.
@@ -207,7 +207,7 @@
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
+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
@@ -239,7 +239,7 @@
""""""""""""""""""""""""""""""""""
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
+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
@@ -297,10 +297,10 @@
''''''''''''''''''''''''
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
+cases, it shouldn't matter because nova-network should have no problem booting a
server with multiple networks. If this is not the case for your cloud then using
an accounts file is recommended because it provides the necessary flexibility to
-describe your configuration. Dynamic credentials is not able to dynamically
+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
@@ -352,7 +352,7 @@
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
+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.
@@ -390,8 +390,8 @@
.. 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
+ 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:
@@ -413,10 +413,10 @@
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
+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
+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.
@@ -432,8 +432,8 @@
^^^^^^^^^^^^^^
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
+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
+``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.
diff --git a/doc/source/data/tempest-plugins-registry.header b/doc/source/data/tempest-plugins-registry.header
index 9821e8e..0de12b7 100644
--- a/doc/source/data/tempest-plugins-registry.header
+++ b/doc/source/data/tempest-plugins-registry.header
@@ -17,7 +17,3 @@
The following are plugins that a script has found in the openstack/
namespace, which includes but is not limited to official OpenStack
projects.
-
-+----------------------------+-------------------------------------------------------------------------+
-|Plugin Name |URL |
-+----------------------------+-------------------------------------------------------------------------+
diff --git a/doc/source/index.rst b/doc/source/index.rst
index f562850..fecf98a 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -80,6 +80,14 @@
library
+Support Policy
+--------------
+
+.. toctree::
+ :maxdepth: 2
+
+ stable_branch_support_policy
+
Indices and tables
==================
diff --git a/doc/source/library.rst b/doc/source/library.rst
index 14415ae..6a12c45 100644
--- a/doc/source/library.rst
+++ b/doc/source/library.rst
@@ -4,12 +4,12 @@
=============================
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
+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
+other Tempest internals outside of lib (including no usage of Tempest
configuration).
Stability
@@ -32,7 +32,7 @@
Making changes
''''''''''''''
When making changes to tempest/lib you have to be conscious of the effect of
-any changes on external consumers. If your proposed changeset will change the
+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.
@@ -40,8 +40,8 @@
Reviewing
'''''''''
When reviewing a proposed change to tempest/lib code we need to be careful to
-ensure that we don't break backwards compatibility. For patches that change
-existing interfaces we have to be careful to make sure we don't break any
+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
@@ -52,7 +52,7 @@
'''''''
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
+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
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index ea868ae..983fa24 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -306,6 +306,10 @@
.. _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
@@ -334,14 +338,30 @@
.. _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
@@ -358,22 +378,30 @@
.. _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#id4
+ .. _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#id49
+ .. _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#id51
+ .. _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
diff --git a/doc/source/plugin.rst b/doc/source/plugin.rst
index 6f6621d..9958792 100644
--- a/doc/source/plugin.rst
+++ b/doc/source/plugin.rst
@@ -5,24 +5,24 @@
=============================
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.
+an external test suite as part of a Tempest run. This will let any project
+leverage being run with the rest of the Tempest suite while not requiring the
+tests live in the Tempest tree.
Creating a plugin
=================
Creating a plugin is fairly straightforward and doesn't require much additional
effort on top of creating a test suite using tempest.lib. One thing to note with
-doing this is that the interfaces exposed by tempest are not considered stable
-(with the exception of configuration variables which ever effort goes into
-ensuring backwards compatibility). You should not need to import anything from
-tempest itself except where explicitly noted.
+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
+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.*
@@ -32,7 +32,7 @@
* tempest.clients
* tempest.test
-If there is an interface from tempest that you need to rely on in your plugin
+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.
@@ -62,7 +62,7 @@
-----------
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
+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
@@ -77,9 +77,9 @@
Standalone Plugin vs In-repo Plugin
-----------------------------------
-Since all that's required for a plugin to be detected by tempest is a valid
+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
+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.
@@ -91,9 +91,9 @@
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
+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
+that users don't have to worry about inadvertently installing a Tempest plugin
when they install another package.
.. _Branchless Tempest Spec: http://specs.openstack.org/openstack/qa-specs/specs/tempest/implemented/branchless-tempest.html
@@ -108,9 +108,9 @@
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
+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:
@@ -147,7 +147,7 @@
services/
client.py
-That will mirror what people expect from tempest. The file
+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
@@ -156,14 +156,14 @@
* **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.
+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
+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
@@ -171,7 +171,7 @@
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
+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
@@ -325,23 +325,23 @@
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.
+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
+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
+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
+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
+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
diff --git a/doc/source/stable_branch_support_policy.rst b/doc/source/stable_branch_support_policy.rst
new file mode 100644
index 0000000..87e3ad1
--- /dev/null
+++ b/doc/source/stable_branch_support_policy.rst
@@ -0,0 +1,30 @@
+Stable 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
diff --git a/doc/source/test_removal.rst b/doc/source/test_removal.rst
index ddae6e2..e249bdd 100644
--- a/doc/source/test_removal.rst
+++ b/doc/source/test_removal.rst
@@ -1,21 +1,23 @@
+.. _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
+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
+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.
+the Tempest tree.
-This procedure might seem overly conservative and slow paced, but this is by
-design to try and ensure we don't remove something that is actually providing
+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
@@ -27,24 +29,24 @@
3 prong rule for removal
^^^^^^^^^^^^^^^^^^^^^^^^
-In the proposal etherpad we'll be looking for answers to 3 questions
+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
+ 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
+#. 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
+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.
@@ -91,32 +93,35 @@
#. 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.
+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
+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
+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
+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
+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.
+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
+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
@@ -133,19 +138,19 @@
Exceptions to this procedure
----------------------------
-For the most part all tempest test removals have to go through 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.
+#. The class of testing has been decided to be outside the scope of Tempest.
#. A revert for a patch which added a broken test, or testing which didn't
actually run in the gate (basically any revert for something which
shouldn't have been added)
#. Tests that would become out of scope as a consequence of an API change,
as described in `API Compatibility`_.
Such tests cannot live in Tempest because of the branchless nature of
- Tempest. Such test must still honor `prong #3`_.
+ Tempest. Such tests must still honor `prong #3`_.
-For the first exception type the only types of testing in tree which have been
+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)
@@ -154,14 +159,14 @@
* 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
+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
+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
@@ -171,23 +176,23 @@
* Neutron
* Swift
-anything that lives in tempest which doesn't test one of these projects can be
+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.
+to maintain continuity after migrating the tests out of Tempest.
.. _tempest plugin mechanism: https://docs.openstack.org/tempest/latest/plugin.html
API Compatibility
"""""""""""""""""
-If an API introduces a non-discoverable, backward incompatible change, and
-such change is not backported to all versions supported by Tempest, tests for
+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 this criteria as consequence of a
-change, the test must be removed according to the procedure discussed into
+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.
diff --git a/doc/source/write_tests.rst b/doc/source/write_tests.rst
index fff2405..0a29b7b 100644
--- a/doc/source/write_tests.rst
+++ b/doc/source/write_tests.rst
@@ -4,7 +4,7 @@
##########################
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
+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.
diff --git a/playbooks/tempest-scenario-multinode-lvm-multibackend/post.yaml b/playbooks/tempest-scenario-multinode-lvm-multibackend/post.yaml
deleted file mode 100644
index e07f551..0000000
--- a/playbooks/tempest-scenario-multinode-lvm-multibackend/post.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-- hosts: primary
- tasks:
-
- - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
- synchronize:
- src: '{{ ansible_user_dir }}/workspace/'
- dest: '{{ zuul.executor.log_root }}'
- mode: pull
- copy_links: true
- verify_host: true
- rsync_opts:
- - --include=/logs/**
- - --include=*/
- - --exclude=*
- - --prune-empty-dirs
diff --git a/playbooks/tempest-scenario-multinode-lvm-multibackend/run.yaml b/playbooks/tempest-scenario-multinode-lvm-multibackend/run.yaml
deleted file mode 100644
index 03f64f9..0000000
--- a/playbooks/tempest-scenario-multinode-lvm-multibackend/run.yaml
+++ /dev/null
@@ -1,65 +0,0 @@
-- hosts: primary
- name: Autoconverted job tempest-scenario-multinode-lvm-multibackend
- from old job gate-tempest-dsvm-neutron-scenario-multinode-lvm-multibackend-ubuntu-xenial-nv
- tasks:
-
- - name: Ensure legacy workspace directory
- file:
- path: '{{ ansible_user_dir }}/workspace'
- state: directory
-
- - shell:
- cmd: |
- set -e
- set -x
- cat > clonemap.yaml << EOF
- clonemap:
- - name: openstack-infra/devstack-gate
- dest: devstack-gate
- EOF
- /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \
- git://git.openstack.org \
- openstack-infra/devstack-gate
- executable: /bin/bash
- chdir: '{{ ansible_user_dir }}/workspace'
- environment: '{{ zuul | zuul_legacy_vars }}'
-
- - shell:
- cmd: |
- set -e
- set -x
- cat << 'EOF' >>"/tmp/dg-local.conf"
- [[local|localrc]]
- ENABLE_IDENTITY_V2=False
- TEMPEST_USE_TEST_ACCOUNTS=True
- # Enable lvm multiple backends to run multi backend slow scenario tests.
- # Note: multi backend experimental job exclude the slow scenario tests.
- CINDER_ENABLED_BACKENDS=lvm:lvmdriver-1,lvm:lvmdriver-2
-
- EOF
- executable: /bin/bash
- chdir: '{{ ansible_user_dir }}/workspace'
- environment: '{{ zuul | zuul_legacy_vars }}'
-
- - shell:
- cmd: |
- set -e
- set -x
- export PYTHONUNBUFFERED=true
- export DEVSTACK_GATE_TEMPEST=1
- # Run scenario and nova migration tests with concurrency 2
- export DEVSTACK_GATE_TEMPEST_REGEX='(^tempest\.(scenario|api\.compute\.admin\.test_(live_|)migration))'
- export TEMPEST_CONCURRENCY=2
- export DEVSTACK_GATE_NEUTRON=1
- export DEVSTACK_GATE_TLSPROXY=1
- export BRANCH_OVERRIDE=default
- if [ "$BRANCH_OVERRIDE" != "default" ] ; then
- export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE
- fi
- export DEVSTACK_GATE_TOPOLOGY="multinode"
-
- cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh
- ./safe-devstack-vm-gate-wrap.sh
- executable: /bin/bash
- chdir: '{{ ansible_user_dir }}/workspace'
- environment: '{{ zuul | zuul_legacy_vars }}'
diff --git a/releasenotes/notes/add-port-profile-config-option-2610b2fa67027960.yaml b/releasenotes/notes/add-port-profile-config-option-2610b2fa67027960.yaml
index b54ee8b..19d47d1 100644
--- a/releasenotes/notes/add-port-profile-config-option-2610b2fa67027960.yaml
+++ b/releasenotes/notes/add-port-profile-config-option-2610b2fa67027960.yaml
@@ -1,11 +1,9 @@
---
-prelude: >
- 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.
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 {}.
+ 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.
diff --git a/releasenotes/notes/add-redirect-param-bea1f6fbce629c70.yaml b/releasenotes/notes/add-redirect-param-bea1f6fbce629c70.yaml
new file mode 100644
index 0000000..f245dcb
--- /dev/null
+++ b/releasenotes/notes/add-redirect-param-bea1f6fbce629c70.yaml
@@ -0,0 +1,16 @@
+---
+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 <https://bugs.launchpad.net/tempest/+bug/1616892>`_]
+ Tempest now allows tests to verify that an API responds with a
+ redirect.
diff --git a/releasenotes/notes/add-storyboard-in-skip-because-decorator-3e139aa8a4f7970f.yaml b/releasenotes/notes/add-storyboard-in-skip-because-decorator-3e139aa8a4f7970f.yaml
new file mode 100644
index 0000000..dd4a90b
--- /dev/null
+++ b/releasenotes/notes/add-storyboard-in-skip-because-decorator-3e139aa8a4f7970f.yaml
@@ -0,0 +1,17 @@
+---
+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``).
diff --git a/releasenotes/notes/agents-client-delete-method-de1a7fb3f845999c.yaml b/releasenotes/notes/agents-client-delete-method-de1a7fb3f845999c.yaml
new file mode 100644
index 0000000..21068ec
--- /dev/null
+++ b/releasenotes/notes/agents-client-delete-method-de1a7fb3f845999c.yaml
@@ -0,0 +1,7 @@
+---
+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
+
diff --git a/releasenotes/notes/bug-1791007-328a8b9a43bfb157.yaml b/releasenotes/notes/bug-1791007-328a8b9a43bfb157.yaml
new file mode 100644
index 0000000..a2e23fd
--- /dev/null
+++ b/releasenotes/notes/bug-1791007-328a8b9a43bfb157.yaml
@@ -0,0 +1,8 @@
+---
+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.
+
+
diff --git a/releasenotes/notes/cinder-use-os-endpoint-type-c11f63fd468ceb4c.yaml b/releasenotes/notes/cinder-use-os-endpoint-type-c11f63fd468ceb4c.yaml
new file mode 100644
index 0000000..1dda4e1
--- /dev/null
+++ b/releasenotes/notes/cinder-use-os-endpoint-type-c11f63fd468ceb4c.yaml
@@ -0,0 +1,6 @@
+---
+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.
diff --git a/releasenotes/notes/config-image-api-v1-default-to-false-39d5f2xafc534ab1.yaml b/releasenotes/notes/config-image-api-v1-default-to-false-39d5f2xafc534ab1.yaml
new file mode 100644
index 0000000..5efa4a9
--- /dev/null
+++ b/releasenotes/notes/config-image-api-v1-default-to-false-39d5f2xafc534ab1.yaml
@@ -0,0 +1,7 @@
+---
+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.
diff --git a/releasenotes/notes/deprecate-volume-api-selection-config-options-b95c5c0ccbf38916.yaml b/releasenotes/notes/deprecate-volume-api-selection-config-options-b95c5c0ccbf38916.yaml
new file mode 100644
index 0000000..1bea6d0
--- /dev/null
+++ b/releasenotes/notes/deprecate-volume-api-selection-config-options-b95c5c0ccbf38916.yaml
@@ -0,0 +1,19 @@
+---
+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.
diff --git a/releasenotes/notes/network-show-version-18e1707a4df0a3d3.yaml b/releasenotes/notes/network-show-version-18e1707a4df0a3d3.yaml
new file mode 100644
index 0000000..36a9710
--- /dev/null
+++ b/releasenotes/notes/network-show-version-18e1707a4df0a3d3.yaml
@@ -0,0 +1,7 @@
+---
+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
diff --git a/releasenotes/notes/omit_X-Subject-Token_from_log-1bf5fef88c80334b.yaml b/releasenotes/notes/omit_X-Subject-Token_from_log-1bf5fef88c80334b.yaml
new file mode 100644
index 0000000..51c8f79
--- /dev/null
+++ b/releasenotes/notes/omit_X-Subject-Token_from_log-1bf5fef88c80334b.yaml
@@ -0,0 +1,7 @@
+---
+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.
diff --git a/releasenotes/notes/removal-deprecated-volume-config-options-21c4412f3c600923.yaml b/releasenotes/notes/removal-deprecated-volume-config-options-21c4412f3c600923.yaml
new file mode 100644
index 0000000..32147c7
--- /dev/null
+++ b/releasenotes/notes/removal-deprecated-volume-config-options-21c4412f3c600923.yaml
@@ -0,0 +1,24 @@
+---
+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
diff --git a/releasenotes/notes/remove-allow_tenant_isolation-option-03f0d998eb498d44.yaml b/releasenotes/notes/remove-allow_tenant_isolation-option-03f0d998eb498d44.yaml
new file mode 100644
index 0000000..4f4516b
--- /dev/null
+++ b/releasenotes/notes/remove-allow_tenant_isolation-option-03f0d998eb498d44.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+ - |
+ Remove deprecated config option ``allow_tenant_isolation`` from
+ ``auth`` and ``compute`` groups. Use ``use_dynamic_credentials`` directly
+ instead of the removed option.
diff --git a/releasenotes/notes/tempest-default-run_validations-9640c41b6a4a9121.yaml b/releasenotes/notes/tempest-default-run_validations-9640c41b6a4a9121.yaml
new file mode 100644
index 0000000..8ff0b5c
--- /dev/null
+++ b/releasenotes/notes/tempest-default-run_validations-9640c41b6a4a9121.yaml
@@ -0,0 +1,10 @@
+---
+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.
diff --git a/releasenotes/notes/tempest-lib-compute-update-service-6019d2dcfe4a1c5d.yaml b/releasenotes/notes/tempest-lib-compute-update-service-6019d2dcfe4a1c5d.yaml
new file mode 100644
index 0000000..d67cdb8
--- /dev/null
+++ b/releasenotes/notes/tempest-lib-compute-update-service-6019d2dcfe4a1c5d.yaml
@@ -0,0 +1,11 @@
+---
+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``)
diff --git a/releasenotes/notes/tempest-rocky-release-0fc3312053923380.yaml b/releasenotes/notes/tempest-rocky-release-0fc3312053923380.yaml
new file mode 100644
index 0000000..e9c77a6
--- /dev/null
+++ b/releasenotes/notes/tempest-rocky-release-0fc3312053923380.yaml
@@ -0,0 +1,16 @@
+---
+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.
\ No newline at end of file
diff --git a/releasenotes/notes/vnc-hardcoded-server-name-removed-6f8d1e90a175dc08.yaml b/releasenotes/notes/vnc-hardcoded-server-name-removed-6f8d1e90a175dc08.yaml
index 0da2ddc..d3a6851 100644
--- a/releasenotes/notes/vnc-hardcoded-server-name-removed-6f8d1e90a175dc08.yaml
+++ b/releasenotes/notes/vnc-hardcoded-server-name-removed-6f8d1e90a175dc08.yaml
@@ -2,9 +2,9 @@
features:
- |
New string configuration option ``vnc_server_header`` is added
- to ``compute-feature-enabled`` section. It offers to provide VNC server
- name that is to be expected in the responce header. For example, obvious
- at hand names is 'WebSockify', 'nginx'.
+ 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
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index 2518703..3be014f 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -6,6 +6,7 @@
:maxdepth: 1
unreleased
+ v19.0.0
v18.0.0
v17.2.0
v17.1.0
diff --git a/releasenotes/source/v19.0.0.rst b/releasenotes/source/v19.0.0.rst
new file mode 100644
index 0000000..bcffe5d
--- /dev/null
+++ b/releasenotes/source/v19.0.0.rst
@@ -0,0 +1,6 @@
+=====================
+v19.0.0 Release Notes
+=====================
+
+.. release-notes:: 19.0.0 Release Notes
+ :version: 19.0.0
diff --git a/roles/run-tempest/README.rst b/roles/run-tempest/README.rst
index 384ca38..71b8e4f 100644
--- a/roles/run-tempest/README.rst
+++ b/roles/run-tempest/README.rst
@@ -39,3 +39,20 @@
: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).*$
diff --git a/roles/run-tempest/defaults/main.yaml b/roles/run-tempest/defaults/main.yaml
index 85e94f2..c89eb93 100644
--- a/roles/run-tempest/defaults/main.yaml
+++ b/roles/run-tempest/defaults/main.yaml
@@ -1,3 +1,4 @@
devstack_base_dir: /opt/stack
tempest_test_regex: ''
tox_envlist: smoke
+tempest_black_regex: ''
diff --git a/roles/run-tempest/tasks/main.yaml b/roles/run-tempest/tasks/main.yaml
index b68507a..54ddc71 100644
--- a/roles/run-tempest/tasks/main.yaml
+++ b/roles/run-tempest/tasks/main.yaml
@@ -35,7 +35,9 @@
when: blacklist_stat.stat.exists
- name: Run Tempest
- command: tox -e {{tox_envlist}} -- {{tempest_test_regex|quote}} {{blacklist_option|default('')}} --concurrency={{tempest_concurrency|default(default_concurrency)}}
+ command: tox -e {{tox_envlist}} -- {{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
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index 57d3983..7a3bfdf 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -25,17 +25,17 @@
CONF = config.CONF
-class AggregatesAdminTestJSON(base.BaseV2ComputeAdminTest):
+class AggregatesAdminTestBase(base.BaseV2ComputeAdminTest):
"""Tests Aggregates API that require admin privileges"""
@classmethod
def setup_clients(cls):
- super(AggregatesAdminTestJSON, cls).setup_clients()
+ super(AggregatesAdminTestBase, cls).setup_clients()
cls.client = cls.os_admin.aggregates_client
@classmethod
def resource_setup(cls):
- super(AggregatesAdminTestJSON, cls).resource_setup()
+ super(AggregatesAdminTestBase, cls).resource_setup()
cls.aggregate_name_prefix = 'test_aggregate'
cls.az_name_prefix = 'test_az'
@@ -48,11 +48,11 @@
if (hyper['hypervisor_type'] ==
CONF.compute.hypervisor_type)]
- hosts_available = [hyper['service']['host'] for hyper in hypers
- if (hyper['state'] == 'up' and
- hyper['status'] == 'enabled')]
- if hosts_available:
- cls.host = hosts_available[0]
+ 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:
@@ -69,6 +69,9 @@
return aggregate
+
+class AggregatesAdminTestJSON(AggregatesAdminTestBase):
+
@decorators.idempotent_id('0d148aa3-d54c-4317-aa8d-42040a475e20')
def test_aggregate_create_delete(self):
# Create and delete an aggregate.
@@ -206,11 +209,49 @@
az_name = data_utils.rand_name(self.az_name_prefix)
aggregate = self._create_test_aggregate(availability_zone=az_name)
- self.client.add_host(aggregate['id'], host=self.host)
- self.addCleanup(self.client.remove_host, aggregate['id'],
- host=self.host)
- admin_servers_client = self.os_admin.servers_client
+ # 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')
- body = admin_servers_client.show_server(server['id'])['server']
- self.assertEqual(self.host, body['OS-EXT-SRV-ATTR:host'])
+ 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)
diff --git a/tempest/api/compute/admin/test_delete_server.py b/tempest/api/compute/admin/test_delete_server.py
index 83444b9..58cac57 100644
--- a/tempest/api/compute/admin/test_delete_server.py
+++ b/tempest/api/compute/admin/test_delete_server.py
@@ -15,11 +15,8 @@
from tempest.api.compute import base
from tempest.common import waiters
-from tempest import config
from tempest.lib import decorators
-CONF = config.CONF
-
class DeleteServersAdminTestJSON(base.BaseV2ComputeAdminTest):
# NOTE: Server creations of each test class should be under 10
diff --git a/tempest/api/compute/admin/test_flavors_microversions.py b/tempest/api/compute/admin/test_flavors_microversions.py
index 027af25..31b9217 100644
--- a/tempest/api/compute/admin/test_flavors_microversions.py
+++ b/tempest/api/compute/admin/test_flavors_microversions.py
@@ -33,11 +33,19 @@
disk=10,
id=flavor_id)['id']
# Checking show API response schema
- self.flavors_client.show_flavor(new_flavor_id)['flavor']
+ self.flavors_client.show_flavor(new_flavor_id)
# Checking update API response schema
self.admin_flavors_client.update_flavor(new_flavor_id,
- description='new')['flavor']
+ description='new')
# Checking list details API response schema
- self.flavors_client.list_flavors(detail=True)['flavors']
+ self.flavors_client.list_flavors(detail=True)
# Checking list API response schema
- self.flavors_client.list_flavors()['flavors']
+ 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.
diff --git a/tempest/api/compute/admin/test_floating_ips_bulk.py b/tempest/api/compute/admin/test_floating_ips_bulk.py
index 72d09ed..2d7e1a7 100644
--- a/tempest/api/compute/admin/test_floating_ips_bulk.py
+++ b/tempest/api/compute/admin/test_floating_ips_bulk.py
@@ -25,6 +25,8 @@
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.
@@ -32,6 +34,7 @@
content/ext-os-floating-ips-bulk.html
"""
max_microversion = '2.35'
+ depends_on_nova_network = True
@classmethod
def setup_clients(cls):
diff --git a/tempest/api/compute/admin/test_hypervisor.py b/tempest/api/compute/admin/test_hypervisor.py
index b23c59f..9822c26 100644
--- a/tempest/api/compute/admin/test_hypervisor.py
+++ b/tempest/api/compute/admin/test_hypervisor.py
@@ -79,7 +79,8 @@
for hyper in hypers:
details = (self.client.show_hypervisor(hyper['id'])
['hypervisor'])
- if details['hypervisor_type'] != 'ironic':
+ if (details['hypervisor_type'] != 'ironic' and
+ details['state'] == 'up'):
hypers_without_ironic.append(hyper)
ironic_only = False
@@ -104,6 +105,19 @@
"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'
diff --git a/tempest/api/compute/admin/test_instance_usage_audit_log.py b/tempest/api/compute/admin/test_instance_usage_audit_log.py
index e4a2ffd..1b62249 100644
--- a/tempest/api/compute/admin/test_instance_usage_audit_log.py
+++ b/tempest/api/compute/admin/test_instance_usage_audit_log.py
@@ -31,27 +31,11 @@
@decorators.idempotent_id('25319919-33d9-424f-9f99-2c203ee48b9d')
def test_list_instance_usage_audit_logs(self):
# list instance usage audit logs
- body = (self.adm_client.list_instance_usage_audit_logs()
- ["instance_usage_audit_logs"])
- expected_items = ['total_errors', 'total_instances', 'log',
- 'num_hosts_running', 'num_hosts_done',
- 'num_hosts', 'hosts_not_run', 'overall_status',
- 'period_ending', 'period_beginning',
- 'num_hosts_not_run']
- for item in expected_items:
- self.assertIn(item, body)
+ 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()
- body = (self.adm_client.show_instance_usage_audit_log(
+ self.adm_client.show_instance_usage_audit_log(
urllib.quote(now.strftime("%Y-%m-%d %H:%M:%S")))
- ["instance_usage_audit_log"])
-
- expected_items = ['total_errors', 'total_instances', 'log',
- 'num_hosts_running', 'num_hosts_done', 'num_hosts',
- 'hosts_not_run', 'overall_status', 'period_ending',
- 'period_beginning', 'num_hosts_not_run']
- for item in expected_items:
- self.assertIn(item, body)
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index bc38144..5a60dc6 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -29,13 +29,11 @@
LOG = logging.getLogger(__name__)
-class LiveMigrationTest(base.BaseV2ComputeAdminTest):
- max_microversion = '2.24'
- block_migration = None
+class LiveMigrationTestBase(base.BaseV2ComputeAdminTest):
@classmethod
def skip_checks(cls):
- super(LiveMigrationTest, cls).skip_checks()
+ super(LiveMigrationTestBase, cls).skip_checks()
if not CONF.compute_feature_enabled.live_migration:
skip_msg = ("%s skipped as live-migration is "
@@ -55,18 +53,19 @@
# TODO(mriedem): SSH validation before and after the instance is
# live migrated would be a nice test wrinkle addition.
cls.set_network_resources(network=True, subnet=True)
- super(LiveMigrationTest, cls).setup_credentials()
+ super(LiveMigrationTestBase, cls).setup_credentials()
@classmethod
def setup_clients(cls):
- super(LiveMigrationTest, cls).setup_clients()
+ 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:
- kwargs['disk_over_commit'] = False
+ 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)
@@ -90,6 +89,11 @@
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.
@@ -167,7 +171,7 @@
self.assertEqual(volume_id1, volume_id2)
-class LiveMigrationRemoteConsolesV26Test(LiveMigrationTest):
+class LiveMigrationRemoteConsolesV26Test(LiveMigrationTestBase):
min_microversion = '2.6'
max_microversion = 'latest'
diff --git a/tempest/api/compute/admin/test_live_migration_negative.py b/tempest/api/compute/admin/test_live_migration_negative.py
index deabbc2..8327a3b 100644
--- a/tempest/api/compute/admin/test_live_migration_negative.py
+++ b/tempest/api/compute/admin/test_live_migration_negative.py
@@ -32,9 +32,10 @@
def _migrate_server_to(self, server_id, dest_host):
bmflm = CONF.compute_feature_enabled.block_migration_for_live_migration
- self.admin_servers_client.live_migrate_server(
- server_id, host=dest_host, block_migration=bmflm,
- disk_over_commit=False)
+ 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')
diff --git a/tempest/api/compute/admin/test_migrations.py b/tempest/api/compute/admin/test_migrations.py
index a6b71b2..83f2e61 100644
--- a/tempest/api/compute/admin/test_migrations.py
+++ b/tempest/api/compute/admin/test_migrations.py
@@ -106,7 +106,7 @@
'ACTIVE')
server = self.servers_client.show_server(server['id'])['server']
- self.assertEqual(flavor['id'], server['flavor']['id'])
+ self.assert_flavor_equal(flavor['id'], server['flavor'])
def _test_cold_migrate_server(self, revert=False):
if CONF.compute.min_compute_nodes < 2:
@@ -114,8 +114,7 @@
raise self.skipException(msg)
server = self.create_test_server(wait_until="ACTIVE")
- src_host = self.admin_servers_client.show_server(
- server['id'])['server']['OS-EXT-SRV-ATTR:host']
+ src_host = self.get_host_for_server(server['id'])
self.admin_servers_client.migrate_server(server['id'])
@@ -131,8 +130,7 @@
waiters.wait_for_server_status(self.servers_client,
server['id'], 'ACTIVE')
- dst_host = self.admin_servers_client.show_server(
- server['id'])['server']['OS-EXT-SRV-ATTR:host']
+ dst_host = self.get_host_for_server(server['id'])
assert_func(src_host, dst_host)
@decorators.idempotent_id('4bf0be52-3b6f-4746-9a27-3143636fe30d')
diff --git a/tempest/api/compute/admin/test_networks.py b/tempest/api/compute/admin/test_networks.py
index 87ce39d..99907a8 100644
--- a/tempest/api/compute/admin/test_networks.py
+++ b/tempest/api/compute/admin/test_networks.py
@@ -26,6 +26,7 @@
API docs:
https://developer.openstack.org/api-ref/compute/#networks-os-networks-deprecated
"""
+ max_microversion = '2.35'
@classmethod
def setup_clients(cls):
diff --git a/tempest/api/compute/admin/test_quotas.py b/tempest/api/compute/admin/test_quotas.py
index df534bc..12c7255 100644
--- a/tempest/api/compute/admin/test_quotas.py
+++ b/tempest/api/compute/admin/test_quotas.py
@@ -25,22 +25,57 @@
LOG = logging.getLogger(__name__)
-class QuotasAdminTestJSON(base.BaseV2ComputeAdminTest):
+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(QuotasAdminTestJSON, self).setUp()
+ super(QuotasAdminTestBase, self).setUp()
@classmethod
def setup_clients(cls):
- super(QuotasAdminTestJSON, cls).setup_clients()
+ 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(QuotasAdminTestJSON, cls).resource_setup()
+ 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
@@ -60,6 +95,8 @@
'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
@@ -103,36 +140,7 @@
# TODO(afazekas): merge these test cases
@decorators.idempotent_id('ce9e0815-8091-4abd-8345-7fe5b85faa1d')
def test_get_updated_quotas(self):
- # Verify that GET shows the updated quota set of project
- project_name = data_utils.rand_name('cpu_quota_project')
- project_desc = project_name + '-desc'
- project = identity.identity_utils(self.os_admin).create_project(
- name=project_name, description=project_desc)
- project_id = project['id']
- self.addCleanup(identity.identity_utils(self.os_admin).delete_project,
- project_id)
-
- self.adm_client.update_quota_set(project_id, ram='5120')
- quota_set = self.adm_client.show_quota_set(project_id)['quota_set']
- self.assertEqual(5120, quota_set['ram'])
-
- # Verify that GET shows the updated quota set of user
- user_name = data_utils.rand_name('cpu_quota_user')
- password = data_utils.rand_password()
- email = user_name + '@testmail.tm'
- user = identity.identity_utils(self.os_admin).create_user(
- username=user_name, password=password, project=project,
- email=email)
- user_id = user['id']
- self.addCleanup(identity.identity_utils(self.os_admin).delete_user,
- user_id)
-
- self.adm_client.update_quota_set(project_id,
- user_id=user_id,
- ram='2048')
- quota_set = self.adm_client.show_quota_set(
- project_id, user_id=user_id)['quota_set']
- self.assertEqual(2048, quota_set['ram'])
+ self._get_updated_quotas()
@decorators.idempotent_id('389d04f0-3a41-405f-9317-e5f86e3c44f0')
def test_delete_quota(self):
@@ -156,6 +164,30 @@
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."""
diff --git a/tempest/api/compute/admin/test_server_diagnostics_negative.py b/tempest/api/compute/admin/test_server_diagnostics_negative.py
index d5b6674..6215c37 100644
--- a/tempest/api/compute/admin/test_server_diagnostics_negative.py
+++ b/tempest/api/compute/admin/test_server_diagnostics_negative.py
@@ -18,8 +18,6 @@
class ServerDiagnosticsNegativeTest(base.BaseV2ComputeAdminTest):
- min_microversion = None
- max_microversion = '2.47'
@classmethod
def setup_clients(cls):
@@ -33,8 +31,3 @@
server_id = self.create_test_server(wait_until='ACTIVE')['id']
self.assertRaises(lib_exc.Forbidden,
self.client.show_server_diagnostics, server_id)
-
-
-class ServerDiagnosticsNegativeV248Test(ServerDiagnosticsNegativeTest):
- min_microversion = '2.48'
- max_microversion = 'latest'
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index cdfc44a..170b2cc 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -176,7 +176,7 @@
self.assertEqual(self.s1_id, rebuilt_server['id'])
rebuilt_image_id = rebuilt_server['image']['id']
self.assertEqual(self.image_ref_alt, rebuilt_image_id)
- self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id'])
+ 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)
diff --git a/tempest/api/compute/admin/test_servers_on_multinodes.py b/tempest/api/compute/admin/test_servers_on_multinodes.py
index d32a5b4..5cd98f4 100644
--- a/tempest/api/compute/admin/test_servers_on_multinodes.py
+++ b/tempest/api/compute/admin/test_servers_on_multinodes.py
@@ -28,7 +28,7 @@
def resource_setup(cls):
super(ServersOnMultiNodesTest, cls).resource_setup()
cls.server01 = cls.create_test_server(wait_until='ACTIVE')['id']
- cls.host01 = cls._get_host(cls.server01)
+ cls.host01 = cls.get_host_for_server(cls.server01)
@classmethod
def skip_checks(cls):
@@ -38,11 +38,6 @@
raise cls.skipException(
"Less than 2 compute nodes, skipping multi-nodes test.")
- @classmethod
- def _get_host(cls, server_id):
- return cls.os_admin.servers_client.show_server(
- server_id)['server']['OS-EXT-SRV-ATTR:host']
-
def _create_servers_with_group(self, policy):
group_id = self.create_test_server_group(policy=[policy])['id']
hints = {'group': group_id}
@@ -61,7 +56,7 @@
hosts = {}
for server in servers:
self.assertIn(server['id'], server_group['members'])
- hosts[server['id']] = self._get_host(server['id'])
+ hosts[server['id']] = self.get_host_for_server(server['id'])
return hosts
@@ -73,7 +68,7 @@
hints = {'same_host': self.server01}
server02 = self.create_test_server(scheduler_hints=hints,
wait_until='ACTIVE')['id']
- host02 = self._get_host(server02)
+ host02 = self.get_host_for_server(server02)
self.assertEqual(self.host01, host02)
@decorators.idempotent_id('cc7ca884-6e3e-42a3-a92f-c522fcf25e8e')
@@ -84,7 +79,7 @@
hints = {'different_host': self.server01}
server02 = self.create_test_server(scheduler_hints=hints,
wait_until='ACTIVE')['id']
- host02 = self._get_host(server02)
+ host02 = self.get_host_for_server(server02)
self.assertNotEqual(self.host01, host02)
@decorators.idempotent_id('7869cc84-d661-4e14-9f00-c18cdc89cf57')
@@ -96,7 +91,7 @@
hints = {'different_host': [self.server01]}
server02 = self.create_test_server(scheduler_hints=hints,
wait_until='ACTIVE')['id']
- host02 = self._get_host(server02)
+ host02 = self.get_host_for_server(server02)
self.assertNotEqual(self.host01, host02)
@decorators.idempotent_id('f8bd0867-e459-45f5-ba53-59134552fe04')
diff --git a/tempest/api/compute/admin/test_services_negative.py b/tempest/api/compute/admin/test_services_negative.py
index 201670a..d264829 100644
--- a/tempest/api/compute/admin/test_services_negative.py
+++ b/tempest/api/compute/admin/test_services_negative.py
@@ -13,12 +13,14 @@
# 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):
@@ -35,7 +37,8 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d0884a69-f693-4e79-a9af-232d15643bf7')
def test_get_service_by_invalid_params(self):
- # return all services if send the request with invalid parameter
+ # 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'])
@@ -58,3 +61,43 @@
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')
diff --git a/tempest/api/compute/admin/test_volume_swap.py b/tempest/api/compute/admin/test_volume_swap.py
index ed8cf20..a853182 100644
--- a/tempest/api/compute/admin/test_volume_swap.py
+++ b/tempest/api/compute/admin/test_volume_swap.py
@@ -82,6 +82,11 @@
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):
@@ -136,6 +141,11 @@
if not CONF.compute_feature_enabled.volume_multiattach:
raise cls.skipException('Volume multi-attach is not available.')
+ # 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')
@utils.services('volume')
def test_volume_swap_with_multiattach(self):
@@ -146,13 +156,19 @@
volume1 = self.create_volume(multiattach=True)
volume2 = self.create_volume(multiattach=True)
- # Boot server1
- server1 = self.create_test_server(wait_until='ACTIVE')
+ # 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)
- # Boot server2
- server2 = self.create_test_server(wait_until='ACTIVE')
# Attach volume1 to server2
+ server2 = servers[1]
self.attach_volume(server2, volume1)
# Swap volume1 to volume2 on server1, volume1 should remain attached
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index d0c1973..4e2b59b 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -208,19 +208,6 @@
raise
@classmethod
- def clear_resources(cls, resource_name, resources, resource_del_func):
- LOG.debug('Clearing %s: %s', resource_name,
- ','.join(map(str, resources)))
- for res_id in resources:
- try:
- test_utils.call_and_ignore_notfound_exc(
- resource_del_func, res_id)
- except Exception as exc:
- LOG.exception('Exception raised deleting %s: %s',
- resource_name, res_id)
- LOG.exception(exc)
-
- @classmethod
def create_test_server(cls, validatable=False, volume_backed=False,
validation_resources=None, **kwargs):
"""Wrapper utility that returns a test server.
@@ -344,8 +331,7 @@
# 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 api_version_utils.compare_version_header_to_response(
- "OpenStack-API-Version", "compute 2.36", image.response, "lt"):
+ if not cls.is_requested_microversion_compatible('2.35'):
client = cls.images_client
else:
client = cls.compute_images_client
@@ -354,6 +340,9 @@
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':
@@ -428,21 +417,16 @@
except Exception:
LOG.exception('Failed to delete server %s', server_id)
- @classmethod
- def resize_server(cls, server_id, new_flavor_id, **kwargs):
+ def resize_server(self, server_id, new_flavor_id, **kwargs):
"""resize and confirm_resize an server, waits for it to be ACTIVE."""
- cls.servers_client.resize_server(server_id, new_flavor_id, **kwargs)
- waiters.wait_for_server_status(cls.servers_client, server_id,
+ self.servers_client.resize_server(server_id, new_flavor_id, **kwargs)
+ waiters.wait_for_server_status(self.servers_client, server_id,
'VERIFY_RESIZE')
- cls.servers_client.confirm_resize_server(server_id)
- waiters.wait_for_server_status(cls.servers_client, server_id, 'ACTIVE')
- server = cls.servers_client.show_server(server_id)['server']
- # Nova API > 2.46 no longer includes flavor.id
- if server['flavor'].get('id'):
- if new_flavor_id != server['flavor']['id']:
- msg = ('Flavor id of %s is not equal to new_flavor_id.'
- % server_id)
- raise lib_exc.TempestException(msg)
+ 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):
@@ -561,6 +545,27 @@
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."""
@@ -588,8 +593,9 @@
self.addCleanup(client.delete_flavor, flavor['id'])
return flavor
- def get_host_for_server(self, server_id):
- server_details = self.admin_servers_client.show_server(server_id)
+ @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):
diff --git a/tempest/api/compute/images/test_images.py b/tempest/api/compute/images/test_images.py
index 29bd6da..c8221c2 100644
--- a/tempest/api/compute/images/test_images.py
+++ b/tempest/api/compute/images/test_images.py
@@ -38,7 +38,10 @@
@classmethod
def setup_clients(cls):
super(ImagesTestJSON, cls).setup_clients()
- cls.client = cls.compute_images_client
+ 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):
diff --git a/tempest/api/compute/images/test_images_negative.py b/tempest/api/compute/images/test_images_negative.py
index e292389..2400348 100644
--- a/tempest/api/compute/images/test_images_negative.py
+++ b/tempest/api/compute/images/test_images_negative.py
@@ -22,11 +22,11 @@
CONF = config.CONF
-class ImagesNegativeTestJSON(base.BaseV2ComputeTest):
+class ImagesNegativeTestBase(base.BaseV2ComputeTest):
@classmethod
def skip_checks(cls):
- super(ImagesNegativeTestJSON, cls).skip_checks()
+ 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)
@@ -37,9 +37,12 @@
@classmethod
def setup_clients(cls):
- super(ImagesNegativeTestJSON, cls).setup_clients()
+ 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):
@@ -82,6 +85,10 @@
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):
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index 058e7e6..3c152c9 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -44,7 +44,10 @@
@classmethod
def setup_clients(cls):
super(ImagesOneServerTestJSON, cls).setup_clients()
- cls.client = cls.compute_images_client
+ 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']
@@ -52,6 +55,13 @@
@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'}
@@ -61,17 +71,22 @@
# Verify the image was created correctly
self.assertEqual(name, image['name'])
- self.assertEqual('test', image['metadata']['image_type'])
+ 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)['image']
+ 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['minRam'], original_image['minRam'])
+ 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['minDisk']),
- (str(original_image['minDisk']), str(flavor_disk_size)))
+ 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'])
@@ -86,7 +101,8 @@
# will return 400(Bad Request) if we attempt to send a name which has
# 4 byte utf-8 character.
utf8_name = data_utils.rand_name(b'\xe2\x82\xa1'.decode('utf-8'))
- body = self.client.create_image(self.server_id, name=utf8_name)
+ body = self.compute_images_client.create_image(
+ self.server_id, name=utf8_name)
if api_version_utils.compare_version_header_to_response(
"OpenStack-API-Version", "compute 2.45", body.response, "lt"):
image_id = body['image_id']
diff --git a/tempest/api/compute/images/test_images_oneserver_negative.py b/tempest/api/compute/images/test_images_oneserver_negative.py
index a2e58c9..512c9d2 100644
--- a/tempest/api/compute/images/test_images_oneserver_negative.py
+++ b/tempest/api/compute/images/test_images_oneserver_negative.py
@@ -33,8 +33,11 @@
def tearDown(self):
"""Terminate test instances created after a test is executed."""
- self.server_check_teardown()
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,
@@ -69,7 +72,10 @@
@classmethod
def setup_clients(cls):
super(ImagesOneServerNegativeTestJSON, cls).setup_clients()
- cls.client = cls.compute_images_client
+ 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):
@@ -119,7 +125,8 @@
# Return an error if snapshot name over 255 characters is passed
snapshot_name = ('a' * 256)
- self.assertRaises(lib_exc.BadRequest, self.client.create_image,
+ self.assertRaises(lib_exc.BadRequest,
+ self.compute_images_client.create_image,
self.server_id, name=snapshot_name)
@decorators.attr(type=['negative'])
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
index d83d8df..2ac7de3 100644
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -31,6 +31,7 @@
class ListImageFiltersTestJSON(base.BaseV2ComputeTest):
+ max_microversion = '2.35'
@classmethod
def skip_checks(cls):
diff --git a/tempest/api/compute/images/test_list_image_filters_negative.py b/tempest/api/compute/images/test_list_image_filters_negative.py
index d37f8fc..81c59f9 100644
--- a/tempest/api/compute/images/test_list_image_filters_negative.py
+++ b/tempest/api/compute/images/test_list_image_filters_negative.py
@@ -22,6 +22,7 @@
class ListImageFiltersNegativeTestJSON(base.BaseV2ComputeTest):
+ max_microversion = '2.35'
@classmethod
def skip_checks(cls):
diff --git a/tempest/api/compute/images/test_list_images.py b/tempest/api/compute/images/test_list_images.py
index e2dbd72..cbb65bb 100644
--- a/tempest/api/compute/images/test_list_images.py
+++ b/tempest/api/compute/images/test_list_images.py
@@ -21,6 +21,7 @@
class ListImagesTestJSON(base.BaseV2ComputeTest):
+ max_microversion = '2.35'
@classmethod
def skip_checks(cls):
diff --git a/tempest/api/compute/keypairs/test_keypairs_negative.py b/tempest/api/compute/keypairs/test_keypairs_negative.py
index f9050a8..81635ca 100644
--- a/tempest/api/compute/keypairs/test_keypairs_negative.py
+++ b/tempest/api/compute/keypairs/test_keypairs_negative.py
@@ -84,6 +84,6 @@
@decorators.idempotent_id('45fbe5e0-acb5-49aa-837a-ff8d0719db91')
def test_create_keypair_invalid_name(self):
# Keypairs with name being an invalid name should not be created
- k_name = 'key_/.\@:'
+ k_name = r'key_/.\@:'
self.assertRaises(lib_exc.BadRequest, self.create_keypair,
k_name)
diff --git a/tempest/api/compute/limits/test_absolute_limits.py b/tempest/api/compute/limits/test_absolute_limits.py
index 0585fec..8c2202e 100644
--- a/tempest/api/compute/limits/test_absolute_limits.py
+++ b/tempest/api/compute/limits/test_absolute_limits.py
@@ -18,6 +18,7 @@
class AbsoluteLimitsTestJSON(base.BaseV2ComputeTest):
+ max_microversion = '2.56'
@classmethod
def setup_clients(cls):
@@ -26,22 +27,14 @@
@decorators.idempotent_id('b54c66af-6ab6-4cf0-a9e5-a0cb58d75e0b')
def test_absLimits_get(self):
- # To check if all limits are present in the response
- limits = self.client.show_limits()['limits']
- absolute_limits = limits['absolute']
- expected_elements = ['maxImageMeta', 'maxPersonality',
- 'maxPersonalitySize',
- 'maxServerMeta', 'maxTotalCores',
- 'maxTotalFloatingIps', 'maxSecurityGroups',
- 'maxSecurityGroupRules', 'maxTotalInstances',
- 'maxTotalKeypairs', 'maxTotalRAMSize',
- 'maxServerGroups', 'maxServerGroupMembers',
- 'totalCoresUsed', 'totalFloatingIpsUsed',
- 'totalSecurityGroupsUsed', 'totalInstancesUsed',
- 'totalRAMUsed', 'totalServerGroupsUsed']
- # check whether all expected elements exist
- missing_elements =\
- [ele for ele in expected_elements if ele not in absolute_limits]
- self.assertEmpty(missing_elements,
- "Failed to find element %s in absolute limits list"
- % ', '.join(ele for ele in missing_elements))
+ # 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.
diff --git a/tempest/api/compute/limits/test_absolute_limits_negative.py b/tempest/api/compute/limits/test_absolute_limits_negative.py
index bef4eb5..500638a 100644
--- a/tempest/api/compute/limits/test_absolute_limits_negative.py
+++ b/tempest/api/compute/limits/test_absolute_limits_negative.py
@@ -33,15 +33,15 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('215cd465-d8ae-49c9-bf33-9c911913a5c8')
- def test_max_image_meta_exceed_limit(self):
- # We should not create vm with image meta over maxImageMeta limit
+ 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']['maxImageMeta']
+ 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 maxImageMeta')
+ raise self.skipException('no limit for maxServerMeta')
# Create server should fail, since we are passing > metadata Limit!
max_meta_data = max_meta + 1
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index 7509ac6..2a5d607 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -23,34 +23,71 @@
from tempest.common.utils import net_utils
from tempest.common import waiters
from tempest import config
+from tempest.lib.common.utils.linux import remote_client
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
CONF = config.CONF
-class AttachInterfacesTestJSON(base.BaseV2ComputeTest):
+class AttachInterfacesTestBase(base.BaseV2ComputeTest):
@classmethod
def skip_checks(cls):
- super(AttachInterfacesTestJSON, cls).skip_checks()
+ 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)
- super(AttachInterfacesTestJSON, cls).setup_credentials()
+ cls.set_network_resources(network=True, subnet=True, router=True,
+ dhcp=True)
+ super(AttachInterfacesTestBase, cls).setup_credentials()
@classmethod
def setup_clients(cls):
- super(AttachInterfacesTestJSON, cls).setup_clients()
+ 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(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.
@@ -92,15 +129,6 @@
if mac_addr:
self.assertEqual(iface['mac_addr'], mac_addr)
- def _create_server_get_interfaces(self):
- server = self.create_test_server(wait_until='ACTIVE')
- ifs = (self.interfaces_client.list_interfaces(server['id'])
- ['interfaceAttachments'])
- body = waiters.wait_for_interface_status(
- self.interfaces_client, server['id'], ifs[0]['port_id'], 'ACTIVE')
- ifs[0]['port_state'] = body['port_state']
- return server, ifs
-
def _test_create_interface(self, server):
iface = (self.interfaces_client.create_interface(server['id'])
['interfaceAttachment'])
@@ -254,6 +282,58 @@
_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)
+ 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:
+ 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')
@@ -277,40 +357,3 @@
if fixed_ip is not None:
break
self.servers_client.remove_fixed_ip(server['id'], address=fixed_ip)
-
- @decorators.idempotent_id('2f3a0127-95c7-4977-92d2-bc5aec602fb4')
- def test_reassign_port_between_servers(self):
- """Tests the following:
-
- 1. Create a port in Neutron.
- 2. Create two servers in Nova.
- 3. Attach the port to the first server.
- 4. Detach the port from the first server.
- 5. Attach the port to the second server.
- 6. Detach the port from the second server.
- """
- network = self.get_tenant_network()
- network_id = network['id']
- port = self.ports_client.create_port(network_id=network_id)
- port_id = port['port']['id']
- self.addCleanup(self.ports_client.delete_port, port_id)
-
- # create two servers
- _, servers = compute.create_test_server(
- self.os_primary, tenant_network=network,
- wait_until='ACTIVE', min_count=2)
- # add our cleanups for the servers since we bypassed the base class
- for server in servers:
- self.addCleanup(self.delete_server, server['id'])
-
- for server in servers:
- # attach the port to the server
- iface = self.interfaces_client.create_interface(
- server['id'], port_id=port_id)['interfaceAttachment']
- self._check_interface(iface, 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)
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index 122c4f5..4f0dbad 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -80,7 +80,7 @@
self.assertEqual("", self.server['image'])
else:
self.assertEqual(self.image_ref, self.server['image']['id'])
- self.assertEqual(self.flavor_ref, self.server['flavor']['id'])
+ self.assert_flavor_equal(self.flavor_ref, self.server['flavor'])
self.assertEqual(self.meta, self.server['metadata'])
@decorators.attr(type='smoke')
diff --git a/tempest/api/compute/servers/test_delete_server.py b/tempest/api/compute/servers/test_delete_server.py
index 0093752..0263b81 100644
--- a/tempest/api/compute/servers/test_delete_server.py
+++ b/tempest/api/compute/servers/test_delete_server.py
@@ -107,11 +107,10 @@
@utils.services('volume')
def test_delete_server_while_in_attached_volume(self):
# Delete a server while a volume is attached to it
- device = '/dev/%s' % CONF.compute.volume_device_name
server = self.create_test_server(wait_until='ACTIVE')
volume = self.create_volume()
- self.attach_volume(server, volume, device=device)
+ self.attach_volume(server, volume)
self.client.delete_server(server['id'])
waiters.wait_for_server_termination(self.client, server['id'])
diff --git a/tempest/api/compute/servers/test_device_tagging.py b/tempest/api/compute/servers/test_device_tagging.py
index ff8ed61..d40f937 100644
--- a/tempest/api/compute/servers/test_device_tagging.py
+++ b/tempest/api/compute/servers/test_device_tagging.py
@@ -138,6 +138,9 @@
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):
@@ -320,7 +323,9 @@
try:
self.assertEmpty(md_dict['devices'])
return True
- except Exception:
+ 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')
@@ -380,5 +385,8 @@
waiters.wait_for_interface_detach(self.interfaces_client,
server['id'],
interface['port_id'])
- self.verify_metadata_from_api(server, ssh_client,
- self.verify_empty_devices)
+ # 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)
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index 14aecfd..3dffd01 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -227,7 +227,7 @@
@decorators.idempotent_id('24a89b0c-0d55-4a28-847f-45075f19b27b')
def test_list_servers_filtered_by_name_regex(self):
# list of regex that should match s1, s2 and s3
- regexes = ['^.*\-instance\-[0-9]+$', '^.*\-instance\-.*$']
+ regexes = [r'^.*\-instance\-[0-9]+$', r'^.*\-instance\-.*$']
for regex in regexes:
params = {'name': regex}
body = self.client.list_servers(**params)
diff --git a/tempest/api/compute/servers/test_novnc.py b/tempest/api/compute/servers/test_novnc.py
index 1dfd0f9..5801db1 100644
--- a/tempest/api/compute/servers/test_novnc.py
+++ b/tempest/api/compute/servers/test_novnc.py
@@ -44,10 +44,13 @@
self._websocket = None
def tearDown(self):
- self.server_check_teardown()
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):
@@ -58,6 +61,9 @@
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."""
@@ -170,8 +176,13 @@
@decorators.idempotent_id('c640fdff-8ab4-45a4-a5d8-7e6146cbd0dc')
def test_novnc(self):
- body = self.client.get_vnc_console(self.server['id'],
- type='novnc')['console']
+ 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'])
@@ -184,8 +195,13 @@
@decorators.idempotent_id('f9c79937-addc-4aaa-9e0e-841eef02aeb7')
def test_novnc_bad_token(self):
- body = self.client.get_vnc_console(self.server['id'],
- type='novnc')['console']
+ 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
url = body['url'].replace('token=', 'token=bad')
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 9fc5af0..f3d7476 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -59,8 +59,11 @@
self.server_id, validatable=True)
def tearDown(self):
- self.server_check_teardown()
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):
@@ -197,7 +200,7 @@
self.assertEqual(self.server_id, rebuilt_server['id'])
rebuilt_image_id = rebuilt_server['image']['id']
self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id))
- self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id'])
+ 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,
@@ -251,7 +254,7 @@
self.assertEqual(self.server_id, rebuilt_server['id'])
rebuilt_image_id = rebuilt_server['image']['id']
self.assertEqual(new_image, rebuilt_image_id)
- self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id'])
+ 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,
@@ -262,6 +265,11 @@
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):
@@ -303,7 +311,7 @@
expected_status)
server = self.client.show_server(server_id)['server']
- self.assertEqual(self.flavor_ref_alt, server['flavor']['id'])
+ self.assert_flavor_equal(self.flavor_ref_alt, server['flavor'])
if stop:
# NOTE(mriedem): tearDown requires the server to be started.
@@ -367,7 +375,43 @@
waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
server = self.client.show_server(self.server_id)['server']
- self.assertEqual(self.flavor_ref, server['flavor']['id'])
+ 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']
+ # The flavor id is not returned in the server response after
+ # microversion 2.46 so handle that gracefully.
+ if server['flavor'].get('id'):
+ self.assertEqual(self.flavor_ref, server['flavor']['id'])
+ 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,
@@ -491,10 +535,10 @@
def _get_output(self):
output = self.client.get_console_output(
- self.server_id, length=10)['output']
+ self.server_id, length=3)['output']
self.assertTrue(output, "Console output was empty.")
lines = len(output.split('\n'))
- self.assertEqual(lines, 10)
+ self.assertEqual(lines, 3)
@decorators.idempotent_id('4b8867e6-fffa-4d54-b1d1-6fdda57be2f3')
@testtools.skipUnless(CONF.compute_feature_enabled.console_output,
@@ -525,8 +569,8 @@
# NOTE: This test tries to get full length console log, and the
# length should be bigger than the one of test_get_console_output.
- self.assertGreater(lines, 10, "Cannot get enough console log "
- "length. (lines: %s)" % lines)
+ self.assertGreater(lines, 3, "Cannot get enough console log "
+ "length. (lines: %s)" % lines)
self.wait_for(_check_full_length_console_log)
@@ -654,8 +698,13 @@
# Get the VNC console of type 'novnc' and 'xvpvnc'
console_types = ['novnc', 'xvpvnc']
for console_type in console_types:
- body = self.client.get_vnc_console(self.server_id,
- type=console_type)['console']
+ if self.is_requested_microversion_compatible('2.5'):
+ body = self.client.get_vnc_console(
+ self.server_id, type=console_type)['console']
+ else:
+ body = self.client.get_remote_console(
+ self.server_id, console_type=console_type,
+ protocol='vnc')['remote_console']
self.assertEqual(console_type, body['type'])
self.assertNotEqual('', body['url'])
self._validate_url(body['url'])
diff --git a/tempest/api/compute/servers/test_server_group.py b/tempest/api/compute/servers/test_server_group.py
index 5286c8f..1b7cb96 100644
--- a/tempest/api/compute/servers/test_server_group.py
+++ b/tempest/api/compute/servers/test_server_group.py
@@ -47,8 +47,16 @@
super(ServerGroupTestJSON, cls).resource_setup()
cls.policy = ['affinity']
- cls.created_server_group = cls.create_test_server_group(
- policy=cls.policy)
+ 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
diff --git a/tempest/api/compute/servers/test_server_personality.py b/tempest/api/compute/servers/test_server_personality.py
index 6f32b46..4f484e2 100644
--- a/tempest/api/compute/servers/test_server_personality.py
+++ b/tempest/api/compute/servers/test_server_personality.py
@@ -45,6 +45,10 @@
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.'
@@ -75,6 +79,10 @@
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(
@@ -117,6 +125,10 @@
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
diff --git a/tempest/api/compute/servers/test_server_rescue_negative.py b/tempest/api/compute/servers/test_server_rescue_negative.py
index 1260c6b..caceb64 100644
--- a/tempest/api/compute/servers/test_server_rescue_negative.py
+++ b/tempest/api/compute/servers/test_server_rescue_negative.py
@@ -43,7 +43,6 @@
@classmethod
def resource_setup(cls):
super(ServerRescueNegativeTestJSON, cls).resource_setup()
- cls.device = CONF.compute.volume_device_name
cls.password = data_utils.rand_password()
rescue_password = data_utils.rand_password()
# Server for negative tests
@@ -125,8 +124,7 @@
self.assertRaises(lib_exc.Conflict,
self.servers_client.attach_volume,
self.server_id,
- volumeId=volume['id'],
- device='/dev/%s' % self.device)
+ volumeId=volume['id'])
@decorators.idempotent_id('f56e465b-fe10-48bf-b75d-646cda3a8bc9')
@utils.services('volume')
@@ -136,7 +134,7 @@
# Attach the volume to the server
server = self.servers_client.show_server(self.server_id)['server']
- self.attach_volume(server, volume, device='/dev/%s' % self.device)
+ self.attach_volume(server, volume)
# Rescue the server
self.servers_client.rescue_server(self.server_id,
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index e944c28..6cabf65 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -40,8 +40,11 @@
self.__class__.server_id = self.recreate_server(self.server_id)
def tearDown(self):
- self.server_check_teardown()
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):
@@ -485,8 +488,11 @@
server = self.client.show_server(self.server_id)['server']
image_name = server['name'] + '-shelved'
- params = {'name': image_name}
- images = self.compute_images_client.list_images(**params)['images']
+ 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'])
diff --git a/tempest/api/compute/servers/test_virtual_interfaces.py b/tempest/api/compute/servers/test_virtual_interfaces.py
index 5fb1711..f810ec5 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces.py
@@ -28,6 +28,7 @@
# 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
diff --git a/tempest/api/compute/servers/test_virtual_interfaces_negative.py b/tempest/api/compute/servers/test_virtual_interfaces_negative.py
index ec4d7a8..f6e8bc9 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces_negative.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces_negative.py
@@ -23,6 +23,7 @@
# 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
diff --git a/tempest/api/compute/test_networks.py b/tempest/api/compute/test_networks.py
index b8c79d7..76131e2 100644
--- a/tempest/api/compute/test_networks.py
+++ b/tempest/api/compute/test_networks.py
@@ -20,6 +20,8 @@
class ComputeNetworksTest(base.BaseV2ComputeTest):
+ max_microversion = '2.35'
+
@classmethod
def skip_checks(cls):
super(ComputeNetworksTest, cls).skip_checks()
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index caa445d..f7b5b4b 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -84,6 +84,11 @@
linux_client.validate_authentication()
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, in that case
+ # CONF.compute.volume_device_name must be set the equal value as
+ # the libvirt auto-assigned one
attachment = self.attach_volume(server, volume,
device=('/dev/%s' % self.device))
@@ -121,8 +126,7 @@
# List volume attachment of the server
server, _ = self._create_server()
volume_1st = self.create_volume()
- attachment_1st = self.attach_volume(server, volume_1st,
- device=('/dev/%s' % self.device))
+ attachment_1st = self.attach_volume(server, volume_1st)
body = self.servers_client.list_volume_attachments(
server['id'])['volumeAttachments']
self.assertEqual(1, len(body))
@@ -160,6 +164,9 @@
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'
@@ -220,6 +227,9 @@
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
@@ -228,8 +238,7 @@
volume = self.create_volume()
num_vol = self._count_volumes(server, validation_resources)
self._shelve_server(server, validation_resources)
- attachment = self.attach_volume(server, volume,
- device=('/dev/%s' % self.device))
+ attachment = self.attach_volume(server, volume)
# Unshelve the instance and check that attached volume exists
self._unshelve_server_and_check_volumes(
@@ -245,6 +254,9 @@
# 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
@@ -255,7 +267,7 @@
self._shelve_server(server, validation_resources)
# Attach and then detach the volume
- self.attach_volume(server, volume, device=('/dev/%s' % self.device))
+ 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')
diff --git a/tempest/api/compute/volumes/test_attach_volume_negative.py b/tempest/api/compute/volumes/test_attach_volume_negative.py
index 8618148..6d08f90 100644
--- a/tempest/api/compute/volumes/test_attach_volume_negative.py
+++ b/tempest/api/compute/volumes/test_attach_volume_negative.py
@@ -35,9 +35,7 @@
def test_delete_attached_volume(self):
server = self.create_test_server(wait_until='ACTIVE')
volume = self.create_volume()
-
- path = "/dev/%s" % CONF.compute.volume_device_name
- self.attach_volume(server, volume, device=path)
+ self.attach_volume(server, volume)
self.assertRaises(lib_exc.BadRequest,
self.delete_volume, volume['id'])
diff --git a/tempest/api/identity/admin/v3/test_application_credentials.py b/tempest/api/identity/admin/v3/test_application_credentials.py
index 4a74ef8..7e802c6 100644
--- a/tempest/api/identity/admin/v3/test_application_credentials.py
+++ b/tempest/api/identity/admin/v3/test_application_credentials.py
@@ -15,13 +15,9 @@
# under the License.
from tempest.api.identity import base
-from tempest import config
from tempest.lib import decorators
-CONF = config.CONF
-
-
class ApplicationCredentialsV3AdminTest(base.BaseApplicationCredentialsV3Test,
base.BaseIdentityV3AdminTest):
diff --git a/tempest/api/identity/admin/v3/test_credentials.py b/tempest/api/identity/admin/v3/test_credentials.py
index ba19ff7..23fe788 100644
--- a/tempest/api/identity/admin/v3/test_credentials.py
+++ b/tempest/api/identity/admin/v3/test_credentials.py
@@ -20,6 +20,10 @@
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):
@@ -27,10 +31,6 @@
cls.projects = list()
cls.creds_list = [['project_id', 'user_id', 'id'],
['access', 'secret']]
- u_name = data_utils.rand_name('user')
- u_desc = '%s description' % u_name
- u_email = '%s@testmail.tm' % u_name
- u_password = data_utils.rand_password()
for _ in range(2):
project = cls.projects_client.create_project(
data_utils.rand_name('project'),
@@ -38,12 +38,8 @@
cls.addClassResourceCleanup(
cls.projects_client.delete_project, project['id'])
cls.projects.append(project['id'])
-
- cls.user_body = cls.users_client.create_user(
- name=u_name, description=u_desc, password=u_password,
- email=u_email, project_id=cls.projects[0])['user']
- cls.addClassResourceCleanup(
- cls.users_client.delete_user, cls.user_body['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)
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index 97a1f36..72b6be4 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -121,11 +121,7 @@
# 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.groups_client.create_group(
- name=data_utils.rand_name('group'),
- domain_id=domain['id'])['group']
- self.addCleanup(test_utils.call_and_ignore_notfound_exc,
- self.groups_client.delete_group, group['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
diff --git a/tempest/api/identity/admin/v3/test_endpoints.py b/tempest/api/identity/admin/v3/test_endpoints.py
index 874aaa4..2cd8906 100644
--- a/tempest/api/identity/admin/v3/test_endpoints.py
+++ b/tempest/api/identity/admin/v3/test_endpoints.py
@@ -20,6 +20,10 @@
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):
diff --git a/tempest/api/identity/admin/v3/test_endpoints_negative.py b/tempest/api/identity/admin/v3/test_endpoints_negative.py
index d54e222..4c3eb1c 100644
--- a/tempest/api/identity/admin/v3/test_endpoints_negative.py
+++ b/tempest/api/identity/admin/v3/test_endpoints_negative.py
@@ -1,4 +1,3 @@
-
# Copyright 2013 IBM Corp.
# All Rights Reserved.
#
@@ -21,6 +20,10 @@
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):
diff --git a/tempest/api/identity/admin/v3/test_groups.py b/tempest/api/identity/admin/v3/test_groups.py
index 507810b..37ce266 100644
--- a/tempest/api/identity/admin/v3/test_groups.py
+++ b/tempest/api/identity/admin/v3/test_groups.py
@@ -30,50 +30,46 @@
@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.groups_client.create_group(
- name=name, domain_id=self.domain['id'],
- description=description)['group']
- self.addCleanup(self.groups_client.delete_group, group['id'])
+ 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'])
- new_name = data_utils.rand_name('UpdateGroup')
- new_desc = data_utils.rand_name('UpdateDescription')
+ # 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=new_name, description=new_desc)['group']
- self.assertEqual(updated_group['name'], new_name)
- self.assertEqual(updated_group['description'], new_desc)
+ 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(new_name, new_group['name'])
- self.assertEqual(new_desc, new_group['description'])
+ self.assertEqual(first_name_update, new_group['name'])
+ self.assertEqual(first_desc_update, new_group['description'])
- @decorators.idempotent_id('b66eb441-b08a-4a6d-81ab-fef71baeb26c')
- def test_group_update_with_few_fields(self):
- name = data_utils.rand_name('Group')
- old_description = data_utils.rand_name('Description')
- group = self.groups_client.create_group(
- name=name, domain_id=self.domain['id'],
- description=old_description)['group']
- self.addCleanup(self.groups_client.delete_group, group['id'])
-
- new_name = data_utils.rand_name('UpdateGroup')
+ # 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=new_name)['group']
- self.assertEqual(new_name, updated_group['name'])
- # Verify that 'description' is not being updated or deleted.
- self.assertEqual(old_description, updated_group['description'])
+ 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')
def test_group_users_add_list_delete(self):
- name = data_utils.rand_name('Group')
- group = self.groups_client.create_group(
- name=name, domain_id=self.domain['id'])['group']
- self.addCleanup(self.groups_client.delete_group, group['id'])
+ group = self.setup_test_group(domain_id=self.domain['id'])
# add user into group
users = []
for _ in range(3):
@@ -100,11 +96,8 @@
# create two groups, and add user into them
groups = []
for _ in range(2):
- name = data_utils.rand_name('Group')
- group = self.groups_client.create_group(
- name=name, domain_id=self.domain['id'])['group']
+ group = self.setup_test_group(domain_id=self.domain['id'])
groups.append(group)
- self.addCleanup(self.groups_client.delete_group, group['id'])
self.groups_client.add_group_user(group['id'], user['id'])
# list groups which user belongs to
user_groups = self.users_client.list_user_groups(user['id'])['groups']
@@ -118,12 +111,7 @@
group_ids = list()
fetched_ids = list()
for _ in range(3):
- name = data_utils.rand_name('Group')
- description = data_utils.rand_name('Description')
- group = self.groups_client.create_group(
- name=name, domain_id=self.domain['id'],
- description=description)['group']
- self.addCleanup(self.groups_client.delete_group, group['id'])
+ group = 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
diff --git a/tempest/api/identity/admin/v3/test_list_projects.py b/tempest/api/identity/admin/v3/test_list_projects.py
index 82664e8..50f3186 100644
--- a/tempest/api/identity/admin/v3/test_list_projects.py
+++ b/tempest/api/identity/admin/v3/test_list_projects.py
@@ -14,11 +14,26 @@
# 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 ListProjectsTestJSON(base.BaseIdentityV3AdminTest):
+
+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``
+ 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):
@@ -48,30 +63,11 @@
cls.p3['id'])
cls.project_ids.append(cls.p3['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.project_ids:
- show_project = self.projects_client.show_project(p)['project']
- self.assertIn(show_project, list_projects)
-
- @decorators.idempotent_id('fab13f3c-f6a6-4b9f-829b-d32fd44fdf10')
- def test_list_projects_with_domains(self):
- # List projects with domain
- self._list_projects_with_params(
- {'domain_id': self.domain['id']}, 'domain_id')
-
@decorators.idempotent_id('0fe7a334-675a-4509-b00e-1c4b95d5dae8')
def test_list_projects_with_enabled(self):
# List the projects with enabled
- self._list_projects_with_params({'enabled': False}, 'enabled')
-
- @decorators.idempotent_id('fa178524-4e6d-4925-907c-7ab9f42c7e26')
- def test_list_projects_with_name(self):
- # List projects with name
- self._list_projects_with_params({'name': self.p1_name}, 'name')
+ 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):
@@ -83,7 +79,50 @@
for project in fetched_projects:
self.assertEqual(self.p3['parent_id'], project['parent_id'])
- def _list_projects_with_params(self, params, key):
- body = self.projects_client.list_projects(params)['projects']
- self.assertIn(self.p1[key], map(lambda x: x[key], body))
- self.assertNotIn(self.p2[key], map(lambda x: x[key], body))
+
+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()
+ cls.domain_id = CONF.identity.default_domain_id
+ cls.project_ids = list()
+ cls.p1_name = cls.os_primary.credentials.project_name
+ cls.p1 = cls.projects_client.show_project(
+ cls.os_primary.credentials.project_id)['project']
+ cls.project_ids.append(cls.p1['id'])
+ p2_name = data_utils.rand_name('project')
+ cls.p2 = cls.projects_client.create_project(
+ p2_name, domain_id=cls.domain_id)['project']
+ cls.addClassResourceCleanup(cls.projects_client.delete_project,
+ cls.p2['id'])
+ cls.project_ids.append(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.project_ids:
+ show_project = self.projects_client.show_project(p)['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):
+ # List projects with domain
+ key = 'domain_id'
+ params = {key: self.domain_id}
+ # Verify both projects are in the self.domain_id which is the default
+ # domain
+ self._list_projects_with_params(
+ [self.p1, self.p2], [], params, key)
diff --git a/tempest/api/identity/admin/v3/test_projects.py b/tempest/api/identity/admin/v3/test_projects.py
index bc94a8e..6ddf42e 100644
--- a/tempest/api/identity/admin/v3/test_projects.py
+++ b/tempest/api/identity/admin/v3/test_projects.py
@@ -14,12 +14,9 @@
# under the License.
from tempest.api.identity import base
-from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
-CONF = config.CONF
-
class ProjectsTestJSON(base.BaseIdentityV3AdminTest):
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index 62ced19..47f663c 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -14,14 +14,11 @@
# under the License.
from tempest.api.identity import base
-from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
-CONF = config.CONF
-
class RolesV3TestJSON(base.BaseIdentityV3AdminTest):
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
index 0845407..8ae43d6 100644
--- a/tempest/api/identity/admin/v3/test_tokens.py
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -19,7 +19,6 @@
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
@@ -28,30 +27,6 @@
credentials = ['primary', 'admin', 'alt']
- @decorators.idempotent_id('0f9f5a5f-d5cd-4a86-8a5b-c5ded151f212')
- def test_tokens(self):
- # Valid user's token is authenticated
- # Create a User
- u_name = data_utils.rand_name('user')
- u_desc = '%s-description' % u_name
- u_password = data_utils.rand_password()
- user = self.create_test_user(
- name=u_name, description=u_desc, password=u_password)
- # Perform Authentication
- resp = self.token.auth(user_id=user['id'],
- password=u_password).response
- subject_token = resp['x-subject-token']
- self.client.check_token_existence(subject_token)
- # Perform GET Token
- token_details = self.client.show_token(subject_token)['token']
- self.assertEqual(resp['x-subject-token'], subject_token)
- self.assertEqual(token_details['user']['id'], user['id'])
- self.assertEqual(token_details['user']['name'], u_name)
- # Perform Delete Token
- self.client.delete_token(subject_token)
- self.assertRaises(lib_exc.NotFound, self.client.check_token_existence,
- subject_token)
-
@decorators.idempotent_id('565fa210-1da1-4563-999b-f7b5b67cf112')
def test_rescope_token(self):
"""Rescope a token.
@@ -201,10 +176,7 @@
role_id = self.setup_test_role()['id']
# Create a group.
- group_name = data_utils.rand_name('Group')
- group_id = self.groups_client.create_group(
- name=group_name, domain_id=domain_id)['group']['id']
- self.addCleanup(self.groups_client.delete_group, group_id)
+ 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)
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 68f2c07..282343c 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -292,6 +292,20 @@
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):
diff --git a/tempest/api/identity/v2/test_users.py b/tempest/api/identity/v2/test_users.py
index 9c77fff..158dfb3 100644
--- a/tempest/api/identity/v2/test_users.py
+++ b/tempest/api/identity/v2/test_users.py
@@ -84,7 +84,7 @@
new_pass = data_utils.rand_password()
user_id = self.creds.user_id
- # to change password back. important for allow_tenant_isolation = false
+ # 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
diff --git a/tempest/api/identity/v3/test_application_credentials.py b/tempest/api/identity/v3/test_application_credentials.py
index caf0b1e..1cee902 100644
--- a/tempest/api/identity/v3/test_application_credentials.py
+++ b/tempest/api/identity/v3/test_application_credentials.py
@@ -19,13 +19,9 @@
from oslo_utils import timeutils
from tempest.api.identity import base
-from tempest import config
from tempest.lib import decorators
-CONF = config.CONF
-
-
class ApplicationCredentialsV3Test(base.BaseApplicationCredentialsV3Test):
def _list_app_creds(self, name=None):
diff --git a/tempest/api/identity/v3/test_tokens.py b/tempest/api/identity/v3/test_tokens.py
index 4c72d82..f13aa10 100644
--- a/tempest/api/identity/v3/test_tokens.py
+++ b/tempest/api/identity/v3/test_tokens.py
@@ -91,3 +91,28 @@
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)
+ self.assertEqual(token_details['user']['name'], user.username)
+ # 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)
diff --git a/tempest/api/identity/v3/test_users.py b/tempest/api/identity/v3/test_users.py
index 1f099df..6d6baca 100644
--- a/tempest/api/identity/v3/test_users.py
+++ b/tempest/api/identity/v3/test_users.py
@@ -82,7 +82,7 @@
old_token = self.non_admin_client.token
new_pass = data_utils.rand_password()
- # to change password back. important for allow_tenant_isolation = false
+ # to change password back. important for use_dynamic_credentials=false
self.addCleanup(self._restore_password, old_pass, new_pass)
# user updates own password
diff --git a/tempest/api/image/v2/test_images_member.py b/tempest/api/image/v2/test_images_member.py
index 0208780..e19d8c8 100644
--- a/tempest/api/image/v2/test_images_member.py
+++ b/tempest/api/image/v2/test_images_member.py
@@ -33,8 +33,8 @@
self.assertIn(image_id, self._list_image_ids_as_alt())
body = self.image_member_client.list_image_members(image_id)
members = body['members']
- member = members[0]
self.assertEqual(len(members), 1, str(members))
+ member = members[0]
self.assertEqual(member['member_id'], self.alt_tenant_id)
self.assertEqual(member['image_id'], image_id)
self.assertEqual(member['status'], 'accepted')
diff --git a/tempest/api/network/admin/test_agent_management.py b/tempest/api/network/admin/test_agent_management.py
index 5068fc4..30ed176 100644
--- a/tempest/api/network/admin/test_agent_management.py
+++ b/tempest/api/network/admin/test_agent_management.py
@@ -15,7 +15,9 @@
from tempest.api.network import base
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.lib import exceptions as lib_exc
class AgentManagementTestJSON(base.BaseAdminNetworkTest):
@@ -86,3 +88,11 @@
origin_agent = {'description': description}
self.admin_agents_client.update_agent(agent_id=self.agent['id'],
agent=origin_agent)
+
+ @decorators.idempotent_id('b33af888-b6ac-4e68-a0ca-0444c2696cf9')
+ @decorators.attr(type=['negative'])
+ def test_delete_agent_negative(self):
+ non_existent_id = data_utils.rand_uuid()
+ self.assertRaises(
+ lib_exc.NotFound,
+ self.agents_client.delete_agent, non_existent_id)
diff --git a/tempest/api/network/admin/test_dhcp_agent_scheduler.py b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
index 8315c5d..4631ea9 100644
--- a/tempest/api/network/admin/test_dhcp_agent_scheduler.py
+++ b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
@@ -63,7 +63,6 @@
def test_add_remove_network_from_dhcp_agent(self):
# The agent is now bound to the network, we can free the port
self.ports_client.delete_port(self.port['id'])
- self.ports.remove(self.port)
agent = dict()
agent['agent_type'] = None
body = self.admin_agents_client.list_agents()
diff --git a/tempest/api/network/admin/test_external_network_extension.py b/tempest/api/network/admin/test_external_network_extension.py
index 49a9cdb..7e8cc8e 100644
--- a/tempest/api/network/admin/test_external_network_extension.py
+++ b/tempest/api/network/admin/test_external_network_extension.py
@@ -13,6 +13,7 @@
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
@@ -117,8 +118,15 @@
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.admin_floating_ips_client.delete_floatingip,
created_floating_ip['id'])
- floatingip_list = self.admin_floating_ips_client.list_floatingips(
- network=external_network['id'])
+ 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'])
diff --git a/tempest/api/network/admin/test_l3_agent_scheduler.py b/tempest/api/network/admin/test_l3_agent_scheduler.py
index 206d867..033bf55 100644
--- a/tempest/api/network/admin/test_l3_agent_scheduler.py
+++ b/tempest/api/network/admin/test_l3_agent_scheduler.py
@@ -14,11 +14,9 @@
from tempest.api.network import base
from tempest.common import utils
-from tempest import config
from tempest.lib import decorators
from tempest.lib import exceptions
-CONF = config.CONF
AGENT_TYPE = 'L3 agent'
AGENT_MODES = (
'legacy',
diff --git a/tempest/api/network/admin/test_negative_quotas.py b/tempest/api/network/admin/test_negative_quotas.py
index 6849653..e79f8c3 100644
--- a/tempest/api/network/admin/test_negative_quotas.py
+++ b/tempest/api/network/admin/test_negative_quotas.py
@@ -59,7 +59,7 @@
# Try to create a third network while the quota is two
with self.assertRaisesRegex(
lib_exc.Conflict,
- "Quota exceeded for resources: \['network'\].*"):
+ r"Quota exceeded for resources: \['network'\].*"):
n3 = self.networks_client.create_network()
self.addCleanup(self.networks_client.delete_network,
n3['network']['id'])
diff --git a/tempest/api/network/admin/test_ports.py b/tempest/api/network/admin/test_ports.py
index 483b405..05363db 100644
--- a/tempest/api/network/admin/test_ports.py
+++ b/tempest/api/network/admin/test_ports.py
@@ -14,11 +14,8 @@
# under the License.
from tempest.api.network import base
-from tempest import config
from tempest.lib import decorators
-CONF = config.CONF
-
class PortsAdminExtendedAttrsTestJSON(base.BaseAdminNetworkTest):
diff --git a/tempest/api/network/admin/test_routers.py b/tempest/api/network/admin/test_routers.py
index 8cdb41e..a7355f3 100644
--- a/tempest/api/network/admin/test_routers.py
+++ b/tempest/api/network/admin/test_routers.py
@@ -32,7 +32,6 @@
def _cleanup_router(self, router):
self.delete_router(router)
- self.routers.remove(router)
def _create_router(self, name=None, admin_state_up=False,
external_network_id=None, enable_snat=None):
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 8670165..9032fdc 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -88,9 +88,6 @@
@classmethod
def resource_setup(cls):
super(BaseNetworkTest, cls).resource_setup()
- cls.subnets = []
- cls.ports = []
- cls.routers = []
cls.ethertype = "IPv" + str(cls._ip_version)
if cls._ip_version == 4:
cls.cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
@@ -155,7 +152,6 @@
cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
cls.subnets_client.delete_subnet,
subnet['id'])
- cls.subnets.append(subnet)
return subnet
@classmethod
@@ -166,7 +162,6 @@
port = body['port']
cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
cls.ports_client.delete_port, port['id'])
- cls.ports.append(port)
return port
@classmethod
@@ -194,7 +189,6 @@
router = body['router']
cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
cls.delete_router, router)
- cls.routers.append(router)
return router
@classmethod
diff --git a/tempest/api/network/test_allowed_address_pair.py b/tempest/api/network/test_allowed_address_pair.py
index 3075047..dec3413 100644
--- a/tempest/api/network/test_allowed_address_pair.py
+++ b/tempest/api/network/test_allowed_address_pair.py
@@ -17,11 +17,8 @@
from tempest.api.network import base
from tempest.common import utils
-from tempest import config
from tempest.lib import decorators
-CONF = config.CONF
-
class AllowedAddressPairTestJSON(base.BaseNetworkTest):
"""Tests the Neutron Allowed Address Pair API extension
diff --git a/tempest/api/network/test_dhcp_ipv6.py b/tempest/api/network/test_dhcp_ipv6.py
index 9d6d700..0730d58 100644
--- a/tempest/api/network/test_dhcp_ipv6.py
+++ b/tempest/api/network/test_dhcp_ipv6.py
@@ -56,6 +56,9 @@
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):
@@ -90,8 +93,10 @@
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))
@@ -182,16 +187,21 @@
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']]
@@ -228,16 +238,21 @@
'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']]
@@ -267,7 +282,9 @@
'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 '
@@ -289,6 +306,7 @@
'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,
@@ -296,6 +314,7 @@
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,
@@ -310,6 +329,7 @@
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(
@@ -327,14 +347,16 @@
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()
- self.create_port(self.network,
- fixed_ips=[
- {'subnet_id': subnet['id'],
- 'ip_address': ip}])
+ 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,
@@ -344,7 +366,9 @@
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'])
diff --git a/tempest/api/network/test_floating_ips.py b/tempest/api/network/test_floating_ips.py
index ef4a23a..504bfa8 100644
--- a/tempest/api/network/test_floating_ips.py
+++ b/tempest/api/network/test_floating_ips.py
@@ -15,6 +15,7 @@
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 import decorators
@@ -63,8 +64,10 @@
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):
- cls.create_port(cls.network)
+ port = cls.create_port(cls.network)
+ cls.ports.append(port)
@decorators.attr(type='smoke')
@decorators.idempotent_id('62595970-ab1c-4b7f-8fcc-fddfe55e8718')
@@ -158,11 +161,21 @@
self.addCleanup(self.floating_ips_client.delete_floatingip,
created_floating_ip['id'])
self.assertEqual(created_floating_ip['router_id'], self.router['id'])
- network2 = self.create_network()
+ network_name = data_utils.rand_name(self.__class__.__name__)
+ network2 = self.networks_client.create_network(
+ name=network_name)['network']
+ self.addCleanup(self.networks_client.delete_network,
+ network2['id'])
subnet2 = self.create_subnet(network2)
+ self.addCleanup(self.subnets_client.delete_subnet, subnet2['id'])
router2 = self.create_router(external_network_id=self.ext_net_id)
+ self.addCleanup(self.routers_client.delete_router, router2['id'])
self.create_router_interface(router2['id'], subnet2['id'])
+ self.addCleanup(self.routers_client.remove_router_interface,
+ router2['id'], subnet_id=subnet2['id'])
port_other_router = self.create_port(network2)
+ self.addCleanup(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'],
diff --git a/tempest/api/network/test_ports.py b/tempest/api/network/test_ports.py
index 5168423..2c9159c 100644
--- a/tempest/api/network/test_ports.py
+++ b/tempest/api/network/test_ports.py
@@ -22,13 +22,10 @@
from tempest.api.network import base_security_groups as sec_base
from tempest.common import custom_matchers
from tempest.common import utils
-from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from tempest.lib import exceptions
-CONF = config.CONF
-
class PortsTestJSON(sec_base.BaseSecGroupTest):
"""Test the following operations for ports:
@@ -52,6 +49,21 @@
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(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(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):
@@ -73,7 +85,7 @@
@decorators.idempotent_id('67f1b811-f8db-43e2-86bd-72c074d4a42c')
def test_create_bulk_port(self):
network1 = self.network
- network2 = self.create_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)
@@ -90,7 +102,7 @@
@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()
+ network = self._create_network()
net_id = network['id']
address = self.cidr
address.prefixlen = self.mask_bits
@@ -100,10 +112,9 @@
raise exceptions.InvalidConfiguration(msg)
allocation_pools = {'allocation_pools': [{'start': str(address[2]),
'end': str(address[-2])}]}
- subnet = self.create_subnet(network, cidr=address,
- mask_bits=address.prefixlen,
- **allocation_pools)
- self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
+ self._create_subnet(network, cidr=address,
+ mask_bits=address.prefixlen,
+ **allocation_pools)
body = self.ports_client.create_port(network_id=net_id)
self.addCleanup(self.ports_client.delete_port, body['port']['id'])
port = body['port']
@@ -153,9 +164,8 @@
@decorators.idempotent_id('e7fe260b-1e79-4dd3-86d9-bec6a7959fc5')
def test_port_list_filter_by_ip(self):
# Create network and subnet
- network = self.create_network()
- subnet = self.create_subnet(network)
- self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
+ network = self._create_network()
+ self._create_subnet(network)
# Create two ports
port_1 = self.ports_client.create_port(network_id=network['id'])
self.addCleanup(self.ports_client.delete_port, port_1['port']['id'])
@@ -187,10 +197,8 @@
'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)
- self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
-
+ network = self._create_network()
+ subnet = self._create_subnet(network)
# Get two IP addresses
ip_address_1 = None
ip_address_2 = None
@@ -261,10 +269,8 @@
@decorators.idempotent_id('5ad01ed0-0e6e-4c5d-8194-232801b15c72')
def test_port_list_filter_by_router_id(self):
# Create a router
- network = self.create_network()
- self.addCleanup(self.networks_client.delete_network, network['id'])
- subnet = self.create_subnet(network)
- self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
+ network = self._create_network()
+ self._create_subnet(network)
router = self.create_router()
self.addCleanup(self.routers_client.delete_router, router['id'])
port = self.ports_client.create_port(network_id=network['id'])
@@ -294,12 +300,9 @@
@decorators.idempotent_id('63aeadd4-3b49-427f-a3b1-19ca81f06270')
def test_create_update_port_with_second_ip(self):
# Create a network with two subnets
- network = self.create_network()
- self.addCleanup(self.networks_client.delete_network, network['id'])
- subnet_1 = self.create_subnet(network)
- self.addCleanup(self.subnets_client.delete_subnet, subnet_1['id'])
- subnet_2 = self.create_subnet(network)
- self.addCleanup(self.subnets_client.delete_subnet, subnet_2['id'])
+ 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']}]
@@ -323,8 +326,7 @@
self.assertEqual(2, len(port['fixed_ips']))
def _update_port_with_security_groups(self, security_groups_names):
- subnet_1 = self.create_subnet(self.network)
- self.addCleanup(self.subnets_client.delete_subnet, subnet_1['id'])
+ subnet_1 = self._create_subnet(self.network)
fixed_ip_1 = [{'subnet_id': subnet_1['id']}]
security_groups_list = list()
@@ -413,10 +415,8 @@
utils.is_extension_enabled('security-group', 'network'),
'security-group extension not enabled.')
def test_create_port_with_no_securitygroups(self):
- network = self.create_network()
- self.addCleanup(self.networks_client.delete_network, network['id'])
- subnet = self.create_subnet(network)
- self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
+ network = self._create_network()
+ self._create_subnet(network)
port = self.create_port(network, security_groups=[])
self.addCleanup(self.ports_client.delete_port, port['id'])
self.assertIsNotNone(port['security_groups'])
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index abbb779..be3cf65 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -27,18 +27,6 @@
class RoutersTest(base.BaseNetworkTest):
- def _cleanup_router(self, router):
- self.delete_router(router)
- self.routers.remove(router)
-
- def _create_router(self, name=None, admin_state_up=False,
- external_network_id=None, enable_snat=None):
- # associate a cleanup with created routers to avoid quota limits
- router = self.create_router(name, admin_state_up,
- external_network_id, enable_snat)
- self.addCleanup(self._cleanup_router, router)
- return router
-
def _add_router_interface_with_subnet_id(self, router_id, subnet_id):
interface = self.routers_client.add_router_interface(
router_id, subnet_id=subnet_id)
@@ -65,12 +53,13 @@
'The public_network_id option must be specified.')
def test_create_show_list_update_delete_router(self):
# Create a router
- name = data_utils.rand_name(self.__class__.__name__ + '-router')
- router = self._create_router(
- name=name,
+ 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.assertEqual(router['name'], name)
+ 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'],
@@ -97,9 +86,15 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('b42e6e39-2e37-49cc-a6f4-8467e940900a')
def test_add_remove_router_interface_with_subnet_id(self):
- network = self.create_network()
+ network_name = data_utils.rand_name(self.__class__.__name__)
+ network = self.networks_client.create_network(
+ name=network_name)['network']
+ self.addCleanup(self.networks_client.delete_network,
+ network['id'])
subnet = self.create_subnet(network)
- router = self._create_router()
+ self.addCleanup(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'])
@@ -116,9 +111,15 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('2b7d2f37-6748-4d78-92e5-1d590234f0d5')
def test_add_remove_router_interface_with_port_id(self):
- network = self.create_network()
- self.create_subnet(network)
- router = self._create_router()
+ network_name = data_utils.rand_name(self.__class__.__name__)
+ network = self.networks_client.create_network(
+ name=network_name)['network']
+ self.addCleanup(self.networks_client.delete_network,
+ network['id'])
+ subnet = self.create_subnet(network)
+ self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
+ router = self.create_router()
+ self.addCleanup(self.delete_router, router)
port_body = self.ports_client.create_port(
network_id=network['id'])
# add router interface to port created above
@@ -176,20 +177,27 @@
test_routes = []
routes_num = 4
# Create a router
- router = self._create_router(admin_state_up=True)
+ 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 = self.create_network()
+ network_name = data_utils.rand_name(self.__class__.__name__)
+ network = self.networks_client.create_network(
+ name=network_name)['network']
+ self.addCleanup(self.networks_client.delete_network,
+ network['id'])
subnet = self.create_subnet(network, cidr=next_cidr)
+ self.addCleanup(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'])
@@ -230,7 +238,8 @@
@decorators.idempotent_id('a8902683-c788-4246-95c7-ad9c6d63a4d9')
def test_update_router_admin_state(self):
- router = self._create_router()
+ 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'],
@@ -242,14 +251,23 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('802c73c9-c937-4cef-824b-2191e24a6aab')
def test_add_multiple_router_interfaces(self):
- network01 = self.create_network(
- network_name=data_utils.rand_name('router-network01-'))
- network02 = self.create_network(
- network_name=data_utils.rand_name('router-network02-'))
+ network_name = data_utils.rand_name(self.__class__.__name__)
+ network01 = self.networks_client.create_network(
+ name=network_name)['network']
+ self.addCleanup(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(self.networks_client.delete_network,
+ network02['id'])
subnet01 = self.create_subnet(network01)
+ self.addCleanup(self.subnets_client.delete_subnet, subnet01['id'])
sub02_cidr = self.cidr.next()
subnet02 = self.create_subnet(network02, cidr=sub02_cidr)
- router = self._create_router()
+ self.addCleanup(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'],
@@ -261,9 +279,15 @@
@decorators.idempotent_id('96522edf-b4b5-45d9-8443-fa11c26e6eff')
def test_router_interface_port_update_with_fixed_ip(self):
- network = self.create_network()
+ network_name = data_utils.rand_name(self.__class__.__name__)
+ network = self.networks_client.create_network(
+ name=network_name)['network']
+ self.addCleanup(self.networks_client.delete_network,
+ network['id'])
subnet = self.create_subnet(network)
- router = self._create_router()
+ self.addCleanup(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'])
diff --git a/tempest/api/network/test_routers_negative.py b/tempest/api/network/test_routers_negative.py
index ddd7d3a..0b61860 100644
--- a/tempest/api/network/test_routers_negative.py
+++ b/tempest/api/network/test_routers_negative.py
@@ -15,13 +15,10 @@
from tempest.api.network import base
from tempest.common import utils
-from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
-CONF = config.CONF
-
class RoutersNegativeTest(base.BaseNetworkTest):
diff --git a/tempest/api/network/test_security_groups.py b/tempest/api/network/test_security_groups.py
index 24bd8ea..ffc1fca 100644
--- a/tempest/api/network/test_security_groups.py
+++ b/tempest/api/network/test_security_groups.py
@@ -15,12 +15,9 @@
from tempest.api.network import base_security_groups as base
from tempest.common import utils
-from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
-CONF = config.CONF
-
class SecGroupTest(base.BaseSecGroupTest):
diff --git a/tempest/api/network/test_versions.py b/tempest/api/network/test_versions.py
index 2f01e50..020cb5c 100644
--- a/tempest/api/network/test_versions.py
+++ b/tempest/api/network/test_versions.py
@@ -29,7 +29,7 @@
"""
result = self.network_versions_client.list_versions()
- expected_versions = ('v2.0')
+ expected_versions = ('v2.0',)
expected_resources = ('id', 'links', 'status')
received_list = result.values()
@@ -38,3 +38,14 @@
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)
diff --git a/tempest/api/volume/admin/test_backends_capabilities.py b/tempest/api/volume/admin/test_backends_capabilities.py
index 607fc43..affed6b 100644
--- a/tempest/api/volume/admin/test_backends_capabilities.py
+++ b/tempest/api/volume/admin/test_backends_capabilities.py
@@ -72,8 +72,8 @@
]
# Returns a tuple of VOLUME_STATS values
- expected_list = list(map(operator.itemgetter(*VOLUME_STATS),
- cinder_pools))
- observed_list = list(map(operator.itemgetter(*VOLUME_STATS),
- capabilities))
+ 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)
diff --git a/tempest/api/volume/admin/test_group_snapshots.py b/tempest/api/volume/admin/test_group_snapshots.py
index 731a055..f695f51 100644
--- a/tempest/api/volume/admin/test_group_snapshots.py
+++ b/tempest/api/volume/admin/test_group_snapshots.py
@@ -215,6 +215,7 @@
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()
diff --git a/tempest/api/volume/admin/test_user_messages.py b/tempest/api/volume/admin/test_user_messages.py
index 20c3538..9907497 100644
--- a/tempest/api/volume/admin/test_user_messages.py
+++ b/tempest/api/volume/admin/test_user_messages.py
@@ -62,8 +62,16 @@
return message_id
@decorators.idempotent_id('50f29e6e-f363-42e1-8ad1-f67ae7fd4d5a')
- def test_list_messages(self):
- self._create_user_message()
+ def test_list_show_messages(self):
+ message_id = self._create_user_message()
+ self.addCleanup(self.messages_client.delete_message, message_id)
+
+ # show message
+ message = self.messages_client.show_message(message_id)['message']
+ for key in MESSAGE_KEYS:
+ self.assertIn(key, message.keys(), 'Missing expected key %s' % key)
+
+ # list messages
messages = self.messages_client.list_messages()['messages']
self.assertIsInstance(messages, list)
for message in messages:
@@ -71,16 +79,6 @@
self.assertIn(key, message.keys(),
'Missing expected key %s' % key)
- @decorators.idempotent_id('55a4a61e-c7b2-4ba0-a05d-b914bdef3070')
- def test_show_message(self):
- message_id = self._create_user_message()
- self.addCleanup(self.messages_client.delete_message, message_id)
-
- message = self.messages_client.show_message(message_id)['message']
-
- for key in MESSAGE_KEYS:
- self.assertIn(key, message.keys(), 'Missing expected key %s' % key)
-
@decorators.idempotent_id('c6eb6901-cdcc-490f-b735-4fe251842aed')
def test_delete_message(self):
message_id = self._create_user_message()
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index e546bff..053a7d9 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -22,37 +22,36 @@
QUOTA_USAGE_KEYS = ['reserved', 'limit', 'in_use']
-class BaseVolumeQuotasAdminTestJSON(base.BaseVolumeAdminTest):
+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(BaseVolumeQuotasAdminTestJSON, self).setUp()
+ super(VolumeQuotasAdminTestJSON, self).setUp()
@classmethod
def setup_credentials(cls):
- super(BaseVolumeQuotasAdminTestJSON, cls).setup_credentials()
+ super(VolumeQuotasAdminTestJSON, cls).setup_credentials()
cls.demo_tenant_id = cls.os_primary.credentials.tenant_id
@classmethod
def setup_clients(cls):
- super(BaseVolumeQuotasAdminTestJSON, cls).setup_clients()
- cls.transfer_client = cls.os_primary.volume_transfers_v2_client
- cls.alt_transfer_client = cls.os_alt.volume_transfers_v2_client
+ 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(BaseVolumeQuotasAdminTestJSON, cls).resource_setup()
+ 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.
- cls.original_quota_set = (cls.admin_quotas_client.show_quota_set(
+ 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 cls.original_quota_set.items()
- if k in QUOTA_KEYS)
+ (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):
diff --git a/tempest/api/volume/admin/test_volume_quotas_negative.py b/tempest/api/volume/admin/test_volume_quotas_negative.py
index f50f336..915aeec 100644
--- a/tempest/api/volume/admin/test_volume_quotas_negative.py
+++ b/tempest/api/volume/admin/test_volume_quotas_negative.py
@@ -23,29 +23,27 @@
'backup_gigabytes', 'per_volume_gigabytes']
-class BaseVolumeQuotasNegativeTestJSON(base.BaseVolumeAdminTest):
+class VolumeQuotasNegativeTestJSON(base.BaseVolumeAdminTest):
@classmethod
def setup_credentials(cls):
- super(BaseVolumeQuotasNegativeTestJSON, cls).setup_credentials()
+ super(VolumeQuotasNegativeTestJSON, cls).setup_credentials()
cls.demo_tenant_id = cls.os_primary.credentials.tenant_id
@classmethod
def resource_setup(cls):
- super(BaseVolumeQuotasNegativeTestJSON, cls).resource_setup()
+ 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.
- cls.original_quota_set = (cls.admin_quotas_client.show_quota_set(
+ 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 cls.original_quota_set.items()
- if k in QUOTA_KEYS)
+ 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,
- **cls.cleanup_quota_set)
+ cls.demo_tenant_id, **cleanup_quota_set)
cls.shared_quota_set = {'gigabytes': 2 * CONF.volume.volume_size,
'volumes': 1}
diff --git a/tempest/api/volume/admin/test_volume_retype_with_migration.py b/tempest/api/volume/admin/test_volume_retype.py
similarity index 67%
rename from tempest/api/volume/admin/test_volume_retype_with_migration.py
rename to tempest/api/volume/admin/test_volume_retype.py
index 025c1be..1c56eb2 100644
--- a/tempest/api/volume/admin/test_volume_retype_with_migration.py
+++ b/tempest/api/volume/admin/test_volume_retype.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import abc
from oslo_log import log as logging
@@ -23,31 +24,7 @@
LOG = logging.getLogger(__name__)
-class VolumeRetypeWithMigrationTest(base.BaseVolumeAdminTest):
-
- @classmethod
- def skip_checks(cls):
- super(VolumeRetypeWithMigrationTest, cls).skip_checks()
-
- if not CONF.volume_feature_enabled.multi_backend:
- raise cls.skipException("Cinder multi-backend feature disabled.")
-
- if len(set(CONF.volume.backend_names)) < 2:
- raise cls.skipException("Requires at least two different "
- "backend names")
-
- @classmethod
- def resource_setup(cls):
- super(VolumeRetypeWithMigrationTest, cls).resource_setup()
- # read backend name from a list.
- backend_src = CONF.volume.backend_names[0]
- backend_dst = CONF.volume.backend_names[1]
-
- extra_specs_src = {"volume_backend_name": backend_src}
- extra_specs_dst = {"volume_backend_name": backend_dst}
-
- 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)
+class VolumeRetypeTest(base.BaseVolumeAdminTest):
def _wait_for_internal_volume_cleanup(self, vol):
# When retyping a volume, Cinder creates an internal volume in the
@@ -70,43 +47,11 @@
fetched_vol['id'])
break
- def _retype_volume(self, volume):
- 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')
+ @abc.abstractmethod
+ def _verify_migration(self, source_vol, dest_vol):
+ pass
- 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='on-demand')
- 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']
-
- # 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])
-
- @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)
-
- @decorators.idempotent_id('d0d9554f-e7a5-4104-8973-f35b27ccb60d')
- def test_volume_from_snapshot_retype_with_migration(self):
+ 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'])
@@ -121,5 +66,115 @@
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)
+ 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')
diff --git a/tempest/api/volume/admin/test_volume_services_negative.py b/tempest/api/volume/admin/test_volume_services_negative.py
index 6f3dbc6..3a863a1 100644
--- a/tempest/api/volume/admin/test_volume_services_negative.py
+++ b/tempest/api/volume/admin/test_volume_services_negative.py
@@ -23,10 +23,9 @@
@classmethod
def resource_setup(cls):
super(VolumeServicesNegativeTest, cls).resource_setup()
- cls.services = cls.admin_volume_services_client.list_services()[
- 'services']
- cls.host = cls.services[0]['host']
- cls.binary = cls.services[0]['binary']
+ 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')
diff --git a/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py b/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
index 74eb792..ff5e7e2 100644
--- a/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
+++ b/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
@@ -44,14 +44,12 @@
# 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.
- cls.original_quota_set = (cls.admin_quotas_client.show_quota_set(
+ 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 cls.original_quota_set.items()
- if k in QUOTA_KEYS)
+ 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,
- **cls.cleanup_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,
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index 1077524..9e24176 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -193,8 +193,13 @@
'is_public': is_public}
updated_vol_type = self.admin_volume_types_client.update_volume_type(
volume_type['id'], **kwargs)['volume_type']
-
- # Verify volume type details were updated
self.assertEqual(name, updated_vol_type['name'])
self.assertEqual(description, updated_vol_type['description'])
self.assertEqual(is_public, updated_vol_type['is_public'])
+
+ # 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'])
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index c179c35..45060d0 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -60,6 +60,8 @@
# 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
@@ -126,6 +128,8 @@
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'],
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 81fd6e6..64fe29a 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -74,24 +74,19 @@
if CONF.service_available.glance:
cls.images_client = cls.os_primary.image_client_v2
- if cls._api_version == 3:
- cls.backups_client = cls.os_primary.backups_v3_client
- cls.volumes_client = cls.os_primary.volumes_v3_client
- cls.messages_client = cls.os_primary.volume_v3_messages_client
- cls.versions_client = cls.os_primary.volume_v3_versions_client
- cls.groups_client = cls.os_primary.groups_v3_client
- cls.group_snapshots_client = (
- cls.os_primary.group_snapshots_v3_client)
- else:
- cls.backups_client = cls.os_primary.backups_v2_client
- cls.volumes_client = cls.os_primary.volumes_v2_client
-
- cls.snapshots_client = cls.os_primary.snapshots_v2_client
+ cls.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_v2_extension_client
+ cls.os_primary.volumes_extension_client_latest
cls.availability_zone_client = (
- cls.os_primary.volume_v2_availability_zone_client)
- cls.volume_limits_client = cls.os_primary.volume_v2_limits_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()
@@ -247,34 +242,34 @@
def setup_clients(cls):
super(BaseVolumeAdminTest, cls).setup_clients()
- cls.admin_volume_qos_client = cls.os_admin.volume_qos_v2_client
+ cls.admin_volume_qos_client = cls.os_admin.volume_qos_client_latest
cls.admin_volume_services_client = \
- cls.os_admin.volume_services_v2_client
- cls.admin_volume_types_client = cls.os_admin.volume_types_v2_client
- cls.admin_volume_manage_client = cls.os_admin.volume_manage_v2_client
- cls.admin_volume_client = cls.os_admin.volumes_v2_client
- if cls._api_version == 3:
- cls.admin_volume_client = cls.os_admin.volumes_v3_client
- cls.admin_groups_client = cls.os_admin.groups_v3_client
- cls.admin_messages_client = cls.os_admin.volume_v3_messages_client
- cls.admin_group_snapshots_client = \
- cls.os_admin.group_snapshots_v3_client
- cls.admin_group_types_client = cls.os_admin.group_types_v3_client
- cls.admin_hosts_client = cls.os_admin.volume_hosts_v2_client
+ cls.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_v2_client
- cls.admin_snapshots_client = cls.os_admin.snapshots_v2_client
- cls.admin_backups_client = cls.os_admin.backups_v2_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_v2_client
+ cls.os_admin.encryption_types_client_latest
cls.admin_quota_classes_client = \
- cls.os_admin.volume_quota_classes_v2_client
- cls.admin_quotas_client = cls.os_admin.volume_quotas_v2_client
- cls.admin_volume_limits_client = cls.os_admin.volume_v2_limits_client
+ cls.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_v2_client
+ cls.os_admin.volume_capabilities_client_latest
cls.admin_scheduler_stats_client = \
- cls.os_admin.volume_scheduler_stats_v2_client
+ cls.os_admin.volume_scheduler_stats_client_latest
@classmethod
def create_test_qos_specs(cls, name=None, consumer=None, **kwargs):
diff --git a/tempest/api/volume/test_volume_transfers.py b/tempest/api/volume/test_volume_transfers.py
index 75e81b7..c85e0bc 100644
--- a/tempest/api/volume/test_volume_transfers.py
+++ b/tempest/api/volume/test_volume_transfers.py
@@ -27,10 +27,10 @@
def setup_clients(cls):
super(VolumesTransfersTest, cls).setup_clients()
- cls.client = cls.os_primary.volume_transfers_v2_client
- cls.alt_client = cls.os_alt.volume_transfers_v2_client
- cls.alt_volumes_client = cls.os_alt.volumes_v2_client
- cls.adm_volumes_client = cls.os_admin.volumes_v2_client
+ 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):
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index 07cfad5..c178272 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -117,6 +117,8 @@
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')
@@ -132,6 +134,8 @@
# 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']
@@ -160,6 +164,8 @@
# 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 = {
diff --git a/tempest/api/volume/test_volumes_extend.py b/tempest/api/volume/test_volumes_extend.py
index 5d339c4..ac9a9c7 100644
--- a/tempest/api/volume/test_volumes_extend.py
+++ b/tempest/api/volume/test_volumes_extend.py
@@ -33,7 +33,7 @@
def test_volume_extend(self):
# Extend Volume Test.
volume = self.create_volume(image_ref=self.image_ref)
- extend_size = volume['size'] + 1
+ 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,
@@ -48,7 +48,7 @@
volume = self.create_volume()
self.create_snapshot(volume['id'])
- extend_size = volume['size'] + 1
+ 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,
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index f139283..866bd87 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -103,21 +103,24 @@
def test_create_volume_with_nonexistent_volume_type(self):
# Should not be able to create volume with non-existent volume type
self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume,
- size='1', volume_type=data_utils.rand_uuid())
+ 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='1', snapshot_id=data_utils.rand_uuid())
+ 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='1', source_volid=data_utils.rand_uuid())
+ size=CONF.volume.volume_size,
+ source_volid=data_utils.rand_uuid())
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0186422c-999a-480e-a026-6a665744c30c')
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index 52114bc..1855386 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -15,6 +15,7 @@
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
@@ -129,17 +130,15 @@
# Delete the snapshot
self.delete_snapshot(snapshot['id'])
- @decorators.idempotent_id('677863d1-3142-456d-b6ac-9924f667a7f4')
- def test_volume_from_snapshot(self):
- # Creates a volume from a snapshot passing a size
- # different from the source
+ 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'])
- # Destination volume bigger than source snapshot
+
dst_vol = self.create_volume(snapshot_id=src_snap['id'],
- size=src_size + 1)
+ 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
@@ -152,7 +151,18 @@
volume = self.volumes_client.show_volume(dst_vol['id'])['volume']
# Should allow
self.assertEqual(volume['snapshot_id'], src_snap['id'])
- self.assertEqual(volume['size'], src_size + 1)
+ 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,
@@ -163,6 +173,8 @@
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'])
diff --git a/tempest/api/volume/test_volumes_snapshots_list.py b/tempest/api/volume/test_volumes_snapshots_list.py
index f12bfd8..8a416ea 100644
--- a/tempest/api/volume/test_volumes_snapshots_list.py
+++ b/tempest/api/volume/test_volumes_snapshots_list.py
@@ -160,3 +160,11 @@
# 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))
diff --git a/tempest/api/volume/test_volumes_snapshots_negative.py b/tempest/api/volume/test_volumes_snapshots_negative.py
index ea5f036..0453c0a 100644
--- a/tempest/api/volume/test_volumes_snapshots_negative.py
+++ b/tempest/api/volume/test_volumes_snapshots_negative.py
@@ -50,7 +50,7 @@
@decorators.idempotent_id('677863d1-34f9-456d-b6ac-9924f667a7f4')
def test_volume_from_snapshot_decreasing_size(self):
# Creates a volume a snapshot passing a size different from the source
- src_size = CONF.volume.volume_size + 1
+ src_size = CONF.volume.volume_size * 2
src_vol = self.create_volume(size=src_size)
src_snap = self.create_snapshot(src_vol['id'])
@@ -58,7 +58,7 @@
# Destination volume smaller than source
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.create_volume,
- size=src_size - 1,
+ size=CONF.volume.volume_size,
snapshot_id=src_snap['id'])
@decorators.attr(type=['negative'])
diff --git a/tempest/clients.py b/tempest/clients.py
index 2a07be9..204ce08 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -222,25 +222,50 @@
def _set_volume_clients(self):
- if CONF.volume_feature_enabled.api_v1:
- self.backups_client = self.volume_v1.BackupsClient()
- self.encryption_types_client = \
- self.volume_v1.EncryptionTypesClient()
- self.snapshots_client = self.volume_v1.SnapshotsClient()
- self.volume_availability_zone_client = \
- self.volume_v1.AvailabilityZoneClient()
- self.volume_hosts_client = self.volume_v1.HostsClient()
- self.volume_limits_client = self.volume_v1.LimitsClient()
- self.volume_qos_client = self.volume_v1.QosSpecsClient()
- self.volume_quotas_client = self.volume_v1.QuotasClient()
- self.volume_services_client = self.volume_v1.ServicesClient()
- self.volume_types_client = self.volume_v1.TypesClient()
- self.volumes_client = self.volume_v1.VolumesClient()
- self.volumes_extension_client = self.volume_v1.ExtensionsClient()
-
# if 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()
@@ -268,11 +293,6 @@
self.volumes_v2_extension_client = \
self.volume_v3.ExtensionsClient()
- # Set default client for users that don't need explicit version
- self.volumes_client_latest = self.volumes_v2_client
- self.snapshots_client_latest = self.snapshots_v2_client
- self.backups_client_latest = self.backups_v2_client
-
if CONF.volume_feature_enabled.api_v3:
self.backups_v3_client = self.volume_v3.BackupsClient()
self.group_types_v3_client = self.volume_v3.GroupTypesClient()
@@ -283,11 +303,7 @@
self.volume_v3_messages_client = self.volume_v3.MessagesClient()
self.volume_v3_versions_client = self.volume_v3.VersionsClient()
self.volumes_v3_client = self.volume_v3.VolumesClient()
-
- # Set default client for users that don't need explicit version
- self.volumes_client_latest = self.volumes_v3_client
- self.snapshots_client_latest = self.snapshots_v3_client
- self.backups_client_latest = self.backups_v3_client
+ # ****************Deprecated alias end here***********************
def _set_object_storage_clients(self):
self.account_client = self.object_storage.AccountClient()
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index 1c671ec..25e91aa 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -162,7 +162,6 @@
if CONF.service_available.swift:
spec.append([CONF.object_storage.operator_role])
spec.append([CONF.object_storage.reseller_admin_role])
- spec.append([CONF.object_storage.operator_role])
if admin:
spec.append('admin')
resources = []
@@ -195,7 +194,6 @@
if test_resource.network:
account['resources'] = {}
- if test_resource.network:
account['resources']['network'] = test_resource.network['name']
accounts.append(account)
if os.path.exists(account_file):
@@ -311,5 +309,6 @@
resources.extend(generate_resources(cred_provider, opts.admin))
dump_accounts(resources, opts.identity_version, opts.accounts)
+
if __name__ == "__main__":
main()
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index 27e1bc1..1a08246 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -315,7 +315,7 @@
class VolumeQuotaService(BaseService):
def __init__(self, manager, **kwargs):
super(VolumeQuotaService, self).__init__(kwargs)
- self.client = manager.volume_quotas_v2_client
+ self.client = manager.volume_quotas_client_latest
def delete(self):
client = self.client
@@ -720,11 +720,11 @@
class ImageService(BaseService):
def __init__(self, manager, **kwargs):
super(ImageService, self).__init__(kwargs)
- self.client = manager.compute_images_client
+ self.client = manager.image_client_v2
def list(self):
client = self.client
- images = client.list_images({"all_tenants": True})['images']
+ 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()]
diff --git a/tempest/cmd/init.py b/tempest/cmd/init.py
index 84c8631..d84f3a3 100644
--- a/tempest/cmd/init.py
+++ b/tempest/cmd/init.py
@@ -26,7 +26,7 @@
LOG = logging.getLogger(__name__)
-STESTR_CONF = """[DEFAULT]
+STESTR_CONF = r"""[DEFAULT]
test_path=%s
top_dir=%s
group_regex=([^\.]*\.)*
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index a27425c..84c6d9a 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -160,8 +160,6 @@
sys.exit(2)
if parsed_args.state:
self._init_state()
- else:
- pass
regex = self._build_regex(parsed_args)
return_code = 0
diff --git a/tempest/cmd/subunit_describe_calls.py b/tempest/cmd/subunit_describe_calls.py
index a4402fe..8dcf575 100644
--- a/tempest/cmd/subunit_describe_calls.py
+++ b/tempest/cmd/subunit_describe_calls.py
@@ -95,7 +95,7 @@
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<name>.*)\): (?P<code>[\d]{3}) '
- '(?P<verb>\w*) (?P<url>.*) .*')
+ r'(?P<verb>\w*) (?P<url>.*) .*')
port_re = re.compile(r'.*:(?P<port>\d+).*')
path_re = re.compile(r'http[s]?://[^/]*/(?P<path>.*)')
request_re = re.compile(r'.* Request - Headers: (?P<headers>.*)')
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 15af271..6c2fee8 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -205,10 +205,6 @@
def verify_cinder_api_versions(os, update):
# Check cinder api versions
versions = _get_api_versions(os, 'cinder')
- if (CONF.volume_feature_enabled.api_v1 !=
- contains_version('v1.', versions)):
- print_and_or_update('api_v1', 'volume-feature-enabled',
- not CONF.volume_feature_enabled.api_v1, update)
if (CONF.volume_feature_enabled.api_v2 !=
contains_version('v2.', versions)):
print_and_or_update('api_v2', 'volume-feature-enabled',
@@ -283,6 +279,9 @@
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
@@ -488,5 +487,6 @@
traceback.print_exc()
raise
+
if __name__ == "__main__":
main()
diff --git a/tempest/cmd/workspace.py b/tempest/cmd/workspace.py
index 929a584..d276bde 100644
--- a/tempest/cmd/workspace.py
+++ b/tempest/cmd/workspace.py
@@ -86,6 +86,7 @@
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()
@@ -128,6 +129,12 @@
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 os.path.exists(path):
print("Path does not exist.")
@@ -141,6 +148,7 @@
# 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()
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 68c4a10..f2730b3 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -165,7 +165,7 @@
if volume_backed:
volume_name = data_utils.rand_name(__name__ + '-volume')
- volumes_client = clients.volumes_v2_client
+ volumes_client = clients.volumes_client_latest
params = {'name': volume_name,
'imageRef': image_id,
'size': CONF.volume.volume_size}
diff --git a/tempest/common/credentials_factory.py b/tempest/common/credentials_factory.py
index 75db155..c6e5dcb 100644
--- a/tempest/common/credentials_factory.py
+++ b/tempest/common/credentials_factory.py
@@ -210,6 +210,7 @@
except exceptions.InvalidConfiguration:
return False
+
# === Credentials
# Type of credentials available from configuration
diff --git a/tempest/common/custom_matchers.py b/tempest/common/custom_matchers.py
index ed11b21..c702d88 100644
--- a/tempest/common/custom_matchers.py
+++ b/tempest/common/custom_matchers.py
@@ -225,9 +225,9 @@
elif key in ('content-type', 'date', 'last-modified',
'x-copied-from-last-modified') and not value:
return InvalidFormat(key, value)
- elif key == 'x-timestamp' and not re.match("^\d+\.?\d*\Z", value):
+ 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("\S+/\S+", 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):
diff --git a/tempest/common/utils/__init__.py b/tempest/common/utils/__init__.py
index 225a713..167bf5b 100644
--- a/tempest/common/utils/__init__.py
+++ b/tempest/common/utils/__init__.py
@@ -40,6 +40,7 @@
self.__dict__[attr] = attr_obj
return attr_obj
+
data_utils = DataUtils()
diff --git a/tempest/config.py b/tempest/config.py
index 1fb5c8e..dbce504 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -61,11 +61,7 @@
"users. This option requires that OpenStack Identity "
"API admin credentials are known. If false, isolated "
"test cases and parallel execution, can still be "
- "achieved configuring a list of test accounts",
- deprecated_opts=[cfg.DeprecatedOpt('allow_tenant_isolation',
- group='auth'),
- cfg.DeprecatedOpt('allow_tenant_isolation',
- group='compute')]),
+ "achieved configuring a list of test accounts"),
cfg.ListOpt('tempest_roles',
help="Roles to assign to all users created by tempest",
default=[]),
@@ -310,7 +306,7 @@
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.DEFAULT]->shelved_offload_time in nova.conf, and '
+ 'nova.conf: DEFAULT.shelved_offload_time, and '
'some tests will run for as long as the time.'),
cfg.IntOpt('min_compute_nodes',
default=1,
@@ -412,7 +408,7 @@
cfg.BoolOpt('vnc_console',
default=False,
help='Enable VNC console. This configuration value should '
- 'be same as [nova.vnc]->vnc_enabled in nova.conf'),
+ 'be same as nova.conf: vnc.enabled'),
cfg.StrOpt('vnc_server_header',
default='WebSockify',
help='Expected VNC server name (WebSockify, nginx, etc) '
@@ -420,16 +416,16 @@
cfg.BoolOpt('spice_console',
default=False,
help='Enable Spice console. This configuration value should '
- 'be same as [nova.spice]->enabled in nova.conf'),
+ '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.rdp]->enabled in nova.conf'),
+ '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.serial_console]->enabled '
- 'in nova.conf'),
+ 'should be the same as '
+ 'nova.conf: serial_console.enabled'),
cfg.BoolOpt('rescue',
default=True,
help='Does the test environment support instance rescue '
@@ -474,7 +470,7 @@
"entry 'all' indicates all filters that are included "
"with nova are enabled. Empty list indicates all filters "
"are disabled. The full list of available filters is in "
- "nova.conf: DEFAULT.scheduler_available_filters. If the "
+ "nova.conf: filter_scheduler.enabled_filters. If the "
"default value is overridden in nova.conf by the test "
"environment (which means that a different set of "
"filters is enabled than what is included in Nova by "
@@ -551,7 +547,7 @@
'test v2 APIs only so this config option '
'will be removed.'),
cfg.BoolOpt('api_v1',
- default=True,
+ default=False,
help="Is the v1 image API enabled",
deprecated_for_removal=True,
deprecated_reason='Glance v1 APIs are deprecated and v2 APIs '
@@ -676,9 +672,11 @@
ValidationGroup = [
cfg.BoolOpt('run_validation',
- default=False,
+ default=True,
help='Enable ssh on created servers and creation of additional'
- ' validation resources to enable remote access'),
+ ' 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.'),
@@ -840,19 +838,31 @@
help='A list of enabled volume extensions with a special '
'entry all which indicates every extension is enabled. '
'Empty list indicates all extensions are disabled'),
- cfg.BoolOpt('api_v1',
- default=False,
- help="Is the v1 volume API enabled",
- deprecated_for_removal=True,
- deprecated_reason="The v1 volume API has been deprecated "
- "since Juno release, and the API will be "
- "removed."),
cfg.BoolOpt('api_v2',
default=True,
- help="Is the v2 volume API enabled"),
+ 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"),
+ 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 '
@@ -1221,6 +1231,11 @@
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()
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index b6e7f8c..2c40cb1 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -15,7 +15,7 @@
import os
import re
-import pep8
+import pycodestyle
PYTHON_CLIENTS = ['cinder', 'glance', 'keystone', 'nova', 'swift', 'neutron',
@@ -34,6 +34,9 @@
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):
@@ -66,7 +69,7 @@
def no_setup_teardown_class_for_tests(physical_line, filename):
- if pep8.noqa(physical_line):
+ if pycodestyle.noqa(physical_line):
return
if 'tempest/test.py' in filename or 'tempest/lib/' in filename:
@@ -161,7 +164,7 @@
if not METHOD.match(physical_line):
return False
- if pep8.noqa(physical_line):
+ if pycodestyle.noqa(physical_line):
return False
return True
@@ -284,13 +287,13 @@
if 'tempest/api/' not in filename:
return
- if pep8.noqa(physical_line):
+ if pycodestyle.noqa(physical_line):
return
- if not re.match('class .*Test.*\(.*Admin.*\):', logical_line):
+ if not re.match(r'class .*Test.*\(.*Admin.*\):', logical_line):
return
- if not re.match('.\/tempest\/api\/.*\/admin\/.*', filename):
+ if not re.match(r'.\/tempest\/api\/.*\/admin\/.*', filename):
msg = 'T115: All admin tests should exist under admin path.'
yield(0, msg)
@@ -306,6 +309,29 @@
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)
@@ -322,3 +348,4 @@
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)
diff --git a/tempest/lib/api_schema/response/compute/v2_1/flavors.py b/tempest/lib/api_schema/response/compute/v2_1/flavors.py
index af5e67f..bd5e3d6 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/flavors.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/flavors.py
@@ -60,7 +60,7 @@
},
'additionalProperties': False,
# 'OS-FLV-DISABLED', 'os-flavor-access', 'rxtx_factor' and
- # 'OS-FLV-EXT-DATA' are API extensions. So they are not 'required'.
+ # 'OS-FLV-EXT-DATA' are API extensions, so they are not 'required'.
'required': ['name', 'links', 'ram', 'vcpus', 'swap', 'disk', 'id']
}
@@ -74,7 +74,7 @@
'items': common_flavor_info
},
# NOTE(gmann): flavors_links attribute is not necessary
- # to be present always So it is not 'required'.
+ # to be present always so it is not 'required'.
'flavors_links': parameter_types.links
},
'additionalProperties': False,
@@ -82,10 +82,6 @@
}
}
-unset_flavor_extra_specs = {
- 'status_code': [200]
-}
-
create_update_get_flavor_details = {
'status_code': [200],
'response_body': {
diff --git a/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py b/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py
index a438d48..3aa1eda 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py
@@ -20,7 +20,7 @@
'extra_specs': {
'type': 'object',
'patternProperties': {
- '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
+ r'^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
}
}
},
@@ -29,12 +29,16 @@
}
}
+unset_flavor_extra_specs = {
+ 'status_code': [200]
+}
+
set_get_flavor_extra_specs_key = {
'status_code': [200],
'response_body': {
'type': 'object',
'patternProperties': {
- '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
+ r'^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
}
}
}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/server_groups.py b/tempest/lib/api_schema/response/compute/v2_1/server_groups.py
new file mode 100644
index 0000000..01db20b
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/server_groups.py
@@ -0,0 +1,65 @@
+# 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']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/servers.py b/tempest/lib/api_schema/response/compute/v2_1/servers.py
index 2954de0..3300298 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/servers.py
@@ -345,58 +345,6 @@
}
}
-common_server_group = {
- 'type': 'object',
- 'properties': {
- 'id': {'type': 'string'},
- 'name': {'type': 'string'},
- 'policies': {
- 'type': 'array',
- 'items': {'type': 'string'}
- },
- # 'members' attribute contains the array of instance's UUID of
- # instances present in server group
- 'members': {
- 'type': 'array',
- 'items': {'type': 'string'}
- },
- 'metadata': {'type': 'object'}
- },
- 'additionalProperties': False,
- 'required': ['id', 'name', 'policies', 'members', 'metadata']
-}
-
-create_show_server_group = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'server_group': common_server_group
- },
- 'additionalProperties': False,
- 'required': ['server_group']
- }
-}
-
-delete_server_group = {
- 'status_code': [204]
-}
-
-list_server_groups = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'server_groups': {
- 'type': 'array',
- 'items': common_server_group
- }
- },
- 'additionalProperties': False,
- 'required': ['server_groups']
- }
-}
-
instance_actions = {
'type': 'object',
'properties': {
@@ -430,8 +378,9 @@
'traceback': {'type': ['string', 'null']}
},
'additionalProperties': False,
- 'required': ['event', 'start_time', 'finish_time', 'result',
- 'traceback']
+ # 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']
}
}
diff --git a/tempest/lib/api_schema/response/compute/v2_11/services.py b/tempest/lib/api_schema/response/compute/v2_11/services.py
index 18b833b..9ece1f9 100644
--- a/tempest/lib/api_schema/response/compute/v2_11/services.py
+++ b/tempest/lib/api_schema/response/compute/v2_11/services.py
@@ -44,3 +44,10 @@
'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)
diff --git a/tempest/lib/api_schema/response/compute/v2_13/servers.py b/tempest/lib/api_schema/response/compute/v2_13/server_groups.py
similarity index 69%
rename from tempest/lib/api_schema/response/compute/v2_13/servers.py
rename to tempest/lib/api_schema/response/compute/v2_13/server_groups.py
index a90f3e4..5cb4241 100644
--- a/tempest/lib/api_schema/response/compute/v2_13/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_13/server_groups.py
@@ -14,21 +14,24 @@
import copy
-from tempest.lib.api_schema.response.compute.v2_1 import servers
+from tempest.lib.api_schema.response.compute.v2_1 import server_groups
-
-common_server_group = copy.deepcopy(servers.common_server_group)
+# 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(servers.create_show_server_group)
+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(servers.delete_server_group)
+delete_server_group = copy.deepcopy(server_groups.delete_server_group)
-list_server_groups = copy.deepcopy(servers.list_server_groups)
+list_server_groups = copy.deepcopy(server_groups.list_server_groups)
list_server_groups['response_body']['properties']['server_groups'][
'items'] = common_server_group
diff --git a/tempest/lib/api_schema/response/compute/v2_16/servers.py b/tempest/lib/api_schema/response/compute/v2_16/servers.py
index 3eb658f..72b84f5 100644
--- a/tempest/lib/api_schema/response/compute/v2_16/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_16/servers.py
@@ -122,7 +122,7 @@
'^[a-zA-Z0-9-_.]+$']['items']['properties'].update({
'OS-EXT-IPS:type': {'type': 'string'},
'OS-EXT-IPS-MAC:mac_addr': parameter_types.mac_address})
-# NOTE(gmann)dd: Update OS-EXT-IPS:type and OS-EXT-IPS-MAC:mac_addr
+# 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'.
@@ -157,4 +157,14 @@
}
}
+# 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)
diff --git a/tempest/lib/api_schema/response/compute/v2_19/servers.py b/tempest/lib/api_schema/response/compute/v2_19/servers.py
index fd9e933..e3e8ad1 100644
--- a/tempest/lib/api_schema/response/compute/v2_19/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_19/servers.py
@@ -16,10 +16,10 @@
from tempest.lib.api_schema.response.compute.v2_16 import servers \
as serversv216
-from tempest.lib.api_schema.response.compute.v2_9 import servers as serversv29
-list_servers = copy.deepcopy(serversv216.list_servers)
-
+# 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']}})
@@ -32,21 +32,29 @@
list_servers_detail['response_body']['properties']['servers']['items'][
'required'].append('description')
-update_server = copy.deepcopy(serversv29.update_server)
+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(serversv29.rebuild_server)
+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(
- serversv29.rebuild_server_with_admin_pass)
+ 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)
diff --git a/tempest/lib/api_schema/response/compute/v2_26/servers.py b/tempest/lib/api_schema/response/compute/v2_26/servers.py
index 5c35eab..8e62dc3 100644
--- a/tempest/lib/api_schema/response/compute/v2_26/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_26/servers.py
@@ -15,7 +15,6 @@
import copy
-from tempest.lib.api_schema.response.compute.v2_1 import servers as servers21
from tempest.lib.api_schema.response.compute.v2_19 import servers as servers219
# The 2.26 microversion changes the server GET and (detailed) LIST responses to
@@ -62,10 +61,6 @@
rebuild_server_with_admin_pass['response_body']['properties']['server'][
'required'].append('tags')
-# list response schema wasn't changed for v2.26 so use v2.1
-
-list_servers = copy.deepcopy(servers21.list_servers)
-
list_tags = {
'status_code': [200],
'response_body': {
@@ -98,3 +93,11 @@
}
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)
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/lib/api_schema/response/compute/v2_28/__init__.py
similarity index 100%
rename from tempest/tests/lib/services/volume/v2/__init__.py
rename to tempest/lib/api_schema/response/compute/v2_28/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_28/hypervisors.py b/tempest/lib/api_schema/response/compute/v2_28/hypervisors.py
new file mode 100644
index 0000000..8ea9ff8
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_28/hypervisors.py
@@ -0,0 +1,40 @@
+# 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)
diff --git a/tempest/lib/api_schema/response/compute/v2_3/servers.py b/tempest/lib/api_schema/response/compute/v2_3/servers.py
index f24103e..18fb352 100644
--- a/tempest/lib/api_schema/response/compute/v2_3/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_3/servers.py
@@ -128,7 +128,7 @@
'^[a-zA-Z0-9-_.]+$']['items']['properties'].update({
'OS-EXT-IPS:type': {'type': 'string'},
'OS-EXT-IPS-MAC:mac_addr': parameter_types.mac_address})
-# NOTE(gmann)dd: Update OS-EXT-IPS:type and OS-EXT-IPS-MAC:mac_addr
+# 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'.
@@ -163,4 +163,13 @@
}
}
+# 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)
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/lib/api_schema/response/compute/v2_36/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v2/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_36/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_36/limits.py b/tempest/lib/api_schema/response/compute/v2_36/limits.py
new file mode 100644
index 0000000..8e94690
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_36/limits.py
@@ -0,0 +1,35 @@
+# 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)
diff --git a/tempest/lib/api_schema/response/compute/v2_36/quotas.py b/tempest/lib/api_schema/response/compute/v2_36/quotas.py
new file mode 100644
index 0000000..f191ed1
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_36/quotas.py
@@ -0,0 +1,54 @@
+# 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)
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/lib/api_schema/response/compute/v2_39/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v2/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_39/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_39/limits.py b/tempest/lib/api_schema/response/compute/v2_39/limits.py
new file mode 100644
index 0000000..3df6616
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_39/limits.py
@@ -0,0 +1,29 @@
+# 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')
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/lib/api_schema/response/compute/v2_41/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v2/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_41/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_41/aggregates.py b/tempest/lib/api_schema/response/compute/v2_41/aggregates.py
new file mode 100644
index 0000000..036bd83
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_41/aggregates.py
@@ -0,0 +1,54 @@
+# 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)
diff --git a/tempest/lib/api_schema/response/compute/v2_47/servers.py b/tempest/lib/api_schema/response/compute/v2_47/servers.py
index 935be70..0fbacd3 100644
--- a/tempest/lib/api_schema/response/compute/v2_47/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_47/servers.py
@@ -26,7 +26,7 @@
'extra_specs': {
'type': 'object',
'patternProperties': {
- '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
+ r'^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
}
}
},
@@ -53,3 +53,16 @@
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)
diff --git a/tempest/lib/api_schema/response/compute/v2_48/servers.py b/tempest/lib/api_schema/response/compute/v2_48/servers.py
index 5904758..84b5a2a 100644
--- a/tempest/lib/api_schema/response/compute/v2_48/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_48/servers.py
@@ -112,4 +112,20 @@
}
}
+# 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)
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/lib/api_schema/response/compute/v2_53/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v2/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_53/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_53/services.py b/tempest/lib/api_schema/response/compute/v2_53/services.py
new file mode 100644
index 0000000..97b0c72
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_53/services.py
@@ -0,0 +1,71 @@
+# 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)
diff --git a/tempest/lib/api_schema/response/compute/v2_54/servers.py b/tempest/lib/api_schema/response/compute/v2_54/servers.py
index c084696..099e1b8 100644
--- a/tempest/lib/api_schema/response/compute/v2_54/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_54/servers.py
@@ -12,7 +12,7 @@
import copy
-from tempest.lib.api_schema.response.compute.v2_47 import servers as servers247
+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
@@ -26,24 +26,32 @@
]
}
-rebuild_server = copy.deepcopy(servers247.rebuild_server)
+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(
- servers247.rebuild_server_with_admin_pass)
+ 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')
-# ****** Schemas unchanged in microversion 2.54 since microversion 2.47 ***
-
# 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.
-get_server = copy.deepcopy(servers247.get_server)
-list_servers_detail = copy.deepcopy(servers247.list_servers_detail)
-update_server = copy.deepcopy(servers247.update_server)
+# ****** 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)
diff --git a/tempest/lib/api_schema/response/compute/v2_55/flavors.py b/tempest/lib/api_schema/response/compute/v2_55/flavors.py
index 823190a..554f43b 100644
--- a/tempest/lib/api_schema/response/compute/v2_55/flavors.py
+++ b/tempest/lib/api_schema/response/compute/v2_55/flavors.py
@@ -11,7 +11,9 @@
# WARRANTIES 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
@@ -110,3 +112,9 @@
'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)
diff --git a/tempest/lib/api_schema/response/compute/v2_57/limits.py b/tempest/lib/api_schema/response/compute/v2_57/limits.py
new file mode 100644
index 0000000..dcb8b3d
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_57/limits.py
@@ -0,0 +1,30 @@
+# 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)
diff --git a/tempest/lib/api_schema/response/compute/v2_57/quotas.py b/tempest/lib/api_schema/response/compute/v2_57/quotas.py
new file mode 100644
index 0000000..4664a1a
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_57/quotas.py
@@ -0,0 +1,53 @@
+# 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)
diff --git a/tempest/lib/api_schema/response/compute/v2_57/servers.py b/tempest/lib/api_schema/response/compute/v2_57/servers.py
index ed1ca7d..0099a2b 100644
--- a/tempest/lib/api_schema/response/compute/v2_57/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_57/servers.py
@@ -43,11 +43,19 @@
rebuild_server_with_admin_pass['response_body']['properties']['server'][
'required'].append('user_data')
-# ****** Schemas unchanged in microversion 2.57 since microversion 2.54 ***
-
# NOTE(gmann): Below are the unchanged schema in this microversion. We need
-# to keeo this schema in this file to have the generic way to select the
+# 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)
diff --git a/tempest/lib/api_schema/response/compute/v2_6/servers.py b/tempest/lib/api_schema/response/compute/v2_6/servers.py
index 29b3e86..d5774de 100644
--- a/tempest/lib/api_schema/response/compute/v2_6/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_6/servers.py
@@ -16,9 +16,18 @@
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)
# NOTE: The consolidated remote console API got introduced with v2.6
# with bp/consolidate-console-api. See Nova commit 578bafeda
@@ -31,7 +40,7 @@
'type': 'object',
'properties': {
'protocol': {'enum': ['vnc', 'rdp', 'serial', 'spice']},
- 'type': {'enum': ['novnc', 'xpvnc', 'rdp-html5',
+ 'type': {'enum': ['novnc', 'xvpvnc', 'rdp-html5',
'spice-html5', 'serial']},
'url': {
'type': 'string',
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/lib/api_schema/response/compute/v2_61/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v2/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_61/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_61/flavors.py b/tempest/lib/api_schema/response/compute/v2_61/flavors.py
new file mode 100644
index 0000000..5119466
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_61/flavors.py
@@ -0,0 +1,106 @@
+# 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)
diff --git a/tempest/lib/api_schema/response/compute/v2_63/servers.py b/tempest/lib/api_schema/response/compute/v2_63/servers.py
index 5cdaf54..3c3d41c 100644
--- a/tempest/lib/api_schema/response/compute/v2_63/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_63/servers.py
@@ -12,8 +12,6 @@
import copy
-from tempest.lib.api_schema.response.compute.v2_26 import servers as servers226
-from tempest.lib.api_schema.response.compute.v2_54 import servers as servers254
from tempest.lib.api_schema.response.compute.v2_57 import servers as servers257
# Nova microversion 2.63 adds 'trusted_image_certificates' (a list of
@@ -30,10 +28,8 @@
'minLength': 1
}
}
-# list response schema wasn't changed for v2.63 so use v2.26
-list_servers = copy.deepcopy(servers226.list_servers)
-list_servers_detail = copy.deepcopy(servers254.list_servers_detail)
+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'][
@@ -52,14 +48,28 @@
rebuild_server_with_admin_pass['response_body']['properties']['server'][
'required'].append('trusted_image_certificates')
-update_server = copy.deepcopy(servers254.update_server)
+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(servers254.get_server)
+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)
diff --git a/tempest/tests/lib/services/volume/v2/__init__.py b/tempest/lib/api_schema/response/compute/v2_8/__init__.py
similarity index 100%
copy from tempest/tests/lib/services/volume/v2/__init__.py
copy to tempest/lib/api_schema/response/compute/v2_8/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_8/servers.py b/tempest/lib/api_schema/response/compute/v2_8/servers.py
new file mode 100644
index 0000000..df7847f
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_8/servers.py
@@ -0,0 +1,37 @@
+# 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)
diff --git a/tempest/lib/api_schema/response/compute/v2_9/servers.py b/tempest/lib/api_schema/response/compute/v2_9/servers.py
index 7df02d5..55f8e75 100644
--- a/tempest/lib/api_schema/response/compute/v2_9/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_9/servers.py
@@ -14,10 +14,7 @@
import copy
-from tempest.lib.api_schema.response.compute.v2_1 import servers as servers_21
-from tempest.lib.api_schema.response.compute.v2_6 import servers
-
-list_servers = copy.deepcopy(servers.list_servers)
+from tempest.lib.api_schema.response.compute.v2_8 import servers
get_server = copy.deepcopy(servers.get_server)
get_server['response_body']['properties']['server'][
@@ -31,21 +28,29 @@
list_servers_detail['response_body']['properties']['servers']['items'][
'required'].append('locked')
-update_server = copy.deepcopy(servers_21.update_server)
+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_21.rebuild_server)
+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_21.rebuild_server_with_admin_pass)
+ 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)
diff --git a/tempest/lib/api_schema/response/volume/qos.py b/tempest/lib/api_schema/response/volume/qos.py
new file mode 100644
index 0000000..d1b3910
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/qos.py
@@ -0,0 +1,123 @@
+# 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]}
diff --git a/tempest/lib/cli/base.py b/tempest/lib/cli/base.py
index 3fb56ec..d8c776b 100644
--- a/tempest/lib/cli/base.py
+++ b/tempest/lib/cli/base.py
@@ -269,7 +269,7 @@
:param merge_stderr: if True the stderr buffer is merged into stdout
:type merge_stderr: boolean
"""
- flags += ' --endpoint-type %s' % endpoint_type
+ flags += ' --os-endpoint-type %s' % endpoint_type
return self.cmd_with_auth(
'cinder', action, flags, params, fail_ok, merge_stderr)
diff --git a/tempest/lib/cli/output_parser.py b/tempest/lib/cli/output_parser.py
index a7d5e49..45d41c7 100644
--- a/tempest/lib/cli/output_parser.py
+++ b/tempest/lib/cli/output_parser.py
@@ -25,7 +25,7 @@
LOG = logging.getLogger(__name__)
-delimiter_line = re.compile('^\+\-[\+\-]+\-\+$')
+delimiter_line = re.compile(r'^\+\-[\+\-]+\-\+$')
def details_multiple(output_lines, with_label=False):
diff --git a/tempest/lib/cmd/check_uuid.py b/tempest/lib/cmd/check_uuid.py
index d1f0888..82fcd0b 100755
--- a/tempest/lib/cmd/check_uuid.py
+++ b/tempest/lib/cmd/check_uuid.py
@@ -358,5 +358,6 @@
"Run 'tox -v -e uuidgen' to automatically fix tests with\n"
"missing @decorators.idempotent_id decorators.")
+
if __name__ == '__main__':
run()
diff --git a/tempest/lib/common/http.py b/tempest/lib/common/http.py
index 738c37f..8c1a802 100644
--- a/tempest/lib/common/http.py
+++ b/tempest/lib/common/http.py
@@ -19,7 +19,8 @@
class ClosingProxyHttp(urllib3.ProxyManager):
def __init__(self, proxy_url, disable_ssl_certificate_validation=False,
- ca_certs=None, timeout=None):
+ ca_certs=None, timeout=None, follow_redirects=True):
+ self.follow_redirects = follow_redirects
kwargs = {}
if disable_ssl_certificate_validation:
@@ -50,9 +51,14 @@
new_headers = dict(original_headers, connection='close')
new_kwargs = dict(kwargs, headers=new_headers)
- # Follow up to 5 redirections. Don't raise an exception if
- # it's exceeded but return the HTTP 3XX response instead.
- retry = urllib3.util.Retry(raise_on_redirect=False, redirect=5)
+ 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
@@ -60,7 +66,8 @@
class ClosingHttp(urllib3.poolmanager.PoolManager):
def __init__(self, disable_ssl_certificate_validation=False,
- ca_certs=None, timeout=None):
+ ca_certs=None, timeout=None, follow_redirects=True):
+ self.follow_redirects = follow_redirects
kwargs = {}
if disable_ssl_certificate_validation:
@@ -93,9 +100,14 @@
new_headers = dict(original_headers, connection='close')
new_kwargs = dict(kwargs, headers=new_headers)
- # Follow up to 5 redirections. Don't raise an exception if
- # it's exceeded but return the HTTP 3XX response instead.
- retry = urllib3.util.Retry(raise_on_redirect=False, redirect=5)
+ 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
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index 22276d4..ec46caf 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -70,6 +70,7 @@
: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
@@ -82,7 +83,7 @@
build_interval=1, build_timeout=60,
disable_ssl_certificate_validation=False, ca_certs=None,
trace_requests='', name=None, http_timeout=None,
- proxy_url=None):
+ proxy_url=None, follow_redirects=True):
self.auth_provider = auth_provider
self.service = service
self.region = region
@@ -107,11 +108,11 @@
self.http_obj = http.ClosingProxyHttp(
proxy_url,
disable_ssl_certificate_validation=dscv, ca_certs=ca_certs,
- timeout=http_timeout)
+ 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)
+ 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
@@ -416,6 +417,8 @@
resp_body=None, extra=None):
if 'X-Auth-Token' in req_headers:
req_headers['X-Auth-Token'] = '<omitted>'
+ if 'X-Subject-Token' in req_headers:
+ req_headers['X-Subject-Token'] = '<omitted>'
# A shallow copy is sufficient
resp_log = resp.copy()
if 'x-subject-token' in resp_log:
@@ -568,8 +571,10 @@
:param str url: Full url to send the request
:param str method: The HTTP verb to use for the request
- :param str headers: Headers to use for the request if none are specifed
- the headers
+ :param 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
@@ -603,8 +608,8 @@
returned by the get_headers() method are to
be used but additional headers are needed in
the request pass them in as a dict.
- :param dict headers: Headers to use for the request if none are
- specifed the headers returned from the
+ :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
@@ -621,10 +626,13 @@
:raises Gone: If a 410 response code is received
:raises Conflict: If a 409 response code is received
:raises PreconditionFailed: If a 412 response code is received
- :raises OverLimit: If a 413 response code is received and over_limit is
- not in the response body
+ :raises 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
- over_limit is in the response body
+ 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
diff --git a/tempest/lib/decorators.py b/tempest/lib/decorators.py
index e99dd24..b399aa0 100644
--- a/tempest/lib/decorators.py
+++ b/tempest/lib/decorators.py
@@ -19,39 +19,83 @@
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
- @param bug: bug number causing the test to skip
- @param condition: optional condition to be True for the skip to have place
+ ``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:
- if not kwargs['bug'].isdigit():
- raise ValueError('bug must be a valid bug number')
- msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
+ 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):
- """A decorator useful to know solutions from launchpad bug reports
+def related_bug(bug, status_code=None, bug_type='launchpad'):
+ """A decorator useful to know solutions from launchpad/storyboard reports
- @param bug: The launchpad bug number causing the test
- @param status_code: The status code related to the bug report
+ :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)
@@ -61,9 +105,10 @@
except Exception as exc:
exc_status_code = getattr(exc, 'status_code', None)
if status_code is None or status_code == exc_status_code:
- LOG.error('Hints: This test was made for the bug %s. '
- 'The failure could be related to '
- 'https://launchpad.net/bugs/%s', bug, bug)
+ 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
diff --git a/tempest/lib/services/clients.py b/tempest/lib/services/clients.py
index 8918a8c..833cfd6 100644
--- a/tempest/lib/services/clients.py
+++ b/tempest/lib/services/clients.py
@@ -331,7 +331,7 @@
self.region = region
# Check if passed or default credentials are valid
if not self.credentials.is_valid():
- raise exceptions.InvalidCredentials()
+ 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
@@ -340,7 +340,9 @@
isinstance(self.credentials, auth.IDENTITY_VERSION[k][0])]
# Zero matches or more than one are both not valid.
if len(identity) != 1:
- raise exceptions.InvalidCredentials()
+ 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
diff --git a/tempest/lib/services/compute/aggregates_client.py b/tempest/lib/services/compute/aggregates_client.py
index 713d7a3..57f5e4e 100644
--- a/tempest/lib/services/compute/aggregates_client.py
+++ b/tempest/lib/services/compute/aggregates_client.py
@@ -15,7 +15,10 @@
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_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
@@ -23,10 +26,15 @@
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)
@@ -34,6 +42,7 @@
"""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)
@@ -48,6 +57,7 @@
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)
@@ -62,12 +72,14 @@
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)
@@ -94,6 +106,7 @@
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)
@@ -108,6 +121,7 @@
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)
@@ -122,5 +136,6 @@
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)
diff --git a/tempest/lib/services/compute/flavors_client.py b/tempest/lib/services/compute/flavors_client.py
index 4923d7e..2fad0a4 100644
--- a/tempest/lib/services/compute/flavors_client.py
+++ b/tempest/lib/services/compute/flavors_client.py
@@ -23,6 +23,8 @@
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
@@ -31,7 +33,8 @@
schema_versions_info = [
{'min': None, 'max': '2.54', 'schema': schema},
- {'min': '2.55', 'max': None, 'schema': schemav255}]
+ {'min': '2.55', 'max': '2.60', 'schema': schemav255},
+ {'min': '2.61', 'max': None, 'schema': schemav261}]
def list_flavors(self, detail=False, **params):
"""Lists flavors.
@@ -202,7 +205,8 @@
"""
resp, body = self.delete('flavors/%s/os-extra_specs/%s' %
(flavor_id, key))
- self.validate_response(schema.unset_flavor_extra_specs, resp, body)
+ 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):
diff --git a/tempest/lib/services/compute/hypervisor_client.py b/tempest/lib/services/compute/hypervisor_client.py
index 23c304e..1cbfcc3 100644
--- a/tempest/lib/services/compute/hypervisor_client.py
+++ b/tempest/lib/services/compute/hypervisor_client.py
@@ -15,16 +15,24 @@
from oslo_serialization import jsonutils as json
-from tempest.lib.api_schema.response.compute.v2_1 import hypervisors as schema
+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'
@@ -39,6 +47,7 @@
"""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)
@@ -46,6 +55,7 @@
"""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)
@@ -53,6 +63,7 @@
"""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)
@@ -60,6 +71,7 @@
"""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)
@@ -69,5 +81,6 @@
"""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)
diff --git a/tempest/lib/services/compute/limits_client.py b/tempest/lib/services/compute/limits_client.py
index efe9889..9af80c4 100644
--- a/tempest/lib/services/compute/limits_client.py
+++ b/tempest/lib/services/compute/limits_client.py
@@ -15,15 +15,25 @@
from oslo_serialization import jsonutils as json
-from tempest.lib.api_schema.response.compute.v2_1 import limits as schema
+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)
diff --git a/tempest/lib/services/compute/quotas_client.py b/tempest/lib/services/compute/quotas_client.py
index 12df895..99c8d0f 100644
--- a/tempest/lib/services/compute/quotas_client.py
+++ b/tempest/lib/services/compute/quotas_client.py
@@ -17,12 +17,19 @@
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.
@@ -42,6 +49,7 @@
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:
@@ -57,6 +65,7 @@
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)
@@ -78,6 +87,7 @@
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)
@@ -87,5 +97,6 @@
https://developer.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)
diff --git a/tempest/lib/services/compute/server_groups_client.py b/tempest/lib/services/compute/server_groups_client.py
index 03cd645..0d440d5 100644
--- a/tempest/lib/services/compute/server_groups_client.py
+++ b/tempest/lib/services/compute/server_groups_client.py
@@ -16,8 +16,10 @@
from oslo_serialization import jsonutils as json
-from tempest.lib.api_schema.response.compute.v2_1 import servers as schema
-from tempest.lib.api_schema.response.compute.v2_13 import servers as schemav213
+from tempest.lib.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
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index 0314356..9eed4b3 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -33,6 +33,7 @@
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_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
@@ -44,7 +45,8 @@
schema_versions_info = [
{'min': None, 'max': '2.2', 'schema': schema},
{'min': '2.3', 'max': '2.5', 'schema': schemav23},
- {'min': '2.6', 'max': '2.8', 'schema': schemav26},
+ {'min': '2.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},
diff --git a/tempest/lib/services/compute/services_client.py b/tempest/lib/services/compute/services_client.py
index b046c35..d52de3a 100644
--- a/tempest/lib/services/compute/services_client.py
+++ b/tempest/lib/services/compute/services_client.py
@@ -20,6 +20,8 @@
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
@@ -28,7 +30,8 @@
schema_versions_info = [
{'min': None, 'max': '2.10', 'schema': schema},
- {'min': '2.11', 'max': None, 'schema': schemav211}]
+ {'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.
@@ -47,9 +50,30 @@
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://developer.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://developer.openstack.org/api-ref/compute/#enable-scheduling-for-a-compute-service
@@ -63,6 +87,8 @@
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://developer.openstack.org/api-ref/compute/#disable-scheduling-for-a-compute-service
@@ -76,6 +102,8 @@
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://developer.openstack.org/api-ref/compute/#disable-scheduling-for-a-compute-service-and-log-disabled-reason
@@ -89,6 +117,8 @@
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://developer.openstack.org/api-ref/compute/#update-forced-down
diff --git a/tempest/lib/services/image/v2/images_client.py b/tempest/lib/services/image/v2/images_client.py
index ed6df47..3c38dba 100644
--- a/tempest/lib/services/image/v2/images_client.py
+++ b/tempest/lib/services/image/v2/images_client.py
@@ -32,7 +32,7 @@
For a full list of available parameters, please refer to the official
API reference:
- http://developer.openstack.org/api-ref/image/v2/index.html#update-an-image
+ https://developer.openstack.org/api-ref/image/v2/#update-image
"""
data = json.dumps(patch)
headers = {"Content-Type": "application/openstack-images-v2.0"
@@ -47,7 +47,7 @@
For a full list of available parameters, please refer to the official
API reference:
- http://developer.openstack.org/api-ref/image/v2/index.html#create-an-image
+ https://developer.openstack.org/api-ref/image/v2/#create-image
"""
data = json.dumps(kwargs)
resp, body = self.post('images', data)
@@ -84,7 +84,7 @@
For a full list of available parameters, please refer to the official
API reference:
- http://developer.openstack.org/api-ref/image/v2/#delete-an-image
+ https://developer.openstack.org/api-ref/image/v2/#delete-image
"""
url = 'images/%s' % image_id
resp, _ = self.delete(url)
@@ -96,7 +96,7 @@
For a full list of available parameters, please refer to the official
API reference:
- http://developer.openstack.org/api-ref/image/v2/#show-images
+ https://developer.openstack.org/api-ref/image/v2/#list-images
"""
url = 'images'
@@ -113,7 +113,7 @@
For a full list of available parameters, please refer to the official
API reference:
- http://developer.openstack.org/api-ref/image/v2/#show-image-details
+ https://developer.openstack.org/api-ref/image/v2/#show-image
"""
url = 'images/%s' % image_id
resp, body = self.get(url)
diff --git a/tempest/lib/services/network/agents_client.py b/tempest/lib/services/network/agents_client.py
index a0f832e..9fa4672 100644
--- a/tempest/lib/services/network/agents_client.py
+++ b/tempest/lib/services/network/agents_client.py
@@ -37,6 +37,16 @@
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://developer.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.
@@ -87,9 +97,11 @@
return self.delete_resource(uri)
def add_dhcp_agent_to_network(self, agent_id, **kwargs):
- # TODO(piyush): Current api-site doesn't contain this API description.
- # After fixing the api-site, we need to fix here also for putting the
- # link to api-site.
- # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1526212
+ """Schedule a network to a DHCP agent.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.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)
diff --git a/tempest/lib/services/network/versions_client.py b/tempest/lib/services/network/versions_client.py
index f87fe87..807f416 100644
--- a/tempest/lib/services/network/versions_client.py
+++ b/tempest/lib/services/network/versions_client.py
@@ -12,32 +12,36 @@
# License for the specific language governing permissions and limitations
# under the License.
-import time
-
from oslo_serialization import jsonutils as json
+from tempest.lib.common import rest_client
from tempest.lib.services.network import base
class NetworkVersionsClient(base.BaseNetworkClient):
def list_versions(self):
- """Do a GET / to fetch available API version information."""
+ """Do a GET / to fetch available API version information.
- version_url = self._get_base_version_url()
+ For more information, please refer to the official API reference:
+ https://developer.openstack.org/api-ref/network/v2/index.html#list-api-versions
+ """
- # Note: we do a raw_request here because we want to use
+ # Note: we do a self.get('/') here because we want to use
# an unversioned URL, not "v2/$project_id/".
- # Since raw_request doesn't log anything, we do that too.
- start = time.time()
- self._log_request_start('GET', version_url)
- response, body = self.raw_request(version_url, 'GET')
- self._error_checker(response, body)
- end = time.time()
- self._log_request('GET', version_url, response,
- secs=(end - start), resp_body=body)
-
- self.response_checker('GET', response, body)
- self.expected_success(200, response.status)
+ resp, body = self.get('/')
body = json.loads(body)
- return body
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_version(self, version):
+ """Do a GET /<version> to fetch available resources.
+
+ For more information, please refer to the official API reference:
+ https://developer.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)
diff --git a/tempest/lib/services/volume/v3/backups_client.py b/tempest/lib/services/volume/v3/backups_client.py
index 10538b0..f2d2d21 100644
--- a/tempest/lib/services/volume/v3/backups_client.py
+++ b/tempest/lib/services/volume/v3/backups_client.py
@@ -23,7 +23,6 @@
class BackupsClient(base_client.BaseClient):
"""Volume V3 Backups client"""
- api_version = "v3"
def create_backup(self, **kwargs):
"""Creates a backup of volume.
diff --git a/tempest/lib/services/volume/v3/base_client.py b/tempest/lib/services/volume/v3/base_client.py
index e78380b..617da2e 100644
--- a/tempest/lib/services/volume/v3/base_client.py
+++ b/tempest/lib/services/volume/v3/base_client.py
@@ -20,4 +20,3 @@
BaseClient = moves.moved_class(base_client.BaseClient, 'BaseClient', __name__,
version="Pike", removal_version='?')
-BaseClient.api_version = 'v3'
diff --git a/tempest/lib/services/volume/v3/group_snapshots_client.py b/tempest/lib/services/volume/v3/group_snapshots_client.py
index 6e53e3e..16412d3 100644
--- a/tempest/lib/services/volume/v3/group_snapshots_client.py
+++ b/tempest/lib/services/volume/v3/group_snapshots_client.py
@@ -23,7 +23,6 @@
class GroupSnapshotsClient(base_client.BaseClient):
"""Client class to send CRUD Volume Group Snapshot API requests"""
- api_version = 'v3'
def create_group_snapshot(self, **kwargs):
"""Creates a group snapshot.
diff --git a/tempest/lib/services/volume/v3/group_types_client.py b/tempest/lib/services/volume/v3/group_types_client.py
index ecbcba1..1ccb9f8 100644
--- a/tempest/lib/services/volume/v3/group_types_client.py
+++ b/tempest/lib/services/volume/v3/group_types_client.py
@@ -22,7 +22,6 @@
class GroupTypesClient(base_client.BaseClient):
"""Client class to send CRUD Volume V3 Group Types API requests"""
- api_version = 'v3'
@property
def resource_type(self):
diff --git a/tempest/lib/services/volume/v3/groups_client.py b/tempest/lib/services/volume/v3/groups_client.py
index e2e477d..3cf1e6a 100644
--- a/tempest/lib/services/volume/v3/groups_client.py
+++ b/tempest/lib/services/volume/v3/groups_client.py
@@ -23,7 +23,6 @@
class GroupsClient(base_client.BaseClient):
"""Client class to send CRUD Volume Group API requests"""
- api_version = 'v3'
def create_group(self, **kwargs):
"""Creates a group.
diff --git a/tempest/lib/services/volume/v3/messages_client.py b/tempest/lib/services/volume/v3/messages_client.py
index 0127271..47538cd 100644
--- a/tempest/lib/services/volume/v3/messages_client.py
+++ b/tempest/lib/services/volume/v3/messages_client.py
@@ -22,7 +22,6 @@
class MessagesClient(base_client.BaseClient):
"""Client class to send user messages API requests."""
- api_version = 'v3'
def show_message(self, message_id):
"""Show details for a single message."""
diff --git a/tempest/lib/services/volume/v3/qos_client.py b/tempest/lib/services/volume/v3/qos_client.py
index 8f4d37f..5205590 100644
--- a/tempest/lib/services/volume/v3/qos_client.py
+++ b/tempest/lib/services/volume/v3/qos_client.py
@@ -14,6 +14,7 @@
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
@@ -45,15 +46,15 @@
"""
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)
+ 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.expected_success(202, resp.status)
+ self.validate_response(schema.delete_qos, resp, body)
return rest_client.ResponseBody(resp, body)
def list_qos(self):
@@ -61,7 +62,7 @@
url = 'qos-specs'
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.list_qos, resp, body)
return rest_client.ResponseBody(resp, body)
def show_qos(self, qos_id):
@@ -69,7 +70,7 @@
url = "qos-specs/%s" % qos_id
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_qos, resp, body)
return rest_client.ResponseBody(resp, body)
def set_qos_key(self, qos_id, **kwargs):
@@ -82,7 +83,7 @@
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)
+ self.validate_response(schema.set_qos_key, resp, body)
return rest_client.ResponseBody(resp, body)
def unset_qos_key(self, qos_id, keys):
@@ -96,7 +97,7 @@
"""
put_body = json.dumps({'keys': keys})
resp, body = self.put('qos-specs/%s/delete_keys' % qos_id, put_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.unset_qos_key, resp, body)
return rest_client.ResponseBody(resp, body)
def associate_qos(self, qos_id, vol_type_id):
@@ -104,7 +105,7 @@
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)
+ self.validate_response(schema.associate_qos, resp, body)
return rest_client.ResponseBody(resp, body)
def show_association_qos(self, qos_id):
@@ -112,7 +113,7 @@
url = "qos-specs/%s/associations" % qos_id
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_association_qos, resp, body)
return rest_client.ResponseBody(resp, body)
def disassociate_qos(self, qos_id, vol_type_id):
@@ -120,12 +121,12 @@
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)
+ 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.expected_success(202, resp.status)
+ self.validate_response(schema.disassociate_all_qos, resp, body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/snapshots_client.py b/tempest/lib/services/volume/v3/snapshots_client.py
index f79bcd8..cae65b2 100644
--- a/tempest/lib/services/volume/v3/snapshots_client.py
+++ b/tempest/lib/services/volume/v3/snapshots_client.py
@@ -22,8 +22,6 @@
class SnapshotsClient(rest_client.RestClient):
"""Client class to send CRUD Volume Snapshot V3 API requests."""
- api_version = "v3"
- create_resp = 202
def list_snapshots(self, detail=False, **params):
"""List all the snapshot.
@@ -67,7 +65,7 @@
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)
+ self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
def update_snapshot(self, snapshot_id, **kwargs):
@@ -114,12 +112,12 @@
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
+ """Update status of a snapshot.
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.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)
diff --git a/tempest/lib/services/volume/v3/versions_client.py b/tempest/lib/services/volume/v3/versions_client.py
index 5702f95..57629bd 100644
--- a/tempest/lib/services/volume/v3/versions_client.py
+++ b/tempest/lib/services/volume/v3/versions_client.py
@@ -22,7 +22,6 @@
class VersionsClient(base_client.BaseClient):
- api_version = 'v3'
def list_versions(self):
"""List API versions
diff --git a/tempest/lib/services/volume/v3/volumes_client.py b/tempest/lib/services/volume/v3/volumes_client.py
index a1185c4..11c5767 100644
--- a/tempest/lib/services/volume/v3/volumes_client.py
+++ b/tempest/lib/services/volume/v3/volumes_client.py
@@ -24,7 +24,6 @@
class VolumesClient(base_client.BaseClient):
"""Client class to send CRUD Volume V3 API requests"""
- api_version = "v3"
def _prepare_params(self, params):
"""Prepares params for use in get or _ext_get methods.
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 9965fe5..00d45cd 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -94,6 +94,10 @@
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,
@@ -221,8 +225,12 @@
if size is None:
size = CONF.volume.volume_size
if imageRef:
- image = self.compute_images_client.show_image(imageRef)['image']
- min_disk = image.get('minDisk')
+ 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")
@@ -297,7 +305,7 @@
def create_volume_type(self, client=None, name=None, backend_name=None):
if not client:
- client = self.os_admin.volume_types_v2_client
+ client = self.os_admin.volume_types_client_latest
randomized_name = name or data_utils.rand_name(
'volume-type-' + self.__class__.__name__)
@@ -443,7 +451,9 @@
disk_format=img_disk_format,
properties=img_properties)
except IOError:
- LOG.debug("A qcow2 image was not found. Try to get a uec image.")
+ 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}
@@ -541,7 +551,7 @@
volume['id'], 'available')
def ping_ip_address(self, ip_address, should_succeed=True,
- ping_timeout=None, mtu=None):
+ ping_timeout=None, mtu=None, server=None):
timeout = ping_timeout or CONF.validation.ping_timeout
cmd = ['ping', '-c1', '-w1']
@@ -575,12 +585,16 @@
'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
@@ -590,43 +604,36 @@
: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),
+ mtu=mtu, server=server),
msg=msg)
if should_connect:
# no need to check ssh for negative connectivity
- self.get_remote_client(ip_address, username, private_key)
-
- def check_public_network_connectivity(self, ip_address, username,
- private_key, should_connect=True,
- msg=None, servers=None, mtu=None):
- # The target login is assumed to have been configured for
- # key-based authentication by cloud-init.
- LOG.debug('checking network connections to IP %s with user: %s',
- ip_address, username)
- try:
- self.check_vm_connectivity(ip_address,
- username,
- private_key,
- should_connect=should_connect,
- mtu=mtu)
- except Exception:
- ex_msg = 'Public network connectivity check failed'
- if msg:
- ex_msg += ": " + msg
- LOG.exception(ex_msg)
- self._log_console_output(servers)
- raise
+ 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"""
@@ -643,9 +650,10 @@
return floating_ip
def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
- private_key=None):
+ private_key=None, server=None):
ssh_client = self.get_remote_client(ip_address,
- private_key=private_key)
+ 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,
@@ -659,9 +667,10 @@
return timestamp
def get_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
- private_key=None):
+ private_key=None, server=None):
ssh_client = self.get_remote_client(ip_address,
- private_key=private_key)
+ 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'
@@ -699,6 +708,11 @@
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.
@@ -807,8 +821,13 @@
return subnet
def _get_server_port_id_and_ip4(self, server, ip_addr=None):
- ports = self.os_admin.ports_client.list_ports(
- device_id=server['id'], fixed_ip=ip_addr)['ports']
+ 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
@@ -1198,9 +1217,9 @@
@classmethod
def setup_clients(cls):
super(EncryptionScenarioTest, cls).setup_clients()
- cls.admin_volume_types_client = cls.os_admin.volume_types_v2_client
+ cls.admin_volume_types_client = cls.os_admin.volume_types_client_latest
cls.admin_encryption_types_client =\
- cls.os_admin.encryption_types_v2_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,
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index 87ce951..e94ce3d 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -90,9 +90,10 @@
floating_ip_addr = floating_ip['floating_ip_address']
# Check FloatingIP status before checking the connectivity
self.check_floating_ip_status(floating_ip, 'ACTIVE')
- self.check_public_network_connectivity(floating_ip_addr, username,
- private_key, should_connect,
- servers=[server])
+ 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,
@@ -101,10 +102,6 @@
'ACTIVE')
self._check_network_connectivity(server, keypair, floating_ip)
- def _get_host_for_server(self, server_id):
- body = self.admin_servers_client.show_server(server_id)['server']
- return body['OS-EXT-SRV-ATTR:host']
-
@decorators.idempotent_id('61f1aa9a-1573-410e-9054-afa557cab021')
@decorators.attr(type='slow')
@utils.services('compute', 'network')
@@ -196,7 +193,14 @@
'VERIFY_RESIZE')
self.servers_client.confirm_resize_server(server['id'])
server = self.servers_client.show_server(server['id'])['server']
- self.assertEqual(resize_flavor, server['flavor']['id'])
+ # 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']
+ for key in ['original_name', 'ram', 'vcpus', 'disk']:
+ self.assertEqual(flavor[key], server['flavor'][key])
self._wait_server_status_and_check_network_connectivity(
server, keypair, floating_ip)
@@ -212,7 +216,7 @@
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'])
+ src_host = self.get_host_for_server(server['id'])
self._wait_server_status_and_check_network_connectivity(
server, keypair, floating_ip)
@@ -222,10 +226,11 @@
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'])
+ dst_host = self.get_host_for_server(server['id'])
self.assertNotEqual(src_host, dst_host)
+ @decorators.skip_because(bug='1788403')
@decorators.idempotent_id('25b188d7-0183-4b1e-a11d-15840c8e2fd6')
@testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
'Cold migration is not available.')
@@ -238,7 +243,7 @@
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'])
+ src_host = self.get_host_for_server(server['id'])
self._wait_server_status_and_check_network_connectivity(
server, keypair, floating_ip)
@@ -248,6 +253,6 @@
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'])
+ dst_host = self.get_host_for_server(server['id'])
self.assertEqual(src_host, dst_host)
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index bcd4ddb..c1132cf 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -175,7 +175,7 @@
def _get_server_key(self, server):
return self.keypairs[server['key_name']]['private_key']
- def check_public_network_connectivity(
+ 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
@@ -199,13 +199,18 @@
if should_connect:
private_key = self._get_server_key(server)
floatingip_status = 'ACTIVE'
+
# Check FloatingIP Status before initiating a connection
if should_check_floating_ip_status:
self.check_floating_ip_status(floating_ip, floatingip_status)
- # call the common method in the parent class
- super(TestNetworkBasicOps, self).check_public_network_connectivity(
- ip_address, ssh_login, private_key, should_connect, msg,
- self.servers, mtu=mtu)
+
+ 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
@@ -404,17 +409,17 @@
"""
self._setup_network_and_servers()
- self.check_public_network_connectivity(should_connect=True)
+ 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._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")
+ 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'),
@@ -425,17 +430,16 @@
"""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)
+ 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(
+ self._check_public_network_connectivity(
should_connect=True, mtu=self.network['mtu'])
@decorators.idempotent_id('1546850e-fbaa-42f5-8b5f-03d8a6a95f15')
@testtools.skipIf(CONF.network.shared_physical_network,
'Connectivity can only be tested when in a '
'multitenant network environment')
- @decorators.skip_because(bug="1610994")
@decorators.attr(type='slow')
@utils.services('compute', 'network')
def test_connectivity_between_vms_on_different_networks(self):
@@ -468,7 +472,7 @@
"""
self._setup_network_and_servers()
- self.check_public_network_connectivity(should_connect=True)
+ 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)
@@ -503,7 +507,7 @@
"""
self._setup_network_and_servers()
- self.check_public_network_connectivity(should_connect=True)
+ self._check_public_network_connectivity(should_connect=True)
self._create_new_network()
self._hotplug_server()
self._check_network_internal_connectivity(network=self.new_net)
@@ -525,19 +529,19 @@
admin_state_up attribute of router to True
"""
self._setup_network_and_servers()
- self.check_public_network_connectivity(
+ 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(
+ 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(
+ self._check_public_network_connectivity(
should_connect=True, msg="after updating "
"admin_state_up of router to True")
@@ -582,7 +586,7 @@
renew_timeout = CONF.network.build_timeout
self._setup_network_and_servers(dns_nameservers=[initial_dns_server])
- self.check_public_network_connectivity(should_connect=True)
+ self._check_public_network_connectivity(should_connect=True)
floating_ip, server = self.floating_ip_tuple
ip_address = floating_ip['floating_ip_address']
@@ -657,20 +661,20 @@
private_key=private_key,
server=server2)
- self.check_public_network_connectivity(
+ 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(
+ 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(
+ 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,
@@ -767,7 +771,7 @@
msg = "Rescheduling test does not apply to distributed routers."
raise self.skipException(msg)
- self.check_public_network_connectivity(should_connect=True)
+ self._check_public_network_connectivity(should_connect=True)
# remove resource from agents
hosting_agents = set(a["id"] for a in
@@ -784,7 +788,7 @@
'unscheduling router failed')
# verify resource is un-functional
- self.check_public_network_connectivity(
+ self._check_public_network_connectivity(
should_connect=False,
msg='after router unscheduling',
)
@@ -801,7 +805,7 @@
"target agent")
# verify resource is functional
- self.check_public_network_connectivity(
+ self._check_public_network_connectivity(
should_connect=True,
msg='After router rescheduling')
@@ -835,7 +839,7 @@
# Create server
self._setup_network_and_servers()
- self.check_public_network_connectivity(should_connect=True)
+ self._check_public_network_connectivity(should_connect=True)
self._create_new_network()
self._hotplug_server()
fip, server = self.floating_ip_tuple
diff --git a/tempest/scenario/test_network_v6.py b/tempest/scenario/test_network_v6.py
index e4e39c3..57a560c 100644
--- a/tempest/scenario/test_network_v6.py
+++ b/tempest/scenario/test_network_v6.py
@@ -12,13 +12,18 @@
# WARRANTIES OR 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):
@@ -154,8 +159,31 @@
% (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\\nIPV6INIT=yes" | '
+ 'sudo tee /etc/sysconfig/network-scripts/ifcfg-%(nic)s; '
+ 'sudo /sbin/service network restart' % {'nic': 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,
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index e39afe0..28a2d64 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -282,11 +282,8 @@
# Verify servers are on different compute nodes
if self.multi_node:
- adm_get_server = self.os_admin.servers_client.show_server
- new_host = adm_get_server(server["id"])["server"][
- "OS-EXT-SRV-ATTR:host"]
- host_list = [adm_get_server(s)["server"]["OS-EXT-SRV-ATTR:host"]
- for s in self.servers]
+ 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.")
@@ -369,7 +366,8 @@
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)
+ access_point_ssh, private_key=private_key,
+ server=tenant.access_point)
return access_point_ssh
def _test_in_tenant_block(self, tenant):
diff --git a/tempest/scenario/test_shelve_instance.py b/tempest/scenario/test_shelve_instance.py
index 68f18d1..d6b6d14 100644
--- a/tempest/scenario/test_shelve_instance.py
+++ b/tempest/scenario/test_shelve_instance.py
@@ -63,7 +63,8 @@
instance_ip = self.get_server_ip(server)
timestamp = self.create_timestamp(instance_ip,
- private_key=keypair['private_key'])
+ private_key=keypair['private_key'],
+ server=server)
# Prevent bug #1257594 from coming back
# Unshelve used to boot the instance with the original image, not
@@ -71,7 +72,8 @@
self._shelve_then_unshelve_server(server)
timestamp2 = self.get_timestamp(instance_ip,
- private_key=keypair['private_key'])
+ private_key=keypair['private_key'],
+ server=server)
self.assertEqual(timestamp, timestamp2)
@decorators.attr(type='slow')
diff --git a/tempest/scenario/test_snapshot_pattern.py b/tempest/scenario/test_snapshot_pattern.py
index b51a781..a33d4d4 100644
--- a/tempest/scenario/test_snapshot_pattern.py
+++ b/tempest/scenario/test_snapshot_pattern.py
@@ -57,7 +57,8 @@
instance_ip = self.get_server_ip(server)
timestamp = self.create_timestamp(instance_ip,
- private_key=keypair['private_key'])
+ private_key=keypair['private_key'],
+ server=server)
# snapshot the instance
snapshot_image = self.create_server_snapshot(server=server)
@@ -71,5 +72,6 @@
# 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'])
+ private_key=keypair['private_key'],
+ server=server_from_snapshot)
self.assertEqual(timestamp, timestamp2)
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index ef369d6..2782119 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -96,7 +96,8 @@
keypair['private_key'])
timestamp = self.create_timestamp(ip_for_server,
CONF.compute.volume_device_name,
- private_key=keypair['private_key'])
+ private_key=keypair['private_key'],
+ server=server)
self.nova_volume_detach(server, volume)
# snapshot the volume
@@ -126,5 +127,6 @@
# 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'])
+ private_key=keypair['private_key'],
+ server=server_from_snapshot)
self.assertEqual(timestamp, timestamp2)
diff --git a/tempest/scenario/test_volume_backup_restore.py b/tempest/scenario/test_volume_backup_restore.py
index c23b564..8a8c54e 100644
--- a/tempest/scenario/test_volume_backup_restore.py
+++ b/tempest/scenario/test_volume_backup_restore.py
@@ -14,6 +14,7 @@
# 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
@@ -56,6 +57,8 @@
# 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']
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 1564f25..810480b 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -11,6 +11,7 @@
# under the License.
from oslo_log import log as logging
+from oslo_serialization import jsonutils
import testtools
from tempest.common import utils
@@ -49,7 +50,8 @@
source_type,
keypair=None,
security_group=None,
- delete_on_termination=False):
+ delete_on_termination=False,
+ name=None):
create_kwargs = dict()
if keypair:
create_kwargs['key_name'] = keypair['name']
@@ -60,6 +62,8 @@
source_id,
source_type,
delete_on_termination=delete_on_termination))
+ if name:
+ create_kwargs['name'] = name
return self.create_server(image_id='', **create_kwargs)
@@ -67,6 +71,10 @@
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')
# Note: This test is being skipped based on 'public_network_id'.
# It is being used in create_floating_ip() method which gets called
@@ -108,7 +116,8 @@
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'])
+ private_key=keypair['private_key'],
+ server=instance_1st)
# delete instance
LOG.info("Deleting first instance: %s", instance_1st)
@@ -126,7 +135,8 @@
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'])
+ private_key=keypair['private_key'],
+ server=instance_2nd)
self.assertEqual(timestamp, timestamp2)
# snapshot a volume
@@ -150,7 +160,8 @@
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'])
+ private_key=keypair['private_key'],
+ server=server_from_snapshot)
self.assertEqual(timestamp, timestamp3)
@decorators.idempotent_id('05795fb2-b2a7-4c9f-8fac-ff25aedb1489')
@@ -199,28 +210,29 @@
def test_image_defined_boot_from_volume(self):
# create an instance from image-backed volume
volume_origin = self._create_volume_from_image()
- instance = self._boot_instance_from_resource(
+ 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)
+ 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(instance)
-
- # Delete the first server which will also delete the first image-backed
- # volume.
- self._delete_server(instance)
+ 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.
- instance = self.create_server(image_id=image['id'])
+ 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 = instance['os-extended-volumes:volumes_attached']
+ 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(
@@ -229,7 +241,7 @@
self.assertEqual(1, len(created_volume['attachments']),
"No server attachment found for volume: %s" %
created_volume)
- self.assertEqual(instance['id'],
+ self.assertEqual(instance2['id'],
created_volume['attachments'][0]['server_id'])
self.assertEqual(volume_attachments[0]['id'],
created_volume['attachments'][0]['volume_id'])
@@ -239,11 +251,30 @@
# Delete the second server which should also delete the second volume
# created from the volume snapshot.
- self._delete_server(instance)
+ 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 = jsonutils.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')
diff --git a/tempest/scenario/test_volume_migrate_attached.py b/tempest/scenario/test_volume_migrate_attached.py
index ff7996a..c54bb38 100644
--- a/tempest/scenario/test_volume_migrate_attached.py
+++ b/tempest/scenario/test_volume_migrate_attached.py
@@ -40,7 +40,7 @@
@classmethod
def setup_clients(cls):
super(TestVolumeMigrateRetypeAttached, cls).setup_clients()
- cls.admin_volumes_client = cls.os_admin.volumes_v2_client
+ cls.admin_volumes_client = cls.os_admin.volumes_client_latest
@classmethod
def skip_checks(cls):
@@ -114,7 +114,8 @@
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'])
+ 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_origin['id'],
@@ -125,5 +126,6 @@
LOG.info("Getting timestamp in postmigrated instance %s",
instance['id'])
timestamp2 = self.get_timestamp(ip_instance,
- private_key=keypair['private_key'])
+ private_key=keypair['private_key'],
+ server=instance)
self.assertEqual(timestamp, timestamp2)
diff --git a/tempest/tests/api/compute/test_base.py b/tempest/tests/api/compute/test_base.py
index 47f4ad6..1593464 100644
--- a/tempest/tests/api/compute/test_base.py
+++ b/tempest/tests/api/compute/test_base.py
@@ -131,8 +131,13 @@
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, 'active')
+ compute_images_client, image_id, status)
servers_client.show_server.assert_called_once_with(
mock.sentinel.server_id)
diff --git a/tempest/tests/cmd/test_account_generator.py b/tempest/tests/cmd/test_account_generator.py
index fd9af08..a1d3a40 100644
--- a/tempest/tests/cmd/test_account_generator.py
+++ b/tempest/tests/cmd/test_account_generator.py
@@ -208,9 +208,9 @@
resources = account_generator.generate_resources(
self.cred_provider, admin=True)
resource_types = [k for k, _ in resources]
- # all options on, expect six credentials
- self.assertEqual(6, len(resources))
- # Ensure create_user was invoked 6 times (6 distinct users)
+ # 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)
@@ -267,14 +267,14 @@
# Ordered args in [0], keyword args in [1]
accounts, f = yaml_dump_mock.call_args[0]
self.assertEqual(handle, f)
- self.assertEqual(6, len(accounts))
+ 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(3, len([x for x in accounts if 'roles' in x]))
+ 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'))
@@ -298,14 +298,14 @@
# Ordered args in [0], keyword args in [1]
accounts, f = yaml_dump_mock.call_args[0]
self.assertEqual(handle, f)
- self.assertEqual(6, len(accounts))
+ 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(3, len([x for x in accounts if 'roles' in x]))
+ 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'))
diff --git a/tempest/tests/cmd/test_cleanup_services.py b/tempest/tests/cmd/test_cleanup_services.py
new file mode 100644
index 0000000..495d127
--- /dev/null
+++ b/tempest/tests/cmd/test_cleanup_services.py
@@ -0,0 +1,563 @@
+# 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.tests import base
+from tempest.tests import fake_config
+from tempest.tests.lib import fake_credentials
+from tempest.tests.lib import fake_http
+
+
+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],
+ }
+
+ # Static list to ensure global service saved items are not deleted
+ saved_state = {"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'}
+ }
+ # 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 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 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)])
diff --git a/tempest/tests/cmd/test_run.py b/tempest/tests/cmd/test_run.py
index 6cc356e..9a6be4e 100644
--- a/tempest/tests/cmd/test_run.py
+++ b/tempest/tests/cmd/test_run.py
@@ -24,11 +24,14 @@
import six
from tempest.cmd import run
+from tempest import config
from tempest.tests import base
DEVNULL = open(os.devnull, 'wb')
atexit.register(DEVNULL.close)
+CONF = config.CONF
+
class TestTempestRun(base.TestCase):
@@ -40,24 +43,18 @@
args = mock.Mock(spec=argparse.Namespace)
setattr(args, 'smoke', False)
setattr(args, 'regex', '')
- setattr(args, 'whitelist_file', None)
- setattr(args, 'blacklist_file', None)
self.assertIsNone(None, self.run_cmd._build_regex(args))
def test__build_regex_smoke(self):
args = mock.Mock(spec=argparse.Namespace)
setattr(args, "smoke", True)
setattr(args, 'regex', '')
- setattr(args, 'whitelist_file', None)
- setattr(args, 'blacklist_file', None)
self.assertEqual(['smoke'], self.run_cmd._build_regex(args))
def test__build_regex_regex(self):
args = mock.Mock(spec=argparse.Namespace)
setattr(args, 'smoke', False)
setattr(args, "regex", 'i_am_a_fun_little_regex')
- setattr(args, 'whitelist_file', None)
- setattr(args, 'blacklist_file', None)
self.assertEqual(['i_am_a_fun_little_regex'],
self.run_cmd._build_regex(args))
@@ -146,6 +143,35 @@
'--regex', 'passing'], 0)
+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.
+
+ self.run_cmd._set_env("/fakedir/fakefile")
+ self.assertEqual("/fakedir/fakefile", CONF._path)
+ self.assertIn('TEMPEST_CONFIG_DIR', os.environ)
+ self.assertEqual("/fakedir/fakefile",
+ os.path.join(os.environ['TEMPEST_CONFIG_DIR'],
+ os.environ['TEMPEST_CONFIG']))
+
+ def test_tempest_run_set_config_no_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 test_workspace_not_registered(self):
class Exception_(Exception):
@@ -186,7 +212,6 @@
tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock())
parsed_args = mock.Mock()
- parsed_args.config_file = []
parsed_args.workspace = None
parsed_args.state = None
diff --git a/tempest/tests/cmd/test_tempest_init.py b/tempest/tests/cmd/test_tempest_init.py
index 5f39ac9..9042b12 100644
--- a/tempest/tests/cmd/test_tempest_init.py
+++ b/tempest/tests/cmd/test_tempest_init.py
@@ -22,7 +22,7 @@
class TestTempestInit(base.TestCase):
- def test_generate_testr_conf(self):
+ def test_generate_stestr_conf(self):
# Create fake conf dir
conf_dir = self.useFixture(fixtures.TempDir())
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index 8641b63..8dbba38 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -12,11 +12,14 @@
# 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
@@ -225,7 +228,7 @@
# This test verifies that wrong config api_v2 = True is detected
class FakeClient(object):
def get_versions(self):
- return (None, ['v1.0'])
+ return (None, ['v1.1'])
fake_os = mock.MagicMock()
fake_module = mock.MagicMock()
@@ -234,8 +237,8 @@
with mock.patch.object(verify_tempest_config,
'print_and_or_update') as print_mock:
verify_tempest_config.verify_glance_api_versions(fake_os, True)
- print_mock.assert_called_once_with('api_v2', 'image-feature-enabled',
- False, True)
+ 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
@@ -250,8 +253,8 @@
with mock.patch.object(verify_tempest_config,
'print_and_or_update') as print_mock:
verify_tempest_config.verify_glance_api_versions(fake_os, True)
- print_mock.assert_called_once_with('api_v2', 'image-feature-enabled',
- False, True)
+ 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
@@ -271,8 +274,7 @@
with mock.patch.object(verify_tempest_config,
'print_and_or_update') as print_mock:
verify_tempest_config.verify_glance_api_versions(fake_os, True)
- print_mock.assert_called_once_with('api_v1', 'image-feature-enabled',
- False, True)
+ print_mock.assert_not_called()
def test_verify_glance_version_no_version(self):
# This test verifies that wrong config api_v1 = True is detected
@@ -343,6 +345,24 @@
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'},
@@ -391,6 +411,24 @@
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'},
@@ -437,6 +475,24 @@
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',
@@ -485,6 +541,24 @@
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',
@@ -494,3 +568,64 @@
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']))
diff --git a/tempest/tests/cmd/test_workspace.py b/tempest/tests/cmd/test_workspace.py
index a1c8c53..a256368 100644
--- a/tempest/tests/cmd/test_workspace.py
+++ b/tempest/tests/cmd/test_workspace.py
@@ -17,6 +17,11 @@
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
@@ -117,6 +122,17 @@
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_move(self):
new_path = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, new_path, ignore_errors=True)
@@ -140,3 +156,54 @@
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_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_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_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")
diff --git a/tempest/tests/common/utils/linux/test_remote_client.py b/tempest/tests/common/utils/linux/test_remote_client.py
index 739357b..1f0080f 100644
--- a/tempest/tests/common/utils/linux/test_remote_client.py
+++ b/tempest/tests/common/utils/linux/test_remote_client.py
@@ -77,7 +77,7 @@
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(
diff --git a/tempest/tests/fake_config.py b/tempest/tests/fake_config.py
index 4a2fff4..25e99d5 100644
--- a/tempest/tests/fake_config.py
+++ b/tempest/tests/fake_config.py
@@ -32,6 +32,7 @@
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',
@@ -59,6 +60,7 @@
self._set_attrs()
self.lock_path = cfg.CONF.oslo_concurrency.lock_path
+
fake_service1_group = cfg.OptGroup(name='fake-service1', title='Fake service1')
FakeService1Group = [
diff --git a/tempest/tests/lib/common/test_http.py b/tempest/tests/lib/common/test_http.py
index 02436e0..336ef4a 100644
--- a/tempest/tests/lib/common/test_http.py
+++ b/tempest/tests/lib/common/test_http.py
@@ -167,3 +167,30 @@
'%s://%s:%i' % (proxy.scheme,
proxy.host,
proxy.port))
+
+
+class TestClosingHttpRedirects(base.TestCase):
+ def setUp(self):
+ super(TestClosingHttpRedirects, self).setUp()
+
+ 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 setUp(self):
+ super(TestClosingProxyHttpRedirects, self).setUp()
+
+ 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)
diff --git a/tempest/tests/lib/common/utils/test_data_utils.py b/tempest/tests/lib/common/utils/test_data_utils.py
index b8385b2..a0267d0 100644
--- a/tempest/tests/lib/common/utils/test_data_utils.py
+++ b/tempest/tests/lib/common/utils/test_data_utils.py
@@ -88,7 +88,7 @@
def test_rand_url(self):
actual = data_utils.rand_url()
self.assertIsInstance(actual, str)
- self.assertRegex(actual, "^https://url-[0-9]*\.com$")
+ self.assertRegex(actual, r"^https://url-[0-9]*\.com$")
actual2 = data_utils.rand_url()
self.assertNotEqual(actual, actual2)
diff --git a/tempest/tests/lib/services/compute/test_services_client.py b/tempest/tests/lib/services/compute/test_services_client.py
index 2dd981c..ba432e3 100644
--- a/tempest/tests/lib/services/compute/test_services_client.py
+++ b/tempest/tests/lib/services/compute/test_services_client.py
@@ -56,6 +56,20 @@
}
}
+ 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()
@@ -119,6 +133,28 @@
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()
@@ -144,3 +180,36 @@
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)
diff --git a/tempest/tests/lib/services/network/test_agents_client.py b/tempest/tests/lib/services/network/test_agents_client.py
new file mode 100644
index 0000000..aabc6ce
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_agents_client.py
@@ -0,0 +1,37 @@
+# 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)
diff --git a/tempest/tests/lib/services/network/test_versions_client.py b/tempest/tests/lib/services/network/test_versions_client.py
index 026dc6d..188fc31 100644
--- a/tempest/tests/lib/services/network/test_versions_client.py
+++ b/tempest/tests/lib/services/network/test_versions_client.py
@@ -12,63 +12,92 @@
# License for the specific language governing permissions and limitations
# under the License.
-import copy
-
from tempest.lib.services.network import versions_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
class TestNetworkVersionsClient(base.BaseServiceTest):
-
- FAKE_INIT_VERSION = {
- "version": {
- "id": "v2.0",
- "links": [
- {
- "href": "http://openstack.example.com/v2.0/",
- "rel": "self"
- },
- {
- "href": "http://docs.openstack.org/",
- "rel": "describedby",
- "type": "text/html"
- }
- ],
- "status": "CURRENT"
- }
- }
+ VERSION = "v2.0"
FAKE_VERSIONS_INFO = {
- "versions": [FAKE_INIT_VERSION["version"]]
- }
-
- FAKE_VERSION_INFO = copy.deepcopy(FAKE_INIT_VERSION)
-
- FAKE_VERSION_INFO["version"]["media-types"] = [
- {
- "base": "application/json",
- "type": "application/vnd.openstack.network+json;version=2.0"
- }
+ "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'))
+ self.versions_client = versions_client.NetworkVersionsClient(
+ fake_auth, 'compute', 'regionOne')
- def _test_versions_client(self, bytes_body=False):
+ def _test_versions_client(self, func, body, bytes_body=False, **kwargs):
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)
+ 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._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(bytes_body=True)
+ 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)
diff --git a/tempest/tests/lib/services/volume/v2/test_backups_client.py b/tempest/tests/lib/services/volume/v2/test_backups_client.py
deleted file mode 100644
index 14e5fb0..0000000
--- a/tempest/tests/lib/services/volume/v2/test_backups_client.py
+++ /dev/null
@@ -1,117 +0,0 @@
-# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.lib.services.volume.v2 import backups_client
-from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services import base
-
-
-class TestBackupsClient(base.BaseServiceTest):
-
- FAKE_BACKUP_LIST = {
- "backups": [
- {
- "id": "2ef47aee-8844-490c-804d-2a8efe561c65",
- "links": [
- {
- "href": "fake-url-1",
- "rel": "self"
- },
- {
- "href": "fake-url-2",
- "rel": "bookmark"
- }
- ],
- "name": "backup001"
- }
- ]
- }
-
- FAKE_BACKUP_LIST_WITH_DETAIL = {
- "backups": [
- {
- "availability_zone": "az1",
- "container": "volumebackups",
- "created_at": "2013-04-02T10:35:27.000000",
- "description": None,
- "fail_reason": None,
- "id": "2ef47aee-8844-490c-804d-2a8efe561c65",
- "links": [
- {
- "href": "fake-url-1",
- "rel": "self"
- },
- {
- "href": "fake-url-2",
- "rel": "bookmark"
- }
- ],
- "name": "backup001",
- "object_count": 22,
- "size": 1,
- "status": "available",
- "volume_id": "e5185058-943a-4cb4-96d9-72c184c337d6",
- "is_incremental": True,
- "has_dependent_backups": False
- }
- ]
- }
-
- def setUp(self):
- super(TestBackupsClient, self).setUp()
- fake_auth = fake_auth_provider.FakeAuthProvider()
- self.client = backups_client.BackupsClient(fake_auth,
- 'volume',
- 'regionOne')
-
- def _test_list_backups(self, detail=False, mock_args='backups',
- bytes_body=False, **params):
- if detail:
- resp_body = self.FAKE_BACKUP_LIST_WITH_DETAIL
- else:
- resp_body = self.FAKE_BACKUP_LIST
- self.check_service_client_function(
- self.client.list_backups,
- 'tempest.lib.common.rest_client.RestClient.get',
- resp_body,
- to_utf=bytes_body,
- mock_args=[mock_args],
- detail=detail,
- **params)
-
- def test_list_backups_with_str_body(self):
- self._test_list_backups()
-
- def test_list_backups_with_bytes_body(self):
- self._test_list_backups(bytes_body=True)
-
- def test_list_backups_with_detail_with_str_body(self):
- mock_args = "backups/detail"
- self._test_list_backups(detail=True, mock_args=mock_args)
-
- def test_list_backups_with_detail_with_bytes_body(self):
- mock_args = "backups/detail"
- self._test_list_backups(detail=True, mock_args=mock_args,
- bytes_body=True)
-
- def test_list_backups_with_params(self):
- # Run the test separately for each param, to avoid assertion error
- # resulting from randomized params order.
- mock_args = 'backups?sort_key=name'
- self._test_list_backups(mock_args=mock_args, sort_key='name')
-
- mock_args = 'backups/detail?limit=10'
- self._test_list_backups(detail=True, mock_args=mock_args,
- bytes_body=True, limit=10)
diff --git a/tempest/tests/lib/services/volume/v2/test_volumes_client.py b/tempest/tests/lib/services/volume/v2/test_volumes_client.py
deleted file mode 100644
index d7b042e..0000000
--- a/tempest/tests/lib/services/volume/v2/test_volumes_client.py
+++ /dev/null
@@ -1,127 +0,0 @@
-# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.services.volume.v2 import volumes_client
-from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services import base
-
-
-class TestVolumesClient(base.BaseServiceTest):
-
- FAKE_VOLUME_METADATA_ITEM = {
- "meta": {
- "key1": "value1"
- }
- }
-
- FAKE_VOLUME_IMAGE_METADATA = {
- "metadata": {
- "container_format": "bare",
- "min_ram": "0",
- "disk_format": "raw",
- "image_name": "xly-ubuntu16-server",
- "image_id": "3e087b0c-10c5-4255-b147-6e8e9dbad6fc",
- "checksum": "008f5d22fe3cb825d714da79607a90f9",
- "min_disk": "0",
- "size": "8589934592"
- }
- }
-
- def setUp(self):
- super(TestVolumesClient, self).setUp()
- fake_auth = fake_auth_provider.FakeAuthProvider()
- self.client = volumes_client.VolumesClient(fake_auth,
- 'volume',
- 'regionOne')
-
- def _test_retype_volume(self, bytes_body=False):
- kwargs = {
- "new_type": "dedup-tier-replication",
- "migration_policy": "never"
- }
-
- self.check_service_client_function(
- self.client.retype_volume,
- 'tempest.lib.common.rest_client.RestClient.post',
- {},
- to_utf=bytes_body,
- status=202,
- volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8",
- **kwargs
- )
-
- def _test_force_detach_volume(self, bytes_body=False):
- kwargs = {
- 'attachment_id': '6980e295-920f-412e-b189-05c50d605acd',
- 'connector': {
- 'initiator': 'iqn.2017-04.org.fake:01'
- }
- }
-
- self.check_service_client_function(
- self.client.force_detach_volume,
- 'tempest.lib.common.rest_client.RestClient.post',
- {},
- to_utf=bytes_body,
- status=202,
- volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8",
- **kwargs
- )
-
- def _test_show_volume_metadata_item(self, bytes_body=False):
- self.check_service_client_function(
- self.client.show_volume_metadata_item,
- 'tempest.lib.common.rest_client.RestClient.get',
- self.FAKE_VOLUME_METADATA_ITEM,
- to_utf=bytes_body,
- volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8",
- id="key1")
-
- def _test_show_volume_image_metadata(self, bytes_body=False):
- fake_volume_id = "a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8"
- self.check_service_client_function(
- self.client.show_volume_image_metadata,
- 'tempest.lib.common.rest_client.RestClient.post',
- self.FAKE_VOLUME_IMAGE_METADATA,
- to_utf=bytes_body,
- mock_args=['volumes/%s/action' % fake_volume_id,
- json.dumps({"os-show_image_metadata": {}})],
- volume_id=fake_volume_id)
-
- def test_force_detach_volume_with_str_body(self):
- self._test_force_detach_volume()
-
- def test_force_detach_volume_with_bytes_body(self):
- self._test_force_detach_volume(bytes_body=True)
-
- def test_show_volume_metadata_item_with_str_body(self):
- self._test_show_volume_metadata_item()
-
- def test_show_volume_metadata_item_with_bytes_body(self):
- self._test_show_volume_metadata_item(bytes_body=True)
-
- def test_show_volume_image_metadata_with_str_body(self):
- self._test_show_volume_image_metadata()
-
- def test_show_volume_image_metadata_with_bytes_body(self):
- self._test_show_volume_image_metadata(bytes_body=True)
-
- def test_retype_volume_with_str_body(self):
- self._test_retype_volume()
-
- def test_retype_volume_with_bytes_body(self):
- self._test_retype_volume(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v2/test_availability_zone_client.py b/tempest/tests/lib/services/volume/v3/test_availability_zone_client.py
similarity index 96%
rename from tempest/tests/lib/services/volume/v2/test_availability_zone_client.py
rename to tempest/tests/lib/services/volume/v3/test_availability_zone_client.py
index 770565c..4827326 100644
--- a/tempest/tests/lib/services/volume/v2/test_availability_zone_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_availability_zone_client.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.lib.services.volume.v2 import availability_zone_client
+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
diff --git a/tempest/tests/lib/services/volume/v3/test_backups_client.py b/tempest/tests/lib/services/volume/v3/test_backups_client.py
index f1ce987..5412064 100644
--- a/tempest/tests/lib/services/volume/v3/test_backups_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_backups_client.py
@@ -20,6 +20,55 @@
class TestBackupsClient(base.BaseServiceTest):
+ FAKE_BACKUP_LIST = {
+ "backups": [
+ {
+ "id": "2ef47aee-8844-490c-804d-2a8efe561c65",
+ "links": [
+ {
+ "href": "fake-url-1",
+ "rel": "self"
+ },
+ {
+ "href": "fake-url-2",
+ "rel": "bookmark"
+ }
+ ],
+ "name": "backup001"
+ }
+ ]
+ }
+
+ FAKE_BACKUP_LIST_WITH_DETAIL = {
+ "backups": [
+ {
+ "availability_zone": "az1",
+ "container": "volumebackups",
+ "created_at": "2013-04-02T10:35:27.000000",
+ "description": None,
+ "fail_reason": None,
+ "id": "2ef47aee-8844-490c-804d-2a8efe561c65",
+ "links": [
+ {
+ "href": "fake-url-1",
+ "rel": "self"
+ },
+ {
+ "href": "fake-url-2",
+ "rel": "bookmark"
+ }
+ ],
+ "name": "backup001",
+ "object_count": 22,
+ "size": 1,
+ "status": "available",
+ "volume_id": "e5185058-943a-4cb4-96d9-72c184c337d6",
+ "is_incremental": True,
+ "has_dependent_backups": False
+ }
+ ]
+ }
+
FAKE_BACKUP_UPDATE = {
"backup": {
"id": "4c65c15f-a5c5-464b-b92a-90e4c04636a7",
@@ -35,6 +84,46 @@
'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,
diff --git a/tempest/tests/lib/services/volume/v2/test_capabilities_client.py b/tempest/tests/lib/services/volume/v3/test_capabilities_client.py
similarity index 97%
rename from tempest/tests/lib/services/volume/v2/test_capabilities_client.py
rename to tempest/tests/lib/services/volume/v3/test_capabilities_client.py
index 3d3f1e1..7efe1ff 100644
--- a/tempest/tests/lib/services/volume/v2/test_capabilities_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_capabilities_client.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.lib.services.volume.v2 import capabilities_client
+from tempest.lib.services.volume.v3 import capabilities_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
diff --git a/tempest/tests/lib/services/volume/v2/test_encryption_types_client.py b/tempest/tests/lib/services/volume/v3/test_encryption_types_client.py
similarity index 98%
rename from tempest/tests/lib/services/volume/v2/test_encryption_types_client.py
rename to tempest/tests/lib/services/volume/v3/test_encryption_types_client.py
index 8de9fb4..c788181 100644
--- a/tempest/tests/lib/services/volume/v2/test_encryption_types_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_encryption_types_client.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.lib.services.volume.v2 import encryption_types_client
+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
diff --git a/tempest/tests/lib/services/volume/v2/test_extensions_client.py b/tempest/tests/lib/services/volume/v3/test_extensions_client.py
similarity index 97%
rename from tempest/tests/lib/services/volume/v2/test_extensions_client.py
rename to tempest/tests/lib/services/volume/v3/test_extensions_client.py
index c0ee421..a8bbffd 100644
--- a/tempest/tests/lib/services/volume/v2/test_extensions_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_extensions_client.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.lib.services.volume.v2 import extensions_client
+from tempest.lib.services.volume.v3 import extensions_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
diff --git a/tempest/tests/lib/services/volume/v2/test_hosts_client.py b/tempest/tests/lib/services/volume/v3/test_hosts_client.py
similarity index 95%
rename from tempest/tests/lib/services/volume/v2/test_hosts_client.py
rename to tempest/tests/lib/services/volume/v3/test_hosts_client.py
index e107910..09bc0b1 100644
--- a/tempest/tests/lib/services/volume/v2/test_hosts_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_hosts_client.py
@@ -13,12 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.lib.services.volume.v2 import hosts_client
+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 TestQuotasClient(base.BaseServiceTest):
+class TestHostsClient(base.BaseServiceTest):
FAKE_LIST_HOSTS = {
"hosts": [
{
@@ -66,7 +66,7 @@
}
def setUp(self):
- super(TestQuotasClient, self).setUp()
+ super(TestHostsClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
self.client = hosts_client.HostsClient(fake_auth,
'volume',
diff --git a/tempest/tests/lib/services/volume/v2/test_limits_client.py b/tempest/tests/lib/services/volume/v3/test_limits_client.py
similarity index 97%
rename from tempest/tests/lib/services/volume/v2/test_limits_client.py
rename to tempest/tests/lib/services/volume/v3/test_limits_client.py
index 202054c..f94fbe1 100644
--- a/tempest/tests/lib/services/volume/v2/test_limits_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_limits_client.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.lib.services.volume.v2 import limits_client
+from tempest.lib.services.volume.v3 import limits_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
diff --git a/tempest/tests/lib/services/volume/v2/test_quota_classes_client.py b/tempest/tests/lib/services/volume/v3/test_quota_classes_client.py
similarity index 97%
rename from tempest/tests/lib/services/volume/v2/test_quota_classes_client.py
rename to tempest/tests/lib/services/volume/v3/test_quota_classes_client.py
index e715fcc..6190733 100644
--- a/tempest/tests/lib/services/volume/v2/test_quota_classes_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_quota_classes_client.py
@@ -15,7 +15,7 @@
import copy
-from tempest.lib.services.volume.v2 import quota_classes_client
+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
diff --git a/tempest/tests/lib/services/volume/v2/test_quotas_client.py b/tempest/tests/lib/services/volume/v3/test_quotas_client.py
similarity index 97%
rename from tempest/tests/lib/services/volume/v2/test_quotas_client.py
rename to tempest/tests/lib/services/volume/v3/test_quotas_client.py
index 6384350..aa5d251 100644
--- a/tempest/tests/lib/services/volume/v2/test_quotas_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_quotas_client.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.lib.services.volume.v2 import quotas_client
+from tempest.lib.services.volume.v3 import quotas_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
diff --git a/tempest/tests/lib/services/volume/v2/test_scheduler_stats_client.py b/tempest/tests/lib/services/volume/v3/test_scheduler_stats_client.py
similarity index 97%
rename from tempest/tests/lib/services/volume/v2/test_scheduler_stats_client.py
rename to tempest/tests/lib/services/volume/v3/test_scheduler_stats_client.py
index 8a5f25f..e0f5566 100644
--- a/tempest/tests/lib/services/volume/v2/test_scheduler_stats_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_scheduler_stats_client.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.lib.services.volume.v2 import scheduler_stats_client
+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
diff --git a/tempest/tests/lib/services/volume/v2/test_snapshot_manage_client.py b/tempest/tests/lib/services/volume/v3/test_snapshot_manage_client.py
similarity index 94%
rename from tempest/tests/lib/services/volume/v2/test_snapshot_manage_client.py
rename to tempest/tests/lib/services/volume/v3/test_snapshot_manage_client.py
index e03a8eb..1b88020 100644
--- a/tempest/tests/lib/services/volume/v2/test_snapshot_manage_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_snapshot_manage_client.py
@@ -17,9 +17,7 @@
from oslo_serialization import jsonutils as json
-from tempest.lib.services.volume.v2 import snapshot_manage_client
-from tempest.lib.services.volume.v3 import snapshot_manage_client \
- as snapshot_manage_clientv3
+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
@@ -65,7 +63,7 @@
# 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_clientv3.json,
+ with mock.patch.object(snapshot_manage_client.json,
'dumps') as mock_dumps:
mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
diff --git a/tempest/tests/lib/services/volume/v2/test_snapshots_client.py b/tempest/tests/lib/services/volume/v3/test_snapshots_client.py
similarity index 99%
rename from tempest/tests/lib/services/volume/v2/test_snapshots_client.py
rename to tempest/tests/lib/services/volume/v3/test_snapshots_client.py
index c9f57a0..2efd2e6 100644
--- a/tempest/tests/lib/services/volume/v2/test_snapshots_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_snapshots_client.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.lib.services.volume.v2 import snapshots_client
+from tempest.lib.services.volume.v3 import snapshots_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
diff --git a/tempest/tests/lib/services/volume/v2/test_transfers_client.py b/tempest/tests/lib/services/volume/v3/test_transfers_client.py
similarity index 95%
rename from tempest/tests/lib/services/volume/v2/test_transfers_client.py
rename to tempest/tests/lib/services/volume/v3/test_transfers_client.py
index 8e7c6f4..d631fe7 100644
--- a/tempest/tests/lib/services/volume/v2/test_transfers_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_transfers_client.py
@@ -18,9 +18,7 @@
import mock
from oslo_serialization import jsonutils as json
-from tempest.lib.services.volume.v2 import transfers_client
-from tempest.lib.services.volume.v3 import transfers_client \
- as transfers_clientv3
+from tempest.lib.services.volume.v3 import transfers_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
@@ -65,7 +63,7 @@
# 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_clientv3.json, 'dumps') as mock_dumps:
+ 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(
@@ -86,7 +84,7 @@
# 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_clientv3.json, 'dumps') as mock_dumps:
+ 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(
diff --git a/tempest/tests/lib/services/volume/v2/test_volume_manage_client.py b/tempest/tests/lib/services/volume/v3/test_volume_manage_client.py
similarity index 94%
rename from tempest/tests/lib/services/volume/v2/test_volume_manage_client.py
rename to tempest/tests/lib/services/volume/v3/test_volume_manage_client.py
index 0fb66bb..902f027 100644
--- a/tempest/tests/lib/services/volume/v2/test_volume_manage_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_volume_manage_client.py
@@ -17,9 +17,7 @@
from oslo_serialization import jsonutils as json
-from tempest.lib.services.volume.v2 import volume_manage_client
-from tempest.lib.services.volume.v3 import volume_manage_client \
- as volume_manage_clientv3
+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
@@ -93,7 +91,7 @@
# 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_clientv3.json,
+ with mock.patch.object(volume_manage_client.json,
'dumps') as mock_dumps:
mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
diff --git a/tempest/tests/lib/services/volume/v3/test_volumes_client.py b/tempest/tests/lib/services/volume/v3/test_volumes_client.py
index a515fd3..1250536 100644
--- a/tempest/tests/lib/services/volume/v3/test_volumes_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_volumes_client.py
@@ -13,6 +13,8 @@
# 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
@@ -27,6 +29,25 @@
}
}
+ FAKE_VOLUME_METADATA_ITEM = {
+ "meta": {
+ "key1": "value1"
+ }
+ }
+
+ FAKE_VOLUME_IMAGE_METADATA = {
+ "metadata": {
+ "container_format": "bare",
+ "min_ram": "0",
+ "disk_format": "raw",
+ "image_name": "xly-ubuntu16-server",
+ "image_id": "3e087b0c-10c5-4255-b147-6e8e9dbad6fc",
+ "checksum": "008f5d22fe3cb825d714da79607a90f9",
+ "min_disk": "0",
+ "size": "8589934592"
+ }
+ }
+
def setUp(self):
super(TestVolumesClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -34,6 +55,60 @@
'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,
@@ -41,6 +116,30 @@
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()
diff --git a/tempest/tests/lib/test_decorators.py b/tempest/tests/lib/test_decorators.py
index ed0eea3..0b1a599 100644
--- a/tempest/tests/lib/test_decorators.py
+++ b/tempest/tests/lib/test_decorators.py
@@ -19,6 +19,7 @@
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
@@ -62,21 +63,40 @@
t = TestFoo('test_bar')
if expected_to_skip:
- self.assertRaises(testtools.TestCase.skipException, t.test_bar)
+ 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_bug(self):
+ def test_skip_because_launchpad_bug(self):
self._test_skip_because_helper(bug='12345')
- def test_skip_because_bug_and_condition_true(self):
+ def test_skip_because_launchpad_bug_and_condition_true(self):
self._test_skip_because_helper(bug='12348', condition=True)
- def test_skip_because_bug_and_condition_false(self):
+ def test_skip_because_launchpad_bug_and_condition_false(self):
self._test_skip_because_helper(expected_to_skip=False,
bug='12349', condition=False)
+ def test_skip_because_storyboard_bug(self):
+ self._test_skip_because_helper(bug='1992', bug_type='storyboard')
+
+ def test_skip_because_storyboard_bug_and_condition_true(self):
+ self._test_skip_because_helper(bug='1992', bug_type='storyboard',
+ condition=True)
+
+ def test_skip_because_storyboard_bug_and_condition_false(self):
+ self._test_skip_because_helper(expected_to_skip=False,
+ bug='1992', bug_type='storyboard',
+ condition=False)
+
def test_skip_because_bug_without_bug_never_skips(self):
"""Never skip without a bug parameter."""
self._test_skip_because_helper(expected_to_skip=False,
@@ -84,8 +104,8 @@
self._test_skip_because_helper(expected_to_skip=False)
def test_skip_because_invalid_bug_number(self):
- """Raise ValueError if with an invalid bug number"""
- self.assertRaises(ValueError, self._test_skip_because_helper,
+ """Raise InvalidParam if with an invalid bug number"""
+ self.assertRaises(lib_exc.InvalidParam, self._test_skip_because_helper,
bug='critical_bug')
@@ -126,6 +146,13 @@
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()
@@ -137,10 +164,9 @@
test_foo(sentinel)
f.assert_called_once_with(sentinel)
- def test_relatedbug_when_exception(self):
- class MyException(Exception):
- def __init__(self, status_code):
- self.status_code = status_code
+ def 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)
@@ -152,4 +178,53 @@
with mock.patch.object(decorators.LOG, 'error') as m_error:
self.assertRaises(MyException, test_foo, object())
- m_error.assert_called_once_with(mock.ANY, '1234', '1234')
+ 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())
diff --git a/tempest/tests/test_hacking.py b/tempest/tests/test_hacking.py
index bc3a753..9534ce8 100644
--- a/tempest/tests/test_hacking.py
+++ b/tempest/tests/test_hacking.py
@@ -193,3 +193,60 @@
"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)
diff --git a/tempest/tests/test_list_tests.py b/tempest/tests/test_list_tests.py
index 4af7463..1cc9c9a 100644
--- a/tempest/tests/test_list_tests.py
+++ b/tempest/tests/test_list_tests.py
@@ -34,7 +34,7 @@
"error on import %s" % ids)
ids = six.text_type(ids).split('\n')
for test_id in ids:
- if re.match('(\w+\.){3}\w+', test_id):
+ if 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]
diff --git a/test-requirements.txt b/test-requirements.txt
index e33f207..196387c 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,7 +1,7 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
-hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
+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
diff --git a/tools/check_logs.py b/tools/check_logs.py
index b80ccc0..de7e41d 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -96,7 +96,7 @@
def collect_url_logs(url):
page = urlreq.urlopen(url)
content = page.read()
- logs = re.findall('(screen-[\w-]+\.txt\.gz)</a>', content)
+ logs = re.findall(r'(screen-[\w-]+\.txt\.gz)</a>', content)
return logs
@@ -162,6 +162,7 @@
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
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index bbb9019..4eb78fb 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -63,12 +63,13 @@
except HTTPError as err:
if err.code == 404:
return False
- p = re.compile('^tempest\.test_plugins', re.M)
+ p = re.compile(r'^tempest\.test_plugins', re.M)
if p.findall(r.read().decode('utf-8')):
return True
else:
False
+
r = urllib.urlopen(url)
# Gerrit prepends 4 garbage octets to the JSON, in order to counter
# cross-site scripting attacks. Therefore we must discard it so the
diff --git a/tools/generate-tempest-plugins-list.sh b/tools/generate-tempest-plugins-list.sh
index 20c99b2..b27b23a 100755
--- a/tools/generate-tempest-plugins-list.sh
+++ b/tools/generate-tempest-plugins-list.sh
@@ -49,13 +49,37 @@
sorted_plugins=$(python tools/generate-tempest-plugins-list.py)
-for k in ${sorted_plugins}; do
- project=${k:0:28}
- giturl="git://git.openstack.org/openstack/${k:0:26}"
- printf "|%-28s|%-73s|\n" "${project}" "${giturl}"
- printf "+----------------------------+-------------------------------------------------------------------------+\n"
+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"
+}
+
+printf "\n\n"
+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 ${sorted_plugins}; do
+ i=$((i+1))
+ giturl="git://git.openstack.org/openstack/${plugin}"
+ gitlink="https://git.openstack.org/cgit/openstack/${plugin}"
+ printf "%-3s %-${name_col_len}s %s\n" "$i" "${plugin}" "\`${giturl} <${gitlink}>\`__"
done
+title_underline ${name_col_len}
+
+printf "\n\n"
+
if [[ -r doc/source/data/tempest-plugins-registry.footer ]]; then
cat doc/source/data/tempest-plugins-registry.footer
fi
diff --git a/tox.ini b/tox.ini
index da0233a..8208066 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = pep8,py35,py27,pip-check-reqs
+envlist = pep8,py36,py27,pip-check-reqs
minversion = 2.3.1
skipsdist = True
@@ -19,7 +19,7 @@
OS_STDOUT_CAPTURE=1
OS_STDERR_CAPTURE=1
OS_TEST_TIMEOUT=160
- PYTHONWARNINGS=default::DeprecationWarning
+ 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
usedevelop = True
install_command = pip install {opts} {packages}
@@ -61,6 +61,26 @@
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 =
@@ -78,6 +98,7 @@
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}
@@ -100,6 +121,7 @@
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}
@@ -135,6 +157,16 @@
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:venv]
deps =
-c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
@@ -176,7 +208,8 @@
# E125 is a won't fix until https://github.com/jcrocholl/pep8/issues/126 is resolved. For further detail see https://review.openstack.org/#/c/36788/
# E123 skipped because it is ignored by default in the default pep8
# E129 skipped because it is too limiting when combined with other rules
-ignore = E125,E123,E129
+# 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