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"""A client that manages Goldfish Virtual Device on compute engine.
17
18** GoldfishComputeClient **
19
20GoldfishComputeClient derives from AndroidComputeClient. It manges a google
21compute engine project that is setup for running Goldfish Virtual Devices.
22It knows how to create a host instance from a Goldfish Stable Host Image, fetch
23Android build, an emulator build, and start Android within the host instance.
24
25** Class hierarchy **
26
27  base_cloud_client.BaseCloudApiClient
28                ^
29                |
30       gcompute_client.ComputeClient
31                ^
32                |
33       android_compute_client.AndroidComputeClient
34                ^
35                |
36       goldfish_compute_client.GoldfishComputeClient
37
38
39TODO: This class should likely be merged with CvdComputeClient
40"""
41
42import getpass
43import logging
44
45from acloud import errors
46from acloud.internal import constants
47from acloud.internal.lib import android_compute_client
48from acloud.internal.lib import gcompute_client
49
50
51logger = logging.getLogger(__name__)
52
53
54class GoldfishComputeClient(android_compute_client.AndroidComputeClient):
55    """Client that manages Goldfish based Android Virtual Device.
56
57    Attributes:
58        acloud_config: An AcloudConfig object.
59        oauth2_credentials: An oauth2client.OAuth2Credentials instance.
60    """
61
62    # To determine if the boot failed
63    BOOT_FAILED_MSG = "VIRTUAL_DEVICE_FAILED"
64
65    # To determine the failure reason
66    # If the emulator build is not available
67    EMULATOR_FETCH_FAILED_MSG = "EMULATOR_FETCH_FAILED"
68    # If the system image build is not available
69    ANDROID_FETCH_FAILED_MSG = "ANDROID_FETCH_FAILED"
70    # If the emulator could not boot in time
71    BOOT_TIMEOUT_MSG = "VIRTUAL_DEVICE_BOOT_FAILED"
72
73    #pylint: disable=signature-differs
74    def _GetDiskArgs(self, disk_name, image_name, image_project, disk_size_gb):
75        """Helper to generate disk args that is used to create an instance.
76
77        Args:
78            disk_name: String, the name of disk.
79            image_name: String, the name of the system image.
80            image_project: String, the name of the project where the image.
81            disk_size_gb: Integer, size of the blank data disk in GB.
82
83        Returns:
84            A dictionary representing disk args.
85        """
86        return [{
87            "type": "PERSISTENT",
88            "boot": True,
89            "mode": "READ_WRITE",
90            "autoDelete": True,
91            "initializeParams": {
92                "diskName":
93                disk_name,
94                "sourceImage":
95                self.GetImage(image_name, image_project)["selfLink"],
96                "diskSizeGb":
97                disk_size_gb
98            },
99        }]
100    #pylint: disable=signature-differs
101
102    def CheckBootFailure(self, serial_out, instance):
103        """Overriding method from the parent class.
104
105        Args:
106            serial_out: String
107            instance: String
108
109        Raises:
110            Raises an errors.DeviceBootError exception if a failure is detected.
111        """
112        if self.BOOT_FAILED_MSG in serial_out:
113            if self.EMULATOR_FETCH_FAILED_MSG in serial_out:
114                raise errors.DeviceBootError(
115                    "Failed to download emulator build. Re-run with a newer build."
116                )
117            if self.ANDROID_FETCH_FAILED_MSG in serial_out:
118                raise errors.DeviceBootError(
119                    "Failed to download system image build. Re-run with a newer build."
120                )
121            if self.BOOT_TIMEOUT_MSG in serial_out:
122                raise errors.DeviceBootError(
123                    "Emulator timed out while booting.")
124
125    @staticmethod
126    def GetKernelBuildArtifact(target):
127        if target == "kernel":
128            return "bzImage"
129        if target == "kernel_x86_64":
130            return "bzImage"
131        if target == "kernel_aarch64":
132            return "Image.gz"
133        raise errors.DeviceBootError(
134            "Don't know the artifact name for '%s' target" % target)
135
136    # pylint: disable=too-many-locals,arguments-differ
137    # TODO: Refactor CreateInstance to pass in an object instead of all these args.
138    def CreateInstance(self,
139                       instance,
140                       image_name,
141                       image_project,
142                       build_target,
143                       branch,
144                       build_id,
145                       kernel_branch=None,
146                       kernel_build_id=None,
147                       kernel_build_target=None,
148                       emulator_branch=None,
149                       emulator_build_id=None,
150                       blank_data_disk_size_gb=None,
151                       gpu=None,
152                       avd_spec=None,
153                       extra_scopes=None,
154                       tags=None):
155        """Create a goldfish instance given a stable host image and a build id.
156
157        Args:
158            instance: String, instance name.
159            image_name: String, the name of the system image.
160            image_project: String, name of the project where the image belongs.
161                           Assume the default project if None.
162            build_target: String, target name, e.g. "sdk_phone_x86_64-sdk"
163            branch: String, branch name, e.g. "git_pi-dev"
164            build_id: String, build id, a string, e.g. "2263051", "P2804227"
165            kernel_branch: String, kernel branch name.
166            kernel_build_id: String, kernel build id.
167            kernel_build_target: kernel target, e.g. "kernel_x86_64"
168            emulator_branch: String, emulator branch name, e.g."aosp-emu-master-dev"
169            emulator_build_id: String, emulator build id, a string, e.g. "2263051", "P2804227"
170            blank_data_disk_size_gb: Integer, size of the blank data disk in GB.
171            gpu: String, GPU that should be attached to the instance, or None of no
172                 acceleration is needed. e.g. "nvidia-tesla-k80"
173            avd_spec: An AVDSpec instance.
174            extra_scopes: A list of extra scopes to be passed to the instance.
175            tags: A list of tags to associate with the instance. e.g.
176                 ["http-server", "https-server"]
177        """
178        self._CheckMachineSize()
179
180        # Add space for possible data partition.
181        boot_disk_size_gb = (
182            int(self.GetImage(image_name, image_project)["diskSizeGb"]) +
183            blank_data_disk_size_gb)
184        disk_args = self._GetDiskArgs(instance, image_name, image_project,
185                                      boot_disk_size_gb)
186
187        # Goldfish instances are metadata compatible with cuttlefish devices.
188        # See details goto/goldfish-deployment
189        metadata = self._metadata.copy()
190        metadata["user"] = getpass.getuser()
191        metadata[constants.INS_KEY_AVD_TYPE] = constants.TYPE_GF
192
193        # Note that we use the same metadata naming conventions as cuttlefish
194        metadata["cvd_01_fetch_android_build_target"] = build_target
195        metadata["cvd_01_fetch_android_bid"] = "{branch}/{build_id}".format(
196            branch=branch, build_id=build_id)
197        if kernel_branch and kernel_build_id and kernel_build_target:
198            metadata["cvd_01_fetch_kernel_bid"] = "{branch}/{build_id}".format(
199                branch=kernel_branch, build_id=kernel_build_id)
200            metadata["cvd_01_fetch_kernel_build_target"] = kernel_build_target
201            metadata["cvd_01_fetch_kernel_build_artifact"] = (
202                self.GetKernelBuildArtifact(kernel_build_target))
203            metadata["cvd_01_use_custom_kernel"] = "true"
204        if emulator_branch and emulator_build_id:
205            metadata[
206                "cvd_01_fetch_emulator_bid"] = "{branch}/{build_id}".format(
207                    branch=emulator_branch, build_id=emulator_build_id)
208        metadata["cvd_01_launch"] = "1"
209
210        # Update metadata by avd_spec
211        # for legacy create_gf cmd, we will keep using resolution.
212        # And always use avd_spec for acloud create cmd.
213        if avd_spec:
214            metadata[constants.INS_KEY_AVD_FLAVOR] = avd_spec.flavor
215            metadata["cvd_01_x_res"] = avd_spec.hw_property[constants.HW_X_RES]
216            metadata["cvd_01_y_res"] = avd_spec.hw_property[constants.HW_Y_RES]
217            metadata["cvd_01_dpi"] = avd_spec.hw_property[constants.HW_ALIAS_DPI]
218            metadata[constants.INS_KEY_DISPLAY] = ("%sx%s (%s)" % (
219                avd_spec.hw_property[constants.HW_X_RES],
220                avd_spec.hw_property[constants.HW_Y_RES],
221                avd_spec.hw_property[constants.HW_ALIAS_DPI]))
222        else:
223            resolution = self._resolution.split("x")
224            metadata["cvd_01_x_res"] = resolution[0]
225            metadata["cvd_01_y_res"] = resolution[1]
226            metadata["cvd_01_dpi"] = resolution[3]
227
228        gcompute_client.ComputeClient.CreateInstance(
229            self,
230            instance=instance,
231            image_name=image_name,
232            image_project=image_project,
233            disk_args=disk_args,
234            metadata=metadata,
235            machine_type=self._machine_type,
236            network=self._network,
237            zone=self._zone,
238            gpu=gpu,
239            tags=tags,
240            extra_scopes=extra_scopes)
241