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