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 17"""Kernel Swapper. 18 19This class manages swapping kernel images for a Cloud Android instance. 20""" 21import os 22import subprocess 23 24from acloud.public import errors 25from acloud.public import report 26from acloud.internal.lib import android_build_client 27from acloud.internal.lib import android_compute_client 28from acloud.internal.lib import auth 29from acloud.internal.lib import gstorage_client 30from acloud.internal.lib import utils 31 32ALL_SCOPES = ' '.join([android_build_client.AndroidBuildClient.SCOPE, 33 gstorage_client.StorageClient.SCOPE, 34 android_compute_client.AndroidComputeClient.SCOPE]) 35 36# ssh flags used to communicate with the Cloud Android instance. 37SSH_FLAGS = [ 38 '-q', '-o UserKnownHostsFile=/dev/null', '-o "StrictHostKeyChecking no"', 39 '-o ServerAliveInterval=10' 40] 41 42# Shell commands run on target. 43MOUNT_CMD = ('if mountpoint -q /boot ; then umount /boot ; fi ; ' 44 'mount -t ext4 /dev/block/sda1 /boot') 45REBOOT_CMD = 'nohup reboot > /dev/null 2>&1 &' 46 47 48class KernelSwapper(object): 49 """A class that manages swapping a kernel image on a Cloud Android instance. 50 51 Attributes: 52 _compute_client: AndroidCopmuteClient object, manages AVD. 53 _instance_name: string, name of Cloud Android Instance. 54 _target_ip: string, IP address of Cloud Android instance. 55 _ssh_flags: string list, flags to be used with ssh and scp. 56 """ 57 58 def __init__(self, cfg, instance_name): 59 """Initialize. 60 61 Args: 62 cfg: AcloudConfig object, used to create credentials. 63 instance_name: string, instance name. 64 """ 65 credentials = auth.CreateCredentials(cfg, ALL_SCOPES) 66 self._compute_client = android_compute_client.AndroidComputeClient( 67 cfg, credentials) 68 # Name of the Cloud Android instance. 69 self._instance_name = instance_name 70 # IP of the Cloud Android instance. 71 self._target_ip = self._compute_client.GetInstanceIP(instance_name) 72 73 def SwapKernel(self, local_kernel_image): 74 """Swaps the kernel image on target AVD with given kernel. 75 76 Mounts boot image containing the kernel image to the filesystem, then 77 overwrites that kernel image with a new kernel image, then reboots the 78 Cloud Android instance. 79 80 Args: 81 local_kernel_image: string, local path to a kernel image. 82 83 Returns: 84 A Report instance. 85 """ 86 r = report.Report(command='swap_kernel') 87 try: 88 self._ShellCmdOnTarget(MOUNT_CMD) 89 self.PushFile(local_kernel_image, '/boot') 90 self.RebootTarget() 91 except subprocess.CalledProcessError as e: 92 r.AddError(str(e)) 93 r.SetStatus(report.Status.FAIL) 94 return r 95 except errors.DeviceBootTimeoutError as e: 96 r.AddError(str(e)) 97 r.SetStatus(report.Status.BOOT_FAIL) 98 return r 99 100 r.SetStatus(report.Status.SUCCESS) 101 return r 102 103 def PushFile(self, src_path, dest_path): 104 """Pushes local file to target Cloud Android instance. 105 106 Args: 107 src_path: string, local path to file to be pushed. 108 dest_path: string, path on target where to push the file to. 109 110 Raises: 111 subprocess.CalledProcessError: see _ShellCmd. 112 """ 113 cmd = 'scp %s %s root@%s:%s' % (' '.join(SSH_FLAGS), src_path, 114 self._target_ip, dest_path) 115 self._ShellCmd(cmd) 116 117 def RebootTarget(self): 118 """Reboots the target Cloud Android instance and waits for boot. 119 120 Raises: 121 subprocess.CalledProcessError: see _ShellCmd. 122 errors.DeviceBootTimeoutError: if booting times out. 123 """ 124 self._ShellCmdOnTarget(REBOOT_CMD) 125 self._compute_client.WaitForBoot(self._instance_name) 126 127 def _ShellCmdOnTarget(self, target_cmd): 128 """Runs a shell command on target Cloud Android instance. 129 130 Args: 131 target_cmd: string, shell command to be run on target. 132 133 Raises: 134 subprocess.CalledProcessError: see _ShellCmd. 135 """ 136 ssh_cmd = 'ssh %s root@%s' % (' '.join(SSH_FLAGS), self._target_ip) 137 host_cmd = ' '.join([ssh_cmd, '"%s"' % target_cmd]) 138 self._ShellCmd(host_cmd) 139 140 def _ShellCmd(self, host_cmd): 141 """Runs a shell command on host device. 142 143 Args: 144 host_cmd: string, shell command to be run on host. 145 146 Raises: 147 subprocess.CalledProcessError: For any non-zero return code of 148 host_cmd. 149 """ 150 utils.Retry( 151 retry_checker=lambda e: isinstance(e, subprocess.CalledProcessError), 152 max_retries=2, 153 functor=lambda cmd: subprocess.check_call(cmd, shell=True), 154 cmd=host_cmd) 155