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 _send_command(self, power_status): """ Either turns power on or off. Retries on failure up to self._RETRIES times. Args: power_status (string): Either "0", or "1" to turn power off and on respectively Returns: None Raises: subprocess32.CalledProcessError or subprocess32.TimeoutExpired on failure """ error = "" for _ in range(self._RETRIES): try: misc.local_execute([ "clewarecontrol", "-d", str(self._cutter_id), "-c", "1", "-as", str(self._channel), str(power_status) ]) except (subprocess32.CalledProcessError, subprocess32.TimeoutExpired) as err: error = err sleep( random.randint(self._MIN_SLEEP_DURATION, self._MAX_SLEEP_DURATION)) else: return raise error
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 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 _clear_dmesg(self): """ Clears kernel message ring buffer Used before reading edison related information from the buffer to make sure any old Edison related messages do not disrupt configuration. """ local_execute(["dmesg", "-C"])
def _prepare_support_fs(self, root_tarball): """ Create directories and copy all the necessary files to the support fs working directory. This is done before booting the support image, as the rootfs might be read only when accessed through nfs Args: root_tarball: The name of the root fs tarball Returns: None """ # Note: need to remove the first '/' from file paths when building # absolute paths on the testing harness, as these paths are # absolute path intended for the support image operations (e.g. /usr/foo # rather than usr/foo, when we want to build # /path/to/nfs/path/to/rootfs/usr/foo) # # The need to remove the '/' is due to os.path.join behaviour, where it # will discard the current path string when encourtering an absolute # path logger.info("Creating directories and copying image files") local_execute([ "mount", "-o", "loop,offset=1048576", "/root/support_image/support.img", "/root/support_fs/beaglebone/" ]) common.make_directory( os.path.join(self.nfs_path, self.working_directory[1:])) common.make_directory(os.path.join(self.nfs_path, self.mount_dir[1:])) shutil.copy(os.path.join(self.parameters["mlo_file"]), os.path.join(self.nfs_path, self.mlo_file[1:])) shutil.copy(os.path.join(self.parameters["u-boot_file"]), os.path.join(self.nfs_path, self.u_boot_file[1:])) if "tar.bz2" in root_tarball: logger.info("Using command line arg for root tarball") shutil.copy(os.path.join(root_tarball), os.path.join(self.nfs_path, self.root_tarball[1:])) else: logger.info("No tarball name passed - using default value") shutil.copy(self.parameters["root_tarball"], os.path.join(self.nfs_path, self.root_tarball[1:])) shutil.copy(os.path.join(self.parameters["dtb_file"]), os.path.join(self.nfs_path, self.dtb_file[1:])) ssh_file = os.path.join(os.path.dirname(__file__), 'data', "authorized_keys") shutil.copy(ssh_file, os.path.join(self.nfs_path, self.ssh_file[1:])) local_execute(["umount", "/root/support_fs/beaglebone"])
def _set_default_directory(self, directory): """ Set VirtualBox default directory Args: directory: The new default VirtualBox VM directory """ misc.local_execute( ("VBoxManage setproperty machinefolder " + directory + "").split())
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 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 pull(remote_ip, source, destination, timeout=60, ignore_return_codes=None, user="******"): """ Transmit a file from remote 'source' to local 'destination' over SCP Args: remote_ip (str): Remote device IP source (str): path to file on the remote filesystem destination (str): path to the file on local filesystem timeout (integer): Timeout in seconds for the operation ignore_return_codes (list(integer)): List of scp return codes that will be ignored user (str): User that will be used with scp Returns: Scp output on success Raises: subprocess32.TimeoutExpired: If timeout expired subprocess32.CalledProcessError: If process returns non-zero, non-ignored return code """ scp_args = [ "scp", "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", user + "@" + str(remote_ip) + ":" + source, destination ] return tools.local_execute(scp_args, timeout, ignore_return_codes)
def _set_host_only_nic(self): """ Set the nic into host only mode so that the local dnsmasq server can lease it the ip address Args: None Returns: None """ misc.local_execute( ("VBoxManage modifyvm " + self._vm_name + " --nic1 hostonly").split()) misc.local_execute( ("VBoxManage modifyvm " + self._vm_name + " --hostonlyadapter1 " + "vboxnet0").split())
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())
def push(remote_ip, source, destination, timeout = 60, ignore_return_codes = None, user = "******"): """ Transmit a file from local 'source' to remote 'destination' over SCP """ scp_args = ["scp", source, user + "@" + str(remote_ip) + ":" + destination] return tools.local_execute(scp_args, timeout, ignore_return_codes)
def pull(remote_ip, source, destination,timeout = 60, ignore_return_codes = None, user = "******"): """ Transmit a file from remote 'source' to local 'destination' over SCP Args: remote_ip (str): Remote device IP source (str): path to file on the remote filesystem destination (str): path to the file on local filesystem timeout (integer): Timeout in seconds for the operation ignore_return_codes (list(integer)): List of scp return codes that will be ignored user (str): User that will be used with scp Returns: Scp output on success Raises: subprocess32.TimeoutExpired: If timeout expired subprocess32.CalledProcessError: If process returns non-zero, non-ignored return code """ scp_args = [ "scp", "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", user + "@" + str(remote_ip) + ":" + source, destination] return tools.local_execute(scp_args, timeout, ignore_return_codes)
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 get_available_cutters(): """ Returns list of available cutters Returns: List of dictionaries with the following format: { "type": "cleware", "cutter": (int) cleware_cutter_id "sockets": (int) number_of_sockets } """ ids_to_sockets = {} ids_to_sockets["512"] = 4 ids_to_sockets["29"] = 4 ids_to_sockets["51"] = 1 output = misc.local_execute(["clewarecontrol", "-l"]) output = output.split("\n") cutter_arrays = [line.split(",") for line in output if "Device: " in line] cutter_values = [(line[2].strip().split(" ")[1], line[3].strip().split(" ")[2]) for line in cutter_arrays] cutters = [] for val in cutter_values: cutters.append({ "type": "cleware", "cutter": int(val[1]), "sockets": ids_to_sockets[val[0]] }) return cutters
def _start_vm(self): if self._is_powered_on: return output = misc.local_execute(( "VBoxManage startvm " + self._vm_name + " --type headless").split()) if "error" in output: raise errors.AFTDeviceError("Failed to start the VM:\n" + output) self._is_powered_on = True
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 check_libcomposite_service_running(self): """ Check if libcomposite.service is running. Return 1 if running, else 0 """ try: out = local_execute( "systemctl status libcomposite.service".split()) if "Active: active" in out: return 1 return 0 except: return 0
def _send_command(self, power_status): """ Either turns power on or off. Retries on failure up to self._RETRIES times. Args: power_status (string): Either "0", or "1" to turn power off and on respectively Returns: None Raises: subprocess32.CalledProcessError or subprocess32.TimeoutExpired on failure """ error = "" for _ in range(self._RETRIES): try: misc.local_execute( [ "clewarecontrol", "-d", str(self._cutter_id), "-c", "1", "-as", str(self._channel), str(power_status) ]) except (subprocess32.CalledProcessError, subprocess32.TimeoutExpired) as err: error = err sleep( random.randint( self._MIN_SLEEP_DURATION, self._MAX_SLEEP_DURATION)) else: return raise error
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(),] return tools.local_execute(ssh_args + command, timeout, ignore_return_codes)
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 get_available_cutters(): """ Returns list of available cutters Returns: List of dictionaries with the following format: { "type": "cleware", "cutter": (int) cleware_cutter_id "sockets": (int) number_of_sockets } """ ids_to_sockets = {} ids_to_sockets["512"] = 4 ids_to_sockets["29"] = 4 ids_to_sockets["51"] = 1 output = misc.local_execute(["clewarecontrol", "-l"]) output = output.split("\n") cutter_arrays = [ line.split(",") for line in output if "Device: " in line ] cutter_values = [(line[2].strip().split(" ")[1], line[3].strip().split(" ")[2]) for line in cutter_arrays] cutters = [] for val in cutter_values: cutters.append({ "type": "cleware", "cutter": int(val[1]), "sockets": ids_to_sockets[val[0]] }) return cutters
def _find_mac_address(self): """ Find VM mac address from showvminfo output Returns: None Raises: aft.errors.AFTDeviceError: If mac address could not be found from showvminfo output """ output = misc.local_execute( ("VBoxManage showvminfo " + self._vm_name).split()) output = output.split("\n") # should contain line like: # NIC 1: MAC: 080027F3FDC2, Attachment: Host-only Interface 'vboxnet0', # Cable connected: on, Trace: off (file: none), Type: 82540EM, Reported # speed: 0 Mbps, Boot priority: 0, Promisc Policy: deny, Bandwidth # group: none # # We grab the mac address from it for line in output: if " MAC: " in line: self._mac_address = line.split()[3] if self._mac_address.endswith(","): self._mac_address = self._mac_address[:-1] # Add colons after every two symbols as_array = [self._mac_address[i:i+2] for i in range(0, len(self._mac_address), 2)] self._mac_address = ":".join(as_array) logger.info("Device mac address: " + self._mac_address) return raise errors.AFTDeviceError( "Failed to find mac address from showvminfo output. Has the " + "output format changed")
def _get_edison_configs(self): """ Return list of Edison networking configurations. Returns: List of Edison networking configurations. Configurations are tuples with the following format: ( "usb_device_tree_path", "host_interface_ip", "edison_network_subnet_ip" ) """ def connection_opener(queue, subnet_ip, line): """ Helper function for opening Edison network interface Args: queue (multiprocessing.Queue): Queue used to communicate results back to the main thread. subnet_ip (integer): The network subnet assigned for this device line (str): Line from dmesg log that contains Edison device registration notification and the usb tree path. Example: [328860.109597] usb 2-1.4.1.2: Product: Edison """ # [:-1]: need to remove ':' from the end usb_port = line.split(" ")[2][:-1] args = {} # Device class constructor expects these keys to be found # Probably should extract networking code from EdisonDevice so that # this hack\workaround wouldn't be needed ### DUMMY DATA START ### args["name"] = "dummy" args["model"] = "dummy" args["id"] = "dummy" args["test_plan"] = "dummy" ### DUMMY DATA END ### args["network_subnet"] = self._config["edison"]["subnet_prefix"] + \ str(subnet_ip) args["edison_usb_port"] = usb_port dev = EdisonDevice(args, None) dev.open_interface() # we do something slightly different here when compared to the # PC-like devices: we return the ip address to the usb network # interface on the host. We then ping this address instead when # detecting if network is still up. This interface disappears when # the edison powers down, so we can associate edison configuration # data. This also means that edison does not actually need a valid # operating system present queue.put((usb_port, dev.get_host_ip(), args["network_subnet"])) # Makes assumptions regarding dmesg message format. Might be fragile dmesg_lines = local_execute("dmesg").split("\n") edison_lines = [line for line in dmesg_lines if "Edison" in line] ip_addresses = [] ip_queue = Queue() processes = [] ip = int(self._config["edison"]["ip_start"]) for line in edison_lines: p = Process(target=connection_opener, args=(ip_queue, ip, line)) p.start() processes.append(p) ip += 4 for p in processes: p.join() ip_addresses.append(ip_queue.get()) return ip_addresses
def _unmount_virtual_drive(self): logger.info("Unmounting virtual hard drive") """ Unmount the VirtualBox virtual hard drive """ misc.local_execute(("guestunmount " + self._MOUNT_DIRECTORY).split())
def _unregister_vm(self): logger.info("Unregistering the vm") if self._vm_name == None: return misc.local_execute(("VBoxManage unregistervm " + self._vm_name).split())