|  | # Copyright 2018 The Chromium Authors. All rights reserved. | 
|  | # Use of this source code is governed by a BSD-style license that can be | 
|  | # found in the LICENSE file. | 
|  |  | 
|  | import logging | 
|  | import os | 
|  | import remote_cmd | 
|  | import subprocess | 
|  | import sys | 
|  | import tempfile | 
|  | import time | 
|  |  | 
|  | _SHUTDOWN_CMD = ['dm', 'poweroff'] | 
|  | _ATTACH_MAX_RETRIES = 10 | 
|  | _ATTACH_RETRY_INTERVAL = 1 | 
|  |  | 
|  |  | 
|  | class FuchsiaTargetException(Exception): | 
|  | def __init__(self, message): | 
|  | super(FuchsiaTargetException, self).__init__(message) | 
|  |  | 
|  |  | 
|  | class Target(object): | 
|  | """Base class representing a Fuchsia deployment target.""" | 
|  |  | 
|  | def __init__(self, output_dir, target_cpu): | 
|  | self._output_dir = output_dir | 
|  | self._started = False | 
|  | self._dry_run = False | 
|  | self._target_cpu = target_cpu | 
|  |  | 
|  | # Functions used by the Python context manager for teardown. | 
|  | def __enter__(self): | 
|  | return self | 
|  | def __exit__(self, exc_type, exc_val, exc_tb): | 
|  | return self | 
|  |  | 
|  | def Start(self): | 
|  | """Handles the instantiation and connection process for the Fuchsia | 
|  | target instance.""" | 
|  |  | 
|  | pass | 
|  |  | 
|  | def IsStarted(self): | 
|  | """Returns True if the Fuchsia target instance is ready to accept | 
|  | commands.""" | 
|  |  | 
|  | return self._started | 
|  |  | 
|  | def IsNewInstance(self): | 
|  | """Returns True if the connected target instance is newly provisioned.""" | 
|  |  | 
|  | return True | 
|  |  | 
|  | def RunCommandPiped(self, command, **kwargs): | 
|  | """Starts a remote command and immediately returns a Popen object for the | 
|  | command. The caller may interact with the streams, inspect the status code, | 
|  | wait on command termination, etc. | 
|  |  | 
|  | command: A list of strings representing the command and arguments. | 
|  | kwargs: A dictionary of parameters to be passed to subprocess.Popen(). | 
|  | The parameters can be used to override stdin and stdout, for | 
|  | example. | 
|  |  | 
|  | Returns: a Popen object. | 
|  |  | 
|  | Note: method does not block.""" | 
|  |  | 
|  | self._AssertIsStarted() | 
|  | logging.debug('running (non-blocking) \'%s\'.' % ' '.join(command)) | 
|  | host, port = self._GetEndpoint() | 
|  | return remote_cmd.RunPipedSsh(self._GetSshConfigPath(), host, port, command, | 
|  | **kwargs) | 
|  |  | 
|  | def RunCommand(self, command, silent=False): | 
|  | """Executes a remote command and waits for it to finish executing. | 
|  |  | 
|  | Returns the exit code of the command.""" | 
|  |  | 
|  | self._AssertIsStarted() | 
|  | logging.debug('running \'%s\'.' % ' '.join(command)) | 
|  | host, port = self._GetEndpoint() | 
|  | return remote_cmd.RunSsh(self._GetSshConfigPath(), host, port, command, | 
|  | silent) | 
|  |  | 
|  | def PutFile(self, source, dest, recursive=False): | 
|  | """Copies a file from the local filesystem to the target filesystem. | 
|  |  | 
|  | source: The path of the file being copied. | 
|  | dest: The path on the remote filesystem which will be copied to. | 
|  | recursive: If true, performs a recursive copy.""" | 
|  |  | 
|  | assert type(source) is str | 
|  | self.PutFiles([source], dest, recursive) | 
|  |  | 
|  | def PutFiles(self, sources, dest, recursive=False): | 
|  | """Copies files from the local filesystem to the target filesystem. | 
|  |  | 
|  | sources: List of local file paths to copy from, or a single path. | 
|  | dest: The path on the remote filesystem which will be copied to. | 
|  | recursive: If true, performs a recursive copy.""" | 
|  |  | 
|  | assert type(sources) is tuple or type(sources) is list | 
|  | self._AssertIsStarted() | 
|  | host, port = self._GetEndpoint() | 
|  | logging.debug('copy local:%s => remote:%s' % (sources, dest)) | 
|  | command = remote_cmd.RunScp(self._GetSshConfigPath(), host, port, | 
|  | sources, dest, remote_cmd.COPY_TO_TARGET, | 
|  | recursive) | 
|  |  | 
|  | def GetFile(self, source, dest): | 
|  | """Copies a file from the target filesystem to the local filesystem. | 
|  |  | 
|  | source: The path of the file being copied. | 
|  | dest: The path on the local filesystem which will be copied to.""" | 
|  | assert type(source) is str | 
|  | self.GetFiles([source], dest) | 
|  |  | 
|  | def GetFiles(self, sources, dest): | 
|  | """Copies files from the target filesystem to the local filesystem. | 
|  |  | 
|  | sources: List of remote file paths to copy. | 
|  | dest: The path on the local filesystem which will be copied to.""" | 
|  | assert type(sources) is tuple or type(sources) is list | 
|  | self._AssertIsStarted() | 
|  | host, port = self._GetEndpoint() | 
|  | logging.debug('copy remote:%s => local:%s' % (sources, dest)) | 
|  | return remote_cmd.RunScp(self._GetSshConfigPath(), host, port, | 
|  | sources, dest, remote_cmd.COPY_FROM_TARGET) | 
|  |  | 
|  | def _GetEndpoint(self): | 
|  | """Returns a (host, port) tuple for the SSH connection to the target.""" | 
|  | raise NotImplementedError | 
|  |  | 
|  | def _GetTargetSdkArch(self): | 
|  | """Returns the Fuchsia SDK architecture name for the target CPU.""" | 
|  | if self._target_cpu == 'arm64': | 
|  | return 'aarch64' | 
|  | elif self._target_cpu == 'x64': | 
|  | return 'x86_64' | 
|  | raise FuchsiaTargetException('Unknown target_cpu:' + self._target_cpu) | 
|  |  | 
|  | def _AssertIsStarted(self): | 
|  | assert self.IsStarted() | 
|  |  | 
|  | def _WaitUntilReady(self, retries=_ATTACH_MAX_RETRIES): | 
|  | logging.info('Connecting to Fuchsia using SSH.') | 
|  | for _ in xrange(retries+1): | 
|  | host, port = self._GetEndpoint() | 
|  | if remote_cmd.RunSsh(self._GetSshConfigPath(), host, port, ['true'], | 
|  | True) == 0: | 
|  | logging.info('Connected!') | 
|  | self._started = True | 
|  | return True | 
|  | time.sleep(_ATTACH_RETRY_INTERVAL) | 
|  | logging.error('Timeout limit reached.') | 
|  | raise FuchsiaTargetException('Couldn\'t connect using SSH.') | 
|  |  | 
|  | def _GetSshConfigPath(self, path): | 
|  | raise NotImplementedError | 
|  |  | 
|  | def _GetTargetSdkArch(self): | 
|  | """Returns the Fuchsia SDK architecture name for the target CPU.""" | 
|  | if self._target_cpu == 'arm64': | 
|  | return 'aarch64' | 
|  | elif self._target_cpu == 'x64': | 
|  | return 'x86_64' | 
|  | raise Exception('Unknown target_cpu %s:' % self._target_cpu) |