blob: b044d3879f6876ddf4bcb664f3819fa170e4782e [file] [log] [blame]
Keun Soo Yimb293fdb2016-09-21 16:03:44 -07001# Copyright 2016 - The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
Fang Deng26e4dc12018-03-04 19:01:59 -080014"""Common Utilities."""
Sam Chiu99dfee32018-11-20 10:19:17 +080015# pylint: disable=too-many-lines
Sam Chiu81bdc652018-06-29 18:45:08 +080016from __future__ import print_function
cylan66713722018-10-06 01:38:26 +080017
18from distutils.spawn import find_executable
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070019import base64
20import binascii
cylan66713722018-10-06 01:38:26 +080021import collections
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070022import errno
Fang Deng69498c32017-03-02 14:29:30 -080023import getpass
herbertxue07293a32018-11-05 20:40:11 +080024import grp
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070025import logging
26import os
Sam Chiu6c738d62018-12-04 10:29:02 +080027import platform
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070028import shutil
29import struct
cylan66713722018-10-06 01:38:26 +080030import socket
Fang Deng69498c32017-03-02 14:29:30 -080031import subprocess
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070032import sys
33import tarfile
34import tempfile
35import time
36import uuid
chojoycecd004bc2018-09-13 10:39:00 +080037import zipfile
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070038
Sam Chiu7de3b232018-12-06 19:45:52 +080039from acloud import errors
herbertxue34776bb2018-07-03 21:57:48 +080040from acloud.internal import constants
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070041
42logger = logging.getLogger(__name__)
43
Fang Deng69498c32017-03-02 14:29:30 -080044SSH_KEYGEN_CMD = ["ssh-keygen", "-t", "rsa", "-b", "4096"]
cylan4f73c1f2018-07-19 16:40:31 +080045SSH_KEYGEN_PUB_CMD = ["ssh-keygen", "-y"]
Kevin Chengce6cfb02018-12-04 13:21:31 -080046SSH_ARGS = ["-o", "UserKnownHostsFile=/dev/null",
47 "-o", "StrictHostKeyChecking=no"]
48SSH_CMD = ["ssh"] + SSH_ARGS
49SCP_CMD = ["scp"] + SSH_ARGS
Kevin Cheng0a33a072018-12-11 15:35:26 -080050GET_BUILD_VAR_CMD = ["build/soong/soong_ui.bash", "--dumpvar-mode"]
Kevin Chengd25feee2018-05-24 10:15:20 -070051DEFAULT_RETRY_BACKOFF_FACTOR = 1
52DEFAULT_SLEEP_MULTIPLIER = 0
Fang Deng69498c32017-03-02 14:29:30 -080053
cylan66713722018-10-06 01:38:26 +080054_SSH_TUNNEL_ARGS = ("-i %(rsa_key_file)s -o UserKnownHostsFile=/dev/null "
55 "-o StrictHostKeyChecking=no "
56 "-L %(vnc_port)d:127.0.0.1:%(target_vnc_port)d "
57 "-L %(adb_port)d:127.0.0.1:%(target_adb_port)d "
58 "-N -f -l %(ssh_user)s %(ip_addr)s")
cylan66713722018-10-06 01:38:26 +080059_ADB_CONNECT_ARGS = "connect 127.0.0.1:%(adb_port)d"
60# Store the ports that vnc/adb are forwarded to, both are integers.
61ForwardedPorts = collections.namedtuple("ForwardedPorts", [constants.VNC_PORT,
62 constants.ADB_PORT])
Kevin Chengae7a49d2018-10-18 14:11:22 -070063_VNC_BIN = "ssvnc"
herbertxue9e1e27a2018-12-12 16:25:27 +080064_CMD_KILL = ["pkill", "-9", "-f"]
herbertxue07293a32018-11-05 20:40:11 +080065_CMD_PGREP = "pgrep"
66_CMD_SG = "sg "
cylan4569dca2018-11-02 12:12:53 +080067_CMD_START_VNC = "%(bin)s vnc://127.0.0.1:%(port)d"
Kevin Chengae7a49d2018-10-18 14:11:22 -070068_CMD_INSTALL_SSVNC = "sudo apt-get --assume-yes install ssvnc"
69_ENV_DISPLAY = "DISPLAY"
Sam Chiu7a477f52018-10-22 11:20:36 +080070_SSVNC_ENV_VARS = {"SSVNC_NO_ENC_WARN": "1", "SSVNC_SCALE": "auto"}
Kevin Cheng53aa5a52018-12-03 01:33:55 -080071_DEFAULT_DISPLAY_SCALE = 1.0
Kevin Cheng0a33a072018-12-11 15:35:26 -080072_DIST_DIR = "DIST_DIR"
Kevin Chengae7a49d2018-10-18 14:11:22 -070073
74_CONFIRM_CONTINUE = ("In order to display the screen to the AVD, we'll need to "
75 "install a vnc client (ssnvc). \nWould you like acloud to "
76 "install it for you? (%s) \nPress 'y' to continue or "
77 "anything else to abort it:[y] ") % _CMD_INSTALL_SSVNC
Sam Chiu99dfee32018-11-20 10:19:17 +080078_EvaluatedResult = collections.namedtuple("EvaluatedResult",
79 ["is_result_ok", "result_message"])
Sam Chiu6c738d62018-12-04 10:29:02 +080080# dict of supported system and their distributions.
81_SUPPORTED_SYSTEMS_AND_DISTS = {"Linux": ["Ubuntu", "Debian"]}
Fang Deng69498c32017-03-02 14:29:30 -080082
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070083class TempDir(object):
Fang Deng26e4dc12018-03-04 19:01:59 -080084 """A context manager that ceates a temporary directory.
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070085
Fang Deng26e4dc12018-03-04 19:01:59 -080086 Attributes:
Sam Chiu81bdc652018-06-29 18:45:08 +080087 path: The path of the temporary directory.
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070088 """
89
Fang Deng26e4dc12018-03-04 19:01:59 -080090 def __init__(self):
91 self.path = tempfile.mkdtemp()
92 os.chmod(self.path, 0o700)
93 logger.debug("Created temporary dir %s", self.path)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070094
95 def __enter__(self):
Fang Deng26e4dc12018-03-04 19:01:59 -080096 """Enter."""
97 return self.path
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070098
Fang Deng26e4dc12018-03-04 19:01:59 -080099 def __exit__(self, exc_type, exc_value, traceback):
100 """Exit.
101
102 Args:
103 exc_type: Exception type raised within the context manager.
104 None if no execption is raised.
105 exc_value: Exception instance raised within the context manager.
106 None if no execption is raised.
107 traceback: Traceback for exeception that is raised within
108 the context manager.
109 None if no execption is raised.
110 Raises:
111 EnvironmentError or OSError when failed to delete temp directory.
112 """
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700113 try:
Fang Deng26e4dc12018-03-04 19:01:59 -0800114 if self.path:
115 shutil.rmtree(self.path)
116 logger.debug("Deleted temporary dir %s", self.path)
117 except EnvironmentError as e:
118 # Ignore error if there is no exception raised
119 # within the with-clause and the EnvironementError is
120 # about problem that directory or file does not exist.
121 if not exc_type and e.errno != errno.ENOENT:
122 raise
123 except Exception as e: # pylint: disable=W0703
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700124 if exc_type:
Fang Deng26e4dc12018-03-04 19:01:59 -0800125 logger.error(
cylan0d77ae12018-05-18 08:36:48 +0000126 "Encountered error while deleting %s: %s",
127 self.path,
128 str(e),
129 exc_info=True)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700130 else:
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700131 raise
132
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700133
cylan0d77ae12018-05-18 08:36:48 +0000134def RetryOnException(retry_checker,
135 max_retries,
136 sleep_multiplier=0,
Fang Dengf24be082018-02-10 10:09:55 -0800137 retry_backoff_factor=1):
cylan0d77ae12018-05-18 08:36:48 +0000138 """Decorater which retries the function call if |retry_checker| returns true.
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700139
cylan0d77ae12018-05-18 08:36:48 +0000140 Args:
141 retry_checker: A callback function which should take an exception instance
142 and return True if functor(*args, **kwargs) should be retried
143 when such exception is raised, and return False if it should
144 not be retried.
145 max_retries: Maximum number of retries allowed.
146 sleep_multiplier: Will sleep sleep_multiplier * attempt_count seconds if
147 retry_backoff_factor is 1. Will sleep
148 sleep_multiplier * (
149 retry_backoff_factor ** (attempt_count - 1))
150 if retry_backoff_factor != 1.
151 retry_backoff_factor: See explanation of sleep_multiplier.
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700152
cylan0d77ae12018-05-18 08:36:48 +0000153 Returns:
154 The function wrapper.
155 """
156
157 def _Wrapper(func):
158 def _FunctionWrapper(*args, **kwargs):
159 return Retry(retry_checker, max_retries, func, sleep_multiplier,
160 retry_backoff_factor, *args, **kwargs)
161
162 return _FunctionWrapper
163
164 return _Wrapper
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700165
166
cylan4f73c1f2018-07-19 16:40:31 +0800167def Retry(retry_checker, max_retries, functor, sleep_multiplier,
168 retry_backoff_factor, *args, **kwargs):
cylan0d77ae12018-05-18 08:36:48 +0000169 """Conditionally retry a function.
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700170
cylan0d77ae12018-05-18 08:36:48 +0000171 Args:
172 retry_checker: A callback function which should take an exception instance
173 and return True if functor(*args, **kwargs) should be retried
174 when such exception is raised, and return False if it should
175 not be retried.
176 max_retries: Maximum number of retries allowed.
177 functor: The function to call, will call functor(*args, **kwargs).
178 sleep_multiplier: Will sleep sleep_multiplier * attempt_count seconds if
179 retry_backoff_factor is 1. Will sleep
180 sleep_multiplier * (
181 retry_backoff_factor ** (attempt_count - 1))
182 if retry_backoff_factor != 1.
183 retry_backoff_factor: See explanation of sleep_multiplier.
184 *args: Arguments to pass to the functor.
185 **kwargs: Key-val based arguments to pass to the functor.
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700186
cylan0d77ae12018-05-18 08:36:48 +0000187 Returns:
188 The return value of the functor.
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700189
cylan0d77ae12018-05-18 08:36:48 +0000190 Raises:
191 Exception: The exception that functor(*args, **kwargs) throws.
192 """
193 attempt_count = 0
194 while attempt_count <= max_retries:
195 try:
196 attempt_count += 1
197 return_value = functor(*args, **kwargs)
198 return return_value
199 except Exception as e: # pylint: disable=W0703
200 if retry_checker(e) and attempt_count <= max_retries:
201 if retry_backoff_factor != 1:
202 sleep = sleep_multiplier * (retry_backoff_factor**
203 (attempt_count - 1))
204 else:
205 sleep = sleep_multiplier * attempt_count
Kevin Chengd25feee2018-05-24 10:15:20 -0700206 time.sleep(sleep)
cylan0d77ae12018-05-18 08:36:48 +0000207 else:
208 raise
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700209
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700210
Fang Dengf24be082018-02-10 10:09:55 -0800211def RetryExceptionType(exception_types, max_retries, functor, *args, **kwargs):
cylan0d77ae12018-05-18 08:36:48 +0000212 """Retry exception if it is one of the given types.
Fang Dengf24be082018-02-10 10:09:55 -0800213
cylan0d77ae12018-05-18 08:36:48 +0000214 Args:
215 exception_types: A tuple of exception types, e.g. (ValueError, KeyError)
216 max_retries: Max number of retries allowed.
217 functor: The function to call. Will be retried if exception is raised and
218 the exception is one of the exception_types.
219 *args: Arguments to pass to Retry function.
220 **kwargs: Key-val based arguments to pass to Retry functions.
Fang Dengf24be082018-02-10 10:09:55 -0800221
cylan0d77ae12018-05-18 08:36:48 +0000222 Returns:
223 The value returned by calling functor.
224 """
225 return Retry(lambda e: isinstance(e, exception_types), max_retries,
226 functor, *args, **kwargs)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700227
228
229def PollAndWait(func, expected_return, timeout_exception, timeout_secs,
230 sleep_interval_secs, *args, **kwargs):
231 """Call a function until the function returns expected value or times out.
232
233 Args:
234 func: Function to call.
235 expected_return: The expected return value.
236 timeout_exception: Exception to raise when it hits timeout.
237 timeout_secs: Timeout seconds.
238 If 0 or less than zero, the function will run once and
239 we will not wait on it.
240 sleep_interval_secs: Time to sleep between two attemps.
241 *args: list of args to pass to func.
242 **kwargs: dictionary of keyword based args to pass to func.
243
244 Raises:
245 timeout_exception: if the run of function times out.
246 """
247 # TODO(fdeng): Currently this method does not kill
248 # |func|, if |func| takes longer than |timeout_secs|.
249 # We can use a more robust version from chromite.
250 start = time.time()
251 while True:
252 return_value = func(*args, **kwargs)
253 if return_value == expected_return:
254 return
255 elif time.time() - start > timeout_secs:
256 raise timeout_exception
257 else:
258 if sleep_interval_secs > 0:
259 time.sleep(sleep_interval_secs)
260
261
262def GenerateUniqueName(prefix=None, suffix=None):
Sam Chiu81bdc652018-06-29 18:45:08 +0800263 """Generate a random unique name using uuid4.
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700264
265 Args:
266 prefix: String, desired prefix to prepend to the generated name.
267 suffix: String, desired suffix to append to the generated name.
268
269 Returns:
270 String, a random name.
271 """
272 name = uuid.uuid4().hex
273 if prefix:
274 name = "-".join([prefix, name])
275 if suffix:
276 name = "-".join([name, suffix])
277 return name
278
279
280def MakeTarFile(src_dict, dest):
281 """Archive files in tar.gz format to a file named as |dest|.
282
283 Args:
284 src_dict: A dictionary that maps a path to be archived
285 to the corresponding name that appears in the archive.
286 dest: String, path to output file, e.g. /tmp/myfile.tar.gz
287 """
288 logger.info("Compressing %s into %s.", src_dict.keys(), dest)
289 with tarfile.open(dest, "w:gz") as tar:
290 for src, arcname in src_dict.iteritems():
291 tar.add(src, arcname=arcname)
292
293
Kevin Chengce6cfb02018-12-04 13:21:31 -0800294def ScpPullFile(src_file, dst_file, host_name, user_name=None,
295 rsa_key_file=None):
296 """Scp pull file from remote.
297
298 Args:
299 src_file: The source file path to be pulled.
300 dst_file: The destiation file path the file is pulled to.
301 host_name: The device host_name or ip to pull file from.
302 user_name: The user_name for scp session.
303 rsa_key_file: The rsa key file.
304 Raises:
305 errors.DeviceConnectionError if scp failed.
306 """
307 scp_cmd_list = SCP_CMD[:]
308 if rsa_key_file:
309 scp_cmd_list.extend(["-i", rsa_key_file])
310 else:
311 logger.warning(
312 "Rsa key file is not specified. "
313 "Will use default rsa key set in user environment")
314 if user_name:
315 scp_cmd_list.append("%s@%s:%s" % (user_name, host_name, src_file))
316 else:
317 scp_cmd_list.append("%s:%s" % (host_name, src_file))
318 scp_cmd_list.append(dst_file)
319 try:
320 subprocess.check_call(scp_cmd_list)
321 except subprocess.CalledProcessError as e:
322 raise errors.DeviceConnectionError(
323 "Failed to pull file %s from %s with '%s': %s" % (
324 src_file, host_name, " ".join(scp_cmd_list), e))
325
326
Fang Deng69498c32017-03-02 14:29:30 -0800327def CreateSshKeyPairIfNotExist(private_key_path, public_key_path):
328 """Create the ssh key pair if they don't exist.
329
cylan4f73c1f2018-07-19 16:40:31 +0800330 Case1. If the private key doesn't exist, we will create both the public key
331 and the private key.
332 Case2. If the private key exists but public key doesn't, we will create the
333 public key by using the private key.
334 Case3. If the public key exists but the private key doesn't, we will create
335 a new private key and overwrite the public key.
Fang Deng69498c32017-03-02 14:29:30 -0800336
337 Args:
338 private_key_path: Path to the private key file.
339 e.g. ~/.ssh/acloud_rsa
340 public_key_path: Path to the public key file.
341 e.g. ~/.ssh/acloud_rsa.pub
cylan4f73c1f2018-07-19 16:40:31 +0800342
Fang Deng69498c32017-03-02 14:29:30 -0800343 Raises:
344 error.DriverError: If failed to create the key pair.
345 """
346 public_key_path = os.path.expanduser(public_key_path)
347 private_key_path = os.path.expanduser(private_key_path)
cylan4f73c1f2018-07-19 16:40:31 +0800348 public_key_exist = os.path.exists(public_key_path)
349 private_key_exist = os.path.exists(private_key_path)
350 if public_key_exist and private_key_exist:
cylan0d77ae12018-05-18 08:36:48 +0000351 logger.debug(
cylan4f73c1f2018-07-19 16:40:31 +0800352 "The ssh private key (%s) and public key (%s) already exist,"
cylan0d77ae12018-05-18 08:36:48 +0000353 "will not automatically create the key pairs.", private_key_path,
354 public_key_path)
Fang Deng69498c32017-03-02 14:29:30 -0800355 return
cylan4f73c1f2018-07-19 16:40:31 +0800356 key_folder = os.path.dirname(private_key_path)
357 if not os.path.exists(key_folder):
358 os.makedirs(key_folder)
Fang Deng69498c32017-03-02 14:29:30 -0800359 try:
cylan4f73c1f2018-07-19 16:40:31 +0800360 if private_key_exist:
361 cmd = SSH_KEYGEN_PUB_CMD + ["-f", private_key_path]
362 with open(public_key_path, 'w') as outfile:
363 stream_content = subprocess.check_output(cmd)
364 outfile.write(
365 stream_content.rstrip('\n') + " " + getpass.getuser())
366 logger.info(
367 "The ssh public key (%s) do not exist, "
368 "automatically creating public key, calling: %s",
369 public_key_path, " ".join(cmd))
370 else:
371 cmd = SSH_KEYGEN_CMD + [
372 "-C", getpass.getuser(), "-f", private_key_path
373 ]
374 logger.info(
375 "Creating public key from private key (%s) via cmd: %s",
376 private_key_path, " ".join(cmd))
377 subprocess.check_call(cmd, stdout=sys.stderr, stderr=sys.stdout)
Fang Deng69498c32017-03-02 14:29:30 -0800378 except subprocess.CalledProcessError as e:
cylan0d77ae12018-05-18 08:36:48 +0000379 raise errors.DriverError("Failed to create ssh key pair: %s" % str(e))
Fang Deng69498c32017-03-02 14:29:30 -0800380 except OSError as e:
381 raise errors.DriverError(
cylan0d77ae12018-05-18 08:36:48 +0000382 "Failed to create ssh key pair, please make sure "
383 "'ssh-keygen' is installed: %s" % str(e))
Fang Deng69498c32017-03-02 14:29:30 -0800384
385 # By default ssh-keygen will create a public key file
386 # by append .pub to the private key file name. Rename it
387 # to what's requested by public_key_path.
388 default_pub_key_path = "%s.pub" % private_key_path
389 try:
390 if default_pub_key_path != public_key_path:
391 os.rename(default_pub_key_path, public_key_path)
392 except OSError as e:
393 raise errors.DriverError(
cylan0d77ae12018-05-18 08:36:48 +0000394 "Failed to rename %s to %s: %s" % (default_pub_key_path,
395 public_key_path, str(e)))
Fang Deng69498c32017-03-02 14:29:30 -0800396
397 logger.info("Created ssh private key (%s) and public key (%s)",
398 private_key_path, public_key_path)
399
400
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700401def VerifyRsaPubKey(rsa):
402 """Verify the format of rsa public key.
403
404 Args:
405 rsa: content of rsa public key. It should follow the format of
406 ssh-rsa AAAAB3NzaC1yc2EA.... test@test.com
407
408 Raises:
409 DriverError if the format is not correct.
410 """
411 if not rsa or not all(ord(c) < 128 for c in rsa):
412 raise errors.DriverError(
413 "rsa key is empty or contains non-ascii character: %s" % rsa)
414
415 elements = rsa.split()
416 if len(elements) != 3:
417 raise errors.DriverError("rsa key is invalid, wrong format: %s" % rsa)
418
419 key_type, data, _ = elements
420 try:
421 binary_data = base64.decodestring(data)
422 # number of bytes of int type
423 int_length = 4
424 # binary_data is like "7ssh-key..." in a binary format.
425 # The first 4 bytes should represent 7, which should be
426 # the length of the following string "ssh-key".
427 # And the next 7 bytes should be string "ssh-key".
428 # We will verify that the rsa conforms to this format.
429 # ">I" in the following line means "big-endian unsigned integer".
430 type_length = struct.unpack(">I", binary_data[:int_length])[0]
431 if binary_data[int_length:int_length + type_length] != key_type:
432 raise errors.DriverError("rsa key is invalid: %s" % rsa)
433 except (struct.error, binascii.Error) as e:
cylan0d77ae12018-05-18 08:36:48 +0000434 raise errors.DriverError(
435 "rsa key is invalid: %s, error: %s" % (rsa, str(e)))
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700436
chojoycecd004bc2018-09-13 10:39:00 +0800437def Decompress(sourcefile, dest=None):
438 """Decompress .zip or .tar.gz.
439
440 Args:
441 sourcefile: A string, a source file path to decompress.
442 dest: A string, a folder path as decompress destination.
443
444 Raises:
445 errors.UnsupportedCompressionFileType: Not supported extension.
446 """
447 logger.info("Start to decompress %s!", sourcefile)
448 dest_path = dest if dest else "."
449 if sourcefile.endswith(".tar.gz"):
450 with tarfile.open(sourcefile, "r:gz") as compressor:
451 compressor.extractall(dest_path)
452 elif sourcefile.endswith(".zip"):
453 with zipfile.ZipFile(sourcefile, 'r') as compressor:
454 compressor.extractall(dest_path)
455 else:
Sam Chiu7de3b232018-12-06 19:45:52 +0800456 raise errors.UnsupportedCompressionFileType(
chojoycecd004bc2018-09-13 10:39:00 +0800457 "Sorry, we could only support compression file type "
458 "for zip or tar.gz.")
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700459
Sam Chiu81bdc652018-06-29 18:45:08 +0800460# pylint: disable=old-style-class,no-init
461class TextColors:
462 """A class that defines common color ANSI code."""
463
464 HEADER = "\033[95m"
465 OKBLUE = "\033[94m"
466 OKGREEN = "\033[92m"
467 WARNING = "\033[93m"
468 FAIL = "\033[91m"
469 ENDC = "\033[0m"
470 BOLD = "\033[1m"
471 UNDERLINE = "\033[4m"
472
473
herbertxuedf01c422018-09-06 19:52:52 +0800474def PrintColorString(message, colors=TextColors.OKBLUE, **kwargs):
Sam Chiu81bdc652018-06-29 18:45:08 +0800475 """A helper function to print out colored text.
476
herbertxuedf01c422018-09-06 19:52:52 +0800477 Use print function "print(message, end="")" to show message in one line.
478 Example code:
479 DisplayMessages("Creating GCE instance...", end="")
480 # Job execute 20s
481 DisplayMessages("Done! (20s)")
482 Display:
483 Creating GCE instance...
484 # After job finished, messages update as following:
485 Creating GCE instance...Done! (20s)
486
Sam Chiu81bdc652018-06-29 18:45:08 +0800487 Args:
488 message: String, the message text.
489 colors: String, color code.
herbertxuedf01c422018-09-06 19:52:52 +0800490 **kwargs: dictionary of keyword based args to pass to func.
Sam Chiu81bdc652018-06-29 18:45:08 +0800491 """
herbertxuedf01c422018-09-06 19:52:52 +0800492 print(colors + message + TextColors.ENDC, **kwargs)
493 sys.stdout.flush()
Sam Chiu81bdc652018-06-29 18:45:08 +0800494
495
496def InteractWithQuestion(question, colors=TextColors.WARNING):
497 """A helper function to define the common way to run interactive cmd.
498
499 Args:
500 question: String, the question to ask user.
501 colors: String, color code.
502
503 Returns:
504 String, input from user.
505 """
506 return str(raw_input(colors + question + TextColors.ENDC).strip())
507
herbertxuedf01c422018-09-06 19:52:52 +0800508
herbertxue34776bb2018-07-03 21:57:48 +0800509def GetUserAnswerYes(question):
510 """Ask user about acloud setup question.
511
512 Args:
513 question: String, ask question for user.
514 Ex: "Are you sure to change bucket name:[y/n]"
515
516 Returns:
517 Boolean, True if answer is "Yes", False otherwise.
518 """
519 answer = InteractWithQuestion(question)
520 return answer.lower() in constants.USER_ANSWER_YES
521
Sam Chiu81bdc652018-06-29 18:45:08 +0800522
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700523class BatchHttpRequestExecutor(object):
524 """A helper class that executes requests in batch with retry.
525
526 This executor executes http requests in a batch and retry
527 those that have failed. It iteratively updates the dictionary
528 self._final_results with latest results, which can be retrieved
529 via GetResults.
530 """
531
532 def __init__(self,
533 execute_once_functor,
534 requests,
535 retry_http_codes=None,
536 max_retry=None,
537 sleep=None,
538 backoff_factor=None,
539 other_retriable_errors=None):
540 """Initializes the executor.
541
542 Args:
543 execute_once_functor: A function that execute requests in batch once.
544 It should return a dictionary like
545 {request_id: (response, exception)}
546 requests: A dictionary where key is request id picked by caller,
547 and value is a apiclient.http.HttpRequest.
548 retry_http_codes: A list of http codes to retry.
Fang Dengf24be082018-02-10 10:09:55 -0800549 max_retry: See utils.Retry.
550 sleep: See utils.Retry.
551 backoff_factor: See utils.Retry.
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700552 other_retriable_errors: A tuple of error types that should be retried
553 other than errors.HttpError.
554 """
555 self._execute_once_functor = execute_once_functor
556 self._requests = requests
557 # A dictionary that maps request id to pending request.
558 self._pending_requests = {}
559 # A dictionary that maps request id to a tuple (response, exception).
560 self._final_results = {}
561 self._retry_http_codes = retry_http_codes
562 self._max_retry = max_retry
563 self._sleep = sleep
564 self._backoff_factor = backoff_factor
565 self._other_retriable_errors = other_retriable_errors
566
567 def _ShoudRetry(self, exception):
Sam Chiu81bdc652018-06-29 18:45:08 +0800568 """Check if an exception is retriable.
569
570 Args:
571 exception: An exception instance.
572 """
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700573 if isinstance(exception, self._other_retriable_errors):
574 return True
575
cylan0d77ae12018-05-18 08:36:48 +0000576 if (isinstance(exception, errors.HttpError)
577 and exception.code in self._retry_http_codes):
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700578 return True
579 return False
580
581 def _ExecuteOnce(self):
582 """Executes pending requests and update it with failed, retriable ones.
583
584 Raises:
585 HasRetriableRequestsError: if some requests fail and are retriable.
586 """
587 results = self._execute_once_functor(self._pending_requests)
588 # Update final_results with latest results.
589 self._final_results.update(results)
590 # Clear pending_requests
591 self._pending_requests.clear()
592 for request_id, result in results.iteritems():
593 exception = result[1]
594 if exception is not None and self._ShoudRetry(exception):
595 # If this is a retriable exception, put it in pending_requests
596 self._pending_requests[request_id] = self._requests[request_id]
597 if self._pending_requests:
598 # If there is still retriable requests pending, raise an error
Fang Dengf24be082018-02-10 10:09:55 -0800599 # so that Retry will retry this function with pending_requests.
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700600 raise errors.HasRetriableRequestsError(
cylan0d77ae12018-05-18 08:36:48 +0000601 "Retriable errors: %s" %
602 [str(results[rid][1]) for rid in self._pending_requests])
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700603
604 def Execute(self):
605 """Executes the requests and retry if necessary.
606
607 Will populate self._final_results.
608 """
cylan0d77ae12018-05-18 08:36:48 +0000609
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700610 def _ShouldRetryHandler(exc):
611 """Check if |exc| is a retriable exception.
612
613 Args:
614 exc: An exception.
615
616 Returns:
617 True if exception is of type HasRetriableRequestsError; False otherwise.
618 """
619 should_retry = isinstance(exc, errors.HasRetriableRequestsError)
620 if should_retry:
621 logger.info("Will retry failed requests.", exc_info=True)
622 logger.info("%s", exc)
623 return should_retry
624
625 try:
626 self._pending_requests = self._requests.copy()
Fang Dengf24be082018-02-10 10:09:55 -0800627 Retry(
cylan0d77ae12018-05-18 08:36:48 +0000628 _ShouldRetryHandler,
629 max_retries=self._max_retry,
Fang Dengf24be082018-02-10 10:09:55 -0800630 functor=self._ExecuteOnce,
631 sleep_multiplier=self._sleep,
632 retry_backoff_factor=self._backoff_factor)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700633 except errors.HasRetriableRequestsError:
634 logger.debug("Some requests did not succeed after retry.")
635
636 def GetResults(self):
637 """Returns final results.
638
639 Returns:
640 results, a dictionary in the following format
641 {request_id: (response, exception)}
642 request_ids are those from requests; response
643 is the http response for the request or None on error;
644 exception is an instance of DriverError or None if no error.
645 """
646 return self._final_results
cylan31fc5332018-09-17 22:12:08 +0800647
648
Sam Chiu99dfee32018-11-20 10:19:17 +0800649def DefaultEvaluator(result):
650 """Default Evaluator always return result is ok.
651
652 Args:
653 result:the return value of the target function.
654 """
655 return _EvaluatedResult(is_result_ok=True, result_message=result)
656
657
658def ReportEvaluator(report):
659 """Evalute the the acloud operation by the report.
660
661 Args:
662 report:acloud.public.report() object.
663 """
664 if report is None or report.errors:
665 return _EvaluatedResult(is_result_ok=False, result_message=report.errors)
666
667 return _EvaluatedResult(is_result_ok=True, result_message=None)
668
669
cylan31fc5332018-09-17 22:12:08 +0800670class TimeExecute(object):
671 """Count the function execute time."""
672
Sam Chiu99dfee32018-11-20 10:19:17 +0800673 def __init__(self, function_description=None, print_before_call=True,
674 print_status=True, result_evaluator=DefaultEvaluator,
675 display_waiting_dots=True):
cylan31fc5332018-09-17 22:12:08 +0800676 """Initializes the class.
677
678 Args:
679 function_description: String that describes function (e.g."Creating
680 Instance...")
681 print_before_call: Boolean, print the function description before
682 calling the function, default True.
683 print_status: Boolean, print the status of the function after the
684 function has completed, default True ("OK" or "Fail").
Sam Chiu99dfee32018-11-20 10:19:17 +0800685 result_evaluator: Func object. Pass func to evaluate result.
686 Default evaluator always report result is ok and
687 failed result will be identified only in exception
688 case.
689 display_waiting_dots: Boolean, if true print the function_description
690 followed by waiting dot.
cylan31fc5332018-09-17 22:12:08 +0800691 """
692 self._function_description = function_description
693 self._print_before_call = print_before_call
694 self._print_status = print_status
Sam Chiu99dfee32018-11-20 10:19:17 +0800695 self._result_evaluator = result_evaluator
696 self._display_waiting_dots = display_waiting_dots
cylan31fc5332018-09-17 22:12:08 +0800697
698 def __call__(self, func):
699 def DecoratorFunction(*args, **kargs):
700 """Decorator function.
701
702 Args:
703 *args: Arguments to pass to the functor.
704 **kwargs: Key-val based arguments to pass to the functor.
705
706 Raises:
707 Exception: The exception that functor(*args, **kwargs) throws.
708 """
709 timestart = time.time()
710 if self._print_before_call:
Sam Chiu99dfee32018-11-20 10:19:17 +0800711 waiting_dots = "..." if self._display_waiting_dots else ""
712 PrintColorString("%s %s"% (self._function_description,
713 waiting_dots), end="")
cylan31fc5332018-09-17 22:12:08 +0800714 try:
715 result = func(*args, **kargs)
Sam Chiu99dfee32018-11-20 10:19:17 +0800716 result_time = time.time() - timestart
cylan31fc5332018-09-17 22:12:08 +0800717 if not self._print_before_call:
718 PrintColorString("%s (%ds)" % (self._function_description,
Sam Chiu99dfee32018-11-20 10:19:17 +0800719 result_time),
cylan31fc5332018-09-17 22:12:08 +0800720 TextColors.OKGREEN)
721 if self._print_status:
Sam Chiu99dfee32018-11-20 10:19:17 +0800722 evaluated_result = self._result_evaluator(result)
723 if evaluated_result.is_result_ok:
724 PrintColorString("OK! (%ds)" % (result_time),
725 TextColors.OKGREEN)
726 else:
727 PrintColorString("Fail! (%ds)" % (result_time),
728 TextColors.FAIL)
729 PrintColorString("Error: %s" %
730 evaluated_result.result_message,
731 TextColors.FAIL)
cylan31fc5332018-09-17 22:12:08 +0800732 return result
733 except:
734 if self._print_status:
735 PrintColorString("Fail! (%ds)" % (time.time()-timestart),
736 TextColors.FAIL)
737 raise
738 return DecoratorFunction
cylan66713722018-10-06 01:38:26 +0800739
740
741def PickFreePort():
742 """Helper to pick a free port.
743
744 Returns:
745 Integer, a free port number.
746 """
747 tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
748 tcp_socket.bind(("", 0))
749 port = tcp_socket.getsockname()[1]
750 tcp_socket.close()
751 return port
752
753
754def _ExecuteCommand(cmd, args):
755 """Execute command.
756
757 Args:
758 cmd: Strings of execute binary name.
759 args: List of args to pass in with cmd.
760
761 Raises:
762 errors.NoExecuteBin: Can't find the execute bin file.
763 """
764 bin_path = find_executable(cmd)
765 if not bin_path:
Sam Chiu7de3b232018-12-06 19:45:52 +0800766 raise errors.NoExecuteCmd("unable to locate %s" % cmd)
cylan66713722018-10-06 01:38:26 +0800767 command = [bin_path] + args
768 logger.debug("Running '%s'", ' '.join(command))
769 with open(os.devnull, "w") as dev_null:
770 subprocess.check_call(command, stderr=dev_null, stdout=dev_null)
771
772
773# pylint: disable=too-many-locals
774def AutoConnect(ip_addr, rsa_key_file, target_vnc_port, target_adb_port, ssh_user):
775 """Autoconnect to an AVD instance.
776
777 Args:
778 ip_addr: String, use to build the adb & vnc tunnel between local
779 and remote instance.
780 rsa_key_file: String, Private key file path to use when creating
781 the ssh tunnels.
782 target_vnc_port: Integer of target vnc port number.
783 target_adb_port: Integer of target adb port number.
784 ssh_user: String of user login into the instance.
785
786 Returns:
787 NamedTuple of (vnc_port, adb_port) SSHTUNNEL of the connect, both are
788 integers.
789 """
790 local_free_vnc_port = PickFreePort()
791 local_free_adb_port = PickFreePort()
792 try:
793 ssh_tunnel_args = _SSH_TUNNEL_ARGS % {
794 "rsa_key_file": rsa_key_file,
795 "vnc_port": local_free_vnc_port,
796 "adb_port": local_free_adb_port,
797 "target_vnc_port": target_vnc_port,
798 "target_adb_port": target_adb_port,
799 "ssh_user": ssh_user,
800 "ip_addr": ip_addr}
Kevin Cheng835a4152018-10-11 10:46:57 -0700801 _ExecuteCommand(constants.SSH_BIN, ssh_tunnel_args.split())
cylan66713722018-10-06 01:38:26 +0800802 except subprocess.CalledProcessError:
803 PrintColorString("Failed to create ssh tunnels, retry with '#acloud "
804 "reconnect'.", TextColors.FAIL)
805 try:
806 adb_connect_args = _ADB_CONNECT_ARGS % {"adb_port": local_free_adb_port}
Kevin Cheng835a4152018-10-11 10:46:57 -0700807 _ExecuteCommand(constants.ADB_BIN, adb_connect_args.split())
cylan66713722018-10-06 01:38:26 +0800808 except subprocess.CalledProcessError:
809 PrintColorString("Failed to adb connect, retry with "
810 "'#acloud reconnect'", TextColors.FAIL)
811
812 return ForwardedPorts(vnc_port=local_free_vnc_port,
813 adb_port=local_free_adb_port)
Kevin Chengeb85e862018-10-09 15:35:13 -0700814
815
816def GetAnswerFromList(answer_list, enable_choose_all=False):
817 """Get answer from a list.
818
819 Args:
820 answer_list: list of the answers to choose from.
821
822 Return:
823 List holding the answer(s).
824 """
825 print("[0] to exit.")
826 start_index = 1
Morris Linc1118a42018-12-09 21:56:47 +0800827 max_choice = len(answer_list)
828
Kevin Chengeb85e862018-10-09 15:35:13 -0700829 if enable_choose_all:
830 start_index = 2
Morris Linc1118a42018-12-09 21:56:47 +0800831 max_choice += 1
Kevin Chengeb85e862018-10-09 15:35:13 -0700832 print("[1] for all.")
833 for num, item in enumerate(answer_list, start_index):
834 print("[%d] %s" % (num, item))
835
836 choice = -1
Morris Linc1118a42018-12-09 21:56:47 +0800837
Kevin Chengeb85e862018-10-09 15:35:13 -0700838 while True:
839 try:
840 choice = raw_input("Enter your choice[0-%d]: " % max_choice)
841 choice = int(choice)
842 except ValueError:
843 print("'%s' is not a valid integer.", choice)
844 continue
845 # Filter out choices
846 if choice == 0:
847 print("Exiting acloud.")
848 sys.exit()
849 if enable_choose_all and choice == 1:
850 return answer_list
851 if choice < 0 or choice > max_choice:
852 print("please choose between 0 and %d" % max_choice)
853 else:
854 return [answer_list[choice-start_index]]
Kevin Chengae7a49d2018-10-18 14:11:22 -0700855
856
Sam Chiu7a477f52018-10-22 11:20:36 +0800857def LaunchVNCFromReport(report, avd_spec):
858 """Launch vnc client according to the instances report.
859
860 Args:
861 report: Report object, that stores and generates report.
862 avd_spec: AVDSpec object that tells us what we're going to create.
863 """
864 for device in report.data.get("devices", []):
cylan4569dca2018-11-02 12:12:53 +0800865 LaunchVncClient(device.get(constants.VNC_PORT),
866 avd_width=avd_spec.hw_property["x_res"],
867 avd_height=avd_spec.hw_property["y_res"])
Sam Chiu7a477f52018-10-22 11:20:36 +0800868
869
cylan4569dca2018-11-02 12:12:53 +0800870def LaunchVncClient(port=constants.DEFAULT_VNC_PORT, avd_width=None,
871 avd_height=None):
Kevin Chengae7a49d2018-10-18 14:11:22 -0700872 """Launch ssvnc.
873
874 Args:
875 port: Integer, port number.
Sam Chiu7a477f52018-10-22 11:20:36 +0800876 avd_width: String, the width of avd.
877 avd_height: String, the height of avd.
Kevin Chengae7a49d2018-10-18 14:11:22 -0700878 """
879 try:
880 os.environ[_ENV_DISPLAY]
881 except KeyError:
882 PrintColorString("Remote terminal can't support VNC. "
883 "Skipping VNC startup.", TextColors.FAIL)
884 return
885
886 if not find_executable(_VNC_BIN):
887 if GetUserAnswerYes(_CONFIRM_CONTINUE):
888 try:
889 PrintColorString("Installing ssvnc vnc client... ", end="")
890 sys.stdout.flush()
891 subprocess.check_output(_CMD_INSTALL_SSVNC, shell=True)
892 PrintColorString("Done", TextColors.OKGREEN)
893 except subprocess.CalledProcessError as cpe:
894 PrintColorString("Failed to install ssvnc: %s" %
895 cpe.output, TextColors.FAIL)
896 return
897 else:
898 return
899 ssvnc_env = os.environ.copy()
900 ssvnc_env.update(_SSVNC_ENV_VARS)
Sam Chiu7a477f52018-10-22 11:20:36 +0800901 # Override SSVNC_SCALE
902 if avd_width or avd_height:
903 scale_ratio = CalculateVNCScreenRatio(avd_width, avd_height)
904 ssvnc_env["SSVNC_SCALE"] = str(scale_ratio)
905 logger.debug("SSVNC_SCALE:%s", scale_ratio)
906
907 ssvnc_args = _CMD_START_VNC % {"bin": find_executable(_VNC_BIN),
908 "port": port}
Kevin Chengae7a49d2018-10-18 14:11:22 -0700909 subprocess.Popen(ssvnc_args.split(), env=ssvnc_env)
910
911
912def PrintDeviceSummary(report):
913 """Display summary of devices created.
914
915 -Display created device details from the report instance.
916 report example:
917 'data': [{'devices':[{'instance_name': 'ins-f6a397-none-53363',
918 'ip': u'35.234.10.162'}]}]
919 -Display error message from report.error.
920
921 Args:
922 report: A Report instance.
923 """
924 PrintColorString("\n")
925 PrintColorString("Device(s) created:")
926 for device in report.data.get("devices", []):
927 adb_serial = "(None)"
928 adb_port = device.get("adb_port")
929 if adb_port:
930 adb_serial = constants.LOCALHOST_ADB_SERIAL % adb_port
931 instance_name = device.get("instance_name")
932 instance_ip = device.get("ip")
933 instance_details = "" if not instance_name else "(%s[%s])" % (
934 instance_name, instance_ip)
935 PrintColorString(" - device serial: %s %s" % (adb_serial,
936 instance_details))
937
938 # TODO(b/117245508): Help user to delete instance if it got created.
939 if report.errors:
940 error_msg = "\n".join(report.errors)
941 PrintColorString("Fail in:\n%s\n" % error_msg, TextColors.FAIL)
Sam Chiu7a477f52018-10-22 11:20:36 +0800942
943
944def CalculateVNCScreenRatio(avd_width, avd_height):
945 """calculate the vnc screen scale ratio to fit into user's monitor.
946
947 Args:
948 avd_width: String, the width of avd.
949 avd_height: String, the height of avd.
950 Return:
951 Float, scale ratio for vnc client.
952 """
Kevin Cheng53aa5a52018-12-03 01:33:55 -0800953 try:
954 import Tkinter
955 # Some python interpreters may not be configured for Tk, just return default scale ratio.
956 except ImportError:
957 return _DEFAULT_DISPLAY_SCALE
Sam Chiu7a477f52018-10-22 11:20:36 +0800958 root = Tkinter.Tk()
959 margin = 100 # leave some space on user's monitor.
960 screen_height = root.winfo_screenheight() - margin
961 screen_width = root.winfo_screenwidth() - margin
962
Kevin Cheng53aa5a52018-12-03 01:33:55 -0800963 scale_h = _DEFAULT_DISPLAY_SCALE
964 scale_w = _DEFAULT_DISPLAY_SCALE
Sam Chiu7a477f52018-10-22 11:20:36 +0800965 if float(screen_height) < float(avd_height):
966 scale_h = round(float(screen_height) / float(avd_height), 1)
967
968 if float(screen_width) < float(avd_width):
969 scale_w = round(float(screen_width) / float(avd_width), 1)
970
971 logger.debug("scale_h: %s (screen_h: %s/avd_h: %s),"
972 " scale_w: %s (screen_w: %s/avd_w: %s)",
973 scale_h, screen_height, avd_height,
974 scale_w, screen_width, avd_width)
975
976 # Return the larger scale-down ratio.
977 return scale_h if scale_h < scale_w else scale_w
herbertxue07293a32018-11-05 20:40:11 +0800978
979
980def IsCommandRunning(command):
981 """Check if command is running.
982
983 Args:
984 command: String of command name.
985
986 Returns:
987 Boolean, True if command is running. False otherwise.
988 """
989 try:
990 with open(os.devnull, "w") as dev_null:
cylan4569dca2018-11-02 12:12:53 +0800991 subprocess.check_call([_CMD_PGREP, "-f", command],
herbertxue07293a32018-11-05 20:40:11 +0800992 stderr=dev_null, stdout=dev_null)
993 return True
994 except subprocess.CalledProcessError:
995 return False
996
997
998def AddUserGroupsToCmd(cmd, user_groups):
999 """Add the user groups to the command if necessary.
1000
1001 As part of local host setup to enable local instance support, the user is
1002 added to certain groups. For those settings to take effect systemwide
1003 requires the user to log out and log back in. In the scenario where the
1004 user has run setup and hasn't logged out, we still want them to be able to
1005 launch a local instance so add the user to the groups as part of the
1006 command to ensure success.
1007
1008 The reason using here-doc instead of '&' is all operations need to be ran in
1009 ths same pid. Here's an example cmd:
1010 $ sg kvm << EOF
1011 sg libvirt
1012 sg cvdnetwork
1013 launch_cvd --cpus 2 --x_res 1280 --y_res 720 --dpi 160 --memory_mb 4096
1014 EOF
1015
1016 Args:
1017 cmd: String of the command to prepend the user groups to.
1018 user_groups: List of user groups name.(String)
1019
1020 Returns:
1021 String of the command with the user groups prepended to it if necessary,
1022 otherwise the same existing command.
1023 """
1024 user_group_cmd = ""
1025 if not CheckUserInGroups(user_groups):
1026 logger.debug("Need to add user groups to the command")
1027 for idx, group in enumerate(user_groups):
1028 user_group_cmd += _CMD_SG + group
1029 if idx == 0:
1030 user_group_cmd += " <<EOF\n"
1031 else:
1032 user_group_cmd += "\n"
1033 cmd += "\nEOF"
1034 user_group_cmd += cmd
1035 logger.debug("user group cmd: %s", user_group_cmd)
1036 return user_group_cmd
1037
1038
1039def CheckUserInGroups(group_name_list):
1040 """Check if the current user is in the group.
1041
1042 Args:
1043 group_name_list: The list of group name.
1044 Returns:
1045 True if current user is in all the groups.
1046 """
1047 logger.info("Checking if user is in following groups: %s", group_name_list)
1048 current_groups = [grp.getgrgid(g).gr_name for g in os.getgroups()]
1049 all_groups_present = True
1050 for group in group_name_list:
1051 if group not in current_groups:
1052 all_groups_present = False
1053 logger.info("missing group: %s", group)
1054 return all_groups_present
Sam Chiu6c738d62018-12-04 10:29:02 +08001055
1056
1057def IsSupportedPlatform(print_warning=False):
1058 """Check if user's os is the supported platform.
1059
1060 Args:
1061 print_warning: Boolean, print the unsupported warning
1062 if True.
1063 Returns:
1064 Boolean, True if user is using supported platform.
1065 """
1066 system = platform.system()
1067 dist = platform.linux_distribution()[0]
1068 platform_supported = (system in _SUPPORTED_SYSTEMS_AND_DISTS and
1069 dist in _SUPPORTED_SYSTEMS_AND_DISTS[system])
1070
1071 logger.info("supported system and dists: %s",
1072 _SUPPORTED_SYSTEMS_AND_DISTS)
1073 platform_supported_msg = ("%s[%s] %s supported platform" %
1074 (system,
1075 dist,
1076 "is a" if platform_supported else "is not a"))
1077 if print_warning and not platform_supported:
1078 PrintColorString(platform_supported_msg, TextColors.WARNING)
1079 else:
1080 logger.info(platform_supported_msg)
1081
1082 return platform_supported
Kevin Cheng0a33a072018-12-11 15:35:26 -08001083
1084
1085def GetDistDir():
1086 """Return the absolute path to the dist dir."""
1087 android_build_top = os.environ.get(constants.ENV_ANDROID_BUILD_TOP)
1088 if not android_build_top:
1089 return None
1090 dist_cmd = GET_BUILD_VAR_CMD[:]
1091 dist_cmd.append(_DIST_DIR)
1092 try:
1093 dist_dir = subprocess.check_output(dist_cmd, cwd=android_build_top)
1094 except subprocess.CalledProcessError:
1095 return None
1096 return os.path.join(android_build_top, dist_dir.strip())
herbertxue9e1e27a2018-12-12 16:25:27 +08001097
1098
1099def CleanupProcess(pattern):
1100 """Cleanup process with pattern.
1101
1102 Args:
1103 pattern: String, string of process pattern.
1104 """
1105 if IsCommandRunning(pattern):
1106 command_kill = _CMD_KILL + [pattern]
1107 subprocess.check_call(command_kill)