diff --git a/tobiko/openstack/glance/_image.py b/tobiko/openstack/glance/_image.py index 32af3062f..d8d788b24 100644 --- a/tobiko/openstack/glance/_image.py +++ b/tobiko/openstack/glance/_image.py @@ -425,9 +425,10 @@ class CustomizedGlanceImageFixture(URLGlanceImageFixture): command = sh.shell_command(['virt-customize', '-a', work_file]) execute = False - for package in self.install_packages: + if self.install_packages: execute = True - command += ['--install', package] + command += ['--install', ','.join(self.install_packages)] + if execute: sh.execute(command) diff --git a/tobiko/openstack/stacks/__init__.py b/tobiko/openstack/stacks/__init__.py index 196db4241..4ea591cd1 100644 --- a/tobiko/openstack/stacks/__init__.py +++ b/tobiko/openstack/stacks/__init__.py @@ -22,13 +22,13 @@ from tobiko.openstack.stacks import _l3ha from tobiko.openstack.stacks import _neutron from tobiko.openstack.stacks import _nova from tobiko.openstack.stacks import _octavia +from tobiko.openstack.stacks import _qos from tobiko.openstack.stacks import _ubuntu CentosFlavorStackFixture = _centos.CentosFlavorStackFixture CentosImageFixture = _centos.CentosImageFixture CentosServerStackFixture = _centos.CentosServerStackFixture CentosExternalServerStackFixture = _centos.CentosExternalServerStackFixture -CentosQosServerStackFixture = _centos.CentosQosServerStackFixture CirrosFlavorStackFixture = _cirros.CirrosFlavorStackFixture CirrosImageFixture = _cirros.CirrosImageFixture @@ -75,13 +75,16 @@ ServerGroupStackFixture = _nova.ServerGroupStackFixture AffinityServerGroupStackFixture = _nova.AffinityServerGroupStackFixture AntiAffinityServerGroupStackFixture = _nova.AntiAffinityServerGroupStackFixture +QosNetworkStackFixture = _qos.QosNetworkStackFixture +QosPolicyStackFixture = _qos.QosPolicyStackFixture +QosServerStackFixture = _qos.QosServerStackFixture + UbuntuFlavorStackFixture = _ubuntu.UbuntuFlavorStackFixture UbuntuImageFixture = _ubuntu.UbuntuImageFixture UbuntuMinimalImageFixture = _ubuntu.UbuntuMinimalImageFixture UbuntuServerStackFixture = _ubuntu.UbuntuServerStackFixture UbuntuMinimalServerStackFixture = _ubuntu.UbuntuMinimalServerStackFixture UbuntuExternalServerStackFixture = _ubuntu.UbuntuExternalServerStackFixture -UbuntuQosServerStackFixture = _ubuntu.UbuntuQosServerStackFixture OctaviaLoadbalancerStackFixture = _octavia.OctaviaLoadbalancerStackFixture OctaviaListenerStackFixture = _octavia.OctaviaListenerStackFixture diff --git a/tobiko/openstack/stacks/_centos.py b/tobiko/openstack/stacks/_centos.py index 0709897f7..1c59f313c 100644 --- a/tobiko/openstack/stacks/_centos.py +++ b/tobiko/openstack/stacks/_centos.py @@ -16,7 +16,6 @@ from __future__ import absolute_import import tobiko from tobiko import config from tobiko.openstack import glance -from tobiko.openstack import nova from tobiko.openstack.stacks import _nova @@ -58,13 +57,3 @@ class CentosServerStackFixture(_nova.CloudInitServerStackFixture): class CentosExternalServerStackFixture(CentosServerStackFixture, _nova.ExternalServerStackFixture): pass - - -class CentosQosServerStackFixture(CentosServerStackFixture, - _nova.QosServerStackFixture): - - @property - def cloud_config(self): - return nova.cloud_config( - super(CentosQosServerStackFixture, self).cloud_config, - packages=['iperf3']) diff --git a/tobiko/openstack/stacks/_neutron.py b/tobiko/openstack/stacks/_neutron.py index bf603d286..7dbd96cff 100644 --- a/tobiko/openstack/stacks/_neutron.py +++ b/tobiko/openstack/stacks/_neutron.py @@ -299,37 +299,6 @@ class NetworkWithNetMtuWriteStackFixture(NetworkStackFixture): return dict(value_specs, mtu=self.custom_mtu_size) -@neutron.skip_if_missing_networking_extensions('qos') -class QosPolicyStackFixture(heat.HeatStackFixture): - """Heat stack with a QoS Policy and some QoS Policy Rules - """ - has_qos_policy = True - has_bwlimit = True - has_dscp_marking = True - bwlimit_kbps = CONF.tobiko.neutron.bwlimit_kbps - bwlimit_burst_kbps = int(0.8 * bwlimit_kbps) - direction = CONF.tobiko.neutron.direction - dscp_mark = CONF.tobiko.neutron.dscp_mark - - #: Heat template file - template = _hot.heat_template_file('neutron/qos.yaml') - - -@neutron.skip_if_missing_networking_extensions('qos') -class NetworkQosPolicyStackFixture(NetworkStackFixture): - - #: stack with the qos policy for the network - qos_stack = tobiko.required_setup_fixture(QosPolicyStackFixture) - - has_qos_policy = True - - @property - def network_value_specs(self): - value_specs = super(NetworkQosPolicyStackFixture, - self).network_value_specs - return dict(value_specs, qos_policy_id=self.qos_stack.qos_policy_id) - - @neutron.skip_if_missing_networking_extensions('security-group') class SecurityGroupsFixture(heat.HeatStackFixture): """Heat stack with some security groups diff --git a/tobiko/openstack/stacks/_nova.py b/tobiko/openstack/stacks/_nova.py index b604759f0..3a07cb339 100644 --- a/tobiko/openstack/stacks/_nova.py +++ b/tobiko/openstack/stacks/_nova.py @@ -82,7 +82,7 @@ class FlavorStackFixture(heat.HeatStackFixture): is_public = None name = None rxtx_factor = None - swap = None + swap: typing.Optional[int] = None vcpus = None @@ -415,12 +415,6 @@ class ExternalServerStackFixture(ServerStackFixture, abc.ABC): return self.network_stack.network_id -class QosServerStackFixture(ServerStackFixture, abc.ABC): - #: stack with the network with a qos policy - network_stack = tobiko.required_setup_fixture( - _neutron.NetworkQosPolicyStackFixture) - - class PeerServerStackFixture(ServerStackFixture, abc.ABC): """Server witch networking access requires passing by another Nova server """ diff --git a/tobiko/openstack/stacks/_qos.py b/tobiko/openstack/stacks/_qos.py new file mode 100644 index 000000000..d230b3817 --- /dev/null +++ b/tobiko/openstack/stacks/_qos.py @@ -0,0 +1,62 @@ +# Copyright (c) 2021 Red Hat, Inc. +# +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from __future__ import absolute_import + +import tobiko +from tobiko import config +from tobiko.openstack import heat +from tobiko.openstack import neutron +from tobiko.openstack.stacks import _neutron +from tobiko.openstack.stacks import _hot +from tobiko.openstack.stacks import _ubuntu + + +CONF = config.CONF + + +@neutron.skip_if_missing_networking_extensions('qos') +class QosPolicyStackFixture(heat.HeatStackFixture): + """Heat stack with a QoS Policy and some QoS Policy Rules + """ + has_qos_policy = True + has_bwlimit = True + has_dscp_marking = True + bwlimit_kbps = CONF.tobiko.neutron.bwlimit_kbps + bwlimit_burst_kbps = int(0.8 * bwlimit_kbps) + direction = CONF.tobiko.neutron.direction + dscp_mark = CONF.tobiko.neutron.dscp_mark + + #: Heat template file + template = _hot.heat_template_file('neutron/qos.yaml') + + +@neutron.skip_if_missing_networking_extensions('qos') +class QosNetworkStackFixture(_neutron.NetworkStackFixture): + + #: stack with the qos policy for the network + qos_stack = tobiko.required_setup_fixture(QosPolicyStackFixture) + + has_qos_policy = True + + @property + def network_value_specs(self): + value_specs = super().network_value_specs + return dict(value_specs, qos_policy_id=self.qos_stack.qos_policy_id) + + +class QosServerStackFixture(_ubuntu.UbuntuServerStackFixture): + #: stack with the network with a qos policy + network_stack = tobiko.required_setup_fixture(QosNetworkStackFixture) diff --git a/tobiko/openstack/stacks/_ubuntu.py b/tobiko/openstack/stacks/_ubuntu.py index 59565f3cb..9f5cb27f2 100644 --- a/tobiko/openstack/stacks/_ubuntu.py +++ b/tobiko/openstack/stacks/_ubuntu.py @@ -16,16 +16,13 @@ from __future__ import absolute_import import tobiko from tobiko import config from tobiko.openstack import glance +from tobiko.openstack import nova from tobiko.openstack.stacks import _nova CONF = config.CONF UBUNTU_IMAGE_VERSION = 'focal' -UBUNTU_IMAGE_URL = ( - f'http://cloud-images.ubuntu.com/{UBUNTU_IMAGE_VERSION}/current/' - f'{UBUNTU_IMAGE_VERSION}-server-cloudimg-amd64.img') - UBUNTU_IMAGE_VERSION_NUMBER = '20.04' UBUNTU_MINIMAL_IMAGE_URL = ( @@ -34,8 +31,8 @@ UBUNTU_MINIMAL_IMAGE_URL = ( f'ubuntu-{UBUNTU_IMAGE_VERSION_NUMBER}-minimal-cloudimg-amd64.img') -class UbuntuImageFixture(glance.URLGlanceImageFixture): - image_url = CONF.tobiko.ubuntu.image_url or UBUNTU_IMAGE_URL +class UbuntuMinimalImageFixture(glance.URLGlanceImageFixture): + image_url = CONF.tobiko.ubuntu.image_url or UBUNTU_MINIMAL_IMAGE_URL image_name = CONF.tobiko.ubuntu.image_name image_file = CONF.tobiko.ubuntu.image_file disk_format = CONF.tobiko.ubuntu.disk_format or "qcow2" @@ -45,55 +42,54 @@ class UbuntuImageFixture(glance.URLGlanceImageFixture): connection_timeout = CONF.tobiko.ubuntu.connection_timeout or 600. -class UbuntuMinimalImageFixture(UbuntuImageFixture): - image_url = UBUNTU_MINIMAL_IMAGE_URL +class UbuntuImageFixture(UbuntuMinimalImageFixture, + glance.CustomizedGlanceImageFixture): + """Ubuntu server image running an HTTP server + + The server has additional commands compared to the minimal one: + iperf3 + ping + """ + + install_packages = ['iperf3', 'iputils-ping', 'nginx'] class UbuntuFlavorStackFixture(_nova.FlavorStackFixture): - ram = 256 - - -class UbuntuMinimalFlavorStackFixture(_nova.FlavorStackFixture): ram = 128 + swap = 512 -class UbuntuServerStackFixture(_nova.CloudInitServerStackFixture): - - #: Glance image used to create a Nova server instance - image_fixture = tobiko.required_setup_fixture(UbuntuImageFixture) - - #: Flavor used to create a Nova server instance - flavor_stack = tobiko.required_setup_fixture(UbuntuFlavorStackFixture) - - #: Setup SWAP file in bytes - swap_maxsize = 1 * 1024 * 1024 * 1024 # 1 GB - - -class UbuntuMinimalServerStackFixture(UbuntuServerStackFixture): +class UbuntuMinimalServerStackFixture(_nova.CloudInitServerStackFixture): #: Glance image used to create a Nova server instance image_fixture = tobiko.required_setup_fixture(UbuntuMinimalImageFixture) #: Flavor used to create a Nova server instance - flavor_stack = tobiko.required_setup_fixture( - UbuntuMinimalFlavorStackFixture) + flavor_stack = tobiko.required_setup_fixture(UbuntuFlavorStackFixture) - #: Setup SWAP file in bytes - swap_maxsize = 512 * 1024 * 1024 # 500 MB + +class UbuntuServerStackFixture(UbuntuMinimalServerStackFixture): + """Ubuntu server running an HTTP server + + The server has additional commands compared to the minimal one: + iperf3 + ping + """ + + #: Glance image used to create a Nova server instance + image_fixture = tobiko.required_setup_fixture(UbuntuImageFixture) + + # port of running HTTP server + http_port = 80 + + @property + def cloud_config(self): + return nova.cloud_config( + super().cloud_config, + runcmd=["sh -c 'hostname > /var/www/html/id'"]) class UbuntuExternalServerStackFixture(UbuntuMinimalServerStackFixture, _nova.ExternalServerStackFixture): - pass - - -class UbuntuQosServerImageFixture(UbuntuMinimalImageFixture, - glance.CustomizedGlanceImageFixture): - install_packages = ['iperf3'] - - -class UbuntuQosServerStackFixture(UbuntuMinimalServerStackFixture, - _nova.QosServerStackFixture): - - #: Glance image used to create a Nova server instance - image_fixture = tobiko.required_setup_fixture(UbuntuQosServerImageFixture) + """Ubuntu server with port on special external network + """ diff --git a/tobiko/shell/curl.py b/tobiko/shell/curl.py index edb6b55b6..fba7d8e9a 100644 --- a/tobiko/shell/curl.py +++ b/tobiko/shell/curl.py @@ -26,7 +26,9 @@ from tobiko.shell import ssh CURL_CONNECTION_ERRORS = { - 7 # Connection refused + 7, # Connection refused + 22, # 404 Not Found + 28, # Connection timedout } diff --git a/tobiko/tests/functional/openstack/stacks/test_cirros.py b/tobiko/tests/functional/openstack/stacks/test_cirros.py index eb1b54fda..f154cfba6 100644 --- a/tobiko/tests/functional/openstack/stacks/test_cirros.py +++ b/tobiko/tests/functional/openstack/stacks/test_cirros.py @@ -22,7 +22,6 @@ import testtools import tobiko from tobiko.openstack import keystone -from tobiko.openstack import neutron from tobiko.openstack import nova from tobiko.openstack import stacks from tobiko.shell import curl @@ -122,24 +121,6 @@ class CirrosServerStackTest(testtools.TestCase): filenames=self.nameservers_filenames) self.assertEqual(subnet_nameservers, server_nameservers) - def test_ping_ipv4_nameservers(self): - self._test_ping_nameservers(ip_version=4) - - def test_ping_ipv6_nameservers(self): - self._test_ping_nameservers(ip_version=6) - - @neutron.skip_unless_is_ovs() - def _test_ping_nameservers(self, ip_version: int): - nameservers = sh.list_nameservers(ssh_client=self.stack.ssh_client, - filenames=self.nameservers_filenames, - ip_version=ip_version) - if not nameservers: - self.skipTest(f"Target server has no IPv{ip_version} " - "nameservers configured") - ping.assert_reachable_hosts(nameservers, - ssh_client=self.stack.ssh_client, - count=5) - class EvacuablesServerStackTest(CirrosServerStackTest): diff --git a/tobiko/tests/functional/openstack/stacks/test_ubuntu.py b/tobiko/tests/functional/openstack/stacks/test_ubuntu.py index f749a884e..27aaa739d 100644 --- a/tobiko/tests/functional/openstack/stacks/test_ubuntu.py +++ b/tobiko/tests/functional/openstack/stacks/test_ubuntu.py @@ -15,8 +15,6 @@ # under the License. from __future__ import absolute_import -import pytest - import tobiko from tobiko.shell import sh from tobiko.openstack import stacks @@ -44,13 +42,8 @@ class UbuntuMinimalServerStackTest(UbuntuServerStackTest): stack = tobiko.required_setup_fixture( stacks.UbuntuMinimalServerStackFixture) - @pytest.mark.skip(reason="ping not installed on image") def test_ping_fixed_ipv4(self): - pass + tobiko.skip_test("ping not installed on image") - @pytest.mark.skip(reason="ping not installed on image") def test_ping_fixed_ipv6(self): - pass - - def _test_ping_nameservers(self, ip_version: int): - self.skipTest("ping not installed on Ubuntu minimal image") + tobiko.skip_test("ping not installed on image") diff --git a/tobiko/tests/functional/shell/test_curl.py b/tobiko/tests/functional/shell/test_curl.py index 58de1979e..1a183760c 100644 --- a/tobiko/tests/functional/shell/test_curl.py +++ b/tobiko/tests/functional/shell/test_curl.py @@ -15,7 +15,6 @@ # under the License. from __future__ import absolute_import -import random import typing import netaddr @@ -23,15 +22,13 @@ import testtools import tobiko from tobiko.shell import curl -from tobiko.shell import sh from tobiko.shell import ssh from tobiko.openstack import stacks class TestCurl(testtools.TestCase): - server_stack = tobiko.required_setup_fixture( - stacks.CirrosServerStackFixture) + stack = tobiko.required_setup_fixture(stacks.UbuntuServerStackFixture) def test_execute_curl( self, @@ -39,41 +36,31 @@ class TestCurl(testtools.TestCase): ssh_client: typing.Optional[ssh.SSHClientFixture] = None): if ip_address is None: # Use the floating IP - ip_address = self.server_stack.ip_address - server_id = self.server_stack.server_id - http_port = random.randint(10000, 30000) - reply = (f"HTTP/1.1 200 OK\r\n" - f"Content-Length:{len(server_id)}\r\n" - "\r\n" - f"{server_id}") - http_server_command = f"nc -lk -p {http_port} -e echo -e '{reply}'" - http_server = sh.process(http_server_command, - ssh_client=self.server_stack.ssh_client) - http_server.execute() - self.addCleanup(http_server.kill) - - reply = curl.execute_curl(scheme='http', - hostname=ip_address, - port=http_port, - ssh_client=ssh_client, - connect_timeout=5., - retry_count=10, - retry_timeout=60.) - self.assertEqual(server_id, reply) + ip_address = self.stack.ip_address + http_port = self.stack.http_port + result = curl.execute_curl(scheme='http', + hostname=ip_address, + port=http_port, + path='/id', + ssh_client=ssh_client, + connect_timeout=10., + retry_count=30, + retry_timeout=300., + retry_interval=10.).strip() + self.assertEqual(self.stack.server_name, result) def test_execute_curl_ipv4(self): self.test_execute_curl(ip_address=self.get_fixed_ip(ip_version=4), - ssh_client=self.server_stack.ssh_client) + ssh_client=self.stack.ssh_client) def test_execute_curl_ipv6(self): self.test_execute_curl(ip_address=self.get_fixed_ip(ip_version=6), - ssh_client=self.server_stack.ssh_client) + ssh_client=self.stack.ssh_client) def get_fixed_ip(self, ip_version): - for fixed_ip in self.server_stack.fixed_ips: + for fixed_ip in self.stack.fixed_ips: ip_address = netaddr.IPAddress(fixed_ip['ip_address']) if ip_version == ip_address.version: return ip_address - self.skipTest( - f"Server {self.server_stack.server_id} has any " - f"IPv{ip_version} address.") + self.skipTest(f"Server {self.stack.server_id} has any " + f"IPv{ip_version} address.") diff --git a/tobiko/tests/scenario/neutron/test_qos.py b/tobiko/tests/scenario/neutron/test_qos.py index cd2bc5db1..207805c3c 100644 --- a/tobiko/tests/scenario/neutron/test_qos.py +++ b/tobiko/tests/scenario/neutron/test_qos.py @@ -28,33 +28,33 @@ from tobiko.tripleo import overcloud LOG = log.getLogger(__name__) -class QoSBasicTest(testtools.TestCase): +class QoSNetworkTest(testtools.TestCase): """Tests QoS basic functionality""" - #: Resources stack with QoS Policy and QoS Rules and Advanced server - stack = tobiko.required_setup_fixture(stacks.UbuntuQosServerStackFixture) + #: Resources stacks with QoS Policy and QoS Rules and Advanced server + network = tobiko.required_setup_fixture(stacks.QosNetworkStackFixture) + policy = tobiko.required_setup_fixture(stacks.QosPolicyStackFixture) + server = tobiko.required_setup_fixture(stacks.QosServerStackFixture) def setUp(self): - """Skip these tests if OVN is configured and OSP version is lower than - 16.1 - """ - super(QoSBasicTest, self).setUp() + super().setUp() if (overcloud.has_overcloud() and topology.verify_osp_version('16.0', lower=True) and containers.ovn_used_on_overcloud()): + # Skip these tests if OVN is configured and OSP version is lower + # than 16.1 self.skipTest("QoS not supported in this setup") def test_network_qos_policy_id(self): - '''Verify QoS Policy attached to the network corresponds with the QoS - Policy previously created''' - self.assertEqual(self.stack.network_stack.qos_stack.qos_policy_id, - self.stack.network_stack.qos_policy_id) + """Verify network policy ID""" + self.assertEqual(self.policy.qos_policy_id, + self.network.qos_policy_id) def test_server_qos_policy_id(self): - self.assertIsNone(self.stack.port_details['qos_policy_id']) + """Verify server policy ID""" + self.assertIsNone(self.server.port_details['qos_policy_id']) def test_qos_bw_limit(self): - '''Verify BW limit using the iperf tool - ''' + """Verify BW limit using the iperf3 tool""" iperf.assert_bw_limit(ssh_client=None, # localhost will act as client - ssh_server=self.stack.peer_ssh_client) + ssh_server=self.server.peer_ssh_client)