Merge "Allow kwargs in create_volume"
diff --git a/.zuul.yaml b/.zuul.yaml
index 5e3f33a..c20f204 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -13,8 +13,6 @@
roles: &base_roles
- zuul: opendev.org/openstack/devstack
vars: &base_vars
- # TODO(gmann): Remove these test skip once nova bug #1882521 is solved
- tempest_black_regex: "(tempest.api.compute.volumes.test_attach_volume.AttachVolumeMultiAttachTest.test_resize_server_with_multiattached_volume|tempest.api.compute.servers.test_server_rescue_negative.ServerRescueNegativeTestJSON|tempest.api.compute.servers.test_server_rescue.ServerStableDeviceRescueTest.test_stable_device_rescue_disk_virtio_with_volume_attached)"
devstack_services:
tempest: true
devstack_local_conf:
@@ -95,6 +93,10 @@
branches: ^(?!stable/ocata).*$
description: |
Base integration test with Neutron networking and py27.
+ This job is supposed to run until stable/train setup only.
+ If you are running it on stable/ussuri gate onwards for python2.7
+ coverage then you need to do override-checkout with any stable
+ branch less than or equal to stable/train.
Former names for this job where:
* legacy-tempest-dsvm-neutron-full
* gate-tempest-dsvm-neutron-full-ubuntu-xenial
@@ -114,7 +116,7 @@
- job:
name: tempest-full-oslo-master
- parent: tempest-full
+ parent: tempest-full-py3
description: |
Integration test using current git of oslo libs.
This ensures that when oslo libs get released that they
@@ -142,9 +144,6 @@
- opendev.org/openstack/oslo.utils
- opendev.org/openstack/oslo.versionedobjects
- opendev.org/openstack/oslo.vmware
- vars:
- devstack_localrc:
- USE_PYTHON3: True
- job:
name: tempest-full-parallel
@@ -201,7 +200,7 @@
branches: ^(?!stable/ocata).*$
description: |
This job runs integration tests for networking. This is subset of
- 'tempest-full' job and run only Neutron and Nova related tests.
+ 'tempest-full-py3' job and run only Neutron and Nova related tests.
This is meant to be run on neutron gate only.
vars:
tox_envlist: integrated-network
@@ -218,11 +217,10 @@
- job:
name: tempest-integrated-compute
parent: devstack-tempest
- nodeset: openstack-single-node-bionic
branches: ^(?!stable/ocata).*$
description: |
This job runs integration tests for compute. This is
- subset of 'tempest-full' job and run Nova, Neutron, Cinder (except backup tests)
+ subset of 'tempest-full-py3' job and run Nova, Neutron, Cinder (except backup tests)
and Glance related tests. This is meant to be run on Nova gate only.
vars:
tox_envlist: integrated-compute
@@ -244,7 +242,7 @@
branches: ^(?!stable/ocata).*$
description: |
This job runs integration tests for placement. This is
- subset of 'tempest-full' job and run Nova and Neutron
+ subset of 'tempest-full-py3' job and run Nova and Neutron
related tests. This is meant to be run on Placement gate only.
vars:
tox_envlist: integrated-placement
@@ -265,7 +263,7 @@
branches: ^(?!stable/ocata).*$
description: |
This job runs integration tests for image & block storage. This is
- subset of 'tempest-full' job and run Cinder, Glance, Swift and Nova
+ subset of 'tempest-full-py3' job and run Cinder, Glance, Swift and Nova
related tests. This is meant to be run on Cinder and Glance gate only.
vars:
tox_envlist: integrated-storage
@@ -281,7 +279,7 @@
branches: ^(?!stable/ocata).*$
description: |
This job runs integration tests for object storage. This is
- subset of 'tempest-full' job and run Swift, Cinder and Glance
+ subset of 'tempest-full-py3' job and run Swift, Cinder and Glance
related tests. This is meant to be run on Swift gate only.
vars:
tox_envlist: integrated-object-storage
@@ -294,9 +292,6 @@
- job:
name: tempest-full-py3-ipv6
parent: devstack-tempest-ipv6
- # This currently works from stable/pike on.
- # Before stable/pike, legacy version of tempest-full
- # 'legacy-tempest-dsvm-neutron-full' run.
branches: ^(?!stable/ocata).*$
description: |
Base integration test with Neutron networking, IPv6 and py3.
@@ -473,6 +468,11 @@
USE_PYTHON3: true
- job:
+ name: tempest-full-victoria-py3
+ parent: tempest-full-py3
+ override-checkout: stable/victoria
+
+- job:
name: tempest-full-ussuri-py3
parent: tempest-full-py3
nodeset: openstack-single-node-bionic
@@ -542,7 +542,7 @@
- job:
name: tempest-pg-full
- parent: tempest-full
+ parent: tempest-full-py3
description: |
Base integration test with Neutron networking and PostgreSQL.
Former name for this job was legacy-tempest-dsvm-neutron-pg-full.
@@ -550,7 +550,6 @@
devstack_localrc:
ENABLE_FILE_INJECTION: true
DATABASE_TYPE: postgresql
- USE_PYTHON3: True
- project-template:
name: integrated-gate-networking
@@ -672,6 +671,11 @@
- tempest-full-py3-ipv6:
voting: false
irrelevant-files: *tempest-irrelevant-files
+ - glance-multistore-cinder-import:
+ voting: false
+ irrelevant-files: *tempest-irrelevant-files
+ - tempest-full-victoria-py3:
+ irrelevant-files: *tempest-irrelevant-files
- tempest-full-ussuri-py3:
irrelevant-files: *tempest-irrelevant-files
- tempest-full-train-py3:
@@ -720,6 +724,7 @@
voting: false
irrelevant-files: *tempest-irrelevant-files
- neutron-tempest-dvr:
+ voting: false
irrelevant-files: *tempest-irrelevant-files
- interop-tempest-consistency:
irrelevant-files: *tempest-irrelevant-files
@@ -763,6 +768,7 @@
irrelevant-files: *tempest-irrelevant-files
periodic-stable:
jobs:
+ - tempest-full-victoria-py3
- tempest-full-ussuri-py3
- tempest-full-train-py3
- tempest-full-stein-py3
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index 5bc0eac..c7004dd 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -352,15 +352,15 @@
* `2.37`_
- .. _2.37: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id34
+ .. _2.37: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id35
* `2.39`_
- .. _2.39: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id35
+ .. _2.39: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id36
* `2.41`_
- .. _2.41: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id37
+ .. _2.41: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id38
* `2.42`_
@@ -368,15 +368,15 @@
* `2.47`_
- .. _2.47: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id42
+ .. _2.47: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id43
* `2.48`_
- .. _2.48: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id43
+ .. _2.48: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id44
* `2.49`_
- .. _2.49: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id44
+ .. _2.49: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id45
* `2.53`_
@@ -384,15 +384,15 @@
* `2.54`_
- .. _2.54: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id49
+ .. _2.54: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id50
* `2.55`_
- .. _2.55: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id50
+ .. _2.55: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id51
* `2.57`_
- .. _2.57: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id52
+ .. _2.57: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id53
* `2.59`_
@@ -404,19 +404,19 @@
* `2.61`_
- .. _2.61: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id55
+ .. _2.61: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id56
* `2.63`_
- .. _2.63: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id57
+ .. _2.63: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id58
* `2.70`_
- .. _2.70: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id63
+ .. _2.70: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id64
* `2.71`_
- .. _2.71: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id64
+ .. _2.71: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id65
* `2.73`_
diff --git a/playbooks/devstack-tempest-ipv6.yaml b/playbooks/devstack-tempest-ipv6.yaml
index 5f72345..4788362 100644
--- a/playbooks/devstack-tempest-ipv6.yaml
+++ b/playbooks/devstack-tempest-ipv6.yaml
@@ -7,11 +7,6 @@
# We run tests only on one node, regardless how many nodes are in the system
- hosts: tempest
- environment:
- # This enviroment variable is used by the optional tempest-gabbi
- # job provided by the gabbi-tempest plugin. It can be safely ignored
- # if that plugin is not being used.
- GABBI_TEMPEST_PATH: "{{ gabbi_tempest_path | default('') }}"
roles:
- setup-tempest-run-dir
- setup-tempest-data-dir
diff --git a/playbooks/devstack-tempest.yaml b/playbooks/devstack-tempest.yaml
index 7ee7411..3b969f2 100644
--- a/playbooks/devstack-tempest.yaml
+++ b/playbooks/devstack-tempest.yaml
@@ -7,11 +7,6 @@
# We run tests only on one node, regardless how many nodes are in the system
- hosts: tempest
- environment:
- # This enviroment variable is used by the optional tempest-gabbi
- # job provided by the gabbi-tempest plugin. It can be safely ignored
- # if that plugin is not being used.
- GABBI_TEMPEST_PATH: "{{ gabbi_tempest_path | default('') }}"
tasks:
- name: Setup Tempest Run Directory
include_role:
@@ -30,9 +25,9 @@
name: tempest-cleanup
vars:
init_saved_state: true
- when:
- - run_tempest_dry_cleanup is defined
- - run_tempest_cleanup is defined
+ when: (run_tempest_dry_cleanup is defined and run_tempest_dry_cleanup | bool) or
+ (run_tempest_cleanup is defined and run_tempest_cleanup | bool) or
+ (run_tempest_fail_if_leaked_resources is defined and run_tempest_fail_if_leaked_resources | bool)
- name: Run Tempest
include_role:
@@ -43,10 +38,9 @@
name: tempest-cleanup
vars:
dry_run: true
- when:
- - run_tempest_dry_cleanup is defined
+ when: run_tempest_dry_cleanup is defined and run_tempest_dry_cleanup | bool
- name: Run tempest cleanup
include_role:
name: tempest-cleanup
- when: run_tempest_cleanup is defined
+ when: run_tempest_cleanup is defined and run_tempest_cleanup | bool
diff --git a/releasenotes/notes/Remove-test_reboot_server_soft-48fa786f38cd94dc.yaml b/releasenotes/notes/Remove-test_reboot_server_soft-48fa786f38cd94dc.yaml
new file mode 100644
index 0000000..fb84d25
--- /dev/null
+++ b/releasenotes/notes/Remove-test_reboot_server_soft-48fa786f38cd94dc.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+ - |
+ The test_reboot_server_soft has been skipped for more than 6 years.
+ Take into account that the minimum scenario test uses soft reboot
+ and the nova functional test also covers reboot.
diff --git a/releasenotes/notes/add-image-alt-ssh-user-config-option-1b775af2f468aa5b.yaml b/releasenotes/notes/add-image-alt-ssh-user-config-option-1b775af2f468aa5b.yaml
new file mode 100644
index 0000000..159bbe8
--- /dev/null
+++ b/releasenotes/notes/add-image-alt-ssh-user-config-option-1b775af2f468aa5b.yaml
@@ -0,0 +1,10 @@
+---
+features:
+ - A new config option in the validation section, image_alt_ssh_user,
+ to specify the user name used to authenticate to an alternative
+ instance (instance using image_ref_alt) in tests. By default this
+ is set to root.
+ - A new config option in the validation section, image_alt_ssh_password,
+ to specify the password used to authenticate to an alternative
+ instance (instance using image_ref_alt) in tests. By default this
+ is set to password.
diff --git a/releasenotes/notes/network_feature_enabled_available_features-35f9ac5f253e2ca3.yaml b/releasenotes/notes/network_feature_enabled_available_features-35f9ac5f253e2ca3.yaml
new file mode 100644
index 0000000..1f2d6b9
--- /dev/null
+++ b/releasenotes/notes/network_feature_enabled_available_features-35f9ac5f253e2ca3.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ New config option to ``network-feature-enabled``: ``available_features``.
+ This is a list which can contain features that are not discoverable
+ through Neutron API, or it can be the special entry ``all``.
diff --git a/roles/tempest-cleanup/README.rst b/roles/tempest-cleanup/README.rst
index 70719ca..d1fad90 100644
--- a/roles/tempest-cleanup/README.rst
+++ b/roles/tempest-cleanup/README.rst
@@ -31,3 +31,31 @@
When true, tempest cleanup creates a report (./dry_run.json) of the
resources that would be cleaned up if the role was ran with dry_run option
set to false.
+
+.. zuul:rolevar:: run_tempest_fail_if_leaked_resources
+ :default: false
+
+ When true, the role will fail if any leaked resources are detected.
+ The detection is done via dry_run.json file which if contains any resources,
+ some must have been leaked. This can be also used to verify that tempest
+ cleanup was successful.
+
+
+Role usage
+----------
+
+The role can be also used for verification that tempest tests don't leak any
+resources or to test that 'tempest cleanup' command deleted all leaked
+resources as expected.
+Either way the role needs to be run first with init_saved_state variable set
+to true prior any tempest tests got executed.
+Then, after tempest tests got executed this role needs to be run again with
+role variables set according to the desired outcome:
+
+1. to verify that tempest tests don't leak any resources
+ run_tempest_dry_cleanup and run_tempest_fail_if_leaked_resources have to
+ be set to true.
+
+2. to check that 'tempest cleanup' command deleted all the leaked resources
+ run_tempest_cleanup and run_tempest_fail_if_leaked_resources have to be set
+ to true.
diff --git a/roles/tempest-cleanup/defaults/main.yaml b/roles/tempest-cleanup/defaults/main.yaml
index fc1948a..ce78bdb 100644
--- a/roles/tempest-cleanup/defaults/main.yaml
+++ b/roles/tempest-cleanup/defaults/main.yaml
@@ -1,3 +1,4 @@
devstack_base_dir: /opt/stack
init_saved_state: false
dry_run: false
+run_tempest_fail_if_leaked_resources: false
diff --git a/roles/tempest-cleanup/tasks/dry_run.yaml b/roles/tempest-cleanup/tasks/dry_run.yaml
new file mode 100644
index 0000000..46749ab
--- /dev/null
+++ b/roles/tempest-cleanup/tasks/dry_run.yaml
@@ -0,0 +1,7 @@
+---
+- name: Run tempest cleanup dry-run
+ become: yes
+ become_user: tempest
+ command: tox -evenv-tempest -- tempest cleanup --dry-run --debug
+ args:
+ chdir: "{{ devstack_base_dir }}/tempest"
diff --git a/roles/tempest-cleanup/tasks/dry_run_checker.py b/roles/tempest-cleanup/tasks/dry_run_checker.py
new file mode 100644
index 0000000..9cd9a85
--- /dev/null
+++ b/roles/tempest-cleanup/tasks/dry_run_checker.py
@@ -0,0 +1,71 @@
+# Copyright 2020 Red Hat, Inc
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Utility for content checking of a given dry_run.json file.
+"""
+
+import argparse
+import json
+import sys
+
+
+def get_parser():
+ parser = argparse.ArgumentParser(__doc__)
+ parser.add_argument('--is-empty', action="store_true", dest='is_empty',
+ default=False,
+ help="""Are values of a given dry_run.json empty?""")
+ parser.add_argument('--file', dest='file', default=None, metavar='PATH',
+ help="A path to a dry_run.json file.")
+ return parser
+
+
+def parse_arguments():
+ parser = get_parser()
+ args = parser.parse_args()
+ if not args.file:
+ sys.stderr.write('Path to a dry_run.json must be specified.\n')
+ sys.exit(1)
+ return args
+
+
+def load_json(path):
+ """Load json content from file addressed by path."""
+ try:
+ with open(path, 'rb') as json_file:
+ json_data = json.load(json_file)
+ except Exception as ex:
+ sys.exit(ex)
+ return json_data
+
+
+def are_values_empty(dry_run_content):
+ """Return true if values of dry_run.json are empty."""
+ for value in dry_run_content.values():
+ if value:
+ return False
+ return True
+
+
+def main():
+ args = parse_arguments()
+ content = load_json(args.file)
+ if args.is_empty:
+ if not are_values_empty(content):
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/roles/tempest-cleanup/tasks/main.yaml b/roles/tempest-cleanup/tasks/main.yaml
index 5444afc..c1d63f0 100644
--- a/roles/tempest-cleanup/tasks/main.yaml
+++ b/roles/tempest-cleanup/tasks/main.yaml
@@ -12,20 +12,35 @@
- when: dry_run
block:
- - name: Run tempest cleanup dry-run
- become: yes
- become_user: tempest
- command: tox -evenv-tempest -- tempest cleanup --dry-run --debug
- args:
- chdir: "{{ devstack_base_dir }}/tempest"
+ - import_tasks: dry_run.yaml
- name: Cat dry_run.json
command: cat "{{ devstack_base_dir }}/tempest/dry_run.json"
-- name: Run tempest cleanup
- become: yes
- become_user: tempest
- command: tox -evenv-tempest -- tempest cleanup --debug
- args:
- chdir: "{{ devstack_base_dir }}/tempest"
- when: not dry_run and not init_saved_state
+- when:
+ - not dry_run
+ - not init_saved_state
+ block:
+ - name: Run tempest cleanup
+ become: yes
+ become_user: tempest
+ command: tox -evenv-tempest -- tempest cleanup --debug
+ args:
+ chdir: "{{ devstack_base_dir }}/tempest"
+
+- when:
+ - run_tempest_fail_if_leaked_resources
+ - not init_saved_state
+ block:
+ # let's run dry run again, if haven't already, to check no leftover
+ # resources were left behind after the cleanup in the previous task
+ - import_tasks: dry_run.yaml
+ when: not dry_run
+
+ - name: Fail if any resources are leaked
+ become: yes
+ become_user: tempest
+ shell: |
+ python3 roles/tempest-cleanup/tasks/dry_run_checker.py --file {{ devstack_base_dir }}/tempest/dry_run.json --is-empty
+ args:
+ chdir: "{{ devstack_base_dir }}/tempest"
diff --git a/tempest/api/compute/admin/test_volume.py b/tempest/api/compute/admin/test_volume.py
new file mode 100644
index 0000000..487337e
--- /dev/null
+++ b/tempest/api/compute/admin/test_volume.py
@@ -0,0 +1,104 @@
+# Copyright 2020 Red Hat Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import six
+
+from tempest.api.compute import base
+from tempest.common import waiters
+from tempest import config
+from tempest.lib import decorators
+
+CONF = config.CONF
+
+
+class BaseAttachSCSIVolumeTest(base.BaseV2ComputeAdminTest):
+ """Base class for the admin volume tests in this module."""
+ create_default_network = True
+
+ @classmethod
+ def skip_checks(cls):
+ super(BaseAttachSCSIVolumeTest, cls).skip_checks()
+ if not CONF.service_available.cinder:
+ skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+
+ @classmethod
+ def setup_credentials(cls):
+ cls.prepare_instance_network()
+ super(BaseAttachSCSIVolumeTest, cls).setup_credentials()
+
+ def _create_image_with_custom_property(self, **kwargs):
+ """Wrapper utility that returns the custom image.
+
+ Creates a new image by downloading the default image's bits and
+ uploading them to a new image. Any kwargs are set as image properties
+ on the new image.
+
+ :param return image_id: The UUID of the newly created image.
+ """
+ image = self.image_client.show_image(CONF.compute.image_ref)
+ image_data = self.image_client.show_image_file(
+ CONF.compute.image_ref).data
+ image_file = six.BytesIO(image_data)
+ create_dict = {
+ 'container_format': image['container_format'],
+ 'disk_format': image['disk_format'],
+ 'min_disk': image['min_disk'],
+ 'min_ram': image['min_ram'],
+ 'visibility': 'public',
+ }
+ create_dict.update(kwargs)
+ new_image = self.image_client.create_image(**create_dict)
+ self.addCleanup(self.image_client.delete_image, new_image['id'])
+ self.image_client.store_image_file(new_image['id'], image_file)
+
+ return new_image['id']
+
+
+class AttachSCSIVolumeTestJSON(BaseAttachSCSIVolumeTest):
+ """Test attaching scsi volume to server"""
+
+ @decorators.idempotent_id('777e468f-17ca-4da4-b93d-b7dbf56c0494')
+ def test_attach_scsi_disk_with_config_drive(self):
+ """Test the attach/detach volume with config drive/scsi disk
+
+ Enable the config drive, followed by booting an instance
+ from an image with meta properties hw_cdrom: scsi and use
+ virtio-scsi mode with further asserting list volume attachments
+ in instance after attach and detach of the volume.
+ """
+ custom_img = self._create_image_with_custom_property(
+ hw_scsi_model='virtio-scsi',
+ hw_disk_bus='scsi',
+ hw_cdrom_bus='scsi')
+ server = self.create_test_server(image_id=custom_img,
+ config_drive=True,
+ wait_until='ACTIVE')
+ volume = self.create_volume()
+ attachment = self.attach_volume(server, volume)
+ waiters.wait_for_volume_resource_status(
+ self.volumes_client, attachment['volumeId'], 'in-use')
+ volume_after_attach = self.servers_client.list_volume_attachments(
+ server['id'])['volumeAttachments']
+ self.assertEqual(1, len(volume_after_attach),
+ "Failed to attach volume")
+ self.servers_client.detach_volume(
+ server['id'], attachment['volumeId'])
+ waiters.wait_for_volume_resource_status(
+ self.volumes_client, attachment['volumeId'], 'available')
+ volume_after_detach = self.servers_client.list_volume_attachments(
+ server['id'])['volumeAttachments']
+ self.assertEqual(0, len(volume_after_detach),
+ "Failed to detach volume")
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 8b847fc..bb0f5ad 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -171,8 +171,11 @@
cls.flavor_ref = CONF.compute.flavor_ref
cls.flavor_ref_alt = CONF.compute.flavor_ref_alt
cls.ssh_user = CONF.validation.image_ssh_user
+ cls.ssh_alt_user = CONF.validation.image_alt_ssh_user
cls.image_ssh_user = CONF.validation.image_ssh_user
+ cls.image_alt_ssh_user = CONF.validation.image_alt_ssh_user
cls.image_ssh_password = CONF.validation.image_ssh_password
+ cls.image_alt_ssh_password = CONF.validation.image_alt_ssh_password
@classmethod
def is_requested_microversion_compatible(cls, max_version):
@@ -634,6 +637,7 @@
cls.os_admin.availability_zone_client)
cls.admin_flavors_client = cls.os_admin.flavors_client
cls.admin_servers_client = cls.os_admin.servers_client
+ cls.image_client = cls.os_admin.image_client_v2
def create_flavor(self, ram, vcpus, disk, name=None,
is_public='True', **kwargs):
diff --git a/tempest/api/compute/security_groups/test_security_group_rules.py b/tempest/api/compute/security_groups/test_security_group_rules.py
index 59848f6..3c4daf6 100644
--- a/tempest/api/compute/security_groups/test_security_group_rules.py
+++ b/tempest/api/compute/security_groups/test_security_group_rules.py
@@ -35,16 +35,16 @@
cls.from_port = 22
cls.to_port = 22
- def setUp(cls):
- super(SecurityGroupRulesTestJSON, cls).setUp()
+ def setUp(self):
+ super(SecurityGroupRulesTestJSON, self).setUp()
- from_port = cls.from_port
- to_port = cls.to_port
+ from_port = self.from_port
+ to_port = self.to_port
group = {}
ip_range = {}
- cls.expected = {
+ self.expected = {
'parent_group_id': None,
- 'ip_protocol': cls.ip_protocol,
+ 'ip_protocol': self.ip_protocol,
'from_port': from_port,
'to_port': to_port,
'ip_range': ip_range,
diff --git a/tempest/api/compute/servers/test_device_tagging.py b/tempest/api/compute/servers/test_device_tagging.py
index a7e2187..58d4d7d 100644
--- a/tempest/api/compute/servers/test_device_tagging.py
+++ b/tempest/api/compute/servers/test_device_tagging.py
@@ -338,7 +338,9 @@
found_devices = [d['tags'][0] for d in md_dict['devices']
if d.get('tags')]
try:
- self.assertItemsEqual(found_devices, ['nic-tag', 'volume-tag'])
+ self.assertEqual(
+ sorted(found_devices),
+ sorted(['nic-tag', 'volume-tag']))
return True
except Exception:
return False
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 4db6987..4527aa9 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -160,15 +160,6 @@
"""
self._test_reboot_server('HARD')
- @decorators.skip_because(bug="1014647")
- @decorators.idempotent_id('4640e3ef-a5df-482e-95a1-ceeeb0faa84d')
- def test_reboot_server_soft(self):
- """Test soft rebooting server
-
- The server should be signaled to reboot gracefully.
- """
- self._test_reboot_server('SOFT')
-
@decorators.idempotent_id('1d1c9104-1b0a-11e7-a3d4-fa163e65f5ce')
def test_remove_server_all_security_groups(self):
"""Test removing all security groups from server"""
@@ -237,7 +228,7 @@
# 4.Plain username/password auth, if a password was given.
linux_client = remote_client.RemoteClient(
self.get_server_ip(rebuilt_server, validation_resources),
- self.ssh_user,
+ self.ssh_alt_user,
password,
validation_resources['keypair']['private_key'],
server=rebuilt_server,
@@ -319,7 +310,7 @@
self.os_primary)
linux_client = remote_client.RemoteClient(
self.get_server_ip(server, validation_resources),
- self.ssh_user,
+ self.ssh_alt_user,
password=None,
pkey=validation_resources['keypair']['private_key'],
server=server,
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index 5445113..c222893 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -16,6 +16,7 @@
import testtools
from tempest.api.compute import base
+from tempest.common import utils
from tempest.common import waiters
from tempest import config
from tempest.lib.common.utils import data_utils
@@ -189,6 +190,7 @@
self._test_stable_device_rescue(server_id, rescue_image_id)
@decorators.idempotent_id('a3772b42-00bf-4310-a90b-1cc6fd3e7eab')
+ @utils.services('volume')
def test_stable_device_rescue_disk_virtio_with_volume_attached(self):
"""Test rescuing server with volume attached
@@ -214,6 +216,13 @@
min_microversion = '2.87'
+ @classmethod
+ def skip_checks(cls):
+ super(ServerBootFromVolumeStableRescueTest, cls).skip_checks()
+ if not CONF.service_available.cinder:
+ skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+
@decorators.attr(type='slow')
@decorators.idempotent_id('48f123cb-922a-4065-8db6-b9a9074a556b')
def test_stable_device_rescue_bfv_blank_volume(self):
diff --git a/tempest/api/identity/admin/v3/test_application_credentials.py b/tempest/api/identity/admin/v3/test_application_credentials.py
index c9cafd8..f5b0356 100644
--- a/tempest/api/identity/admin/v3/test_application_credentials.py
+++ b/tempest/api/identity/admin/v3/test_application_credentials.py
@@ -37,7 +37,7 @@
secret = app_cred['secret']
# Check that the application credential is functional
- token_id, resp = self.non_admin_token.get_token(
+ _, resp = self.non_admin_token.get_token(
app_cred_id=app_cred['id'],
app_cred_secret=secret,
auth_data=True
diff --git a/tempest/api/identity/v3/test_application_credentials.py b/tempest/api/identity/v3/test_application_credentials.py
index 77ad720..06734aa 100644
--- a/tempest/api/identity/v3/test_application_credentials.py
+++ b/tempest/api/identity/v3/test_application_credentials.py
@@ -51,7 +51,7 @@
self.assertNotIn('secret', app_cred)
# Check that the application credential is functional
- token_id, resp = self.non_admin_token.get_token(
+ _, resp = self.non_admin_token.get_token(
app_cred_id=app_cred['id'],
app_cred_secret=secret,
auth_data=True
diff --git a/tempest/api/image/v2/admin/test_images.py b/tempest/api/image/v2/admin/test_images.py
index 7e13d7f..ad68d82 100644
--- a/tempest/api/image/v2/admin/test_images.py
+++ b/tempest/api/image/v2/admin/test_images.py
@@ -13,10 +13,16 @@
# License for the specific language governing permissions and limitations
# under the License.
+import six
+
from tempest.api.image import base
+from tempest.common import waiters
+from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
+CONF = config.CONF
+
class BasicOperationsImagesAdminTest(base.BaseV2ImageAdminTest):
""""Test image operations about image owner"""
@@ -52,3 +58,65 @@
self.assertEqual(random_id_2, updated_image_info['owner'])
self.assertNotEqual(created_image_info['owner'],
updated_image_info['owner'])
+
+
+class ImportCopyImagesTest(base.BaseV2ImageAdminTest):
+ """Test the import copy-image operations"""
+
+ @classmethod
+ def skip_checks(cls):
+ super(ImportCopyImagesTest, cls).skip_checks()
+ if not CONF.image_feature_enabled.import_image:
+ skip_msg = (
+ "%s skipped as image import is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+
+ @decorators.idempotent_id('9b3b644e-03d1-11eb-a036-fa163e2eaf49')
+ def test_image_copy_image_import(self):
+ """Test 'copy-image' import functionalities
+
+ Create image, import image with copy-image method and
+ verify that import succeeded.
+ """
+ available_stores = self.get_available_stores()
+ available_import_methods = self.client.info_import()[
+ 'import-methods']['value']
+ # NOTE(gmann): Skip if copy-image import method and multistore
+ # are not available.
+ if ('copy-image' not in available_import_methods or
+ not available_stores):
+ raise self.skipException('Either copy-image import method or '
+ 'multistore is not available')
+ uuid = data_utils.rand_uuid()
+ image_name = data_utils.rand_name('copy-image')
+ container_format = CONF.image.container_formats[0]
+ disk_format = CONF.image.disk_formats[0]
+ image = self.create_image(name=image_name,
+ container_format=container_format,
+ disk_format=disk_format,
+ visibility='private',
+ ramdisk_id=uuid)
+ self.assertEqual('queued', image['status'])
+
+ file_content = data_utils.random_bytes()
+ image_file = six.BytesIO(file_content)
+ self.client.store_image_file(image['id'], image_file)
+
+ body = self.client.show_image(image['id'])
+ self.assertEqual(image['id'], body['id'])
+ self.assertEqual(len(file_content), body.get('size'))
+ self.assertEqual('active', body['status'])
+
+ # Copy image to all the stores. In case of all_stores request
+ # glance will skip the stores where image is already available.
+ self.admin_client.image_import(image['id'], method='copy-image',
+ all_stores=True,
+ all_stores_must_succeed=False)
+
+ # Wait for copy to finished on all stores.
+ failed_stores = waiters.wait_for_image_copied_to_stores(
+ self.client, image['id'])
+ # Assert if copy is failed on any store.
+ self.assertEqual(0, len(failed_stores),
+ "Failed to copy the following stores: %s" %
+ str(failed_stores))
diff --git a/tempest/api/network/test_floating_ips.py b/tempest/api/network/test_floating_ips.py
index c32d3c1..eb31d24 100644
--- a/tempest/api/network/test_floating_ips.py
+++ b/tempest/api/network/test_floating_ips.py
@@ -66,7 +66,7 @@
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):
+ for _ in range(2):
port = cls.create_port(cls.network)
cls.ports.append(port)
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index c5334a9..eb2ef7f 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -92,7 +92,7 @@
# create object in container
object_name = data_utils.rand_name(name='TestSyncObject')
data = object_name[::-1].encode() # Raw data, we need bytes
- resp, _ = obj_client[0].create_object(cont[0], object_name, data)
+ obj_client[0].create_object(cont[0], object_name, data)
self.objects.append(object_name)
# wait until container contents list is not empty
diff --git a/tempest/api/object_storage/test_crossdomain.py b/tempest/api/object_storage/test_crossdomain.py
index c611ed6..365dc78 100644
--- a/tempest/api/object_storage/test_crossdomain.py
+++ b/tempest/api/object_storage/test_crossdomain.py
@@ -32,9 +32,6 @@
cls.xml_end = "</cross-domain-policy>"
- def setUp(self):
- super(CrossdomainTest, self).setUp()
-
@decorators.idempotent_id('d1b8b031-b622-4010-82f9-ff78a9e915c7')
@utils.requires_ext(extension='crossdomain', service='object')
def test_get_crossdomain_policy(self):
diff --git a/tempest/api/object_storage/test_healthcheck.py b/tempest/api/object_storage/test_healthcheck.py
index f5e2443..d4a6a9f2 100644
--- a/tempest/api/object_storage/test_healthcheck.py
+++ b/tempest/api/object_storage/test_healthcheck.py
@@ -21,9 +21,6 @@
class HealthcheckTest(base.BaseObjectTest):
"""Test healthcheck"""
- def setUp(self):
- super(HealthcheckTest, self).setUp()
-
@decorators.idempotent_id('db5723b1-f25c-49a9-bfeb-7b5640caf337')
def test_get_healthcheck(self):
"""Test getting healthcheck"""
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index 4ecbcad..fc9b1a2 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -182,6 +182,7 @@
self.assertEqual(data, body)
@decorators.idempotent_id('4f84422a-e2f2-4403-b601-726a4220b54e')
+ @decorators.skip_because(bug='1905432')
def test_create_object_with_transfer_encoding(self):
"""Test creating object with transfer_encoding"""
object_name = data_utils.rand_name(name='TestObject')
@@ -770,11 +771,11 @@
headers = {}
headers['X-Copy-From'] = "%s/%s" % (str(self.container_name),
str(object_name))
- resp, body = self.object_client.create_object(self.container_name,
- object_name,
- data=None,
- metadata=metadata,
- headers=headers)
+ resp, _ = self.object_client.create_object(self.container_name,
+ object_name,
+ data=None,
+ metadata=metadata,
+ headers=headers)
self.assertHeaders(resp, 'Object', 'PUT')
# check the content type
diff --git a/tempest/api/object_storage/test_object_slo.py b/tempest/api/object_storage/test_object_slo.py
index 7e553ca..664bbc8 100644
--- a/tempest/api/object_storage/test_object_slo.py
+++ b/tempest/api/object_storage/test_object_slo.py
@@ -165,6 +165,6 @@
self.assertHeaders(resp, 'Object', 'DELETE')
- resp, body = self.container_client.list_container_objects(
+ resp, _ = self.container_client.list_container_objects(
self.container_name)
self.assertEqual(int(resp['x-container-object-count']), 0)
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index edb9d16..da3a4a9 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -64,7 +64,7 @@
def create_test_server(clients, validatable=False, validation_resources=None,
tenant_network=None, wait_until=None,
volume_backed=False, name=None, flavor=None,
- image_id=None, **kwargs):
+ image_id=None, wait_for_sshable=True, **kwargs):
"""Common wrapper utility returning a test server.
This method is a common wrapper returning a test server that can be
@@ -100,6 +100,8 @@
CONF.compute.flavor_ref will be used instead.
:param image_id: ID of the image to be used to provision the server. If not
defined, CONF.compute.image_ref will be used instead.
+ :param wait_for_sshable: Check server's console log and wait until it will
+ be ready to login.
:returns: a tuple
"""
@@ -270,6 +272,10 @@
LOG.exception('Server %s failed to delete in time',
server['id'])
+ if (validatable and CONF.compute_feature_enabled.console_output and
+ wait_for_sshable):
+ waiters.wait_for_guest_os_boot(clients.servers_client, server['id'])
+
return body, servers
diff --git a/tempest/common/utils/__init__.py b/tempest/common/utils/__init__.py
index 167bf5b..914acf7 100644
--- a/tempest/common/utils/__init__.py
+++ b/tempest/common/utils/__init__.py
@@ -128,3 +128,18 @@
if extension_name in config_dict[service]:
return True
return False
+
+
+def is_network_feature_enabled(feature_name):
+ """A function that will check the list of available network features
+
+ """
+ list_of_features = CONF.network_feature_enabled.available_features
+
+ if not list_of_features:
+ return False
+ if list_of_features[0] == 'all':
+ return True
+ if feature_name in list_of_features:
+ return True
+ return False
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index cc8778b..17796df 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -124,12 +124,18 @@
raise lib_exc.DeleteErrorException(
"Server %s failed to delete and is in ERROR status" %
server_id)
+
if server_status == 'SOFT_DELETED':
# Soft-deleted instances need to be forcibly deleted to
# prevent some test cases from failing.
LOG.debug("Automatically force-deleting soft-deleted server %s",
server_id)
- client.force_delete_server(server_id)
+ try:
+ client.force_delete_server(server_id)
+ except lib_exc.NotFound:
+ # The instance may have been deleted so ignore
+ # NotFound exception
+ return
if int(time.time()) - start_time >= client.build_timeout:
raise lib_exc.TimeoutException
@@ -209,6 +215,37 @@
raise lib_exc.TimeoutException(message)
+def wait_for_image_copied_to_stores(client, image_id):
+ """Waits for an image to be copied on all requested stores.
+
+ The client should also have build_interval and build_timeout attributes.
+ This return the list of stores where copy is failed.
+ """
+
+ start = int(time.time())
+ store_left = []
+ while int(time.time()) - start < client.build_timeout:
+ image = client.show_image(image_id)
+ store_left = image.get('os_glance_importing_to_stores')
+ # NOTE(danms): If os_glance_importing_to_stores is None, then
+ # we've raced with the startup of the task and should continue
+ # to wait.
+ if store_left is not None and not store_left:
+ return image['os_glance_failed_import']
+ if image['status'].lower() == 'killed':
+ raise exceptions.ImageKilledException(image_id=image_id,
+ status=image['status'])
+
+ time.sleep(client.build_interval)
+
+ message = ('Image %(image_id)s failed to finish the copy operation '
+ 'on stores: %s' % str(store_left))
+ caller = test_utils.find_test_caller()
+ if caller:
+ message = '(%s) %s' % (caller, message)
+ raise lib_exc.TimeoutException(message)
+
+
def wait_for_volume_resource_status(client, resource_id, status):
"""Waits for a volume resource to reach a given status.
@@ -400,3 +437,20 @@
'the required time (%s s)' % (port_id, server_id,
client.build_timeout))
raise lib_exc.TimeoutException(message)
+
+
+def wait_for_guest_os_boot(client, server_id):
+ start_time = int(time.time())
+ while True:
+ console_output = client.get_console_output(server_id)['output']
+ for line in console_output.split('\n'):
+ if 'login:' in line.lower():
+ return
+ if int(time.time()) - start_time >= client.build_timeout:
+ LOG.info("Guest OS on server %s probably isn't ready or its "
+ "console log can't be parsed properly. If guest OS "
+ "isn't ready, that may cause problems with SSH to "
+ "the server.",
+ server_id)
+ return
+ time.sleep(client.build_interval)
diff --git a/tempest/config.py b/tempest/config.py
index 2f2c2e9..26a7fab 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -790,6 +790,13 @@
"entry all which indicates every extension is enabled. "
"Empty list indicates all extensions are disabled. "
"To get the list of extensions run: 'neutron ext-list'"),
+ cfg.ListOpt('available_features',
+ default=['all'],
+ help="A list of available network features with a special "
+ "entry all that indicates every feature is available. "
+ "Empty list indicates all features are disabled."
+ "This list can contain features that are not "
+ "discoverable through API."),
cfg.BoolOpt('ipv6_subnet_attributes',
default=False,
help="Allow the execution of IPv6 subnet tests that use "
@@ -858,10 +865,17 @@
cfg.StrOpt('image_ssh_user',
default="root",
help="User name used to authenticate to an instance."),
+ cfg.StrOpt('image_alt_ssh_user',
+ default="root",
+ help="User name used to authenticate to an alt instance."),
cfg.StrOpt('image_ssh_password',
default="password",
help="Password used to authenticate to an instance.",
secret=True),
+ cfg.StrOpt('image_alt_ssh_password',
+ default="password",
+ help="Password used to authenticate to an alt instance.",
+ secret=True),
cfg.StrOpt('ssh_shell_prologue',
default="set -eu -o pipefail; PATH=$$PATH:/sbin:/usr/sbin;",
help="Shell fragments to use before executing a command "
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index 6723516..e82b58f 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -646,7 +646,7 @@
For a full list of available parameters, please refer to the official
API reference:
- https://docs.openstack.org/api-ref/compute/#create-remote-console
+ https://docs.openstack.org/api-ref/compute/#create-console
"""
param = {
'remote_console': {
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 218bcb6..eb5b845 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -625,7 +625,7 @@
LOG.debug("image:%s", image['id'])
return image['id']
- def _log_console_output(self, servers=None, client=None):
+ def _log_console_output(self, servers=None, client=None, **kwargs):
"""Console log output"""
if not CONF.compute_feature_enabled.console_output:
LOG.debug('Console output not supported, cannot log')
@@ -637,7 +637,7 @@
for server in servers:
try:
console_output = client.get_console_output(
- server['id'])['output']
+ server['id'], **kwargs)['output']
LOG.debug('Console output for %s\nbody=\n%s',
server['id'], console_output)
except lib_exc.NotFound:
@@ -697,17 +697,20 @@
image_name, server['name'])
return snapshot_image
- def nova_volume_attach(self, server, volume_to_attach):
+ def nova_volume_attach(self, server, volume_to_attach, **kwargs):
"""Compute volume attach
This utility attaches volume from compute and waits for the
volume status to be 'in-use' state.
"""
volume = self.servers_client.attach_volume(
- server['id'], volumeId=volume_to_attach['id'])['volumeAttachment']
+ server['id'], volumeId=volume_to_attach['id'],
+ **kwargs)['volumeAttachment']
self.assertEqual(volume_to_attach['id'], volume['id'])
waiters.wait_for_volume_resource_status(self.volumes_client,
volume['id'], 'in-use')
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.nova_volume_detach, server, volume)
# Return the updated volume after the attachment
return self.volumes_client.show_volume(volume['id'])['volume']
@@ -810,13 +813,15 @@
LOG.exception(extra_msg)
raise
- def create_floating_ip(self, server, pool_name=None):
+ def create_floating_ip(self, server, pool_name=None, **kwargs):
"""Create a floating IP and associates to a server on Nova"""
if not pool_name:
pool_name = CONF.network.floating_network_name
+
floating_ip = (self.compute_floating_ips_client.
- create_floating_ip(pool=pool_name)['floating_ip'])
+ create_floating_ip(pool=pool_name,
+ **kwargs)['floating_ip'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.compute_floating_ips_client.delete_floating_ip,
floating_ip['id'])
@@ -865,18 +870,22 @@
ssh_client.exec_command('sudo umount %s' % mount_path)
return timestamp
- def get_server_ip(self, server):
+ def get_server_ip(self, server, **kwargs):
"""Get the server fixed or floating IP.
Based on the configuration we're in, return a correct ip
address for validating that a guest is up.
+
+ If CONF.validation.connect_method is floating, then
+ a floating ip will be created passing kwargs as additional
+ argument.
"""
if CONF.validation.connect_method == 'floating':
# The tests calling this method don't have a floating IP
# and can't make use of the validation resources. So the
# method is creating the floating IP there.
- return self.create_floating_ip(server)['ip']
+ return self.create_floating_ip(server, **kwargs)['ip']
elif CONF.validation.connect_method == 'fixed':
# Determine the network name to look for based on config or creds
# provider network resources.
@@ -916,14 +925,14 @@
keypair=None,
security_group=None,
delete_on_termination=False,
- name=None):
+ name=None, **kwargs):
"""Boot instance from resource
This wrapper utility boots instance from resource with block device
mapping with source info passed in arguments
"""
- create_kwargs = dict()
+ create_kwargs = dict({'image_id': ''})
if keypair:
create_kwargs['key_name'] = keypair['name']
if security_group:
@@ -935,8 +944,9 @@
delete_on_termination=delete_on_termination))
if name:
create_kwargs['name'] = name
+ create_kwargs.update(kwargs)
- return self.create_server(image_id='', **create_kwargs)
+ return self.create_server(**create_kwargs)
def create_volume_from_image(self):
"""Create volume from image"""
diff --git a/tempest/scenario/test_aggregates_basic_ops.py b/tempest/scenario/test_aggregates_basic_ops.py
index b515639..58e234f 100644
--- a/tempest/scenario/test_aggregates_basic_ops.py
+++ b/tempest/scenario/test_aggregates_basic_ops.py
@@ -51,10 +51,27 @@
return aggregate
def _get_host_name(self):
+ # Find a host that has not been added to other availability zone,
+ # for one host can't be added to different availability zones.
svc_list = self.services_client.list_services(
binary='nova-compute')['services']
self.assertNotEmpty(svc_list)
- return svc_list[0]['host']
+ hosts_available = []
+ for host in svc_list:
+ if (host['state'] == 'up' and host['status'] == 'enabled'):
+ hosts_available.append(host['host'])
+ aggregates = self.aggregates_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 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)
+ return hosts[0]
def _add_host(self, aggregate_id, host):
aggregate = (self.aggregates_client.add_host(aggregate_id, host=host)
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index e26dc9d..dbab212 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -80,8 +80,8 @@
return floating_ip
def _check_network_connectivity(self, server, keypair, floating_ip,
- should_connect=True):
- username = CONF.validation.image_ssh_user
+ should_connect=True,
+ username=CONF.validation.image_ssh_user):
private_key = keypair['private_key']
self.check_tenant_network_connectivity(
server, username, private_key,
@@ -95,12 +95,13 @@
'Public network connectivity check failed',
server)
- def _wait_server_status_and_check_network_connectivity(self, server,
- keypair,
- floating_ip):
+ def _wait_server_status_and_check_network_connectivity(
+ self, server, keypair, floating_ip,
+ username=CONF.validation.image_ssh_user):
waiters.wait_for_server_status(self.servers_client, server['id'],
'ACTIVE')
- self._check_network_connectivity(server, keypair, floating_ip)
+ self._check_network_connectivity(server, keypair, floating_ip,
+ username=username)
@decorators.idempotent_id('61f1aa9a-1573-410e-9054-afa557cab021')
@decorators.attr(type='slow')
@@ -137,10 +138,11 @@
server = self._setup_server(keypair)
floating_ip = self._setup_network(server, keypair)
image_ref_alt = CONF.compute.image_ref_alt
+ username_alt = CONF.validation.image_alt_ssh_user
self.servers_client.rebuild_server(server['id'],
image_ref=image_ref_alt)
self._wait_server_status_and_check_network_connectivity(
- server, keypair, floating_ip)
+ server, keypair, floating_ip, username_alt)
@decorators.idempotent_id('2b2642db-6568-4b35-b812-eceed3fa20ce')
@testtools.skipUnless(CONF.compute_feature_enabled.pause,
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
index 73924bd..f03c7cc 100755
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -131,6 +131,36 @@
mock.call('server_id')])
sleep.assert_called_once_with(client.build_interval)
+ def test_wait_for_guest_os_boot(self):
+ get_console_output = mock.Mock(
+ side_effect=[
+ {'output': 'os not ready yet\n'},
+ {'output': 'login:\n'}
+ ])
+ client = self.mock_client(get_console_output=get_console_output)
+ self.patch('time.time', return_value=0.)
+ sleep = self.patch('time.sleep')
+
+ with mock.patch.object(waiters.LOG, "info") as log_info:
+ waiters.wait_for_guest_os_boot(client, 'server_id')
+
+ get_console_output.assert_has_calls([
+ mock.call('server_id'), mock.call('server_id')])
+ sleep.assert_called_once_with(client.build_interval)
+ log_info.assert_not_called()
+
+ def test_wait_for_guest_os_boot_timeout(self):
+ get_console_output = mock.Mock(
+ return_value={'output': 'os not ready yet\n'})
+ client = self.mock_client(get_console_output=get_console_output)
+ self.patch('time.time', side_effect=[0., client.build_timeout + 1.])
+ self.patch('time.sleep')
+
+ with mock.patch.object(waiters.LOG, "info") as log_info:
+ waiters.wait_for_guest_os_boot(client, 'server_id')
+
+ log_info.assert_called_once()
+
class TestVolumeWaiters(base.TestCase):
vol_migrating_src_host = {
diff --git a/tools/tempest-integrated-gate-networking-blacklist.txt b/tools/tempest-integrated-gate-networking-blacklist.txt
index 97808d9..263b2e4 100644
--- a/tools/tempest-integrated-gate-networking-blacklist.txt
+++ b/tools/tempest-integrated-gate-networking-blacklist.txt
@@ -17,8 +17,3 @@
tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_boot_server_from_encrypted_volume_luks
tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_image_defined_boot_from_volume
tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_create_server_from_volume_snapshot
-
-# TODO(gmann): Remove these test skip once nova bug #1882521 is solved
-tempest.api.compute.volumes.test_attach_volume.AttachVolumeMultiAttachTest.test_resize_server_with_multiattached_volume
-tempest.api.compute.servers.test_server_rescue_negative.ServerRescueNegativeTestJSON
-tempest.api.compute.servers.test_server_rescue.ServerStableDeviceRescueTest.test_stable_device_rescue_disk_virtio_with_volume_attached
diff --git a/tools/tempest-integrated-gate-placement-blacklist.txt b/tools/tempest-integrated-gate-placement-blacklist.txt
index 657bda2..efba796 100644
--- a/tools/tempest-integrated-gate-placement-blacklist.txt
+++ b/tools/tempest-integrated-gate-placement-blacklist.txt
@@ -17,8 +17,3 @@
tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_boot_server_from_encrypted_volume_luks
tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_image_defined_boot_from_volume
tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_create_server_from_volume_snapshot
-
-# TODO(gmann): Remove these test skip once nova bug #1882521 is solved
-tempest.api.compute.volumes.test_attach_volume.AttachVolumeMultiAttachTest.test_resize_server_with_multiattached_volume
-tempest.api.compute.servers.test_server_rescue_negative.ServerRescueNegativeTestJSON
-tempest.api.compute.servers.test_server_rescue.ServerStableDeviceRescueTest.test_stable_device_rescue_disk_virtio_with_volume_attached
diff --git a/tools/tempest-integrated-gate-storage-blacklist.txt b/tools/tempest-integrated-gate-storage-blacklist.txt
index cbd3e9d..1ef6bb5 100644
--- a/tools/tempest-integrated-gate-storage-blacklist.txt
+++ b/tools/tempest-integrated-gate-storage-blacklist.txt
@@ -12,8 +12,3 @@
tempest.scenario.test_network_basic_ops.TestNetworkBasicOps
tempest.scenario.test_network_v6.TestGettingAddress
tempest.scenario.test_security_groups_basic_ops.TestSecurityGroupsBasicOps
-
-# TODO(gmann): Remove these test skip once nova bug #1882521 is solved
-tempest.api.compute.volumes.test_attach_volume.AttachVolumeMultiAttachTest.test_resize_server_with_multiattached_volume
-tempest.api.compute.servers.test_server_rescue_negative.ServerRescueNegativeTestJSON
-tempest.api.compute.servers.test_server_rescue.ServerStableDeviceRescueTest.test_stable_device_rescue_disk_virtio_with_volume_attached
diff --git a/tox.ini b/tox.ini
index 2ea8129..d8e059a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -23,7 +23,7 @@
OS_STDERR_CAPTURE=1
OS_TEST_TIMEOUT=160
PYTHONWARNINGS=default::DeprecationWarning,ignore::DeprecationWarning:distutils,ignore::DeprecationWarning:site
-passenv = OS_STDOUT_CAPTURE OS_STDERR_CAPTURE OS_TEST_TIMEOUT OS_TEST_LOCK_PATH TEMPEST_CONFIG TEMPEST_CONFIG_DIR http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY ZUUL_CACHE_DIR REQUIREMENTS_PIP_LOCATION GENERATE_TEMPEST_PLUGIN_LIST GABBI_TEMPEST_PATH
+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}
whitelist_externals = *