blob: 6c889b1c174efb15b709a1aca47f9461399d691b [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright 2018 - The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import os
import subprocess
import socket
import threading
from acts import context
from acts import utils
from acts.controllers.adb_lib.error import AdbCommandError
from acts.controllers.android_device import AndroidDevice
from acts.controllers.iperf_server import _AndroidDeviceBridge
from acts.controllers.fuchsia_lib.utils_lib import create_ssh_connection
from acts.controllers.fuchsia_lib.utils_lib import ssh_is_connected
from acts.controllers.fuchsia_lib.utils_lib import SshResults
from acts.controllers.utils_lib.ssh import connection
from acts.controllers.utils_lib.ssh import settings
from acts.event import event_bus
from acts.event.decorators import subscribe_static
from acts.event.event import TestClassBeginEvent
from acts.event.event import TestClassEndEvent
from acts.libs.proc import job
from paramiko.buffered_pipe import PipeTimeout
from paramiko.ssh_exception import SSHException
MOBLY_CONTROLLER_CONFIG_NAME = 'IPerfClient'
ACTS_CONTROLLER_REFERENCE_NAME = 'iperf_clients'
class IPerfError(Exception):
"""Raised on execution errors of iPerf."""
def create(configs):
"""Factory method for iperf clients.
The function creates iperf clients based on at least one config.
If configs contain ssh settings or and AndroidDevice, remote iperf clients
will be started on those devices, otherwise, a the client will run on the
local machine.
Args:
configs: config parameters for the iperf server
"""
results = []
for c in configs:
if type(c) is dict and 'AndroidDevice' in c:
results.append(
IPerfClientOverAdb(c['AndroidDevice'],
test_interface=c.get('test_interface')))
elif type(c) is dict and 'ssh_config' in c:
results.append(
IPerfClientOverSsh(c['ssh_config'],
use_paramiko=c.get('use_paramiko'),
test_interface=c.get('test_interface')))
else:
results.append(IPerfClient())
return results
def get_info(iperf_clients):
"""Placeholder for info about iperf clients
Returns:
None
"""
return None
def destroy(_):
# No cleanup needed.
pass
class IPerfClientBase(object):
"""The Base class for all IPerfClients.
This base class is responsible for synchronizing the logging to prevent
multiple IPerfClients from writing results to the same file, as well
as providing the interface for IPerfClient objects.
"""
# Keeps track of the number of IPerfClient logs to prevent file name
# collisions.
__log_file_counter = 0
__log_file_lock = threading.Lock()
@staticmethod
def _get_full_file_path(tag=''):
"""Returns the full file path for the IPerfClient log file.
Note: If the directory for the file path does not exist, it will be
created.
Args:
tag: The tag passed in to the server run.
"""
current_context = context.get_current_context()
full_out_dir = os.path.join(current_context.get_full_output_path(),
'iperf_client_files')
with IPerfClientBase.__log_file_lock:
os.makedirs(full_out_dir, exist_ok=True)
tags = ['IPerfClient', tag, IPerfClientBase.__log_file_counter]
out_file_name = '%s.log' % (','.join(
[str(x) for x in tags if x != '' and x is not None]))
IPerfClientBase.__log_file_counter += 1
return os.path.join(full_out_dir, out_file_name)
def start(self, ip, iperf_args, tag, timeout=3600, iperf_binary=None):
"""Starts iperf client, and waits for completion.
Args:
ip: iperf server ip address.
iperf_args: A string representing arguments to start iperf
client. Eg: iperf_args = "-t 10 -p 5001 -w 512k/-u -b 200M -J".
tag: A string to further identify iperf results file
timeout: the maximum amount of time the iperf client can run.
iperf_binary: Location of iperf3 binary. If none, it is assumed the
the binary is in the path.
Returns:
full_out_path: iperf result path.
"""
raise NotImplementedError('start() must be implemented.')
class IPerfClient(IPerfClientBase):
"""Class that handles iperf3 client operations."""
def start(self, ip, iperf_args, tag, timeout=3600, iperf_binary=None):
"""Starts iperf client, and waits for completion.
Args:
ip: iperf server ip address.
iperf_args: A string representing arguments to start iperf
client. Eg: iperf_args = "-t 10 -p 5001 -w 512k/-u -b 200M -J".
tag: tag to further identify iperf results file
timeout: unused.
iperf_binary: Location of iperf3 binary. If none, it is assumed the
the binary is in the path.
Returns:
full_out_path: iperf result path.
"""
if not iperf_binary:
logging.debug('No iperf3 binary specified. '
'Assuming iperf3 is in the path.')
iperf_binary = 'iperf3'
else:
logging.debug('Using iperf3 binary located at %s' % iperf_binary)
iperf_cmd = [str(iperf_binary), '-c', ip] + iperf_args.split(' ')
full_out_path = self._get_full_file_path(tag)
with open(full_out_path, 'w') as out_file:
subprocess.call(iperf_cmd, stdout=out_file)
return full_out_path
class IPerfClientOverSsh(IPerfClientBase):
"""Class that handles iperf3 client operations on remote machines."""
def __init__(self, ssh_config, use_paramiko=False, test_interface=None):
self._ssh_settings = settings.from_config(ssh_config)
if not (utils.is_valid_ipv4_address(self._ssh_settings.hostname)
or utils.is_valid_ipv6_address(self._ssh_settings.hostname)):
mdns_ip = utils.get_fuchsia_mdns_ipv6_address(
self._ssh_settings.hostname)
if mdns_ip:
self._ssh_settings.hostname = mdns_ip
# use_paramiko may be passed in as a string (from JSON), so this line
# guarantees it is a converted to a bool.
self._use_paramiko = str(use_paramiko).lower() == 'true'
self._ssh_session = None
self.start_ssh()
self.hostname = self._ssh_settings.hostname
self.test_interface = test_interface
def start(self, ip, iperf_args, tag, timeout=3600, iperf_binary=None):
"""Starts iperf client, and waits for completion.
Args:
ip: iperf server ip address.
iperf_args: A string representing arguments to start iperf
client. Eg: iperf_args = "-t 10 -p 5001 -w 512k/-u -b 200M -J".
tag: tag to further identify iperf results file
timeout: the maximum amount of time to allow the iperf client to run
iperf_binary: Location of iperf3 binary. If none, it is assumed the
the binary is in the path.
Returns:
full_out_path: iperf result path.
"""
if not iperf_binary:
logging.debug('No iperf3 binary specified. '
'Assuming iperf3 is in the path.')
iperf_binary = 'iperf3'
else:
logging.debug('Using iperf3 binary located at %s' % iperf_binary)
iperf_cmd = '{} -c {} {}'.format(iperf_binary, ip, iperf_args)
full_out_path = self._get_full_file_path(tag)
try:
if not self._ssh_session:
self.start_ssh()
if self._use_paramiko:
if not ssh_is_connected(self._ssh_session):
logging.info('Lost SSH connection to %s. Reconnecting.' %
self._ssh_settings.hostname)
self._ssh_session.close()
self._ssh_session = create_ssh_connection(
ip_address=self._ssh_settings.hostname,
ssh_username=self._ssh_settings.username,
ssh_config=self._ssh_settings.ssh_config)
cmd_result_stdin, cmd_result_stdout, cmd_result_stderr = (
self._ssh_session.exec_command(iperf_cmd, timeout=timeout))
iperf_process = SshResults(cmd_result_stdin, cmd_result_stdout,
cmd_result_stderr,
cmd_result_stdout.channel)
else:
iperf_process = self._ssh_session.run(iperf_cmd,
timeout=timeout)
iperf_output = iperf_process.stdout
with open(full_out_path, 'w') as out_file:
out_file.write(iperf_output)
except PipeTimeout:
raise TimeoutError('Paramiko PipeTimeout. Timed out waiting for '
'iperf client to finish.')
except socket.timeout:
raise TimeoutError('Socket timeout. Timed out waiting for iperf '
'client to finish.')
except SSHException as err:
raise ConnectionError('SSH connection failed: {}'.format(err))
except Exception as err:
logging.exception('iperf run failed: {}'.format(err))
return full_out_path
def start_ssh(self):
"""Starts an ssh session to the iperf client."""
if not self._ssh_session:
if self._use_paramiko:
self._ssh_session = create_ssh_connection(
ip_address=self._ssh_settings.hostname,
ssh_username=self._ssh_settings.username,
ssh_config=self._ssh_settings.ssh_config)
else:
self._ssh_session = connection.SshConnection(
self._ssh_settings)
def close_ssh(self):
"""Closes the ssh session to the iperf client, if one exists, preventing
connection reset errors when rebooting client device.
"""
if self._ssh_session:
self._ssh_session.close()
self._ssh_session = None
class IPerfClientOverAdb(IPerfClientBase):
"""Class that handles iperf3 operations over ADB devices."""
def __init__(self, android_device_or_serial, test_interface=None):
"""Creates a new IPerfClientOverAdb object.
Args:
android_device_or_serial: Either an AndroidDevice object, or the
serial that corresponds to the AndroidDevice. Note that the
serial must be present in an AndroidDevice entry in the ACTS
config.
test_interface: The network interface that will be used to send
traffic to the iperf server.
"""
self._android_device_or_serial = android_device_or_serial
self.test_interface = test_interface
@property
def _android_device(self):
if isinstance(self._android_device_or_serial, AndroidDevice):
return self._android_device_or_serial
else:
return _AndroidDeviceBridge.android_devices()[
self._android_device_or_serial]
def start(self, ip, iperf_args, tag, timeout=3600, iperf_binary=None):
"""Starts iperf client, and waits for completion.
Args:
ip: iperf server ip address.
iperf_args: A string representing arguments to start iperf
client. Eg: iperf_args = "-t 10 -p 5001 -w 512k/-u -b 200M -J".
tag: tag to further identify iperf results file
timeout: the maximum amount of time to allow the iperf client to run
iperf_binary: Location of iperf3 binary. If none, it is assumed the
the binary is in the path.
Returns:
The iperf result file path.
"""
clean_out = ''
try:
if not iperf_binary:
logging.debug('No iperf3 binary specified. '
'Assuming iperf3 is in the path.')
iperf_binary = 'iperf3'
else:
logging.debug('Using iperf3 binary located at %s' %
iperf_binary)
iperf_cmd = '{} -c {} {}'.format(iperf_binary, ip, iperf_args)
out = self._android_device.adb.shell(str(iperf_cmd),
timeout=timeout)
clean_out = out.split('\n')
if 'error' in clean_out[0].lower():
raise IPerfError(clean_out)
except (job.TimeoutError, AdbCommandError):
logging.warning('TimeoutError: Iperf measurement failed.')
full_out_path = self._get_full_file_path(tag)
with open(full_out_path, 'w') as out_file:
out_file.write('\n'.join(clean_out))
return full_out_path