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
17"""Create cuttlefish instances.
18
19TODO: This module now just contains the skeleton but not the actual logic.
20      Need to fill in the actuall logic.
21"""
22
23import logging
24
25from acloud.public.actions import common_operations
26from acloud.public.actions import base_device_factory
27from acloud.internal import constants
28from acloud.internal.lib import android_build_client
29from acloud.internal.lib import auth
30from acloud.internal.lib import cvd_compute_client
31from acloud.internal.lib import cvd_compute_client_multi_stage
32from acloud.internal.lib import utils
33
34
35logger = logging.getLogger(__name__)
36
37
38class CuttlefishDeviceFactory(base_device_factory.BaseDeviceFactory):
39    """A class that can produce a cuttlefish device.
40
41    Attributes:
42        cfg: An AcloudConfig instance.
43        build_target: String,Target name.
44        build_id: String, Build id, e.g. "2263051", "P2804227"
45        kernel_build_id: String, Kernel build id.
46        gpu: String, GPU to attach to the device or None. e.g. "nvidia-tesla-k80"
47    """
48
49    LOG_FILES = ["/home/vsoc-01/cuttlefish_runtime/kernel.log",
50                 "/home/vsoc-01/cuttlefish_runtime/logcat",
51                 "/home/vsoc-01/cuttlefish_runtime/cuttlefish_config.json"]
52
53    #pylint: disable=too-many-locals
54    def __init__(self, cfg, build_target, build_id, branch=None,
55                 kernel_build_id=None, kernel_branch=None,
56                 kernel_build_target=None, system_branch=None,
57                 system_build_id=None, system_build_target=None,
58                 boot_timeout_secs=None, ins_timeout_secs=None,
59                 report_internal_ip=None, gpu=None):
60
61        self.credentials = auth.CreateCredentials(cfg)
62
63        if cfg.enable_multi_stage:
64            compute_client = cvd_compute_client_multi_stage.CvdComputeClient(
65                cfg, self.credentials, boot_timeout_secs, ins_timeout_secs,
66                report_internal_ip, gpu)
67        else:
68            compute_client = cvd_compute_client.CvdComputeClient(
69                cfg, self.credentials)
70        super(CuttlefishDeviceFactory, self).__init__(compute_client)
71
72        # Private creation parameters
73        self._cfg = cfg
74        self._build_target = build_target
75        self._build_id = build_id
76        self._branch = branch
77        self._kernel_build_id = kernel_build_id
78        self._blank_data_disk_size_gb = cfg.extra_data_disk_size_gb
79        self._extra_scopes = cfg.extra_scopes
80
81        # Configure clients for interaction with GCE/Build servers
82        self._build_client = android_build_client.AndroidBuildClient(
83            self.credentials)
84
85        # Get build_info namedtuple for platform, kernel, system build
86        self.build_info = self._build_client.GetBuildInfo(
87            build_target, build_id, branch)
88        self.kernel_build_info = self._build_client.GetBuildInfo(
89            kernel_build_target or cfg.kernel_build_target, kernel_build_id,
90            kernel_branch)
91        self.system_build_info = self._build_client.GetBuildInfo(
92            system_build_target or build_target, system_build_id, system_branch)
93
94    def GetBuildInfoDict(self):
95        """Get build info dictionary.
96
97        Returns:
98          A build info dictionary.
99        """
100        build_info_dict = {
101            key: val for key, val in utils.GetDictItems(self.build_info) if val}
102
103        build_info_dict.update(
104            {"kernel_%s" % key: val
105             for key, val in utils.GetDictItems(self.kernel_build_info) if val}
106        )
107        build_info_dict.update(
108            {"system_%s" % key: val
109             for key, val in utils.GetDictItems(self.system_build_info) if val}
110        )
111        return build_info_dict
112
113    def GetFailures(self):
114        """Get failures from all devices.
115
116        Returns:
117            A dictionary that contains all the failures.
118            The key is the name of the instance that fails to boot,
119            and the value is an errors.DeviceBootError object.
120        """
121        return self._compute_client.all_failures
122
123    @staticmethod
124    def _GetGcsBucketBuildId(build_id, release_id):
125        """Get GCS Bucket Build Id.
126
127        Args:
128          build_id: The incremental build id. For example 5325535.
129          release_id: The release build id, None if not a release build.
130                      For example AAAA.190220.001.
131
132        Returns:
133          GCS bucket build id. For example: AAAA.190220.001-5325535
134        """
135        return "-".join([release_id, build_id]) if release_id else build_id
136
137    def CreateInstance(self):
138        """Creates singe configured cuttlefish device.
139
140        Override method from parent class.
141
142        Returns:
143            A string, representing instance name.
144        """
145
146        # Create host instances for cuttlefish device. Currently one host instance
147        # has one cuttlefish device. In the future, these logics should be modified
148        # to support multiple cuttlefish devices per host instance.
149        instance = self._compute_client.GenerateInstanceName(
150            build_id=self.build_info.build_id, build_target=self._build_target)
151
152        if self._cfg.enable_multi_stage:
153            remote_build_id = self.build_info.build_id
154        else:
155            remote_build_id = self._GetGcsBucketBuildId(
156                self.build_info.build_id, self.build_info.release_build_id)
157
158        if self._cfg.enable_multi_stage:
159            remote_system_build_id = self.system_build_info.build_id
160        else:
161            remote_system_build_id = self._GetGcsBucketBuildId(
162                self.system_build_info.build_id, self.system_build_info.release_build_id)
163
164        host_image_name = self._compute_client.GetHostImageName(
165            self._cfg.stable_host_image_name,
166            self._cfg.stable_host_image_family,
167            self._cfg.stable_host_image_project)
168        # Create an instance from Stable Host Image
169        self._compute_client.CreateInstance(
170            instance=instance,
171            image_name=host_image_name,
172            image_project=self._cfg.stable_host_image_project,
173            build_target=self.build_info.build_target,
174            branch=self.build_info.branch,
175            build_id=remote_build_id,
176            kernel_branch=self.kernel_build_info.branch,
177            kernel_build_id=self.kernel_build_info.build_id,
178            kernel_build_target=self.kernel_build_info.build_target,
179            blank_data_disk_size_gb=self._blank_data_disk_size_gb,
180            extra_scopes=self._extra_scopes,
181            system_build_target=self.system_build_info.build_target,
182            system_branch=self.system_build_info.branch,
183            system_build_id=remote_system_build_id)
184
185        return instance
186
187
188#pylint: disable=too-many-locals
189def CreateDevices(cfg,
190                  build_target=None,
191                  build_id=None,
192                  branch=None,
193                  kernel_build_id=None,
194                  kernel_branch=None,
195                  kernel_build_target=None,
196                  system_branch=None,
197                  system_build_id=None,
198                  system_build_target=None,
199                  gpu=None,
200                  num=1,
201                  serial_log_file=None,
202                  autoconnect=False,
203                  report_internal_ip=False,
204                  boot_timeout_secs=None,
205                  ins_timeout_secs=None):
206    """Create one or multiple Cuttlefish devices.
207
208    Args:
209        cfg: An AcloudConfig instance.
210        build_target: String, Target name.
211        build_id: String, Build id, e.g. "2263051", "P2804227"
212        branch: Branch name, a string, e.g. aosp_master
213        kernel_build_id: String, Kernel build id.
214        kernel_branch: String, Kernel branch name.
215        kernel_build_target: String, Kernel build target name.
216        system_branch: Branch name to consume the system.img from, a string.
217        system_build_id: System branch build id, a string.
218        system_build_target: System image build target, a string.
219        gpu: String, GPU to attach to the device or None. e.g. "nvidia-tesla-k80"
220        num: Integer, Number of devices to create.
221        serial_log_file: String, A path to a tar file where serial output should
222                         be saved to.
223        autoconnect: Boolean, Create ssh tunnel(s) and adb connect after device
224                     creation.
225        report_internal_ip: Boolean to report the internal ip instead of
226                            external ip.
227        boot_timeout_secs: Integer, the maximum time in seconds used to wait
228                           for the AVD to boot.
229        ins_timeout_secs: Integer, the maximum time in seconds used to wait for
230                          the instance ready.
231
232    Returns:
233        A Report instance.
234    """
235    client_adb_port = None
236    unlock_screen = False
237    wait_for_boot = True
238    logger.info(
239        "Creating a cuttlefish device in project %s, "
240        "build_target: %s, "
241        "build_id: %s, "
242        "branch: %s, "
243        "kernel_build_id: %s, "
244        "kernel_branch: %s, "
245        "kernel_build_target: %s, "
246        "system_branch: %s, "
247        "system_build_id: %s, "
248        "system_build_target: %s, "
249        "gpu: %s"
250        "num: %s, "
251        "serial_log_file: %s, "
252        "autoconnect: %s, "
253        "report_internal_ip: %s", cfg.project, build_target,
254        build_id, branch, kernel_build_id, kernel_branch, kernel_build_target,
255        system_branch, system_build_id, system_build_target, gpu, num,
256        serial_log_file, autoconnect, report_internal_ip)
257    # If multi_stage enable, launch_cvd don't write serial log to instance. So
258    # it doesn't go WaitForBoot function.
259    if cfg.enable_multi_stage:
260        wait_for_boot = False
261    device_factory = CuttlefishDeviceFactory(
262        cfg, build_target, build_id, branch=branch,
263        kernel_build_id=kernel_build_id, kernel_branch=kernel_branch,
264        kernel_build_target=kernel_build_target, system_branch=system_branch,
265        system_build_id=system_build_id,
266        system_build_target=system_build_target,
267        boot_timeout_secs=boot_timeout_secs,
268        ins_timeout_secs=ins_timeout_secs,
269        report_internal_ip=report_internal_ip,
270        gpu=gpu)
271    return common_operations.CreateDevices("create_cf", cfg, device_factory,
272                                           num, constants.TYPE_CF,
273                                           report_internal_ip, autoconnect,
274                                           serial_log_file, client_adb_port,
275                                           boot_timeout_secs, unlock_screen,
276                                           wait_for_boot)
277