Merge "Do not run openstacksdk-functional-devstack on victoria"
diff --git a/releasenotes/notes/add-ssh-allow-agent-2dee6448fd250e50.yaml b/releasenotes/notes/add-ssh-allow-agent-2dee6448fd250e50.yaml
new file mode 100644
index 0000000..33f11ce
--- /dev/null
+++ b/releasenotes/notes/add-ssh-allow-agent-2dee6448fd250e50.yaml
@@ -0,0 +1,10 @@
+---
+features:
+ - |
+ Adds a ``ssh_allow_agent`` parameter to the ``RemoteClient`` class
+ wrapper and the direct ssh ``Client`` class to allow a caller to
+ explicitly request that the SSH Agent is not consulted for
+ authentication. This is useful if your attempting explicit password
+ based authentication as ``paramiko``, the underlying library used for
+ SSH, defaults to utilizing an ssh-agent process before attempting
+ password authentication.
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index 2826f56..1cb8004 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -140,6 +140,7 @@
LOG.info("Live migrate back to source %s", source_host)
self._live_migrate(server_id, source_host, state, volume_backed)
+ @decorators.attr(type='multinode')
@decorators.idempotent_id('1dce86b8-eb04-4c03-a9d8-9c1dc3ee0c7b')
@testtools.skipUnless(CONF.compute_feature_enabled.
block_migration_for_live_migration,
@@ -148,6 +149,7 @@
"""Test live migrating an active server"""
self._test_live_migration()
+ @decorators.attr(type='multinode')
@decorators.idempotent_id('1e107f21-61b2-4988-8f22-b196e938ab88')
@testtools.skipUnless(CONF.compute_feature_enabled.
block_migration_for_live_migration,
@@ -158,6 +160,7 @@
"""Test live migrating a paused server"""
self._test_live_migration(state='PAUSED')
+ @decorators.attr(type='multinode')
@testtools.skipUnless(CONF.compute_feature_enabled.
volume_backed_live_migration,
'Volume-backed live migration not available')
@@ -167,6 +170,7 @@
"""Test live migrating an active server booted from volume"""
self._test_live_migration(volume_backed=True)
+ @decorators.attr(type='multinode')
@decorators.idempotent_id('e19c0cc6-6720-4ed8-be83-b6603ed5c812')
@testtools.skipIf(not CONF.compute_feature_enabled.
block_migration_for_live_migration,
@@ -253,6 +257,7 @@
port = self.ports_client.show_port(port_id)['port']
return port['status'] == 'ACTIVE'
+ @decorators.attr(type='multinode')
@decorators.idempotent_id('0022c12e-a482-42b0-be2d-396b5f0cffe3')
@utils.requires_ext(service='network', extension='trunk')
@utils.services('network')
@@ -297,6 +302,7 @@
min_microversion = '2.6'
max_microversion = 'latest'
+ @decorators.attr(type='multinode')
@decorators.idempotent_id('6190af80-513e-4f0f-90f2-9714e84955d7')
@testtools.skipUnless(CONF.compute_feature_enabled.serial_console,
'Serial console not supported.')
diff --git a/tempest/api/compute/admin/test_migrations.py b/tempest/api/compute/admin/test_migrations.py
index 89152d6..b3d2833 100644
--- a/tempest/api/compute/admin/test_migrations.py
+++ b/tempest/api/compute/admin/test_migrations.py
@@ -158,6 +158,7 @@
dst_host = self.get_host_for_server(server['id'])
assert_func(src_host, dst_host)
+ @decorators.attr(type='multinode')
@decorators.idempotent_id('4bf0be52-3b6f-4746-9a27-3143636fe30d')
@testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
'Cold migration not available.')
@@ -165,6 +166,7 @@
"""Test cold migrating server and then confirm the migration"""
self._test_cold_migrate_server(revert=False)
+ @decorators.attr(type='multinode')
@decorators.idempotent_id('caa1aa8b-f4ef-4374-be0d-95f001c2ac2d')
@testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
'Cold migration not available.')
diff --git a/tempest/api/compute/admin/test_servers_on_multinodes.py b/tempest/api/compute/admin/test_servers_on_multinodes.py
index 9082306..013e7d8 100644
--- a/tempest/api/compute/admin/test_servers_on_multinodes.py
+++ b/tempest/api/compute/admin/test_servers_on_multinodes.py
@@ -61,6 +61,7 @@
return hosts
+ @decorators.attr(type='multinode')
@decorators.idempotent_id('26a9d5df-6890-45f2-abc4-a659290cb130')
@testtools.skipUnless(
compute.is_scheduler_filter_enabled("SameHostFilter"),
@@ -73,6 +74,7 @@
host02 = self.get_host_for_server(server02)
self.assertEqual(self.host01, host02)
+ @decorators.attr(type='multinode')
@decorators.idempotent_id('cc7ca884-6e3e-42a3-a92f-c522fcf25e8e')
@testtools.skipUnless(
compute.is_scheduler_filter_enabled("DifferentHostFilter"),
@@ -85,6 +87,7 @@
host02 = self.get_host_for_server(server02)
self.assertNotEqual(self.host01, host02)
+ @decorators.attr(type='multinode')
@decorators.idempotent_id('7869cc84-d661-4e14-9f00-c18cdc89cf57')
@testtools.skipUnless(
compute.is_scheduler_filter_enabled("DifferentHostFilter"),
@@ -97,6 +100,7 @@
host02 = self.get_host_for_server(server02)
self.assertNotEqual(self.host01, host02)
+ @decorators.attr(type='multinode')
@decorators.idempotent_id('f8bd0867-e459-45f5-ba53-59134552fe04')
@testtools.skipUnless(
compute.is_scheduler_filter_enabled("ServerGroupAntiAffinityFilter"),
@@ -112,6 +116,7 @@
self.assertNotEqual(hostnames[0], hostnames[1],
'Servers are on the same host: %s' % hosts)
+ @decorators.attr(type='multinode')
@decorators.idempotent_id('9d2e924a-baf4-11e7-b856-fa163e65f5ce')
@testtools.skipUnless(
compute.is_scheduler_filter_enabled("ServerGroupAffinityFilter"),
@@ -152,6 +157,7 @@
waiters.wait_for_server_status(self.servers_client, server['id'],
'ACTIVE')
+ @decorators.attr(type='multinode')
@decorators.idempotent_id('b5cc0889-50c2-46a0-b8ff-b5fb4c3a6e20')
def test_unshelve_to_specific_host(self):
"""Test unshelve to a specific host, new behavior introduced in
diff --git a/tempest/api/compute/admin/test_volume.py b/tempest/api/compute/admin/test_volume.py
index 2fcd053..e7c931e 100644
--- a/tempest/api/compute/admin/test_volume.py
+++ b/tempest/api/compute/admin/test_volume.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import io
-
from tempest.api.compute import base
from tempest.common import waiters
from tempest import config
@@ -49,9 +47,11 @@
:param return image_id: The UUID of the newly created image.
"""
image = self.admin_image_client.show_image(CONF.compute.image_ref)
- image_data = self.admin_image_client.show_image_file(
- CONF.compute.image_ref).data
- image_file = io.BytesIO(image_data)
+ # NOTE(danms): We need to stream this, so chunked=True means we get
+ # back a urllib3.HTTPResponse and have to carefully pass it to
+ # store_image_file() to upload it in pieces.
+ image_data_resp = self.admin_image_client.show_image_file(
+ CONF.compute.image_ref, chunked=True)
create_dict = {
'container_format': image['container_format'],
'disk_format': image['disk_format'],
@@ -60,24 +60,22 @@
'visibility': 'public',
}
create_dict.update(kwargs)
- new_image = self.admin_image_client.create_image(**create_dict)
- self.addCleanup(self.admin_image_client.wait_for_resource_deletion,
- new_image['id'])
- self.addCleanup(self.admin_image_client.delete_image, new_image['id'])
- self.admin_image_client.store_image_file(new_image['id'], image_file)
-
+ try:
+ new_image = self.admin_image_client.create_image(**create_dict)
+ self.addCleanup(self.admin_image_client.wait_for_resource_deletion,
+ new_image['id'])
+ self.addCleanup(
+ self.admin_image_client.delete_image, new_image['id'])
+ self.admin_image_client.store_image_file(new_image['id'],
+ image_data_resp)
+ finally:
+ image_data_resp.release_conn()
return new_image['id']
class AttachSCSIVolumeTestJSON(BaseAttachSCSIVolumeTest):
"""Test attaching scsi volume to server"""
- # NOTE(gibi): https://bugs.launchpad.net/nova/+bug/2002951/comments/5 shows
- # that calling _create_image_with_custom_property can cause excessive
- # memory usage in the test executor as it downloads a glance image in
- # memory. This is causing gate failures so the test is disabled. One
- # potential fix is to do a chunked data download / upload loop instead.
- @decorators.skip_because(bug="2002951", condition=True)
@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
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index d590668..1d05f13 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -16,6 +16,7 @@
import io
import random
+import time
from oslo_log import log as logging
from tempest.api.image import base
@@ -27,6 +28,7 @@
CONF = config.CONF
LOG = logging.getLogger(__name__)
+BAD_REQUEST_RETRIES = 3
class ImportImagesTest(base.BaseV2ImageTest):
@@ -817,8 +819,21 @@
# Add a new location
new_loc = {'metadata': {'foo': 'bar'},
'url': CONF.image.http_image}
- self.client.update_image(image['id'], [
- dict(add='/locations/-', value=new_loc)])
+
+ # NOTE(danms): If glance was unable to fetch the remote image via
+ # HTTP, it will return BadRequest. Because this can be transient in
+ # CI, we try this a few times before we agree that it has failed
+ # for a reason worthy of failing the test.
+ for i in range(BAD_REQUEST_RETRIES):
+ try:
+ self.client.update_image(image['id'], [
+ dict(add='/locations/-', value=new_loc)])
+ break
+ except lib_exc.BadRequest:
+ if i + 1 == BAD_REQUEST_RETRIES:
+ raise
+ else:
+ time.sleep(1)
# The image should now be active, with one location that looks
# like we expect
@@ -848,8 +863,21 @@
new_loc = {'metadata': {'speed': '88mph'},
'url': '%s#new' % CONF.image.http_image}
- self.client.update_image(image['id'], [
- dict(add='/locations/-', value=new_loc)])
+
+ # NOTE(danms): If glance was unable to fetch the remote image via
+ # HTTP, it will return BadRequest. Because this can be transient in
+ # CI, we try this a few times before we agree that it has failed
+ # for a reason worthy of failing the test.
+ for i in range(BAD_REQUEST_RETRIES):
+ try:
+ self.client.update_image(image['id'], [
+ dict(add='/locations/-', value=new_loc)])
+ break
+ except lib_exc.BadRequest:
+ if i + 1 == BAD_REQUEST_RETRIES:
+ raise
+ else:
+ time.sleep(1)
# The image should now have two locations and the last one
# (locations are ordered) should have the new URL.
diff --git a/tempest/lib/common/http.py b/tempest/lib/common/http.py
index 33f871b..d163968 100644
--- a/tempest/lib/common/http.py
+++ b/tempest/lib/common/http.py
@@ -60,7 +60,12 @@
retry = urllib3.util.Retry(redirect=False)
r = super(ClosingProxyHttp, self).request(method, url, retries=retry,
*args, **new_kwargs)
- return Response(r), r.data
+ if not kwargs.get('preload_content', True):
+ # This means we asked urllib3 for streaming content, so we
+ # need to return the raw response and not read any data yet
+ return r, b''
+ else:
+ return Response(r), r.data
class ClosingHttp(urllib3.poolmanager.PoolManager):
@@ -109,4 +114,9 @@
retry = urllib3.util.Retry(redirect=False)
r = super(ClosingHttp, self).request(method, url, retries=retry,
*args, **new_kwargs)
- return Response(r), r.data
+ if not kwargs.get('preload_content', True):
+ # This means we asked urllib3 for streaming content, so we
+ # need to return the raw response and not read any data yet
+ return r, b''
+ else:
+ return Response(r), r.data
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index a11b7c1..6cf5b73 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -19,6 +19,7 @@
import re
import time
import urllib
+import urllib3
import jsonschema
from oslo_log import log as logging
@@ -298,7 +299,7 @@
"""
return self.request('POST', url, extra_headers, headers, body, chunked)
- def get(self, url, headers=None, extra_headers=False):
+ def get(self, url, headers=None, extra_headers=False, chunked=False):
"""Send a HTTP GET request using keystone service catalog and auth
:param str url: the relative url to send the get request to
@@ -307,11 +308,19 @@
returned by the get_headers() method are to
be used but additional headers are needed in
the request pass them in as a dict.
+ :param bool chunked: Boolean value that indicates if we should stream
+ the response instead of reading it all at once.
+ If True, data will be empty and the raw urllib3
+ response object will be returned.
+ NB: If you pass True here, you **MUST** call
+ release_conn() on the response object before
+ finishing!
:return: a tuple with the first entry containing the response headers
and the second the response body
:rtype: tuple
"""
- return self.request('GET', url, extra_headers, headers)
+ return self.request('GET', url, extra_headers, headers,
+ chunked=chunked)
def delete(self, url, headers=None, body=None, extra_headers=False):
"""Send a HTTP DELETE request using keystone service catalog and auth
@@ -480,7 +489,7 @@
self.LOG.info(
'Request (%s): %s %s %s%s',
caller_name,
- resp['status'],
+ resp.status,
method,
req_url,
secs,
@@ -617,17 +626,30 @@
"""
if headers is None:
headers = self.get_headers()
+ # In urllib3, chunked only affects the upload. However, we may
+ # want to read large responses to GET incrementally. Re-purpose
+ # chunked=True on a GET to also control how we handle the response.
+ preload = not (method.lower() == 'get' and chunked)
+ if not preload:
+ # NOTE(danms): Not specifically necessary, but don't send
+ # chunked=True to urllib3 on a GET, since it is technically
+ # for PUT/POST type operations
+ chunked = False
# Do the actual request, and time it
start = time.time()
self._log_request_start(method, url)
resp, resp_body = self.http_obj.request(
url, method, headers=headers,
- body=body, chunked=chunked)
+ body=body, chunked=chunked, preload_content=preload)
end = time.time()
req_body = body if log_req_body is None else log_req_body
- self._log_request(method, url, resp, secs=(end - start),
- req_headers=headers, req_body=req_body,
- resp_body=resp_body)
+ if preload:
+ # NOTE(danms): If we are reading the whole response, we can do
+ # this logging. If not, skip the logging because it will result
+ # in us reading the response data prematurely.
+ self._log_request(method, url, resp, secs=(end - start),
+ req_headers=headers, req_body=req_body,
+ resp_body=resp_body)
return resp, resp_body
def request(self, method, url, extra_headers=False, headers=None,
@@ -773,6 +795,10 @@
# resp this could possibly fail
if str(type(resp)) == "<type 'instance'>":
ctype = resp.getheader('content-type')
+ elif isinstance(resp, urllib3.HTTPResponse):
+ # If we requested chunked=True streaming, this will be a raw
+ # urllib3.HTTPResponse
+ ctype = resp.getheaders()['content-type']
else:
try:
ctype = resp['content-type']
diff --git a/tempest/lib/common/ssh.py b/tempest/lib/common/ssh.py
index cb59a82..aad04b8 100644
--- a/tempest/lib/common/ssh.py
+++ b/tempest/lib/common/ssh.py
@@ -53,7 +53,8 @@
def __init__(self, host, username, password=None, timeout=300, pkey=None,
channel_timeout=10, look_for_keys=False, key_filename=None,
- port=22, proxy_client=None, ssh_key_type='rsa'):
+ port=22, proxy_client=None, ssh_key_type='rsa',
+ ssh_allow_agent=True):
"""SSH client.
Many of parameters are just passed to the underlying implementation
@@ -76,6 +77,9 @@
for ssh-over-ssh. The default is None, which means
not to use ssh-over-ssh.
:param ssh_key_type: ssh key type (rsa, ecdsa)
+ :param ssh_allow_agent: boolean, default True, if the SSH client is
+ allowed to also utilize the ssh-agent. Explicit use of passwords
+ in some tests may need this set as False.
:type proxy_client: ``tempest.lib.common.ssh.Client`` object
"""
self.host = host
@@ -105,6 +109,7 @@
raise exceptions.SSHClientProxyClientLoop(
host=self.host, port=self.port, username=self.username)
self._proxy_conn = None
+ self.ssh_allow_agent = ssh_allow_agent
def _get_ssh_connection(self, sleep=1.5, backoff=1):
"""Returns an ssh connection to the specified host."""
@@ -133,7 +138,7 @@
look_for_keys=self.look_for_keys,
key_filename=self.key_filename,
timeout=self.channel_timeout, pkey=self.pkey,
- sock=proxy_chan)
+ sock=proxy_chan, allow_agent=self.ssh_allow_agent)
LOG.info("ssh connection to %s@%s successfully created",
self.username, self.host)
return ssh
diff --git a/tempest/lib/common/utils/linux/remote_client.py b/tempest/lib/common/utils/linux/remote_client.py
index d0cdc25..662b452 100644
--- a/tempest/lib/common/utils/linux/remote_client.py
+++ b/tempest/lib/common/utils/linux/remote_client.py
@@ -69,7 +69,8 @@
server=None, servers_client=None, ssh_timeout=300,
connect_timeout=60, console_output_enabled=True,
ssh_shell_prologue="set -eu -o pipefail; PATH=$PATH:/sbin;",
- ping_count=1, ping_size=56, ssh_key_type='rsa'):
+ ping_count=1, ping_size=56, ssh_key_type='rsa',
+ ssh_allow_agent=True):
"""Executes commands in a VM over ssh
:param ip_address: IP address to ssh to
@@ -85,6 +86,8 @@
:param ping_count: Number of ping packets
:param ping_size: Packet size for ping packets
:param ssh_key_type: ssh key type (rsa, ecdsa)
+ :param ssh_allow_agent: Boolean if ssh agent support is permitted.
+ Defaults to True.
"""
self.server = server
self.servers_client = servers_client
@@ -94,11 +97,14 @@
self.ping_count = ping_count
self.ping_size = ping_size
self.ssh_key_type = ssh_key_type
+ self.ssh_allow_agent = ssh_allow_agent
self.ssh_client = ssh.Client(ip_address, username, password,
ssh_timeout, pkey=pkey,
channel_timeout=connect_timeout,
- ssh_key_type=ssh_key_type)
+ ssh_key_type=ssh_key_type,
+ ssh_allow_agent=ssh_allow_agent,
+ )
@debug_ssh
def exec_command(self, cmd):
diff --git a/tempest/lib/services/image/v2/images_client.py b/tempest/lib/services/image/v2/images_client.py
index ae6ce25..8460b57 100644
--- a/tempest/lib/services/image/v2/images_client.py
+++ b/tempest/lib/services/image/v2/images_client.py
@@ -248,17 +248,26 @@
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp)
- def show_image_file(self, image_id):
+ def show_image_file(self, image_id, chunked=False):
"""Download binary image data.
+ :param bool chunked: If True, do not read the body and return only
+ the raw urllib3 response object for processing.
+ NB: If you pass True here, you **MUST** call
+ release_conn() on the response object before
+ finishing!
+
For a full list of available parameters, please refer to the official
API reference:
https://docs.openstack.org/api-ref/image/v2/#download-binary-image-data
"""
url = 'images/%s/file' % image_id
- resp, body = self.get(url)
+ resp, body = self.get(url, chunked=chunked)
self.expected_success([200, 204, 206], resp.status)
- return rest_client.ResponseBodyData(resp, body)
+ if chunked:
+ return resp
+ else:
+ return rest_client.ResponseBodyData(resp, body)
def add_image_tag(self, image_id, tag):
"""Add an image tag.
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index 90e1bc5..5513f4d 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -86,6 +86,7 @@
'%s' % (secgroup['id'], server['id']))
raise exceptions.TimeoutException(msg)
+ @decorators.attr(type='slow')
@decorators.idempotent_id('bdbb5441-9204-419d-a225-b4fdbfb1a1a8')
@utils.services('compute', 'volume', 'image', 'network')
def test_minimum_basic_scenario(self):
@@ -159,6 +160,7 @@
self.servers_client, server, floating_ip,
wait_for_disassociate=True)
+ @decorators.attr(type='slow')
@decorators.idempotent_id('a8fd48ec-1d01-4895-b932-02321661ec1e')
@testtools.skipUnless(CONF.volume_feature_enabled.snapshot,
"Cinder volume snapshots are disabled")
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index e630e29..f4f37b0 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -218,7 +218,7 @@
@testtools.skipUnless(CONF.compute.min_compute_nodes > 1,
'Less than 2 compute nodes, skipping multinode '
'tests.')
- @decorators.attr(type='slow')
+ @decorators.attr(type=['slow', 'multinode'])
@utils.services('compute', 'network')
def test_server_connectivity_cold_migration(self):
keypair = self.create_keypair()
@@ -244,7 +244,7 @@
@testtools.skipUnless(CONF.compute.min_compute_nodes > 1,
'Less than 2 compute nodes, skipping multinode '
'tests.')
- @decorators.attr(type='slow')
+ @decorators.attr(type=['slow', 'multinode'])
@utils.services('compute', 'network')
def test_server_connectivity_live_migration(self):
keypair = self.create_keypair()
@@ -289,7 +289,7 @@
@testtools.skipUnless(CONF.compute.min_compute_nodes > 1,
'Less than 2 compute nodes, skipping multinode '
'tests.')
- @decorators.attr(type='slow')
+ @decorators.attr(type=['slow', 'multinode'])
@utils.services('compute', 'network')
def test_server_connectivity_cold_migration_revert(self):
keypair = self.create_keypair()
diff --git a/tempest/scenario/test_network_qos_placement.py b/tempest/scenario/test_network_qos_placement.py
index 365eb1b..0b2cfcb 100644
--- a/tempest/scenario/test_network_qos_placement.py
+++ b/tempest/scenario/test_network_qos_placement.py
@@ -278,6 +278,7 @@
port = self.os_admin.ports_client.show_port(not_valid_port['id'])
self.assertEqual(0, len(port['port']['binding:profile']))
+ @decorators.attr(type='multinode')
@decorators.idempotent_id('8a98150c-a506-49a5-96c6-73a5e7b04ada')
@testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
'Cold migration is not available.')
@@ -851,6 +852,7 @@
self.assert_allocations(server, port, min_kbps, min_kpps)
+ @decorators.attr(type='multinode')
@decorators.idempotent_id('bdd0b31c-c8b0-4b7b-b80a-545a46b32abe')
@testtools.skipUnless(
CONF.compute_feature_enabled.cold_migration,
@@ -1033,6 +1035,7 @@
self.assert_allocations(server, port2, 0, 0)
+ @decorators.attr(type='multinode')
@decorators.idempotent_id('36ffdb85-6cc2-4cc9-a426-cad5bac8626b')
@testtools.skipUnless(
CONF.compute.min_compute_nodes > 1,
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index aff7509..2fc5f32 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -480,6 +480,7 @@
direction='ingress')
return ruleset
+ @decorators.attr(type='multinode')
@decorators.idempotent_id('e79f879e-debb-440c-a7e4-efeda05b6848')
@utils.services('compute', 'network')
def test_cross_tenant_traffic(self):
@@ -510,6 +511,7 @@
self._log_console_output_for_all_tenants()
raise
+ @decorators.attr(type='multinode')
@decorators.idempotent_id('63163892-bbf6-4249-aa12-d5ea1f8f421b')
@utils.services('compute', 'network')
def test_in_tenant_traffic(self):
@@ -524,7 +526,7 @@
raise
@decorators.idempotent_id('f4d556d7-1526-42ad-bafb-6bebf48568f6')
- @decorators.attr(type='slow')
+ @decorators.attr(type=['slow', 'multinode'])
@utils.services('compute', 'network')
def test_port_update_new_security_group(self):
"""Verifies the traffic after updating the vm port
@@ -578,7 +580,7 @@
raise
@decorators.idempotent_id('d2f77418-fcc4-439d-b935-72eca704e293')
- @decorators.attr(type='slow')
+ @decorators.attr(type=['slow', 'multinode'])
@utils.services('compute', 'network')
def test_multiple_security_groups(self):
"""Verify multiple security groups and checks that rules
@@ -610,7 +612,7 @@
private_key=private_key,
should_connect=True)
- @decorators.attr(type='slow')
+ @decorators.attr(type=['slow', 'multinode'])
@utils.requires_ext(service='network', extension='port-security')
@decorators.idempotent_id('7c811dcc-263b-49a3-92d2-1b4d8405f50c')
@utils.services('compute', 'network')
@@ -650,7 +652,7 @@
self._log_console_output_for_all_tenants()
raise
- @decorators.attr(type='slow')
+ @decorators.attr(type=['slow', 'multinode'])
@utils.requires_ext(service='network', extension='port-security')
@decorators.idempotent_id('13ccf253-e5ad-424b-9c4a-97b88a026699')
# TODO(mriedem): We shouldn't actually need to check this since neutron
diff --git a/tempest/scenario/test_server_multinode.py b/tempest/scenario/test_server_multinode.py
index fdf875c..9285da2 100644
--- a/tempest/scenario/test_server_multinode.py
+++ b/tempest/scenario/test_server_multinode.py
@@ -35,7 +35,7 @@
"Less than 2 compute nodes, skipping multinode tests.")
@decorators.idempotent_id('9cecbe35-b9d4-48da-a37e-7ce70aa43d30')
- @decorators.attr(type='smoke')
+ @decorators.attr(type=['smoke', 'multinode'])
@utils.services('compute', 'network')
def test_schedule_to_all_nodes(self):
available_zone = \
diff --git a/tempest/scenario/test_shelve_instance.py b/tempest/scenario/test_shelve_instance.py
index 29612ec..204471e 100644
--- a/tempest/scenario/test_shelve_instance.py
+++ b/tempest/scenario/test_shelve_instance.py
@@ -119,7 +119,7 @@
def test_shelve_volume_backed_instance(self):
self._create_server_then_shelve_and_unshelve(boot_from_volume=True)
- @decorators.attr(type='slow')
+ @decorators.attr(type=['slow', 'multinode'])
@decorators.idempotent_id('1295fd9e-193a-4cf8-b211-55358e021bae')
@testtools.skipUnless(CONF.network.public_network_id,
'The public_network_id option must be specified.')
diff --git a/tempest/tests/lib/common/test_http.py b/tempest/tests/lib/common/test_http.py
index a19153f..aae6ba2 100644
--- a/tempest/tests/lib/common/test_http.py
+++ b/tempest/tests/lib/common/test_http.py
@@ -149,6 +149,31 @@
'xtra key': 'Xtra Value'},
response)
+ def test_request_preload(self):
+ # Given
+ connection = self.closing_http()
+ headers = {'Xtra Key': 'Xtra Value'}
+ http_response = urllib3.HTTPResponse(headers=headers)
+ request = self.patch('urllib3.PoolManager.request',
+ return_value=http_response)
+ retry = self.patch('urllib3.util.Retry')
+
+ # When
+ response, _ = connection.request(
+ method=REQUEST_METHOD,
+ url=REQUEST_URL,
+ headers=headers,
+ preload_content=False)
+
+ # Then
+ request.assert_called_once_with(
+ REQUEST_METHOD,
+ REQUEST_URL,
+ headers=dict(headers, connection='close'),
+ preload_content=False,
+ retries=retry(raise_on_redirect=False, redirect=5))
+ self.assertIsInstance(response, urllib3.HTTPResponse)
+
class TestClosingProxyHttp(TestClosingHttp):
diff --git a/tempest/tests/lib/common/test_rest_client.py b/tempest/tests/lib/common/test_rest_client.py
index 910756f..81a76e0 100644
--- a/tempest/tests/lib/common/test_rest_client.py
+++ b/tempest/tests/lib/common/test_rest_client.py
@@ -55,6 +55,7 @@
def test_get(self):
__, return_dict = self.rest_client.get(self.url)
self.assertEqual('GET', return_dict['method'])
+ self.assertTrue(return_dict['preload_content'])
def test_delete(self):
__, return_dict = self.rest_client.delete(self.url)
@@ -78,6 +79,17 @@
__, return_dict = self.rest_client.copy(self.url)
self.assertEqual('COPY', return_dict['method'])
+ def test_get_chunked(self):
+ self.useFixture(fixtures.MockPatchObject(self.rest_client,
+ '_log_request'))
+ __, return_dict = self.rest_client.get(self.url, chunked=True)
+ # Default is preload_content=True, make sure we passed False
+ self.assertFalse(return_dict['preload_content'])
+ # Make sure we did not pass chunked=True to urllib3 for GET
+ self.assertFalse(return_dict['chunked'])
+ # Make sure we did not call _log_request() on the raw response
+ self.rest_client._log_request.assert_not_called()
+
class TestRestClientNotFoundHandling(BaseRestClientTestClass):
def setUp(self):
diff --git a/tempest/tests/lib/fake_http.py b/tempest/tests/lib/fake_http.py
index cfa4b93..5fa0c43 100644
--- a/tempest/tests/lib/fake_http.py
+++ b/tempest/tests/lib/fake_http.py
@@ -21,14 +21,17 @@
self.return_type = return_type
def request(self, uri, method="GET", body=None, headers=None,
- redirections=5, connection_type=None, chunked=False):
+ redirections=5, connection_type=None, chunked=False,
+ preload_content=False):
if not self.return_type:
fake_headers = fake_http_response(headers)
return_obj = {
'uri': uri,
'method': method,
'body': body,
- 'headers': headers
+ 'headers': headers,
+ 'chunked': chunked,
+ 'preload_content': preload_content,
}
return (fake_headers, return_obj)
elif isinstance(self.return_type, int):
diff --git a/tempest/tests/lib/services/image/v2/test_images_client.py b/tempest/tests/lib/services/image/v2/test_images_client.py
index 5b162f8..27a50a9 100644
--- a/tempest/tests/lib/services/image/v2/test_images_client.py
+++ b/tempest/tests/lib/services/image/v2/test_images_client.py
@@ -13,6 +13,9 @@
# under the License.
import io
+from unittest import mock
+
+import fixtures
from tempest.lib.common.utils import data_utils
from tempest.lib.services.image.v2 import images_client
@@ -239,6 +242,21 @@
headers={'Content-Type': 'application/octet-stream'},
status=200)
+ def test_show_image_file_chunked(self):
+ # Since chunked=True on a GET should pass the response object
+ # basically untouched, we use a mock here so we get some assurances.
+ http_response = mock.MagicMock()
+ http_response.status = 200
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.rest_client.RestClient.get',
+ return_value=(http_response, b'')))
+ resp = self.client.show_image_file(
+ self.FAKE_CREATE_UPDATE_SHOW_IMAGE['id'],
+ chunked=True)
+ self.assertEqual(http_response, resp)
+ resp.__contains__.assert_not_called()
+ resp.__getitem__.assert_not_called()
+
def test_add_image_tag(self):
self.check_service_client_function(
self.client.add_image_tag,
diff --git a/tempest/tests/lib/test_ssh.py b/tempest/tests/lib/test_ssh.py
index 886d99c..13870ba 100644
--- a/tempest/tests/lib/test_ssh.py
+++ b/tempest/tests/lib/test_ssh.py
@@ -75,7 +75,8 @@
look_for_keys=False,
timeout=10.0,
password=None,
- sock=None
+ sock=None,
+ allow_agent=True
)]
self.assertEqual(expected_connect, client_mock.connect.mock_calls)
self.assertEqual(0, s_mock.call_count)
@@ -91,7 +92,8 @@
proxy_client = ssh.Client('proxy-host', 'proxy-user', timeout=2)
client = ssh.Client('localhost', 'root', timeout=2,
- proxy_client=proxy_client)
+ proxy_client=proxy_client,
+ ssh_allow_agent=False)
client._get_ssh_connection(sleep=1)
aa_mock.assert_has_calls([mock.call(), mock.call()])
@@ -106,7 +108,8 @@
look_for_keys=False,
timeout=10.0,
password=None,
- sock=None
+ sock=None,
+ allow_agent=True
)]
self.assertEqual(proxy_expected_connect,
proxy_client_mock.connect.mock_calls)
@@ -121,7 +124,8 @@
look_for_keys=False,
timeout=10.0,
password=None,
- sock=proxy_client_mock.get_transport().open_session()
+ sock=proxy_client_mock.get_transport().open_session(),
+ allow_agent=False
)]
self.assertEqual(expected_connect, client_mock.connect.mock_calls)
self.assertEqual(0, s_mock.call_count)
diff --git a/tools/tempest-extra-tests-list.txt b/tools/tempest-extra-tests-list.txt
new file mode 100644
index 0000000..9c88109
--- /dev/null
+++ b/tools/tempest-extra-tests-list.txt
@@ -0,0 +1,20 @@
+# This file includes the list of tests which need to be
+# excluded to run from integrated testing (tempest-full job
+# or other generic jobs. We will run these tests in a separate
+# jobs. This is needed to avoid the job timeout, details in
+# bug#2004780.
+# Basic criteria to add test in this list is:
+# * Admin test which are not needed for interop and most of them
+# are running as part of other API and Scenario tests.
+# * Negative tests which are mostly covered in tempest API tests
+# or service unit/functional tests.
+
+# All admin tests except keystone admin test which might not have much
+# coverage in existing other tests
+tempest.api.compute.admin
+tempest.api.volume.admin
+tempest.api.image.admin
+tempest.api.network.admin
+
+# All negative tests
+negative
diff --git a/tox.ini b/tox.ini
index e1c17df..47ef5eb 100644
--- a/tox.ini
+++ b/tox.ini
@@ -126,17 +126,49 @@
tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' {posargs}
tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)|(^tempest\.serial_tests)' {posargs}
+[testenv:integrated-full]
+envdir = .tox/tempest
+sitepackages = {[tempestenv]sitepackages}
+basepython = {[tempestenv]basepython}
+setenv = {[tempestenv]setenv}
+deps = {[tempestenv]deps}
+# The regex below is used to select which tests to run. It exclude the extra
+# tests mentioned in tools/tempest-extra-tests-list.txt and slow tag:
+# See the testrepository bug: https://bugs.launchpad.net/testrepository/+bug/1208610
+# FIXME: We can replace it with the `--exclude-regex` option to exclude tests now.
+regex1 = '(?!.*\[.*\bslow\b.*\])(^tempest\.api)'
+regex2 = '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)|(^tempest\.serial_tests)'
+commands =
+ find . -type f -name "*.pyc" -delete
+ tempest run --regex {[testenv:integrated-full]regex1} --exclude-list ./tools/tempest-extra-tests-list.txt {posargs}
+ tempest run --combine --serial --regex {[testenv:integrated-full]regex2} {posargs}
+
+[testenv:extra-tests]
+envdir = .tox/tempest
+sitepackages = {[tempestenv]sitepackages}
+basepython = {[tempestenv]basepython}
+setenv = {[tempestenv]setenv}
+deps = {[tempestenv]deps}
+# The regex below is used to select extra tests mentioned in
+# tools/tempest-extra-tests-list.txt and exclude slow tag tests:
+# See the testrepository bug: https://bugs.launchpad.net/testrepository/+bug/1208610
+# FIXME: We can replace it with the `--exclude-regex` option to exclude tests now.
+exclude-regex = '\[.*\bslow\b.*\]'
+commands =
+ find . -type f -name "*.pyc" -delete
+ tempest run --exclude-regex {[testenv:extra-tests]exclude-regex} --include-list ./tools/tempest-extra-tests-list.txt {posargs}
+
[testenv:full-parallel]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
basepython = {[tempestenv]basepython}
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
+# But exlcude the extra tests mentioned in tools/tempest-extra-tests-list.txt
regex = '(^tempest\.scenario.*)|(^tempest\.serial_tests)|(?!.*\[.*\bslow\b.*\])(^tempest\.api)'
-# The regex below is used to select all tempest scenario and including the non slow api tests
commands =
find . -type f -name "*.pyc" -delete
- tempest run --regex {[testenv:full-parallel]regex} {posargs}
+ tempest run --regex {[testenv:full-parallel]regex} --exclude-list ./tools/tempest-extra-tests-list.txt {posargs}
[testenv:api-microversion-tests]
envdir = .tox/tempest
@@ -289,6 +321,30 @@
find . -type f -name "*.pyc" -delete
tempest run --serial --regex {[testenv:slow-serial]regex} {posargs}
+[testenv:slow]
+envdir = .tox/tempest
+sitepackages = {[tempestenv]sitepackages}
+basepython = {[tempestenv]basepython}
+setenv = {[tempestenv]setenv}
+deps = {[tempestenv]deps}
+# The regex below is used to select the slow tagged tests:
+regex = '\[.*\bslow\b.*\]'
+commands =
+ find . -type f -name "*.pyc" -delete
+ tempest run --regex {[testenv:slow]regex} {posargs}
+
+[testenv:multinode]
+envdir = .tox/tempest
+sitepackages = {[tempestenv]sitepackages}
+basepython = {[tempestenv]basepython}
+setenv = {[tempestenv]setenv}
+deps = {[tempestenv]deps}
+# The regex below is used to select the multinode and smoke tagged tests
+regex = '\[.*\bsmoke|multinode\b.*\]'
+commands =
+ find . -type f -name "*.pyc" -delete
+ tempest run --regex {[testenv:multinode]regex} {posargs}
+
[testenv:ipv6-only]
envdir = .tox/tempest
sitepackages = {[tempestenv]sitepackages}
diff --git a/zuul.d/base.yaml b/zuul.d/base.yaml
index 3deb944..2d978c0 100644
--- a/zuul.d/base.yaml
+++ b/zuul.d/base.yaml
@@ -72,7 +72,8 @@
and a tempest one exist.
timeout: 10800
vars:
- tox_envlist: full
+ # This job run multinode and smoke tests.
+ tox_envlist: multinode
devstack_localrc:
FORCE_CONFIG_DRIVE: false
NOVA_ALLOW_MOVE_TO_SAME_HOST: false
diff --git a/zuul.d/integrated-gate.yaml b/zuul.d/integrated-gate.yaml
index 0652666..ba28a7f 100644
--- a/zuul.d/integrated-gate.yaml
+++ b/zuul.d/integrated-gate.yaml
@@ -60,6 +60,15 @@
c-bak: false
- job:
+ name: tempest-extra-tests
+ parent: devstack-tempest
+ description: |
+ This job runs the extra tests mentioned in
+ tools/tempest-extra-tests-list.txt.
+ vars:
+ tox_envlist: extra-tests
+
+- job:
name: tempest-full-py3
parent: devstack-tempest
# This job version is with swift enabled on py3
@@ -74,7 +83,7 @@
required-projects:
- openstack/horizon
vars:
- tox_envlist: full
+ tox_envlist: integrated-full
devstack_localrc:
USE_PYTHON3: true
FORCE_CONFIG_DRIVE: true
@@ -107,6 +116,7 @@
# Required until bug/1949606 is resolved when using libvirt and QEMU
# >=5.0.0 with a [libvirt]virt_type of qemu (TCG).
configure_swap_size: 4096
+ tox_envlist: full
- job:
name: tempest-integrated-networking
@@ -246,10 +256,15 @@
neutron: https://opendev.org/openstack/neutron
devstack_services:
neutron-trunk: true
+ br-ex-tcpdump: true
+ br-int-flows: true
group-vars:
subnode:
devstack_localrc:
USE_PYTHON3: true
+ devstack_services:
+ br-ex-tcpdump: true
+ br-int-flows: true
- job:
name: tempest-slow
@@ -294,6 +309,13 @@
vars: *tempest_slow_vars
- job:
+ name: tempest-slow-parallel
+ parent: tempest-slow-py3
+ # This job run slow tests in parallel.
+ vars:
+ tox_envlist: slow
+
+- job:
name: tempest-cinder-v2-api
parent: devstack-tempest
# NOTE(gmann): Cinder v2 APIs are available until
@@ -368,6 +390,7 @@
CINDER_ENFORCE_SCOPE: true
GLANCE_ENFORCE_SCOPE: true
NEUTRON_ENFORCE_SCOPE: true
+ PLACEMENT_ENFORCE_SCOPE: true
- project-template:
name: integrated-gate-networking
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index 966cc9a..d20186e 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -11,7 +11,7 @@
- openstack-tox-py38
- openstack-tox-py39
- openstack-tox-py310
- - tempest-full-parallel:
+ - tempest-full-py3:
# Define list of irrelevant files to use everywhere else
irrelevant-files: &tempest-irrelevant-files
- ^.*\.rst$
@@ -26,20 +26,15 @@
- ^.gitignore$
- ^.gitreview$
- ^.mailmap$
- - tempest-full-py3:
+ - tempest-extra-tests:
irrelevant-files: *tempest-irrelevant-files
- tempest-full-ubuntu-focal:
irrelevant-files: *tempest-irrelevant-files
- - tempest-full-py3-ipv6:
- voting: false
- irrelevant-files: *tempest-irrelevant-files
- glance-multistore-cinder-import:
voting: false
irrelevant-files: *tempest-irrelevant-files
- tempest-full-zed:
irrelevant-files: *tempest-irrelevant-files
- - tempest-full-yoga:
- irrelevant-files: *tempest-irrelevant-files
- tempest-full-xena:
irrelevant-files: *tempest-irrelevant-files
- tempest-multinode-full-py3:
@@ -66,6 +61,7 @@
- ^tools/tempest-integrated-gate-placement-exclude-list.txt
- ^tools/tempest-integrated-gate-storage-blacklist.txt
- ^tools/tempest-integrated-gate-storage-exclude-list.txt
+ - ^tools/tempest-extra-tests-list.txt
- ^tools/verify-ipv6-only-deployments.sh
- ^tools/with_venv.sh
# tools/ is not here since this relies on a script in tools/.
@@ -89,6 +85,7 @@
- ^tools/tempest-integrated-gate-placement-exclude-list.txt
- ^tools/tempest-integrated-gate-storage-blacklist.txt
- ^tools/tempest-integrated-gate-storage-exclude-list.txt
+ - ^tools/tempest-extra-tests-list.txt
- ^tools/tempest-plugin-sanity.sh
- ^tools/with_venv.sh
- ^.coveragerc$
@@ -118,16 +115,8 @@
- tempest-full-test-account-py3:
voting: false
irrelevant-files: *tempest-irrelevant-files
- - tempest-full-test-account-no-admin-py3:
- voting: false
- irrelevant-files: *tempest-irrelevant-files
- openstack-tox-bashate:
irrelevant-files: *tempest-irrelevant-files-2
- - tempest-full-centos-9-stream:
- # TODO(gmann): make it voting once below fix is merged
- # https://review.opendev.org/c/openstack/tempest/+/842140
- voting: false
- irrelevant-files: *tempest-irrelevant-files
gate:
jobs:
- openstack-tox-pep8
@@ -142,6 +131,8 @@
irrelevant-files: *tempest-irrelevant-files
- tempest-full-py3:
irrelevant-files: *tempest-irrelevant-files
+ - tempest-extra-tests:
+ irrelevant-files: *tempest-irrelevant-files
- grenade:
irrelevant-files: *tempest-irrelevant-files
- tempest-ipv6-only:
@@ -152,13 +143,13 @@
irrelevant-files: *tempest-irrelevant-files
#- devstack-plugin-ceph-tempest-py3:
# irrelevant-files: *tempest-irrelevant-files
- #- tempest-full-centos-9-stream:
- # irrelevant-files: *tempest-irrelevant-files
- nova-live-migration:
irrelevant-files: *tempest-irrelevant-files
experimental:
jobs:
- nova-multi-cell
+ - nova-ceph-multistore:
+ irrelevant-files: *tempest-irrelevant-files
- tempest-with-latest-microversion
- tempest-stestr-master
- tempest-cinder-v2-api:
@@ -173,8 +164,14 @@
irrelevant-files: *tempest-irrelevant-files
- tempest-pg-full:
irrelevant-files: *tempest-irrelevant-files
+ - tempest-full-py3-ipv6:
+ irrelevant-files: *tempest-irrelevant-files
+ - tempest-full-centos-9-stream:
+ irrelevant-files: *tempest-irrelevant-files
- tempest-centos9-stream-fips:
irrelevant-files: *tempest-irrelevant-files
+ - tempest-full-test-account-no-admin-py3:
+ irrelevant-files: *tempest-irrelevant-files
periodic-stable:
jobs:
- tempest-full-zed
@@ -183,9 +180,17 @@
- tempest-slow-zed
- tempest-slow-yoga
- tempest-slow-xena
+ - tempest-full-zed-extra-tests
+ - tempest-full-yoga-extra-tests
+ - tempest-full-xena-extra-tests
periodic:
jobs:
- tempest-all
+ - tempest-slow-parallel
+ - tempest-full-parallel
- tempest-full-oslo-master
- tempest-stestr-master
+ - tempest-full-py3-ipv6
- tempest-centos9-stream-fips
+ - tempest-full-centos-9-stream
+ - tempest-full-test-account-no-admin-py3
diff --git a/zuul.d/stable-jobs.yaml b/zuul.d/stable-jobs.yaml
index fb2300b..f70e79c 100644
--- a/zuul.d/stable-jobs.yaml
+++ b/zuul.d/stable-jobs.yaml
@@ -18,6 +18,24 @@
override-checkout: stable/xena
- job:
+ name: tempest-full-zed-extra-tests
+ parent: tempest-extra-tests
+ nodeset: openstack-single-node-focal
+ override-checkout: stable/zed
+
+- job:
+ name: tempest-full-yoga-extra-tests
+ parent: tempest-extra-tests
+ nodeset: openstack-single-node-focal
+ override-checkout: stable/yoga
+
+- job:
+ name: tempest-full-xena-extra-tests
+ parent: tempest-extra-tests
+ nodeset: openstack-single-node-focal
+ override-checkout: stable/xena
+
+- job:
name: tempest-slow-zed
parent: tempest-slow-py3
nodeset: openstack-two-node-focal
diff --git a/zuul.d/tempest-specific.yaml b/zuul.d/tempest-specific.yaml
index ca9ba7f..972123e 100644
--- a/zuul.d/tempest-specific.yaml
+++ b/zuul.d/tempest-specific.yaml
@@ -30,11 +30,12 @@
- opendev.org/openstack/oslo.utils
- opendev.org/openstack/oslo.versionedobjects
- opendev.org/openstack/oslo.vmware
+ vars:
+ tox_envlist: full
- job:
name: tempest-full-parallel
parent: tempest-full-py3
- voting: false
branches:
- master
description: |