| #!/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 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: tring, 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. |
| """ |
| reboot_image = report.Report(command='swap_kernel') |
| try: |
| self._ShellCmdOnTarget(MOUNT_CMD) |
| self.PushFile(local_kernel_image, '/boot') |
| self.RebootTarget() |
| except subprocess.CalledProcessError as e: |
| reboot_image.AddError(str(e)) |
| reboot_image.SetStatus(report.Status.FAIL) |
| return reboot_image |
| except errors.DeviceBootError as e: |
| reboot_image.AddError(str(e)) |
| reboot_image.SetStatus(report.Status.BOOT_FAIL) |
| return reboot_image |
| |
| reboot_image.SetStatus(report.Status.SUCCESS) |
| return reboot_image |
| |
| 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.DeviceBootError: if target fails to boot. |
| """ |
| 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) |
| |
| @staticmethod |
| def _ShellCmd(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.Retry( |
| retry_checker= |
| lambda e: isinstance(e, subprocess.CalledProcessError), |
| max_retries=2, |
| functor=lambda cmd: subprocess.check_call(cmd, shell=True), |
| sleep_multiplier=0, |
| retry_backoff_factor=1, |
| cmd=host_cmd) |