diff mbox

[Branch,~linaro-validation/lava-dispatcher/trunk] Rev 543: Galaxy Nexus support.

Message ID 20130123175516.24917.68358.launchpad@ackee.canonical.com
State Accepted
Headers show

Commit Message

Antonio Terceiro Jan. 23, 2013, 5:55 p.m. UTC
Merge authors:
  Antonio Terceiro (terceiro)
Related merge proposals:
  https://code.launchpad.net/~terceiro/lava-dispatcher/nexus/+merge/144204
  proposed by: Antonio Terceiro (terceiro)
  review: Approve - Michael Hudson-Doyle (mwhudson)
------------------------------------------------------------
revno: 543 [merge]
committer: Antonio Terceiro <antonio.terceiro@linaro.org>
branch nick: trunk
timestamp: Wed 2013-01-23 14:53:29 -0300
message:
  Galaxy Nexus support.
  
  This merge introduces support for the Galaxy Nexus, using fastboot for image
  flashing and adb for interaction with Android.
added:
  lava_dispatcher/default-config/lava-dispatcher/device-types/nexus.conf
  lava_dispatcher/device/nexus.py
modified:
  lava_dispatcher/config.py


--
lp:lava-dispatcher
https://code.launchpad.net/~linaro-validation/lava-dispatcher/trunk

You are subscribed to branch lp:lava-dispatcher.
To unsubscribe from this branch go to https://code.launchpad.net/~linaro-validation/lava-dispatcher/trunk/+edit-subscription
diff mbox

Patch

=== modified file 'lava_dispatcher/config.py'
--- lava_dispatcher/config.py	2013-01-18 11:08:24 +0000
+++ lava_dispatcher/config.py	2013-01-21 13:11:47 +0000
@@ -92,6 +92,9 @@ 
     arm_probe_config = schema.StringOption(default='/usr/local/etc/arm-probe-config')
     arm_probe_channels = schema.ListOption(default=['VDD_VCORE1'])
 
+    adb_command = schema.StringOption()
+    fastboot_command = schema.StringOption()
+    nexus_working_directory = schema.StringOption(default=None)
 
 class OptionDescriptor(object):
     def __init__(self, name):

=== added file 'lava_dispatcher/default-config/lava-dispatcher/device-types/nexus.conf'
--- lava_dispatcher/default-config/lava-dispatcher/device-types/nexus.conf	1970-01-01 00:00:00 +0000
+++ lava_dispatcher/default-config/lava-dispatcher/device-types/nexus.conf	2013-01-22 01:58:09 +0000
@@ -0,0 +1,46 @@ 
+client_type = nexus
+
+# The ADB command line.
+#
+# In the case where there are multiple android devices plugged into a
+# single host, this connection command must be overriden on each device to
+# include the serial number of the device, e.g.
+#
+#   serial_number = XXXXXXXXXXXXXXXX
+#   adb_command = adb -s %(serial_number)s
+adb_command = adb
+
+# The fastboot command.
+#
+# The same as above: if you have more than one device, you will want to
+# override this in your device config to add a serial number, e.g.
+#
+#   serial_number = XXXXXXXXXXXXXXXX
+#   fastboot_command = fastboot -s %(serial_number)s
+#
+# Of course, in the case you override both adb_command *and* fastboot_command,
+# you don't need to specify `serial_number` twice.
+fastboot_command = fastboot
+
+# Working directory for temporary files. By default, the usual place for LAVA
+# images will be used.
+#
+# This is useful when the lava dispatcher is controlling Nexus phones that are
+# physically plugged to other machines by setting adb_command to something like
+# "ssh <phone-host> adb" and fastboot_command to something like "ssh
+# <phone-host> fastboot". adb and fastboot always operate on local files, so
+# you need your local files to also be seen as local files on the host where
+# adb/fastboot are executed.
+#
+# In this case, you should set nexus_work_directory to a shared directory
+# between the machine running the dispatcher and the machine where the phone is
+# plugged.  This shared directory must have the same path in both machines.
+# For example, you can have your /var/tmp/lava mounted at /var/tmp/lava at
+# <phone-host> (or the other way around).
+nexus_working_directory =
+
+connection_command = %(adb_command)s shell
+
+enable_network_after_boot_android = false
+android_adb_over_usb = true
+android_adb_over_tcp = false

