• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #!/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.
16 r"""LocalImageLocalInstance class.
17 
18 Create class that is responsible for creating a local instance AVD with a
19 local image. For launching multiple local instances under the same user,
20 The cuttlefish tool requires 3 variables:
21 - ANDROID_HOST_OUT: To locate the launch_cvd tool.
22 - HOME: To specify the temporary folder of launch_cvd.
23 - CUTTLEFISH_INSTANCE: To specify the instance id.
24 Acloud user must either set ANDROID_HOST_OUT or run acloud with --local-tool.
25 Acloud sets the other 2 variables for each local instance.
26 
27 The adb port and vnc port of local instance will be decided according to
28 instance id. The rule of adb port will be '6520 + [instance id] - 1' and the vnc
29 port will be '6444 + [instance id] - 1'.
30 e.g:
31 If instance id = 3 the adb port will be 6522 and vnc port will be 6446.
32 
33 To delete the local instance, we will call stop_cvd with the environment variable
34 [CUTTLEFISH_CONFIG_FILE] which is pointing to the runtime cuttlefish json.
35 """
36 
37 import logging
38 import os
39 import shutil
40 import subprocess
41 import threading
42 import sys
43 
44 from acloud import errors
45 from acloud.create import base_avd_create
46 from acloud.internal import constants
47 from acloud.internal.lib import utils
48 from acloud.internal.lib.adb_tools import AdbTools
49 from acloud.list import list as list_instance
50 from acloud.list import instance
51 from acloud.public import report
52 
53 
54 logger = logging.getLogger(__name__)
55 
56 _CMD_LAUNCH_CVD_ARGS = (" -daemon -cpus %s -x_res %s -y_res %s -dpi %s "
57                         "-memory_mb %s -run_adb_connector=%s "
58                         "-system_image_dir %s -instance_dir %s")
59 _CMD_LAUNCH_CVD_DISK_ARGS = (" -blank_data_image_mb %s "
60                              "-data_policy always_create")
61 _CONFIRM_RELAUNCH = ("\nCuttlefish AVD[id:%d] is already running. \n"
62                      "Enter 'y' to terminate current instance and launch a new "
63                      "instance, enter anything else to exit out[y/N]: ")
64 _LAUNCH_CVD_TIMEOUT_SECS = 120  # default timeout as 120 seconds
65 _LAUNCH_CVD_TIMEOUT_ERROR = ("Cuttlefish AVD launch timeout, did not complete "
66                              "within %d secs.")
67 _VIRTUAL_DISK_PATHS = "virtual_disk_paths"
68 
69 
70 class LocalImageLocalInstance(base_avd_create.BaseAVDCreate):
71     """Create class for a local image local instance AVD."""
72 
73     @utils.TimeExecute(function_description="Total time: ",
74                        print_before_call=False, print_status=False)
75     def _CreateAVD(self, avd_spec, no_prompts):
76         """Create the AVD.
77 
78         Args:
79             avd_spec: AVDSpec object that tells us what we're going to create.
80             no_prompts: Boolean, True to skip all prompts.
81 
82         Raises:
83             errors.LaunchCVDFail: Launch AVD failed.
84 
85         Returns:
86             A Report instance.
87         """
88         # Running instances on local is not supported on all OS.
89         if not utils.IsSupportedPlatform(print_warning=True):
90             result_report = report.Report(command="create")
91             result_report.SetStatus(report.Status.FAIL)
92             return result_report
93 
94         self.PrintDisclaimer()
95         local_image_path, host_bins_path = self.GetImageArtifactsPath(avd_spec)
96 
97         launch_cvd_path = os.path.join(host_bins_path, "bin",
98                                        constants.CMD_LAUNCH_CVD)
99         cmd = self.PrepareLaunchCVDCmd(launch_cvd_path,
100                                        avd_spec.hw_property,
101                                        avd_spec.connect_adb,
102                                        local_image_path,
103                                        avd_spec.local_instance_id)
104 
105         result_report = report.Report(command="create")
106         instance_name = instance.GetLocalInstanceName(
107             avd_spec.local_instance_id)
108         try:
109             self.CheckLaunchCVD(
110                 cmd, host_bins_path, avd_spec.local_instance_id, local_image_path,
111                 no_prompts, avd_spec.boot_timeout_secs or _LAUNCH_CVD_TIMEOUT_SECS)
112         except errors.LaunchCVDFail as launch_error:
113             result_report.SetStatus(report.Status.BOOT_FAIL)
114             result_report.AddDeviceBootFailure(
115                 instance_name, constants.LOCALHOST, None, None,
116                 error=str(launch_error))
117             return result_report
118 
119         active_ins = list_instance.GetActiveCVD(avd_spec.local_instance_id)
120         if active_ins:
121             result_report.SetStatus(report.Status.SUCCESS)
122             result_report.AddDevice(instance_name, constants.LOCALHOST,
123                                     active_ins.adb_port, active_ins.vnc_port)
124             # Launch vnc client if we're auto-connecting.
125             if avd_spec.connect_vnc:
126                 utils.LaunchVNCFromReport(result_report, avd_spec, no_prompts)
127             if avd_spec.unlock_screen:
128                 AdbTools(active_ins.adb_port).AutoUnlockScreen()
129         else:
130             err_msg = "cvd_status return non-zero after launch_cvd"
131             logger.error(err_msg)
132             result_report.SetStatus(report.Status.BOOT_FAIL)
133             result_report.AddDeviceBootFailure(
134                 instance_name, constants.LOCALHOST, None, None, error=err_msg)
135 
136         return result_report
137 
138     @staticmethod
139     def _FindCvdHostBinaries(search_paths):
140         """Return the directory that contains CVD host binaries."""
141         for search_path in search_paths:
142             if os.path.isfile(os.path.join(search_path, "bin",
143                                            constants.CMD_LAUNCH_CVD)):
144                 return search_path
145 
146         host_out_dir = os.environ.get(constants.ENV_ANDROID_HOST_OUT)
147         if (host_out_dir and
148                 os.path.isfile(os.path.join(host_out_dir, "bin",
149                                             constants.CMD_LAUNCH_CVD))):
150             return host_out_dir
151 
152         raise errors.GetCvdLocalHostPackageError(
153             "CVD host binaries are not found. Please run `make hosttar`, or "
154             "set --local-tool to an extracted CVD host package.")
155 
156     def GetImageArtifactsPath(self, avd_spec):
157         """Get image artifacts path.
158 
159         This method will check if launch_cvd is exist and return the tuple path
160         (image path and host bins path) where they are located respectively.
161         For remote image, RemoteImageLocalInstance will override this method and
162         return the artifacts path which is extracted and downloaded from remote.
163 
164         Args:
165             avd_spec: AVDSpec object that tells us what we're going to create.
166 
167         Returns:
168             Tuple of (local image file, host bins package) paths.
169         """
170         return (avd_spec.local_image_dir,
171                 self._FindCvdHostBinaries(avd_spec.local_tool_dirs))
172 
173     @staticmethod
174     def PrepareLaunchCVDCmd(launch_cvd_path, hw_property, connect_adb,
175                             system_image_dir, local_instance_id):
176         """Prepare launch_cvd command.
177 
178         Create the launch_cvd commands with all the required args and add
179         in the user groups to it if necessary.
180 
181         Args:
182             launch_cvd_path: String of launch_cvd path.
183             hw_property: dict object of hw property.
184             system_image_dir: String of local images path.
185             connect_adb: Boolean flag that enables adb_connector.
186             local_instance_id: Integer of instance id.
187 
188         Returns:
189             String, launch_cvd cmd.
190         """
191         instance_dir = instance.GetLocalInstanceRuntimeDir(local_instance_id)
192         launch_cvd_w_args = launch_cvd_path + _CMD_LAUNCH_CVD_ARGS % (
193             hw_property["cpu"], hw_property["x_res"], hw_property["y_res"],
194             hw_property["dpi"], hw_property["memory"],
195             ("true" if connect_adb else "false"), system_image_dir,
196             instance_dir)
197         if constants.HW_ALIAS_DISK in hw_property:
198             launch_cvd_w_args = (launch_cvd_w_args + _CMD_LAUNCH_CVD_DISK_ARGS %
199                                  hw_property[constants.HW_ALIAS_DISK])
200 
201         launch_cmd = utils.AddUserGroupsToCmd(launch_cvd_w_args,
202                                               constants.LIST_CF_USER_GROUPS)
203         logger.debug("launch_cvd cmd:\n %s", launch_cmd)
204         return launch_cmd
205 
206     def CheckLaunchCVD(self, cmd, host_bins_path, local_instance_id,
207                        local_image_path, no_prompts=False,
208                        timeout_secs=_LAUNCH_CVD_TIMEOUT_SECS):
209         """Execute launch_cvd command and wait for boot up completed.
210 
211         1. Check if the provided image files are in use by any launch_cvd process.
212         2. Check if launch_cvd with the same instance id is running.
213         3. Launch local AVD.
214 
215         Args:
216             cmd: String, launch_cvd command.
217             host_bins_path: String of host package directory.
218             local_instance_id: Integer of instance id.
219             local_image_path: String of local image directory.
220             no_prompts: Boolean, True to skip all prompts.
221             timeout_secs: Integer, the number of seconds to wait for the AVD to boot up.
222         """
223         # launch_cvd assumes host bins are in $ANDROID_HOST_OUT, let's overwrite
224         # it to wherever we're running launch_cvd since they could be in a
225         # different dir (e.g. downloaded image).
226         os.environ[constants.ENV_ANDROID_HOST_OUT] = host_bins_path
227         # Check if the instance with same id is running.
228         existing_ins = list_instance.GetActiveCVD(local_instance_id)
229         if existing_ins:
230             if no_prompts or utils.GetUserAnswerYes(_CONFIRM_RELAUNCH %
231                                                     local_instance_id):
232                 existing_ins.Delete()
233             else:
234                 sys.exit(constants.EXIT_BY_USER)
235         else:
236             # Image files can't be shared among instances, so check if any running
237             # launch_cvd process is using this path.
238             occupied_ins_id = self.IsLocalImageOccupied(local_image_path)
239             if occupied_ins_id:
240                 utils.PrintColorString(
241                     "The image path[%s] is already used by current running AVD"
242                     "[id:%d]\nPlease choose another path to launch local "
243                     "instance." % (local_image_path, occupied_ins_id),
244                     utils.TextColors.FAIL)
245                 sys.exit(constants.EXIT_BY_USER)
246 
247         self._LaunchCvd(cmd, local_instance_id, timeout=timeout_secs)
248 
249     @staticmethod
250     @utils.TimeExecute(function_description="Waiting for AVD(s) to boot up")
251     def _LaunchCvd(cmd, local_instance_id, timeout=None):
252         """Execute Launch CVD.
253 
254         Kick off the launch_cvd command and log the output.
255 
256         Args:
257             cmd: String, launch_cvd command.
258             local_instance_id: Integer of instance id.
259             timeout: Integer, the number of seconds to wait for the AVD to boot up.
260 
261         Raises:
262             errors.LaunchCVDFail when any CalledProcessError.
263         """
264         # Delete the cvd home/runtime temp if exist. The runtime folder is
265         # under the cvd home dir, so we only delete them from home dir.
266         cvd_home_dir = instance.GetLocalInstanceHomeDir(local_instance_id)
267         cvd_runtime_dir = instance.GetLocalInstanceRuntimeDir(local_instance_id)
268         shutil.rmtree(cvd_home_dir, ignore_errors=True)
269         os.makedirs(cvd_runtime_dir)
270 
271         cvd_env = os.environ.copy()
272         cvd_env[constants.ENV_CVD_HOME] = cvd_home_dir
273         cvd_env[constants.ENV_CUTTLEFISH_INSTANCE] = str(local_instance_id)
274         # Check the result of launch_cvd command.
275         # An exit code of 0 is equivalent to VIRTUAL_DEVICE_BOOT_COMPLETED
276         process = subprocess.Popen(cmd, shell=True, stderr=subprocess.STDOUT,
277                                    env=cvd_env)
278         if timeout:
279             timer = threading.Timer(timeout, process.kill)
280             timer.start()
281         process.wait()
282         if timeout:
283             timer.cancel()
284         if process.returncode == 0:
285             return
286         raise errors.LaunchCVDFail(
287             "Can't launch cuttlefish AVD. Return code:%s. \nFor more detail: "
288             "%s/launcher.log" % (str(process.returncode), cvd_runtime_dir))
289 
290     @staticmethod
291     def PrintDisclaimer():
292         """Print Disclaimer."""
293         utils.PrintColorString(
294             "(Disclaimer: Local cuttlefish instance is not a fully supported\n"
295             "runtime configuration, fixing breakages is on a best effort SLO.)\n",
296             utils.TextColors.WARNING)
297 
298     @staticmethod
299     def IsLocalImageOccupied(local_image_dir):
300         """Check if the given image path is being used by a running CVD process.
301 
302         Args:
303             local_image_dir: String, path of local image.
304 
305         Return:
306             Integer of instance id which using the same image path.
307         """
308         # TODO(149602560): Remove occupied image checking after after cf disk
309         # overlay is stable
310         for cf_runtime_config_path in instance.GetAllLocalInstanceConfigs():
311             ins = instance.LocalInstance(cf_runtime_config_path)
312             if ins.CvdStatus():
313                 for disk_path in ins.virtual_disk_paths:
314                     if local_image_dir in disk_path:
315                         return ins.instance_id
316         return None
317