Refactor QoS test case
- iperf3 is installed on the Ubuntu image used by the QoS tests - iperf3 server is started as a systemctl service at VM boot and will manage any - connections initiated from iperf3 clients Co-Author: Eduardo Olivares <eolivares@redhat.com> Change-Id: I1bff4953c2fdb47583174781c2578bcc8848f0a2
This commit is contained in:
parent
de72e5c13f
commit
ecddd6c438
|
@ -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']
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
|
@ -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]
|
|
@ -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)
|
|
@ -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'.
|
||||
"""
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -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]
|
|
@ -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)
|
|
@ -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,
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue