diff --git a/tobiko/config.py b/tobiko/config.py index e30f801a1..a6fd77f29 100644 --- a/tobiko/config.py +++ b/tobiko/config.py @@ -36,7 +36,7 @@ CONFIG_MODULES = ['tobiko.openstack.glance.config', 'tobiko.openstack.topology.config', 'tobiko.shell.ssh.config', 'tobiko.shell.ping.config', - 'tobiko.shell.iperf.config', + 'tobiko.shell.iperf3.config', 'tobiko.shell.sh.config', 'tobiko.tripleo.config'] diff --git a/tobiko/openstack/stacks/_ubuntu.py b/tobiko/openstack/stacks/_ubuntu.py index 16537b14b..9d3976a67 100644 --- a/tobiko/openstack/stacks/_ubuntu.py +++ b/tobiko/openstack/stacks/_ubuntu.py @@ -44,18 +44,37 @@ class UbuntuMinimalImageFixture(glance.URLGlanceImageFixture): connection_timeout = CONF.tobiko.ubuntu.connection_timeout or 600. +IPERF3_SERVICE_FILE = """ +[Unit] +Description=iperf3 server on port %i +After=syslog.target network.target + +[Service] +ExecStart=/usr/bin/iperf3 -s -p %i +Restart=always +RuntimeMaxSec=3600 +User=root + +[Install] +WantedBy=multi-user.target +DefaultInstance=5201 +""" + + class UbuntuImageFixture(UbuntuMinimalImageFixture, glance.CustomizedGlanceImageFixture): """Ubuntu server image running an HTTP server - The server has additional commands compared to the minimal one: - iperf3 - ping - ncat - nginx + The server has additional installed packages compared to + the minimal one: + - iperf3 + - ping + - ncat + - nginx - The image will also have a running HTTPD server listening on - TCP port 80 + The image will also have below running services: + - nginx HTTP server listening on TCP port 80 + - iperf3 server listening on TCP port 5201 """ @property @@ -70,8 +89,22 @@ class UbuntuImageFixture(UbuntuMinimalImageFixture, 'ncat', 'nginx'] + # port of running HTTP server http_port = 80 + # port of running Iperf3 server + iperf3_port = 5201 + + @property + def run_commands(self) -> typing.List[str]: + run_commands = super().run_commands + run_commands.append( + f'echo "{IPERF3_SERVICE_FILE}" ' + '> /etc/systemd/system/iperf3-server@.service') + run_commands.append( + f"systemctl enable iperf3-server@{self.iperf3_port}") + return run_commands + class UbuntuFlavorStackFixture(_nova.FlavorStackFixture): ram = 128 @@ -103,6 +136,10 @@ class UbuntuServerStackFixture(UbuntuMinimalServerStackFixture): def http_port(self) -> int: return self.image_fixture.http_port + @property + def iperf3_port(self) -> int: + return self.image_fixture.iperf3_port + class UbuntuExternalServerStackFixture(UbuntuServerStackFixture, _nova.ExternalServerStackFixture): diff --git a/tobiko/shell/iperf/_assert.py b/tobiko/shell/iperf/_assert.py deleted file mode 100644 index e08519c45..000000000 --- a/tobiko/shell/iperf/_assert.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright (c) 2019 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 -from __future__ import division - -from oslo_log import log - -import tobiko -from tobiko import config -from tobiko.shell.iperf import _iperf - - -CONF = config.CONF -LOG = log.getLogger(__name__) - - -def calculate_bw(iperf_measures): - # first interval is removed because BW measured during it is not - # limited - it takes ~ 1 second to traffic shaping algorithm to apply - # bw limit properly (buffer is empty when traffic starts being sent) - intervals = iperf_measures['intervals'][1:] - - bits_received = sum([interval['sum']['bytes'] * 8 - for interval in intervals]) - totaltime = sum([interval['sum']['seconds'] for interval in intervals]) - # bw in bits per second - return bits_received / totaltime - - -def assert_bw_limit(ssh_client, ssh_server, **params): - iperf_measures = _iperf.iperf(ssh_client, ssh_server, **params) - measured_bw = calculate_bw(iperf_measures) - - testcase = tobiko.get_test_case() - bw_limit = float(params.get('bw_limit') or - CONF.tobiko.neutron.bwlimit_kbps * 1000.) - LOG.debug('measured_bw = %f', measured_bw) - LOG.debug('bw_limit = %f', bw_limit) - # a 5% of upper deviation is allowed - testcase.assertLess(measured_bw, bw_limit * 1.1) - # an 8% of lower deviation is allowed - testcase.assertGreater(measured_bw, bw_limit * 0.9) diff --git a/tobiko/shell/iperf/_interface.py b/tobiko/shell/iperf/_interface.py deleted file mode 100644 index d1bfe6581..000000000 --- a/tobiko/shell/iperf/_interface.py +++ /dev/null @@ -1,147 +0,0 @@ -# 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 - -from oslo_log import log - -import tobiko -from tobiko.shell import sh - - -LOG = log.getLogger(__name__) - - -def get_iperf_command(parameters, ssh_client): - interface = get_iperf_interface(ssh_client=ssh_client) - return interface.get_iperf_command(parameters) - - -def get_iperf_interface(ssh_client): - manager = tobiko.setup_fixture(IperfInterfaceManager) - interface = manager.get_iperf_interface(ssh_client=ssh_client) - tobiko.check_valid_type(interface, IperfInterface) - return interface - - -class IperfInterfaceManager(tobiko.SharedFixture): - def __init__(self): - super(IperfInterfaceManager, self).__init__() - self.client_interfaces = {} - self.interfaces = [] - self.default_interface = IperfInterface() - - def add_iperf_interface(self, interface): - LOG.debug('Register iperf interface %r', interface) - self.interfaces.append(interface) - - def get_iperf_interface(self, ssh_client): - try: - return self.client_interfaces[ssh_client] - except KeyError: - pass - - LOG.debug('Assign default iperf interface to SSH client %r', - ssh_client) - self.client_interfaces[ssh_client] = self.default_interface - return self.default_interface - - -class IperfInterface(object): - def get_iperf_command(self, parameters): - command = sh.shell_command(['iperf3'] + - self.get_iperf_options(parameters)) - LOG.debug(f'Got iperf command: {command}') - return command - - def get_iperf_options(self, parameters): - options = [] - - port = parameters.port - if port: - options += self.get_port_option(port) - - timeout = parameters.timeout - if timeout and parameters.mode == 'client': - options += self.get_timeout_option(timeout) - - output_format = parameters.output_format - if output_format: - options += self.get_output_format_option(output_format) - - bitrate = parameters.bitrate - if bitrate and parameters.mode == 'client': - options += self.get_bitrate_option(bitrate) - - download = parameters.download - if download and parameters.mode == 'client': - options += self.get_download_option(download) - - protocol = parameters.protocol - if protocol and parameters.mode == 'client': - options += self.get_protocol_option(protocol) - - options += self.get_mode_option(parameters) - - return options - - @staticmethod - def get_mode_option(parameters): - mode = parameters.mode - if not mode or mode not in ('client', 'server'): - raise ValueError('iperf mode values allowed: [client|server]') - elif mode == 'client' and not parameters.ip: - raise ValueError('iperf client mode requires a destination ' - 'IP address') - elif mode == 'client': - return ['-c', parameters.ip] - else: # mode == 'server' - return ['-s', '-D'] # server mode is executed with daemon mode - - @staticmethod - def get_download_option(download): - if download: - return ['-R'] - else: - return [] - - @staticmethod - def get_protocol_option(protocol): - if protocol == 'tcp': - return [] - elif protocol == 'udp': - return ['-u'] - else: - raise ValueError('iperf protocol values allowed: [tcp|udp]') - - @staticmethod - def get_timeout_option(timeout): - return ['-t', timeout] - - @staticmethod - def get_output_format_option(output_format): - if output_format == 'json': - return ['-J'] - else: - raise ValueError('iperf output format values allowed: ' - '[json]') - - @staticmethod - def get_port_option(port): - return ['-p', port] - - @staticmethod - def get_bitrate_option(bitrate): - return ['-b', bitrate] diff --git a/tobiko/shell/iperf/_iperf.py b/tobiko/shell/iperf/_iperf.py deleted file mode 100644 index 6c2939229..000000000 --- a/tobiko/shell/iperf/_iperf.py +++ /dev/null @@ -1,74 +0,0 @@ -# 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 json -import time - -from oslo_log import log - -from tobiko.shell.iperf import _interface -from tobiko.shell.iperf import _parameters -from tobiko.shell import sh - - -LOG = log.getLogger(__name__) - - -def iperf(ssh_client, ssh_server, **iperf_params): - """Run iperf on both client and server machines and return obtained - statistics - - :param ssh_client: ssh connection to client - :param ssh_server: ssh connection to server - :param **iperf_params: parameters to be forwarded to get_statistics() - function - :returns: dict - """ - parameters_server = _parameters.get_iperf_parameters( - mode='server', **iperf_params) - # no output expected - execute_iperf_server(parameters_server, ssh_server) - - time.sleep(0.1) - parameters_client = _parameters.get_iperf_parameters( - mode='client', ip=ssh_server.host, **iperf_params) - # output is a dictionary - output = execute_iperf_client(parameters_client, ssh_client) - - return output - - -def execute_iperf_server(parameters, ssh_client): - # kill any iperf3 process running before executing it again - sh.execute(command='pkill iperf3', - ssh_client=ssh_client, - expect_exit_status=None) - time.sleep(1) - - # server is executed in background and no output is expected - command = _interface.get_iperf_command(parameters=parameters, - ssh_client=ssh_client) - sh.execute(command=command, ssh_client=ssh_client) - - -def execute_iperf_client(parameters, ssh_client): - command = _interface.get_iperf_command(parameters=parameters, - ssh_client=ssh_client) - result = sh.execute(command=command, - ssh_client=ssh_client, - timeout=parameters.timeout + 5.) - return json.loads(result.stdout) diff --git a/tobiko/shell/iperf/_parameters.py b/tobiko/shell/iperf/_parameters.py deleted file mode 100644 index c3a5a7ba7..000000000 --- a/tobiko/shell/iperf/_parameters.py +++ /dev/null @@ -1,58 +0,0 @@ -# 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 collections - -from tobiko import config - - -CONF = config.CONF - - -def get_iperf_parameters(mode, ip=None, **iperf_params): - """Get iperf parameters - mode allowed values: client or server - ip is only needed for client mode - """ - return IperfParameters( - mode=mode, - ip=ip, - port=iperf_params.get('port', CONF.tobiko.iperf.port), - timeout=iperf_params.get('timeout', CONF.tobiko.iperf.timeout), - output_format=iperf_params.get('output_format', - CONF.tobiko.iperf.output_format), - download=iperf_params.get('download', CONF.tobiko.iperf.download), - bitrate=iperf_params.get('bitrate', CONF.tobiko.iperf.bitrate), - protocol=iperf_params.get('protocol', CONF.tobiko.iperf.protocol)) - - -class IperfParameters(collections.namedtuple('IperfParameters', - ['mode', - 'ip', - 'port', - 'timeout', - 'output_format', - 'download', - 'bitrate', - 'protocol'])): - """Recollect parameters to be used to format iperf command line - - IperfParameters class is a data model recollecting parameters used to - create an iperf command line. It provides the feature of copying default - values from another instance of IperfParameters passed using constructor - parameter 'default'. - """ diff --git a/tobiko/shell/iperf/__init__.py b/tobiko/shell/iperf3/__init__.py similarity index 86% rename from tobiko/shell/iperf/__init__.py rename to tobiko/shell/iperf3/__init__.py index 4fccae854..ff840fc80 100644 --- a/tobiko/shell/iperf/__init__.py +++ b/tobiko/shell/iperf3/__init__.py @@ -15,7 +15,7 @@ # under the License. from __future__ import absolute_import -from tobiko.shell.iperf import _assert +from tobiko.shell.iperf3 import _assert -assert_bw_limit = _assert.assert_bw_limit +assert_has_bandwith_limits = _assert.assert_has_bandwith_limits diff --git a/tobiko/shell/iperf3/_assert.py b/tobiko/shell/iperf3/_assert.py new file mode 100644 index 000000000..9966b924b --- /dev/null +++ b/tobiko/shell/iperf3/_assert.py @@ -0,0 +1,57 @@ +# Copyright (c) 2019 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 +from __future__ import division + +import typing + +import netaddr +from oslo_log import log + +import tobiko +from tobiko import config +from tobiko.shell.iperf3 import _execute +from tobiko.shell import ssh + + +CONF = config.CONF +LOG = log.getLogger(__name__) + + +def assert_has_bandwith_limits( + address: typing.Union[str, netaddr.IPAddress], + min_bandwith: float, + max_bandwith: float, + bitrate: int = None, + download: bool = None, + port: int = None, + protocol: str = None, + ssh_client: ssh.SSHClientType = None, + timeout: tobiko.Seconds = None) -> None: + bandwith = _execute.get_bandwidth(address=address, + bitrate=bitrate, + download=download, + port=port, + protocol=protocol, + ssh_client=ssh_client, + timeout=timeout) + testcase = tobiko.get_test_case() + LOG.debug(f'measured bandwith: {bandwith}') + LOG.debug(f'bandwith limits: {min_bandwith} ... {max_bandwith}') + # an 8% of lower deviation is allowed + testcase.assertGreater(bandwith, min_bandwith) + # a 5% of upper deviation is allowed + testcase.assertLess(bandwith, max_bandwith) diff --git a/tobiko/shell/iperf3/_execute.py b/tobiko/shell/iperf3/_execute.py new file mode 100644 index 000000000..540f00eec --- /dev/null +++ b/tobiko/shell/iperf3/_execute.py @@ -0,0 +1,88 @@ +# 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 +from __future__ import division + +import json +import typing + +import netaddr +from oslo_log import log + +import tobiko +from tobiko.shell.iperf3 import _interface +from tobiko.shell.iperf3 import _parameters +from tobiko.shell import sh +from tobiko.shell import ssh + + +LOG = log.getLogger(__name__) + + +def get_bandwidth(address: typing.Union[str, netaddr.IPAddress], + bitrate: int = None, + download: bool = None, + port: int = None, + protocol: str = None, + ssh_client: ssh.SSHClientType = None, + timeout: tobiko.Seconds = None) -> float: + iperf_measures = execute_iperf3_client(address=address, + bitrate=bitrate, + download=download, + port=port, + protocol=protocol, + ssh_client=ssh_client, + timeout=timeout) + return calculate_bandwith(iperf_measures) + + +def calculate_bandwith(iperf_measures) -> float: + # first interval is removed because BW measured during it is not + # limited - it takes ~ 1 second to traffic shaping algorithm to apply + # bw limit properly (buffer is empty when traffic starts being sent) + intervals = iperf_measures['intervals'][1:] + bits_received = sum([interval['sum']['bytes'] * 8 + for interval in intervals]) + elapsed_time = sum([interval['sum']['seconds'] + for interval in intervals]) + # bw in bits per second + return bits_received / elapsed_time + + +def execute_iperf3_client(address: typing.Union[str, netaddr.IPAddress], + bitrate: int = None, + download: bool = None, + port: int = None, + protocol: str = None, + ssh_client: ssh.SSHClientType = None, + timeout: tobiko.Seconds = None) \ + -> typing.Dict: + params_timeout: typing.Optional[int] = None + if timeout is not None: + params_timeout = int(timeout - 0.5) + parameters = _parameters.iperf3_client_parameters(address=address, + bitrate=bitrate, + download=download, + port=port, + protocol=protocol, + timeout=params_timeout) + command = _interface.get_iperf3_client_command(parameters) + + # output is a dictionary + output = sh.execute(command, + ssh_client=ssh_client, + timeout=timeout).stdout + return json.loads(output) diff --git a/tobiko/shell/iperf3/_interface.py b/tobiko/shell/iperf3/_interface.py new file mode 100644 index 000000000..f29ca1beb --- /dev/null +++ b/tobiko/shell/iperf3/_interface.py @@ -0,0 +1,92 @@ +# 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 + +from oslo_log import log + +from tobiko.shell.iperf3 import _parameters +from tobiko.shell import sh + + +LOG = log.getLogger(__name__) + + +def get_iperf3_client_command(parameters: _parameters.Iperf3ClientParameters): + interface = Iperf3Interface() + return interface.get_iperf3_client_command(parameters) + + +class Iperf3Interface: + + def get_iperf3_client_command( + self, + parameters: _parameters.Iperf3ClientParameters) \ + -> sh.ShellCommand: + options = self.get_iperf3_client_options(parameters=parameters) + return sh.shell_command('iperf3') + options + + def get_iperf3_client_options( + self, + parameters: _parameters.Iperf3ClientParameters) \ + -> sh.ShellCommand: + options = sh.ShellCommand(['-J']) + options += self.get_client_mode_option(parameters.address) + if parameters.port is not None: + options += self.get_port_option(parameters.port) + if parameters.timeout is not None: + options += self.get_timeout_option(parameters.timeout) + if parameters.bitrate is not None: + options += self.get_bitrate_option(parameters.bitrate) + if parameters.download is not None: + options += self.get_download_option(parameters.download) + if parameters.protocol is not None: + options += self.get_protocol_option(parameters.protocol) + return options + + @staticmethod + def get_bitrate_option(bitrate: int): + return ['-b', max(0, bitrate)] + + @staticmethod + def get_client_mode_option(server_address: str): + return ['-c', server_address] + + @staticmethod + def get_download_option(download: bool): + if download: + return ['-R'] + else: + return [] + + @staticmethod + def get_protocol_option(protocol: str): + if protocol == 'tcp': + return [] + elif protocol == 'udp': + return ['-u'] + else: + raise ValueError('iperf3 protocol values allowed: [tcp|udp]') + + @staticmethod + def get_timeout_option(timeout: int): + if timeout > 0: + return ['-t', timeout] + else: + return [] + + @staticmethod + def get_port_option(port): + return ['-p', port] diff --git a/tobiko/shell/iperf3/_parameters.py b/tobiko/shell/iperf3/_parameters.py new file mode 100644 index 000000000..fc7f8410c --- /dev/null +++ b/tobiko/shell/iperf3/_parameters.py @@ -0,0 +1,63 @@ +# 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 typing + +import netaddr + +import tobiko + + +class Iperf3ClientParameters(typing.NamedTuple): + address: str + bitrate: typing.Optional[int] = None + download: typing.Optional[bool] = None + port: typing.Optional[int] = None + protocol: typing.Optional[str] = None + timeout: typing.Optional[int] = None + + +def iperf3_client_parameters( + address: typing.Union[str, netaddr.IPAddress], + bitrate: int = None, + download: bool = None, + port: int = None, + protocol: str = None, + timeout: int = None): + """Get iperf3 client parameters + mode allowed values: client or server + ip is only needed for client mode + """ + config = tobiko.tobiko_config().iperf3 + if isinstance(address, netaddr.IPAddress): + address = str(address) + if bitrate is None: + bitrate = config.bitrate + if download is None: + download = config.download + if port is None: + port = config.port + if protocol is None: + protocol = config.protocol + if timeout is None: + timeout = config.timeout + return Iperf3ClientParameters(address=address, + bitrate=bitrate, + download=download, + port=port, + protocol=protocol, + timeout=timeout) diff --git a/tobiko/shell/iperf/config.py b/tobiko/shell/iperf3/config.py similarity index 82% rename from tobiko/shell/iperf/config.py rename to tobiko/shell/iperf3/config.py index 570360c0a..21e0b9c44 100644 --- a/tobiko/shell/iperf/config.py +++ b/tobiko/shell/iperf3/config.py @@ -17,24 +17,21 @@ import itertools from oslo_config import cfg -GROUP_NAME = "iperf" + +GROUP_NAME = "iperf3" OPTIONS = [ cfg.IntOpt('port', - default=1234, + default=None, help="Port number"), cfg.StrOpt('protocol', - default='tcp', + default=None, choices=['tcp', 'udp'], help="tcp and udp values are supported"), - cfg.StrOpt('output_format', - default='json', - choices=['', 'json'], - help="output format"), cfg.IntOpt('bitrate', - default=20000000, + default=20000000, # 20 Mb help="target bit rate"), cfg.BoolOpt('download', - default=True, + default=None, help="direction download (True) or upload (False)"), cfg.IntOpt('timeout', default=10, diff --git a/tobiko/tests/scenario/neutron/test_qos.py b/tobiko/tests/scenario/neutron/test_qos.py index d9294b185..580ab0191 100644 --- a/tobiko/tests/scenario/neutron/test_qos.py +++ b/tobiko/tests/scenario/neutron/test_qos.py @@ -21,8 +21,9 @@ import tobiko from tobiko.openstack import keystone from tobiko.openstack import stacks from tobiko.openstack import neutron -from tobiko.shell import iperf +from tobiko.shell import iperf3 from tobiko.shell import ping +from tobiko.shell import sh LOG = log.getLogger(__name__) @@ -52,5 +53,22 @@ class QoSNetworkTest(testtools.TestCase): def test_qos_bw_limit(self): """Verify BW limit using the iperf3 tool""" - iperf.assert_bw_limit(ssh_client=None, # localhost will act as client - ssh_server=self.server.peer_ssh_client) + # localhost will act as client + bandwidth_limit = self.policy.bwlimit_kbps * 1000. + for attempt in tobiko.retry(timeout=100., interval=5.): + try: + iperf3.assert_has_bandwith_limits( + address=self.server.ip_address, + min_bandwith=bandwidth_limit * 0.9, + max_bandwith=bandwidth_limit * 1.1, + port=self.server.iperf3_port, + download=True) + break + except sh.ShellCommandFailed as err: + if ('unable to connect to server: Connection refused' + in str(err)): + attempt.check_limits() + LOG.debug('iperf command failed because the iperf server ' + 'was not ready yet - retrying...') + else: + raise err