Home | History | Annotate | Download | only in acloud_kernel
      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 
     19 This class manages swapping kernel images for a Cloud Android instance.
     20 """
     21 import os
     22 import subprocess
     23 
     24 from acloud.public import errors
     25 from acloud.public import report
     26 from acloud.internal.lib import android_build_client
     27 from acloud.internal.lib import android_compute_client
     28 from acloud.internal.lib import auth
     29 from acloud.internal.lib import gstorage_client
     30 from acloud.internal.lib import utils
     31 
     32 ALL_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.
     37 SSH_FLAGS = [
     38     '-q', '-o UserKnownHostsFile=/dev/null', '-o "StrictHostKeyChecking no"',
     39     '-o ServerAliveInterval=10'
     40 ]
     41 
     42 # Shell commands run on target.
     43 MOUNT_CMD = ('if mountpoint -q /boot ; then umount /boot ; fi ; '
     44              'mount -t ext4 /dev/block/sda1 /boot')
     45 REBOOT_CMD = 'nohup reboot > /dev/null 2>&1 &'
     46 
     47 
     48 class 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