1#!/usr/bin/env python 2# 3# Copyright 2016 Google Inc. 4# 5# Use of this source code is governed by a BSD-style license that can be 6# found in the LICENSE file. 7 8 9import default_flavor 10import os 11import posixpath 12import subprocess 13import ssh_devices 14 15 16"""Utils for running tests remotely over SSH.""" 17 18 19class SSHFlavorUtils(default_flavor.DefaultFlavorUtils): 20 def __init__(self, *args, **kwargs): 21 super(SSHFlavorUtils, self).__init__(*args, **kwargs) 22 slave_info = ssh_devices.SLAVE_INFO.get(self._bot_info.slave_name, 23 ssh_devices.SLAVE_INFO['default']) 24 self._host = slave_info.ssh_host 25 self._port = slave_info.ssh_port 26 self._user = slave_info.ssh_user 27 28 @property 29 def host(self): 30 return self._host 31 32 @property 33 def port(self): 34 return self._port 35 36 @property 37 def user(self): 38 return self._user 39 40 def ssh(self, cmd, **kwargs): 41 """Run the given SSH command.""" 42 ssh_cmd = ['ssh'] 43 if self.port: 44 ssh_cmd.extend(['-p', self.port]) 45 dest = self.host 46 if self.user: 47 dest = self.user + '@' + dest 48 ssh_cmd.append(dest) 49 ssh_cmd.extend(cmd) 50 return self._bot_info.run(ssh_cmd, **kwargs) 51 52 def step(self, *args, **kwargs): 53 """Run the given step over SSH.""" 54 self.ssh(*args, **kwargs) 55 56 def device_path_join(self, *args): 57 """Like os.path.join(), but for paths on a remote machine.""" 58 return posixpath.join(*args) 59 60 def device_path_exists(self, path): # pragma: no cover 61 """Like os.path.exists(), but for paths on a remote device.""" 62 try: 63 self.ssh(['test', '-e', path]) 64 return True 65 except subprocess.CalledProcessError: 66 return False 67 68 def _remove_device_dir(self, path): 69 """Remove the directory on the device.""" 70 self.ssh(['rm', '-rf', path]) 71 72 def _create_device_dir(self, path): 73 """Create the directory on the device.""" 74 self.ssh(['mkdir', '-p', path]) 75 76 def create_clean_device_dir(self, path): 77 """Like shutil.rmtree() + os.makedirs(), but on a remote device.""" 78 self._remove_device_dir(path) 79 self._create_device_dir(path) 80 81 def _make_scp_cmd(self, remote_path, recurse=True): 82 """Prepare an SCP command. 83 84 Returns a partial SCP command and an adjusted remote path. 85 """ 86 cmd = ['scp'] 87 if recurse: 88 cmd.append('-r') 89 if self.port: 90 cmd.extend(['-P', self.port]) 91 adj_remote_path = self.host + ':' + remote_path 92 if self.user: 93 adj_remote_path = self.user + '@' + adj_remote_path 94 return cmd, adj_remote_path 95 96 def copy_directory_contents_to_device(self, host_dir, device_dir): 97 """Like shutil.copytree(), but for copying to a remote device.""" 98 _, remote_path = self._make_scp_cmd(device_dir) 99 cmd = [os.path.join(self._bot_info.skia_dir, 'tools', 100 'scp_dir_contents.sh'), 101 host_dir, remote_path] 102 self._bot_info.run(cmd) 103 104 def copy_directory_contents_to_host(self, device_dir, host_dir): 105 """Like shutil.copytree(), but for copying from a remote device.""" 106 _, remote_path = self._make_scp_cmd(device_dir) 107 cmd = [os.path.join(self._bot_info.skia_dir, 'tools', 108 'scp_dir_contents.sh'), 109 remote_path, host_dir] 110 self._bot_info.run(cmd) 111 112 def copy_file_to_device(self, host_path, device_path): 113 """Like shutil.copyfile, but for copying to a connected device.""" 114 cmd, remote_path = self._make_scp_cmd(device_path, recurse=False) 115 cmd.extend([host_path, remote_path]) 116 self._bot_info.run(cmd) 117 118 def read_file_on_device(self, path): 119 return self.ssh(['cat', path]).rstrip() 120 121 def remove_file_on_device(self, path): 122 """Delete the given file.""" 123 return self.ssh(['rm', '-f', path]) 124