aboutsummaryrefslogtreecommitdiff
path: root/internal/lib/cvd_compute_client.py
blob: 920e807f1728f3f59230f6b261f1fb218db57314 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#!/usr/bin/env python
#
# Copyright 2018 - The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A client that manages Cuttlefish Virtual Device on compute engine.

** CvdComputeClient **

CvdComputeClient derives from AndroidComputeClient. It manges a google
compute engine project that is setup for running Cuttlefish Virtual Devices.
It knows how to create a host instance from Cuttlefish Stable Host Image, fetch
Android build, and start Android within the host instance.

** Class hierarchy **

  base_cloud_client.BaseCloudApiClient
                ^
                |
       gcompute_client.ComputeClient
                ^
                |
       android_compute_client.AndroidComputeClient
                ^
                |
       cvd_compute_client.CvdComputeClient

"""

import getpass
import logging

from acloud.internal import constants
from acloud.internal.lib import android_compute_client
from acloud.internal.lib import gcompute_client
from acloud.internal.lib import utils

logger = logging.getLogger(__name__)


class CvdComputeClient(android_compute_client.AndroidComputeClient):
    """Client that manages Anadroid Virtual Device."""

    DATA_POLICY_CREATE_IF_MISSING = "create_if_missing"

    # TODO: refactor CreateInstance to take in an object that contains these
    # args, this method differs too and holds way too cf-specific args to put in
    # the parent method.
    # pylint: disable=arguments-differ,too-many-locals
    @utils.TimeExecute(function_description="Creating GCE instance")
    def CreateInstance(self, instance, image_name, image_project,
                       build_target=None, branch=None, build_id=None,
                       kernel_branch=None, kernel_build_id=None,
                       blank_data_disk_size_gb=None, avd_spec=None,
                       extra_scopes=None):
        """Create a cuttlefish instance given stable host image and build id.

        Args:
            instance: instance name.
            image_name: A string, the name of the GCE image.
            image_project: A string, name of the project where the image lives.
                           Assume the default project if None.
            build_target: Target name, e.g. "aosp_cf_x86_phone-userdebug"
            branch: Branch name, e.g. "aosp-master"
            build_id: Build id, a string, e.g. "2263051", "P2804227"
            kernel_branch: Kernel branch name, e.g. "kernel-common-android-4.14"
            kernel_build_id: Kernel build id, a string, e.g. "223051", "P280427"
            blank_data_disk_size_gb: Size of the blank data disk in GB.
            avd_spec: An AVDSpec instance.
            extra_scopes: A list of extra scopes to be passed to the instance.
        """
        self._CheckMachineSize()

        # A blank data disk would be created on the host. Make sure the size of
        # the boot disk is large enough to hold it.
        boot_disk_size_gb = (
            int(self.GetImage(image_name, image_project)["diskSizeGb"]) +
            blank_data_disk_size_gb)
        disk_args = self._GetDiskArgs(
            instance, image_name, image_project, boot_disk_size_gb)

        # Transitional metadata variable as outlined in go/cuttlefish-deployment
        # These metadata tell the host instance to fetch and launch one
        # cuttlefish device (cvd-01). Ideally we should use a separate tool to
        # manage CVD devices on the host instance and not through metadata.
        # TODO(b/77626419): Remove these metadata once the
        # cuttlefish-google.service is turned off on the host instance.
        metadata = self._metadata.copy()
        metadata["cvd_01_fetch_android_build_target"] = build_target
        metadata["cvd_01_fetch_android_bid"] = "{branch}/{build_id}".format(
            branch=branch, build_id=build_id)
        if kernel_branch and kernel_build_id:
            metadata["cvd_01_fetch_kernel_bid"] = "{branch}/{build_id}".format(
                branch=kernel_branch, build_id=kernel_build_id)
        metadata["cvd_01_launch"] = (self._launch_args
                                     if self._launch_args else "1")

        # The cuttlefish-google tools changed the usage of this cvd_01_launch
        # variable. For the local image, we remove the cvd_01_launch from
        # metadata to tell server not to launch cvd  while instance is booted
        # up.
        if avd_spec and avd_spec.image_source == constants.IMAGE_SRC_LOCAL:
            metadata.pop("cvd_01_launch", None)

        if blank_data_disk_size_gb > 0:
            # Policy 'create_if_missing' would create a blank userdata disk if
            # missing. If already exist, reuse the disk.
            metadata["cvd_01_data_policy"] = self.DATA_POLICY_CREATE_IF_MISSING
            metadata["cvd_01_blank_data_disk_size"] = str(
                blank_data_disk_size_gb * 1024)
        metadata["user"] = getpass.getuser()
        # Update metadata by avd_spec
        # for legacy create_cf cmd, we will keep using resolution.
        # And always use avd_spec for acloud create cmd.
        # TODO(b/118406018): deprecate resolution config and use hw_proprty for
        # all create cmds.
        if avd_spec:
            metadata[constants.INS_KEY_AVD_TYPE] = avd_spec.avd_type
            metadata[constants.INS_KEY_AVD_FLAVOR] = avd_spec.flavor
            metadata["cvd_01_x_res"] = avd_spec.hw_property[constants.HW_X_RES]
            metadata["cvd_01_y_res"] = avd_spec.hw_property[constants.HW_Y_RES]
            metadata["cvd_01_dpi"] = avd_spec.hw_property[constants.HW_ALIAS_DPI]
            metadata["cvd_01_blank_data_disk_size"] = avd_spec.hw_property[
                constants.HW_ALIAS_DISK]
            # Use another METADATA_DISPLAY to record resolution which will be
            # retrieved in acloud list cmd. We try not to use cvd_01_x_res
            # since cvd_01_xxx metadata is going to deprecated by cuttlefish.
            metadata[constants.INS_KEY_DISPLAY] = ("%sx%s (%s)" % (
                avd_spec.hw_property[constants.HW_X_RES],
                avd_spec.hw_property[constants.HW_Y_RES],
                avd_spec.hw_property[constants.HW_ALIAS_DPI]))
        else:
            resolution = self._resolution.split("x")
            metadata["cvd_01_dpi"] = resolution[3]
            metadata["cvd_01_x_res"] = resolution[0]
            metadata["cvd_01_y_res"] = resolution[1]

        # Add per-instance ssh key
        if self._ssh_public_key_path:
            rsa = self._LoadSshPublicKey(self._ssh_public_key_path)
            logger.info("ssh_public_key_path is specified in config: %s, "
                        "will add the key to the instance.",
                        self._ssh_public_key_path)
            metadata["sshKeys"] = "%s:%s" % (getpass.getuser(), rsa)
        else:
            logger.warning(
                "ssh_public_key_path is not specified in config, "
                "only project-wide key will be effective.")

        # Add labels for giving the instances ability to be filter for
        # acloud list/delete cmds.
        labels = {constants.LABEL_CREATE_BY: getpass.getuser()}

        gcompute_client.ComputeClient.CreateInstance(
            self,
            instance=instance,
            image_name=image_name,
            image_project=image_project,
            disk_args=disk_args,
            metadata=metadata,
            machine_type=self._machine_type,
            network=self._network,
            zone=self._zone,
            labels=labels,
            extra_scopes=extra_scopes)