blob: d30eb9e67c83577d18ee5173c58d5e6fcd9a45e0 [file] [log] [blame]
Keun Soo Yimb293fdb2016-09-21 16:03:44 -07001#!/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 """
Fang Dengf24be082018-02-10 10:09:55 -0800150 utils.Retry(
151 retry_checker=lambda e: isinstance(e, subprocess.CalledProcessError),
152 max_retries=2,
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700153 functor=lambda cmd: subprocess.check_call(cmd, shell=True),
154 cmd=host_cmd)