initial commit of acloud for open sourcing
The Cloud Android Driver Binaries (namely, acloud) in this project
provide the standard APIs to access and control Cloud Android devices
(i.e., Android Virtual Devices on Google Compute Engine) instantiated
by using the Android source code (e.g., device/google/gce* projects).
No code change required in the initial commit which is to track
all the changes submitted after the initial commit.
Unit tests are not part of this initial commit and thus will be
submitted as the second commit due to their current dependencies
Test: no build rule defined for python yet
Change-Id: Ib6aaadf33fa110f4532ba2d5b7be91e8ddc632a9
diff --git a/public/acloud_kernel/kernel_swapper.py b/public/acloud_kernel/kernel_swapper.py
new file mode 100755
index 0000000..ca48c26
--- /dev/null
+++ b/public/acloud_kernel/kernel_swapper.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python
+#
+# Copyright 2016 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Kernel Swapper.
+
+This class manages swapping kernel images for a Cloud Android instance.
+"""
+import os
+import subprocess
+
+from acloud.public import errors
+from acloud.public import report
+from acloud.internal.lib import android_build_client
+from acloud.internal.lib import android_compute_client
+from acloud.internal.lib import auth
+from acloud.internal.lib import gstorage_client
+from acloud.internal.lib import utils
+
+ALL_SCOPES = ' '.join([android_build_client.AndroidBuildClient.SCOPE,
+ gstorage_client.StorageClient.SCOPE,
+ android_compute_client.AndroidComputeClient.SCOPE])
+
+# ssh flags used to communicate with the Cloud Android instance.
+SSH_FLAGS = [
+ '-q', '-o UserKnownHostsFile=/dev/null', '-o "StrictHostKeyChecking no"',
+ '-o ServerAliveInterval=10'
+]
+
+# Shell commands run on target.
+MOUNT_CMD = ('if mountpoint -q /boot ; then umount /boot ; fi ; '
+ 'mount -t ext4 /dev/block/sda1 /boot')
+REBOOT_CMD = 'nohup reboot > /dev/null 2>&1 &'
+
+
+class KernelSwapper(object):
+ """A class that manages swapping a kernel image on a Cloud Android instance.
+
+ Attributes:
+ _compute_client: AndroidCopmuteClient object, manages AVD.
+ _instance_name: string, name of Cloud Android Instance.
+ _target_ip: string, IP address of Cloud Android instance.
+ _ssh_flags: string list, flags to be used with ssh and scp.
+ """
+
+ def __init__(self, cfg, instance_name):
+ """Initialize.
+
+ Args:
+ cfg: AcloudConfig object, used to create credentials.
+ instance_name: string, instance name.
+ """
+ credentials = auth.CreateCredentials(cfg, ALL_SCOPES)
+ self._compute_client = android_compute_client.AndroidComputeClient(
+ cfg, credentials)
+ # Name of the Cloud Android instance.
+ self._instance_name = instance_name
+ # IP of the Cloud Android instance.
+ self._target_ip = self._compute_client.GetInstanceIP(instance_name)
+
+ def SwapKernel(self, local_kernel_image):
+ """Swaps the kernel image on target AVD with given kernel.
+
+ Mounts boot image containing the kernel image to the filesystem, then
+ overwrites that kernel image with a new kernel image, then reboots the
+ Cloud Android instance.
+
+ Args:
+ local_kernel_image: string, local path to a kernel image.
+
+ Returns:
+ A Report instance.
+ """
+ r = report.Report(command='swap_kernel')
+ try:
+ self._ShellCmdOnTarget(MOUNT_CMD)
+ self.PushFile(local_kernel_image, '/boot')
+ self.RebootTarget()
+ except subprocess.CalledProcessError as e:
+ r.AddError(str(e))
+ r.SetStatus(report.Status.FAIL)
+ return r
+ except errors.DeviceBootTimeoutError as e:
+ r.AddError(str(e))
+ r.SetStatus(report.Status.BOOT_FAIL)
+ return r
+
+ r.SetStatus(report.Status.SUCCESS)
+ return r
+
+ def PushFile(self, src_path, dest_path):
+ """Pushes local file to target Cloud Android instance.
+
+ Args:
+ src_path: string, local path to file to be pushed.
+ dest_path: string, path on target where to push the file to.
+
+ Raises:
+ subprocess.CalledProcessError: see _ShellCmd.
+ """
+ cmd = 'scp %s %s root@%s:%s' % (' '.join(SSH_FLAGS), src_path,
+ self._target_ip, dest_path)
+ self._ShellCmd(cmd)
+
+ def RebootTarget(self):
+ """Reboots the target Cloud Android instance and waits for boot.
+
+ Raises:
+ subprocess.CalledProcessError: see _ShellCmd.
+ errors.DeviceBootTimeoutError: if booting times out.
+ """
+ self._ShellCmdOnTarget(REBOOT_CMD)
+ self._compute_client.WaitForBoot(self._instance_name)
+
+ def _ShellCmdOnTarget(self, target_cmd):
+ """Runs a shell command on target Cloud Android instance.
+
+ Args:
+ target_cmd: string, shell command to be run on target.
+
+ Raises:
+ subprocess.CalledProcessError: see _ShellCmd.
+ """
+ ssh_cmd = 'ssh %s root@%s' % (' '.join(SSH_FLAGS), self._target_ip)
+ host_cmd = ' '.join([ssh_cmd, '"%s"' % target_cmd])
+ self._ShellCmd(host_cmd)
+
+ def _ShellCmd(self, host_cmd):
+ """Runs a shell command on host device.
+
+ Args:
+ host_cmd: string, shell command to be run on host.
+
+ Raises:
+ subprocess.CalledProcessError: For any non-zero return code of
+ host_cmd.
+ """
+ utils.GenericRetry(
+ handler=lambda e: isinstance(e, subprocess.CalledProcessError),
+ max_retry=2,
+ functor=lambda cmd: subprocess.check_call(cmd, shell=True),
+ cmd=host_cmd)