Home | History | Annotate | Download | only in network
      1 # Copyright 2016 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import logging
      6 import os
      7 import time
      8 import re
      9 import shutil
     10 
     11 import common
     12 from autotest_lib.client.common_lib import error
     13 from autotest_lib.client.common_lib.cros.network import ap_constants
     14 from autotest_lib.client.common_lib.cros.network import iw_runner
     15 from autotest_lib.server import hosts
     16 from autotest_lib.server import frontend
     17 from autotest_lib.server import site_utils
     18 from autotest_lib.server.cros.ap_configurators import ap_configurator
     19 from autotest_lib.server.cros.ap_configurators import ap_cartridge
     20 from autotest_lib.server.cros.ap_configurators import ap_spec as ap_spec_module
     21 
     22 
     23 def allocate_packet_capturer(lock_manager, hostname, prefix):
     24     """Allocates a machine to capture packets.
     25 
     26     Locks the allocated machine if the machine was discovered via AFE
     27     to prevent tests stomping on each other.
     28 
     29     @param lock_manager HostLockManager object.
     30     @param hostname string optional hostname of a packet capture machine.
     31     @param prefix string chamber location (ex. chromeos3, chromeos5, chromeos7)
     32 
     33     @return: An SSHHost object representing a locked packet_capture machine.
     34     """
     35     if hostname is not None:
     36         return hosts.SSHHost(hostname)
     37 
     38     afe = frontend.AFE(debug=True,
     39                        server=site_utils.get_global_afe_hostname())
     40     available_pcaps = afe.get_hosts(label='packet_capture')
     41     for pcap in available_pcaps:
     42         pcap_prefix = pcap.hostname.split('-')[0]
     43         # Ensure the pcap and dut are in the same subnet
     44         if pcap_prefix == prefix:
     45             if lock_manager.lock([pcap.hostname]):
     46                 return hosts.SSHHost(pcap.hostname + '.cros')
     47             else:
     48                 logging.info('Unable to lock %s', pcap.hostname)
     49                 continue
     50     raise error.TestError('Unable to lock any pcaps - check in cautotest if '
     51                           'pcaps in %s are locked.', prefix)
     52 
     53 def allocate_webdriver_instance(lock_manager):
     54     """Allocates a machine to capture webdriver instance.
     55 
     56     Locks the allocated machine if the machine was discovered via AFE
     57     to prevent tests stomping on each other.
     58 
     59     @param lock_manager HostLockManager object.
     60 
     61     @return An SSHHost object representing a locked webdriver instance.
     62     """
     63     afe = frontend.AFE(debug=True,
     64                        server=site_utils.get_global_afe_hostname())
     65     hostname = '%s.cros' % site_utils.lock_host_with_labels(
     66         afe, lock_manager, labels=['webdriver'])
     67     webdriver_host = hosts.SSHHost(hostname)
     68     if webdriver_host is not None:
     69         return webdriver_host
     70     logging.error("Unable to allocate VM instance")
     71     return None
     72 
     73 
     74 def is_VM_running(master, instance):
     75     """Check if locked VM is running.
     76 
     77     @param master: chaosvmmaster SSHHost
     78     @param instance: locked webdriver instance
     79 
     80     @return True if locked VM is running; False otherwise
     81     """
     82     hostname = instance.hostname.split('.')[0]
     83     logging.debug('Check %s VM status', hostname)
     84     list_running_vms_cmd = 'VBoxManage list runningvms'
     85     running_vms = master.run(list_running_vms_cmd).stdout
     86     return hostname in running_vms
     87 
     88 
     89 def power_on_VM(master, instance):
     90     """Power on VM
     91 
     92     @param master: chaosvmmaster SSHHost
     93     @param instance: locked webdriver instance
     94 
     95     """
     96     hostname = instance.hostname.split('.')[0]
     97     logging.debug('Powering on %s VM without GUI', hostname)
     98     power_on_cmd = 'VBoxManage startvm %s --type headless' % hostname
     99     master.run(power_on_cmd)
    100 
    101 
    102 def power_off_VM(master, instance):
    103     """Power off VM
    104 
    105     @param master: chaosvmmaster SSHHost
    106     @param instance: locked webdriver instance
    107 
    108     """
    109     hostname = instance.hostname.split('.')[0]
    110     logging.debug('Powering off %s VM', hostname)
    111     power_off_cmd = 'VBoxManage controlvm %s poweroff' % hostname
    112     master.run(power_off_cmd)
    113 
    114 
    115 def power_down_aps(aps, broken_pdus=[]):
    116      """Powers down a list of aps.
    117 
    118      @param aps: a list of APConfigurator objects.
    119      @param broken_pdus: a list of broken PDUs identified.
    120      """
    121      cartridge = ap_cartridge.APCartridge()
    122      for ap in aps:
    123          ap.power_down_router()
    124          cartridge.push_configurator(ap)
    125      cartridge.run_configurators(broken_pdus)
    126 
    127 
    128 def configure_aps(aps, ap_spec, broken_pdus=[]):
    129     """Configures a given list of APs.
    130 
    131     @param aps: a list of APConfigurator objects.
    132     @param ap_spec: APSpec object corresponding to the AP configuration.
    133     @param broken_pdus: a list of broken PDUs identified.
    134     """
    135     cartridge = ap_cartridge.APCartridge()
    136     for ap in aps:
    137         ap.set_using_ap_spec(ap_spec)
    138         cartridge.push_configurator(ap)
    139     cartridge.run_configurators(broken_pdus)
    140 
    141 
    142 def is_dut_healthy(client, ap):
    143     """Returns if iw scan is working properly.
    144 
    145     Sometimes iw scan will die, especially on the Atheros chips.
    146     This works around that bug.  See crbug.com/358716.
    147 
    148     @param client: a wifi_client for the DUT
    149     @param ap: ap_configurator object
    150 
    151     @returns True if the DUT is healthy (iw scan works); False otherwise.
    152     """
    153     # The SSID doesn't matter, all that needs to be verified is that iw
    154     # works.
    155     networks = client.iw_runner.wait_for_scan_result(
    156             client.wifi_if, ssids=[ap.ssid])
    157     if networks == None:
    158         return False
    159     return True
    160 
    161 
    162 def is_conn_worker_healthy(conn_worker, ap, assoc_params, job):
    163     """Returns if the connection worker is working properly.
    164 
    165     From time to time the connection worker will fail to establish a
    166     connection to the APs.
    167 
    168     @param conn_worker: conn_worker object
    169     @param ap: an ap_configurator object
    170     @param assoc_params: the connection association parameters
    171     @param job: the Autotest job object
    172 
    173     @returns True if the worker is healthy; False otherwise
    174     """
    175     if conn_worker is None:
    176         return True
    177     conn_status = conn_worker.connect_work_client(assoc_params)
    178     if not conn_status:
    179         job.run_test('network_WiFi_ChaosConfigFailure', ap=ap,
    180                      error_string=ap_constants.WORK_CLI_CONNECT_FAIL,
    181                      tag=ap.ssid)
    182         # Obtain the logs from the worker
    183         log_dir_name = str('worker_client_logs_%s' % ap.ssid)
    184         log_dir = os.path.join(job.resultdir, log_dir_name)
    185         conn_worker.host.collect_logs(
    186                 '/var/log', log_dir, ignore_errors=True)
    187         return False
    188     return True
    189 
    190 
    191 def release_ap(ap, batch_locker, broken_pdus=[]):
    192     """Powers down and unlocks the given AP.
    193 
    194     @param ap: the APConfigurator under test.
    195     @param batch_locker: the batch locker object.
    196     @param broken_pdus: a list of broken PDUs identified.
    197     """
    198     ap.power_down_router()
    199     try:
    200         ap.apply_settings()
    201     except ap_configurator.PduNotResponding as e:
    202         if ap.pdu not in broken_pdus:
    203             broken_pdus.append(ap.pdu)
    204     batch_locker.unlock_one_ap(ap.host_name)
    205 
    206 
    207 def filter_quarantined_and_config_failed_aps(aps, batch_locker, job,
    208                                              broken_pdus=[]):
    209     """Filter out all PDU quarantined and config failed APs.
    210 
    211     @param aps: the list of ap_configurator objects to filter
    212     @param batch_locker: the batch_locker object
    213     @param job: an Autotest job object
    214     @param broken_pdus: a list of broken PDUs identified.
    215 
    216     @returns a list of ap_configuration objects.
    217     """
    218     aps_to_remove = list()
    219     for ap in aps:
    220         failed_ap = False
    221         if ap.pdu in broken_pdus:
    222             ap.configuration_success = ap_constants.PDU_FAIL
    223         if (ap.configuration_success == ap_constants.PDU_FAIL):
    224             failed_ap = True
    225             error_string = ap_constants.AP_PDU_DOWN
    226             tag = ap.host_name + '_PDU'
    227         elif (ap.configuration_success == ap_constants.CONFIG_FAIL):
    228             failed_ap = True
    229             error_string = ap_constants.AP_CONFIG_FAIL
    230             tag = ap.host_name
    231         if failed_ap:
    232             tag += '_' + str(int(round(time.time())))
    233             job.run_test('network_WiFi_ChaosConfigFailure',
    234                          ap=ap,
    235                          error_string=error_string,
    236                          tag=tag)
    237             aps_to_remove.append(ap)
    238             if error_string == ap_constants.AP_CONFIG_FAIL:
    239                 release_ap(ap, batch_locker, broken_pdus)
    240             else:
    241                 # Cannot use _release_ap, since power_down will fail
    242                 batch_locker.unlock_one_ap(ap.host_name)
    243     return list(set(aps) - set(aps_to_remove))
    244 
    245 
    246 def get_security_from_scan(ap, networks, job):
    247     """Returns a list of securities determined from the scan result.
    248 
    249     @param ap: the APConfigurator being testing against.
    250     @param networks: List of matching networks returned from scan.
    251     @param job: an Autotest job object
    252 
    253     @returns a list of possible securities for the given network.
    254     """
    255     securities = list()
    256     # Sanitize MIXED security setting for both Static and Dynamic
    257     # configurators before doing the comparison.
    258     security = networks[0].security
    259     if (security == iw_runner.SECURITY_MIXED and
    260         ap.configurator_type == ap_spec_module.CONFIGURATOR_STATIC):
    261         securities = [iw_runner.SECURITY_WPA, iw_runner.SECURITY_WPA2]
    262         # We have only seen WPA2 be backwards compatible, and we want
    263         # to verify the configurator did the right thing. So we
    264         # promote this to WPA2 only.
    265     elif (security == iw_runner.SECURITY_MIXED and
    266           ap.configurator_type == ap_spec_module.CONFIGURATOR_DYNAMIC):
    267         securities = [iw_runner.SECURITY_WPA2]
    268     else:
    269         securities = [security]
    270     return securities
    271 
    272 
    273 def scan_for_networks(ssid, capturer, ap_spec):
    274     """Returns a list of matching networks after running iw scan.
    275 
    276     @param ssid: the SSID string to look for in scan.
    277     @param capturer: a packet capture device.
    278     @param ap_spec: APSpec object corresponding to the AP configuration.
    279 
    280     @returns a list of the matching networks; if no networks are found at
    281              all, returns None.
    282     """
    283     # Setup a managed interface to perform scanning on the
    284     # packet capture device.
    285     freq = ap_spec_module.FREQUENCY_TABLE[ap_spec.channel]
    286     wifi_if = capturer.get_wlanif(freq, 'managed')
    287     capturer.host.run('%s link set %s up' % (capturer.cmd_ip, wifi_if))
    288     # We have some APs that need a while to come on-line
    289     networks = capturer.iw_runner.wait_for_scan_result(
    290             wifi_if, ssids=[ssid], timeout_seconds=300)
    291     capturer.remove_interface(wifi_if)
    292     return networks
    293 
    294 
    295 def return_available_networks(ap, capturer, job, ap_spec):
    296     """Returns a list of networks configured as described by an APSpec.
    297 
    298     @param ap: the APConfigurator being testing against.
    299     @param capturer: a packet capture device
    300     @param job: an Autotest job object.
    301     @param ap_spec: APSpec object corresponding to the AP configuration.
    302 
    303     @returns a list of networks returned from _scan_for_networks().
    304     """
    305     for i in range(2):
    306         networks = scan_for_networks(ap.ssid, capturer, ap_spec)
    307         if networks is None:
    308             return None
    309         if len(networks) == 0:
    310             # The SSID wasn't even found, abort
    311             logging.error('The ssid %s was not found in the scan', ap.ssid)
    312             job.run_test('network_WiFi_ChaosConfigFailure', ap=ap,
    313                          error_string=ap_constants.AP_SSID_NOTFOUND,
    314                          tag=ap.ssid)
    315             return list()
    316         security = get_security_from_scan(ap, networks, job)
    317         if ap_spec.security in security:
    318             return networks
    319         if i == 0:
    320             # The SSID exists but the security is wrong, give the AP time
    321             # to possible update it.
    322             time.sleep(60)
    323     if ap_spec.security not in security:
    324         logging.error('%s was the expected security but got %s: %s',
    325                       ap_spec.security,
    326                       str(security).strip('[]'),
    327                       networks)
    328         job.run_test('network_WiFi_ChaosConfigFailure',
    329                      ap=ap,
    330                      error_string=ap_constants.AP_SECURITY_MISMATCH,
    331                      tag=ap.ssid)
    332         networks = list()
    333     return networks
    334 
    335 
    336 def sanitize_client(host):
    337     """Clean up logs and reboot the DUT.
    338 
    339     @param host: the cros host object to use for RPC calls.
    340     """
    341     host.run('rm -rf /var/log')
    342     host.reboot()
    343 
    344 
    345 def get_firmware_ver(host):
    346     """Get firmware version of DUT from /var/log/messages.
    347 
    348     WiFi firmware version is matched against list of known firmware versions
    349     from ToT.
    350 
    351     @param host: the cros host object to use for RPC calls.
    352 
    353     @returns the WiFi firmware version as a string, None if the version
    354              cannot be found.
    355     """
    356     # TODO(rpius): Need to find someway to get this info for Android/Brillo.
    357     if host.get_os_type() != 'cros':
    358         return None
    359 
    360     # Firmware versions manually aggregated by installing ToT on each device
    361     known_firmware_ver = ['Atheros', 'mwifiex', 'loaded firmware version',
    362                           'brcmf_c_preinit_dcmds']
    363     # Find and return firmware version in logs
    364     for firmware_ver in known_firmware_ver:
    365         result_str = host.run(
    366             'awk "/%s/ {print}" /var/log/messages' % firmware_ver).stdout
    367         if not result_str:
    368             continue
    369         else:
    370             if 'Atheros' in result_str:
    371                 pattern = '%s \w+ Rev:\d' % firmware_ver
    372             elif 'mwifiex' in result_str:
    373                 pattern = '%s [\d.]+ \([\w.]+\)' % firmware_ver
    374             elif 'loaded firmware version' in result_str:
    375                 pattern = '(\d+\.\d+\.\d+)'
    376             elif 'Firmware version' in result_str:
    377                 pattern = '\d+\.\d+\.\d+ \([\w.]+\)'
    378             else:
    379                 logging.info('%s does not match known firmware versions.',
    380                              result_str)
    381                 return None
    382             result = re.search(pattern, result_str)
    383             if result:
    384                 return result.group(0)
    385     return None
    386 
    387 
    388 def collect_pcap_info(tracedir, pcap_filename, try_count):
    389         """Gather .trc and .trc.log files into android debug directory.
    390 
    391         @param tracedir: string name of the directory that has the trace files.
    392         @param pcap_filename: string name of the pcap file.
    393         @param try_count: int Connection attempt number.
    394 
    395         """
    396         pcap_file = os.path.join(tracedir, pcap_filename)
    397         pcap_log_file = os.path.join(tracedir, '%s.log' % pcap_filename)
    398         debug_dir = 'android_debug_try_%d' % try_count
    399         debug_dir_path = os.path.join(tracedir, 'debug/%s' % debug_dir)
    400         if os.path.exists(debug_dir_path):
    401             pcap_dir_path = os.path.join(debug_dir_path, 'pcap')
    402             if not os.path.exists(pcap_dir_path):
    403                 os.makedirs(pcap_dir_path)
    404                 shutil.copy(pcap_file, pcap_dir_path)
    405                 shutil.copy(pcap_log_file, pcap_dir_path)
    406         logging.debug('Copied failed packet capture data to directory')
    407