xref: /aosp_15_r20/tools/acloud/create/remote_image_remote_instance.py (revision 800a58d989c669b8eb8a71d8df53b1ba3d411444)
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.
16r"""RemoteImageRemoteInstance class.
17
18Create class that is responsible for creating a remote instance AVD with a
19remote image.
20"""
21
22import logging
23import re
24import subprocess
25import time
26
27from acloud.create import base_avd_create
28from acloud.internal import constants
29from acloud.internal.lib import oxygen_client
30from acloud.internal.lib import utils
31from acloud.public.actions import common_operations
32from acloud.public.actions import remote_instance_cf_device_factory
33from acloud.public.actions import remote_instance_trusty_device_factory
34from acloud.public import report
35
36
37logger = logging.getLogger(__name__)
38_DEVICE = "device"
39_DEVICES = "devices"
40_LAUNCH_CVD_TIME = "launch_cvd_time"
41_RE_SESSION_ID = re.compile(r".*session_id:\"(?P<session_id>[^\"]+)")
42_RE_SERVER_URL = re.compile(r".*server_url:\"(?P<server_url>[^\"]+)")
43_RE_OXYGEN_LEASE_ERROR = re.compile(
44    r".*Error received while trying to lease device: (?P<error>.*)$", re.DOTALL)
45
46
47class RemoteImageRemoteInstance(base_avd_create.BaseAVDCreate):
48    """Create class for a remote image remote instance AVD."""
49
50    @utils.TimeExecute(function_description="Total time: ",
51                       print_before_call=False, print_status=False)
52    def _CreateAVD(self, avd_spec, no_prompts):
53        """Create the AVD.
54
55        Args:
56            avd_spec: AVDSpec object that tells us what we're going to create.
57            no_prompts: Boolean, True to skip all prompts.
58
59        Returns:
60            A Report instance.
61        """
62        if avd_spec.oxygen:
63            return self._LeaseOxygenAVD(avd_spec)
64        if avd_spec.gce_only:
65            return self._CreateGceInstance(avd_spec)
66        if avd_spec.avd_type == constants.TYPE_CF:
67            command = "create_cf"
68            device_factory = remote_instance_cf_device_factory.RemoteInstanceDeviceFactory(
69                avd_spec)
70        elif avd_spec.avd_type == constants.TYPE_TRUSTY:
71            command = "create_trusty"
72            device_factory = remote_instance_trusty_device_factory.RemoteInstanceDeviceFactory(
73                avd_spec)
74        else:
75            # This type isn't correctly registered in create.py.
76            raise ValueError(f"Unsupported AVD type: {avd_spec.avd_type}")
77        create_report = common_operations.CreateDevices(
78            command, avd_spec.cfg, device_factory, avd_spec.num,
79            report_internal_ip=avd_spec.report_internal_ip,
80            autoconnect=avd_spec.autoconnect,
81            avd_type=avd_spec.avd_type,
82            boot_timeout_secs=avd_spec.boot_timeout_secs,
83            unlock_screen=avd_spec.unlock_screen,
84            wait_for_boot=False,
85            connect_webrtc=avd_spec.connect_webrtc,
86            client_adb_port=avd_spec.client_adb_port)
87        if create_report.status == report.Status.SUCCESS:
88            if avd_spec.connect_vnc:
89                utils.LaunchVNCFromReport(create_report, avd_spec, no_prompts)
90            if avd_spec.connect_webrtc:
91                utils.LaunchBrowserFromReport(create_report)
92
93        return create_report
94
95    def _LeaseOxygenAVD(self, avd_spec):
96        """Lease the AVD from the AVD pool.
97
98        Args:
99            avd_spec: AVDSpec object that tells us what we're going to create.
100
101        Returns:
102            A Report instance.
103        """
104        timestart = time.time()
105        session_id = None
106        server_url = None
107        try:
108            response = oxygen_client.OxygenClient.LeaseDevice(
109                avd_spec.remote_image[constants.BUILD_TARGET],
110                avd_spec.remote_image[constants.BUILD_ID],
111                avd_spec.remote_image[constants.BUILD_BRANCH],
112                avd_spec.system_build_info[constants.BUILD_TARGET],
113                avd_spec.system_build_info[constants.BUILD_ID],
114                avd_spec.kernel_build_info[constants.BUILD_TARGET],
115                avd_spec.kernel_build_info[constants.BUILD_ID],
116                avd_spec.cfg.oxygen_client,
117                avd_spec.cfg.oxygen_lease_args)
118            session_id, server_url = self._GetDeviceInfoFromResponse(response)
119            execution_time = round(time.time() - timestart, 2)
120        except subprocess.CalledProcessError as e:
121            logger.error("Failed to lease device from Oxygen, error: %s",
122                e.output)
123            response = e.output
124
125        reporter = report.Report(command="create_cf")
126        if session_id and server_url:
127            reporter.SetStatus(report.Status.SUCCESS)
128            device_data = {"instance_name": session_id,
129                           "ip": server_url}
130            device_data[_LAUNCH_CVD_TIME] = execution_time
131            dict_devices = {_DEVICES: [device_data]}
132            reporter.UpdateData(dict_devices)
133        else:
134            # Try to parse client error
135            match = _RE_OXYGEN_LEASE_ERROR.match(response)
136            if match:
137                response = match.group("error").strip()
138
139            reporter.SetStatus(report.Status.FAIL)
140            reporter.SetErrorType(constants.ACLOUD_OXYGEN_LEASE_ERROR)
141            reporter.AddError(response)
142
143        return reporter
144
145    @staticmethod
146    def _GetDeviceInfoFromResponse(response):
147        """Get session id and server url from response.
148
149        Args:
150            response: String of the response from oxygen proxy client.
151                      e.g. "2021/08/02 11:28:52 session_id: "74b6b835"
152                      server_url: "0.0.0.34" port:{type:WATERFALL ..."
153
154        Returns:
155            The session id and the server url of leased device.
156        """
157        session_id = ""
158        for line in response.splitlines():
159            session_id_match = _RE_SESSION_ID.match(line)
160            if session_id_match:
161                session_id = session_id_match.group("session_id")
162                break
163
164        server_url = ""
165        for line in response.splitlines():
166            server_url_match = _RE_SERVER_URL.match(line)
167            if server_url_match:
168                server_url = server_url_match.group("server_url")
169                break
170        return session_id, server_url
171
172    @staticmethod
173    def _CreateGceInstance(avd_spec):
174        """Create the GCE instance.
175
176        Args:
177            avd_spec: AVDSpec object.
178
179        Returns:
180            A Report instance.
181        """
182        device_factory = remote_instance_cf_device_factory.RemoteInstanceDeviceFactory(
183            avd_spec)
184        instance = device_factory.CreateGceInstance()
185        compute_client = device_factory.GetComputeClient()
186        ip = compute_client.GetInstanceIP(instance)
187        reporter = report.Report(command="create_cf")
188        reporter.SetStatus(report.Status.SUCCESS)
189        device_data = {"instance_name": instance,
190                       "ip": ip.internal if avd_spec.report_internal_ip
191                       else ip.external}
192        dict_devices = {_DEVICES: [device_data]}
193        reporter.UpdateData(dict_devices)
194        return reporter
195