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        # Create an instance from Stable Host Image
165        self._compute_client.CreateInstance(
166            instance=instance,
167            image_name=self._cfg.stable_host_image_name,
168            image_project=self._cfg.stable_host_image_project,
169            build_target=self.build_info.build_target,
170            branch=self.build_info.branch,
171            build_id=remote_build_id,
172            kernel_branch=self.kernel_build_info.branch,
173            kernel_build_id=self.kernel_build_info.build_id,
174            kernel_build_target=self.kernel_build_info.build_target,
175            blank_data_disk_size_gb=self._blank_data_disk_size_gb,
176            extra_scopes=self._extra_scopes,
177            system_build_target=self.system_build_info.build_target,
178            system_branch=self.system_build_info.branch,
179            system_build_id=remote_system_build_id)
180
181        return instance
182
183
184#pylint: disable=too-many-locals
185def CreateDevices(cfg,
186                  build_target=None,
187                  build_id=None,
188                  branch=None,
189                  kernel_build_id=None,
190                  kernel_branch=None,
191                  kernel_build_target=None,
192                  system_branch=None,
193                  system_build_id=None,
194                  system_build_target=None,
195                  gpu=None,
196                  num=1,
197                  serial_log_file=None,
198                  autoconnect=False,
199                  report_internal_ip=False,
200                  boot_timeout_secs=None,
201                  ins_timeout_secs=None):
202    """Create one or multiple Cuttlefish devices.
203
204    Args:
205        cfg: An AcloudConfig instance.
206        build_target: String, Target name.
207        build_id: String, Build id, e.g. "2263051", "P2804227"
208        branch: Branch name, a string, e.g. aosp_master
209        kernel_build_id: String, Kernel build id.
210        kernel_branch: String, Kernel branch name.
211        kernel_build_target: String, Kernel build target name.
212        system_branch: Branch name to consume the system.img from, a string.
213        system_build_id: System branch build id, a string.
214        system_build_target: System image build target, a string.
215        gpu: String, GPU to attach to the device or None. e.g. "nvidia-tesla-k80"
216        num: Integer, Number of devices to create.
217        serial_log_file: String, A path to a tar file where serial output should
218                         be saved to.
219        autoconnect: Boolean, Create ssh tunnel(s) and adb connect after device
220                     creation.
221        report_internal_ip: Boolean to report the internal ip instead of
222                            external ip.
223        boot_timeout_secs: Integer, the maximum time in seconds used to wait
224                           for the AVD to boot.
225        ins_timeout_secs: Integer, the maximum time in seconds used to wait for
226                          the instance ready.
227
228    Returns:
229        A Report instance.
230    """
231    client_adb_port = None
232    unlock_screen = False
233    wait_for_boot = True
234    logger.info(
235        "Creating a cuttlefish device in project %s, "
236        "build_target: %s, "
237        "build_id: %s, "
238        "branch: %s, "
239        "kernel_build_id: %s, "
240        "kernel_branch: %s, "
241        "kernel_build_target: %s, "
242        "system_branch: %s, "
243        "system_build_id: %s, "
244        "system_build_target: %s, "
245        "gpu: %s"
246        "num: %s, "
247        "serial_log_file: %s, "
248        "autoconnect: %s, "
249        "report_internal_ip: %s", cfg.project, build_target,
250        build_id, branch, kernel_build_id, kernel_branch, kernel_build_target,
251        system_branch, system_build_id, system_build_target, gpu, num,
252        serial_log_file, autoconnect, report_internal_ip)
253    # If multi_stage enable, launch_cvd don't write serial log to instance. So
254    # it doesn't go WaitForBoot function.
255    if cfg.enable_multi_stage:
256        wait_for_boot = False
257    device_factory = CuttlefishDeviceFactory(
258        cfg, build_target, build_id, branch=branch,
259        kernel_build_id=kernel_build_id, kernel_branch=kernel_branch,
260        kernel_build_target=kernel_build_target, system_branch=system_branch,
261        system_build_id=system_build_id,
262        system_build_target=system_build_target,
263        boot_timeout_secs=boot_timeout_secs,
264        ins_timeout_secs=ins_timeout_secs,
265        report_internal_ip=report_internal_ip,
266        gpu=gpu)
267    return common_operations.CreateDevices("create_cf", cfg, device_factory,
268                                           num, constants.TYPE_CF,
269                                           report_internal_ip, autoconnect,
270                                           serial_log_file, client_adb_port,
271                                           boot_timeout_secs, unlock_screen,
272                                           wait_for_boot)
273