xref: /aosp_15_r20/tools/acloud/internal/lib/utils.py (revision 800a58d989c669b8eb8a71d8df53b1ba3d411444)
1*800a58d9SAndroid Build Coastguard Worker# Copyright 2016 - The Android Open Source Project
2*800a58d9SAndroid Build Coastguard Worker#
3*800a58d9SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*800a58d9SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*800a58d9SAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*800a58d9SAndroid Build Coastguard Worker#
7*800a58d9SAndroid Build Coastguard Worker#     http://www.apache.org/licenses/LICENSE-2.0
8*800a58d9SAndroid Build Coastguard Worker#
9*800a58d9SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*800a58d9SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*800a58d9SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*800a58d9SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*800a58d9SAndroid Build Coastguard Worker# limitations under the License.
14*800a58d9SAndroid Build Coastguard Worker"""Common Utilities."""
15*800a58d9SAndroid Build Coastguard Worker# pylint: disable=too-many-lines
16*800a58d9SAndroid Build Coastguard Workerfrom __future__ import print_function
17*800a58d9SAndroid Build Coastguard Worker
18*800a58d9SAndroid Build Coastguard Workerimport base64
19*800a58d9SAndroid Build Coastguard Workerimport binascii
20*800a58d9SAndroid Build Coastguard Workerimport collections
21*800a58d9SAndroid Build Coastguard Workerimport errno
22*800a58d9SAndroid Build Coastguard Workerimport getpass
23*800a58d9SAndroid Build Coastguard Workerimport grp
24*800a58d9SAndroid Build Coastguard Workerimport logging
25*800a58d9SAndroid Build Coastguard Workerimport os
26*800a58d9SAndroid Build Coastguard Workerimport platform
27*800a58d9SAndroid Build Coastguard Workerimport re
28*800a58d9SAndroid Build Coastguard Workerimport shlex
29*800a58d9SAndroid Build Coastguard Workerimport shutil
30*800a58d9SAndroid Build Coastguard Workerimport signal
31*800a58d9SAndroid Build Coastguard Workerimport struct
32*800a58d9SAndroid Build Coastguard Workerimport socket
33*800a58d9SAndroid Build Coastguard Workerimport stat
34*800a58d9SAndroid Build Coastguard Workerimport subprocess
35*800a58d9SAndroid Build Coastguard Workerimport sys
36*800a58d9SAndroid Build Coastguard Workerimport tarfile
37*800a58d9SAndroid Build Coastguard Workerimport tempfile
38*800a58d9SAndroid Build Coastguard Workerimport time
39*800a58d9SAndroid Build Coastguard Workerimport uuid
40*800a58d9SAndroid Build Coastguard Workerimport webbrowser
41*800a58d9SAndroid Build Coastguard Workerimport zipfile
42*800a58d9SAndroid Build Coastguard Worker
43*800a58d9SAndroid Build Coastguard Workerfrom acloud import errors
44*800a58d9SAndroid Build Coastguard Workerfrom acloud.internal import constants
45*800a58d9SAndroid Build Coastguard Worker
46*800a58d9SAndroid Build Coastguard Worker
47*800a58d9SAndroid Build Coastguard Workerlogger = logging.getLogger(__name__)
48*800a58d9SAndroid Build Coastguard Worker
49*800a58d9SAndroid Build Coastguard WorkerSSH_KEYGEN_CMD = ["ssh-keygen", "-t", "rsa", "-b", "4096"]
50*800a58d9SAndroid Build Coastguard WorkerSSH_KEYGEN_PUB_CMD = ["ssh-keygen", "-y"]
51*800a58d9SAndroid Build Coastguard WorkerSSH_ARGS = ["-o", "UserKnownHostsFile=/dev/null",
52*800a58d9SAndroid Build Coastguard Worker            "-o", "StrictHostKeyChecking=no"]
53*800a58d9SAndroid Build Coastguard WorkerSSH_CMD = ["ssh"] + SSH_ARGS
54*800a58d9SAndroid Build Coastguard WorkerSCP_CMD = ["scp"] + SSH_ARGS
55*800a58d9SAndroid Build Coastguard WorkerGET_BUILD_VAR_CMD = ["build/soong/soong_ui.bash", "--dumpvar-mode"]
56*800a58d9SAndroid Build Coastguard WorkerDEFAULT_RETRY_BACKOFF_FACTOR = 1
57*800a58d9SAndroid Build Coastguard WorkerDEFAULT_SLEEP_MULTIPLIER = 0
58*800a58d9SAndroid Build Coastguard Worker
59*800a58d9SAndroid Build Coastguard Worker_SSH_TUNNEL_ARGS = (
60*800a58d9SAndroid Build Coastguard Worker    "-i %(rsa_key_file)s -o ControlPath=none -o UserKnownHostsFile=/dev/null "
61*800a58d9SAndroid Build Coastguard Worker    "-o StrictHostKeyChecking=no "
62*800a58d9SAndroid Build Coastguard Worker    "%(port_mapping)s"
63*800a58d9SAndroid Build Coastguard Worker    "-N -f -l %(ssh_user)s %(ip_addr)s")
64*800a58d9SAndroid Build Coastguard Worker_SSH_COMMAND_PS = (
65*800a58d9SAndroid Build Coastguard Worker    "exec %(ssh_bin)s -i %(rsa_key_file)s -o ControlPath=none "
66*800a58d9SAndroid Build Coastguard Worker    "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "
67*800a58d9SAndroid Build Coastguard Worker    "%(extra_args)s -l %(ssh_user)s %(ip_addr)s ps aux")
68*800a58d9SAndroid Build Coastguard WorkerPORT_MAPPING = "-L %(local_port)d:127.0.0.1:%(target_port)d "
69*800a58d9SAndroid Build Coastguard Worker_RELEASE_PORT_CMD = "kill $(lsof -t -i :%d)"
70*800a58d9SAndroid Build Coastguard Worker_WEBRTC_OPERATOR_PATTERN = re.compile(r"(.+)(webrtc_operator )(.+)")
71*800a58d9SAndroid Build Coastguard Worker_PORT_8443 = 8443
72*800a58d9SAndroid Build Coastguard Worker_PORT_1443 = 1443
73*800a58d9SAndroid Build Coastguard WorkerPortMapping = collections.namedtuple("PortMapping", ["local", "target"])
74*800a58d9SAndroid Build Coastguard Worker# Acloud uses only part of default webrtc port range to support both local and remote.
75*800a58d9SAndroid Build Coastguard Worker# The default webrtc port range is [15550, 15599].
76*800a58d9SAndroid Build Coastguard WorkerWEBRTC_PORT_START = 15555
77*800a58d9SAndroid Build Coastguard WorkerWEBRTC_PORT_END = 15579
78*800a58d9SAndroid Build Coastguard WorkerWEBRTC_PORTS_MAPPING = [PortMapping(port, port) for port in range(WEBRTC_PORT_START, WEBRTC_PORT_END + 1)]
79*800a58d9SAndroid Build Coastguard Worker_RE_GROUP_WEBRTC = "local_webrtc_port"
80*800a58d9SAndroid Build Coastguard Worker_RE_WEBRTC_SSH_TUNNEL_PATTERN = (
81*800a58d9SAndroid Build Coastguard Worker    r"((.*-L\s)(?P<local_webrtc_port>\d+):127.0.0.1:%s)(.+%s)")
82*800a58d9SAndroid Build Coastguard Worker_ADB_CONNECT_ARGS = "connect 127.0.0.1:%(adb_port)d"
83*800a58d9SAndroid Build Coastguard Worker# Store the ports that vnc/adb are forwarded to, both are integers.
84*800a58d9SAndroid Build Coastguard WorkerForwardedPorts = collections.namedtuple("ForwardedPorts", [constants.VNC_PORT,
85*800a58d9SAndroid Build Coastguard Worker                                                           constants.ADB_PORT])
86*800a58d9SAndroid Build Coastguard Worker
87*800a58d9SAndroid Build Coastguard WorkerAVD_PORT_DICT = {
88*800a58d9SAndroid Build Coastguard Worker    constants.TYPE_GCE: ForwardedPorts(constants.GCE_VNC_PORT,
89*800a58d9SAndroid Build Coastguard Worker                                       constants.GCE_ADB_PORT),
90*800a58d9SAndroid Build Coastguard Worker    constants.TYPE_CF: ForwardedPorts(constants.CF_VNC_PORT,
91*800a58d9SAndroid Build Coastguard Worker                                      constants.CF_ADB_PORT),
92*800a58d9SAndroid Build Coastguard Worker    constants.TYPE_GF: ForwardedPorts(constants.GF_VNC_PORT,
93*800a58d9SAndroid Build Coastguard Worker                                      constants.GF_ADB_PORT),
94*800a58d9SAndroid Build Coastguard Worker    constants.TYPE_CHEEPS: ForwardedPorts(constants.CHEEPS_VNC_PORT,
95*800a58d9SAndroid Build Coastguard Worker                                          constants.CHEEPS_ADB_PORT),
96*800a58d9SAndroid Build Coastguard Worker    constants.TYPE_FVP: ForwardedPorts(None, constants.FVP_ADB_PORT),
97*800a58d9SAndroid Build Coastguard Worker    constants.TYPE_TRUSTY: ForwardedPorts(None, constants.TRUSTY_ADB_PORT),
98*800a58d9SAndroid Build Coastguard Worker}
99*800a58d9SAndroid Build Coastguard Worker
100*800a58d9SAndroid Build Coastguard Worker_VNC_BIN = "ssvnc"
101*800a58d9SAndroid Build Coastguard Worker# search_dirs and the files can be symbolic links. The -H flag makes the
102*800a58d9SAndroid Build Coastguard Worker# command skip the links except search_dirs. The returned files are unique.
103*800a58d9SAndroid Build Coastguard Worker_CMD_FIND_FILES = "find -H %(search_dirs)s -type f"
104*800a58d9SAndroid Build Coastguard Worker_CMD_KILL = ["pkill", "-9", "-f"]
105*800a58d9SAndroid Build Coastguard Worker_CMD_SG = "sg "
106*800a58d9SAndroid Build Coastguard Worker_CMD_START_VNC = "%(bin)s vnc://127.0.0.1:%(port)d"
107*800a58d9SAndroid Build Coastguard Worker_CMD_INSTALL_SSVNC = "sudo apt-get --assume-yes install ssvnc"
108*800a58d9SAndroid Build Coastguard Worker_ENV_DISPLAY = "DISPLAY"
109*800a58d9SAndroid Build Coastguard Worker_SSVNC_ENV_VARS = {"SSVNC_NO_ENC_WARN": "1", "SSVNC_SCALE": "auto", "VNCVIEWER_X11CURSOR": "1"}
110*800a58d9SAndroid Build Coastguard Worker_DEFAULT_DISPLAY_SCALE = 1.0
111*800a58d9SAndroid Build Coastguard Worker_DIST_DIR = "DIST_DIR"
112*800a58d9SAndroid Build Coastguard Worker
113*800a58d9SAndroid Build Coastguard Worker# For webrtc
114*800a58d9SAndroid Build Coastguard Worker_WEBRTC_URL = "https://%(webrtc_ip)s:%(webrtc_port)d"
115*800a58d9SAndroid Build Coastguard Worker
116*800a58d9SAndroid Build Coastguard Worker_CONFIRM_CONTINUE = ("In order to display the screen to the AVD, we'll need to "
117*800a58d9SAndroid Build Coastguard Worker                     "install a vnc client (ssvnc). \nWould you like acloud to "
118*800a58d9SAndroid Build Coastguard Worker                     "install it for you? (%s) \nPress 'y' to continue or "
119*800a58d9SAndroid Build Coastguard Worker                     "anything else to abort it[y/N]: ") % _CMD_INSTALL_SSVNC
120*800a58d9SAndroid Build Coastguard Worker_EvaluatedResult = collections.namedtuple("EvaluatedResult",
121*800a58d9SAndroid Build Coastguard Worker                                          ["is_result_ok", "result_message"])
122*800a58d9SAndroid Build Coastguard Worker# dict of supported system and their distributions.
123*800a58d9SAndroid Build Coastguard Worker_SUPPORTED_SYSTEMS_AND_DISTS = {"Linux": ["Ubuntu", "ubuntu", "Debian", "debian"]}
124*800a58d9SAndroid Build Coastguard Worker_DEFAULT_TIMEOUT_ERR = "Function did not complete within %d secs."
125*800a58d9SAndroid Build Coastguard Worker_SSVNC_VIEWER_PATTERN = "vnc://127.0.0.1:%(vnc_port)d"
126*800a58d9SAndroid Build Coastguard Worker
127*800a58d9SAndroid Build Coastguard Worker# Determine the environment whether to support kvm.
128*800a58d9SAndroid Build Coastguard Worker_KVM_PATH = "/dev/kvm"
129*800a58d9SAndroid Build Coastguard Worker
130*800a58d9SAndroid Build Coastguard Worker
131*800a58d9SAndroid Build Coastguard Workerclass TempDir:
132*800a58d9SAndroid Build Coastguard Worker    """A context manager that ceates a temporary directory.
133*800a58d9SAndroid Build Coastguard Worker
134*800a58d9SAndroid Build Coastguard Worker    Attributes:
135*800a58d9SAndroid Build Coastguard Worker        path: The path of the temporary directory.
136*800a58d9SAndroid Build Coastguard Worker    """
137*800a58d9SAndroid Build Coastguard Worker
138*800a58d9SAndroid Build Coastguard Worker    def __init__(self):
139*800a58d9SAndroid Build Coastguard Worker        self.path = tempfile.mkdtemp()
140*800a58d9SAndroid Build Coastguard Worker        os.chmod(self.path, 0o700)
141*800a58d9SAndroid Build Coastguard Worker        logger.debug("Created temporary dir %s", self.path)
142*800a58d9SAndroid Build Coastguard Worker
143*800a58d9SAndroid Build Coastguard Worker    def __enter__(self):
144*800a58d9SAndroid Build Coastguard Worker        """Enter."""
145*800a58d9SAndroid Build Coastguard Worker        return self.path
146*800a58d9SAndroid Build Coastguard Worker
147*800a58d9SAndroid Build Coastguard Worker    def __exit__(self, exc_type, exc_value, traceback):
148*800a58d9SAndroid Build Coastguard Worker        """Exit.
149*800a58d9SAndroid Build Coastguard Worker
150*800a58d9SAndroid Build Coastguard Worker        Args:
151*800a58d9SAndroid Build Coastguard Worker            exc_type: Exception type raised within the context manager.
152*800a58d9SAndroid Build Coastguard Worker                      None if no execption is raised.
153*800a58d9SAndroid Build Coastguard Worker            exc_value: Exception instance raised within the context manager.
154*800a58d9SAndroid Build Coastguard Worker                       None if no execption is raised.
155*800a58d9SAndroid Build Coastguard Worker            traceback: Traceback for exeception that is raised within
156*800a58d9SAndroid Build Coastguard Worker                       the context manager.
157*800a58d9SAndroid Build Coastguard Worker                       None if no execption is raised.
158*800a58d9SAndroid Build Coastguard Worker        Raises:
159*800a58d9SAndroid Build Coastguard Worker            EnvironmentError or OSError when failed to delete temp directory.
160*800a58d9SAndroid Build Coastguard Worker        """
161*800a58d9SAndroid Build Coastguard Worker        try:
162*800a58d9SAndroid Build Coastguard Worker            if self.path:
163*800a58d9SAndroid Build Coastguard Worker                shutil.rmtree(self.path)
164*800a58d9SAndroid Build Coastguard Worker                logger.debug("Deleted temporary dir %s", self.path)
165*800a58d9SAndroid Build Coastguard Worker        except EnvironmentError as e:
166*800a58d9SAndroid Build Coastguard Worker            # Ignore error if there is no exception raised
167*800a58d9SAndroid Build Coastguard Worker            # within the with-clause and the EnvironementError is
168*800a58d9SAndroid Build Coastguard Worker            # about problem that directory or file does not exist.
169*800a58d9SAndroid Build Coastguard Worker            if not exc_type and e.errno != errno.ENOENT:
170*800a58d9SAndroid Build Coastguard Worker                raise
171*800a58d9SAndroid Build Coastguard Worker        except Exception as e:  # pylint: disable=W0703
172*800a58d9SAndroid Build Coastguard Worker            if exc_type:
173*800a58d9SAndroid Build Coastguard Worker                logger.error(
174*800a58d9SAndroid Build Coastguard Worker                    "Encountered error while deleting %s: %s",
175*800a58d9SAndroid Build Coastguard Worker                    self.path,
176*800a58d9SAndroid Build Coastguard Worker                    str(e),
177*800a58d9SAndroid Build Coastguard Worker                    exc_info=True)
178*800a58d9SAndroid Build Coastguard Worker            else:
179*800a58d9SAndroid Build Coastguard Worker                raise
180*800a58d9SAndroid Build Coastguard Worker
181*800a58d9SAndroid Build Coastguard Worker
182*800a58d9SAndroid Build Coastguard Workerdef RetryOnException(retry_checker,
183*800a58d9SAndroid Build Coastguard Worker                     max_retries,
184*800a58d9SAndroid Build Coastguard Worker                     sleep_multiplier=0,
185*800a58d9SAndroid Build Coastguard Worker                     retry_backoff_factor=1):
186*800a58d9SAndroid Build Coastguard Worker    """Decorater which retries the function call if |retry_checker| returns true.
187*800a58d9SAndroid Build Coastguard Worker
188*800a58d9SAndroid Build Coastguard Worker    Args:
189*800a58d9SAndroid Build Coastguard Worker        retry_checker: A callback function which should take an exception instance
190*800a58d9SAndroid Build Coastguard Worker                       and return True if functor(*args, **kwargs) should be retried
191*800a58d9SAndroid Build Coastguard Worker                       when such exception is raised, and return False if it should
192*800a58d9SAndroid Build Coastguard Worker                       not be retried.
193*800a58d9SAndroid Build Coastguard Worker        max_retries: Maximum number of retries allowed.
194*800a58d9SAndroid Build Coastguard Worker        sleep_multiplier: Will sleep sleep_multiplier * attempt_count seconds if
195*800a58d9SAndroid Build Coastguard Worker                          retry_backoff_factor is 1.  Will sleep
196*800a58d9SAndroid Build Coastguard Worker                          sleep_multiplier * (
197*800a58d9SAndroid Build Coastguard Worker                              retry_backoff_factor ** (attempt_count -  1))
198*800a58d9SAndroid Build Coastguard Worker                          if retry_backoff_factor != 1.
199*800a58d9SAndroid Build Coastguard Worker        retry_backoff_factor: See explanation of sleep_multiplier.
200*800a58d9SAndroid Build Coastguard Worker
201*800a58d9SAndroid Build Coastguard Worker    Returns:
202*800a58d9SAndroid Build Coastguard Worker        The function wrapper.
203*800a58d9SAndroid Build Coastguard Worker    """
204*800a58d9SAndroid Build Coastguard Worker
205*800a58d9SAndroid Build Coastguard Worker    def _Wrapper(func):
206*800a58d9SAndroid Build Coastguard Worker        def _FunctionWrapper(*args, **kwargs):
207*800a58d9SAndroid Build Coastguard Worker            return Retry(retry_checker, max_retries, func, sleep_multiplier,
208*800a58d9SAndroid Build Coastguard Worker                         retry_backoff_factor, *args, **kwargs)
209*800a58d9SAndroid Build Coastguard Worker
210*800a58d9SAndroid Build Coastguard Worker        return _FunctionWrapper
211*800a58d9SAndroid Build Coastguard Worker
212*800a58d9SAndroid Build Coastguard Worker    return _Wrapper
213*800a58d9SAndroid Build Coastguard Worker
214*800a58d9SAndroid Build Coastguard Worker
215*800a58d9SAndroid Build Coastguard Workerdef Retry(retry_checker, max_retries, functor, sleep_multiplier,
216*800a58d9SAndroid Build Coastguard Worker          retry_backoff_factor, *args, **kwargs):
217*800a58d9SAndroid Build Coastguard Worker    """Conditionally retry a function.
218*800a58d9SAndroid Build Coastguard Worker
219*800a58d9SAndroid Build Coastguard Worker    Args:
220*800a58d9SAndroid Build Coastguard Worker        retry_checker: A callback function which should take an exception instance
221*800a58d9SAndroid Build Coastguard Worker                       and return True if functor(*args, **kwargs) should be retried
222*800a58d9SAndroid Build Coastguard Worker                       when such exception is raised, and return False if it should
223*800a58d9SAndroid Build Coastguard Worker                       not be retried.
224*800a58d9SAndroid Build Coastguard Worker        max_retries: Maximum number of retries allowed.
225*800a58d9SAndroid Build Coastguard Worker        functor: The function to call, will call functor(*args, **kwargs).
226*800a58d9SAndroid Build Coastguard Worker        sleep_multiplier: Will sleep sleep_multiplier * attempt_count seconds if
227*800a58d9SAndroid Build Coastguard Worker                          retry_backoff_factor is 1.  Will sleep
228*800a58d9SAndroid Build Coastguard Worker                          sleep_multiplier * (
229*800a58d9SAndroid Build Coastguard Worker                              retry_backoff_factor ** (attempt_count -  1))
230*800a58d9SAndroid Build Coastguard Worker                          if retry_backoff_factor != 1.
231*800a58d9SAndroid Build Coastguard Worker        retry_backoff_factor: See explanation of sleep_multiplier.
232*800a58d9SAndroid Build Coastguard Worker        *args: Arguments to pass to the functor.
233*800a58d9SAndroid Build Coastguard Worker        **kwargs: Key-val based arguments to pass to the functor.
234*800a58d9SAndroid Build Coastguard Worker
235*800a58d9SAndroid Build Coastguard Worker    Returns:
236*800a58d9SAndroid Build Coastguard Worker        The return value of the functor.
237*800a58d9SAndroid Build Coastguard Worker
238*800a58d9SAndroid Build Coastguard Worker    Raises:
239*800a58d9SAndroid Build Coastguard Worker        Exception: The exception that functor(*args, **kwargs) throws.
240*800a58d9SAndroid Build Coastguard Worker    """
241*800a58d9SAndroid Build Coastguard Worker    attempt_count = 0
242*800a58d9SAndroid Build Coastguard Worker    while attempt_count <= max_retries:
243*800a58d9SAndroid Build Coastguard Worker        try:
244*800a58d9SAndroid Build Coastguard Worker            attempt_count += 1
245*800a58d9SAndroid Build Coastguard Worker            return_value = functor(*args, **kwargs)
246*800a58d9SAndroid Build Coastguard Worker            return return_value
247*800a58d9SAndroid Build Coastguard Worker        except Exception as e:  # pylint: disable=W0703
248*800a58d9SAndroid Build Coastguard Worker            if retry_checker(e) and attempt_count <= max_retries:
249*800a58d9SAndroid Build Coastguard Worker                if retry_backoff_factor != 1:
250*800a58d9SAndroid Build Coastguard Worker                    sleep = sleep_multiplier * (retry_backoff_factor**
251*800a58d9SAndroid Build Coastguard Worker                                                (attempt_count - 1))
252*800a58d9SAndroid Build Coastguard Worker                else:
253*800a58d9SAndroid Build Coastguard Worker                    sleep = sleep_multiplier * attempt_count
254*800a58d9SAndroid Build Coastguard Worker                time.sleep(sleep)
255*800a58d9SAndroid Build Coastguard Worker            else:
256*800a58d9SAndroid Build Coastguard Worker                raise
257*800a58d9SAndroid Build Coastguard Worker
258*800a58d9SAndroid Build Coastguard Worker
259*800a58d9SAndroid Build Coastguard Workerdef RetryExceptionType(exception_types, max_retries, functor, *args, **kwargs):
260*800a58d9SAndroid Build Coastguard Worker    """Retry exception if it is one of the given types.
261*800a58d9SAndroid Build Coastguard Worker
262*800a58d9SAndroid Build Coastguard Worker    Args:
263*800a58d9SAndroid Build Coastguard Worker        exception_types: A tuple of exception types, e.g. (ValueError, KeyError)
264*800a58d9SAndroid Build Coastguard Worker        max_retries: Max number of retries allowed.
265*800a58d9SAndroid Build Coastguard Worker        functor: The function to call. Will be retried if exception is raised and
266*800a58d9SAndroid Build Coastguard Worker                 the exception is one of the exception_types.
267*800a58d9SAndroid Build Coastguard Worker        *args: Arguments to pass to Retry function.
268*800a58d9SAndroid Build Coastguard Worker        **kwargs: Key-val based arguments to pass to Retry functions.
269*800a58d9SAndroid Build Coastguard Worker
270*800a58d9SAndroid Build Coastguard Worker    Returns:
271*800a58d9SAndroid Build Coastguard Worker        The value returned by calling functor.
272*800a58d9SAndroid Build Coastguard Worker    """
273*800a58d9SAndroid Build Coastguard Worker    return Retry(lambda e: isinstance(e, exception_types), max_retries,
274*800a58d9SAndroid Build Coastguard Worker                 functor, *args, **kwargs)
275*800a58d9SAndroid Build Coastguard Worker
276*800a58d9SAndroid Build Coastguard Worker
277*800a58d9SAndroid Build Coastguard Workerdef PollAndWait(func, expected_return, timeout_exception, timeout_secs,
278*800a58d9SAndroid Build Coastguard Worker                sleep_interval_secs, *args, **kwargs):
279*800a58d9SAndroid Build Coastguard Worker    """Call a function until the function returns expected value or times out.
280*800a58d9SAndroid Build Coastguard Worker
281*800a58d9SAndroid Build Coastguard Worker    Args:
282*800a58d9SAndroid Build Coastguard Worker        func: Function to call.
283*800a58d9SAndroid Build Coastguard Worker        expected_return: The expected return value.
284*800a58d9SAndroid Build Coastguard Worker        timeout_exception: Exception to raise when it hits timeout.
285*800a58d9SAndroid Build Coastguard Worker        timeout_secs: Timeout seconds.
286*800a58d9SAndroid Build Coastguard Worker                      If 0 or less than zero, the function will run once and
287*800a58d9SAndroid Build Coastguard Worker                      we will not wait on it.
288*800a58d9SAndroid Build Coastguard Worker        sleep_interval_secs: Time to sleep between two attemps.
289*800a58d9SAndroid Build Coastguard Worker        *args: list of args to pass to func.
290*800a58d9SAndroid Build Coastguard Worker        **kwargs: dictionary of keyword based args to pass to func.
291*800a58d9SAndroid Build Coastguard Worker
292*800a58d9SAndroid Build Coastguard Worker    Raises:
293*800a58d9SAndroid Build Coastguard Worker        timeout_exception: if the run of function times out.
294*800a58d9SAndroid Build Coastguard Worker    """
295*800a58d9SAndroid Build Coastguard Worker    # TODO(fdeng): Currently this method does not kill
296*800a58d9SAndroid Build Coastguard Worker    # |func|, if |func| takes longer than |timeout_secs|.
297*800a58d9SAndroid Build Coastguard Worker    # We can use a more robust version from chromite.
298*800a58d9SAndroid Build Coastguard Worker    start = time.time()
299*800a58d9SAndroid Build Coastguard Worker    while True:
300*800a58d9SAndroid Build Coastguard Worker        return_value = func(*args, **kwargs)
301*800a58d9SAndroid Build Coastguard Worker        if return_value == expected_return:
302*800a58d9SAndroid Build Coastguard Worker            return
303*800a58d9SAndroid Build Coastguard Worker        if time.time() - start > timeout_secs:
304*800a58d9SAndroid Build Coastguard Worker            raise timeout_exception
305*800a58d9SAndroid Build Coastguard Worker        if sleep_interval_secs > 0:
306*800a58d9SAndroid Build Coastguard Worker            time.sleep(sleep_interval_secs)
307*800a58d9SAndroid Build Coastguard Worker
308*800a58d9SAndroid Build Coastguard Worker
309*800a58d9SAndroid Build Coastguard Workerdef GenerateUniqueName(prefix=None, suffix=None):
310*800a58d9SAndroid Build Coastguard Worker    """Generate a random unique name using uuid4.
311*800a58d9SAndroid Build Coastguard Worker
312*800a58d9SAndroid Build Coastguard Worker    Args:
313*800a58d9SAndroid Build Coastguard Worker        prefix: String, desired prefix to prepend to the generated name.
314*800a58d9SAndroid Build Coastguard Worker        suffix: String, desired suffix to append to the generated name.
315*800a58d9SAndroid Build Coastguard Worker
316*800a58d9SAndroid Build Coastguard Worker    Returns:
317*800a58d9SAndroid Build Coastguard Worker        String, a random name.
318*800a58d9SAndroid Build Coastguard Worker    """
319*800a58d9SAndroid Build Coastguard Worker    name = uuid.uuid4().hex
320*800a58d9SAndroid Build Coastguard Worker    if prefix:
321*800a58d9SAndroid Build Coastguard Worker        name = "-".join([prefix, name])
322*800a58d9SAndroid Build Coastguard Worker    if suffix:
323*800a58d9SAndroid Build Coastguard Worker        name = "-".join([name, suffix])
324*800a58d9SAndroid Build Coastguard Worker    return name
325*800a58d9SAndroid Build Coastguard Worker
326*800a58d9SAndroid Build Coastguard Worker
327*800a58d9SAndroid Build Coastguard Workerdef MakeTarFile(src_dict, dest):
328*800a58d9SAndroid Build Coastguard Worker    """Archive files in tar.gz format to a file named as |dest|.
329*800a58d9SAndroid Build Coastguard Worker
330*800a58d9SAndroid Build Coastguard Worker    Args:
331*800a58d9SAndroid Build Coastguard Worker        src_dict: A dictionary that maps a path to be archived
332*800a58d9SAndroid Build Coastguard Worker                  to the corresponding name that appears in the archive.
333*800a58d9SAndroid Build Coastguard Worker        dest: String, path to output file, e.g. /tmp/myfile.tar.gz
334*800a58d9SAndroid Build Coastguard Worker    """
335*800a58d9SAndroid Build Coastguard Worker    logger.info("Compressing %s into %s.", src_dict.keys(), dest)
336*800a58d9SAndroid Build Coastguard Worker    with tarfile.open(dest, "w:gz") as tar:
337*800a58d9SAndroid Build Coastguard Worker        for src, arcname in src_dict.items():
338*800a58d9SAndroid Build Coastguard Worker            tar.add(src, arcname=arcname)
339*800a58d9SAndroid Build Coastguard Worker
340*800a58d9SAndroid Build Coastguard Workerdef CreateSshKeyPairIfNotExist(private_key_path, public_key_path):
341*800a58d9SAndroid Build Coastguard Worker    """Create the ssh key pair if they don't exist.
342*800a58d9SAndroid Build Coastguard Worker
343*800a58d9SAndroid Build Coastguard Worker    Case1. If the private key doesn't exist, we will create both the public key
344*800a58d9SAndroid Build Coastguard Worker           and the private key.
345*800a58d9SAndroid Build Coastguard Worker    Case2. If the private key exists but public key doesn't, we will create the
346*800a58d9SAndroid Build Coastguard Worker           public key by using the private key.
347*800a58d9SAndroid Build Coastguard Worker    Case3. If the public key exists but the private key doesn't, we will create
348*800a58d9SAndroid Build Coastguard Worker           a new private key and overwrite the public key.
349*800a58d9SAndroid Build Coastguard Worker
350*800a58d9SAndroid Build Coastguard Worker    Args:
351*800a58d9SAndroid Build Coastguard Worker        private_key_path: Path to the private key file.
352*800a58d9SAndroid Build Coastguard Worker                          e.g. ~/.ssh/acloud_rsa
353*800a58d9SAndroid Build Coastguard Worker        public_key_path: Path to the public key file.
354*800a58d9SAndroid Build Coastguard Worker                         e.g. ~/.ssh/acloud_rsa.pub
355*800a58d9SAndroid Build Coastguard Worker
356*800a58d9SAndroid Build Coastguard Worker    Raises:
357*800a58d9SAndroid Build Coastguard Worker        error.DriverError: If failed to create the key pair.
358*800a58d9SAndroid Build Coastguard Worker    """
359*800a58d9SAndroid Build Coastguard Worker    public_key_path = os.path.expanduser(public_key_path)
360*800a58d9SAndroid Build Coastguard Worker    private_key_path = os.path.expanduser(private_key_path)
361*800a58d9SAndroid Build Coastguard Worker    public_key_exist = os.path.exists(public_key_path)
362*800a58d9SAndroid Build Coastguard Worker    private_key_exist = os.path.exists(private_key_path)
363*800a58d9SAndroid Build Coastguard Worker    if public_key_exist and private_key_exist:
364*800a58d9SAndroid Build Coastguard Worker        logger.debug(
365*800a58d9SAndroid Build Coastguard Worker            "The ssh private key (%s) and public key (%s) already exist,"
366*800a58d9SAndroid Build Coastguard Worker            "will not automatically create the key pairs.", private_key_path,
367*800a58d9SAndroid Build Coastguard Worker            public_key_path)
368*800a58d9SAndroid Build Coastguard Worker        return
369*800a58d9SAndroid Build Coastguard Worker    key_folder = os.path.dirname(private_key_path)
370*800a58d9SAndroid Build Coastguard Worker    if not os.path.exists(key_folder):
371*800a58d9SAndroid Build Coastguard Worker        os.makedirs(key_folder)
372*800a58d9SAndroid Build Coastguard Worker    try:
373*800a58d9SAndroid Build Coastguard Worker        if private_key_exist:
374*800a58d9SAndroid Build Coastguard Worker            cmd = SSH_KEYGEN_PUB_CMD + ["-f", private_key_path]
375*800a58d9SAndroid Build Coastguard Worker            with open(public_key_path, 'w') as outfile:
376*800a58d9SAndroid Build Coastguard Worker                stream_content = CheckOutput(cmd)
377*800a58d9SAndroid Build Coastguard Worker                outfile.write(
378*800a58d9SAndroid Build Coastguard Worker                    stream_content.rstrip('\n') + " " + getpass.getuser())
379*800a58d9SAndroid Build Coastguard Worker            logger.info(
380*800a58d9SAndroid Build Coastguard Worker                "The ssh public key (%s) do not exist, "
381*800a58d9SAndroid Build Coastguard Worker                "automatically creating public key, calling: %s",
382*800a58d9SAndroid Build Coastguard Worker                public_key_path, " ".join(cmd))
383*800a58d9SAndroid Build Coastguard Worker        else:
384*800a58d9SAndroid Build Coastguard Worker            cmd = SSH_KEYGEN_CMD + [
385*800a58d9SAndroid Build Coastguard Worker                "-C", getpass.getuser(), "-f", private_key_path
386*800a58d9SAndroid Build Coastguard Worker            ]
387*800a58d9SAndroid Build Coastguard Worker            logger.info(
388*800a58d9SAndroid Build Coastguard Worker                "Creating public key from private key (%s) via cmd: %s",
389*800a58d9SAndroid Build Coastguard Worker                private_key_path, " ".join(cmd))
390*800a58d9SAndroid Build Coastguard Worker            subprocess.check_call(cmd, stdout=sys.stderr, stderr=sys.stdout)
391*800a58d9SAndroid Build Coastguard Worker    except subprocess.CalledProcessError as e:
392*800a58d9SAndroid Build Coastguard Worker        raise errors.DriverError("Failed to create ssh key pair: %s" % str(e))
393*800a58d9SAndroid Build Coastguard Worker    except OSError as e:
394*800a58d9SAndroid Build Coastguard Worker        raise errors.DriverError(
395*800a58d9SAndroid Build Coastguard Worker            "Failed to create ssh key pair, please make sure "
396*800a58d9SAndroid Build Coastguard Worker            "'ssh-keygen' is installed: %s" % str(e))
397*800a58d9SAndroid Build Coastguard Worker
398*800a58d9SAndroid Build Coastguard Worker    # By default ssh-keygen will create a public key file
399*800a58d9SAndroid Build Coastguard Worker    # by append .pub to the private key file name. Rename it
400*800a58d9SAndroid Build Coastguard Worker    # to what's requested by public_key_path.
401*800a58d9SAndroid Build Coastguard Worker    default_pub_key_path = "%s.pub" % private_key_path
402*800a58d9SAndroid Build Coastguard Worker    try:
403*800a58d9SAndroid Build Coastguard Worker        if default_pub_key_path != public_key_path:
404*800a58d9SAndroid Build Coastguard Worker            os.rename(default_pub_key_path, public_key_path)
405*800a58d9SAndroid Build Coastguard Worker    except OSError as e:
406*800a58d9SAndroid Build Coastguard Worker        raise errors.DriverError(
407*800a58d9SAndroid Build Coastguard Worker            "Failed to rename %s to %s: %s" % (default_pub_key_path,
408*800a58d9SAndroid Build Coastguard Worker                                               public_key_path, str(e)))
409*800a58d9SAndroid Build Coastguard Worker
410*800a58d9SAndroid Build Coastguard Worker    logger.info("Created ssh private key (%s) and public key (%s)",
411*800a58d9SAndroid Build Coastguard Worker                private_key_path, public_key_path)
412*800a58d9SAndroid Build Coastguard Worker
413*800a58d9SAndroid Build Coastguard Worker
414*800a58d9SAndroid Build Coastguard Workerdef VerifyRsaPubKey(rsa):
415*800a58d9SAndroid Build Coastguard Worker    """Verify the format of rsa public key.
416*800a58d9SAndroid Build Coastguard Worker
417*800a58d9SAndroid Build Coastguard Worker    Args:
418*800a58d9SAndroid Build Coastguard Worker        rsa: content of rsa public key. It should follow the format of
419*800a58d9SAndroid Build Coastguard Worker             ssh-rsa AAAAB3NzaC1yc2EA.... test@test.com
420*800a58d9SAndroid Build Coastguard Worker
421*800a58d9SAndroid Build Coastguard Worker    Raises:
422*800a58d9SAndroid Build Coastguard Worker        DriverError if the format is not correct.
423*800a58d9SAndroid Build Coastguard Worker    """
424*800a58d9SAndroid Build Coastguard Worker    if not rsa or not all(ord(c) < 128 for c in rsa):
425*800a58d9SAndroid Build Coastguard Worker        raise errors.DriverError(
426*800a58d9SAndroid Build Coastguard Worker            "rsa key is empty or contains non-ascii character: %s" % rsa)
427*800a58d9SAndroid Build Coastguard Worker
428*800a58d9SAndroid Build Coastguard Worker    elements = rsa.split()
429*800a58d9SAndroid Build Coastguard Worker    if len(elements) != 3:
430*800a58d9SAndroid Build Coastguard Worker        raise errors.DriverError("rsa key is invalid, wrong format: %s" % rsa)
431*800a58d9SAndroid Build Coastguard Worker
432*800a58d9SAndroid Build Coastguard Worker    key_type, data, _ = elements
433*800a58d9SAndroid Build Coastguard Worker    try:
434*800a58d9SAndroid Build Coastguard Worker        binary_data = base64.decodebytes(data.encode())
435*800a58d9SAndroid Build Coastguard Worker        # number of bytes of int type
436*800a58d9SAndroid Build Coastguard Worker        int_length = 4
437*800a58d9SAndroid Build Coastguard Worker        # binary_data is like "7ssh-key..." in a binary format.
438*800a58d9SAndroid Build Coastguard Worker        # The first 4 bytes should represent 7, which should be
439*800a58d9SAndroid Build Coastguard Worker        # the length of the following string "ssh-key".
440*800a58d9SAndroid Build Coastguard Worker        # And the next 7 bytes should be string "ssh-key".
441*800a58d9SAndroid Build Coastguard Worker        # We will verify that the rsa conforms to this format.
442*800a58d9SAndroid Build Coastguard Worker        # ">I" in the following line means "big-endian unsigned integer".
443*800a58d9SAndroid Build Coastguard Worker        type_length = struct.unpack(">I", binary_data[:int_length])[0]
444*800a58d9SAndroid Build Coastguard Worker        if binary_data[int_length:int_length + type_length] != key_type.encode():
445*800a58d9SAndroid Build Coastguard Worker            raise errors.DriverError("rsa key is invalid: %s" % rsa)
446*800a58d9SAndroid Build Coastguard Worker    except (struct.error, binascii.Error) as e:
447*800a58d9SAndroid Build Coastguard Worker        raise errors.DriverError(
448*800a58d9SAndroid Build Coastguard Worker            "rsa key is invalid: %s, error: %s" % (rsa, str(e)))
449*800a58d9SAndroid Build Coastguard Worker
450*800a58d9SAndroid Build Coastguard Worker
451*800a58d9SAndroid Build Coastguard Workerdef Decompress(sourcefile, dest=None):
452*800a58d9SAndroid Build Coastguard Worker    """Decompress .zip or .tar.gz.
453*800a58d9SAndroid Build Coastguard Worker
454*800a58d9SAndroid Build Coastguard Worker    Args:
455*800a58d9SAndroid Build Coastguard Worker        sourcefile: A string, a source file path to decompress.
456*800a58d9SAndroid Build Coastguard Worker        dest: A string, a folder path as decompress destination.
457*800a58d9SAndroid Build Coastguard Worker
458*800a58d9SAndroid Build Coastguard Worker    Raises:
459*800a58d9SAndroid Build Coastguard Worker        errors.UnsupportedCompressionFileType: Not supported extension.
460*800a58d9SAndroid Build Coastguard Worker    """
461*800a58d9SAndroid Build Coastguard Worker    logger.info("Start to decompress %s!", sourcefile)
462*800a58d9SAndroid Build Coastguard Worker    dest_path = dest if dest else "."
463*800a58d9SAndroid Build Coastguard Worker    if sourcefile.endswith(".tar.gz"):
464*800a58d9SAndroid Build Coastguard Worker        with tarfile.open(sourcefile, "r:gz") as compressor:
465*800a58d9SAndroid Build Coastguard Worker            compressor.extractall(dest_path)
466*800a58d9SAndroid Build Coastguard Worker    elif sourcefile.endswith(".zip"):
467*800a58d9SAndroid Build Coastguard Worker        with zipfile.ZipFile(sourcefile, 'r') as compressor:
468*800a58d9SAndroid Build Coastguard Worker            compressor.extractall(dest_path)
469*800a58d9SAndroid Build Coastguard Worker    else:
470*800a58d9SAndroid Build Coastguard Worker        raise errors.UnsupportedCompressionFileType(
471*800a58d9SAndroid Build Coastguard Worker            "Sorry, we could only support compression file type "
472*800a58d9SAndroid Build Coastguard Worker            "for zip or tar.gz.")
473*800a58d9SAndroid Build Coastguard Worker
474*800a58d9SAndroid Build Coastguard Worker
475*800a58d9SAndroid Build Coastguard Worker# pylint: disable=no-init
476*800a58d9SAndroid Build Coastguard Workerclass TextColors:
477*800a58d9SAndroid Build Coastguard Worker    """A class that defines common color ANSI code."""
478*800a58d9SAndroid Build Coastguard Worker
479*800a58d9SAndroid Build Coastguard Worker    HEADER = "\033[95m"
480*800a58d9SAndroid Build Coastguard Worker    OKBLUE = "\033[94m"
481*800a58d9SAndroid Build Coastguard Worker    OKGREEN = "\033[92m"
482*800a58d9SAndroid Build Coastguard Worker    WARNING = "\033[33m"
483*800a58d9SAndroid Build Coastguard Worker    FAIL = "\033[91m"
484*800a58d9SAndroid Build Coastguard Worker    ENDC = "\033[0m"
485*800a58d9SAndroid Build Coastguard Worker    BOLD = "\033[1m"
486*800a58d9SAndroid Build Coastguard Worker    UNDERLINE = "\033[4m"
487*800a58d9SAndroid Build Coastguard Worker
488*800a58d9SAndroid Build Coastguard Worker
489*800a58d9SAndroid Build Coastguard Workerdef PrintColorString(message, colors=TextColors.OKBLUE, **kwargs):
490*800a58d9SAndroid Build Coastguard Worker    """A helper function to print out colored text.
491*800a58d9SAndroid Build Coastguard Worker
492*800a58d9SAndroid Build Coastguard Worker    Use print function "print(message, end="")" to show message in one line.
493*800a58d9SAndroid Build Coastguard Worker    Example code:
494*800a58d9SAndroid Build Coastguard Worker        DisplayMessages("Creating GCE instance...", end="")
495*800a58d9SAndroid Build Coastguard Worker        # Job execute 20s
496*800a58d9SAndroid Build Coastguard Worker        DisplayMessages("Done! (20s)")
497*800a58d9SAndroid Build Coastguard Worker    Display:
498*800a58d9SAndroid Build Coastguard Worker        Creating GCE instance...
499*800a58d9SAndroid Build Coastguard Worker        # After job finished, messages update as following:
500*800a58d9SAndroid Build Coastguard Worker        Creating GCE instance...Done! (20s)
501*800a58d9SAndroid Build Coastguard Worker
502*800a58d9SAndroid Build Coastguard Worker    Args:
503*800a58d9SAndroid Build Coastguard Worker        message: String, the message text.
504*800a58d9SAndroid Build Coastguard Worker        colors: String, color code.
505*800a58d9SAndroid Build Coastguard Worker        **kwargs: dictionary of keyword based args to pass to func.
506*800a58d9SAndroid Build Coastguard Worker    """
507*800a58d9SAndroid Build Coastguard Worker    print(colors + message + TextColors.ENDC, **kwargs)
508*800a58d9SAndroid Build Coastguard Worker    sys.stdout.flush()
509*800a58d9SAndroid Build Coastguard Worker
510*800a58d9SAndroid Build Coastguard Worker
511*800a58d9SAndroid Build Coastguard Workerdef InteractWithQuestion(question, colors=TextColors.WARNING):
512*800a58d9SAndroid Build Coastguard Worker    """A helper function to define the common way to run interactive cmd.
513*800a58d9SAndroid Build Coastguard Worker
514*800a58d9SAndroid Build Coastguard Worker    Args:
515*800a58d9SAndroid Build Coastguard Worker        question: String, the question to ask user.
516*800a58d9SAndroid Build Coastguard Worker        colors: String, color code.
517*800a58d9SAndroid Build Coastguard Worker
518*800a58d9SAndroid Build Coastguard Worker    Returns:
519*800a58d9SAndroid Build Coastguard Worker        String, input from user.
520*800a58d9SAndroid Build Coastguard Worker    """
521*800a58d9SAndroid Build Coastguard Worker    return str(input(colors + question + TextColors.ENDC).strip())
522*800a58d9SAndroid Build Coastguard Worker
523*800a58d9SAndroid Build Coastguard Worker
524*800a58d9SAndroid Build Coastguard Workerdef GetUserAnswerYes(question):
525*800a58d9SAndroid Build Coastguard Worker    """Ask user about acloud setup question.
526*800a58d9SAndroid Build Coastguard Worker
527*800a58d9SAndroid Build Coastguard Worker    Args:
528*800a58d9SAndroid Build Coastguard Worker        question: String of question for user. Enter is equivalent to pressing
529*800a58d9SAndroid Build Coastguard Worker                  n. We should hint user with upper case N surrounded in square
530*800a58d9SAndroid Build Coastguard Worker                  brackets.
531*800a58d9SAndroid Build Coastguard Worker                  Ex: "Are you sure to change bucket name[y/N]:"
532*800a58d9SAndroid Build Coastguard Worker
533*800a58d9SAndroid Build Coastguard Worker    Returns:
534*800a58d9SAndroid Build Coastguard Worker        Boolean, True if answer is "Yes", False otherwise.
535*800a58d9SAndroid Build Coastguard Worker    """
536*800a58d9SAndroid Build Coastguard Worker    answer = InteractWithQuestion(question)
537*800a58d9SAndroid Build Coastguard Worker    return answer.lower() in constants.USER_ANSWER_YES
538*800a58d9SAndroid Build Coastguard Worker
539*800a58d9SAndroid Build Coastguard Worker
540*800a58d9SAndroid Build Coastguard Workerclass BatchHttpRequestExecutor:
541*800a58d9SAndroid Build Coastguard Worker    """A helper class that executes requests in batch with retry.
542*800a58d9SAndroid Build Coastguard Worker
543*800a58d9SAndroid Build Coastguard Worker    This executor executes http requests in a batch and retry
544*800a58d9SAndroid Build Coastguard Worker    those that have failed. It iteratively updates the dictionary
545*800a58d9SAndroid Build Coastguard Worker    self._final_results with latest results, which can be retrieved
546*800a58d9SAndroid Build Coastguard Worker    via GetResults.
547*800a58d9SAndroid Build Coastguard Worker    """
548*800a58d9SAndroid Build Coastguard Worker
549*800a58d9SAndroid Build Coastguard Worker    def __init__(self,
550*800a58d9SAndroid Build Coastguard Worker                 execute_once_functor,
551*800a58d9SAndroid Build Coastguard Worker                 requests,
552*800a58d9SAndroid Build Coastguard Worker                 retry_http_codes=None,
553*800a58d9SAndroid Build Coastguard Worker                 max_retry=None,
554*800a58d9SAndroid Build Coastguard Worker                 sleep=None,
555*800a58d9SAndroid Build Coastguard Worker                 backoff_factor=None,
556*800a58d9SAndroid Build Coastguard Worker                 other_retriable_errors=None):
557*800a58d9SAndroid Build Coastguard Worker        """Initializes the executor.
558*800a58d9SAndroid Build Coastguard Worker
559*800a58d9SAndroid Build Coastguard Worker        Args:
560*800a58d9SAndroid Build Coastguard Worker            execute_once_functor: A function that execute requests in batch once.
561*800a58d9SAndroid Build Coastguard Worker                                  It should return a dictionary like
562*800a58d9SAndroid Build Coastguard Worker                                  {request_id: (response, exception)}
563*800a58d9SAndroid Build Coastguard Worker            requests: A dictionary where key is request id picked by caller,
564*800a58d9SAndroid Build Coastguard Worker                      and value is a apiclient.http.HttpRequest.
565*800a58d9SAndroid Build Coastguard Worker            retry_http_codes: A list of http codes to retry.
566*800a58d9SAndroid Build Coastguard Worker            max_retry: See utils.Retry.
567*800a58d9SAndroid Build Coastguard Worker            sleep: See utils.Retry.
568*800a58d9SAndroid Build Coastguard Worker            backoff_factor: See utils.Retry.
569*800a58d9SAndroid Build Coastguard Worker            other_retriable_errors: A tuple of error types that should be retried
570*800a58d9SAndroid Build Coastguard Worker                                    other than errors.HttpError.
571*800a58d9SAndroid Build Coastguard Worker        """
572*800a58d9SAndroid Build Coastguard Worker        self._execute_once_functor = execute_once_functor
573*800a58d9SAndroid Build Coastguard Worker        self._requests = requests
574*800a58d9SAndroid Build Coastguard Worker        # A dictionary that maps request id to pending request.
575*800a58d9SAndroid Build Coastguard Worker        self._pending_requests = {}
576*800a58d9SAndroid Build Coastguard Worker        # A dictionary that maps request id to a tuple (response, exception).
577*800a58d9SAndroid Build Coastguard Worker        self._final_results = {}
578*800a58d9SAndroid Build Coastguard Worker        self._retry_http_codes = retry_http_codes
579*800a58d9SAndroid Build Coastguard Worker        self._max_retry = max_retry
580*800a58d9SAndroid Build Coastguard Worker        self._sleep = sleep
581*800a58d9SAndroid Build Coastguard Worker        self._backoff_factor = backoff_factor
582*800a58d9SAndroid Build Coastguard Worker        self._other_retriable_errors = other_retriable_errors
583*800a58d9SAndroid Build Coastguard Worker
584*800a58d9SAndroid Build Coastguard Worker    def _ShoudRetry(self, exception):
585*800a58d9SAndroid Build Coastguard Worker        """Check if an exception is retriable.
586*800a58d9SAndroid Build Coastguard Worker
587*800a58d9SAndroid Build Coastguard Worker        Args:
588*800a58d9SAndroid Build Coastguard Worker            exception: An exception instance.
589*800a58d9SAndroid Build Coastguard Worker        """
590*800a58d9SAndroid Build Coastguard Worker        if isinstance(exception, self._other_retriable_errors):
591*800a58d9SAndroid Build Coastguard Worker            return True
592*800a58d9SAndroid Build Coastguard Worker
593*800a58d9SAndroid Build Coastguard Worker        if (isinstance(exception, errors.HttpError)
594*800a58d9SAndroid Build Coastguard Worker                and exception.code in self._retry_http_codes):
595*800a58d9SAndroid Build Coastguard Worker            return True
596*800a58d9SAndroid Build Coastguard Worker        return False
597*800a58d9SAndroid Build Coastguard Worker
598*800a58d9SAndroid Build Coastguard Worker    def _ExecuteOnce(self):
599*800a58d9SAndroid Build Coastguard Worker        """Executes pending requests and update it with failed, retriable ones.
600*800a58d9SAndroid Build Coastguard Worker
601*800a58d9SAndroid Build Coastguard Worker        Raises:
602*800a58d9SAndroid Build Coastguard Worker            HasRetriableRequestsError: if some requests fail and are retriable.
603*800a58d9SAndroid Build Coastguard Worker        """
604*800a58d9SAndroid Build Coastguard Worker        results = self._execute_once_functor(self._pending_requests)
605*800a58d9SAndroid Build Coastguard Worker        # Update final_results with latest results.
606*800a58d9SAndroid Build Coastguard Worker        self._final_results.update(results)
607*800a58d9SAndroid Build Coastguard Worker        # Clear pending_requests
608*800a58d9SAndroid Build Coastguard Worker        self._pending_requests.clear()
609*800a58d9SAndroid Build Coastguard Worker        for request_id, result in results.items():
610*800a58d9SAndroid Build Coastguard Worker            exception = result[1]
611*800a58d9SAndroid Build Coastguard Worker            if exception is not None and self._ShoudRetry(exception):
612*800a58d9SAndroid Build Coastguard Worker                # If this is a retriable exception, put it in pending_requests
613*800a58d9SAndroid Build Coastguard Worker                self._pending_requests[request_id] = self._requests[request_id]
614*800a58d9SAndroid Build Coastguard Worker        if self._pending_requests:
615*800a58d9SAndroid Build Coastguard Worker            # If there is still retriable requests pending, raise an error
616*800a58d9SAndroid Build Coastguard Worker            # so that Retry will retry this function with pending_requests.
617*800a58d9SAndroid Build Coastguard Worker            raise errors.HasRetriableRequestsError(
618*800a58d9SAndroid Build Coastguard Worker                "Retriable errors: %s" %
619*800a58d9SAndroid Build Coastguard Worker                [str(results[rid][1]) for rid in self._pending_requests])
620*800a58d9SAndroid Build Coastguard Worker
621*800a58d9SAndroid Build Coastguard Worker    def Execute(self):
622*800a58d9SAndroid Build Coastguard Worker        """Executes the requests and retry if necessary.
623*800a58d9SAndroid Build Coastguard Worker
624*800a58d9SAndroid Build Coastguard Worker        Will populate self._final_results.
625*800a58d9SAndroid Build Coastguard Worker        """
626*800a58d9SAndroid Build Coastguard Worker
627*800a58d9SAndroid Build Coastguard Worker        def _ShouldRetryHandler(exc):
628*800a58d9SAndroid Build Coastguard Worker            """Check if |exc| is a retriable exception.
629*800a58d9SAndroid Build Coastguard Worker
630*800a58d9SAndroid Build Coastguard Worker            Args:
631*800a58d9SAndroid Build Coastguard Worker                exc: An exception.
632*800a58d9SAndroid Build Coastguard Worker
633*800a58d9SAndroid Build Coastguard Worker            Returns:
634*800a58d9SAndroid Build Coastguard Worker                True if exception is of type HasRetriableRequestsError; False otherwise.
635*800a58d9SAndroid Build Coastguard Worker            """
636*800a58d9SAndroid Build Coastguard Worker            should_retry = isinstance(exc, errors.HasRetriableRequestsError)
637*800a58d9SAndroid Build Coastguard Worker            if should_retry:
638*800a58d9SAndroid Build Coastguard Worker                logger.info("Will retry failed requests.", exc_info=True)
639*800a58d9SAndroid Build Coastguard Worker                logger.info("%s", exc)
640*800a58d9SAndroid Build Coastguard Worker            return should_retry
641*800a58d9SAndroid Build Coastguard Worker
642*800a58d9SAndroid Build Coastguard Worker        try:
643*800a58d9SAndroid Build Coastguard Worker            self._pending_requests = self._requests.copy()
644*800a58d9SAndroid Build Coastguard Worker            Retry(
645*800a58d9SAndroid Build Coastguard Worker                _ShouldRetryHandler,
646*800a58d9SAndroid Build Coastguard Worker                max_retries=self._max_retry,
647*800a58d9SAndroid Build Coastguard Worker                functor=self._ExecuteOnce,
648*800a58d9SAndroid Build Coastguard Worker                sleep_multiplier=self._sleep,
649*800a58d9SAndroid Build Coastguard Worker                retry_backoff_factor=self._backoff_factor)
650*800a58d9SAndroid Build Coastguard Worker        except errors.HasRetriableRequestsError:
651*800a58d9SAndroid Build Coastguard Worker            logger.debug("Some requests did not succeed after retry.")
652*800a58d9SAndroid Build Coastguard Worker
653*800a58d9SAndroid Build Coastguard Worker    def GetResults(self):
654*800a58d9SAndroid Build Coastguard Worker        """Returns final results.
655*800a58d9SAndroid Build Coastguard Worker
656*800a58d9SAndroid Build Coastguard Worker        Returns:
657*800a58d9SAndroid Build Coastguard Worker            results, a dictionary in the following format
658*800a58d9SAndroid Build Coastguard Worker            {request_id: (response, exception)}
659*800a58d9SAndroid Build Coastguard Worker            request_ids are those from requests; response
660*800a58d9SAndroid Build Coastguard Worker            is the http response for the request or None on error;
661*800a58d9SAndroid Build Coastguard Worker            exception is an instance of DriverError or None if no error.
662*800a58d9SAndroid Build Coastguard Worker        """
663*800a58d9SAndroid Build Coastguard Worker        return self._final_results
664*800a58d9SAndroid Build Coastguard Worker
665*800a58d9SAndroid Build Coastguard Worker
666*800a58d9SAndroid Build Coastguard Workerdef DefaultEvaluator(result):
667*800a58d9SAndroid Build Coastguard Worker    """Default Evaluator always return result is ok.
668*800a58d9SAndroid Build Coastguard Worker
669*800a58d9SAndroid Build Coastguard Worker    Args:
670*800a58d9SAndroid Build Coastguard Worker        result:the return value of the target function.
671*800a58d9SAndroid Build Coastguard Worker
672*800a58d9SAndroid Build Coastguard Worker    Returns:
673*800a58d9SAndroid Build Coastguard Worker        _EvaluatedResults namedtuple.
674*800a58d9SAndroid Build Coastguard Worker    """
675*800a58d9SAndroid Build Coastguard Worker    return _EvaluatedResult(is_result_ok=True, result_message=result)
676*800a58d9SAndroid Build Coastguard Worker
677*800a58d9SAndroid Build Coastguard Worker
678*800a58d9SAndroid Build Coastguard Workerdef ReportEvaluator(report):
679*800a58d9SAndroid Build Coastguard Worker    """Evalute the acloud operation by the report.
680*800a58d9SAndroid Build Coastguard Worker
681*800a58d9SAndroid Build Coastguard Worker    Args:
682*800a58d9SAndroid Build Coastguard Worker        report: acloud.public.report() object.
683*800a58d9SAndroid Build Coastguard Worker
684*800a58d9SAndroid Build Coastguard Worker    Returns:
685*800a58d9SAndroid Build Coastguard Worker        _EvaluatedResults namedtuple.
686*800a58d9SAndroid Build Coastguard Worker    """
687*800a58d9SAndroid Build Coastguard Worker    if report is None or report.errors:
688*800a58d9SAndroid Build Coastguard Worker        return _EvaluatedResult(is_result_ok=False,
689*800a58d9SAndroid Build Coastguard Worker                                result_message=report.errors)
690*800a58d9SAndroid Build Coastguard Worker
691*800a58d9SAndroid Build Coastguard Worker    return _EvaluatedResult(is_result_ok=True, result_message=None)
692*800a58d9SAndroid Build Coastguard Worker
693*800a58d9SAndroid Build Coastguard Worker
694*800a58d9SAndroid Build Coastguard Workerdef BootEvaluator(boot_dict):
695*800a58d9SAndroid Build Coastguard Worker    """Evaluate if the device booted successfully.
696*800a58d9SAndroid Build Coastguard Worker
697*800a58d9SAndroid Build Coastguard Worker    Args:
698*800a58d9SAndroid Build Coastguard Worker        boot_dict: Dict of instance_name:boot error.
699*800a58d9SAndroid Build Coastguard Worker
700*800a58d9SAndroid Build Coastguard Worker    Returns:
701*800a58d9SAndroid Build Coastguard Worker        _EvaluatedResults namedtuple.
702*800a58d9SAndroid Build Coastguard Worker    """
703*800a58d9SAndroid Build Coastguard Worker    if boot_dict:
704*800a58d9SAndroid Build Coastguard Worker        return _EvaluatedResult(is_result_ok=False, result_message=boot_dict)
705*800a58d9SAndroid Build Coastguard Worker    return _EvaluatedResult(is_result_ok=True, result_message=None)
706*800a58d9SAndroid Build Coastguard Worker
707*800a58d9SAndroid Build Coastguard Worker
708*800a58d9SAndroid Build Coastguard Workerclass TimeExecute:
709*800a58d9SAndroid Build Coastguard Worker    """Count the function execute time."""
710*800a58d9SAndroid Build Coastguard Worker
711*800a58d9SAndroid Build Coastguard Worker    def __init__(self, function_description=None, print_before_call=True,
712*800a58d9SAndroid Build Coastguard Worker                 print_status=True, result_evaluator=DefaultEvaluator,
713*800a58d9SAndroid Build Coastguard Worker                 display_waiting_dots=True):
714*800a58d9SAndroid Build Coastguard Worker        """Initializes the class.
715*800a58d9SAndroid Build Coastguard Worker
716*800a58d9SAndroid Build Coastguard Worker        Args:
717*800a58d9SAndroid Build Coastguard Worker            function_description: String that describes function (e.g."Creating
718*800a58d9SAndroid Build Coastguard Worker                                  Instance...")
719*800a58d9SAndroid Build Coastguard Worker            print_before_call: Boolean, print the function description before
720*800a58d9SAndroid Build Coastguard Worker                               calling the function, default True.
721*800a58d9SAndroid Build Coastguard Worker            print_status: Boolean, print the status of the function after the
722*800a58d9SAndroid Build Coastguard Worker                          function has completed, default True ("OK" or "Fail").
723*800a58d9SAndroid Build Coastguard Worker            result_evaluator: Func object. Pass func to evaluate result.
724*800a58d9SAndroid Build Coastguard Worker                              Default evaluator always report result is ok and
725*800a58d9SAndroid Build Coastguard Worker                              failed result will be identified only in exception
726*800a58d9SAndroid Build Coastguard Worker                              case.
727*800a58d9SAndroid Build Coastguard Worker            display_waiting_dots: Boolean, if true print the function_description
728*800a58d9SAndroid Build Coastguard Worker                                  followed by waiting dot.
729*800a58d9SAndroid Build Coastguard Worker        """
730*800a58d9SAndroid Build Coastguard Worker        self._function_description = function_description
731*800a58d9SAndroid Build Coastguard Worker        self._print_before_call = print_before_call
732*800a58d9SAndroid Build Coastguard Worker        self._print_status = print_status
733*800a58d9SAndroid Build Coastguard Worker        self._result_evaluator = result_evaluator
734*800a58d9SAndroid Build Coastguard Worker        self._display_waiting_dots = display_waiting_dots
735*800a58d9SAndroid Build Coastguard Worker
736*800a58d9SAndroid Build Coastguard Worker    def __call__(self, func):
737*800a58d9SAndroid Build Coastguard Worker        def DecoratorFunction(*args, **kargs):
738*800a58d9SAndroid Build Coastguard Worker            """Decorator function.
739*800a58d9SAndroid Build Coastguard Worker
740*800a58d9SAndroid Build Coastguard Worker            Args:
741*800a58d9SAndroid Build Coastguard Worker                *args: Arguments to pass to the functor.
742*800a58d9SAndroid Build Coastguard Worker                **kwargs: Key-val based arguments to pass to the functor.
743*800a58d9SAndroid Build Coastguard Worker
744*800a58d9SAndroid Build Coastguard Worker            Raises:
745*800a58d9SAndroid Build Coastguard Worker                Exception: The exception that functor(*args, **kwargs) throws.
746*800a58d9SAndroid Build Coastguard Worker            """
747*800a58d9SAndroid Build Coastguard Worker            timestart = time.time()
748*800a58d9SAndroid Build Coastguard Worker            if self._print_before_call:
749*800a58d9SAndroid Build Coastguard Worker                waiting_dots = "..." if self._display_waiting_dots else ""
750*800a58d9SAndroid Build Coastguard Worker                PrintColorString("%s %s"% (self._function_description,
751*800a58d9SAndroid Build Coastguard Worker                                           waiting_dots), end="")
752*800a58d9SAndroid Build Coastguard Worker            try:
753*800a58d9SAndroid Build Coastguard Worker                result = func(*args, **kargs)
754*800a58d9SAndroid Build Coastguard Worker                result_time = time.time() - timestart
755*800a58d9SAndroid Build Coastguard Worker                if not self._print_before_call:
756*800a58d9SAndroid Build Coastguard Worker                    PrintColorString("%s (%ds)" % (self._function_description,
757*800a58d9SAndroid Build Coastguard Worker                                                   result_time),
758*800a58d9SAndroid Build Coastguard Worker                                     TextColors.OKGREEN)
759*800a58d9SAndroid Build Coastguard Worker                if self._print_status:
760*800a58d9SAndroid Build Coastguard Worker                    evaluated_result = self._result_evaluator(result)
761*800a58d9SAndroid Build Coastguard Worker                    if evaluated_result.is_result_ok:
762*800a58d9SAndroid Build Coastguard Worker                        PrintColorString("OK! (%ds)" % (result_time),
763*800a58d9SAndroid Build Coastguard Worker                                         TextColors.OKGREEN)
764*800a58d9SAndroid Build Coastguard Worker                    else:
765*800a58d9SAndroid Build Coastguard Worker                        PrintColorString("Fail! (%ds)" % (result_time),
766*800a58d9SAndroid Build Coastguard Worker                                         TextColors.FAIL)
767*800a58d9SAndroid Build Coastguard Worker                        PrintColorString("Error: %s" %
768*800a58d9SAndroid Build Coastguard Worker                                         evaluated_result.result_message,
769*800a58d9SAndroid Build Coastguard Worker                                         TextColors.FAIL)
770*800a58d9SAndroid Build Coastguard Worker                return result
771*800a58d9SAndroid Build Coastguard Worker            except:
772*800a58d9SAndroid Build Coastguard Worker                if self._print_status:
773*800a58d9SAndroid Build Coastguard Worker                    PrintColorString("Fail! (%ds)" % (time.time() - timestart),
774*800a58d9SAndroid Build Coastguard Worker                                     TextColors.FAIL)
775*800a58d9SAndroid Build Coastguard Worker                raise
776*800a58d9SAndroid Build Coastguard Worker        return DecoratorFunction
777*800a58d9SAndroid Build Coastguard Worker
778*800a58d9SAndroid Build Coastguard Worker
779*800a58d9SAndroid Build Coastguard Workerdef PickFreePort():
780*800a58d9SAndroid Build Coastguard Worker    """Helper to pick a free port.
781*800a58d9SAndroid Build Coastguard Worker
782*800a58d9SAndroid Build Coastguard Worker    Returns:
783*800a58d9SAndroid Build Coastguard Worker        Integer, a free port number.
784*800a58d9SAndroid Build Coastguard Worker    """
785*800a58d9SAndroid Build Coastguard Worker    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
786*800a58d9SAndroid Build Coastguard Worker    tcp_socket.bind(("", 0))
787*800a58d9SAndroid Build Coastguard Worker    port = tcp_socket.getsockname()[1]
788*800a58d9SAndroid Build Coastguard Worker    tcp_socket.close()
789*800a58d9SAndroid Build Coastguard Worker    return port
790*800a58d9SAndroid Build Coastguard Worker
791*800a58d9SAndroid Build Coastguard Worker
792*800a58d9SAndroid Build Coastguard Workerdef CheckPortFree(port):
793*800a58d9SAndroid Build Coastguard Worker    """Check the availablity of the tcp port.
794*800a58d9SAndroid Build Coastguard Worker
795*800a58d9SAndroid Build Coastguard Worker    Args:
796*800a58d9SAndroid Build Coastguard Worker        Integer, a port number.
797*800a58d9SAndroid Build Coastguard Worker
798*800a58d9SAndroid Build Coastguard Worker    Raises:
799*800a58d9SAndroid Build Coastguard Worker        PortOccupied: This port is not available.
800*800a58d9SAndroid Build Coastguard Worker    """
801*800a58d9SAndroid Build Coastguard Worker    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
802*800a58d9SAndroid Build Coastguard Worker    try:
803*800a58d9SAndroid Build Coastguard Worker        tcp_socket.bind(("", port))
804*800a58d9SAndroid Build Coastguard Worker    except socket.error as port_error:
805*800a58d9SAndroid Build Coastguard Worker        raise errors.PortOccupied("Port (%d) is taken, please choose another "
806*800a58d9SAndroid Build Coastguard Worker                                  "port." % port) from port_error
807*800a58d9SAndroid Build Coastguard Worker    tcp_socket.close()
808*800a58d9SAndroid Build Coastguard Worker
809*800a58d9SAndroid Build Coastguard Worker
810*800a58d9SAndroid Build Coastguard Workerdef _ExecuteCommand(cmd, args):
811*800a58d9SAndroid Build Coastguard Worker    """Execute command.
812*800a58d9SAndroid Build Coastguard Worker
813*800a58d9SAndroid Build Coastguard Worker    Args:
814*800a58d9SAndroid Build Coastguard Worker        cmd: Strings of execute binary name.
815*800a58d9SAndroid Build Coastguard Worker        args: List of args to pass in with cmd.
816*800a58d9SAndroid Build Coastguard Worker
817*800a58d9SAndroid Build Coastguard Worker    Raises:
818*800a58d9SAndroid Build Coastguard Worker        errors.NoExecuteBin: Can't find the execute bin file.
819*800a58d9SAndroid Build Coastguard Worker    """
820*800a58d9SAndroid Build Coastguard Worker    bin_path = FindExecutable(cmd)
821*800a58d9SAndroid Build Coastguard Worker    if not bin_path:
822*800a58d9SAndroid Build Coastguard Worker        raise errors.NoExecuteCmd("unable to locate %s" % cmd)
823*800a58d9SAndroid Build Coastguard Worker    command = [bin_path] + args
824*800a58d9SAndroid Build Coastguard Worker    logger.debug("Running '%s'", ' '.join(command))
825*800a58d9SAndroid Build Coastguard Worker    with open(os.devnull, "w") as dev_null:
826*800a58d9SAndroid Build Coastguard Worker        subprocess.check_call(command, stderr=dev_null, stdout=dev_null)
827*800a58d9SAndroid Build Coastguard Worker
828*800a58d9SAndroid Build Coastguard Worker
829*800a58d9SAndroid Build Coastguard Workerdef ReleasePort(port):
830*800a58d9SAndroid Build Coastguard Worker    """Release local port.
831*800a58d9SAndroid Build Coastguard Worker
832*800a58d9SAndroid Build Coastguard Worker    Args:
833*800a58d9SAndroid Build Coastguard Worker        port: Integer of local port number.
834*800a58d9SAndroid Build Coastguard Worker    """
835*800a58d9SAndroid Build Coastguard Worker    try:
836*800a58d9SAndroid Build Coastguard Worker        with open(os.devnull, "w") as dev_null:
837*800a58d9SAndroid Build Coastguard Worker            subprocess.check_call(_RELEASE_PORT_CMD % port,
838*800a58d9SAndroid Build Coastguard Worker                                  stderr=dev_null, stdout=dev_null, shell=True)
839*800a58d9SAndroid Build Coastguard Worker    except subprocess.CalledProcessError:
840*800a58d9SAndroid Build Coastguard Worker        logger.debug("The port %d is available.", constants.WEBRTC_LOCAL_PORT)
841*800a58d9SAndroid Build Coastguard Worker
842*800a58d9SAndroid Build Coastguard Worker
843*800a58d9SAndroid Build Coastguard Workerdef EstablishSshTunnel(ip_addr, rsa_key_file, ssh_user,
844*800a58d9SAndroid Build Coastguard Worker                       port_mapping, extra_args_ssh_tunnel=None):
845*800a58d9SAndroid Build Coastguard Worker    """Create an ssh tunnel.
846*800a58d9SAndroid Build Coastguard Worker
847*800a58d9SAndroid Build Coastguard Worker    Args:
848*800a58d9SAndroid Build Coastguard Worker        ip_addr: String, use to build the adb & vnc tunnel between local
849*800a58d9SAndroid Build Coastguard Worker                 and remote instance.
850*800a58d9SAndroid Build Coastguard Worker        rsa_key_file: String, Private key file path to use when creating
851*800a58d9SAndroid Build Coastguard Worker                      the ssh tunnels.
852*800a58d9SAndroid Build Coastguard Worker        ssh_user: String of user login into the instance.
853*800a58d9SAndroid Build Coastguard Worker        port_mapping: List of tuples, each tuple is a pair of integers
854*800a58d9SAndroid Build Coastguard Worker                      representing a local port and a remote port.
855*800a58d9SAndroid Build Coastguard Worker        extra_args_ssh_tunnel: String, extra args for ssh tunnel connection.
856*800a58d9SAndroid Build Coastguard Worker
857*800a58d9SAndroid Build Coastguard Worker    Raises:
858*800a58d9SAndroid Build Coastguard Worker        subprocess.CalledProcessError if the ssh command fails.
859*800a58d9SAndroid Build Coastguard Worker    """
860*800a58d9SAndroid Build Coastguard Worker    port_mapping = [PORT_MAPPING % {
861*800a58d9SAndroid Build Coastguard Worker        "local_port": ports[0],
862*800a58d9SAndroid Build Coastguard Worker        "target_port": ports[1]} for ports in port_mapping]
863*800a58d9SAndroid Build Coastguard Worker    ssh_tunnel_args = _SSH_TUNNEL_ARGS % {
864*800a58d9SAndroid Build Coastguard Worker        "rsa_key_file": rsa_key_file,
865*800a58d9SAndroid Build Coastguard Worker        "ssh_user": ssh_user,
866*800a58d9SAndroid Build Coastguard Worker        "ip_addr": ip_addr,
867*800a58d9SAndroid Build Coastguard Worker        "port_mapping": " ".join(port_mapping)}
868*800a58d9SAndroid Build Coastguard Worker    ssh_tunnel_args_list = shlex.split(ssh_tunnel_args)
869*800a58d9SAndroid Build Coastguard Worker    if extra_args_ssh_tunnel:
870*800a58d9SAndroid Build Coastguard Worker        ssh_tunnel_args_list.extend(shlex.split(extra_args_ssh_tunnel))
871*800a58d9SAndroid Build Coastguard Worker    _ExecuteCommand(constants.SSH_BIN, ssh_tunnel_args_list)
872*800a58d9SAndroid Build Coastguard Worker
873*800a58d9SAndroid Build Coastguard Worker
874*800a58d9SAndroid Build Coastguard Workerdef EstablishWebRTCSshTunnel(ip_addr, webrtc_local_port, rsa_key_file, ssh_user,
875*800a58d9SAndroid Build Coastguard Worker                             extra_args_ssh_tunnel=None):
876*800a58d9SAndroid Build Coastguard Worker    """Create ssh tunnels for webrtc.
877*800a58d9SAndroid Build Coastguard Worker
878*800a58d9SAndroid Build Coastguard Worker    Pick up an available local port to establish one WebRTC tunnel and forward to
879*800a58d9SAndroid Build Coastguard Worker    the port of the webrtc operator of the remote instance.
880*800a58d9SAndroid Build Coastguard Worker
881*800a58d9SAndroid Build Coastguard Worker    Args:
882*800a58d9SAndroid Build Coastguard Worker        ip_addr: String, use to build the adb & vnc tunnel between local
883*800a58d9SAndroid Build Coastguard Worker                 and remote instance.
884*800a58d9SAndroid Build Coastguard Worker        webrtc_local_port: Integer, pick a free port as webrtc local port.
885*800a58d9SAndroid Build Coastguard Worker        rsa_key_file: String, Private key file path to use when creating
886*800a58d9SAndroid Build Coastguard Worker                      the ssh tunnels.
887*800a58d9SAndroid Build Coastguard Worker        ssh_user: String of user login into the instance.
888*800a58d9SAndroid Build Coastguard Worker        extra_args_ssh_tunnel: String, extra args for ssh tunnel connection.
889*800a58d9SAndroid Build Coastguard Worker
890*800a58d9SAndroid Build Coastguard Worker    Raises:
891*800a58d9SAndroid Build Coastguard Worker        subprocess.CalledProcessError if the ssh command fails.
892*800a58d9SAndroid Build Coastguard Worker    """
893*800a58d9SAndroid Build Coastguard Worker    webrtc_server_port = GetWebRTCServerPort(
894*800a58d9SAndroid Build Coastguard Worker        ip_addr, rsa_key_file, ssh_user, extra_args_ssh_tunnel)
895*800a58d9SAndroid Build Coastguard Worker
896*800a58d9SAndroid Build Coastguard Worker    # TODO(b/209502647): design a better way to forward webrtc ports.
897*800a58d9SAndroid Build Coastguard Worker    if extra_args_ssh_tunnel:
898*800a58d9SAndroid Build Coastguard Worker        for webrtc_port in WEBRTC_PORTS_MAPPING:
899*800a58d9SAndroid Build Coastguard Worker            ReleasePort(webrtc_port.local)
900*800a58d9SAndroid Build Coastguard Worker    port_mapping = (WEBRTC_PORTS_MAPPING +
901*800a58d9SAndroid Build Coastguard Worker                    [PortMapping(webrtc_local_port, webrtc_server_port)])
902*800a58d9SAndroid Build Coastguard Worker    try:
903*800a58d9SAndroid Build Coastguard Worker        EstablishSshTunnel(ip_addr, rsa_key_file, ssh_user,
904*800a58d9SAndroid Build Coastguard Worker                           port_mapping, extra_args_ssh_tunnel)
905*800a58d9SAndroid Build Coastguard Worker    except subprocess.CalledProcessError as e:
906*800a58d9SAndroid Build Coastguard Worker        PrintColorString("\n%s\nFailed to create ssh tunnels, retry with '#acloud "
907*800a58d9SAndroid Build Coastguard Worker                         "reconnect'." % e, TextColors.FAIL)
908*800a58d9SAndroid Build Coastguard Worker
909*800a58d9SAndroid Build Coastguard Worker
910*800a58d9SAndroid Build Coastguard Workerdef GetWebRTCServerPort(ip_addr, rsa_key_file, ssh_user,
911*800a58d9SAndroid Build Coastguard Worker                        extra_args_ssh_tunnel=None):
912*800a58d9SAndroid Build Coastguard Worker    """Get WebRTC server port.
913*800a58d9SAndroid Build Coastguard Worker
914*800a58d9SAndroid Build Coastguard Worker    List all process information to find the "webrtc_operator" process, then
915*800a58d9SAndroid Build Coastguard Worker    determine the WebRTC server port is 8443 or 1443.
916*800a58d9SAndroid Build Coastguard Worker
917*800a58d9SAndroid Build Coastguard Worker    Args:
918*800a58d9SAndroid Build Coastguard Worker        ip_addr: String, use to build the adb & vnc tunnel between local
919*800a58d9SAndroid Build Coastguard Worker                 and remote instance.
920*800a58d9SAndroid Build Coastguard Worker        rsa_key_file: String, Private key file path to use when creating
921*800a58d9SAndroid Build Coastguard Worker                      the ssh tunnels.
922*800a58d9SAndroid Build Coastguard Worker        ssh_user: String of user login into the instance.
923*800a58d9SAndroid Build Coastguard Worker        extra_args_ssh_tunnel: String, extra args for ssh tunnel connection.
924*800a58d9SAndroid Build Coastguard Worker
925*800a58d9SAndroid Build Coastguard Worker    Returns:
926*800a58d9SAndroid Build Coastguard Worker        The WebRTC server port number.
927*800a58d9SAndroid Build Coastguard Worker
928*800a58d9SAndroid Build Coastguard Worker    Raises:
929*800a58d9SAndroid Build Coastguard Worker        subprocess.CalledProcessError if the ssh command fails.
930*800a58d9SAndroid Build Coastguard Worker    """
931*800a58d9SAndroid Build Coastguard Worker    ssh_cmd = _SSH_COMMAND_PS % {
932*800a58d9SAndroid Build Coastguard Worker        "ssh_bin": FindExecutable(constants.SSH_BIN),
933*800a58d9SAndroid Build Coastguard Worker        "rsa_key_file": rsa_key_file,
934*800a58d9SAndroid Build Coastguard Worker        "ssh_user": ssh_user,
935*800a58d9SAndroid Build Coastguard Worker        "extra_args": extra_args_ssh_tunnel or "",
936*800a58d9SAndroid Build Coastguard Worker        "ip_addr": ip_addr}
937*800a58d9SAndroid Build Coastguard Worker    logger.info("Running command \"%s\"", ssh_cmd)
938*800a58d9SAndroid Build Coastguard Worker    try:
939*800a58d9SAndroid Build Coastguard Worker        process = subprocess.Popen(
940*800a58d9SAndroid Build Coastguard Worker            ssh_cmd, shell=True, stdin=None, universal_newlines=True,
941*800a58d9SAndroid Build Coastguard Worker            stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
942*800a58d9SAndroid Build Coastguard Worker        stdout, _ = process.communicate()
943*800a58d9SAndroid Build Coastguard Worker        for line in stdout.splitlines():
944*800a58d9SAndroid Build Coastguard Worker            webrtc_match = _WEBRTC_OPERATOR_PATTERN.match(line)
945*800a58d9SAndroid Build Coastguard Worker            if webrtc_match:
946*800a58d9SAndroid Build Coastguard Worker                return _PORT_8443
947*800a58d9SAndroid Build Coastguard Worker    except subprocess.CalledProcessError as e:
948*800a58d9SAndroid Build Coastguard Worker        logger.debug("Failed to list processes: %s", e)
949*800a58d9SAndroid Build Coastguard Worker    return _PORT_1443
950*800a58d9SAndroid Build Coastguard Worker
951*800a58d9SAndroid Build Coastguard Worker
952*800a58d9SAndroid Build Coastguard Workerdef GetWebrtcPortFromSSHTunnel(ip):
953*800a58d9SAndroid Build Coastguard Worker    """Get forwarding webrtc port from ssh tunnel.
954*800a58d9SAndroid Build Coastguard Worker
955*800a58d9SAndroid Build Coastguard Worker    Args:
956*800a58d9SAndroid Build Coastguard Worker        ip: String, ip address.
957*800a58d9SAndroid Build Coastguard Worker
958*800a58d9SAndroid Build Coastguard Worker    Returns:
959*800a58d9SAndroid Build Coastguard Worker        webrtc local port.
960*800a58d9SAndroid Build Coastguard Worker    """
961*800a58d9SAndroid Build Coastguard Worker    re_pattern = re.compile(_RE_WEBRTC_SSH_TUNNEL_PATTERN %
962*800a58d9SAndroid Build Coastguard Worker                            (constants.WEBRTC_LOCAL_PORT, ip))
963*800a58d9SAndroid Build Coastguard Worker    process_output = CheckOutput(constants.COMMAND_PS)
964*800a58d9SAndroid Build Coastguard Worker    for line in process_output.splitlines():
965*800a58d9SAndroid Build Coastguard Worker        match = re_pattern.match(line)
966*800a58d9SAndroid Build Coastguard Worker        if match:
967*800a58d9SAndroid Build Coastguard Worker            webrtc_port = int(match.group(_RE_GROUP_WEBRTC))
968*800a58d9SAndroid Build Coastguard Worker            return webrtc_port
969*800a58d9SAndroid Build Coastguard Worker
970*800a58d9SAndroid Build Coastguard Worker    logger.debug("Can't get webrtc local port from ip %s.", ip)
971*800a58d9SAndroid Build Coastguard Worker    return None
972*800a58d9SAndroid Build Coastguard Worker
973*800a58d9SAndroid Build Coastguard Worker
974*800a58d9SAndroid Build Coastguard Worker# TODO(147337696): create ssh tunnels tear down as adb and vnc.
975*800a58d9SAndroid Build Coastguard Worker# pylint: disable=too-many-locals
976*800a58d9SAndroid Build Coastguard Workerdef AutoConnect(ip_addr, rsa_key_file, target_vnc_port, target_adb_port,
977*800a58d9SAndroid Build Coastguard Worker                ssh_user, client_adb_port=None, extra_args_ssh_tunnel=None):
978*800a58d9SAndroid Build Coastguard Worker    """Autoconnect to an AVD instance.
979*800a58d9SAndroid Build Coastguard Worker
980*800a58d9SAndroid Build Coastguard Worker    Args:
981*800a58d9SAndroid Build Coastguard Worker        ip_addr: String, use to build the adb & vnc tunnel between local
982*800a58d9SAndroid Build Coastguard Worker                 and remote instance.
983*800a58d9SAndroid Build Coastguard Worker        rsa_key_file: String, Private key file path to use when creating
984*800a58d9SAndroid Build Coastguard Worker                      the ssh tunnels.
985*800a58d9SAndroid Build Coastguard Worker        target_vnc_port: Integer of target vnc port number.
986*800a58d9SAndroid Build Coastguard Worker        target_adb_port: Integer of target adb port number.
987*800a58d9SAndroid Build Coastguard Worker        ssh_user: String of user login into the instance.
988*800a58d9SAndroid Build Coastguard Worker        client_adb_port: Integer, Specified adb port to establish connection.
989*800a58d9SAndroid Build Coastguard Worker        extra_args_ssh_tunnel: String, extra args for ssh tunnel connection.
990*800a58d9SAndroid Build Coastguard Worker
991*800a58d9SAndroid Build Coastguard Worker    Returns:
992*800a58d9SAndroid Build Coastguard Worker        NamedTuple of (vnc_port, adb_port) SSHTUNNEL of the connect, both are
993*800a58d9SAndroid Build Coastguard Worker        integers.
994*800a58d9SAndroid Build Coastguard Worker    """
995*800a58d9SAndroid Build Coastguard Worker    local_adb_port = client_adb_port or PickFreePort()
996*800a58d9SAndroid Build Coastguard Worker    port_mapping = [(local_adb_port, target_adb_port)]
997*800a58d9SAndroid Build Coastguard Worker    local_free_vnc_port = None
998*800a58d9SAndroid Build Coastguard Worker    if target_vnc_port:
999*800a58d9SAndroid Build Coastguard Worker        local_free_vnc_port = PickFreePort()
1000*800a58d9SAndroid Build Coastguard Worker        port_mapping.append((local_free_vnc_port, target_vnc_port))
1001*800a58d9SAndroid Build Coastguard Worker    try:
1002*800a58d9SAndroid Build Coastguard Worker        EstablishSshTunnel(ip_addr, rsa_key_file, ssh_user,
1003*800a58d9SAndroid Build Coastguard Worker                           port_mapping, extra_args_ssh_tunnel)
1004*800a58d9SAndroid Build Coastguard Worker    except subprocess.CalledProcessError as e:
1005*800a58d9SAndroid Build Coastguard Worker        PrintColorString("\n%s\nFailed to create ssh tunnels, retry with '#acloud "
1006*800a58d9SAndroid Build Coastguard Worker                         "reconnect'." % e, TextColors.FAIL)
1007*800a58d9SAndroid Build Coastguard Worker        return ForwardedPorts(vnc_port=None, adb_port=None)
1008*800a58d9SAndroid Build Coastguard Worker
1009*800a58d9SAndroid Build Coastguard Worker    try:
1010*800a58d9SAndroid Build Coastguard Worker        adb_connect_args = _ADB_CONNECT_ARGS % {"adb_port": local_adb_port}
1011*800a58d9SAndroid Build Coastguard Worker        _ExecuteCommand(constants.ADB_BIN, adb_connect_args.split())
1012*800a58d9SAndroid Build Coastguard Worker    except subprocess.CalledProcessError:
1013*800a58d9SAndroid Build Coastguard Worker        PrintColorString("Failed to adb connect, retry with "
1014*800a58d9SAndroid Build Coastguard Worker                         "'#acloud reconnect'", TextColors.FAIL)
1015*800a58d9SAndroid Build Coastguard Worker
1016*800a58d9SAndroid Build Coastguard Worker    return ForwardedPorts(vnc_port=local_free_vnc_port,
1017*800a58d9SAndroid Build Coastguard Worker                          adb_port=local_adb_port)
1018*800a58d9SAndroid Build Coastguard Worker
1019*800a58d9SAndroid Build Coastguard Worker
1020*800a58d9SAndroid Build Coastguard Workerdef FindRemoteFiles(ssh_obj, search_dirs):
1021*800a58d9SAndroid Build Coastguard Worker    """Get all files, except symbolic links, under remote directories.
1022*800a58d9SAndroid Build Coastguard Worker
1023*800a58d9SAndroid Build Coastguard Worker    Args:
1024*800a58d9SAndroid Build Coastguard Worker        ssh_obj: An Ssh object.
1025*800a58d9SAndroid Build Coastguard Worker        search_dirs: A list of strings, the remote directories.
1026*800a58d9SAndroid Build Coastguard Worker
1027*800a58d9SAndroid Build Coastguard Worker    Returns:
1028*800a58d9SAndroid Build Coastguard Worker        A list of strings, the file paths.
1029*800a58d9SAndroid Build Coastguard Worker
1030*800a58d9SAndroid Build Coastguard Worker    Raises:
1031*800a58d9SAndroid Build Coastguard Worker        errors.SubprocessFail if the ssh execution returns non-zero.
1032*800a58d9SAndroid Build Coastguard Worker    """
1033*800a58d9SAndroid Build Coastguard Worker    if not search_dirs:
1034*800a58d9SAndroid Build Coastguard Worker        return []
1035*800a58d9SAndroid Build Coastguard Worker    ssh_cmd = (ssh_obj.GetBaseCmd(constants.SSH_BIN) + " " +
1036*800a58d9SAndroid Build Coastguard Worker               _CMD_FIND_FILES % {"search_dirs": " ".join(search_dirs)})
1037*800a58d9SAndroid Build Coastguard Worker    proc = subprocess.run(ssh_cmd, shell=True, capture_output=True,
1038*800a58d9SAndroid Build Coastguard Worker                          check=False)
1039*800a58d9SAndroid Build Coastguard Worker    if proc.returncode != 0:
1040*800a58d9SAndroid Build Coastguard Worker        raise errors.SubprocessFail("`%s` returned %d. with standard error: %s" %
1041*800a58d9SAndroid Build Coastguard Worker                                    (ssh_cmd, proc.returncode, proc.stderr.decode()))
1042*800a58d9SAndroid Build Coastguard Worker    if proc.stderr:
1043*800a58d9SAndroid Build Coastguard Worker        logger.debug("`%s` stderr: %s", ssh_cmd, proc.stderr.decode())
1044*800a58d9SAndroid Build Coastguard Worker    if proc.stdout:
1045*800a58d9SAndroid Build Coastguard Worker        return proc.stdout.decode().splitlines()
1046*800a58d9SAndroid Build Coastguard Worker    return []
1047*800a58d9SAndroid Build Coastguard Worker
1048*800a58d9SAndroid Build Coastguard Worker
1049*800a58d9SAndroid Build Coastguard Workerdef GetAnswerFromList(answer_list, enable_choose_all=False):
1050*800a58d9SAndroid Build Coastguard Worker    """Get answer from a list.
1051*800a58d9SAndroid Build Coastguard Worker
1052*800a58d9SAndroid Build Coastguard Worker    Args:
1053*800a58d9SAndroid Build Coastguard Worker        answer_list: list of the answers to choose from.
1054*800a58d9SAndroid Build Coastguard Worker        enable_choose_all: True to choose all items from answer list.
1055*800a58d9SAndroid Build Coastguard Worker
1056*800a58d9SAndroid Build Coastguard Worker    Return:
1057*800a58d9SAndroid Build Coastguard Worker        List holding the answer(s).
1058*800a58d9SAndroid Build Coastguard Worker    """
1059*800a58d9SAndroid Build Coastguard Worker    print("[0] to exit.")
1060*800a58d9SAndroid Build Coastguard Worker    start_index = 1
1061*800a58d9SAndroid Build Coastguard Worker    max_choice = len(answer_list)
1062*800a58d9SAndroid Build Coastguard Worker
1063*800a58d9SAndroid Build Coastguard Worker    for num, item in enumerate(answer_list, start_index):
1064*800a58d9SAndroid Build Coastguard Worker        print("[%d] %s" % (num, item))
1065*800a58d9SAndroid Build Coastguard Worker    if enable_choose_all:
1066*800a58d9SAndroid Build Coastguard Worker        max_choice += 1
1067*800a58d9SAndroid Build Coastguard Worker        print("[%d] for all." % max_choice)
1068*800a58d9SAndroid Build Coastguard Worker
1069*800a58d9SAndroid Build Coastguard Worker    choice = -1
1070*800a58d9SAndroid Build Coastguard Worker
1071*800a58d9SAndroid Build Coastguard Worker    while True:
1072*800a58d9SAndroid Build Coastguard Worker        try:
1073*800a58d9SAndroid Build Coastguard Worker            choice = input("Enter your choice[0-%d]: " % max_choice)
1074*800a58d9SAndroid Build Coastguard Worker            choice = int(choice)
1075*800a58d9SAndroid Build Coastguard Worker        except ValueError:
1076*800a58d9SAndroid Build Coastguard Worker            print("'%s' is not a valid integer.", choice)
1077*800a58d9SAndroid Build Coastguard Worker            continue
1078*800a58d9SAndroid Build Coastguard Worker        # Filter out choices
1079*800a58d9SAndroid Build Coastguard Worker        if choice == 0:
1080*800a58d9SAndroid Build Coastguard Worker            sys.exit(constants.EXIT_BY_USER)
1081*800a58d9SAndroid Build Coastguard Worker        if enable_choose_all and choice == max_choice:
1082*800a58d9SAndroid Build Coastguard Worker            return answer_list
1083*800a58d9SAndroid Build Coastguard Worker        if choice < 0 or choice > max_choice:
1084*800a58d9SAndroid Build Coastguard Worker            print("please choose between 0 and %d" % max_choice)
1085*800a58d9SAndroid Build Coastguard Worker        else:
1086*800a58d9SAndroid Build Coastguard Worker            return [answer_list[choice-start_index]]
1087*800a58d9SAndroid Build Coastguard Worker
1088*800a58d9SAndroid Build Coastguard Worker
1089*800a58d9SAndroid Build Coastguard Workerdef LaunchVNCFromReport(report, avd_spec, no_prompts=False):
1090*800a58d9SAndroid Build Coastguard Worker    """Launch vnc client according to the instances report.
1091*800a58d9SAndroid Build Coastguard Worker
1092*800a58d9SAndroid Build Coastguard Worker    Args:
1093*800a58d9SAndroid Build Coastguard Worker        report: Report object, that stores and generates report.
1094*800a58d9SAndroid Build Coastguard Worker        avd_spec: AVDSpec object that tells us what we're going to create.
1095*800a58d9SAndroid Build Coastguard Worker        no_prompts: Boolean, True to skip all prompts.
1096*800a58d9SAndroid Build Coastguard Worker    """
1097*800a58d9SAndroid Build Coastguard Worker    for device in report.data.get("devices", []):
1098*800a58d9SAndroid Build Coastguard Worker        if device.get(constants.VNC_PORT):
1099*800a58d9SAndroid Build Coastguard Worker            LaunchVncClient(device.get(constants.VNC_PORT),
1100*800a58d9SAndroid Build Coastguard Worker                            avd_width=avd_spec.hw_property["x_res"],
1101*800a58d9SAndroid Build Coastguard Worker                            avd_height=avd_spec.hw_property["y_res"],
1102*800a58d9SAndroid Build Coastguard Worker                            no_prompts=no_prompts)
1103*800a58d9SAndroid Build Coastguard Worker        else:
1104*800a58d9SAndroid Build Coastguard Worker            PrintColorString("No VNC port specified, skipping VNC startup.",
1105*800a58d9SAndroid Build Coastguard Worker                             TextColors.FAIL)
1106*800a58d9SAndroid Build Coastguard Worker
1107*800a58d9SAndroid Build Coastguard Worker
1108*800a58d9SAndroid Build Coastguard Workerdef LaunchBrowserFromReport(report):
1109*800a58d9SAndroid Build Coastguard Worker    """Open browser when autoconnect to webrtc according to the instances report.
1110*800a58d9SAndroid Build Coastguard Worker
1111*800a58d9SAndroid Build Coastguard Worker    Args:
1112*800a58d9SAndroid Build Coastguard Worker        report: Report object, that stores and generates report.
1113*800a58d9SAndroid Build Coastguard Worker    """
1114*800a58d9SAndroid Build Coastguard Worker    for device in report.data.get("devices", []):
1115*800a58d9SAndroid Build Coastguard Worker        if device.get("ip"):
1116*800a58d9SAndroid Build Coastguard Worker            LaunchBrowser(constants.WEBRTC_LOCAL_HOST,
1117*800a58d9SAndroid Build Coastguard Worker                          device.get(constants.WEBRTC_PORT,
1118*800a58d9SAndroid Build Coastguard Worker                                     constants.WEBRTC_LOCAL_PORT))
1119*800a58d9SAndroid Build Coastguard Worker
1120*800a58d9SAndroid Build Coastguard Worker
1121*800a58d9SAndroid Build Coastguard Workerdef LaunchBrowser(ip_addr, port):
1122*800a58d9SAndroid Build Coastguard Worker    """Launch browser to connect the webrtc AVD.
1123*800a58d9SAndroid Build Coastguard Worker
1124*800a58d9SAndroid Build Coastguard Worker    Args:
1125*800a58d9SAndroid Build Coastguard Worker        ip_addr: String, use to connect to webrtc AVD on the instance.
1126*800a58d9SAndroid Build Coastguard Worker        port: Integer, port number.
1127*800a58d9SAndroid Build Coastguard Worker    """
1128*800a58d9SAndroid Build Coastguard Worker    webrtc_link = _WEBRTC_URL % {
1129*800a58d9SAndroid Build Coastguard Worker        "webrtc_ip": ip_addr,
1130*800a58d9SAndroid Build Coastguard Worker        "webrtc_port": port}
1131*800a58d9SAndroid Build Coastguard Worker    PrintColorString("WebRTC AVD URL: %s "% webrtc_link)
1132*800a58d9SAndroid Build Coastguard Worker    if os.environ.get(_ENV_DISPLAY, None):
1133*800a58d9SAndroid Build Coastguard Worker        webbrowser.open_new_tab(webrtc_link)
1134*800a58d9SAndroid Build Coastguard Worker    else:
1135*800a58d9SAndroid Build Coastguard Worker        PrintColorString("Remote terminal can't support launch webbrowser.",
1136*800a58d9SAndroid Build Coastguard Worker                         TextColors.FAIL)
1137*800a58d9SAndroid Build Coastguard Worker
1138*800a58d9SAndroid Build Coastguard Worker
1139*800a58d9SAndroid Build Coastguard Workerdef LaunchVncClient(port, avd_width=None, avd_height=None, no_prompts=False):
1140*800a58d9SAndroid Build Coastguard Worker    """Launch ssvnc.
1141*800a58d9SAndroid Build Coastguard Worker
1142*800a58d9SAndroid Build Coastguard Worker    Args:
1143*800a58d9SAndroid Build Coastguard Worker        port: Integer, port number.
1144*800a58d9SAndroid Build Coastguard Worker        avd_width: String, the width of avd.
1145*800a58d9SAndroid Build Coastguard Worker        avd_height: String, the height of avd.
1146*800a58d9SAndroid Build Coastguard Worker        no_prompts: Boolean, True to skip all prompts.
1147*800a58d9SAndroid Build Coastguard Worker    """
1148*800a58d9SAndroid Build Coastguard Worker    try:
1149*800a58d9SAndroid Build Coastguard Worker        os.environ[_ENV_DISPLAY]
1150*800a58d9SAndroid Build Coastguard Worker    except KeyError:
1151*800a58d9SAndroid Build Coastguard Worker        PrintColorString("Remote terminal can't support VNC. "
1152*800a58d9SAndroid Build Coastguard Worker                         "Skipping VNC startup. "
1153*800a58d9SAndroid Build Coastguard Worker                         "VNC server is listening at 127.0.0.1:{}.".format(port),
1154*800a58d9SAndroid Build Coastguard Worker                         TextColors.FAIL)
1155*800a58d9SAndroid Build Coastguard Worker        return
1156*800a58d9SAndroid Build Coastguard Worker
1157*800a58d9SAndroid Build Coastguard Worker    if IsSupportedPlatform() and not FindExecutable(_VNC_BIN):
1158*800a58d9SAndroid Build Coastguard Worker        if no_prompts or GetUserAnswerYes(_CONFIRM_CONTINUE):
1159*800a58d9SAndroid Build Coastguard Worker            try:
1160*800a58d9SAndroid Build Coastguard Worker                PrintColorString("Installing ssvnc vnc client... ", end="")
1161*800a58d9SAndroid Build Coastguard Worker                sys.stdout.flush()
1162*800a58d9SAndroid Build Coastguard Worker                CheckOutput(_CMD_INSTALL_SSVNC, shell=True)
1163*800a58d9SAndroid Build Coastguard Worker                PrintColorString("Done", TextColors.OKGREEN)
1164*800a58d9SAndroid Build Coastguard Worker            except subprocess.CalledProcessError as cpe:
1165*800a58d9SAndroid Build Coastguard Worker                PrintColorString("Failed to install ssvnc: %s" %
1166*800a58d9SAndroid Build Coastguard Worker                                 cpe.output, TextColors.FAIL)
1167*800a58d9SAndroid Build Coastguard Worker                return
1168*800a58d9SAndroid Build Coastguard Worker        else:
1169*800a58d9SAndroid Build Coastguard Worker            return
1170*800a58d9SAndroid Build Coastguard Worker    ssvnc_env = os.environ.copy()
1171*800a58d9SAndroid Build Coastguard Worker    ssvnc_env.update(_SSVNC_ENV_VARS)
1172*800a58d9SAndroid Build Coastguard Worker    # Override SSVNC_SCALE
1173*800a58d9SAndroid Build Coastguard Worker    if avd_width or avd_height:
1174*800a58d9SAndroid Build Coastguard Worker        scale_ratio = CalculateVNCScreenRatio(avd_width, avd_height)
1175*800a58d9SAndroid Build Coastguard Worker        ssvnc_env["SSVNC_SCALE"] = str(scale_ratio)
1176*800a58d9SAndroid Build Coastguard Worker        logger.debug("SSVNC_SCALE:%s", scale_ratio)
1177*800a58d9SAndroid Build Coastguard Worker
1178*800a58d9SAndroid Build Coastguard Worker    ssvnc_args = _CMD_START_VNC % {"bin": FindExecutable(_VNC_BIN),
1179*800a58d9SAndroid Build Coastguard Worker                                   "port": port}
1180*800a58d9SAndroid Build Coastguard Worker    subprocess.Popen(ssvnc_args.split(), env=ssvnc_env)
1181*800a58d9SAndroid Build Coastguard Worker
1182*800a58d9SAndroid Build Coastguard Worker
1183*800a58d9SAndroid Build Coastguard Workerdef PrintDeviceSummary(report):
1184*800a58d9SAndroid Build Coastguard Worker    """Display summary of devices.
1185*800a58d9SAndroid Build Coastguard Worker
1186*800a58d9SAndroid Build Coastguard Worker    -Display device details from the report instance.
1187*800a58d9SAndroid Build Coastguard Worker        report example:
1188*800a58d9SAndroid Build Coastguard Worker            'data': [{'devices':[{'instance_name': 'ins-f6a397-none-53363',
1189*800a58d9SAndroid Build Coastguard Worker                                  'ip': u'35.234.10.162'}]}]
1190*800a58d9SAndroid Build Coastguard Worker    -Display error message from report.error.
1191*800a58d9SAndroid Build Coastguard Worker
1192*800a58d9SAndroid Build Coastguard Worker    Args:
1193*800a58d9SAndroid Build Coastguard Worker        report: A Report instance.
1194*800a58d9SAndroid Build Coastguard Worker    """
1195*800a58d9SAndroid Build Coastguard Worker    PrintColorString("\n")
1196*800a58d9SAndroid Build Coastguard Worker    PrintColorString("Device summary:")
1197*800a58d9SAndroid Build Coastguard Worker    for device in report.data.get("devices", []):
1198*800a58d9SAndroid Build Coastguard Worker        adb_serial = device.get(constants.DEVICE_SERIAL)
1199*800a58d9SAndroid Build Coastguard Worker        if not adb_serial:
1200*800a58d9SAndroid Build Coastguard Worker            adb_port = device.get("adb_port")
1201*800a58d9SAndroid Build Coastguard Worker            if adb_port:
1202*800a58d9SAndroid Build Coastguard Worker                adb_serial = constants.LOCALHOST_ADB_SERIAL % adb_port
1203*800a58d9SAndroid Build Coastguard Worker            else:
1204*800a58d9SAndroid Build Coastguard Worker                adb_serial = "(None)"
1205*800a58d9SAndroid Build Coastguard Worker
1206*800a58d9SAndroid Build Coastguard Worker        instance_name = device.get("instance_name")
1207*800a58d9SAndroid Build Coastguard Worker        instance_ip = device.get("ip")
1208*800a58d9SAndroid Build Coastguard Worker        instance_details = "" if not instance_name else "(%s[%s])" % (
1209*800a58d9SAndroid Build Coastguard Worker            instance_name, instance_ip)
1210*800a58d9SAndroid Build Coastguard Worker        PrintColorString(f" - device serial: {adb_serial} {instance_details}")
1211*800a58d9SAndroid Build Coastguard Worker        PrintColorString("\n")
1212*800a58d9SAndroid Build Coastguard Worker        PrintColorString("Note: To ensure Tradefed uses this AVD, please run:")
1213*800a58d9SAndroid Build Coastguard Worker        PrintColorString("\texport ANDROID_SERIAL=%s" % adb_serial)
1214*800a58d9SAndroid Build Coastguard Worker        ssh_command = device.get("ssh_command")
1215*800a58d9SAndroid Build Coastguard Worker        if ssh_command:
1216*800a58d9SAndroid Build Coastguard Worker            PrintColorString("\n")
1217*800a58d9SAndroid Build Coastguard Worker            PrintColorString("Note: To ssh connect to the device, please run:")
1218*800a58d9SAndroid Build Coastguard Worker            PrintColorString(f"\tssh command: {ssh_command}")
1219*800a58d9SAndroid Build Coastguard Worker        screen_command = device.get("screen_command")
1220*800a58d9SAndroid Build Coastguard Worker        if screen_command:
1221*800a58d9SAndroid Build Coastguard Worker            PrintColorString("\n")
1222*800a58d9SAndroid Build Coastguard Worker            PrintColorString("Note: To access the console, please run:")
1223*800a58d9SAndroid Build Coastguard Worker            PrintColorString(f"\tscreen command: {screen_command}")
1224*800a58d9SAndroid Build Coastguard Worker
1225*800a58d9SAndroid Build Coastguard Worker    # TODO(b/117245508): Help user to delete instance if it got created.
1226*800a58d9SAndroid Build Coastguard Worker    if report.errors:
1227*800a58d9SAndroid Build Coastguard Worker        error_msg = "\n".join(report.errors)
1228*800a58d9SAndroid Build Coastguard Worker        PrintColorString("Fail in:\n%s\n" % error_msg, TextColors.FAIL)
1229*800a58d9SAndroid Build Coastguard Worker
1230*800a58d9SAndroid Build Coastguard Worker
1231*800a58d9SAndroid Build Coastguard Worker# pylint: disable=import-outside-toplevel
1232*800a58d9SAndroid Build Coastguard Workerdef CalculateVNCScreenRatio(avd_width, avd_height):
1233*800a58d9SAndroid Build Coastguard Worker    """calculate the vnc screen scale ratio to fit into user's monitor.
1234*800a58d9SAndroid Build Coastguard Worker
1235*800a58d9SAndroid Build Coastguard Worker    Args:
1236*800a58d9SAndroid Build Coastguard Worker        avd_width: String, the width of avd.
1237*800a58d9SAndroid Build Coastguard Worker        avd_height: String, the height of avd.
1238*800a58d9SAndroid Build Coastguard Worker    Return:
1239*800a58d9SAndroid Build Coastguard Worker        Float, scale ratio for vnc client.
1240*800a58d9SAndroid Build Coastguard Worker    """
1241*800a58d9SAndroid Build Coastguard Worker    try:
1242*800a58d9SAndroid Build Coastguard Worker        import Tkinter
1243*800a58d9SAndroid Build Coastguard Worker    # Some python interpreters may not be configured for Tk, just return default scale ratio.
1244*800a58d9SAndroid Build Coastguard Worker    except ImportError:
1245*800a58d9SAndroid Build Coastguard Worker        try:
1246*800a58d9SAndroid Build Coastguard Worker            import tkinter as Tkinter
1247*800a58d9SAndroid Build Coastguard Worker        except ImportError:
1248*800a58d9SAndroid Build Coastguard Worker            PrintColorString(
1249*800a58d9SAndroid Build Coastguard Worker                "no module named tkinter, vnc display scale were not be fit."
1250*800a58d9SAndroid Build Coastguard Worker                "please run 'sudo apt-get install python3-tk' to install it.")
1251*800a58d9SAndroid Build Coastguard Worker            return _DEFAULT_DISPLAY_SCALE
1252*800a58d9SAndroid Build Coastguard Worker    root = Tkinter.Tk()
1253*800a58d9SAndroid Build Coastguard Worker    margin = 100 # leave some space on user's monitor.
1254*800a58d9SAndroid Build Coastguard Worker    screen_height = root.winfo_screenheight() - margin
1255*800a58d9SAndroid Build Coastguard Worker    screen_width = root.winfo_screenwidth() - margin
1256*800a58d9SAndroid Build Coastguard Worker
1257*800a58d9SAndroid Build Coastguard Worker    scale_h = _DEFAULT_DISPLAY_SCALE
1258*800a58d9SAndroid Build Coastguard Worker    scale_w = _DEFAULT_DISPLAY_SCALE
1259*800a58d9SAndroid Build Coastguard Worker    if float(screen_height) < float(avd_height):
1260*800a58d9SAndroid Build Coastguard Worker        scale_h = round(float(screen_height) / float(avd_height), 1)
1261*800a58d9SAndroid Build Coastguard Worker
1262*800a58d9SAndroid Build Coastguard Worker    if float(screen_width) < float(avd_width):
1263*800a58d9SAndroid Build Coastguard Worker        scale_w = round(float(screen_width) / float(avd_width), 1)
1264*800a58d9SAndroid Build Coastguard Worker
1265*800a58d9SAndroid Build Coastguard Worker    logger.debug("scale_h: %s (screen_h: %s/avd_h: %s),"
1266*800a58d9SAndroid Build Coastguard Worker                 " scale_w: %s (screen_w: %s/avd_w: %s)",
1267*800a58d9SAndroid Build Coastguard Worker                 scale_h, screen_height, avd_height,
1268*800a58d9SAndroid Build Coastguard Worker                 scale_w, screen_width, avd_width)
1269*800a58d9SAndroid Build Coastguard Worker
1270*800a58d9SAndroid Build Coastguard Worker    # Return the larger scale-down ratio.
1271*800a58d9SAndroid Build Coastguard Worker    return scale_h if scale_h < scale_w else scale_w
1272*800a58d9SAndroid Build Coastguard Worker
1273*800a58d9SAndroid Build Coastguard Worker
1274*800a58d9SAndroid Build Coastguard Workerdef IsCommandRunning(command):
1275*800a58d9SAndroid Build Coastguard Worker    """Check if command is running.
1276*800a58d9SAndroid Build Coastguard Worker
1277*800a58d9SAndroid Build Coastguard Worker    Args:
1278*800a58d9SAndroid Build Coastguard Worker        command: String of command name.
1279*800a58d9SAndroid Build Coastguard Worker
1280*800a58d9SAndroid Build Coastguard Worker    Returns:
1281*800a58d9SAndroid Build Coastguard Worker        Boolean, True if command is running. False otherwise.
1282*800a58d9SAndroid Build Coastguard Worker    """
1283*800a58d9SAndroid Build Coastguard Worker    try:
1284*800a58d9SAndroid Build Coastguard Worker        with open(os.devnull, "w") as dev_null:
1285*800a58d9SAndroid Build Coastguard Worker            subprocess.check_call([constants.CMD_PGREP, "-af", command],
1286*800a58d9SAndroid Build Coastguard Worker                                  stderr=dev_null, stdout=dev_null)
1287*800a58d9SAndroid Build Coastguard Worker        return True
1288*800a58d9SAndroid Build Coastguard Worker    except subprocess.CalledProcessError:
1289*800a58d9SAndroid Build Coastguard Worker        return False
1290*800a58d9SAndroid Build Coastguard Worker
1291*800a58d9SAndroid Build Coastguard Worker
1292*800a58d9SAndroid Build Coastguard Workerdef AddUserGroupsToCmd(cmd, user_groups):
1293*800a58d9SAndroid Build Coastguard Worker    """Add the user groups to the command if necessary.
1294*800a58d9SAndroid Build Coastguard Worker
1295*800a58d9SAndroid Build Coastguard Worker    As part of local host setup to enable local instance support, the user is
1296*800a58d9SAndroid Build Coastguard Worker    added to certain groups. For those settings to take effect systemwide
1297*800a58d9SAndroid Build Coastguard Worker    requires the user to log out and log back in. In the scenario where the
1298*800a58d9SAndroid Build Coastguard Worker    user has run setup and hasn't logged out, we still want them to be able to
1299*800a58d9SAndroid Build Coastguard Worker    launch a local instance so add the user to the groups as part of the
1300*800a58d9SAndroid Build Coastguard Worker    command to ensure success.
1301*800a58d9SAndroid Build Coastguard Worker
1302*800a58d9SAndroid Build Coastguard Worker    The reason using here-doc instead of '&' is all operations need to be ran in
1303*800a58d9SAndroid Build Coastguard Worker    ths same pid.  Here's an example cmd:
1304*800a58d9SAndroid Build Coastguard Worker    $ sg kvm  << EOF
1305*800a58d9SAndroid Build Coastguard Worker    sg libvirt
1306*800a58d9SAndroid Build Coastguard Worker    sg cvdnetwork
1307*800a58d9SAndroid Build Coastguard Worker    launch_cvd --cpus 2 --x_res 1280 --y_res 720 --dpi 160 --memory_mb 4096
1308*800a58d9SAndroid Build Coastguard Worker    EOF
1309*800a58d9SAndroid Build Coastguard Worker
1310*800a58d9SAndroid Build Coastguard Worker    Args:
1311*800a58d9SAndroid Build Coastguard Worker        cmd: String of the command to prepend the user groups to.
1312*800a58d9SAndroid Build Coastguard Worker        user_groups: List of user groups name.(String)
1313*800a58d9SAndroid Build Coastguard Worker
1314*800a58d9SAndroid Build Coastguard Worker    Returns:
1315*800a58d9SAndroid Build Coastguard Worker        String of the command with the user groups prepended to it if necessary,
1316*800a58d9SAndroid Build Coastguard Worker        otherwise the same existing command.
1317*800a58d9SAndroid Build Coastguard Worker    """
1318*800a58d9SAndroid Build Coastguard Worker    user_group_cmd = ""
1319*800a58d9SAndroid Build Coastguard Worker    if not CheckUserInGroups(user_groups):
1320*800a58d9SAndroid Build Coastguard Worker        logger.debug("Need to add user groups to the command")
1321*800a58d9SAndroid Build Coastguard Worker        for idx, group in enumerate(user_groups):
1322*800a58d9SAndroid Build Coastguard Worker            user_group_cmd += _CMD_SG + group
1323*800a58d9SAndroid Build Coastguard Worker            if idx == 0:
1324*800a58d9SAndroid Build Coastguard Worker                user_group_cmd += " <<EOF\n"
1325*800a58d9SAndroid Build Coastguard Worker            else:
1326*800a58d9SAndroid Build Coastguard Worker                user_group_cmd += "\n"
1327*800a58d9SAndroid Build Coastguard Worker        cmd += "\nEOF"
1328*800a58d9SAndroid Build Coastguard Worker    user_group_cmd += cmd
1329*800a58d9SAndroid Build Coastguard Worker    logger.debug("user group cmd: %s", user_group_cmd)
1330*800a58d9SAndroid Build Coastguard Worker    return user_group_cmd
1331*800a58d9SAndroid Build Coastguard Worker
1332*800a58d9SAndroid Build Coastguard Worker
1333*800a58d9SAndroid Build Coastguard Workerdef CheckUserInGroups(group_name_list):
1334*800a58d9SAndroid Build Coastguard Worker    """Check if the current user is in the group.
1335*800a58d9SAndroid Build Coastguard Worker
1336*800a58d9SAndroid Build Coastguard Worker    Args:
1337*800a58d9SAndroid Build Coastguard Worker        group_name_list: The list of group name.
1338*800a58d9SAndroid Build Coastguard Worker    Returns:
1339*800a58d9SAndroid Build Coastguard Worker        True if current user is in all the groups.
1340*800a58d9SAndroid Build Coastguard Worker    """
1341*800a58d9SAndroid Build Coastguard Worker    logger.info("Checking if user is in following groups: %s", group_name_list)
1342*800a58d9SAndroid Build Coastguard Worker    all_groups = [g.gr_name for g in grp.getgrall()]
1343*800a58d9SAndroid Build Coastguard Worker    for group in group_name_list:
1344*800a58d9SAndroid Build Coastguard Worker        if group not in all_groups:
1345*800a58d9SAndroid Build Coastguard Worker            logger.info("This group doesn't exist: %s", group)
1346*800a58d9SAndroid Build Coastguard Worker            return False
1347*800a58d9SAndroid Build Coastguard Worker        if getpass.getuser() not in grp.getgrnam(group).gr_mem:
1348*800a58d9SAndroid Build Coastguard Worker            logger.info("Current user isn't in this group: %s", group)
1349*800a58d9SAndroid Build Coastguard Worker            return False
1350*800a58d9SAndroid Build Coastguard Worker    return True
1351*800a58d9SAndroid Build Coastguard Worker
1352*800a58d9SAndroid Build Coastguard Worker
1353*800a58d9SAndroid Build Coastguard Workerdef IsSupportedPlatform(print_warning=False):
1354*800a58d9SAndroid Build Coastguard Worker    """Check if user's os is the supported platform.
1355*800a58d9SAndroid Build Coastguard Worker
1356*800a58d9SAndroid Build Coastguard Worker    platform.version() return such as '#1 SMP Debian 5.6.14-1rodete2...'
1357*800a58d9SAndroid Build Coastguard Worker    and use to judge supported or not.
1358*800a58d9SAndroid Build Coastguard Worker
1359*800a58d9SAndroid Build Coastguard Worker    Args:
1360*800a58d9SAndroid Build Coastguard Worker        print_warning: Boolean, print the unsupported warning
1361*800a58d9SAndroid Build Coastguard Worker                       if True.
1362*800a58d9SAndroid Build Coastguard Worker    Returns:
1363*800a58d9SAndroid Build Coastguard Worker        Boolean, True if user is using supported platform.
1364*800a58d9SAndroid Build Coastguard Worker    """
1365*800a58d9SAndroid Build Coastguard Worker    system = platform.system()
1366*800a58d9SAndroid Build Coastguard Worker    # TODO(b/161085678): After python3 fully migrated, then use distro to fix.
1367*800a58d9SAndroid Build Coastguard Worker    platform_supported = False
1368*800a58d9SAndroid Build Coastguard Worker    if system in _SUPPORTED_SYSTEMS_AND_DISTS:
1369*800a58d9SAndroid Build Coastguard Worker        for dist in _SUPPORTED_SYSTEMS_AND_DISTS[system]:
1370*800a58d9SAndroid Build Coastguard Worker            if dist in platform.version():
1371*800a58d9SAndroid Build Coastguard Worker                platform_supported = True
1372*800a58d9SAndroid Build Coastguard Worker                break
1373*800a58d9SAndroid Build Coastguard Worker
1374*800a58d9SAndroid Build Coastguard Worker    logger.info("Updated supported system and dists: %s",
1375*800a58d9SAndroid Build Coastguard Worker                _SUPPORTED_SYSTEMS_AND_DISTS)
1376*800a58d9SAndroid Build Coastguard Worker    platform_supported_msg = ("%s[%s] %s supported platform" %
1377*800a58d9SAndroid Build Coastguard Worker                              (system,
1378*800a58d9SAndroid Build Coastguard Worker                               platform.version(),
1379*800a58d9SAndroid Build Coastguard Worker                               "is a" if platform_supported else "is not a"))
1380*800a58d9SAndroid Build Coastguard Worker    if print_warning and not platform_supported:
1381*800a58d9SAndroid Build Coastguard Worker        PrintColorString(platform_supported_msg, TextColors.WARNING)
1382*800a58d9SAndroid Build Coastguard Worker    else:
1383*800a58d9SAndroid Build Coastguard Worker        logger.info(platform_supported_msg)
1384*800a58d9SAndroid Build Coastguard Worker
1385*800a58d9SAndroid Build Coastguard Worker    return platform_supported
1386*800a58d9SAndroid Build Coastguard Worker
1387*800a58d9SAndroid Build Coastguard Workerdef IsSupportedKvm():
1388*800a58d9SAndroid Build Coastguard Worker    """Check if support kvm.
1389*800a58d9SAndroid Build Coastguard Worker
1390*800a58d9SAndroid Build Coastguard Worker    Returns:
1391*800a58d9SAndroid Build Coastguard Worker        True if environment supported kvm.
1392*800a58d9SAndroid Build Coastguard Worker    """
1393*800a58d9SAndroid Build Coastguard Worker    if os.path.exists(_KVM_PATH):
1394*800a58d9SAndroid Build Coastguard Worker        return True
1395*800a58d9SAndroid Build Coastguard Worker
1396*800a58d9SAndroid Build Coastguard Worker    PrintColorString(
1397*800a58d9SAndroid Build Coastguard Worker        "The environment doesn't support virtualization. Please run "
1398*800a58d9SAndroid Build Coastguard Worker        "the remote instance by \"acloud create\" instead. If you want to "
1399*800a58d9SAndroid Build Coastguard Worker        "launch AVD on the local instance, Please refer to http://go/"
1400*800a58d9SAndroid Build Coastguard Worker        "acloud-cloudtop#acloud-create-local-instance-on-the-cloudtop",
1401*800a58d9SAndroid Build Coastguard Worker        TextColors.FAIL)
1402*800a58d9SAndroid Build Coastguard Worker    return False
1403*800a58d9SAndroid Build Coastguard Worker
1404*800a58d9SAndroid Build Coastguard Workerdef GetDistDir():
1405*800a58d9SAndroid Build Coastguard Worker    """Return the absolute path to the dist dir."""
1406*800a58d9SAndroid Build Coastguard Worker    android_build_top = os.environ.get(constants.ENV_ANDROID_BUILD_TOP)
1407*800a58d9SAndroid Build Coastguard Worker    if not android_build_top:
1408*800a58d9SAndroid Build Coastguard Worker        return None
1409*800a58d9SAndroid Build Coastguard Worker    dist_cmd = GET_BUILD_VAR_CMD[:]
1410*800a58d9SAndroid Build Coastguard Worker    dist_cmd.append(_DIST_DIR)
1411*800a58d9SAndroid Build Coastguard Worker    try:
1412*800a58d9SAndroid Build Coastguard Worker        dist_dir = CheckOutput(dist_cmd, cwd=android_build_top)
1413*800a58d9SAndroid Build Coastguard Worker    except subprocess.CalledProcessError:
1414*800a58d9SAndroid Build Coastguard Worker        return None
1415*800a58d9SAndroid Build Coastguard Worker    return os.path.join(android_build_top, dist_dir.strip())
1416*800a58d9SAndroid Build Coastguard Worker
1417*800a58d9SAndroid Build Coastguard Worker
1418*800a58d9SAndroid Build Coastguard Workerdef CleanupProcess(pattern):
1419*800a58d9SAndroid Build Coastguard Worker    """Cleanup process with pattern.
1420*800a58d9SAndroid Build Coastguard Worker
1421*800a58d9SAndroid Build Coastguard Worker    Args:
1422*800a58d9SAndroid Build Coastguard Worker        pattern: String, string of process pattern.
1423*800a58d9SAndroid Build Coastguard Worker    """
1424*800a58d9SAndroid Build Coastguard Worker    if IsCommandRunning(pattern):
1425*800a58d9SAndroid Build Coastguard Worker        command_kill = _CMD_KILL + [pattern]
1426*800a58d9SAndroid Build Coastguard Worker        subprocess.check_call(command_kill)
1427*800a58d9SAndroid Build Coastguard Worker
1428*800a58d9SAndroid Build Coastguard Worker
1429*800a58d9SAndroid Build Coastguard Workerdef TimeoutException(timeout_secs, timeout_error=_DEFAULT_TIMEOUT_ERR):
1430*800a58d9SAndroid Build Coastguard Worker    """Decorater which function timeout setup and raise custom exception.
1431*800a58d9SAndroid Build Coastguard Worker
1432*800a58d9SAndroid Build Coastguard Worker    Args:
1433*800a58d9SAndroid Build Coastguard Worker        timeout_secs: Number of maximum seconds of waiting time.
1434*800a58d9SAndroid Build Coastguard Worker        timeout_error: String to describe timeout exception.
1435*800a58d9SAndroid Build Coastguard Worker
1436*800a58d9SAndroid Build Coastguard Worker    Returns:
1437*800a58d9SAndroid Build Coastguard Worker        The function wrapper.
1438*800a58d9SAndroid Build Coastguard Worker    """
1439*800a58d9SAndroid Build Coastguard Worker    if timeout_error == _DEFAULT_TIMEOUT_ERR:
1440*800a58d9SAndroid Build Coastguard Worker        timeout_error = timeout_error % timeout_secs
1441*800a58d9SAndroid Build Coastguard Worker
1442*800a58d9SAndroid Build Coastguard Worker    def _Wrapper(func):
1443*800a58d9SAndroid Build Coastguard Worker        # pylint: disable=unused-argument
1444*800a58d9SAndroid Build Coastguard Worker        def _HandleTimeout(signum, frame):
1445*800a58d9SAndroid Build Coastguard Worker            raise errors.FunctionTimeoutError(timeout_error)
1446*800a58d9SAndroid Build Coastguard Worker
1447*800a58d9SAndroid Build Coastguard Worker        def _FunctionWrapper(*args, **kwargs):
1448*800a58d9SAndroid Build Coastguard Worker            signal.signal(signal.SIGALRM, _HandleTimeout)
1449*800a58d9SAndroid Build Coastguard Worker            signal.alarm(timeout_secs)
1450*800a58d9SAndroid Build Coastguard Worker            try:
1451*800a58d9SAndroid Build Coastguard Worker                result = func(*args, **kwargs)
1452*800a58d9SAndroid Build Coastguard Worker            finally:
1453*800a58d9SAndroid Build Coastguard Worker                signal.alarm(0)
1454*800a58d9SAndroid Build Coastguard Worker            return result
1455*800a58d9SAndroid Build Coastguard Worker
1456*800a58d9SAndroid Build Coastguard Worker        return _FunctionWrapper
1457*800a58d9SAndroid Build Coastguard Worker
1458*800a58d9SAndroid Build Coastguard Worker    return _Wrapper
1459*800a58d9SAndroid Build Coastguard Worker
1460*800a58d9SAndroid Build Coastguard Worker
1461*800a58d9SAndroid Build Coastguard Workerdef GetBuildEnvironmentVariable(variable_name):
1462*800a58d9SAndroid Build Coastguard Worker    """Get build environment variable.
1463*800a58d9SAndroid Build Coastguard Worker
1464*800a58d9SAndroid Build Coastguard Worker    Args:
1465*800a58d9SAndroid Build Coastguard Worker        variable_name: String of variable name.
1466*800a58d9SAndroid Build Coastguard Worker
1467*800a58d9SAndroid Build Coastguard Worker    Returns:
1468*800a58d9SAndroid Build Coastguard Worker        String, the value of the variable.
1469*800a58d9SAndroid Build Coastguard Worker
1470*800a58d9SAndroid Build Coastguard Worker    Raises:
1471*800a58d9SAndroid Build Coastguard Worker        errors.GetAndroidBuildEnvVarError: No environment variable found.
1472*800a58d9SAndroid Build Coastguard Worker    """
1473*800a58d9SAndroid Build Coastguard Worker    try:
1474*800a58d9SAndroid Build Coastguard Worker        return os.environ[variable_name]
1475*800a58d9SAndroid Build Coastguard Worker    except KeyError as no_env_error:
1476*800a58d9SAndroid Build Coastguard Worker        raise errors.GetAndroidBuildEnvVarError(
1477*800a58d9SAndroid Build Coastguard Worker            "Could not get environment var: %s\n"
1478*800a58d9SAndroid Build Coastguard Worker            "Try to run 'source build/envsetup.sh && lunch <target>'"
1479*800a58d9SAndroid Build Coastguard Worker            % variable_name
1480*800a58d9SAndroid Build Coastguard Worker        ) from no_env_error
1481*800a58d9SAndroid Build Coastguard Worker
1482*800a58d9SAndroid Build Coastguard Worker
1483*800a58d9SAndroid Build Coastguard Worker# pylint: disable=no-member,import-outside-toplevel
1484*800a58d9SAndroid Build Coastguard Workerdef FindExecutable(filename):
1485*800a58d9SAndroid Build Coastguard Worker    """A compatibility function to find execution file path.
1486*800a58d9SAndroid Build Coastguard Worker
1487*800a58d9SAndroid Build Coastguard Worker    Args:
1488*800a58d9SAndroid Build Coastguard Worker        filename: String of execution filename.
1489*800a58d9SAndroid Build Coastguard Worker
1490*800a58d9SAndroid Build Coastguard Worker    Returns:
1491*800a58d9SAndroid Build Coastguard Worker        String: execution file path.
1492*800a58d9SAndroid Build Coastguard Worker    """
1493*800a58d9SAndroid Build Coastguard Worker    try:
1494*800a58d9SAndroid Build Coastguard Worker        from distutils.spawn import find_executable
1495*800a58d9SAndroid Build Coastguard Worker        return find_executable(filename)
1496*800a58d9SAndroid Build Coastguard Worker    except ImportError:
1497*800a58d9SAndroid Build Coastguard Worker        return shutil.which(filename)
1498*800a58d9SAndroid Build Coastguard Worker
1499*800a58d9SAndroid Build Coastguard Worker
1500*800a58d9SAndroid Build Coastguard Workerdef GetDictItems(namedtuple_object):
1501*800a58d9SAndroid Build Coastguard Worker    """A compatibility function to access the OrdereDict object from the given namedtuple object.
1502*800a58d9SAndroid Build Coastguard Worker
1503*800a58d9SAndroid Build Coastguard Worker    Args:
1504*800a58d9SAndroid Build Coastguard Worker        namedtuple_object: namedtuple object.
1505*800a58d9SAndroid Build Coastguard Worker
1506*800a58d9SAndroid Build Coastguard Worker    Returns:
1507*800a58d9SAndroid Build Coastguard Worker        collections.namedtuple._asdict().items() when using python3.
1508*800a58d9SAndroid Build Coastguard Worker    """
1509*800a58d9SAndroid Build Coastguard Worker    return namedtuple_object._asdict().items()
1510*800a58d9SAndroid Build Coastguard Worker
1511*800a58d9SAndroid Build Coastguard Worker
1512*800a58d9SAndroid Build Coastguard Workerdef CleanupSSVncviewer(vnc_port):
1513*800a58d9SAndroid Build Coastguard Worker    """Cleanup the old disconnected ssvnc viewer.
1514*800a58d9SAndroid Build Coastguard Worker
1515*800a58d9SAndroid Build Coastguard Worker    Args:
1516*800a58d9SAndroid Build Coastguard Worker        vnc_port: Integer, port number of vnc.
1517*800a58d9SAndroid Build Coastguard Worker    """
1518*800a58d9SAndroid Build Coastguard Worker    ssvnc_viewer_pattern = _SSVNC_VIEWER_PATTERN % {"vnc_port":vnc_port}
1519*800a58d9SAndroid Build Coastguard Worker    CleanupProcess(ssvnc_viewer_pattern)
1520*800a58d9SAndroid Build Coastguard Worker
1521*800a58d9SAndroid Build Coastguard Worker
1522*800a58d9SAndroid Build Coastguard Workerdef CheckOutput(cmd, **kwargs):
1523*800a58d9SAndroid Build Coastguard Worker    """Call subprocess.check_output to get output.
1524*800a58d9SAndroid Build Coastguard Worker
1525*800a58d9SAndroid Build Coastguard Worker    The subprocess.check_output return type is "bytes" in python 3, we have
1526*800a58d9SAndroid Build Coastguard Worker    to convert bytes as string with .decode() in advance.
1527*800a58d9SAndroid Build Coastguard Worker
1528*800a58d9SAndroid Build Coastguard Worker    Args:
1529*800a58d9SAndroid Build Coastguard Worker        cmd: String of command.
1530*800a58d9SAndroid Build Coastguard Worker        **kwargs: dictionary of keyword based args to pass to func.
1531*800a58d9SAndroid Build Coastguard Worker
1532*800a58d9SAndroid Build Coastguard Worker    Return:
1533*800a58d9SAndroid Build Coastguard Worker        String to command output.
1534*800a58d9SAndroid Build Coastguard Worker    """
1535*800a58d9SAndroid Build Coastguard Worker    return subprocess.check_output(cmd, **kwargs).decode()
1536*800a58d9SAndroid Build Coastguard Worker
1537*800a58d9SAndroid Build Coastguard Worker
1538*800a58d9SAndroid Build Coastguard Workerdef Popen(*command, **popen_args):
1539*800a58d9SAndroid Build Coastguard Worker    """Execute subprocess.Popen command and log the output.
1540*800a58d9SAndroid Build Coastguard Worker
1541*800a58d9SAndroid Build Coastguard Worker    This method waits for the process to terminate. It kills the process
1542*800a58d9SAndroid Build Coastguard Worker    if it's interrupted due to timeout.
1543*800a58d9SAndroid Build Coastguard Worker
1544*800a58d9SAndroid Build Coastguard Worker    Args:
1545*800a58d9SAndroid Build Coastguard Worker        command: Strings, the command.
1546*800a58d9SAndroid Build Coastguard Worker        popen_kwargs: The arguments to be passed to subprocess.Popen.
1547*800a58d9SAndroid Build Coastguard Worker
1548*800a58d9SAndroid Build Coastguard Worker    Raises:
1549*800a58d9SAndroid Build Coastguard Worker        errors.SubprocessFail if the process returns non-zero.
1550*800a58d9SAndroid Build Coastguard Worker    """
1551*800a58d9SAndroid Build Coastguard Worker    proc = None
1552*800a58d9SAndroid Build Coastguard Worker    try:
1553*800a58d9SAndroid Build Coastguard Worker        logger.info("Execute %s", command)
1554*800a58d9SAndroid Build Coastguard Worker        popen_args["stdin"] = subprocess.PIPE
1555*800a58d9SAndroid Build Coastguard Worker        popen_args["stdout"] = subprocess.PIPE
1556*800a58d9SAndroid Build Coastguard Worker        popen_args["stderr"] = subprocess.PIPE
1557*800a58d9SAndroid Build Coastguard Worker
1558*800a58d9SAndroid Build Coastguard Worker        # Some OTA tools are Python scripts in different versions. The
1559*800a58d9SAndroid Build Coastguard Worker        # PYTHONPATH for acloud may be incompatible with the tools.
1560*800a58d9SAndroid Build Coastguard Worker        if "env" not in popen_args and "PYTHONPATH" in os.environ:
1561*800a58d9SAndroid Build Coastguard Worker            popen_env = os.environ.copy()
1562*800a58d9SAndroid Build Coastguard Worker            del popen_env["PYTHONPATH"]
1563*800a58d9SAndroid Build Coastguard Worker            popen_args["env"] = popen_env
1564*800a58d9SAndroid Build Coastguard Worker
1565*800a58d9SAndroid Build Coastguard Worker        proc = subprocess.Popen(command, **popen_args)
1566*800a58d9SAndroid Build Coastguard Worker        stdout, stderr = proc.communicate()
1567*800a58d9SAndroid Build Coastguard Worker        logger.info("%s stdout: %s", command[0], stdout)
1568*800a58d9SAndroid Build Coastguard Worker        logger.info("%s stderr: %s", command[0], stderr)
1569*800a58d9SAndroid Build Coastguard Worker
1570*800a58d9SAndroid Build Coastguard Worker        if proc.returncode != 0:
1571*800a58d9SAndroid Build Coastguard Worker            raise errors.SubprocessFail("%s returned %d." %
1572*800a58d9SAndroid Build Coastguard Worker                                        (command[0], proc.returncode))
1573*800a58d9SAndroid Build Coastguard Worker    finally:
1574*800a58d9SAndroid Build Coastguard Worker        if proc and proc.poll() is None:
1575*800a58d9SAndroid Build Coastguard Worker            logger.info("Kill %s", command[0])
1576*800a58d9SAndroid Build Coastguard Worker            proc.kill()
1577*800a58d9SAndroid Build Coastguard Worker
1578*800a58d9SAndroid Build Coastguard Worker
1579*800a58d9SAndroid Build Coastguard Workerdef SetExecutable(path):
1580*800a58d9SAndroid Build Coastguard Worker    """Grant the persmission to execute a file.
1581*800a58d9SAndroid Build Coastguard Worker
1582*800a58d9SAndroid Build Coastguard Worker    Args:
1583*800a58d9SAndroid Build Coastguard Worker        path: String, the file path.
1584*800a58d9SAndroid Build Coastguard Worker
1585*800a58d9SAndroid Build Coastguard Worker    Raises:
1586*800a58d9SAndroid Build Coastguard Worker        OSError if any file operation fails.
1587*800a58d9SAndroid Build Coastguard Worker    """
1588*800a58d9SAndroid Build Coastguard Worker    mode = os.stat(path).st_mode
1589*800a58d9SAndroid Build Coastguard Worker    os.chmod(path, mode | (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH |
1590*800a58d9SAndroid Build Coastguard Worker                           stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH))
1591*800a58d9SAndroid Build Coastguard Worker
1592*800a58d9SAndroid Build Coastguard Worker
1593*800a58d9SAndroid Build Coastguard Workerdef SetDirectoryTreeExecutable(dir_path):
1594*800a58d9SAndroid Build Coastguard Worker    """Grant the permission to execute all files in a directory.
1595*800a58d9SAndroid Build Coastguard Worker
1596*800a58d9SAndroid Build Coastguard Worker    Args:
1597*800a58d9SAndroid Build Coastguard Worker        dir_path: String, the directory path.
1598*800a58d9SAndroid Build Coastguard Worker
1599*800a58d9SAndroid Build Coastguard Worker    Raises:
1600*800a58d9SAndroid Build Coastguard Worker        OSError if any file operation fails.
1601*800a58d9SAndroid Build Coastguard Worker    """
1602*800a58d9SAndroid Build Coastguard Worker    for parent_dir, _, file_names in os.walk(dir_path):
1603*800a58d9SAndroid Build Coastguard Worker        for name in file_names:
1604*800a58d9SAndroid Build Coastguard Worker            SetExecutable(os.path.join(parent_dir, name))
1605*800a58d9SAndroid Build Coastguard Worker
1606*800a58d9SAndroid Build Coastguard Worker
1607*800a58d9SAndroid Build Coastguard Workerdef GetCvdPorts():
1608*800a58d9SAndroid Build Coastguard Worker    """Get CVD ports
1609*800a58d9SAndroid Build Coastguard Worker
1610*800a58d9SAndroid Build Coastguard Worker
1611*800a58d9SAndroid Build Coastguard Worker    Returns:
1612*800a58d9SAndroid Build Coastguard Worker        ForwardedPorts: vnc port and adb port.
1613*800a58d9SAndroid Build Coastguard Worker    """
1614*800a58d9SAndroid Build Coastguard Worker    return AVD_PORT_DICT[constants.TYPE_CF]
1615*800a58d9SAndroid Build Coastguard Worker
1616*800a58d9SAndroid Build Coastguard Worker
1617*800a58d9SAndroid Build Coastguard Workerdef SetCvdPorts(base_instance_num):
1618*800a58d9SAndroid Build Coastguard Worker    """Adjust ports by base_instance_num.
1619*800a58d9SAndroid Build Coastguard Worker
1620*800a58d9SAndroid Build Coastguard Worker    Args:
1621*800a58d9SAndroid Build Coastguard Worker        base_instance_num: int, cuttlefish base_instance_num.
1622*800a58d9SAndroid Build Coastguard Worker    """
1623*800a58d9SAndroid Build Coastguard Worker    offset = (base_instance_num or 1) - 1
1624*800a58d9SAndroid Build Coastguard Worker    AVD_PORT_DICT[constants.TYPE_CF] = ForwardedPorts(
1625*800a58d9SAndroid Build Coastguard Worker        constants.CF_VNC_PORT + offset, constants.CF_ADB_PORT + offset)
1626*800a58d9SAndroid Build Coastguard Worker
1627*800a58d9SAndroid Build Coastguard Worker    # TODO: adjust WebRTC ports
1628