| # 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. |
| |
| """Implements commands for running and interacting with Fuchsia on devices.""" |
| |
| import boot_data |
| import logging |
| import os |
| import subprocess |
| import target |
| import time |
| import uuid |
| |
| from common import SDK_ROOT, EnsurePathExists |
| |
| CONNECT_RETRY_COUNT = 20 |
| CONNECT_RETRY_WAIT_SECS = 1 |
| |
| class DeviceTarget(target.Target): |
| def __init__(self, output_dir, target_cpu, host=None, port=None, |
| ssh_config=None): |
| """output_dir: The directory which will contain the files that are |
| generated to support the deployment. |
| target_cpu: The CPU architecture of the deployment target. Can be |
| "x64" or "arm64". |
| host: The address of the deployment target device. |
| port: The port of the SSH service on the deployment target device. |
| ssh_config: The path to SSH configuration data.""" |
| |
| super(DeviceTarget, self).__init__(output_dir, target_cpu) |
| |
| self._port = 22 |
| self._auto = not host or not ssh_config |
| self._new_instance = True |
| |
| if self._auto: |
| self._ssh_config_path = EnsurePathExists( |
| boot_data.GetSSHConfigPath(output_dir)) |
| else: |
| self._ssh_config_path = os.path.expanduser(ssh_config) |
| self._host = host |
| if port: |
| self._port = port |
| self._new_instance = False |
| |
| def __Discover(self, node_name): |
| """Returns the IP address and port of a Fuchsia instance discovered on |
| the local area network.""" |
| |
| netaddr_path = os.path.join(SDK_ROOT, 'tools', 'netaddr') |
| command = [netaddr_path, '--fuchsia', '--nowait', node_name] |
| logging.debug(' '.join(command)) |
| proc = subprocess.Popen(command, |
| stdout=subprocess.PIPE, |
| stderr=open(os.devnull, 'w')) |
| proc.wait() |
| if proc.returncode == 0: |
| return proc.stdout.readlines()[0].strip() |
| return None |
| |
| def Start(self): |
| if self._auto: |
| logging.debug('Starting automatic device deployment.') |
| node_name = boot_data.GetNodeName(self._output_dir) |
| self._host = self.__Discover(node_name) |
| if self._host and self._WaitUntilReady(retries=0): |
| logging.info('Connected to an already booted device.') |
| self._new_instance = False |
| return |
| |
| logging.info('Netbooting Fuchsia. ' + |
| 'Please ensure that your device is in bootloader mode.') |
| bootserver_path = os.path.join(SDK_ROOT, 'tools', 'bootserver') |
| bootserver_command = [ |
| bootserver_path, |
| '-1', |
| '--efi', |
| EnsurePathExists(boot_data.GetTargetFile(self._GetTargetSdkArch(), |
| 'local.esp.blk')), |
| '--fvm', |
| EnsurePathExists(boot_data.GetTargetFile(self._GetTargetSdkArch(), |
| 'fvm.sparse.blk')), |
| '--fvm', |
| EnsurePathExists( |
| boot_data.ConfigureDataFVM(self._output_dir, |
| boot_data.FVM_TYPE_SPARSE)), |
| EnsurePathExists(boot_data.GetTargetFile(self._GetTargetSdkArch(), |
| 'zircon.bin')), |
| EnsurePathExists(boot_data.GetTargetFile(self._GetTargetSdkArch(), |
| 'bootdata-blob.bin')), |
| '--'] + boot_data.GetKernelArgs(self._output_dir) |
| logging.debug(' '.join(bootserver_command)) |
| subprocess.check_call(bootserver_command) |
| |
| logging.debug('Waiting for device to join network.') |
| for _ in xrange(CONNECT_RETRY_COUNT): |
| self._host = self.__Discover(node_name) |
| if self._host: |
| break |
| time.sleep(CONNECT_RETRY_WAIT_SECS) |
| if not self._host: |
| raise Exception('Couldn\'t connect to device.') |
| |
| logging.debug('host=%s, port=%d' % (self._host, self._port)) |
| |
| self._WaitUntilReady(); |
| |
| def IsNewInstance(self): |
| return self._new_instance |
| |
| def _GetEndpoint(self): |
| return (self._host, self._port) |
| |
| def _GetSshConfigPath(self): |
| return self._ssh_config_path |