• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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