blob: 1bb63e844a333af9b8f7598a782f3489b97a25a4 [file] [log] [blame] [edit]
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import common
import datetime
import fnmatch
import logging
import os
import re
import stat
import tempfile
import time
import traceback
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib import utils
from autotest_lib.client.common_lib.cros import site_eap_certs
from autotest_lib.client.cros import constants
from autotest_lib.server import autotest
from autotest_lib.server import hosts
from autotest_lib.server import site_attenuator
from autotest_lib.server import site_bsd_router
from autotest_lib.server import site_cisco_router
from autotest_lib.server import site_host_attributes
from autotest_lib.server import site_host_route
from autotest_lib.server import site_linux_bridge_router
from autotest_lib.server import site_linux_cros_router
from autotest_lib.server import site_linux_router
from autotest_lib.server import site_linux_server
from autotest_lib.server import site_linux_system
from autotest_lib.server import site_linux_vm_router
from autotest_lib.server import test
from autotest_lib.server.cros import remote_command
from autotest_lib.server.cros import wifi_test_utils
from autotest_lib.server.cros.wlan import wifi_client
class ScriptNotFound(Exception):
"""Raised when site_wlan scripts cannot be found."""
def __init__(self, scriptname):
super(ScriptNotFound, self).__init__(
'Script %s not found in search path' % scriptname)
class WiFiTest(object):
"""
WiFi Test.
Each test is specified as a dict. There must be a "name" entry that
gives the test name (a string) and a "steps" entry that has an ordered
tuple of test steps, where each step is a tuple [func, {args}].
Step funcs are one of:
config configure the router/AP using the specified params
(ssid automatically supplied); params are give as
BSD ifconfig(8) parameters and translated to match
the target router/AP's cli syntax
deconfig de-configure/shut-off the router/AP
connect connect client to AP using specified parameters
(ssid automatically supplied)
disconnect disconnect client from AP
client_check_* check the client's connection state to verify
a parameter was setup as expected; e.g.
client_check_bintval checks the beacon interval
set on the AP was adopted by the client
client_monitor_start start monitoring for wireless system events as
needed (e.g, kick off a process that listens)
client_monitor_stop stop monitoring for wireless system events
client_check_event_* check the client's event log for an event;
should always be preceded by client_monitor_start
sleep pause on the autotest server for a time
client_ping ping the server on the client machine
server_ping ping the client on the server machine
client_iperf run iperf on the client to the server
server_iperf run iperf on the server to the client
client_netperf run netperf on the client to the server
server_netperf run netperf on the server to the client
vpn_client_load_tunnel load 'tun' device for VPN client
vpn_client_kill Kill the running VPN client. Do nothing
if not running.
vpn_client_connect launch a VPN client to connect with the
VPN server
client_reboot reboots the client and waits for it to come back.
The amount of time to wait can be specified by a
'timeout' parameter, in seconds.
Steps that are done on the client or server machine are implemented in
this class. Steps that are done on the wifi router are implemented in
a separate class that knows how to control the router. There are presently
two classes: BSDRouter for routers based on FreeBSD and LinuxRouter for
those based on Linux/mac80211. Additional router support can be added
by adding a new class and auto-selecting it in __init__.
The WiFiTest class could be generalized to handle clients other than
ChromeOS; this would useful for systems that use Network Manager or
wpa_supplicant directly.
"""
_result_expect_failure = 1
_result_expect_success = 2
_result_dont_care = 3
_capability_5ghz = "5ghz"
_capability_multi_ap = "multi_ap"
_capability_multi_ap_same_band = "multi_ap_same_band"
def __init__(self, name, steps, client_requirements, config):
self.name = name
self.steps = steps
self.debug_dir = './debug'
step_req_client, step_req_router = self.__get_step_requirements()
self.client_requirements = (client_requirements + step_req_client)
self.router_requirements = step_req_router
self.perf_keyvals = {}
self.cur_frequency = None
self.cur_phymode = None
self.cur_security = None
self.cur_attenuation = None
self.vpn_kind = None
#
# There is a case that leaves some profiles in profile storage but
# not on the stack. This will cause the failure for 'profile_create'
# in later test cases. We use a list to record the created profiles
# and remove them all after the test case ends.
#
# Example:
# 1. profile_create: top (in profile storage)
# 2. profile_push: top (in profile storage and also on the stack)
# 3. connect
# 4. profile_pop: top (still in profile storage)
# 5. The test case ends with a failure.
# 6. Another test case starts.
# 7. profile_create: top (failure)
#
self.created_profiles = []
router = config['router']
#
# The server machine may be multi-homed or only on the wifi
# network. When only on the wifi net we suppress server_*
# requests since we cannot initiate them from the control machine.
#
server = config['server']
# NB: server may not be reachable on the control network
self.router = None
if not router['addr'].startswith('cisco'):
self.router = hosts.SSHHost(router['addr'],
port=int(router.get('port',22)))
self.defssid = wifi_test_utils.get_default_ssid(self.name,
router['addr'],
self.router)
defaults = config.get('defaults', {})
self.deftimeout = defaults.get('timeout', 30)
self.defpingcount = defaults.get('pingcount', 10)
self.defwaittime = defaults.get('netperf_wait_time', 3)
self.defiperfport = str(defaults.get('iperf_port', 12866))
self.defnetperfport = str(defaults.get('netperf_port', 12865))
if 'type' not in router:
# auto-detect router type
if router['addr'].startswith('cisco:'):
router['addr'] = router['addr'][6:]
router['type'] = 'cisco'
elif site_linux_router.isLinuxRouter(self.router):
router['type'] = 'linux'
elif site_bsd_router.isBSDRouter(self.router):
router['type'] = 'bsd'
else:
raise error.TestFail('Unable to autodetect router type')
if router['type'] == 'linux':
if config['router']['addr'] == config['client']['addr']:
self.wifi = site_linux_vm_router.LinuxVMRouter(
self.router, router, self.defssid)
elif site_linux_cros_router.isLinuxCrosRouter(self.router):
self.wifi = site_linux_cros_router.LinuxCrosRouter(
self.router, router, self.defssid)
else:
self.wifi = site_linux_bridge_router.LinuxBridgeRouter(
self.router, router, self.defssid)
elif router['type'] == 'bsd':
self.wifi = site_bsd_router.BSDRouter(self.router, router,
self.defssid)
elif router['type'] == 'cisco':
self.wifi = site_cisco_router.CiscoRouter(server['addr'], router,
self.defssid, router['addr'])
self.router = self.wifi.get_proxy()
else:
raise error.TestFail('Unsupported router')
attenuator = config.get('attenuator', dict())
# NB: Attenuator must be reachable on the control network
if attenuator.get('addr', None):
attenuator_host = hosts.SSHHost(attenuator['addr'],
port=int(attenuator.get('port',22)))
self.attenuator = site_attenuator.Attenuator(attenuator_host)
#
# The client machine must be reachable from the control machine.
# The address on the wifi network is retrieved each time it
# associates to the router.
#
client = config['client']
self.client_proxy = wifi_client.WiFiClient(
hosts.create_host(client['addr']),
self.debug_dir)
self.client_at = autotest.Autotest(self.client)
self.client_wifi_ip = None # client's IP address on wifi net
self.client_wifi_device_path = None # client's flimflam wifi path
self.client_signal_info = {}
self.client_installed_scripts = {}
self.client_logfile = client.get("logfile", "/var/log/messages")
self.ping_stats = {}
if not 'addr' in server:
raise error.TestError('All current WiFi tests require a server '
'reachable on the control network.')
# The 'hosting_server' is a machine which hosts network
# services, such as OpenVPN or StrongSwan.
self.hosting_server = site_linux_server.LinuxServer(
hosts.SSHHost(server['addr'], port=int(server.get('port', 22))),
server)
# potential bg thread for ping untilstop
self.ping_thread = None
# potential bg thread for client network monitoring
self.client_netdump_thread = None
self.client_stats_thread = None
# TODO(wiley) Fix the actual FLIMFLAM_TEST_PATH rather than this hack.
self.client_cmd_flimflam_lib = ('/usr/local' +
constants.FLIMFLAM_TEST_PATH)
self.host_route_args = {}
# Make sure powersave mode is off by default.
self.client_powersave_off([])
# Synchronize time on all devices
self.time_sync([])
# Find all repeated steps and create iterators for them
self.iterated_steps = {}
step_names = [step[0] for step in steps]
for step_name in list(set(step_names)):
if step_names.count(step_name) > 1:
self.iterated_steps[step_name] = 0
self.run_options = config['run_options']
if (config['router']['addr'] == config['server']['addr'] and
'server_capture_all' in self.run_options):
# Do not perform capture on the server if it is the same host
# as the router. Instead, use the 'router_capture_all' option
# so the router instance can mediate phy usage.
self.run_options.remove('server_capture_all')
if 'router_capture_all' not in self.run_options:
self.run_options.append('router_capture_all')
self.command_hooks = {}
if 'server_capture_all' in self.run_options:
self.__add_hook('config', self.hosting_server.start_capture_params)
if 'router_capture_all' in self.run_options:
self.__add_hook('config', self.wifi.start_capture_params)
if 'client_capture_all' in self.run_options:
self.__add_hook('config', self.client_start_capture)
if 'client_stats_all' in self.run_options:
self.__add_hook('config', self.client_start_statistics)
self.ethernet_mac_address = None
mac_string = wifi_test_utils.get_interface_mac(
self.client, 'eth0', self.client_proxy.command_ip)
if mac_string:
pieces = mac_string.split(":")
self.ethernet_mac_address = "".join(pieces)
self.init_profile()
self.client_capabilities = self.__get_client_capabilities()
self.router_capabilities = self.__get_router_capabilities()
@property
def client(self):
""" @return host object representing the DUT. """
return self.client_proxy.host
@property
def server(self):
""" @return host object representing the server. """
return self.hosting_server.server
@property
def server_wifi_ip(self):
""" @return string IPv4 address for the client to ping. """
if self.wifi.force_local_server:
# Server WiFi IP is created using a local server address.
return self.wifi.local_server_address(0)
return self.hosting_server.wifi_ip
@property
def client_wlanif(self):
"""@return string client WiFi device (e.g. mlan0)."""
return self.client_proxy.wifi_if
def init_profile(self):
# NB: do last so code above doesn't need to cleanup on failure
self.test_profile = {'name':'test'}
# cleanup in case a previous failure left the profile around
self.profile_cleanup()
self.profile_create(self.test_profile)
self.profile_push(self.test_profile)
def cleanup(self, params):
""" Cleanup state: disconnect client and destroy ap """
if 'no_cleanup_disconnect' not in self.run_options:
self.disconnect({})
self.wifi.destroy({})
self.wifi.cleanup({})
self.profile_cleanup()
self.client_stop_capture({})
self.client_stop_statistics({})
self.client_proxy.firewall_cleanup()
self.host_route_cleanup({})
self.wifi.stop_capture()
self.hosting_server.stop_capture()
def __get_client_capabilities(self):
caps = []
# Find out if this device supports 5GHz
system = site_linux_system.LinuxSystem(self.client, {}, '')
if [freq for freq in system.phys_for_frequency.keys() if freq > 5000]:
caps.append(WiFiTest._capability_5ghz)
logging.info("Client system capabilities: %s" % repr(caps))
return caps
def __get_router_capabilities(self):
caps = []
# Find out if this device supports multi-AP
system = site_linux_system.LinuxSystem(self.router, {}, '')
phymap = system.phys_for_frequency
frequencies = phymap.keys()
if [freq for freq in frequencies if freq > 5000]:
caps.append(WiFiTest._capability_5ghz)
if [freq for freq in frequencies if len(phymap[freq]) > 1]:
caps.append(WiFiTest._capability_multi_ap_same_band)
caps.append(WiFiTest._capability_multi_ap)
elif len(system.phy_bus_type) > 1:
caps.append(WiFiTest._capability_multi_ap)
logging.info("Router system capabilities: %s" % repr(caps))
return caps
def __get_step_requirements(self):
# This finds out what additional requirements are implicit based on
# the steps outlined in the test description.
client_reqs = set()
router_reqs = set()
for step in self.steps:
if len(step) < 2 or step[1].__class__ != dict:
continue
method = step[0]
params = step[1]
if method != 'config':
continue
if 'channel' in params and int(params['channel']) > 5000:
client_reqs.add(WiFiTest._capability_5ghz)
router_reqs.add(WiFiTest._capability_5ghz)
if 'multi_interface' in params:
router_reqs.add(WiFiTest._capability_multi_ap)
logging.info("Step requirements: Client: %s, AP: %s" %
(repr(client_reqs), repr(router_reqs)))
return list(client_reqs), list(router_reqs)
def __add_hook(self, hook, fn):
if hook not in self.command_hooks:
self.command_hooks[hook] = []
self.command_hooks[hook].append(fn)
def __run_hooks(self, hook, params):
if hook not in self.command_hooks:
return
for fn in self.command_hooks[hook]:
if getattr(fn, 'im_class', None):
hook_name = "%s.%s" % (fn.im_class.__name__, fn.__name__)
else:
hook_name = fn.__name__
logging.info("%s: hook '%s' for method '%s' params %s", self.name,
hook_name, hook, params)
fn(params)
def run(self):
"""
Run a WiFi test. Each step is interpreted as a method either in this
class or in one of the ancillary router or server classes and invoked
with the supplied parameter dictionary.
This routine bases its expectation of the method's result on the value
(if any) of a prefix before the method name:
- No prefix: The operation is expected to succeed.
- '!': The operation is expected to fail; this is useful, for
example, for testing parameter checking in flimflam.
- '~': We don't care whether the operation succeeds or fails; this
is especially useful during cleanup (e.g., deleting profiles).
"""
for requirement in self.client_requirements:
if not requirement in self.client_capabilities:
raise error.TestNAError(
"%s: client is missing required capability: %s" %
(self.name, requirement))
for requirement in self.router_requirements:
if not requirement in self.router_capabilities:
raise error.TestNAError(
"%s: AP is missing required capability: %s" %
(self.name, requirement))
for step_number, s in enumerate(self.steps):
method = s[0]
if method[0] == '!':
expect_result = WiFiTest._result_expect_failure
method = method[1:]
elif method[0] == '~':
expect_result = WiFiTest._result_dont_care
method = method[1:]
else:
expect_result = WiFiTest._result_expect_success
if len(s) > 1:
params = s[1]
else:
params = {}
if len(s) > 2:
failure_string = s[2]
else:
failure_string = None
# What should perf data be prefixed with?
if 'perf_prefix' in params:
self.prefix = '%s_%s' % (method, params.pop('perf_prefix'))
elif method in self.iterated_steps:
self.prefix = '%s_%02d' % (method, self.iterated_steps[method])
self.iterated_steps[method] += 1
else:
self.prefix = method
self.__run_hooks(method, params)
expectation = (" (expect failure)"
if expect_result is WiFiTest._result_expect_failure
else "")
logging.info("-------------------------------------------")
logging.info("%s: step %d '%s'%s params %s",
self.name, step_number+1, method, expectation, params)
self.error_message = ''
func = getattr(self, method, None)
if func is None:
func = getattr(self.wifi, method, None)
if func is None:
func = getattr(self.hosting_server, method, None)
if func is None and self.attenuator: # Must be an Attenuator method
func = getattr(self.attenuator, method, None)
if func is not None:
try:
func(params)
if expect_result is WiFiTest._result_expect_failure:
# TODO(wdg): This should be rewritten so that we don't
# swap the expectation value of a succeeding test,
# here. It's non-intuitive.
expect_result = WiFiTest._result_expect_success
raise error.TestFail("Succeeded (but expected "
"failure).")
except Exception, e:
if expect_result is WiFiTest._result_dont_care:
logging.info("%s: Failed (but we don't care).",
self.name)
continue
elif expect_result is WiFiTest._result_expect_failure:
if not failure_string:
logging.info("%s: Failed (but we expected that).",
self.name)
continue
# If test did not explicitly specify an error message,
# perhaps we can scoop one out of the exception
if not self.error_message and hasattr(e, 'result_obj'):
self.error_message = (e.result_obj.stderr +
e.result_obj.stdout)
if re.search(failure_string, self.error_message):
logging.info("%s: Failed (but we expected that).",
self.name)
continue
logging.error("Expected failure, but error string does "
"not match what was expected. Got %s but "
"expected %s.",
self.error_message,
failure_string)
logging.error("%s: Step '%s' failed: %s; abort test",
self.name, method, str(e))
logging.info("===========================================")
self.cleanup({})
traceback.print_exc()
raise e
else:
logging.error("%s: Step '%s' unknown; abort test",
self.name, method)
logging.info("===========================================")
self.cleanup({})
break
else:
logging.info("===========================================")
# If all steps ran successfully perform the normal cleanup steps
self.cleanup({})
def write_keyvals(self, job):
job.write_perf_keyval(self.perf_keyvals)
def write_perf(self, data):
for key, value in data.iteritems():
if value is not None:
self.perf_keyvals['%s_%s' % (self.prefix, key)] = value
def __get_interface_addresses(self, host, ifname, ip_version):
addresses = []
result = host.run("%s -%d addr show dev %s" %
(self.client_proxy.command_ip, ip_version, ifname))
for line in result.stdout.splitlines():
addr_match = re.search("inet\S* (\S*)", line)
if addr_match is not None:
addresses.append(addr_match.group(1))
return addresses
def __get_ipaddr(self, host, ifnet):
addrs = self.__get_interface_addresses(host, ifnet, 4)
if not addrs:
raise error.TestFail("No inet address found")
return addrs[0].split('/')[0]
def __get_ip6addrs(self, host, ifnet):
return self.__get_interface_addresses(host, ifnet, 6)
def __get_local_file(self, pattern):
"""
Pass a string pattern with a "%...d" in it, and get back a unique
string with the number of times this pattern has been used. This
is useful for creating unique local file names to store data
related to each test run.
"""
if not getattr(self, 'local_file_counts', None):
self.local_file_counts = {}
file_count = self.local_file_counts.get(pattern, 0)
self.local_file_counts[pattern] = file_count + 1
return os.join(self.debug_dir, pattern % file_count)
def install_script(self, script_name, *support_scripts):
if script_name in self.client_installed_scripts:
return self.client_installed_scripts[script_name]
script_client_dir = self.client.get_tmp_dir()
script_client_file = os.path.join(script_client_dir, script_name)
for copy_file in [script_name] + list(support_scripts):
# Look either relative to the current location of this file or
# relative to ../client/common_lib/cros/site_wlan for the script.
script_relative_paths = [['.'],
['..', 'client', 'common_lib',
'cros', 'site_wlan']]
for rel_path in script_relative_paths:
src_file = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
*(rel_path + [copy_file]))
if os.path.exists(src_file):
break
else:
raise ScriptNotFound(copy_file)
dest_file = os.path.join(script_client_dir,
os.path.basename(src_file))
self.client.send_file(src_file, dest_file, delete_dest=True)
self.client_installed_scripts[script_name] = script_client_file
return script_client_file
def insert_file(self, host, filename, contents):
"""
Send a byte string to a file on a remote host.
@param host host object representing a remote machine.
@param filename string path on remote machine to copy to.
@param contents raw contents of the file to be created
"""
# Write the contents to local disk first so we can use the easy
# built in mechanism to do this.
with tempfile.NamedTemporaryFile() as f:
f.write(contents)
f.flush()
os.chmod(f.name, stat.S_IRUSR | stat.S_IWUSR |
stat.S_IRGRP | stat.S_IWGRP |
stat.S_IROTH | stat.S_IWOTH)
host.send_file(f.name, filename, delete_dest=True)
def install_files(self, params):
""" Install files on the client or router with the provided
contents"""
systemname = params.get('system', None)
if systemname == 'router':
system = self.router
elif systemname == 'client':
system = self.client
elif systemname == 'server':
system = self.server
else:
raise error.TestFail('install_files: Must specify router, '
'server or client')
for name,contents in params.get('files', {}).iteritems():
self.insert_file(system, name, contents)
def __clean_tpm(self):
self.client.run("initctl restart chapsd")
cryptohome_cmd = "/usr/sbin/cryptohome"
self.client.run(cryptohome_cmd + " --action=tpm_take_ownership",
ignore_status = True)
self.client.run(cryptohome_cmd + " --action=tpm_wait_ownership",
ignore_status = True)
def __load_tpm_token(self, token_auth):
chaps_dir = "/tmp/chaps/"
self.client.run("rm -rf " + chaps_dir)
self.client.run("mkdir " + chaps_dir)
self.client.run("chown %s:%s %s" % (
"chaps",
"chronos-access",
chaps_dir))
self.client.run("chaps_client --load --path=%s --auth=\"%s\"" % (
chaps_dir,
token_auth))
def install_tpm_object(self, params):
if not "data" in params:
raise error.TestFail("Need a data parameter to install a TPM"
"certificate")
if not "id" in params:
raise error.TestFail("Need an object id to install a TPM"
"certificate")
if not "object_type" in params:
raise error.TestFail("Need to know whether the requested TPM "
"object is a private key or a certificate.")
if params["object_type"] == "cert":
conv_cmd = "openssl x509 -in %s -inform PEM -out %s -outform DER"
load_cmd = "p11_replay --import --path=%s --type=cert --id=%s"
elif params["object_type"] == "key":
conv_cmd = "openssl rsa -in %s -inform PEM -out %s -outform DER"
load_cmd = "p11_replay --import --path=%s --type=privkey --id=%s"
else:
raise error.TestFail("Invalid object type, expected either cert "
"or key.")
data = params["data"]
object_id = params["id"]
pem_path = self.client.run("mktemp").stdout.strip()
der_path = self.client.run("mktemp").stdout.strip()
self.install_files({ "system":"client", "files":{ pem_path:data } } )
# Convert those keys into DER format
self.client.run(conv_cmd % (pem_path, der_path))
# load that stuff into the TPM
self.client.run(load_cmd % (der_path, object_id))
def initialize_tpm(self, params):
""" Remove past state from TPM, and install a new token so that we can
later install objects to the TPM. Call this function before calling
install_tpm_object. """
self.__clean_tpm()
token_auth = site_eap_certs.auth_pin
self.__load_tpm_token(token_auth)
def install_nss_certificate(self, params):
""" Install an PEM certifcate into the NSS database on the client. """
if not 'data' in params:
raise error.TestFail('Need a data parameter to install a NSS '
'certificate')
if not 'id' in params:
raise error.TestFail('Need an object id to install an NSS '
'certificate')
data = params['data']
object_id = params['id']
pem_path = self.client.run('mktemp').stdout.strip()
der_path = self.client.run('mktemp').stdout.strip()
# Copy the PEM input certificate into a temporary file on the client.
self.install_files({ 'system' : 'client',
'files' : { pem_path : data } } )
# Convert the certificate into DER format
self.client.run('openssl x509 -in %s -inform PEM -out %s -outform DER' %
(pem_path, der_path))
# Load that stuff into the NSS database.
self.client.run('nsscertutil -A -t P,, -i %s -n %s -d sql:%s' %
(der_path, object_id, site_eap_certs.nss_cert_db_path))
# Make sure the NSS database remains accessible by the NSS user.
self.client.run('chown -R %s: %s' %
(site_eap_certs.nss_cert_db_user,
site_eap_certs.nss_cert_db_path))
# Cleanup.
self.client.run('rm -f %s %s' % (pem_path, der_path))
def initialize_nss(self, params):
""" Initialize the NSS database on the client. """
self.client.run('rm -rf %s' % site_eap_certs.nss_cert_db_path)
self.client.run('mkdir -p %s' % site_eap_certs.nss_cert_db_path)
self.client.run('echo "\n\n" | nsscertutil -N -d sql:%s' %
site_eap_certs.nss_cert_db_path)
def connect(self, params):
""" Connect client to AP/router """
script_client_file = self.install_script('site_wlan_connect.py',
'site_wlan_dbus_setup.py',
'site_wlan_wait_state.py',
'constants.py')
flags = []
if params.get('debug', True):
flags.append('--debug')
if params.get('hidden', False):
flags.append('--hidden')
if 'mode' in params:
flags.append('--mode=%s' % params['mode'])
if params.get('nosave', False):
flags.append('--nosave')
result = self.client.run('python "%s" %s "%s" "%s" "%s" "%d" "%d"' %
(script_client_file,
' '.join(flags),
params.get('ssid', self.wifi.get_ssid()),
params.get('security', ''),
params.get('psk', ''),
params.get('assoc_timeout', self.deftimeout),
params.get('config_timeout', self.deftimeout))).stdout.rstrip()
result_times = re.match('OK ([0-9\.]*) ([0-9\.]*) ([0-9\.]*) '
'([0-9\.]*) ([0-9]+) (\S+) (\w+) .*',
result)
if not result_times:
raise error.TestFail('Connect succeeded but result not parsed: ' +
result)
self.write_perf({'acquire_s' : result_times.group(1),
'select_s' : result_times.group(2),
'assoc_s' : result_times.group(3),
'config_s' : result_times.group(4),
'frequency' : result_times.group(5)})
for k in ('already_connected', 'clear_error', 'fast_fail',
'get_prop', 'in_progress', 'lost_dbus', 'multiple_attempts'):
if re.search(k, result) is not None:
self.write_perf({k:'true'})
print "%s: %s" % (self.name, result)
# stash connection state to emit for each test result
self.cur_frequency = result_times.group(5)
self.cur_phymode = result_times.group(6)
self.cur_security = result_times.group(7)
# fetch IP address of wireless device
self.client_wifi_ip = self.__get_ipaddr(self.client, self.client_wlanif)
logging.info("%s: client WiFi-IP is %s", self.name, self.client_wifi_ip)
# TODO(sleffler) not right for non-mac80211 devices
# TODO(sleffler) verify debugfs is mounted @ /sys/kernel/debug
self.client_debugfs_path = "/sys/kernel/debug/ieee80211/%s/netdev:%s" \
% ("phy0", self.client_wlanif)
def disconnect(self, params):
""" Disconnect previously connected client """
self.client_ping_bg_stop({})
script_client_file = self.install_script('site_wlan_disconnect.py',
'site_wlan_dbus_setup.py',
'constants.py')
result = self.client.run('python "%s" "%s" "%d"' %
(script_client_file,
params.get('ssid', self.wifi.get_ssid()),
params.get('wait_timeout', self.deftimeout))).stdout.rstrip()
print "%s: %s" % (self.name, result)
def profile(self, params):
""" Display profile information -- for debugging. """
print "\nSERVICES:"
result = self.client.run('%s/list-services' %
(self.client_cmd_flimflam_lib),
ignore_status=True)
print "%s: %s" % (self.name, result)
print "\nENTRIES:"
result = self.client.run('%s/profile list-entries' %
(self.client_cmd_flimflam_lib),
ignore_status=True)
print "%s: %s" % (self.name, result)
def client_check_profile_properties(self, params):
""" Verify that profile/entries properties equal expected values. """
args = ['--param %s:%s' % (var, val) for var, val in params.iteritems()]
if self.ethernet_mac_address:
args.append('--ethmac %s' % self.ethernet_mac_address)
args.append('--command ClientCheckProfileProperties')
script_client_file = self.install_script('site_wlan_profiles.py',
'site_wlan_dbus_setup.py',
'constants.py')
result = self.client.run('python "%s" %s' %
(script_client_file, ' '.join(args))).stdout.rstrip()
print "%s: %s" % (self.name, result)
def client_profile_delete_entry(self, params):
""" Verify that profile/entries properties equal expected values. """
args = ['--param %s:%s' % (var, val) for var, val in params.iteritems()]
if self.ethernet_mac_address:
args.append('--ethmac %s' % self.ethernet_mac_address)
args.append('--command ClientProfileDeleteEntry')
script_client_file = self.install_script('site_wlan_profiles.py',
'site_wlan_dbus_setup.py',
'constants.py')
result = self.client.run('python "%s" %s' %
(script_client_file, ' '.join(args))).stdout.rstrip()
print "%s: %s" % (self.name, result)
def __wait_service_start(self, params):
""" Wait for service transitions on client. """
script_client_file = self.install_script('site_wlan_wait_state.py',
'site_wlan_dbus_setup.py',
'constants.py')
args = []
# Whether to print out all state transitions of watched services to
# stderr
if params.get('debug', True):
args.append('--debug')
# Time limit on the execution of a single step
if 'step_timeout' in params:
args.append('--step_timeout %d' % int(params['step_timeout']))
# Time limit to wait for a service to appear in the service list
if 'service_timeout' in params:
args.append('--svc_timeout %d' % int(params['service_timeout']))
# Time limit on the execution of the entire series of steps
args.append('--run_timeout=%d' % int(params.get('run_timeout', 10)))
states = params.get('states', [])
if not states:
raise error.TestFail('No states given to wait for')
for service, state in states:
args.append('"%s=%s"' % (service or self.wifi.get_ssid(), state))
self.wait_service_states = states
return 'python "%s" %s' % (script_client_file, ' '.join(args))
def __wait_service_complete(self, params, result):
print "%s: %s" % (self.name, result)
states = self.wait_service_states
cstates = []
counts = {}
for service, state in states:
cstate = state.strip('+-').replace(':', '_')
cstates.append(cstate)
if state in counts:
counts[cstate] = 1
else:
counts[cstate] = 0
for cstate, intr in zip(cstates, result.stdout.split(' ')):
if intr.startswith('ERR_'):
raise error.TestFail('Wait for step %s failed with error %s' %
(cstate, intr))
if counts[cstate]:
index = '%s%d' % (cstate, counts[cstate] - 1)
counts[cstate] += 1
else:
index = cstate
self.write_perf({ index:float(intr) })
print " %s: %s" % (state, intr)
max = 'max_' + cstate
if max in params and float(intr) > float(params[max]):
raise error.TestFail('Too long to reach %s state: %f > %f' %
(cstate, float(intr), float(params[max])))
def wait_service(self, params):
result = self.client.run(self.__wait_service_start(params))
self.__wait_service_complete(params, result)
def wait_service_suspend_bg(self, params):
params['after_command'] = self.__wait_service_start(params)
self.client_suspend_bg(params)
def wait_service_suspend_end(self, params):
self.client_suspend_end(params)
self.__wait_service_complete(params, self.client_suspend_thread.result)
def client_powersave_on(self, params):
""" Enable power save operation """
self.client.run("iw dev %s set power_save on" % self.client_wlanif)
def client_powersave_off(self, params):
""" Disable power save operation """
self.client.run("iw dev %s set power_save off" % self.client_wlanif)
def client_check_powersave(self, params):
""" Check status of power save mode """
result = self.client.run("iw dev %s get power_save" %
self.client_wlanif)
output = result.stdout.rstrip() # NB: chop \n
# output should be either "Power save: on" or "Power save: off"
find_re = re.compile('([^:]+):\s+(\w+)')
find_results = find_re.match(output)
want = params[0]
if not find_results:
raise error.TestFail("wanted %s but not found" % want)
got = find_results.group(2)
if got != want:
raise error.TestFail("client_check_powersave: wanted %s got %s" %
(want, got))
def __client_check(self, param, want):
""" Verify negotiated station mode parameter """
result = self.client.run("cat '%s/%s'" %
(self.client_debugfs_path, param))
got = result.stdout.rstrip() # NB: chop \n
if got != want:
raise error.TestFail("client_check_%s: wanted %s got %s" %
(param, want, got))
def __client_check_iw_link(self, param, want):
""" Verify negotiated station mode parameter """
result = self.client.run("%s dev %s link" %
(self.client_proxy.command_iw,
self.client_wlanif))
find_re = re.compile("\s*%s:\s*(.*\S)\s*$" % param)
find_results = filter(bool, map(find_re.match,
result.stdout.splitlines()))
if not find_results:
raise error.TestFail("wanted %s but %s not found" % (want, param))
got = find_results[0].group(1)
if not re.match(want, got):
raise error.TestFail("wanted %s got %s" % (want, got))
def client_check_bintval(self, params):
""" Verify negotiated beacon interval """
self.__client_check_iw_link("beacon int", params[0])
def client_check_dtimperiod(self, params):
""" Verify negotiated DTIM period """
self.__client_check_iw_link("dtim period", params[0])
def client_check_rifs(self, params):
""" Verify negotiated RIFS setting """
self.__client_check("rifs", params[0])
def client_check_shortgi20(self, params):
""" Verify negotiated Short GI setting """
self.__client_check("sgi20", params[0])
def client_check_shortgi40(self, params):
""" Verify negotiated Short GI setting """
self.__client_check("sgi40", params[0])
def client_check_shortslot(self, params):
""" Verify negotiated Short Slot setting """
self.__client_check("short_slot", params[0])
def client_check_protection(self, params):
""" Verify negotiated CTS protection setting """
self.__client_check("cts_prot", params[0])
def client_monitor_start(self, params):
""" Start monitoring system events """
raise NotImplementedError("client_monitor_start")
def client_monitor_stop(self, params):
""" Stop monitoring system events """
raise NotImplementedError("client_monitor_stop")
def client_check_event_mic(self, params):
""" Check for MIC error event """
raise NotImplementedError("client_check_event_mic")
def client_check_event_countermeasures(self, params):
""" Check for WPA CounterMeasures event """
raise NotImplementedError("client_check_event_countermeasures")
def client_check_frequency(self, params):
""" Verify current frequency """
self.__client_check_iw_link("freq", params[0])
def client_check_service_properties(self, params):
""" Verify that service properties attained their expected values. """
service = params.pop("service", None)
if self.ethernet_mac_address and service == "ethernet":
service = "ethernet_%s" % self.ethernet_mac_address
states = [(service, "%s:%s" % (var, val))
for var, val in params.iteritems()]
self.wait_service({ "run_timeout": 0, "states": states})
def sleep(self, params):
time.sleep(float(params['time']))
def __unreachable(self, method):
logging.info("%s: SKIP step %s; server is unreachable",
self.name, method)
def __get_pingstats(self, ping_output):
stats = wifi_test_utils.parse_ping_output(ping_output)
stats['frequency'] = self.cur_frequency
stats['phymode'] = self.cur_phymode
stats['security'] = self.cur_security
return stats
def __print_pingstats(self, label, stats):
logging.info("%s: %s%s/%s, %s%% loss, rtt %s/%s/%s",
self.name, label, stats['xmit'], stats['recv'], stats['loss'],
stats['min'], stats['avg'], stats['max'])
def iw_event_scan(self, params):
""" Obtain the event scan output and store it
Params:
duration: Indicates time in seconds. If no duration
provided, iw even runs until iw_even_scan_stop
is called.
"""
duration = params.get('duration', 0)
cmd = '%s event -f' % self.client_proxy.command_iw
self.iw_event_thread = remote_command.Command(self.client, cmd)
if duration:
time.sleep(float(duration))
self.iw_event_thread_stop({})
def iw_event_scan_stop(self, params):
""" Stops the iw event thread """
self.iw_event_thread.join()
self.iw_event_thread_output = self.iw_event_thread.result.stdout
logging.debug('Output of iw scan is %s' % self.iw_event_thread_output)
def search_iw_events(self, params):
""" Searches through the last run iw event scan for strings
Params:
match: A list of strings to match.
Raises:
error.TestFail if any strings in match are not found in
iw event output.
"""
match_list = params.get('match', [])
if not hasattr(self, 'iw_event_thread_output'):
return
for match in match_list:
if match not in self.iw_event_thread_output:
raise error.TestFail("Expecting %s in iw event output but "
"it wasn't present.")
def client_ping(self, params):
"""
Ping the server from the client.
@param params dict of parameters used to form ping arguments.
"""
if 'ping_ip' in params:
ping_ip = params['ping_ip']
else:
if 'dest' in params:
ping_dest = params['dest']
else:
if self.wifi.has_local_server():
ping_dest = 'router'
else:
ping_dest = 'server'
if ping_dest == 'server':
ping_ip = self.server_wifi_ip
elif ping_dest == 'router':
ping_ip = self.wifi.get_wifi_ip(params.get('ap', 0))
else:
raise error.TestFail('Unknown ping destination "%s"' %
ping_dest)
count = int(params.get('count', self.defpingcount))
save_stats = params.get('save_stats', None)
stdout = self.client_proxy.ping(ping_ip,
params,
save_stats=save_stats,
count=count)
stats = self.__get_pingstats(stdout)
self.write_perf(stats)
self.__print_pingstats("client_ping ", stats)
def client_ping_bg(self, params):
"""
Ping the server from the client in the background of the test.
@param params dict of parameters used to form ping arguments.
"""
ping_ip = params.get('ping_ip', self.server_wifi_ip)
self.client_proxy.ping_bg(ping_ip, params)
def client_ping_bg_stop(self, params):
"""
Stop a client background ping.
@param params dict May contain key 'save_stats' which maps to a
string key to save the statistics from the background ping
under.
"""
self.client_proxy.ping_bg_stop(save_stats=params.get('save_stats',
None))
def assert_ping_similarity(self, params):
""" Assert that two specified sets of ping parameters are 'similar' """
if "stats0" not in params or "stats1" not in params:
raise error.TestFail("Missing ping statistics keys")
self.client_proxy.assert_ping_similarity(params["stats0"],
params["stats1"])
def server_ping(self, params):
""" Ping the client from the server """
ping_ip = params.get('ping_ip', self.client_wifi_ip)
count = params.get('count', self.defpingcount)
stats = self.hosting_server.ping(ping_ip, count, params)
self.write_perf(stats)
self.__print_pingstats("server_ping ", stats)
def server_ping_bg(self, params):
""" Ping the client from the server """
ping_ip = params.get('ping_ip', self.client_wifi_ip)
self.hosting_server.ping_bg(ping_ip, params)
def server_ping_bg_stop(self, params):
self.hosting_server.ping_bg_stop()
def client_ping6(self, params):
""" Ping the server from the client via IPv6 """
if 'ping_ip' in params:
ping_ip = params['ping_ip']
else:
result = self.client.run('%s -6 route show dev %s default' %
(self.client_proxy.command_ip,
self.client_wlanif))
router_match = re.search('via (\S*)', result.stdout)
if not router_match:
raise error.TestFail('Cannot find default router')
ping_ip = router_match.group(1)
params.setdefault('interface', self.client_wlanif)
count = params.get('count', self.defpingcount)
# set timeout for 3s / ping packet
result = self.client.run(
"%s %s %s" % (self.client_proxy.command_ping6,
wifi_test_utils.ping_args(params),
ping_ip),
timeout=3*int(count))
stats = self.__get_pingstats(result.stdout)
self.write_perf(stats)
self.__print_pingstats("client_ping6 ", stats)
def __run_iperf(self, mode, params):
""" Executes iperf w/ user-specified command-line options
Caller is responsible for passing in correct options. Otherwise a test
error would be raised.
"""
iperf_args = ""
if 'udp' in params:
iperf_args += " -u"
test = "UDP"
else:
test = "TCP"
if 'nodelay' in params:
iperf_args += " -N"
self.write_perf({'nodelay':'true'})
if 'window' in params:
iperf_args += " -w %s" % params['window']
self.write_perf({'window':params['window']})
if 'bufsize' in params: # Set buffer size in bytes
iperf_args += " -l %s" % params['bufsize']
self.write_perf({'bufsize':params['bufsize']})
iperf_args += " -p %s" % self.defiperfport
# Assemble client-specific arguments
test_time = params.get('test_time', 15)
client_args = iperf_args + " -f m -t %s" % test_time
if 'bandwidth' in params:
client_args += " -b %s" % params['bandwidth']
self.write_perf({'bandwidth':params['bandwidth']})
elif 'udp' in params:
bw = None
# Supply nominal channel bandwidth
if self.cur_phymode == '802.11b':
bw = '7m'
elif self.cur_phymode == '802.11a':
bw = '30m' # assumes no bursting
elif self.cur_phymode == '802.11g':
bw = '30m' # assumes no bursting
elif self.cur_phymode == '802.11n':
# TODO(sleffler) distinguish HT20/HT40 and # streams
bw = '110m'
if bw is not None:
client_args += " -b %s" % bw
self.write_perf({'bandwidth':bw})
if mode == 'server':
server = { 'host': self.client,
'cmd': self.client_proxy.command_iperf }
client = { 'host': self.server,
'cmd': self.hosting_server.cmd_iperf,
'target': self.client_wifi_ip }
# Open up access from the server into our DUT
self.client_proxy.firewall_open('tcp', self.server_wifi_ip)
self.client_proxy.firewall_open('udp', self.server_wifi_ip)
else: # mode == 'client'
server = { 'host': self.server,
'cmd': self.hosting_server.cmd_iperf }
client = { 'host': self.client,
'cmd': self.client_proxy.command_iperf,
'target': self.server_wifi_ip }
iperf_thread = remote_command.Command(server['host'],
"%s -s %s" % (server['cmd'], iperf_args))
# NB: block to allow server time to startup
time.sleep(self.defwaittime)
# Run iperf command and receive command results
t0 = time.time()
# Set timeout to be twice the test_time
results = client['host'].run("%s -c %s%s" % \
(client['cmd'], client['target'], client_args),
timeout=2*int(test_time))
actual_time = time.time() - t0
logging.info('actual_time: %f', actual_time)
iperf_thread.join()
# Close up whatever firewall rules we created for iperf
self.client_proxy.firewall_cleanup()
self.write_perf({
'attenuation': self.cur_attenuation or 'unknown',
'frequency' : self.cur_frequency,
'phymode' : self.cur_phymode,
'security' : self.cur_security,
'test' : test,
'mode' : mode,
'actual_time': actual_time,
})
logging.info(results)
lines = results.stdout.splitlines()
# Each test type has a different form of output
if test in ['TCP', 'TCP_NODELAY']:
"""Parses the following and returns a singleton containing
throughput.
------------------------------------------------------------
Client connecting to localhost, TCP port 5001
TCP window size: 49.4 KByte (default)
------------------------------------------------------------
[ 3] local 127.0.0.1 port 57936 connected with 127.0.0.1 port 5001
[ ID] Interval Transfer Bandwidth
[ 3] 0.0-10.0 sec 2.09 GBytes 1.79 Gbits/sec
"""
tcp_tokens = lines[6].split()
if len(tcp_tokens) >= 6 and tcp_tokens[-1].endswith('bits/sec'):
self.write_perf({'throughput':float(tcp_tokens[-2])})
elif test in ['UDP', 'UDP_NODELAY']:
"""Parses the following and returns a tuple containing throughput
and the number of errors.
------------------------------------------------------------
Client connecting to localhost, UDP port 5001
Sending 1470 byte datagrams
UDP buffer size: 108 KByte (default)
------------------------------------------------------------
[ 3] local 127.0.0.1 port 54244 connected with 127.0.0.1 port 5001
[ ID] Interval Transfer Bandwidth
[ 3] 0.0-10.0 sec 1.25 MBytes 1.05 Mbits/sec
[ 3] Sent 893 datagrams
[ 3] Server Report:
[ ID] Interval Transfer Bandwidth Jitter Lost/Total Datagrams
[ 3] 0.0-10.0 sec 1.25 MBytes 1.05 Mbits/sec 0.032 ms 1/ 894 (0.11%)
[ 3] 0.0-15.0 sec 14060 datagrams received out-of-order
"""
# Search for the last row containing the word 'Bytes'
mb_row = [row for row,data in enumerate(lines)
if 'Bytes' in data][-1]
udp_tokens = lines[mb_row].replace('/', ' ').split()
# Find the column ending with "...Bytes"
mb_col = [col for col,data in enumerate(udp_tokens)
if data.endswith('Bytes')]
if len(mb_col) > 0 and len(udp_tokens) >= mb_col[0] + 9:
# Make a sublist starting after the column named "MBytes"
stat_tokens = udp_tokens[mb_col[0]+1:]
self.write_perf({'throughput':float(stat_tokens[0]),
'jitter':float(stat_tokens[3]),
'lost':float(stat_tokens[7].strip('()%'))})
else:
raise error.TestError('Unhandled test')
return True
def client_iperf(self, params):
""" Run iperf on the client against the server """
self.__run_iperf('client', params)
def server_iperf(self, params):
""" Run iperf on the server against the client """
self.__run_iperf('server', params)
def set_attenuation(self, params):
""" Record current attenuation value """
self.cur_attenuation = self.attenuator.set_attenuation(params)
def _parse_attenuation(self, params):
""" Sanity check attenuation values and make simple corrections """
# Attenuations are measured in units of dB
fixed_atten = params.get('fixed_atten', None)
start_atten = params.get('start_atten', fixed_atten)
end_atten = params.get('end_atten', None)
if ((fixed_atten is None) or (start_atten is None) or
(end_atten is None)):
err = ('Please specify all attenuation values for this test: '
'fixed_atten, start_atten, end_atten.')
raise error.TestFail(err)
fixed_atten = int(fixed_atten)
start_atten = int(start_atten)
end_atten = int(end_atten)
if start_atten < fixed_atten:
logging.warning('start_atten (%d) reset to fixed_atten (%d)',
start_atten, fixed_atten)
start_atten = fixed_atten
if end_atten < start_atten:
logging.warning('end_atten (%d) reset to start_atten (%d)',
end_atten, start_atten)
end_atten = start_atten
return fixed_atten, start_atten, end_atten
def _parse_iperf_options(self, params):
""" Parse iperf options and set reasonable defaults """
proto = params.get('proto', 'udp')
iperf_params = {
proto: None,
'test_time': params.get('test_time', '60'),
'bufsize': params.get('bufsize', '1500'),
}
if proto == 'udp':
iperf_params['bandwidth'] = params.get('bandwidth', '150M')
else:
iperf_params['window'] = params.get('window', '512K')
return proto, iperf_params
def rvr_test(self, params):
""" Run rate vs. range tests using a variable attenuator """
if self.attenuator is None:
raise error.TestFail(
'No variable attenuator specified for this test.')
fixed_atten, start_atten, end_atten = self._parse_attenuation(params)
proto, iperf_params = self._parse_iperf_options(params)
atten_step = params.get('atten_step', 2)
# Pad 1 to end_atten to ensure it's not excluded by range(), e.g.
# input: start_atten = 60, end_atten = 70, atten_step = 2
# output with padding = [60, 62, 64, 66, 68, 70] (desired)
# output without padding = [60, 62, 64, 66, 68] (probabaly undesired)
atten_sequence = range(start_atten, end_atten+1, atten_step)
for step_number, atten in enumerate(atten_sequence):
for port in [0, 1]: # Grover testbed uses ports 0 and 1
self.set_attenuation(dict(va_port=port, fixed_atten=fixed_atten,
total_atten=atten))
iperf_params['attenuation'] = self.cur_attenuation
self.sleep(dict(time='5')) # Wait 5 seconds for config to propagate
# Set iteration prefix for write_perf() to use
self.prefix = 'rvr_%s_%d' % (proto, step_number)
self.server_iperf(iperf_params)
def __run_netperf(self, mode, params):
if mode == 'server':
server = { 'host': self.client,
'cmd': self.client_proxy.command_netserv }
client = { 'host': self.server,
'cmd': self.hosting_server.cmd_netperf,
'target': self.client_wifi_ip }
# Open up access from the server into our DUT
self.client_proxy.firewall_open('tcp', self.server_wifi_ip)
self.client_proxy.firewall_open('udp', self.server_wifi_ip)
else:
server = { 'host': self.server,
'cmd': self.hosting_server.cmd_netserv }
client = { 'host': self.client,
'cmd': self.client_proxy.command_netperf,
'target': self.server_wifi_ip }
netperf_thread = remote_command.Command(server['host'],
"%s -p %s" % (server['cmd'], self.defnetperfport))
# NB: block to allow server time to startup
time.sleep(self.defwaittime)
# Assemble arguments for client command
test = params.get('test', 'TCP_STREAM')
netperf_args = '-H %s -p %s -t %s -l %d' % (client['target'],
self.defnetperfport, test, params.get('test_time', 15))
# Run netperf command and receive command results
t0 = time.time()
results = client['host'].run("%s %s" % (client['cmd'], netperf_args))
actual_time = time.time() - t0
logging.info('actual_time: %f', actual_time)
netperf_thread.join()
# Close up whatever firewall rules we created for netperf
self.client_proxy.firewall_cleanup()
self.write_perf({
'frequency' : self.cur_frequency,
'phymode' : self.cur_phymode,
'security' : self.cur_security,
'test' : test,
'mode' : mode,
'actual_time': actual_time,
})
logging.info(results)
lines = results.stdout.splitlines()
# Each test type has a different form of output
if test in ['TCP_STREAM', 'TCP_MAERTS', 'TCP_SENDFILE']:
"""Parses the following (works for both TCP_STREAM, TCP_MAERTS and
TCP_SENDFILE) and returns a singleton containing throughput.
TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to \
foo.bar.com (10.10.10.3) port 0 AF_INET
Recv Send Send
Socket Socket Message Elapsed
Size Size Size Time Throughput
bytes bytes bytes secs. 10^6bits/sec
87380 16384 16384 2.00 941.28
"""
self.write_perf({'Throughput':float(lines[6].split()[4])})
elif test == 'UDP_STREAM':
"""Parses the following and returns a touple containing throughput
and the number of errors.
UDP UNIDIRECTIONAL SEND TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET \
to foo.bar.com (10.10.10.3) port 0 AF_INET
Socket Message Elapsed Messages
Size Size Time Okay Errors Throughput
bytes bytes secs # # 10^6bits/sec
129024 65507 2.00 3673 0 961.87
131072 2.00 3673 961.87
"""
udp_tokens = lines[5].split()
self.write_perf({'Throughput':float(udp_tokens[5]),
'Errors':float(udp_tokens[4])})
elif test in ['TCP_RR', 'TCP_CRR', 'UDP_RR']:
"""Parses the following which works for both rr (TCP and UDP)
and crr tests and returns a singleton containing transfer rate.
TCP REQUEST/RESPONSE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET \
to foo.bar.com (10.10.10.3) port 0 AF_INET
Local /Remote
Socket Size Request Resp. Elapsed Trans.
Send Recv Size Size Time Rate
bytes Bytes bytes bytes secs. per sec
16384 87380 1 1 2.00 14118.53
16384 87380
"""
self.write_perf({'Transfer_Rate':float(lines[6].split()[5])})
else:
raise error.TestError('Unhandled test')
return True
def client_netperf(self, params):
""" Run netperf on the client against the server """
self.__run_netperf('client', params)
def server_netperf(self, params):
""" Run netperf on the server against the client """
self.__run_netperf('server', params)
def client_start_capture(self, params):
"""Start capturing network traffic on the client.
@param params dict of site_wifitest parameters.
"""
self.client_proxy.start_capture()
def client_stop_capture(self, params):
"""Stop capturing network traffic on the client.
Stop capturing packets on the client, and copy the previous
ongoing packet capture file to the autotest server.
@param params dict of site_wifitest parameters.
"""
self.client_proxy.stop_capture()
def client_suspend(self, params):
""" Suspend the system """
script_client_file = self.install_script('site_system_suspend.py',
'../client/cros/rtc.py',
'../client/cros/'
'sys_power.py',
'../client/cros/'
'upstart.py')
result = self.client.run('python "%s" %d' %
(script_client_file, int(params.get("suspend_time", 5))))
def client_suspend_bg(self, params):
""" Suspend the system in the background """
script_client_file = self.install_script('site_system_suspend.py',
'../client/cros/rtc.py',
'../client/cros/'
'sys_power.py',
'../client/cros/'
'upstart.py')
cmd = ('python "%s" %d %s' %
(script_client_file,
int(params.get("suspend_time", 5)),
params.get("after_command", '')))
self.client_suspend_thread = remote_command.Command(self.client, cmd)
def client_suspend_end(self, params):
""" Join the backgrounded suspend thread """
self.client_suspend_thread.join()
if self.client_suspend_thread.result.exit_status:
raise error.TestError('suspend failed')
def restart_supplicant(self, params):
""" Restart wpa_supplicant. Cert params are unfortunately "sticky". """
self.client.run("stop wpasupplicant; start wpasupplicant")
def profile_create(self, params):
""" Create a profile with the specified name """
self.client.run('%s/profile create %s' %
(self.client_cmd_flimflam_lib, params['name']))
self.created_profiles.append(params['name'])
def profile_remove(self, params, ignore_status=False):
""" Remove the specified profile """
self.client.run('%s/profile remove %s' %
(self.client_cmd_flimflam_lib, params['name']),
ignore_status=ignore_status)
def profile_push(self, params):
""" Push the specified profile on the stack """
self.client.run('%s/profile push %s' %
(self.client_cmd_flimflam_lib, params['name']))
def profile_pop(self, params, ignore_status=False):
""" Pop the specified profile from the stack or any profile
if no name is specified.
"""
if 'name' in params:
self.client.run('%s/profile pop %s' %
(self.client_cmd_flimflam_lib, params['name']),
ignore_status=ignore_status)
else:
self.client.run('%s/profile pop' %
(self.client_cmd_flimflam_lib),
ignore_status=ignore_status)
def profile_cleanup(self):
""" Cleanup all profiles """
# Pop and remove all profiles on the stack until 'default' is found.
self.client.run('%s/profile clean' %
(self.client_cmd_flimflam_lib))
# Some profiles may still in profile storage but not on the stack,
# invoke 'profile_remove' for self.created_profiles to make sure that
# all profiles are deleted.
for profile_name in self.created_profiles:
self.profile_remove({'name':profile_name}, ignore_status=True)
def __get_wifi_device_path(self):
if self.client_wifi_device_path:
return self.client_wifi_device_path
ret = []
result = self.client.run('%s/list-devices' %
self.client_cmd_flimflam_lib)
device_path = None
for line in result.stdout.splitlines():
m = re.match('\[\s*(\S*)\s*\]', line)
if m is not None:
device_path = m.group(1)
continue
if re.search('Name = Wireless', line) is not None:
self.client_wifi_device_path = device_path
break
return self.client_wifi_device_path
def enable_wifi(self, params):
wifi = self.__get_wifi_device_path()
if wifi:
self.client.run('%s/enable-device %s' %
(self.client_cmd_flimflam_lib, wifi))
def disable_wifi(self, params):
wifi = self.__get_wifi_device_path()
if wifi:
self.client.run('%s/disable-device %s' %
(self.client_cmd_flimflam_lib, wifi))
def client_get_signal(self, params):
result = self.client.run('%s dev %s link; '
'%s dev %s station dump; '
'%s dev %s survey dump' %
((self.client_proxy.command_iw,
self.client_wlanif) * 3))
current_frequency = None
link_frequency = None
signal_info = {}
signal_values = ( 'frequency', 'signal', 'signal avg', 'noise' )
for line in result.stdout.splitlines():
m = re.match('\s*(\S.*):\s*(\S*)', line)
if m is None:
continue
var, val = m.groups()
if var == 'freq':
link_frequency = val
elif var == 'frequency':
current_frequency = val
if (var in signal_values and
(current_frequency == None or
current_frequency == link_frequency)):
signal_info[var] = val
self.client_signal_info = signal_info
logging.info('Signal Info: %s' % repr(signal_info))
def bgscan_set(self, params):
""" Control wpa_supplicant bgscan """
opts = ""
if params.get('short_interval', None):
opts += " BgscanShortInterval=%s" % params['short_interval']
if params.get('long_interval', None):
opts += " ScanInterval=%s" % params['long_interval']
if params.get('signal', None):
signal = params['signal']
if signal == 'auto':
if 'signal avg' not in self.client_signal_info:
raise error.TestError('No signal info')
else:
signal = int(self.client_signal_info['signal avg'])
if 'offset' in params:
signal += int(params['offset'])
if 'noise' in self.client_signal_info:
# Compensate for real noise vs standard estimate
signal -= 95 + int(self.client_signal_info['noise'])
logging.info('Auto signal: %s' % repr(signal))
opts += " BgscanSignalThreshold=%s" % signal
if params.get('method', None):
opts += " BgscanMethod=%s" % params['method']
self.client.run('%s/set-bgscan --interface %s %s' %
(self.client_cmd_flimflam_lib, self.client_wlanif,
opts))
def bgscan_disable(self, params):
""" Disable wpa_supplicant bgscan """
self.bgscan_set({'method' : 'none'})
def bgscan_enable(self, params):
""" Enable wpa_supplicant bgscan """
self.bgscan_set({'method' : 'default'})
def scan(self, params):
scan_params = ''
frequencies = params.get('freq', [])
if frequencies:
scan_params += ' freq %s' % ' '.join(frequencies)
ssids = params.get('ssid', [])
if ssids:
scan_params += ' ssid "%s"' % '" "'.join(ssids)
result = self.client.run("%s dev %s scan%s" %
(self.client_proxy.command_iw,
self.client_wlanif,
scan_params))
scan_lines = result.stdout.splitlines()
for ssid in ssids:
if ssid and ('\tSSID: %s' % ssid) not in scan_lines:
raise error.TestFail('SSID %s is not in scan results: %s' %
(ssid, result.stdout))
def time_sync(self, params):
for name in params or ['client', 'server', 'router']:
system = { 'client': self.client,
'server': self.server,
'router': self.router }.get(name)
epoch_seconds = time.time()
busybox_format = '%Y%m%d%H%M.%S'
busybox_date = datetime.datetime.utcnow().strftime(busybox_format)
system.run('date -u --set=@%s 2>/dev/null || date -u %s' % \
(epoch_seconds, busybox_date))
def vpn_client_load_tunnel(self, params):
""" Load the 'tun' device.
Necessary when the VPN Server is configured with 'dev tun'.
"""
result = self.client.run('modprobe tun') # When server using tunnel.
def vpn_client_connect(self, params):
""" Configure & launch the VPN client.
Parameters:
'kind' : required
Indicates the kind of VPN which is to be used.
Valid values are:
l2tpipsec-cert
l2tpipsec-psk
openvpn
'vpn-host-ip': optional
Specifies the IP of the VPN server. If not provided,
defaults to 'self.server_wifi_ip'
'files' : required
A dict which contains a set of file names.
'ca-certificate' : path to CA certificate file
'client-certificate' : path to client certificate file
'client-key' : path to client key file
'remote-cert-tls' : optional
If provided, this option can be 'server', 'client' or
'none'.
If not specified, the default is 'none'.
The value provided is passed directly to 'connect-vpn'.
"""
self.vpn_client_kill({}) # Must be first. Relies on self.vpn_kind.
self.vpn_kind = params.get('kind', None)
# Starting up the VPN client may cause the DUT's routing table (esp.
# the default route) to change. Set up a host route backwards so
# we don't lose our control connection in that event.
self.__add_host_route(self.client)
if self.vpn_kind is None:
raise error.TestFail('No VPN kind specified for this test.')
elif self.vpn_kind == 'openvpn':
# 'ca_certificate', 'client-certificate' and 'client-key'.
vpn_host_ip = params.get('vpn-host-ip',
self.server_wifi_ip)
cert_pathnames = params.get('files', {})
remote_cert_tls_option = ""
remote_cert_tls = params.get('remote-cert-tls', None)
if remote_cert_tls is not None:
remote_cert_tls_option = "--remote-cert-tls " + remote_cert_tls
result = self.client.run('%s/connect-vpn '
'--verbose '
'%s '
'openvpn vpn-name %s vpn-domain '
'%s ' # ca certificate
'%s ' # client certificate
'%s' % # client key
(self.client_cmd_flimflam_lib,
remote_cert_tls_option,
vpn_host_ip,
cert_pathnames['ca-certificate'],
cert_pathnames['client-certificate'],
cert_pathnames['client-key']))
elif self.vpn_kind == 'l2tpipsec-psk':
# vpn_host_ip is self.server.ip because that is the
# adapter that ipsec listens on.
vpn_host_ip = params.get('vpn-host-ip', self.server.ip)
password = params.get('password' , None)
chapuser = params.get('chapuser' , None)
chapsecret = params.get('chapsecret', None)
result = self.client.run('%s/connect-vpn '
'--verbose '
'l2tpipsec-psk vpn-name %s vpn-domain '
'%s ' # password
'%s ' # chapuser
'%s' % # chapsecret
(self.client_cmd_flimflam_lib,
vpn_host_ip,
password, chapuser, chapsecret))
elif self.vpn_kind == 'l2tpipsec-cert':
# vpn_host_ip is self.server.ip because that is the
# adapter that ipsec listens on.
vpn_host_ip = params.get('vpn-host-ip', self.server.ip)
chapuser = params.get('chapuser' , None)
chapsecret = params.get('chapsecret', None)
ca_cert_id = params.get('cacertid', None)
result = self.client.run('%s/connect-vpn '
'--verbose '
'l2tpipsec-cert vpn-name %s vpn-domain '
'%s ' # CACertNSS
'0 ' # ClientCertSlot
'%s ' # ClientCertID
'%s ' # PIN
'%s ' # chapuser
'%s' % # chapsecret
(self.client_cmd_flimflam_lib,
vpn_host_ip,
ca_cert_id,
site_eap_certs.cert_1_tpm_key_id,
site_eap_certs.auth_pin,
chapuser,
chapsecret))
else:
raise error.TestFail('(internal error): No launch case '
'for VPN kind (%s)' % self.vpn_kind)
def vpn_client_kill(self, params):
""" Kill the VPN client if it's running. """
if self.vpn_kind is not None:
if self.vpn_kind == 'openvpn':
self.client.run("pkill openvpn")
elif self.vpn_kind in ('l2tpipsec-psk', 'l2tpipsec-cert'):
self.client.run("/usr/sbin/ipsec stop")
else:
raise error.TestFail('(internal error): No kill case '
'for VPN kind (%s)' % self.vpn_kind)
self.vpn_kind = None
self.__del_host_route(self.client)
def __add_host_route(self, host):
# What is the local address we use to get to the test host?
local_ip = site_host_route.LocalHostRoute(host.ip).route_info["src"]
# How does the test host currently get to this local address?
host_route = site_host_route.RemoteHostRoute(host, local_ip).route_info
# Flatten the returned dict into a single string
route_args = " ".join(" ".join(x) for x in host_route.iteritems())
self.host_route_args[host.ip] = "%s %s" % (local_ip, route_args)
host.run("ip route add %s" % self.host_route_args[host.ip])
def __del_host_route(self, host):
if host.ip in self.host_route_args:
host.run("ip route del %s" % self.host_route_args.pop(host.ip))
def host_route_cleanup(self, params):
for host in (self.client, self.server, self.router):
self.__del_host_route(host)
def log_time_diff(self, params):
log_file = self.client_logfile
time_diff = self.install_script('site_log_time_diff.py')
result = self.client.run("python '%s' --from='%s' --to='%s'" %
(time_diff, params['from'], params['to']))
if '-' in result.stdout:
logging.info("Unable to find timespan")
return
if "perf" in params:
self.write_perf({params['perf']:float(result.stdout)})
def client_deauth(self, params):
self.wifi.deauth({'client': self.client_proxy.wifi_mac})
def client_reboot(self, params):
self.client_installed_scripts = {}
if 'timeout' not in params:
logging.info("Using default reboot timeout")
self.client.reboot()
else:
reboot_timeout = float(params['timeout'])
logging.info("Reboot timeout is %f seconds", reboot_timeout)
self.client.reboot(timeout=reboot_timeout)
self.profile_cleanup()
self.profile_create(self.test_profile)
self.profile_push(self.test_profile)
def __store_pkcs11_resource(self, pkcs11_lib, user_pin, slot_id,
label, der_file_path, resource_type):
self.client.run('pkcs11-tool '
'--module=%s '
'--pin %s '
'--id %s '
'--label %s '
'--write-object %s '
'--type %s' %
(pkcs11_lib,
user_pin,
slot_id,
label,
der_file_path,
resource_type))
def client_check_ipv6(self, params):
addrs = self.__get_ip6addrs(self.client, self.client_wlanif)
errors = []
if 'address_count' in params:
expected = int(params['address_count'])
if expected != len(addrs):
errors.append('IPv6 address count %d is different from '
'expected %d' % (len(addrs), expected))
if 'local_count' in params:
local_count = 0
expected = int(params['local_count'])
for addr in addrs:
if addr.startswith('fe80'):
local_count += 1
if local_count != expected:
errors.append('IPv6 address local count %d is different from '
'expected %d' % (local_count, expected))
if 'mac_count' in params:
mac_count = 0
expected = int(params['mac_count'])
mac_parts = self.client_proxy.wifi_mac.split(':')
# Convert last 3 octets of MAC into suffix of IPv6 address
shorts = [int(mac_parts[-3], 16), int(''.join(mac_parts[-2:]), 16)]
mac_suffix_re = re.compile('%x:%x/' % tuple(shorts))
for addr in addrs:
if mac_suffix_re.search(addr):
mac_count += 1
if mac_count != expected:
errors.append('IPv6 address mac count %d is different from '
'expected %d' % (mac_count, expected))
if 'default_route' in params:
result = self.client.run('%s -6 route show dev %s default' %
(self.client_proxy.command_ip,
self.client_wlanif))
found = bool('default' in result.stdout)
expected = bool(params['default_route'])
if found != expected:
errors.append('IPv6 default route found == %s '
'from expected %s' % (found, expected))
if errors:
errors.append('Addresses are: %s' % ', '.join(addrs))
raise error.TestFail('\n'.join(errors))
def client_start_statistics(self, params):
""" Start capturing network statistics on the client """
self.client_stop_statistics({})
script = 'site_wlan_statistics.py'
script_client_file = self.install_script(script)
cmd = ('python %s --count=%s --period=%s' %
(script_client_file,
params.get('count', '-1'),
params.get('period', '1')))
logging.info(cmd)
self.client_stats_thread = remote_command.Command(self.client, cmd)
def client_stop_statistics(self, params):
self.client.run('pkill -f site_wlan_statistics.py',
ignore_status=True)
if self.client_stats_thread is not None:
self.client_stats_thread.join()
stats = self.client_stats_thread.result.stdout
logging.info(stats)
file(self.__get_local_file(
'client_interface_statistics_%02d.txt'), 'w').write(stats)
self.client_stats_thread = None
def client_test_ipaddr(self, params):
interface = params.get('interface', self.client_wlanif)
self.__get_ipaddr(self.client, interface)
def client_configure_service(self, params):
guid = params.pop('GUID', '')
args = ''
for var, val in params.iteritems():
args += ' %s %s' % (var, val)
self.client.run('%s/configure-service %s %s' %
(self.client_cmd_flimflam_lib, guid, args))
def client_roam(self, params):
instance = params.get('instance', 0)
wifi_mac = self.wifi.get_hostapd_mac(instance)
# The "wpa_cli" command can only be run as the "wpa" user. That
# user does not have a valid shell, so without specifying one, the
# "su" command will fail.
self.client.run('su wpa -s /bin/bash -c "%s roam %s"' %
(self.client_proxy.command_wpa_cli, wifi_mac))
def __byfile(a, b):
if a['file'] < b['file']:
return -1
elif a['file'] > b['file']:
return 1
else:
return 0
def read_tests(dir, *args):
"""
Collect WiFi test tuples from files. File names are used to
sort the test objects so the convention is to name them NNN<test>
where NNN is a decimal number used to sort and <test> is an
identifying name for the test; e.g. 000Check11b
"""
tests = []
for file in os.listdir(dir):
if any(fnmatch.fnmatch(file, pat) for pat in args):
fd = open(os.path.join(dir, file))
try:
test = eval(fd.read())
except Exception, e:
logging.error("%s: %s", os.path.join(dir, file), str(e))
raise e
test['file'] = file
tests.append(test)
# use filenames to sort
return sorted(tests, cmp=__byfile)
def read_wifi_testbed_config(file, client_addr=None, server_addr=None,
router_addr=None):
# read configuration file
fd = open(file)
config = eval(fd.read())
# Read in attributes from host config database
client_attributes = site_host_attributes.HostAttributes(client_addr)
# client must be reachable on the control network
client = config['client']
if client_addr is not None:
client['addr'] = client_addr
# router must be reachable on the control network
router = config['router']
if router_addr is None and hasattr(client_attributes, 'router_addr'):
router_addr = client_attributes.router_addr
if router_addr is not None:
router['addr'] = router_addr
server = config['server']
if server_addr is None and hasattr(client_attributes, 'server_addr'):
server_addr = client_attributes.server_addr
if server_addr is not None:
server['addr'] = server_addr
# tag jobs w/ the router's address on the control network
config['tagname'] = router['addr']
return config
def run_test_dir(test_name, job, args, machine):
# convert autoserv args to something usable
opts = dict([[k, v] for (k, e, v) in [x.partition('=') for x in args]])
# config file located under third_party/autotest/files/client/config/
config_file = opts.get('config_file', 'wifi_testbed_config')
test_pat = opts.get('test_pat', '[0-9]*')
if utils.host_is_in_lab_zone(machine):
# If we are in the lab use the names for the server, AKA rspro,
# and the router as defined in:
# go/chromeos-lab-hostname-convention
server_addr = wifi_test_utils.get_server_addr_in_lab(machine)
router_addr = wifi_test_utils.get_router_addr_in_lab(machine)
else:
server_addr = opts.get('server_addr', None)
router_addr = opts.get('router_addr', None)
run_options = opts.get('run_options', '').split(',')
config = read_wifi_testbed_config(
os.path.join(job.configdir, config_file),
client_addr = machine, # NB: take client identity from command line
router_addr = router_addr,
server_addr = server_addr)
server = config['server']
router = config['router']
attenuator = config.get('attenuator', dict())
config['run_options'] = run_options
logging.info("Client %s, Server %s, AP %s, Attenuator %s" % \
(machine, server.get('addr', 'N/A'), router['addr'],
attenuator.get('addr', 'N/A')))
test_dir = os.path.join(job.serverdir, "site_tests", test_name)
for t in read_tests(test_dir, test_pat):
job.run_test(test_name, testcase=t, config=config, tag=t['file'])
class test(test.test):
"""
Base class for network_WiFi* classes that are created in the control
directory for each test suite
"""
version = 1
testtype = WiFiTest
def expect_failure(self, name, reason):
if reason is None:
reason = "no reason given"
logging.info("%s: ignore failure (%s)", name, reason)
# The testcase config, setup, etc are done out side the individual
# test loop, in the control file.
def run_once(self, testcase, config):
name = testcase['name']
try:
if 'skip_test' in testcase and 'no_skip' not in config['run_options']:
logging.info("%s: SKIP: %s", name, testcase['skip_test'])
raise error.TestNAError(testcase['skip_test'])
else:
wt = self.testtype(name, testcase['steps'],
testcase.get('requires', []), config)
wt.run()
wt.write_keyvals(self)
except error.TestFail:
if 'expect_failure' in testcase:
self.expect_failure(name, testcase['expect_failure'])
else:
raise
except Exception, e:
if 'expect_failure' in testcase:
self.expect_failure(name, testcase['expect_failure'])
else:
raise