1#!/usr/bin/env python 2# 3# Copyright 2016 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16"""Kernel Swapper. 17 18This class manages swapping kernel images for a Cloud Android instance. 19""" 20import subprocess 21 22from acloud import errors 23from acloud.public import report 24from acloud.internal.lib import android_compute_client 25from acloud.internal.lib import auth 26from acloud.internal.lib import utils 27 28# ssh flags used to communicate with the Cloud Android instance. 29SSH_FLAGS = [ 30 '-q', '-o UserKnownHostsFile=/dev/null', '-o "StrictHostKeyChecking no"', 31 '-o ServerAliveInterval=10' 32] 33 34# Shell commands run on target. 35MOUNT_CMD = ('if mountpoint -q /boot ; then umount /boot ; fi ; ' 36 'mount -t ext4 /dev/block/sda1 /boot') 37REBOOT_CMD = 'nohup reboot > /dev/null 2>&1 &' 38 39 40class KernelSwapper(object): 41 """A class that manages swapping a kernel image on a Cloud Android instance. 42 43 Attributes: 44 _compute_client: AndroidCopmuteClient object, manages AVD. 45 _instance_name: tring, name of Cloud Android Instance. 46 _target_ip: string, IP address of Cloud Android instance. 47 _ssh_flags: string list, flags to be used with ssh and scp. 48 """ 49 50 def __init__(self, cfg, instance_name): 51 """Initialize. 52 53 Args: 54 cfg: AcloudConfig object, used to create credentials. 55 instance_name: string, instance name. 56 """ 57 credentials = auth.CreateCredentials(cfg) 58 self._compute_client = android_compute_client.AndroidComputeClient( 59 cfg, credentials) 60 # Name of the Cloud Android instance. 61 self._instance_name = instance_name 62 # IP of the Cloud Android instance. 63 self._target_ip = self._compute_client.GetInstanceIP(instance_name) 64 65 def SwapKernel(self, local_kernel_image): 66 """Swaps the kernel image on target AVD with given kernel. 67 68 Mounts boot image containing the kernel image to the filesystem, then 69 overwrites that kernel image with a new kernel image, then reboots the 70 Cloud Android instance. 71 72 Args: 73 local_kernel_image: string, local path to a kernel image. 74 75 Returns: 76 A Report instance. 77 """ 78 reboot_image = report.Report(command='swap_kernel') 79 try: 80 self._ShellCmdOnTarget(MOUNT_CMD) 81 self.PushFile(local_kernel_image, '/boot') 82 self.RebootTarget() 83 except subprocess.CalledProcessError as e: 84 reboot_image.AddError(str(e)) 85 reboot_image.SetStatus(report.Status.FAIL) 86 return reboot_image 87 except errors.DeviceBootError as e: 88 reboot_image.AddError(str(e)) 89 reboot_image.SetStatus(report.Status.BOOT_FAIL) 90 return reboot_image 91 92 reboot_image.SetStatus(report.Status.SUCCESS) 93 return reboot_image 94 95 def PushFile(self, src_path, dest_path): 96 """Pushes local file to target Cloud Android instance. 97 98 Args: 99 src_path: string, local path to file to be pushed. 100 dest_path: string, path on target where to push the file to. 101 102 Raises: 103 subprocess.CalledProcessError: see _ShellCmd. 104 """ 105 cmd = 'scp %s %s root@%s:%s' % (' '.join(SSH_FLAGS), src_path, 106 self._target_ip, dest_path) 107 self._ShellCmd(cmd) 108 109 def RebootTarget(self): 110 """Reboots the target Cloud Android instance and waits for boot. 111 112 Raises: 113 subprocess.CalledProcessError: see _ShellCmd. 114 errors.DeviceBootError: if target fails to boot. 115 """ 116 self._ShellCmdOnTarget(REBOOT_CMD) 117 self._compute_client.WaitForBoot(self._instance_name) 118 119 def _ShellCmdOnTarget(self, target_cmd): 120 """Runs a shell command on target Cloud Android instance. 121 122 Args: 123 target_cmd: string, shell command to be run on target. 124 125 Raises: 126 subprocess.CalledProcessError: see _ShellCmd. 127 """ 128 ssh_cmd = 'ssh %s root@%s' % (' '.join(SSH_FLAGS), self._target_ip) 129 host_cmd = ' '.join([ssh_cmd, '"%s"' % target_cmd]) 130 self._ShellCmd(host_cmd) 131 132 @staticmethod 133 def _ShellCmd(host_cmd): 134 """Runs a shell command on host device. 135 136 Args: 137 host_cmd: string, shell command to be run on host. 138 139 Raises: 140 subprocess.CalledProcessError: For any non-zero return code of 141 host_cmd. 142 """ 143 utils.Retry( 144 retry_checker=lambda e: isinstance(e, subprocess.CalledProcessError), 145 max_retries=2, 146 functor=lambda cmd: subprocess.check_call(cmd, shell=True), 147 sleep_multiplier=0, 148 retry_backoff_factor=1, 149 cmd=host_cmd) 150