def __init__(self, device): self._device = device self.test_cases = [] self._results = [] self._start_time = None self._end_time = None test_plan_name = device.test_plan test_plan_file = os.path.join("/etc/aft/test_plan/", device.test_plan + ".cfg") test_plan_config = ConfigParser.SafeConfigParser() test_plan_config.read(test_plan_file) if len(test_plan_config.sections()) == 0: raise errors.AFTConfigurationError( "Test plan " + str(test_plan_name) + " (" + str(test_plan_file) + ") doesn't " + "have any test cases. Does the file exist?") for test_case_name in test_plan_config.sections(): test_case_config = dict(test_plan_config.items(test_case_name)) test_case_config["name"] = test_case_name test_case = aft.testcasefactory.build_test_case(test_case_config) self.test_cases.append(test_case) logger.info("Built test plan with " + str(len(self.test_cases)) + " test cases.")
def _enter_test_mode(self): """ Enter test mode by booting from sd card Returns: None Raises: aft.errors.AFTDeviceError if the device failed to enter the test mode """ # device by default boots from sd card, so if everything has gone well, # we can just power cycle to boot the testable image logger.info("Entering test mode") for _ in range(self._TEST_MODE_RETRY_ATTEMPTS): try: self._power_cycle() self.dev_ip = self._wait_for_responsive_ip() if self.dev_ip and self._verify_mode( self.parameters["test_mode"]): return else: logger.warning("Failed to enter test mode") except KeyboardInterrupt: raise except: _err = sys.exc_info() logger.error(str(_err[0]).split("'")[1] + ": " + str(_err[1])) raise errors.AFTDeviceError("Could not set the device in test mode")
def get_root_partition_path(self, image_file_name): """ Select either the default config value to be the root_partition or if the disk layout file exists, use the rootfs from it. Args: image_file_name (str): The name of the image file. Disk layout file name is based on this Returns: (str): path to the disk pseudo file """ layout_file_name = self.get_layout_file_name(image_file_name) if not os.path.isfile(layout_file_name): logger.info("Disk layout file " + layout_file_name + " doesn't exist. Finding root partition.") return self.find_root_partition() layout_file = open(layout_file_name, "r") disk_layout = json.load(layout_file) rootfs_partition = next( partition for partition in list(disk_layout.values()) \ if isinstance(partition, dict) and \ partition["name"] == "rootfs") return os.path.join( "/dev", "disk", "by-partuuid", rootfs_partition["uuid"])
def get_root_partition_path(self, image_file_name): """ Select either the default config value to be the root_partition or if the disk layout file exists, use the rootfs from it. Args: image_file_name (str): The name of the image file. Disk layout file name is based on this Returns: (str): path to the disk pseudo file """ layout_file_name = self.get_layout_file_name(image_file_name) if not os.path.isfile(layout_file_name): logger.info("Disk layout file " + layout_file_name + " doesn't exist. Finding root partition.") return self.find_root_partition() layout_file = open(layout_file_name, "r") disk_layout = json.load(layout_file) rootfs_partition = next( partition for partition in list(disk_layout.values()) \ if isinstance(partition, dict) and \ partition["name"] == "rootfs") return os.path.join("/dev", "disk", "by-partuuid", rootfs_partition["uuid"])
def _find_active_serial_ports_from(self, wait_duration, device_files): """ Find and returns list of active USB serial ports. This spawns a process that actually does the work. Args: device_files (list of strings): List of device files that will be checked for serial ports. Note that any other device file than ttyUSBx will be ignored. Returns: List of device files that have active serial port. Example: ["ttyUSB2", "ttyUSB4", "ttyUSB7"] """ serial_results = Queue() serial_finder = Process( target=TopologyBuilder._get_active_serial_device_files, args=(self, serial_results, wait_duration, device_files)) if self._verbose: print("Serial thread - Finding active serial ports") logger.info("Finding active serial ports") serial_finder.start() return serial_results
def verify_device_mode(ip, mode_name): """ Check that the device with given ip is responsive to ssh and is in the specified mode. The mode is checked by checking that the mode_name arg is present in the /proc/version file Args: ip (str): The device ip address mode_name (str): Word to check for in /proc/version Returns: True if the device is in the desired mode, False otherwise """ try: sshout = ssh.remote_execute(ip, ["cat", "/proc/version"]) if mode_name in sshout: logger.info("Found " + mode_name + " in DUT /proc/version") return True logger.info("Didn't find " + mode_name + " in DUT /proc/version") logger.debug("/cat/proc/version: " + str(sshout)) return False except subprocess32.CalledProcessError as err: logger.warning("Failed verifying the device mode with command: '" + str(err.cmd) + "' failed with error code: '" + str(err.returncode) + "' and output: '" + str(err.output) + "'.") return False
def _create_configuration(self): """ Create and return ConfigParser object containing the device configurations Return: ConfigParser object containing the configurations """ logger.info("Creating configuration object") config = SafeConfigParser() device_ids = {} for device in self._devices: # lack of model generally means that there was an unused power # cutter socket if not "model" in device: continue if not device["model"] in device_ids: device_ids[device["model"]] = 1 dev_id = device_ids[device["model"]] device_ids[device["model"]] = dev_id + 1 section = device["model"].upper() + "_" + str(dev_id) config.add_section(section) for key in device: config.set(section, key, str(device[key])) return config
def _find_active_serial_ports_from(self, wait_duration, device_files): """ Find and returns list of active USB serial ports. This spawns a process that actually does the work. Args: device_files (list of strings): List of device files that will be checked for serial ports. Note that any other device file than ttyUSBx will be ignored. Returns: List of device files that have active serial port. Example: ["ttyUSB2", "ttyUSB4", "ttyUSB7"] """ serial_results = Queue() serial_finder = Process( target=TopologyBuilder._get_active_serial_device_files, args=(self, serial_results, wait_duration, device_files), ) if self._verbose: print("Serial thread - Finding active serial ports") logger.info("Finding active serial ports") serial_finder.start() return serial_results
def _remove_blacklisted_devices(self, devices): """ Remove blacklisted devices from the device list Args: List of devices Returns: Filtered list of devices """ _device_blacklist = self._construct_blacklist() filtered_devices = [] for device in devices: for blacklisted_device in _device_blacklist: if blacklisted_device["id"] == device.dev_id: msg = ("Removed blacklisted device " + blacklisted_device["name"] + " from device pool " + "(Reason: " + blacklisted_device["reason"] + ")") logger.info(msg) print(msg) break else: # else clause for the for loop filtered_devices.append(device) return filtered_devices
def write_image(self, file_name): """ Writes the new image into the Edison Args: file_name (str): The file name of the image that will be flashed on the device Returns: True Raises: aft.errors.AFTDeviceError on various failures aft.errors.AFTConnectionError if the ssh connection could not be formed """ file_name_no_extension = os.path.splitext(file_name)[0] self._mount_local(file_name_no_extension) self._add_usb_networking() self._add_ssh_key() self._unmount_local() # self._flashing_attempts = 0 # dfu-util may occasionally fail. Extra # attempts could be used? logger.info("Executing flashing sequence.") return self._flash_image(file_name_no_extension)
def _enter_test_mode(self): """ Enter test mode by booting from sd card Returns: None Raises: aft.errors.AFTDeviceError if the device failed to enter the test mode """ # device by default boots from sd card, so if everything has gone well, # we can just power cycle to boot the testable image logger.info("Entering test mode") for _ in range(self._TEST_MODE_RETRY_ATTEMPTS): self._power_cycle() self.dev_ip = self._wait_for_responsive_ip() if self.dev_ip and self._verify_mode(self.parameters["test_mode"]): return else: logger.warning("Failed to enter test mode") raise errors.AFTDeviceError("Could not set the device in test mode")
def _stop_vm(self): if not self._is_powered_on: return logger.info("Stopping the vm") misc.local_execute(( "VBoxManage controlvm " + self._vm_name + " poweroff").split())
def _create_configuration(self): """ Create and return ConfigParser object containing the device configurations Return: ConfigParser object containing the configurations """ logger.info("Creating configuration object") config = SafeConfigParser() device_ids = {} for device in self._devices: # lack of model generally means that there was an unused power # cutter socket if not "model" in device: continue if not device["model"] in device_ids: device_ids[device["model"]] = 1 dev_id = device_ids[device["model"]] device_ids[device["model"]] = dev_id + 1 section = device["model"].upper() + "_" + str(dev_id) config.add_section(section) for key in device: config.set(section, key, str(device[key])) return config
def run_remote_command(self, device): """ Executes a command remotely, on the device. """ self.output = device.execute(self.parameters.split(), timeout=120) logger.info("Command: " + str(self.parameters) + "\nresult: " + str(self.output) + ".") return self._check_for_success()
def _set_device_pem_port(self, device, pem_results): """ Check if any PEM port has stopped responding, and if so, associate it with the device. Args: device (dictionary): The device dictionary that is used to store device information. pem_results (multiprocessing.Queue): Queue containing list of active PEM device files. """ logger.info("Configuring device PEM settings") active_pem_ports = pem_results.get() dead_ports = list(set(self._pem_ports).difference(active_pem_ports)) if len(dead_ports) > 1: if self._verbose: print("Too many usb devices disappeared - cannot configure PEM" "port") logger.warning("Too many usb devices disappeared - cannot " + "configure PEM port") logger.warning("Device dictionary: " + str(device)) elif len(dead_ports) == 0: if self._verbose: print("All USB devices still active - device seems to not to " "use PEM") else: if self._verbose: print("PEM port " + dead_ports[0] + " disappeared") device["pem_port"] = "/dev/" + dead_ports[0] # At the time of writing this, PEM only supports serial connection. device["pem_interface"] = "serialconnection" self._pem_ports.remove(dead_ports[0])
def remote_execute(remote_ip, command, timeout = 60, ignore_return_codes = None, user = "******", connect_timeout = 15): """ Execute a Bash command over ssh on a remote device with IP 'remote_ip'. Returns combines stdout and stderr if there are no errors. On error raises subprocess32 errors. """ ssh_args = ["ssh", "-i", "".join([os.path.expanduser("~"), "/.ssh/id_rsa_testing_harness"]), "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", "-o", "BatchMode=yes", "-o", "LogLevel=ERROR", "-o", "ConnectTimeout=" + str(connect_timeout), user + "@" + str(remote_ip), _get_proxy_settings(),] logger.info("Executing " + " ".join(command), filename="ssh.log") ret = "" try: ret = tools.local_execute(ssh_args + command, timeout, ignore_return_codes) except subprocess32.CalledProcessError as err: logger.error("Command raised exception: " + str(err), filename="ssh.log") logger.error("Output: " + str(err.output), filename="ssh.log") raise err return ret
def check_poweron(self): """ Checks if device powers on sucessfully by checking if it enters DFU mode correctly Returns: None Raises: aft.errors.AFTDeviceError on failure to connect to the device after running out of retries aft.errors.AFTConfigurationError if for some reason all retries fail and no other exception is raised """ attempts = 3 exception = None for i in range(attempts): logger.info("Attempt " + str(i + 1) + " of " + str(attempts) + " to power on the device " + self._configuration["name"]) try: self._power_cycle() self._wait_for_device() except errors.AFTDeviceError as error: exception = error pass else: return if exception: raise exception raise errors.AFTConfigurationError("Failed to power on the device")
def open_interface(self): """ Open the host's network interface for testing Returns: None """ interface = self._get_usb_nic() ip_subnet = self._host_ip + "/30" logger.info("Opening the host network interface for testing.") # The ifconfig command requires root privileges to run, and in general # we would like to run AFT without root privileges. However, we can add # a shell script to the sudoers file, which allows us to invoke it with # sudo, without the whole program requiring sudo. Hence, the below commands # will succeed even without root privileges # Note: Assumes that this file is under aft/devices, and that the shell # script is under aft/tools interface_script = os.path.join(os.path.dirname(__file__), os.path.pardir, "tools", "interface_script.sh") subprocess32.check_call(["sudo", interface_script, interface, "up"]) subprocess32.check_call( ["sudo", interface_script, interface, ip_subnet])
def write_image(self, file_name): """ Writes the new image into the Edison Args: file_name (str): The file name of the image that will be flashed on the device Returns: True Raises: aft.errors.AFTDeviceError on various failures aft.errors.AFTConnectionError if the ssh connection could not be formed """ file_name_no_extension = os.path.splitext(file_name)[0] self._mount_local(file_name_no_extension) self._add_usb_networking() self._add_ssh_key() self._unmount_local() # self._flashing_attempts = 0 # dfu-util may occasionally fail. Extra # attempts could be used? logger.info("Executing flashing sequence.") return self._flash_image(file_name_no_extension)
def _mount_local(self, file_name_no_extension): """ Mount a image-file to a class-defined folder. Aborts if the mount command fails. Args: file_name_no_extension (str): The file name of the image that will be flashed on the device Returns: None """ logger.info( "Mounting the root partition for ssh-key and USB-networking " + "service injection.") try: common.make_directory(self._LOCAL_MOUNT_DIR) root_file_system_file = file_name_no_extension + "." + \ self._root_extension subprocess32.check_call( ["mount", root_file_system_file, self._LOCAL_MOUNT_DIR]) except subprocess32.CalledProcessError as err: logger.info("Failed to mount.") common.log_subprocess32_error_and_abort(err)
def verify_device_mode(ip, mode_name): """ Check that the device with given ip is responsive to ssh and is in the specified mode. The mode is checked by checking that the mode_name arg is present in the /proc/version file Args: ip (str): The device ip address mode_name (str): Word to check for in /proc/version Returns: True if the device is in the desired mode, False otherwise """ try: sshout = ssh.remote_execute(ip, ["cat", "/proc/version"]) if mode_name in sshout: logger.info("Found " + mode_name + " in DUT /proc/version") return True logger.info("Didn't find " + mode_name + " in DUT /proc/version") logger.debug("/cat/proc/version: " + str(sshout)) return False except subprocess32.CalledProcessError as err: logger.warning( "Failed verifying the device mode with command: '" + str(err.cmd) + "' failed with error code: '" + str(err.returncode) + "' and output: '" + str(err.output) + "'.") return False
def _set_device_serial_port(self, device, serial_results): """ Checks if any serial port has stopped responding, and if so, associates it with the device. Args: device (dictionary): The device dictionary that is used to store device information. serial_results (multiprocessing.Queue): Queue containing list of active serial device files. """ logger.info("Configuring device serial settings") active_serial_ports = serial_results.get() dead_ports = list( set(self._serial_ports).difference(active_serial_ports)) if len(dead_ports) > 1: if self._verbose: print("Too many usb devices disappeared - cannot configure " "serial port") logger.warning("Too many usb devices disappeared - cannot " + "configure serial port") logger.warning("Device dictionary: " + str(device)) elif len(dead_ports) == 0: if self._verbose: print("All USB devices still active - device seems to not to " "use serial port") else: if self._verbose: print("Serial port " + dead_ports[0] + " disappeared") device["serial_port"] = "/dev/" + dead_ports[0] device["serial_bauds"] = 115200 self._serial_ports.remove(dead_ports[0])
def _mount_local(self, file_name_no_extension): """ Mount a image-file to a class-defined folder. Aborts if the mount command fails. Args: file_name_no_extension (str): The file name of the image that will be flashed on the device Returns: None """ logger.info( "Mounting the root partition for ssh-key and USB-networking " + "service injection.") try: common.make_directory(self._LOCAL_MOUNT_DIR) root_file_system_file = file_name_no_extension + "." + \ self._root_extension # guestmount allows us to mount the image without root privileges subprocess32.check_call( ["guestmount", "-a", root_file_system_file, "-m", "/dev/sda", self._LOCAL_MOUNT_DIR]) except subprocess32.CalledProcessError as err: logger.info("Failed to mount.") common.log_subprocess32_error_and_abort(err)
def _set_device_serial_port(self, device, serial_results): """ Checks if any serial port has stopped responding, and if so, associates it with the device. Args: device (dictionary): The device dictionary that is used to store device information. serial_results (multiprocessing.Queue): Queue containing list of active serial device files. """ logger.info("Configuring device serial settings") active_serial_ports = serial_results.get() dead_ports = list(set(self._serial_ports).difference(active_serial_ports)) if len(dead_ports) > 1: if self._verbose: print("Too many usb devices disappeared - cannot configure " "serial port") logger.warning("Too many usb devices disappeared - cannot " + "configure serial port") logger.warning("Device dictionary: " + str(device)) elif len(dead_ports) == 0: if self._verbose: print("All USB devices still active - device seems to not to " "use serial port") else: if self._verbose: print("Serial port " + dead_ports[0] + " disappeared") device["serial_port"] = "/dev/" + dead_ports[0] device["serial_bauds"] = 115200 self._serial_ports.remove(dead_ports[0])
def check_poweron(self): """ Checks if device powers on sucessfully by checking if it enters DFU mode correctly Returns: None Raises: aft.errors.AFTDeviceError on failure to connect to the device after running out of retries aft.errors.AFTConfigurationError if for some reason all retries fail and no other exception is raised """ attempts = 3 exception = None for i in range(attempts): logger.info("Attempt " + str(i + 1) + " of " + str(attempts) + " to power on the device " + self._configuration["name"]) try: self._power_cycle() self._wait_for_device() except errors.AFTDeviceError as error: exception = error pass else: return if exception: raise exception raise errors.AFTConfigurationError("Failed to power on the device")
def _get_device_configuration(self, cutter): """ Disconnects a cutter, then checks if any ip, serial port or PEM has stopped responding. These will then be associated with each other. Args: cutter (aft.Cutter): The cutter that will be disconnected Returns: Dictionary containing all the associated information (ports, cutters etc). This varies depending on actual device type and physical connections. Edisons for example use USB networking and have different attributes present as a result. Example dictionary (content can and will vary): { "model": "MinnowboardMAX" "id": "12:34:56:78:90:ab", "cutter": "123456", "channel": "4", "pem_interface": "serialconnection", "pem_port": "/dev/ttyUSB9", "serial_port" = "/dev/ttyUSB2", "serial_bauds": "115200" } """ logger.info("Shutting down a cutter") if self._verbose: print("Disconnected cutter") pprint.pprint(cutter.get_cutter_config()) print("") print("Pinging addresses and checking ports for dead ones") cutter.disconnect() # start the threads as soon as possible so that their results are # available as soon as possible wait_duration = 30 pem_results = self._find_active_pem_ports_from(wait_duration, self._pem_ports) serial_results = self._find_active_serial_ports_from(wait_duration, self._serial_ports) device = {} self._set_device_cutter_config(device, cutter) self._set_device_network_and_type(device) self._set_device_serial_port(device, serial_results) self._set_device_pem_port(device, pem_results) if self._verbose: print("Created device configuration:") print("") pprint.pprint(device) print("") return device
def check(args): """ Checks that the specified device is configured correctly Args: args (configuration object): Program command line arguments Returns: Tuple (Bool, String): Test status code and result message string. True indicates tests passed succesfully, false indicates that there were failures """ if not args.device: raise errors.AFTConfigurationError( "You must specify the device that will be checked") if args.verbose: print("Running configuration check on " + args.device) if args.checkall: logger.init_thread(args.device + "_") logger.info("Running configuration check on " + args.device) manager = DevicesManager(args) device = manager.reserve_specific(args.device) if args.verbose: print("Device " + args.device + " acquired, running checks") try: sanity_results = _run_sanity_tests(args, device) image_test_results = (True, "Image Test result: Not run") # only run image test if sanity test passed if sanity_results[0] == True: image_test_results = _run_tests_on_know_good_image(args, device) finally: if args.verbose: print("Releasing device " + args.device) if not args.nopoweroff: device.detach() manager.release(device) results = (sanity_results[0] and image_test_results[0], sanity_results[1] + "\n" + image_test_results[1]) if not results[0]: common.blacklist_device(device.dev_id, args.device, "Failed device health check") msg = "Device " + args.device + " failed health test - blacklisting" logger.info(msg) if args.verbose: print msg return results
def _power_cycle(self): """ Reboot the device. """ logger.info("Rebooting the device.") self.detach() sleep(self._POWER_CYCLE_DELAY) self.attach()
def _power_cycle(self): """ Reboot the device. """ logger.info("Rebooting the device.") self.detach() sleep(self._POWER_CYCLE_DELAY) self.attach()
def stop_image_usb_emulation(self, leases_file): """ Stop using the image with USB mass storage emulation """ self.free_dnsmasq_leases(leases_file) local_execute("stop_libcomposite".split()) local_execute("systemctl start libcomposite.service".split()) logger.info("Stopped USB mass storage emulation with an image")
def send_a_key(self, key, timeout=20): ''' HID keyboard message length is 8 bytes and format is: [modifier, reserved, Key1, Key2, Key3, Key4, Key6, Key7] So first byte is for modifier key and all bytes after third one are for normal keys. After sending a key stroke, empty message with zeroes has to be sent to stop the key being pressed. Messages are sent by writing to the emulated HID usb port in /dev/. US HID keyboard hex codes are used for translating keys. Args: key: A key to send, for example: "a", "z", "3", "F2", "ENTER" timeout: how long sending a key will be tried until quitting [s] ''' def writer(path, message, empty): while True: try: with open(path, "w") as emulator: emulator.write(message.decode()) # Send the key emulator.write(empty) # Stop the key being pressed except IOError: sleep(1) else: return 0 # Empty message which will be sent to stop any keys being pressed empty = "\x00\x00\x00\x00\x00\x00\x00\x00" usb_message = bytearray(empty.encode()) # Initialize usb message hex_key, _modifier = self.key_to_hex(key) # Translate key to hex code # Override self.modifier if the key needs a specific one if _modifier: modifier = _modifier else: modifier = self.modifier usb_message[2] = hex_key usb_message[0] = modifier # Do the writing in a subprocess as it hangs in some rare cases writer = Process(target=writer, args=(self.emulator, usb_message, empty)) writer.start() writer.join(20) if writer.is_alive(): writer.terminate() msg = "Keyboard emulator couldn't connect to host or it froze" logger.error(msg, "kb_emulator.log") raise TimeoutError(msg) logger.info( "Sent key: " + key.ljust(5) + " hex code: " + format(hex_key, '#04x') + " modifier: " + format(modifier, '#04x'), "kb_emulator.log") return 0
def _result_has_zero_fails(self): """ Test if there are FAILED test cases in the QA-test case output """ logger.info(self.output) failed_matches = re.findall("FAILED", self.output) result = True if len(failed_matches) > 0: result = False return result
def _result_has_zero_fails(self): """ Test if there are FAILED test cases in the QA-test case output """ logger.info(self.output) failed_matches = re.findall("FAILED", self.output) result = True if len(failed_matches) > 0: result = False return result
def _save_test_results(self): """ Store the test results. """ logger.info("Storing the test results.") xunit_results = self._results_to_xunit() results_filename = self.get_results_location() with open(results_filename, "w") as results_file: results_file.write(xunit_results) logger.info("Results saved to " + str(results_filename) + ".")
def _save_test_results(self): """ Store the test results. """ logger.info("Storing the test results.") xunit_results = self._results_to_xunit() results_filename = self.get_results_location() with open(results_filename, "w") as results_file: results_file.write(xunit_results) logger.info("Results saved to " + str(results_filename) + ".")
def send_a_key(self, key, timeout=20): ''' HID keyboard message length is 8 bytes and format is: [modifier, reserved, Key1, Key2, Key3, Key4, Key6, Key7] So first byte is for modifier key and all bytes after third one are for normal keys. After sending a key stroke, empty message with zeroes has to be sent to stop the key being pressed. Messages are sent by writing to the emulated HID usb port in /dev/. US HID keyboard hex codes are used for translating keys. Args: key: A key to send, for example: "a", "z", "3", "F2", "ENTER" timeout: how long sending a key will be tried until quitting [s] ''' def writer(path, message, empty): while True: try: with open(path, "w") as emulator: emulator.write(message.decode()) # Send the key emulator.write(empty) # Stop the key being pressed except IOError: sleep(1) else: return 0 # Empty message which will be sent to stop any keys being pressed empty = "\x00\x00\x00\x00\x00\x00\x00\x00" usb_message = bytearray(empty.encode()) # Initialize usb message hex_key, _modifier = self.key_to_hex(key) # Translate key to hex code # Override self.modifier if the key needs a specific one if _modifier: modifier = _modifier else: modifier = self.modifier usb_message[2] = hex_key usb_message[0] = modifier # Do the writing in a subprocess as it hangs in some rare cases writer = Process(target=writer, args=(self.emulator, usb_message, empty)) writer.start() writer.join(20) if writer.is_alive(): writer.terminate() msg = "Keyboard emulator couldn't connect to host or it froze" logger.error(msg, "kb_emulator.log") raise TimeoutError(msg) logger.info("Sent key: " + key.ljust(5) + " hex code: " + format(hex_key, '#04x') + " modifier: " + format(modifier, '#04x'), "kb_emulator.log") return 0
def _enter_mode(self, mode): """ Try to put the device into the specified mode. Args: mode (Dictionary): Dictionary that contains the mode specific information Returns: None Raises: aft.errors.AFTDeviceError if device fails to enter the mode or if keyboard emulator fails to connect """ # Sometimes booting to a mode fails. logger.info("Trying to enter " + mode["name"] + " mode up to " + str(self._RETRY_ATTEMPTS) + " times.") for _ in range(self._RETRY_ATTEMPTS): try: self._power_cycle() if self.kb_emulator: logger.info("Using " + type(self.kb_emulator).__name__ + " to send keyboard sequence " + mode["sequence"]) self.kb_emulator.send_keystrokes(mode["sequence"]) else: logger.warning( "No keyboard emulator defined for the device") ip_address = self._wait_for_responsive_ip() if ip_address: if self._verify_mode(mode["name"]): return else: logger.warning("Failed entering " + mode["name"] + " mode.") except KeyboardInterrupt: raise except: _err = sys.exc_info() logger.error(str(_err[0]).split("'")[1] + ": " + str(_err[1])) logger.critical("Unable to get the device in mode " + mode["name"]) raise errors.AFTDeviceError("Could not set the device in mode " + mode["name"])
def _unmount_over_ssh(self): """ Unmount the mounted directory at self.mount_dir Returns: None """ logger.info("Unmounting " + self.mount_dir) try: ssh.remote_execute(self.dev_ip, ["umount", self.mount_dir]) except subprocess32.CalledProcessError as err: common.log_subprocess32_error_and_abort(err)
def _unmount_over_ssh(self): """ Unmount the mounted directory at self.mount_dir Returns: None """ logger.info("Unmounting " + self.mount_dir) try: ssh.remote_execute(self.dev_ip, ["umount", self.mount_dir]) except subprocess32.CalledProcessError as err: common.log_subprocess32_error_and_abort(err)
def free_dnsmasq_leases(self, leases_file): """ dnsmasq.leases file needs to be cleared and dnsmasq.service restarted or there will be no IP address to give for DUT if same image is used in quick succession with and without --emulateusb """ local_execute("systemctl stop dnsmasq.service".split()) with open(leases_file, "w") as f: f.write("") f.flush() local_execute("systemctl start dnsmasq.service".split()) logger.info("Freed dnsmasq leases")
def _do_import_vm(self, ova_appliance): """ Import the .ova appliance and grab the virtual hard drive name and VM name Args: ova_appliance (str): The ova appliance file Returns: None Raises: aft.errors.AFTDeviceError: If hard drive name or vm name were not set """ output = misc.local_execute( ("VBoxManage import " + ova_appliance + "").split()) # Get virtual hard drive name and VM name from output output = output.split("\n") for line in output: # Get hard drive name if "Hard disk image" in line: hdd_path_portion = line.split()[7].split("=") if hdd_path_portion[0] != "path": break self._vhdd = hdd_path_portion[1] if self._vhdd.endswith(","): self._vhdd = self._vhdd[:-1] # get VM name if "Suggested VM name" in line: self._vm_name = line.split()[4] # Strip starting ", if present if self._vm_name.startswith('"'): self._vm_name = self._vm_name[1:] # Strip ending ", if present if self._vm_name.endswith('"'): self._vm_name = self._vm_name[:-1] if self._vhdd and self._vm_name: logger.info("VM name: " + self._vm_name) logger.info("VHDD name: " + self._vhdd) return raise errors.AFTDeviceError( "Failed to find the VM name or virtual hard drive path. Has the " + "VirtualBox output format changed?")
def _import_vm(self, ova_appliance): """ Set default VirtualBox directory and import the .ova appliance into VirtualBox as a VM """ logger.info("Importing VM appliance") self._set_default_directory( os.path.join( os.getcwd(), self._VM_DIRECTORY)) self._do_import_vm(ova_appliance)
def _mount_single_layer(self, image_file_name): """ Mount a hdddirect partition Returns: None """ logger.info("Mount one layer.") ssh.remote_execute(self.dev_ip, ["mount", self.get_root_partition_path(image_file_name), self._ROOT_PARTITION_MOUNT_POINT])
def start_image_usb_emulation(self, args, leases_file): """ Start using the image with USB mass storage emulation """ if self.check_libcomposite_service_running(): local_execute("systemctl stop libcomposite.service".split()) self.free_dnsmasq_leases(leases_file) image_file = os.path.abspath(args.file_name) if not os.path.isfile(image_file): print("Image file doesn't exist") raise errors.AFTImageNameError("Image file doesn't exist") local_execute(("start_libcomposite " + image_file).split()) logger.info("Started USB mass storage emulation using " + image_file)
def execute(self, device): """ Prepare and executes the test case, storing the results. """ start_time = datetime.datetime.now() logger.info("Test case start time: " + str(start_time)) self._prepare() # Test cases are run using the Visitor pattern to allow last-minute # preparation of the device for the test. self.result = device.test(self) self.duration = datetime.datetime.now() - start_time logger.info("Test Duration: " + str(self.duration)) self._build_xunit_section()
def _result_has_zero_fails(self): """ Test if there are FAILED test cases in the QA-test case output """ logger.info(self.output) # qa_log_file = open("results-runtest.py.log", "r") # qa_log = qa_log_file.read() # qa_log_file.close() failed_matches = re.findall("FAILED", self.output) result = True if len(failed_matches) > 0: result = False return result
def _result_has_zero_fails(self): """ Test if there are FAILED test cases in the QA-test case output """ logger.info(self.output) # qa_log_file = open("results-runtest.py.log", "r") # qa_log = qa_log_file.read() # qa_log_file.close() failed_matches = re.findall("FAILED", self.output) result = True if len(failed_matches) > 0: result = False return result
def execute(self, device): """ Prepare and executes the test case, storing the results. """ start_time = datetime.datetime.now() logger.info("Test case start time: " + str(start_time)) self._prepare() # Test cases are run using the Visitor pattern to allow last-minute # preparation of the device for the test. self.result = device.test(self) self.duration = datetime.datetime.now() - start_time logger.info("Test Duration: " + str(self.duration)) self._build_xunit_section()
def _mount_single_layer(self, image_file_name): """ Mount a hdddirect partition Returns: None """ logger.info("Mount one layer.") ssh.remote_execute(self.dev_ip, [ "mount", self.get_root_partition_path(image_file_name), self._ROOT_PARTITION_MOUNT_POINT ])
def _send_PEM_keystrokes(self, keystrokes, attempts=1, timeout=60): """ Try to send keystrokes within the time limit Args: keystrokes (str): PEM keystroke file attempts (integer): How many attempts will be made timeout (integer): Timeout for a single attempt Returns: None Raises: aft.errors.AFTDeviceError if PEM connection times out """ def call_pem(exceptions): try: pem_main( [ "pem", "--interface", self.pem_interface, "--port", self.pem_port, "--playback", keystrokes ]) except Exception as err: exceptions.put(err) for i in range(attempts): logger.info( "Attempt " + str(i + 1) + " of " + str(attempts) + " to send " + "keystrokes through PEM") exception_queue = Queue() process = Process(target=call_pem, args=(exception_queue,)) # ensure python process is closed in case main process dies but # the subprocess is still waiting for timeout process.daemon = True process.start() process.join(timeout) if not exception_queue.empty(): raise exception_queue.get() if process.is_alive(): process.terminate() else: return raise errors.AFTDeviceError("Failed to connect to PEM")
def _do_inject_ssh_key(self): """ Inject ssh key into the mounted virtual hard drive """ logger.info("Injecting ssh key") source_file = os.path.join(self._MODULE_DATA_PATH, self._HARNESS_AUTHORIZED_KEYS_FILE) ssh_path = os.path.join( os.curdir, self._MOUNT_DIRECTORY, "home", "root", ".ssh") ssh_file = os.path.join(ssh_path, "authorized_keys") logger.info("Injecting ssh key from '" + source_file + "' to '" + ssh_file + "'") common.make_directory(ssh_path) shutil.copy(source_file, ssh_file) sha1sum = misc.local_execute(("sha1sum " + ssh_file).split()) sha1sum = sha1sum.split()[0] # discard the file path logger.info("Adding IMA attribute to the ssh-key") # The setfattr command requires root privileges to run, and in general # we would like to run AFT without root privileges. However, we can add # a shell script to the sudoers file, which allows us to invoke it with # sudo, without the whole program requiring sudo. Hence, the below commands # will succeed even without root privileges. # The script also validates that the ssh_file path is at least somewhat # sane. This should be taken account if file or directory names are # changed # Note: Assumes that this file is under aft/devices, and that the shell # script is under aft/tools setfattr_script = os.path.join(os.path.dirname(__file__), os.path.pardir, "tools", "setfattr_script.sh") misc.local_execute( [ "sudo", setfattr_script, "0x01" + sha1sum + " ", ssh_file ]) logger.info("Fixing ownership and permissions") # ensure .ssh directory and authorized key file is owned by root os.chown(ssh_path, 0, 0) os.chown(ssh_file, 0, 0) # and ensure the permissions are correct # Note: incompatibility with Python 3 in chmod octal numbers os.chmod(ssh_path, 0700) os.chmod(ssh_file, 0600)
def recovery_flash(self): """ Execute the flashing of device-side DFU-tools Aborts if the flashing fails Note that only one Edison should be powered on when doing the recovery flashing Returns: None """ logger.info("Recovery flashing.") try: # This can cause race condition if multiple devices are booted at # the same time! attempts = 0 xfstk_parameters = ["xfstk-dldr-solo", "--gpflags", "0x80000007", "--osimage", os.path.join( self._MODULE_DATA_PATH, "u-boot-edison.img"), "--fwdnx", os.path.join( self._MODULE_DATA_PATH, "edison_dnx_fwr.bin"), "--fwimage", os.path.join( self._MODULE_DATA_PATH, "edison_ifwi-dbg-00.bin"), "--osdnx", os.path.join( self._MODULE_DATA_PATH, "edison_dnx_osr.bin")] self._power_cycle() while subprocess32.call(xfstk_parameters) and attempts < 10: logger.info( "Rebooting and trying recovery flashing again. " + str(attempts)) self._power_cycle() time.sleep(random.randint(10, 30)) attempts += 1 except subprocess32.CalledProcessError as err: common.log_subprocess32_error_and_abort(err) except OSError as err: logger.critical("Failed recovery flashing, errno = " + str(err.errno) + ". Is the xFSTK tool installed?") sys.exit(1)
def _mount_two_layers(self): """ Mount a hddimg which has 'rootfs' partition Returns: None """ logger.info("Mounts two layers.") ssh.remote_execute(self.dev_ip, ["modprobe", "vfat"]) # mount the first layer of .hddimg ssh.remote_execute(self.dev_ip, ["mount", self._target_device, self._SUPER_ROOT_MOUNT_POINT]) ssh.remote_execute(self.dev_ip, ["mount", self._SUPER_ROOT_MOUNT_POINT + "rootfs.img", self._ROOT_PARTITION_MOUNT_POINT])
def _mount_virtual_drive(self): """ Mount the VirtualBox virtual hard drive """ # create mount folder common.make_directory(self._MOUNT_DIRECTORY) path = os.path.join( self._VM_DIRECTORY, self._vm_name, self._vhdd); logger.info("Mounting '" + path + "' with device '" + self._ROOTFS_DEVICE + "' into '" + self._MOUNT_DIRECTORY + "' for ssh key injection") misc.local_execute( ("guestmount -a " + path +" -m " + self._ROOTFS_DEVICE + " " + self._MOUNT_DIRECTORY + " -o allow_other").split())