=== added file 'lava_dispatcher/device/nexus.py'
--- lava_dispatcher/device/nexus.py	1970-01-01 00:00:00 +0000
+++ lava_dispatcher/device/nexus.py	2013-01-22 01:45:32 +0000
@@ -0,0 +1,193 @@ 
+# Copyright (C) 2012 Linaro Limited
+#
+# Author: Antonio Terceiro <antonio.terceiro@linaro.org>
+#
+# This file is part of LAVA Dispatcher.
+#
+# LAVA Dispatcher is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# LAVA Dispatcher is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along
+# with this program; if not, see <http://www.gnu.org/licenses>.
+
+import subprocess
+import pexpect
+from time import sleep
+import logging
+import contextlib
+
+from lava_dispatcher.device.target import (
+    Target
+)
+from lava_dispatcher.downloader import (
+    download_image
+)
+from lava_dispatcher.utils import (
+    logging_system,
+    logging_spawn,
+    mkdtemp
+)
+from lava_dispatcher.errors import (
+    CriticalError
+)
+
+class NexusTarget(Target):
+
+    def __init__(self, context, config):
+        super(NexusTarget, self).__init__(context, config)
+
+        if not config.hard_reset_command:
+            logging.warn(
+                "Setting the hard_reset_command config option "
+                "is highly recommended!"
+            )
+
+        self._booted = False
+        self._working_dir = None
+
+    def deploy_android(self, boot, system, userdata):
+
+        boot = self._get_image(boot)
+        system = self._get_image(system)
+        userdata = self._get_image(userdata)
+
+        self._enter_fastboot()
+        self._fastboot('erase boot')
+        self._fastboot('flash system %s' % system)
+        self._fastboot('flash userdata %s' % userdata)
+
+        self.deployment_data = Target.android_deployment_data
+        self.deployment_data['boot_image'] = boot
+
+    def power_on(self):
+        if not self.deployment_data.get('boot_image', False):
+            raise CriticalError('Deploy action must be run first')
+
+        self._enter_fastboot()
+        self._boot_test_image()
+
+        self._booted = True
+        proc = self._adb('shell', spawn = True)
+        proc.sendline("") # required to put the adb shell in a reasonable state
+        proc.sendline("export PS1='%s'" % self.deployment_data['TESTER_PS1'])
+        self._runner = self._get_runner(proc)
+
+        return proc
+
+    def power_off(self, proc):
+        # We always leave the device on
+        pass
+
+    @contextlib.contextmanager
+    def file_system(self, partition, directory):
+
+        if not self._booted:
+            self.power_on()
+
+        mount_point = self._get_partition_mount_point(partition)
+
+        host_dir = '%s/mnt/%s' % (self.working_dir, directory)
+        target_dir = '%s/%s' % (mount_point, directory)
+
+        subprocess.check_call(['mkdir', '-p', host_dir])
+        self._adb('pull %s %s' % (target_dir, host_dir), ignore_failure = True)
+
+        yield host_dir
+
+        self._adb('push %s %s' % (host_dir, target_dir))
+
+    def get_device_version(self):
+        # this is tricky, because fastboot does not have a visible version
+        # number. For now let's use just the adb version number.
+        return subprocess.check_output(
+            "%s version | sed 's/.* version //'" % self.config.adb_command,
+            shell = True
+        ).strip()
+
+    # start of private methods
+
+    def _enter_fastboot(self):
+        if self._already_on_fastboot():
+            logging.debug("Device is on fastboot - no need to hard reset")
+            return
+        try:
+            # First we try a gentle reset
+            self._adb('reboot')
+        except subprocess.CalledProcessError:
+            # Now a more brute force attempt. In this case the device is
+            # probably hung.
+            if self.config.hard_reset_command:
+                logging.debug("Will hard reset the device")
+                logging_system(self.config.hard_reset_command)
+            else:
+                logging.critical(
+                    "Hard reset command not configured. "
+                    "Please reset the device manually."
+                )
+
+    def _already_on_fastboot(self):
+        try:
+            self._fastboot('getvar all', timeout = 2)
+            return True
+        except subprocess.CalledProcessError:
+            return False
+
+    def _boot_test_image(self):
+        # We need an extra bootloader reboot before actually booting the image
+        # to avoid the phone entering charging mode and getting stuck.
+        self._fastboot('reboot')
+        # specifically after `fastboot reset`, we have to wait a little
+        sleep(10)
+        self._fastboot('boot %s' % self.deployment_data['boot_image'])
+        self._adb('wait-for-device')
+
+    def _get_partition_mount_point(self, partition):
+        lookup = {
+            self.config.data_part_android_org: '/data',
+            self.config.sys_part_android_org: '/system',
+        }
+        return lookup[partition]
+
+    def _adb(self, args, ignore_failure = False, spawn = False, timeout = 600):
+        cmd = self.config.adb_command + ' ' + args
+        if spawn:
+            return logging_spawn(cmd, timeout = 60)
+        else:
+            self._call(cmd, ignore_failure, timeout)
+
+    def _fastboot(self, args, ignore_failure = False, timeout = 600):
+        self._call(self.config.fastboot_command + ' ' + args, ignore_failure, timeout)
+
+    def _call(self, cmd, ignore_failure, timeout):
+        cmd = 'timeout ' + str(timeout) + 's ' + cmd
+        logging.debug("Running on the host: %s" % cmd)
+        if ignore_failure:
+            subprocess.call(cmd, shell = True)
+        else:
+            subprocess.check_call(cmd, shell = True)
+
+    def _get_image(self, url):
+        sdir = self.working_dir
+        image = download_image(url, self.context, sdir, decompress=False)
+        return image
+
+    @property
+    def working_dir(self):
+        if (self.config.nexus_working_directory is None or
+            self.config.nexus_working_directory.strip() == ''):
+            return self.scratch_dir
+
+        if self._working_dir is None:
+            self._working_dir = mkdtemp(self.config.nexus_working_directory)
+        return self._working_dir
+
+
+target_class = NexusTarget