1# Copyright 2016 The Chromium 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 6from recipe_engine import recipe_api 7 8from . import default 9 10 11"""SSH flavor, used for running code on a remote device via SSH. 12 13Must be subclassed to set self.device_dirs. The default implementation assumes 14a Linux-based device. 15""" 16 17 18class SSHFlavor(default.DefaultFlavor): 19 20 def __init__(self, m, app_name): 21 super(SSHFlavor, self).__init__(m, app_name) 22 self._did_ssh_setup = False 23 self._use_old_flow = self.m.vars.builder_cfg['model'] in ( 24 'Kevin', 'Sparky360', 'Spin513', 'Spin514') 25 self._ssh_args = [] 26 self._user_ip = 'root@variable_chromeos_device_hostname' 27 if self._use_old_flow: 28 self._user_ip = '' # Will be filled in later. 29 30 def _ssh_setup(self): 31 if self._use_old_flow or self._did_ssh_setup: 32 return 33 34 tmp = self.m.path.mkdtemp('chromite') 35 with self.m.context(cwd=tmp): 36 chromite_url = 'https://chromium.googlesource.com/chromiumos/chromite.git' 37 self.m.step('clone chromite', ['git', 'clone', chromite_url]) 38 testing_rsa = tmp.join('chromite', 'ssh_keys', 'testing_rsa') 39 self.m.step('chmod 600 testing_rsa', ['chmod', '600', testing_rsa]) 40 self._ssh_args = [ 41 '-oConnectTimeout=30', '-oConnectionAttempts=4', 42 '-oNumberOfPasswordPrompts=0', '-oProtocol=2', '-oServerAliveInterval=15', 43 '-oServerAliveCountMax=8', '-oStrictHostKeyChecking=no', 44 '-oUserKnownHostsFile=/dev/null', '-oIdentitiesOnly=yes', 45 '-i', testing_rsa 46 ] 47 48 self._did_ssh_setup = True 49 50 @property 51 def user_ip(self): 52 if not self._user_ip: 53 path = '/tmp/ssh_machine.json' 54 ssh_info = self.m.file.read_json('read ssh_machine.json', path, 55 test_data={'user_ip':'foo@127.0.0.1'}) 56 self._user_ip = ssh_info.get(u'user_ip') 57 return self._user_ip 58 59 def ssh(self, title, *cmd, **kwargs): 60 self._ssh_setup() 61 62 if 'infra_step' not in kwargs: 63 kwargs['infra_step'] = True 64 65 ssh_cmd = ['ssh', '-oConnectTimeout=15', '-oBatchMode=yes', '-t', '-t'] + \ 66 self._ssh_args + [self.user_ip] + list(cmd) 67 68 return self._run(title, ssh_cmd, **kwargs) 69 70 def ensure_device_dir(self, path): 71 self.ssh('mkdir %s' % path, 'mkdir', '-p', path) 72 73 def install(self): 74 self.ensure_device_dir(self.device_dirs.resource_dir) 75 if self.app_name: 76 self.create_clean_device_dir(self.device_dirs.bin_dir) 77 host_path = self.host_dirs.bin_dir.joinpath(self.app_name) 78 device_path = self.device_path_join(self.device_dirs.bin_dir, self.app_name) 79 self.copy_file_to_device(host_path, device_path) 80 self.ssh('make %s executable' % self.app_name, 'chmod', '+x', device_path) 81 82 def create_clean_device_dir(self, path): 83 # use -f to silently return if path doesn't exist 84 self.ssh('rm %s' % path, 'rm', '-rf', path) 85 self.ensure_device_dir(path) 86 87 def read_file_on_device(self, path, **kwargs): 88 rv = self.ssh('read %s' % path, 89 'cat', path, stdout=self.m.raw_io.output(), 90 **kwargs) 91 return rv.stdout.decode('utf-8').rstrip() if rv and rv.stdout else None 92 93 def remove_file_on_device(self, path): 94 # use -f to silently return if path doesn't exist 95 self.ssh('rm %s' % path, 'rm', '-f', path) 96 97 def scp_device_path(self, device_path): 98 return '%s:%s' % (self.user_ip, device_path) 99 100 def copy_file_to_device(self, host_path, device_path): 101 device_path = self.scp_device_path(device_path) 102 self._run('scp %s %s' % (host_path, device_path), 103 ['scp'] + self._ssh_args + [host_path, device_path], infra_step=True) 104 105 # TODO(benjaminwagner): implement with rsync 106 #def copy_directory_contents_to_device(self, host_path, device_path): 107 108 # TODO(benjaminwagner): implement with rsync 109 #def copy_directory_contents_to_host(self, device_path, host_path): 110 111 def step(self, name, cmd, **kwargs): 112 # Run cmd (installed above) 113 cmd[0] = self.device_path_join(self.device_dirs.bin_dir, cmd[0]) 114 self.ssh(str(name), *cmd) 115