OLD | NEW |
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 """A wrapper around ssh for common operations on a CrOS-based device""" | 4 """A wrapper around ssh for common operations on a CrOS-based device""" |
5 import logging | 5 import logging |
6 import os | 6 import os |
7 import re | 7 import re |
8 import socket | |
9 import subprocess | 8 import subprocess |
10 import sys | 9 import sys |
11 import time | 10 import time |
12 import tempfile | 11 import tempfile |
13 import util | 12 |
| 13 from chrome_remote_control import util |
14 | 14 |
15 _next_remote_port = 9224 | 15 _next_remote_port = 9224 |
16 | 16 |
17 # TODO(nduca): This whole file is built up around making individual ssh calls | 17 # TODO(nduca): This whole file is built up around making individual ssh calls |
18 # for each operation. It really could get away with a single ssh session built | 18 # for each operation. It really could get away with a single ssh session built |
19 # around pexpect, I suspect, if we wanted it to be faster. But, this was | 19 # around pexpect, I suspect, if we wanted it to be faster. But, this was |
20 # convenient. | 20 # convenient. |
21 | 21 |
22 def RunCmd(args, cwd=None): | 22 def RunCmd(args, cwd=None): |
23 """Opens a subprocess to execute a program and returns its return value. | 23 """Opens a subprocess to execute a program and returns its return value. |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
56 stderr=subprocess.PIPE, stdin=devnull, shell=False) | 56 stderr=subprocess.PIPE, stdin=devnull, shell=False) |
57 stdout, stderr = p.communicate() | 57 stdout, stderr = p.communicate() |
58 logging.debug(' > stdout=[%s], stderr=[%s]', stdout, stderr) | 58 logging.debug(' > stdout=[%s], stderr=[%s]', stdout, stderr) |
59 return stdout, stderr | 59 return stdout, stderr |
60 | 60 |
61 class DeviceSideProcess(object): | 61 class DeviceSideProcess(object): |
62 def __init__(self, | 62 def __init__(self, |
63 cri, | 63 cri, |
64 device_side_args, | 64 device_side_args, |
65 prevent_output=True, | 65 prevent_output=True, |
66 extra_ssh_args=[], | 66 extra_ssh_args=None, |
67 leave_ssh_alive=False, | 67 leave_ssh_alive=False, |
68 env={}, | 68 env=None, |
69 login_shell=False): | 69 login_shell=False): |
70 | 70 |
71 # Init members first so that Close will always succeed. | 71 # Init members first so that Close will always succeed. |
72 self._cri = cri | 72 self._cri = cri |
73 self._proc = None | 73 self._proc = None |
74 self._devnull = open(os.devnull, 'w') | 74 self._devnull = open(os.devnull, 'w') |
75 | 75 |
76 if prevent_output: | 76 if prevent_output: |
77 out = self._devnull | 77 out = self._devnull |
78 else: | 78 else: |
79 out = sys.stderr | 79 out = sys.stderr |
80 | 80 |
81 cri.GetCmdOutput(['rm', '-rf', '/tmp/cros_interface_remote_device_pid']) | 81 cri.GetCmdOutput(['rm', '-rf', '/tmp/cros_interface_remote_device_pid']) |
82 env_str = ' '.join(['%s=%s' % (k,v) for k,v in env.items()]) | |
83 cmd_str = ' '.join(device_side_args) | 82 cmd_str = ' '.join(device_side_args) |
84 if env_str: | 83 if env: |
| 84 env_str = ' '.join(['%s=%s' % (k, v) for k, v in env.items()]) |
85 cmd = env_str + ' ' + cmd_str | 85 cmd = env_str + ' ' + cmd_str |
86 else: | 86 else: |
87 cmd = cmd_str | 87 cmd = cmd_str |
88 contents = """%s&\n""" % cmd | 88 contents = """%s&\n""" % cmd |
89 contents += 'echo $! > /tmp/cros_interface_remote_device_pid\n' | 89 contents += 'echo $! > /tmp/cros_interface_remote_device_pid\n' |
90 cri.PushContents(contents, '/tmp/cros_interface_remote_device_bootstrap.sh') | 90 cri.PushContents(contents, '/tmp/cros_interface_remote_device_bootstrap.sh') |
91 | 91 |
92 cmdline = ['/bin/bash'] | 92 cmdline = ['/bin/bash'] |
93 if login_shell: | 93 if login_shell: |
94 cmdline.append('-l') | 94 cmdline.append('-l') |
95 cmdline.append('/tmp/cros_interface_remote_device_bootstrap.sh') | 95 cmdline.append('/tmp/cros_interface_remote_device_bootstrap.sh') |
96 proc =subprocess.Popen( | 96 proc = subprocess.Popen( |
97 cri._FormSSHCommandLine(cmdline, | 97 cri.FormSSHCommandLine(cmdline, |
98 extra_ssh_args=extra_ssh_args), | 98 extra_ssh_args=extra_ssh_args), |
99 stdout=out, | 99 stdout=out, |
100 stderr=out, | 100 stderr=out, |
101 stdin=self._devnull, | 101 stdin=self._devnull, |
102 shell=False) | 102 shell=False) |
103 | 103 |
104 time.sleep(0.1) | 104 time.sleep(0.1) |
105 def TryGetResult(): | 105 def TryGetResult(): |
106 try: | 106 try: |
107 self._pid = cri.GetFileContents( | 107 self._pid = cri.GetFileContents( |
(...skipping 15 matching lines...) Expand all Loading... |
123 | 123 |
124 self._pid = int(self._pid) | 124 self._pid = int(self._pid) |
125 if not self.IsAlive(): | 125 if not self.IsAlive(): |
126 raise OSError('Process did not come up or did not stay alive verry long!') | 126 raise OSError('Process did not come up or did not stay alive verry long!') |
127 self._cri = cri | 127 self._cri = cri |
128 | 128 |
129 def Close(self, try_sigint_first=False): | 129 def Close(self, try_sigint_first=False): |
130 if self.IsAlive(): | 130 if self.IsAlive(): |
131 # Try to politely shutdown, first. | 131 # Try to politely shutdown, first. |
132 if try_sigint_first: | 132 if try_sigint_first: |
133 stdout, stder = self._cri._GetAllCmdOutput( | 133 self._cri.GetAllCmdOutput( |
134 ['kill', '-INT', str(self._pid)]) | 134 ['kill', '-INT', str(self._pid)]) |
135 try: | 135 try: |
136 self.Wait(timeout=0.5) | 136 self.Wait(timeout=0.5) |
137 except util.TimeoutException: | 137 except util.TimeoutException: |
138 pass | 138 pass |
139 | 139 |
140 if self.IsAlive(): | 140 if self.IsAlive(): |
141 stdout, stder = self._cri._GetAllCmdOutput( | 141 self._cri.GetAllCmdOutput( |
142 ['kill', '-KILL', str(self._pid)]) | 142 ['kill', '-KILL', str(self._pid)]) |
143 try: | 143 try: |
144 self.Wait(timeout=1) | 144 self.Wait(timeout=5) |
145 except util.TimeoutException: | 145 except util.TimeoutException: |
146 pass | 146 pass |
147 | 147 |
148 if self.IsAlive(): | 148 if self.IsAlive(): |
149 raise Exception('Could not shutdown the process.') | 149 raise Exception('Could not shutdown the process.') |
150 | 150 |
151 self._cri = None | 151 self._cri = None |
152 if self._proc: | 152 if self._proc: |
153 self._proc.kill() | 153 self._proc.kill() |
154 self._proc = None | 154 self._proc = None |
(...skipping 30 matching lines...) Expand all Loading... |
185 return True | 185 return True |
186 except OSError: | 186 except OSError: |
187 return False | 187 return False |
188 | 188 |
189 class LoginException(Exception): | 189 class LoginException(Exception): |
190 pass | 190 pass |
191 | 191 |
192 class KeylessLoginRequiredException(LoginException): | 192 class KeylessLoginRequiredException(LoginException): |
193 pass | 193 pass |
194 | 194 |
195 class CrOSInterface(object): | 195 class CrOSInterface(object): # pylint: disable=R0923 |
196 def __init__(self, hostname): | 196 def __init__(self, hostname): |
197 self._hostname = hostname | 197 self._hostname = hostname |
198 | 198 |
199 def _FormSSHCommandLine(self, args, extra_ssh_args=[]): | 199 @property |
| 200 def hostname(self): |
| 201 return self._hostname |
| 202 |
| 203 def FormSSHCommandLine(self, args, extra_ssh_args=None): |
200 full_args = ['ssh', | 204 full_args = ['ssh', |
201 '-o ConnectTimeout=5', | 205 '-o ConnectTimeout=5', |
202 '-o ForwardAgent=no', | 206 '-o ForwardAgent=no', |
203 '-o ForwardX11=no', | 207 '-o ForwardX11=no', |
204 '-o ForwardX11Trusted=no', | 208 '-o ForwardX11Trusted=no', |
205 '-o KbdInteractiveAuthentication=no', | 209 '-o KbdInteractiveAuthentication=no', |
206 '-o StrictHostKeyChecking=yes', | 210 '-o StrictHostKeyChecking=yes', |
207 '-n'] | 211 '-n'] |
208 if len(extra_ssh_args): | 212 if extra_ssh_args: |
209 full_args.extend(extra_ssh_args) | 213 full_args.extend(extra_ssh_args) |
210 full_args.append('root@%s' % self._hostname) | 214 full_args.append('root@%s' % self._hostname) |
211 full_args.extend(args) | 215 full_args.extend(args) |
212 return full_args | 216 return full_args |
213 | 217 |
214 def _GetAllCmdOutput(self, args, cwd=None): | 218 def GetAllCmdOutput(self, args, cwd=None): |
215 return GetAllCmdOutput(self._FormSSHCommandLine(args), cwd) | 219 return GetAllCmdOutput(self.FormSSHCommandLine(args), cwd) |
216 | 220 |
217 def TryLogin(self): | 221 def TryLogin(self): |
218 stdout, stderr = self._GetAllCmdOutput(['echo', '$USER']) | 222 stdout, stderr = self.GetAllCmdOutput(['echo', '$USER']) |
219 | 223 |
220 if stderr != '': | 224 if stderr != '': |
221 if 'Host key verification failed' in stderr: | 225 if 'Host key verification failed' in stderr: |
222 raise LoginException(('%s host key verification failed. ' + | 226 raise LoginException(('%s host key verification failed. ' + |
223 'SSH to it manually to fix connectivity.') % | 227 'SSH to it manually to fix connectivity.') % |
224 self._hostname) | 228 self._hostname) |
225 if 'Operation timed out' in stderr: | 229 if 'Operation timed out' in stderr: |
226 raise LoginException('Timed out while logging into %s' % self._hostname) | 230 raise LoginException('Timed out while logging into %s' % self._hostname) |
227 raise LoginException('While logging into %s, got %s' % ( | 231 raise LoginException('While logging into %s, got %s' % ( |
228 self._hostname,stderr.strip())) | 232 self._hostname, stderr)) |
229 if stdout != 'root\n': | 233 if stdout != 'root\n': |
230 raise LoginException( | 234 raise LoginException( |
231 'Logged into %s, expected $USER=root, but got %s.' % ( | 235 'Logged into %s, expected $USER=root, but got %s.' % ( |
232 self._hostname, stdout)) | 236 self._hostname, stdout)) |
233 | 237 |
234 def FileExistsOnDevice(self, file_name): | 238 def FileExistsOnDevice(self, file_name): |
235 stdout, stderr = self._GetAllCmdOutput([ | 239 stdout, stderr = self.GetAllCmdOutput([ |
236 'if', 'test', '-a', file_name, ';', | 240 'if', 'test', '-a', file_name, ';', |
237 'then', 'echo', '1', ';', | 241 'then', 'echo', '1', ';', |
238 'fi' | 242 'fi' |
239 ]) | 243 ]) |
240 if stderr != '': | 244 if stderr != '': |
241 if "Connection timed out" in stderr: | 245 if "Connection timed out" in stderr: |
242 raise OSError('Machine wasn\'t responding to ssh: %s' % | 246 raise OSError('Machine wasn\'t responding to ssh: %s' % |
243 stderr) | 247 stderr) |
244 raise OSError('Unepected error: %s' % stderr) | 248 raise OSError('Unepected error: %s' % stderr) |
245 return stdout == '1\n' | 249 return stdout == '1\n' |
(...skipping 23 matching lines...) Expand all Loading... |
269 'root@%s:%s' % (self._hostname, filename), | 273 'root@%s:%s' % (self._hostname, filename), |
270 os.path.abspath(f.name)]) | 274 os.path.abspath(f.name)]) |
271 if stderr != '': | 275 if stderr != '': |
272 assert 'No such file or directory' in stderr | 276 assert 'No such file or directory' in stderr |
273 raise OSError | 277 raise OSError |
274 | 278 |
275 with open(f.name, 'r') as f2: | 279 with open(f.name, 'r') as f2: |
276 return f2.read() | 280 return f2.read() |
277 | 281 |
278 def ListProcesses(self): | 282 def ListProcesses(self): |
279 stdout, stderr = self._GetAllCmdOutput([ | 283 stdout, stderr = self.GetAllCmdOutput([ |
280 '/bin/ps', '--no-headers', | 284 '/bin/ps', '--no-headers', |
281 '-A', | 285 '-A', |
282 '-o', 'pid,args']) | 286 '-o', 'pid,args']) |
283 assert stderr == '' | 287 assert stderr == '' |
284 procs = [] | 288 procs = [] |
285 for l in stdout.split('\n'): | 289 for l in stdout.split('\n'): # pylint: disable=E1103 |
286 if l == '': | 290 if l == '': |
287 continue | 291 continue |
288 m = re.match('^\s*(\d+)\s+(.+)', l, re.DOTALL) | 292 m = re.match('^\s*(\d+)\s+(.+)', l, re.DOTALL) |
289 assert m | 293 assert m |
290 procs.append(m.groups()) | 294 procs.append(m.groups()) |
291 return procs | 295 return procs |
292 | 296 |
293 def KillAllMatching(self, predicate): | 297 def KillAllMatching(self, predicate): |
294 kills = ['kill', '-KILL'] | 298 kills = ['kill', '-KILL'] |
295 for p in self.ListProcesses(): | 299 for p in self.ListProcesses(): |
296 if predicate(p[1]): | 300 if predicate(p[1]): |
297 logging.info('Killing %s', repr(p)) | 301 logging.info('Killing %s', repr(p)) |
298 kills.append(p[0]) | 302 kills.append(p[0]) |
299 if len(kills) > 2: | 303 if len(kills) > 2: |
300 self.GetCmdOutput(kills) | 304 self.GetCmdOutput(kills) |
301 return len(kills) - 2 | 305 return len(kills) - 2 |
302 | 306 |
303 def IsServiceRunning(self, service_name): | 307 def IsServiceRunning(self, service_name): |
304 stdout, stderr = self._GetAllCmdOutput([ | 308 stdout, stderr = self.GetAllCmdOutput([ |
305 'status', service_name]) | 309 'status', service_name]) |
306 assert stderr == '' | 310 assert stderr == '' |
307 return 'running, process' in stdout | 311 return 'running, process' in stdout |
308 | 312 |
309 def GetCmdOutput(self, args): | 313 def GetCmdOutput(self, args): |
310 stdout, stderr = self._GetAllCmdOutput(args) | 314 stdout, stderr = self.GetAllCmdOutput(args) |
311 assert stderr == '' | 315 assert stderr == '' |
312 return stdout | 316 return stdout |
313 | 317 |
314 def GetRemotePort(self): | 318 def GetRemotePort(self): |
315 global _next_remote_port | 319 global _next_remote_port |
316 port = _next_remote_port | 320 port = _next_remote_port |
317 _next_remote_port += 1 | 321 _next_remote_port += 1 |
318 return port | 322 return port |
OLD | NEW |