1# Copyright 2015 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"""Provides a work around for various adb commands on android gce instances.
6
7Some adb commands don't work well when the device is a cloud vm, namely
8'push' and 'pull'. With gce instances, moving files through adb can be
9painfully slow and hit timeouts, so the methods here just use scp instead.
10"""
11# pylint: disable=unused-argument
12
13import logging
14import os
15import subprocess
16
17from devil.android import device_errors
18from devil.android.sdk import adb_wrapper
19from devil.utils import cmd_helper
20
21
22class GceAdbWrapper(adb_wrapper.AdbWrapper):
23
24  def __init__(self, device_serial):
25    super(GceAdbWrapper, self).__init__(device_serial)
26    self._Connect()
27    self.Root()
28    self._instance_ip = self.Shell('getprop net.gce.ip').strip()
29
30  def _Connect(self, timeout=adb_wrapper.DEFAULT_TIMEOUT,
31               retries=adb_wrapper.DEFAULT_RETRIES):
32    """Connects ADB to the android gce instance."""
33    cmd = ['connect', self._device_serial]
34    output = self._RunAdbCmd(cmd, timeout=timeout, retries=retries)
35    if 'unable to connect' in output:
36      raise device_errors.AdbCommandFailedError(cmd, output)
37    self.WaitForDevice()
38
39  # override
40  def Root(self, **kwargs):
41    super(GceAdbWrapper, self).Root()
42    self._Connect()
43
44  # override
45  def Push(self, local, remote, **kwargs):
46    """Pushes an object from the host to the gce instance.
47
48    Args:
49      local: Path on the host filesystem.
50      remote: Path on the instance filesystem.
51    """
52    adb_wrapper.VerifyLocalFileExists(local)
53    if os.path.isdir(local):
54      self.Shell('mkdir -p %s' % cmd_helper.SingleQuote(remote))
55
56      # When the object to be pushed is a directory, adb merges the source dir
57      # with the destination dir. So if local is a dir, just scp its contents.
58      for f in os.listdir(local):
59        self._PushObject(os.path.join(local, f), os.path.join(remote, f))
60        self.Shell('chmod 777 %s' %
61                   cmd_helper.SingleQuote(os.path.join(remote, f)))
62    else:
63      parent_dir = remote[0:remote.rfind('/')]
64      if parent_dir:
65        self.Shell('mkdir -p %s' % cmd_helper.SingleQuote(parent_dir))
66      self._PushObject(local, remote)
67      self.Shell('chmod 777 %s' % cmd_helper.SingleQuote(remote))
68
69  def _PushObject(self, local, remote):
70    """Copies an object from the host to the gce instance using scp.
71
72    Args:
73      local: Path on the host filesystem.
74      remote: Path on the instance filesystem.
75    """
76    cmd = [
77        'scp',
78        '-r',
79        '-o', 'UserKnownHostsFile=/dev/null',
80        '-o', 'StrictHostKeyChecking=no',
81        local,
82        'root@%s:%s' % (self._instance_ip, remote)
83    ]
84    status, _ = cmd_helper.GetCmdStatusAndOutput(cmd)
85    if status:
86      raise device_errors.AdbCommandFailedError(
87          cmd, 'File not reachable on host: %s' % local,
88          device_serial=str(self))
89
90  # override
91  def Pull(self, remote, local, **kwargs):
92    """Pulls a file from the gce instance to the host.
93
94    Args:
95      remote: Path on the instance filesystem.
96      local: Path on the host filesystem.
97    """
98    cmd = [
99        'scp',
100        '-p',
101        '-r',
102        '-o', 'UserKnownHostsFile=/dev/null',
103        '-o', 'StrictHostKeyChecking=no',
104        'root@%s:%s' % (self._instance_ip, remote),
105        local,
106    ]
107    status, _ = cmd_helper.GetCmdStatusAndOutput(cmd)
108    if status:
109      raise device_errors.AdbCommandFailedError(
110          cmd, 'File not reachable on host: %s' % local,
111          device_serial=str(self))
112
113    try:
114      adb_wrapper.VerifyLocalFileExists(local)
115    except (subprocess.CalledProcessError, IOError):
116      logging.exception('Error when pulling files from android instance.')
117      raise device_errors.AdbCommandFailedError(
118          cmd, 'File not reachable on host: %s' % local,
119          device_serial=str(self))
120
121  # override
122  def Install(self, apk_path, forward_lock=False, reinstall=False,
123              sd_card=False, **kwargs):
124    """Installs an apk on the gce instance
125
126    Args:
127      apk_path: Host path to the APK file.
128      forward_lock: (optional) If set forward-locks the app.
129      reinstall: (optional) If set reinstalls the app, keeping its data.
130      sd_card: (optional) If set installs on the SD card.
131    """
132    adb_wrapper.VerifyLocalFileExists(apk_path)
133    cmd = ['install']
134    if forward_lock:
135      cmd.append('-l')
136    if reinstall:
137      cmd.append('-r')
138    if sd_card:
139      cmd.append('-s')
140    self.Push(apk_path, '/data/local/tmp/tmp.apk')
141    cmd = ['pm'] + cmd
142    cmd.append('/data/local/tmp/tmp.apk')
143    output = self.Shell(' '.join(cmd))
144    self.Shell('rm /data/local/tmp/tmp.apk')
145    if 'Success' not in output:
146      raise device_errors.AdbCommandFailedError(
147          cmd, output, device_serial=self._device_serial)
148
149  # override
150  @property
151  def is_emulator(self):
152    return True
153