blob: ff82ab8a8c0126fcf582a0bbf24f5835e6f0190f [file] [log] [blame]
Kevin Cheng3ce4b452018-08-23 14:47:22 -07001#!/usr/bin/env python
2#
3# Copyright 2018 - 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.
16r"""LocalImageLocalInstance class.
17
18Create class that is responsible for creating a local instance AVD with a
19local image.
20"""
21
Sam Chiuafbc6582018-09-04 20:47:13 +080022from __future__ import print_function
23import logging
24import os
25import subprocess
26import time
27
28from acloud import errors
Kevin Cheng3ce4b452018-08-23 14:47:22 -070029from acloud.create import base_avd_create
Sam Chiuafbc6582018-09-04 20:47:13 +080030from acloud.create import create_common
31from acloud.internal import constants
32from acloud.internal.lib import utils
33from acloud.setup import host_setup_runner
34
35logger = logging.getLogger(__name__)
36
37_BOOT_COMPLETE = "VIRTUAL_DEVICE_BOOT_COMPLETED"
38_CMD_LAUNCH_CVD = "launch_cvd"
39# TODO(b/117366819): Currently --serial_number is not working.
40_CMD_LAUNCH_CVD_ARGS = (" --daemon --cpus %s --x_res %s --y_res %s --dpi %s "
41 "--memory_mb %s --blank_data_image_mb %s "
42 "--data_policy always_create "
43 "--system_image_dir %s "
44 "--vnc_server_port %s "
45 "--serial_number %s")
46_CMD_PGREP = "pgrep"
47_CMD_SG = "sg "
48_CMD_STOP_CVD = "stop_cvd"
49_CONFIRM_RELAUNCH = ("\nCuttlefish AVD is already running. \nPress 'y' to "
50 "terminate current instance and launch new instance \nor "
51 "anything else to exit out.")
52_CVD_SERIAL_PREFIX = "acloudCF"
53_ENV_ANDROID_HOST_OUT = "ANDROID_HOST_OUT"
Kevin Cheng3ce4b452018-08-23 14:47:22 -070054
55
56class LocalImageLocalInstance(base_avd_create.BaseAVDCreate):
57 """Create class for a local image local instance AVD."""
58
Kevin Cheng3ce4b452018-08-23 14:47:22 -070059 def Create(self, avd_spec):
60 """Create the AVD.
61
62 Args:
63 avd_spec: AVDSpec object that tells us what we're going to create.
64 """
Sam Chiuafbc6582018-09-04 20:47:13 +080065 self.PrintAvdDetails(avd_spec)
66 start = time.time()
67
68 local_image_path, launch_cvd_path = self.GetImageArtifactsPath(avd_spec)
69
70 cmd = self.PrepareLaunchCVDCmd(launch_cvd_path,
71 avd_spec.hw_property,
72 local_image_path,
73 avd_spec.flavor)
74 try:
75 self.CheckLaunchCVD(cmd)
76 except errors.LaunchCVDFail as launch_error:
77 raise launch_error
78
79 utils.PrintColorString("\n")
80 utils.PrintColorString("Total time: %ds" % (time.time() - start),
81 utils.TextColors.WARNING)
82 # TODO(b/117366819): Should display the correct device serial
83 # according to the args --serial_number.
84 utils.PrintColorString("Device serial: 127.0.0.1:6520",
85 utils.TextColors.WARNING)
86 if avd_spec.autoconnect:
87 self.LaunchVncClient()
88
89
90 @staticmethod
91 def GetImageArtifactsPath(avd_spec):
92 """Get image artifacts path.
93
94 This method will check if local image and launch_cvd are exist and
95 return the tuple path where they are located respectively.
96 For remote image, RemoteImageLocalInstance will override this method and
97 return the artifacts path which is extracted and downloaded from remote.
98
99 Args:
100 avd_spec: AVDSpec object that tells us what we're going to create.
101
102 Returns:
103 Tuple of (local image file, launch_cvd package) paths.
104 """
105 try:
106 # Check if local image is exist.
107 create_common.VerifyLocalImageArtifactsExist(
108 avd_spec.local_image_dir)
109
110 # TODO(b/117306227): help user to build out images and host package if
111 # anything needed is not found.
112 except errors.GetLocalImageError as imgerror:
113 logger.error(imgerror.message)
114 raise imgerror
115
116 # Check if launch_cvd is exist.
117 launch_cvd_path = os.path.join(
118 os.environ.get(_ENV_ANDROID_HOST_OUT), "bin", _CMD_LAUNCH_CVD)
119 if not os.path.exists(launch_cvd_path):
120 raise errors.GetCvdLocalHostPackageError(
121 "No launch_cvd found. Please run \"m launch_cvd\" first")
122
123 return avd_spec.local_image_dir, launch_cvd_path
124
125 @staticmethod
126 def PrepareLaunchCVDCmd(launch_cvd_path, hw_property, system_image_dir,
127 flavor):
128 """Prepare launch_cvd command.
129
130 Combine whole launch_cvd cmd including the hw property options and login
131 as the required groups if need. The reason using here-doc instead of
132 ampersand sign is all operations need to be ran in ths same pid.
133 The example of cmd:
134 $ sg kvm << EOF
135 sg libvirt
136 sg cvdnetwork
137 launch_cvd --cpus 2 --x_res 1280 --y_res 720 --dpi 160 --memory_mb 4096
138 EOF
139
140 Args:
141 launch_cvd_path: String of launch_cvd path.
142 hw_property: dict object of hw property.
143 system_image_dir: String of local images path.
144 flavor: String of flavor type.
145
146 Returns:
147 String, launch_cvd cmd.
148 """
149 launch_cvd_w_args = launch_cvd_path + _CMD_LAUNCH_CVD_ARGS % (
150 hw_property["cpu"], hw_property["x_res"], hw_property["y_res"],
151 hw_property["dpi"], hw_property["memory"], hw_property["disk"],
cylan66713722018-10-06 01:38:26 +0800152 system_image_dir, constants.DEFAULT_VNC_PORT,
153 _CVD_SERIAL_PREFIX+flavor)
Sam Chiuafbc6582018-09-04 20:47:13 +0800154
155 combined_launch_cmd = ""
156 host_setup = host_setup_runner.CuttlefishHostSetup()
157 if not host_setup.CheckUserInGroups(constants.LIST_CF_USER_GROUPS):
158 # As part of local host setup to enable local instance support,
159 # the user is added to certain groups. For those settings to
160 # take effect systemwide requires the user to log out and
161 # log back in. In the scenario where the user has run setup and
162 # hasn't logged out, we still want them to be able to launch a
163 # local instance so add the user to the groups as part of the
164 # command to ensure success.
165 logger.debug("User group is not ready for cuttlefish")
166 for idx, group in enumerate(constants.LIST_CF_USER_GROUPS):
167 combined_launch_cmd += _CMD_SG + group
168 if idx == 0:
169 combined_launch_cmd += " <<EOF\n"
170 else:
171 combined_launch_cmd += "\n"
172 launch_cvd_w_args += "\nEOF"
173
174 combined_launch_cmd += launch_cvd_w_args
175 logger.debug("launch_cvd cmd:\n %s", combined_launch_cmd)
176 return combined_launch_cmd
177
178 def CheckLaunchCVD(self, cmd):
179 """Execute launch_cvd command and wait for boot up completed.
180
181 Args:
182 cmd: String, launch_cvd command.
183 """
184 start = time.time()
185
186 # Cuttlefish support launch single AVD at one time currently.
187 if self._IsLaunchCVDInUse():
188 logger.info("Cuttlefish AVD is already running.")
189 if utils.GetUserAnswerYes(_CONFIRM_RELAUNCH):
190 stop_cvd_cmd = os.path.join(os.environ.get(_ENV_ANDROID_HOST_OUT),
191 "bin", _CMD_STOP_CVD)
192 subprocess.check_output(stop_cvd_cmd)
193 else:
194 print("Only 1 cuttlefish AVD at a time, "
195 "please stop the current AVD via #acloud delete")
196 return
197
198 utils.PrintColorString("Waiting for AVD to boot... ",
199 utils.TextColors.WARNING, end="")
200
201 process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
202 stderr=subprocess.STDOUT)
203
204 boot_complete = False
205 for line in iter(process.stdout.readline, b''):
206 logger.debug(line.strip())
207 # cvd is still running and got boot complete.
208 if _BOOT_COMPLETE in line:
209 utils.PrintColorString("OK! (%ds)" % (time.time() - start),
210 utils.TextColors.OKGREEN)
211 boot_complete = True
212 break
213
214 if not boot_complete:
215 utils.PrintColorString("Fail!", utils.TextColors.WARNING)
216 raise errors.LaunchCVDFail(
217 "Can't launch cuttlefish AVD. No %s found" % _BOOT_COMPLETE)
218
219 @staticmethod
220 def _IsLaunchCVDInUse():
221 """Check if launch_cvd is running.
222
223 Returns:
224 Boolean, True if launch_cvd is running. False otherwise.
225 """
226 try:
227 subprocess.check_output([_CMD_PGREP, _CMD_LAUNCH_CVD])
228 return True
229 except subprocess.CalledProcessError:
230 # launch_cvd process is not in use.
231 return False