ronnie
2022-10-14 1504bb53e29d3d46222c0b3ea994fc494b48e153
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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
#!/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.
r"""LocalImageRemoteInstance class.
 
Create class that is responsible for creating a remote instance AVD with a
local image.
"""
 
from distutils.spawn import find_executable
import getpass
import logging
import os
import subprocess
 
from acloud import errors
from acloud.create import base_avd_create
from acloud.create import create_common
from acloud.internal import constants
from acloud.internal.lib import auth
from acloud.internal.lib import cvd_compute_client
from acloud.internal.lib import utils
from acloud.public.actions import base_device_factory
from acloud.public.actions import common_operations
 
logger = logging.getLogger(__name__)
 
_CVD_HOST_PACKAGE = "cvd-host_package.tar.gz"
_CVD_USER = getpass.getuser()
_CMD_LAUNCH_CVD_ARGS = (" -cpus %s -x_res %s -y_res %s -dpi %s "
                        "-memory_mb %s -blank_data_image_mb %s "
                        "-data_policy always_create ")
#Output to Serial port 1 (console) group in the instance
_OUTPUT_CONSOLE_GROUPS = "tty"
SSH_BIN = "ssh"
_SSH_CMD = (" -i %(rsa_key_file)s "
            "-q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "
            "-l %(login_user)s %(ip_addr)s ")
_SSH_CMD_MAX_RETRY = 2
_SSH_CMD_RETRY_SLEEP = 3
_USER_BUILD = "userbuild"
 
class RemoteInstanceDeviceFactory(base_device_factory.BaseDeviceFactory):
    """A class that can produce a cuttlefish device.
 
    Attributes:
        avd_spec: AVDSpec object that tells us what we're going to create.
        cfg: An AcloudConfig instance.
        image_path: A string, upload image artifact to instance.
        cvd_host_package: A string, upload host package artifact to instance.
        credentials: An oauth2client.OAuth2Credentials instance.
        compute_client: An object of cvd_compute_client.CvdComputeClient.
    """
    def __init__(self, avd_spec, local_image_artifact, cvd_host_package_artifact):
        """Constructs a new remote instance device factory."""
        self._avd_spec = avd_spec
        self._cfg = avd_spec.cfg
        self._local_image_artifact = local_image_artifact
        self._cvd_host_package_artifact = cvd_host_package_artifact
        self._report_internal_ip = avd_spec.report_internal_ip
        self.credentials = auth.CreateCredentials(avd_spec.cfg)
        compute_client = cvd_compute_client.CvdComputeClient(
            avd_spec.cfg, self.credentials)
        super(RemoteInstanceDeviceFactory, self).__init__(compute_client)
        # Private creation parameters
        self._ssh_cmd = None
 
    def CreateInstance(self):
        """Create a single configured cuttlefish device.
 
        1. Create gcp instance.
        2. setup the AVD env in the instance.
        3. upload the artifacts to instance.
        4. Launch CVD.
 
        Returns:
            A string, representing instance name.
        """
        instance = self._CreateGceInstance()
        self._SetAVDenv(_CVD_USER)
        self._UploadArtifacts(_CVD_USER,
                              self._local_image_artifact,
                              self._cvd_host_package_artifact)
        self._LaunchCvd(_CVD_USER, self._avd_spec.hw_property)
        return instance
 
    @staticmethod
    def _ShellCmdWithRetry(remote_cmd):
        """Runs a shell command on remote device.
 
        If the network is unstable and causes SSH connect fail, it will retry.
        When it retry in a short time, you may encounter unstable network. We
        will use the mechanism of RETRY_BACKOFF_FACTOR. The retry time for each
        failure is times * retries.
 
        Args:
            remote_cmd: A string, shell command to be run on remote.
 
        Raises:
            subprocess.CalledProcessError: For any non-zero return code of
                                           remote_cmd.
 
        Returns:
            Boolean, True if the command was successfully executed. False otherwise.
        """
        return utils.RetryExceptionType(
            exception_types=subprocess.CalledProcessError,
            max_retries=_SSH_CMD_MAX_RETRY,
            functor=lambda cmd: subprocess.check_call(cmd, shell=True),
            sleep_multiplier=_SSH_CMD_RETRY_SLEEP,
            retry_backoff_factor=utils.DEFAULT_RETRY_BACKOFF_FACTOR,
            cmd=remote_cmd)
 
    def _CreateGceInstance(self):
        """Create a single configured cuttlefish device.
 
        Override method from parent class.
        build_target: The format is like "aosp_cf_x86_phone". We only get info
                      from the user build image file name. If the file name is
                      not custom format (no "-"), We will use the original
                      flavor as our build_target.
 
        Returns:
            A string, representing instance name.
        """
        image_name = os.path.basename(self._local_image_artifact)
        build_target = self._avd_spec.flavor if "-" not in image_name else image_name.split(
            "-")[0]
        instance = self._compute_client.GenerateInstanceName(
            build_target=build_target, build_id=_USER_BUILD)
        # Create an instance from Stable Host Image
        self._compute_client.CreateInstance(
            instance=instance,
            image_name=self._cfg.stable_host_image_name,
            image_project=self._cfg.stable_host_image_project,
            blank_data_disk_size_gb=self._cfg.extra_data_disk_size_gb,
            avd_spec=self._avd_spec)
        ip = self._compute_client.GetInstanceIP(instance)
        self._ssh_cmd = find_executable(SSH_BIN) + _SSH_CMD % {
            "login_user": getpass.getuser(),
            "rsa_key_file": self._cfg.ssh_private_key_path,
            "ip_addr": (ip.internal if self._report_internal_ip
                        else ip.external)}
        return instance
 
    @utils.TimeExecute(function_description="Setting up GCE environment")
    def _SetAVDenv(self, cvd_user):
        """set the user to run AVD in the instance.
 
        Args:
            cvd_user: A string, user run the cvd in the instance.
        """
        avd_list_of_groups = []
        avd_list_of_groups.extend(constants.LIST_CF_USER_GROUPS)
        avd_list_of_groups.append(_OUTPUT_CONSOLE_GROUPS)
        remote_cmd = ""
        for group in avd_list_of_groups:
            remote_cmd += "\"sudo usermod -aG %s %s;\"" %(group, cvd_user)
        logger.debug("remote_cmd:\n %s", remote_cmd)
        self._ShellCmdWithRetry(self._ssh_cmd + remote_cmd)
 
    @utils.TimeExecute(function_description="Uploading local image")
    def _UploadArtifacts(self,
                         cvd_user,
                         local_image_artifact,
                         cvd_host_package_artifact):
        """Upload local image and avd local host package to instance.
 
        Args:
            cvd_user: A string, user upload the artifacts to instance.
            local_image_artifact: A string, path to local image.
            cvd_host_package_artifact: A string, path to cvd host package.
        """
        # TODO(b/129376163) Use lzop for fast sparse image upload
        remote_cmd = ("\"sudo su -c '/usr/bin/install_zip.sh .' - '%s'\" < %s" %
                      (cvd_user, local_image_artifact))
        logger.debug("remote_cmd:\n %s", remote_cmd)
        self._ShellCmdWithRetry(self._ssh_cmd + remote_cmd)
 
        # host_package
        remote_cmd = ("\"sudo su -c 'tar -x -z -f -' - '%s'\" < %s" %
                      (cvd_user, cvd_host_package_artifact))
        logger.debug("remote_cmd:\n %s", remote_cmd)
        self._ShellCmdWithRetry(self._ssh_cmd + remote_cmd)
 
    def _LaunchCvd(self, cvd_user, hw_property):
        """Launch CVD."""
        lunch_cvd_args = _CMD_LAUNCH_CVD_ARGS % (
            hw_property["cpu"],
            hw_property["x_res"],
            hw_property["y_res"],
            hw_property["dpi"],
            hw_property["memory"],
            hw_property["disk"])
        remote_cmd = ("\"sudo su -c 'bin/launch_cvd %s>&/dev/ttyS0&' - '%s'\"" %
                      (lunch_cvd_args, cvd_user))
        logger.debug("remote_cmd:\n %s", remote_cmd)
        subprocess.Popen(self._ssh_cmd + remote_cmd, shell=True)
 
 
