| # 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 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_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 test |
| from autotest_lib.server.cros import remote_command |
| from autotest_lib.server.cros import wifi_test_utils |
| from autotest_lib.server.cros.network 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): |
| """ |
| Deprecated 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_dtimperiod checks the DTIM period |
| 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 |
| 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. We support |
| a setup that uses a stumpy with an extra radio and a special software |
| image set up to facillitate routing. The driver stack on this machine |
| is 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 |
| |
| |
| 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 = hosts.SSHHost(router['addr'], |
| port=int(router.get('port', 22))) |
| |
| defaults = config.get('defaults', {}) |
| self.deftimeout = defaults.get('timeout', 30) |
| self.defpingcount = defaults.get('pingcount', 10) |
| |
| if not site_linux_router.isLinuxRouter(self.router): |
| raise error.TestFail('Unsupported router') |
| |
| if site_linux_cros_router.isLinuxCrosRouter(self.router): |
| self.wifi = site_linux_cros_router.LinuxCrosRouter( |
| self.router, router, self.name) |
| else: |
| self.wifi = site_linux_bridge_router.LinuxBridgeRouter( |
| self.router, router, self.name) |
| |
| 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_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") |
| |
| 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 = {} |
| |
| # Synchronize time on all devices |
| for system in (self.client_proxy, self.hosting_server, self.wifi): |
| system.sync_host_times() |
| |
| # 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() |
| |
| |
| @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.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_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(site_linux_system.LinuxSystem.CAPABILITY_5GHZ) |
| router_reqs.add(site_linux_system.LinuxSystem.CAPABILITY_5GHZ) |
| if 'multi_interface' in params: |
| router_reqs.add( |
| site_linux_system.LinuxSystem.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_proxy.capabilities: |
| raise error.TestNAError( |
| "%s: client is missing required capability: %s" % |
| (self.name, requirement)) |
| |
| self.wifi.require_capabilities(self.router_requirements) |
| |
| 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_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 |
| logging.info("%s: client WiFi-IP is %s", |
| self.name, self.client_proxy.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 """ |
| 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. |
| |
| @param params ignored, but required by this framework. |
| |
| """ |
| self.client_proxy.powersave_switch(True) |
| |
| |
| def client_powersave_off(self, params): |
| """Disable power save operation. |
| |
| @param params ignored, but required by this framework. |
| |
| """ |
| self.client_proxy.powersave_switch(False) |
| |
| |
| 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, iw_link_key, desired_value): |
| """Assert that the current wireless link property is |desired_value|. |
| |
| @param iw_link_key string one of IW_LINK_KEY_* defined in WiFiClient. |
| @param desired_value string desired value of iw link property. |
| |
| """ |
| self.client_proxy.check_iw_link_value(iw_link_key, desired_value) |
| |
| |
| 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, stats): |
| 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. |
| |
| """ |
| params = params.copy() |
| if 'ping_ip' in params: |
| ping_ip = params.pop('ping_ip') |
| else: |
| if 'dest' in params: |
| ping_dest = params.pop('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.pop('count', self.defpingcount)) |
| if params: |
| raise error.TestFail('Unhandled ping parameters %r.' % params) |
| |
| ping_config = ping_runner.PingConfig(ping_ip, count=count, |
| ignore_result=True) |
| stats = self.client_proxy.ping(ping_config).old_style_output |
| stats = self.__get_pingstats(stdout) |
| self.write_perf(stats) |
| self.__print_pingstats("client_ping ", stats) |
| |
| |
| 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 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. |
| |
| @param params dict of site_wifitest params. |
| |
| """ |
| config = xmlrpc_datatypes.BgscanConfiguration() |
| if params.get('short_interval', None): |
| config.short_interval = params['short_interval'] |
| if params.get('long_interval', None): |
| config.long_interval = 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') |
| |
| config.set_auto_signal( |
| self.client_signal_info['signal avg'], |
| signal_offset=params.get('offset', None), |
| signal_noise=self.client_signal_info.get('noise', None)) |
| logging.info('Auto signal: %s' % repr(signal)) |
| config.signal = signal |
| if params.get('method', None): |
| config.method = params['method'] |
| self.client_proxy.configure_bgscan(config) |
| |
| |
| def bgscan_disable(self, params): |
| """Disable wpa_supplicant bgscan. |
| |
| @param params dict (ignored). |
| |
| """ |
| self.client_proxy.disable_bgscan() |
| |
| |
| def bgscan_enable(self, params): |
| """Enable wpa_supplicant bgscan. |
| |
| @param params dict (ignored). |
| |
| """ |
| self.client_proxy.enable_bgscan() |
| |
| |
| def scan(self, params): |
| """Ask the DUT to scan for SSIDs on frequencies. |
| |
| |params| may contain 'freq' and 'ssid' keys, which should |
| map to lists of frequencies and ssids respectively. Asserts |
| that we find all 'ssid' members listed. |
| |
| @param params dict of settings for scan. |
| |
| """ |
| frequencies = map(int, params.get('freq', [])) |
| ssids = params.get('ssid', []) |
| self.client_proxy.scan(frequencies, ssids) |
| |
| |
| 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_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_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 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]]) |
| |
| 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) |
| |
| config = {'client': {'addr': machine}, |
| 'router': {'addr': router_addr}, |
| 'server': {'addr': server_addr}, |
| 'run_options': opts.get('run_options', '').split(',')} |
| |
| logging.info('Client %r, Server %r, AP %r' % |
| (machine, server_addr, router_addr)) |
| |
| if not all([server_addr, router_addr]): |
| raise error.TestFail('User must specify server_addr and router_addr') |
| |
| test_pat = opts.get('test_pat', '[0-9]*') |
| 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 |