• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2019 - The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""RemoteInstanceDeviceFactory provides basic interface to create a cuttlefish
16device factory."""
17
18import logging
19import os
20import shutil
21import subprocess
22import tempfile
23
24from acloud import errors
25from acloud.create import create_common
26from acloud.internal import constants
27from acloud.internal.lib import cvd_utils
28from acloud.internal.lib import utils
29from acloud.public.actions import gce_device_factory
30from acloud.pull import pull
31
32
33logger = logging.getLogger(__name__)
34
35
36class RemoteInstanceDeviceFactory(gce_device_factory.GCEDeviceFactory):
37    """A class that can produce a cuttlefish device.
38
39    Attributes:
40        avd_spec: AVDSpec object that tells us what we're going to create.
41        cfg: An AcloudConfig instance.
42        local_image_artifact: A string, path to local image.
43        cvd_host_package_artifact: A string, path to cvd host package.
44        report_internal_ip: Boolean, True for the internal ip is used when
45                            connecting from another GCE instance.
46        credentials: An oauth2client.OAuth2Credentials instance.
47        compute_client: An object of cvd_compute_client.CvdComputeClient.
48        ssh: An Ssh object.
49    """
50    def __init__(self, avd_spec, local_image_artifact=None,
51                 cvd_host_package_artifact=None):
52        super().__init__(avd_spec, local_image_artifact)
53        self._all_logs = {}
54        self._cvd_host_package_artifact = cvd_host_package_artifact
55
56    # pylint: disable=broad-except
57    def CreateInstance(self):
58        """Create a single configured cuttlefish device.
59
60        Returns:
61            A string, representing instance name.
62        """
63        instance = self.CreateGceInstance()
64        # If instance is failed, no need to go next step.
65        if instance in self.GetFailures():
66            return instance
67        try:
68            image_args = self._ProcessArtifacts()
69            failures = self._compute_client.LaunchCvd(
70                instance, self._avd_spec, cvd_utils.GCE_BASE_DIR, image_args)
71            for failing_instance, error_msg in failures.items():
72                self._SetFailures(failing_instance, error_msg)
73        except Exception as e:
74            self._SetFailures(instance, e)
75
76        try:
77            self._FindLogFiles(
78                instance,
79                (instance in self.GetFailures() and
80                 not self._avd_spec.no_pull_log))
81        except (errors.SubprocessFail, errors.DeviceConnectionError,
82                subprocess.CalledProcessError, subprocess.TimeoutExpired) as e:
83            logger.error("Fail to find log files: %s", e)
84
85        return instance
86
87    def _ProcessArtifacts(self):
88        """Process artifacts.
89
90        - If images source is local, tool will upload images from local site to
91          remote instance.
92        - If images source is remote, tool will download images from android
93          build to remote instance. Before download images, we have to update
94          fetch_cvd to remote instance.
95
96        Returns:
97            A list of strings, the launch_cvd arguments.
98        """
99        avd_spec = self._avd_spec
100        launch_cvd_args = []
101        temp_dir = None
102        try:
103            target_files_dir = None
104            if cvd_utils.AreTargetFilesRequired(avd_spec):
105                temp_dir = tempfile.mkdtemp(prefix="acloud_remote_ins")
106                target_files_dir = self._GetLocalTargetFilesDir(temp_dir)
107
108            if avd_spec.image_source == constants.IMAGE_SRC_LOCAL:
109                cvd_utils.UploadArtifacts(
110                    self._ssh, cvd_utils.GCE_BASE_DIR,
111                    (target_files_dir or self._local_image_artifact or
112                     avd_spec.local_image_dir),
113                    self._cvd_host_package_artifact)
114            elif avd_spec.image_source == constants.IMAGE_SRC_REMOTE:
115                self._compute_client.FetchBuild(
116                    avd_spec.remote_image,
117                    avd_spec.system_build_info,
118                    avd_spec.kernel_build_info,
119                    avd_spec.boot_build_info,
120                    avd_spec.bootloader_build_info,
121                    avd_spec.android_efi_loader_build_info,
122                    avd_spec.ota_build_info,
123                    avd_spec.host_package_build_info)
124
125            launch_cvd_args += cvd_utils.UploadExtraImages(
126                self._ssh, cvd_utils.GCE_BASE_DIR, avd_spec, target_files_dir)
127        finally:
128            if temp_dir:
129                shutil.rmtree(temp_dir)
130
131        if avd_spec.mkcert and avd_spec.connect_webrtc:
132            self._compute_client.UpdateCertificate()
133
134        if avd_spec.extra_files:
135            self._compute_client.UploadExtraFiles(avd_spec.extra_files)
136
137        return [arg for arg_pair in launch_cvd_args for arg in arg_pair]
138
139    @utils.TimeExecute(function_description="Downloading target_files archive")
140    def _DownloadTargetFiles(self, download_dir):
141        """Download target_files zip to a directory.
142
143        Args:
144            download_dir: The directory to which the zip is downloaded.
145
146        Returns:
147            The path to the target_files zip.
148        """
149        avd_spec = self._avd_spec
150        build_id = avd_spec.remote_image[constants.BUILD_ID]
151        build_target = avd_spec.remote_image[constants.BUILD_TARGET]
152        name = cvd_utils.GetMixBuildTargetFilename(build_target, build_id)
153        create_common.DownloadRemoteArtifact(avd_spec.cfg, build_target,
154                                             build_id, name, download_dir)
155        return os.path.join(download_dir, name)
156
157    def _GetLocalTargetFilesDir(self, temp_dir):
158        """Return a directory of extracted target_files or local images.
159
160        Args:
161            temp_dir: Temporary directory to store downloaded build artifacts
162                      and extracted target_files archive.
163
164        Returns:
165            The path to the target_files directory.
166        """
167        avd_spec = self._avd_spec
168        if avd_spec.image_source == constants.IMAGE_SRC_LOCAL:
169            if self._local_image_artifact:
170                target_files_zip = self._local_image_artifact
171                target_files_dir = os.path.join(temp_dir, "local_images")
172            else:
173                return os.path.abspath(avd_spec.local_image_dir)
174        else:  # must be IMAGE_SRC_REMOTE
175            target_files_zip = self._DownloadTargetFiles(temp_dir)
176            target_files_dir = os.path.join(temp_dir, "remote_images")
177
178        os.makedirs(target_files_dir, exist_ok=True)
179        cvd_utils.ExtractTargetFilesZip(target_files_zip, target_files_dir)
180        return target_files_dir
181
182    def _FindLogFiles(self, instance, download):
183        """Find and pull all log files from instance.
184
185        Args:
186            instance: String, instance name.
187            download: Whether to download the files to a temporary directory
188                      and show messages to the user.
189        """
190        logs = [cvd_utils.HOST_KERNEL_LOG]
191        if self._avd_spec.image_source == constants.IMAGE_SRC_REMOTE:
192            logs.append(
193                cvd_utils.GetRemoteFetcherConfigJson(cvd_utils.GCE_BASE_DIR))
194        logs.extend(cvd_utils.FindRemoteLogs(
195            self._ssh,
196            cvd_utils.GCE_BASE_DIR,
197            self._avd_spec.base_instance_num,
198            self._avd_spec.num_avds_per_instance))
199        self._all_logs[instance] = logs
200
201        if download:
202            # To avoid long download time, fetch from the first device only.
203            log_files = pull.GetAllLogFilePaths(self._ssh,
204                                                constants.REMOTE_LOG_FOLDER)
205            error_log_folder = pull.PullLogs(self._ssh, log_files, instance)
206            self._compute_client.ExtendReportData(constants.ERROR_LOG_FOLDER,
207                                                  error_log_folder)
208
209    def GetOpenWrtInfoDict(self):
210        """Get openwrt info dictionary.
211
212        Returns:
213            A openwrt info dictionary. None for the case is not openwrt device.
214        """
215        if not self._avd_spec.openwrt:
216            return None
217        return cvd_utils.GetOpenWrtInfoDict(self._ssh, cvd_utils.GCE_BASE_DIR)
218
219    def GetAdbPorts(self):
220        """Get ADB ports of the created devices.
221
222        Returns:
223            The port numbers as a list of integers.
224        """
225        return cvd_utils.GetAdbPorts(self._avd_spec.base_instance_num,
226                                     self._avd_spec.num_avds_per_instance)
227
228    def GetVncPorts(self):
229        """Get VNC ports of the created devices.
230
231        Returns:
232            The port numbers as a list of integers.
233        """
234        return cvd_utils.GetVncPorts(self._avd_spec.base_instance_num,
235                                     self._avd_spec.num_avds_per_instance)
236
237    def GetBuildInfoDict(self):
238        """Get build info dictionary.
239
240        Returns:
241            A build info dictionary. None for local image case.
242        """
243        if self._avd_spec.image_source == constants.IMAGE_SRC_LOCAL:
244            return None
245        return cvd_utils.GetRemoteBuildInfoDict(self._avd_spec)
246
247    def GetLogs(self):
248        """Get all device logs.
249
250        Returns:
251            A dictionary that maps instance names to lists of report.LogFile.
252        """
253        return self._all_logs
254