class LocalImageRemoteInstance(base_avd_create.BaseAVDCreate):
    """Create class for a local image remote instance AVD.
 
    Attributes:
        local_image_artifact: A string, path to local image.
        cvd_host_package_artifact: A string, path to cvd host package.
    """
 
    def __init__(self):
        """LocalImageRemoteInstance initialize."""
        self.cvd_host_package_artifact = None
 
    def VerifyHostPackageArtifactsExist(self):
        """Verify the host package exists and return its path.
 
        Look for the host package in $ANDROID_HOST_OUT and dist dir.
 
        Return:
            A string, the path to the host package.
        """
        dirs_to_check = filter(None,
                               [os.environ.get(constants.ENV_ANDROID_HOST_OUT)])
        dist_dir = utils.GetDistDir()
        if dist_dir:
            dirs_to_check.append(dist_dir)
 
        cvd_host_package_artifact = self.GetCvdHostPackage(dirs_to_check)
        logger.debug("cvd host package: %s", cvd_host_package_artifact)
        return cvd_host_package_artifact
 
    @staticmethod
    def GetCvdHostPackage(paths):
        """Get cvd host package path.
 
        Args:
            paths: A list, holds the paths to check for the host package.
 
        Returns:
            String, full path of cvd host package.
 
        Raises:
            errors.GetCvdLocalHostPackageError: Can't find cvd host package.
        """
        for path in paths:
            cvd_host_package = os.path.join(path, _CVD_HOST_PACKAGE)
            if os.path.exists(cvd_host_package):
                return cvd_host_package
        raise errors.GetCvdLocalHostPackageError, (
            "Can't find the cvd host package (Try lunching a cuttlefish target"
            " like aosp_cf_x86_phone-userdebug and running 'm'): \n%s" %
            '\n'.join(paths))
 
    @utils.TimeExecute(function_description="Total time: ",
                       print_before_call=False, print_status=False)
    def _CreateAVD(self, avd_spec, no_prompts):
        """Create the AVD.
 
        Args:
            avd_spec: AVDSpec object that tells us what we're going to create.
            no_prompts: Boolean, True to skip all prompts.
        """
        self.cvd_host_package_artifact = self.VerifyHostPackageArtifactsExist()
 
        if avd_spec.local_image_artifact:
            local_image_artifact = avd_spec.local_image_artifact
        else:
            local_image_artifact = create_common.ZipCFImageFiles(
                avd_spec.local_image_dir)
 
        device_factory = RemoteInstanceDeviceFactory(
            avd_spec,
            local_image_artifact,
            self.cvd_host_package_artifact)
        report = common_operations.CreateDevices(
            "create_cf", avd_spec.cfg, device_factory, avd_spec.num,
            report_internal_ip=avd_spec.report_internal_ip,
            autoconnect=avd_spec.autoconnect,
            avd_type=constants.TYPE_CF)
        # Launch vnc client if we're auto-connecting.
        if avd_spec.autoconnect:
            utils.LaunchVNCFromReport(report, avd_spec, no_prompts)
        return report