1# Copyright (c) 2012 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import inspect 6import logging 7import os 8import re 9import signal 10import socket 11import struct 12import time 13import urllib2 14import uuid 15 16from autotest_lib.client.common_lib import base_utils 17from autotest_lib.client.common_lib import error 18from autotest_lib.client.common_lib import global_config 19from autotest_lib.client.common_lib import lsbrelease_utils 20from autotest_lib.client.common_lib.cros.graphite import stats_es_mock 21from autotest_lib.client.cros import constants 22 23 24CONFIG = global_config.global_config 25 26# Keep checking if the pid is alive every second until the timeout (in seconds) 27CHECK_PID_IS_ALIVE_TIMEOUT = 6 28 29_LOCAL_HOST_LIST = ('localhost', '127.0.0.1') 30 31# The default address of a vm gateway. 32DEFAULT_VM_GATEWAY = '10.0.2.2' 33 34# Google Storage bucket URI to store results in. 35DEFAULT_OFFLOAD_GSURI = CONFIG.get_config_value( 36 'CROS', 'results_storage_server', default=None) 37 38# Default Moblab Ethernet Interface. 39_MOBLAB_ETH_0 = 'eth0' 40_MOBLAB_ETH_1 = 'eth1' 41 42# A list of subnets that requires dedicated devserver and drone in the same 43# subnet. Each item is a tuple of (subnet_ip, mask_bits), e.g., 44# ('192.168.0.0', 24)) 45RESTRICTED_SUBNETS = [] 46restricted_subnets_list = CONFIG.get_config_value( 47 'CROS', 'restricted_subnets', type=list, default=[]) 48# TODO(dshi): Remove the code to split subnet with `:` after R51 is off stable 49# channel, and update shadow config to use `/` as delimiter for consistency. 50for subnet in restricted_subnets_list: 51 ip, mask_bits = subnet.split('/') if '/' in subnet else subnet.split(':') 52 RESTRICTED_SUBNETS.append((ip, int(mask_bits))) 53 54# regex pattern for CLIENT/wireless_ssid_ config. For example, global config 55# can have following config in CLIENT section to indicate that hosts in subnet 56# 192.168.0.1/24 should use wireless ssid of `ssid_1` 57# wireless_ssid_192.168.0.1/24: ssid_1 58WIRELESS_SSID_PATTERN = 'wireless_ssid_(.*)/(\d+)' 59 60def get_built_in_ethernet_nic_name(): 61 """Gets the moblab public network interface. 62 63 If the eth0 is an USB interface, try to use eth1 instead. Otherwise 64 use eth0 by default. 65 """ 66 try: 67 cmd_result = base_utils.run('readlink -f /sys/class/net/eth0') 68 if cmd_result.exit_status == 0 and 'usb' in cmd_result.stdout: 69 cmd_result = base_utils.run('readlink -f /sys/class/net/eth1') 70 if cmd_result.exit_status == 0 and not ('usb' in cmd_result.stdout): 71 logging.info('Eth0 is a USB dongle, use eth1 as moblab nic.') 72 return _MOBLAB_ETH_1 73 except error.CmdError: 74 # readlink is not supported. 75 logging.info('No readlink available, use eth0 as moblab nic.') 76 pass 77 return _MOBLAB_ETH_0 78 79 80def ping(host, deadline=None, tries=None, timeout=60): 81 """Attempt to ping |host|. 82 83 Shell out to 'ping' if host is an IPv4 addres or 'ping6' if host is an 84 IPv6 address to try to reach |host| for |timeout| seconds. 85 Returns exit code of ping. 86 87 Per 'man ping', if you specify BOTH |deadline| and |tries|, ping only 88 returns 0 if we get responses to |tries| pings within |deadline| seconds. 89 90 Specifying |deadline| or |count| alone should return 0 as long as 91 some packets receive responses. 92 93 Note that while this works with literal IPv6 addresses it will not work 94 with hostnames that resolve to IPv6 only. 95 96 @param host: the host to ping. 97 @param deadline: seconds within which |tries| pings must succeed. 98 @param tries: number of pings to send. 99 @param timeout: number of seconds after which to kill 'ping' command. 100 @return exit code of ping command. 101 """ 102 args = [host] 103 ping_cmd = 'ping6' if re.search(r':.*:', host) else 'ping' 104 105 if deadline: 106 args.append('-w%d' % deadline) 107 if tries: 108 args.append('-c%d' % tries) 109 110 return base_utils.run(ping_cmd, args=args, verbose=True, 111 ignore_status=True, timeout=timeout, 112 stdout_tee=base_utils.TEE_TO_LOGS, 113 stderr_tee=base_utils.TEE_TO_LOGS).exit_status 114 115 116def host_is_in_lab_zone(hostname): 117 """Check if the host is in the CLIENT.dns_zone. 118 119 @param hostname: The hostname to check. 120 @returns True if hostname.dns_zone resolves, otherwise False. 121 """ 122 host_parts = hostname.split('.') 123 dns_zone = CONFIG.get_config_value('CLIENT', 'dns_zone', default=None) 124 fqdn = '%s.%s' % (host_parts[0], dns_zone) 125 try: 126 socket.gethostbyname(fqdn) 127 return True 128 except socket.gaierror: 129 return False 130 131 132def host_could_be_in_afe(hostname): 133 """Check if the host could be in Autotest Front End. 134 135 Report whether or not a host could be in AFE, without actually 136 consulting AFE. This method exists because some systems are in the 137 lab zone, but not actually managed by AFE. 138 139 @param hostname: The hostname to check. 140 @returns True if hostname is in lab zone, and does not match *-dev-* 141 """ 142 # Do the 'dev' check first, so that we skip DNS lookup if the 143 # hostname matches. This should give us greater resilience to lab 144 # failures. 145 return (hostname.find('-dev-') == -1) and host_is_in_lab_zone(hostname) 146 147 148def get_chrome_version(job_views): 149 """ 150 Retrieves the version of the chrome binary associated with a job. 151 152 When a test runs we query the chrome binary for it's version and drop 153 that value into a client keyval. To retrieve the chrome version we get all 154 the views associated with a test from the db, including those of the 155 server and client jobs, and parse the version out of the first test view 156 that has it. If we never ran a single test in the suite the job_views 157 dictionary will not contain a chrome version. 158 159 This method cannot retrieve the chrome version from a dictionary that 160 does not conform to the structure of an autotest tko view. 161 162 @param job_views: a list of a job's result views, as returned by 163 the get_detailed_test_views method in rpc_interface. 164 @return: The chrome version string, or None if one can't be found. 165 """ 166 167 # Aborted jobs have no views. 168 if not job_views: 169 return None 170 171 for view in job_views: 172 if (view.get('attributes') 173 and constants.CHROME_VERSION in view['attributes'].keys()): 174 175 return view['attributes'].get(constants.CHROME_VERSION) 176 177 logging.warning('Could not find chrome version for failure.') 178 return None 179 180 181def get_default_interface_mac_address(): 182 """Returns the default moblab MAC address.""" 183 return get_interface_mac_address( 184 get_built_in_ethernet_nic_name()) 185 186 187def get_interface_mac_address(interface): 188 """Return the MAC address of a given interface. 189 190 @param interface: Interface to look up the MAC address of. 191 """ 192 interface_link = base_utils.run( 193 'ip addr show %s | grep link/ether' % interface).stdout 194 # The output will be in the format of: 195 # 'link/ether <mac> brd ff:ff:ff:ff:ff:ff' 196 return interface_link.split()[1] 197 198 199def get_moblab_id(): 200 """Gets the moblab random id. 201 202 The random id file is cached on disk. If it does not exist, a new file is 203 created the first time. 204 205 @returns the moblab random id. 206 """ 207 moblab_id_filepath = '/home/moblab/.moblab_id' 208 if os.path.exists(moblab_id_filepath): 209 with open(moblab_id_filepath, 'r') as moblab_id_file: 210 random_id = moblab_id_file.read() 211 else: 212 random_id = uuid.uuid1() 213 with open(moblab_id_filepath, 'w') as moblab_id_file: 214 moblab_id_file.write('%s' % random_id) 215 return random_id 216 217 218def get_offload_gsuri(): 219 """Return the GSURI to offload test results to. 220 221 For the normal use case this is the results_storage_server in the 222 global_config. 223 224 However partners using Moblab will be offloading their results to a 225 subdirectory of their image storage buckets. The subdirectory is 226 determined by the MAC Address of the Moblab device. 227 228 @returns gsuri to offload test results to. 229 """ 230 # For non-moblab, use results_storage_server or default. 231 if not lsbrelease_utils.is_moblab(): 232 return DEFAULT_OFFLOAD_GSURI 233 234 # For moblab, use results_storage_server or image_storage_server as bucket 235 # name and mac-address/moblab_id as path. 236 gsuri = DEFAULT_OFFLOAD_GSURI 237 if not gsuri: 238 gsuri = "%sresults/" % CONFIG.get_config_value('CROS', 'image_storage_server') 239 240 return '%s%s/%s/' % ( 241 gsuri, get_interface_mac_address(get_built_in_ethernet_nic_name()), 242 get_moblab_id()) 243 244 245# TODO(petermayo): crosbug.com/31826 Share this with _GsUpload in 246# //chromite.git/buildbot/prebuilt.py somewhere/somehow 247def gs_upload(local_file, remote_file, acl, result_dir=None, 248 transfer_timeout=300, acl_timeout=300): 249 """Upload to GS bucket. 250 251 @param local_file: Local file to upload 252 @param remote_file: Remote location to upload the local_file to. 253 @param acl: name or file used for controlling access to the uploaded 254 file. 255 @param result_dir: Result directory if you want to add tracing to the 256 upload. 257 @param transfer_timeout: Timeout for this upload call. 258 @param acl_timeout: Timeout for the acl call needed to confirm that 259 the uploader has permissions to execute the upload. 260 261 @raise CmdError: the exit code of the gsutil call was not 0. 262 263 @returns True/False - depending on if the upload succeeded or failed. 264 """ 265 # https://developers.google.com/storage/docs/accesscontrol#extension 266 CANNED_ACLS = ['project-private', 'private', 'public-read', 267 'public-read-write', 'authenticated-read', 268 'bucket-owner-read', 'bucket-owner-full-control'] 269 _GSUTIL_BIN = 'gsutil' 270 acl_cmd = None 271 if acl in CANNED_ACLS: 272 cmd = '%s cp -a %s %s %s' % (_GSUTIL_BIN, acl, local_file, remote_file) 273 else: 274 # For private uploads we assume that the overlay board is set up 275 # properly and a googlestore_acl.xml is present, if not this script 276 # errors 277 cmd = '%s cp -a private %s %s' % (_GSUTIL_BIN, local_file, remote_file) 278 if not os.path.exists(acl): 279 logging.error('Unable to find ACL File %s.', acl) 280 return False 281 acl_cmd = '%s setacl %s %s' % (_GSUTIL_BIN, acl, remote_file) 282 if not result_dir: 283 base_utils.run(cmd, timeout=transfer_timeout, verbose=True) 284 if acl_cmd: 285 base_utils.run(acl_cmd, timeout=acl_timeout, verbose=True) 286 return True 287 with open(os.path.join(result_dir, 'tracing'), 'w') as ftrace: 288 ftrace.write('Preamble\n') 289 base_utils.run(cmd, timeout=transfer_timeout, verbose=True, 290 stdout_tee=ftrace, stderr_tee=ftrace) 291 if acl_cmd: 292 ftrace.write('\nACL setting\n') 293 # Apply the passed in ACL xml file to the uploaded object. 294 base_utils.run(acl_cmd, timeout=acl_timeout, verbose=True, 295 stdout_tee=ftrace, stderr_tee=ftrace) 296 ftrace.write('Postamble\n') 297 return True 298 299 300def gs_ls(uri_pattern): 301 """Returns a list of URIs that match a given pattern. 302 303 @param uri_pattern: a GS URI pattern, may contain wildcards 304 305 @return A list of URIs matching the given pattern. 306 307 @raise CmdError: the gsutil command failed. 308 309 """ 310 gs_cmd = ' '.join(['gsutil', 'ls', uri_pattern]) 311 result = base_utils.system_output(gs_cmd).splitlines() 312 return [path.rstrip() for path in result if path] 313 314 315def nuke_pids(pid_list, signal_queue=[signal.SIGTERM, signal.SIGKILL]): 316 """ 317 Given a list of pid's, kill them via an esclating series of signals. 318 319 @param pid_list: List of PID's to kill. 320 @param signal_queue: Queue of signals to send the PID's to terminate them. 321 322 @return: A mapping of the signal name to the number of processes it 323 was sent to. 324 """ 325 sig_count = {} 326 # Though this is slightly hacky it beats hardcoding names anyday. 327 sig_names = dict((k, v) for v, k in signal.__dict__.iteritems() 328 if v.startswith('SIG')) 329 for sig in signal_queue: 330 logging.debug('Sending signal %s to the following pids:', sig) 331 sig_count[sig_names.get(sig, 'unknown_signal')] = len(pid_list) 332 for pid in pid_list: 333 logging.debug('Pid %d', pid) 334 try: 335 os.kill(pid, sig) 336 except OSError: 337 # The process may have died from a previous signal before we 338 # could kill it. 339 pass 340 if sig == signal.SIGKILL: 341 return sig_count 342 pid_list = [pid for pid in pid_list if base_utils.pid_is_alive(pid)] 343 if not pid_list: 344 break 345 time.sleep(CHECK_PID_IS_ALIVE_TIMEOUT) 346 failed_list = [] 347 for pid in pid_list: 348 if base_utils.pid_is_alive(pid): 349 failed_list.append('Could not kill %d for process name: %s.' % pid, 350 base_utils.get_process_name(pid)) 351 if failed_list: 352 raise error.AutoservRunError('Following errors occured: %s' % 353 failed_list, None) 354 return sig_count 355 356 357def externalize_host(host): 358 """Returns an externally accessible host name. 359 360 @param host: a host name or address (string) 361 362 @return An externally visible host name or address 363 364 """ 365 return socket.gethostname() if host in _LOCAL_HOST_LIST else host 366 367 368def urlopen_socket_timeout(url, data=None, timeout=5): 369 """ 370 Wrapper to urllib2.urlopen with a socket timeout. 371 372 This method will convert all socket timeouts to 373 TimeoutExceptions, so we can use it in conjunction 374 with the rpc retry decorator and continue to handle 375 other URLErrors as we see fit. 376 377 @param url: The url to open. 378 @param data: The data to send to the url (eg: the urlencoded dictionary 379 used with a POST call). 380 @param timeout: The timeout for this urlopen call. 381 382 @return: The response of the urlopen call. 383 384 @raises: error.TimeoutException when a socket timeout occurs. 385 urllib2.URLError for errors that not caused by timeout. 386 urllib2.HTTPError for errors like 404 url not found. 387 """ 388 old_timeout = socket.getdefaulttimeout() 389 socket.setdefaulttimeout(timeout) 390 try: 391 return urllib2.urlopen(url, data=data) 392 except urllib2.URLError as e: 393 if type(e.reason) is socket.timeout: 394 raise error.TimeoutException(str(e)) 395 raise 396 finally: 397 socket.setdefaulttimeout(old_timeout) 398 399 400def parse_chrome_version(version_string): 401 """ 402 Parse a chrome version string and return version and milestone. 403 404 Given a chrome version of the form "W.X.Y.Z", return "W.X.Y.Z" as 405 the version and "W" as the milestone. 406 407 @param version_string: Chrome version string. 408 @return: a tuple (chrome_version, milestone). If the incoming version 409 string is not of the form "W.X.Y.Z", chrome_version will 410 be set to the incoming "version_string" argument and the 411 milestone will be set to the empty string. 412 """ 413 match = re.search('(\d+)\.\d+\.\d+\.\d+', version_string) 414 ver = match.group(0) if match else version_string 415 milestone = match.group(1) if match else '' 416 return ver, milestone 417 418 419def is_localhost(server): 420 """Check if server is equivalent to localhost. 421 422 @param server: Name of the server to check. 423 424 @return: True if given server is equivalent to localhost. 425 426 @raise socket.gaierror: If server name failed to be resolved. 427 """ 428 if server in _LOCAL_HOST_LIST: 429 return True 430 try: 431 return (socket.gethostbyname(socket.gethostname()) == 432 socket.gethostbyname(server)) 433 except socket.gaierror: 434 logging.error('Failed to resolve server name %s.', server) 435 return False 436 437 438def is_puppylab_vm(server): 439 """Check if server is a virtual machine in puppylab. 440 441 In the virtual machine testing environment (i.e., puppylab), each 442 shard VM has a hostname like localhost:<port>. 443 444 @param server: Server name to check. 445 446 @return True if given server is a virtual machine in puppylab. 447 448 """ 449 # TODO(mkryu): This is a puppylab specific hack. Please update 450 # this method if you have a better solution. 451 regex = re.compile(r'(.+):\d+') 452 m = regex.match(server) 453 if m: 454 return m.group(1) in _LOCAL_HOST_LIST 455 return False 456 457 458def get_function_arg_value(func, arg_name, args, kwargs): 459 """Get the value of the given argument for the function. 460 461 @param func: Function being called with given arguments. 462 @param arg_name: Name of the argument to look for value. 463 @param args: arguments for function to be called. 464 @param kwargs: keyword arguments for function to be called. 465 466 @return: The value of the given argument for the function. 467 468 @raise ValueError: If the argument is not listed function arguemnts. 469 @raise KeyError: If no value is found for the given argument. 470 """ 471 if arg_name in kwargs: 472 return kwargs[arg_name] 473 474 argspec = inspect.getargspec(func) 475 index = argspec.args.index(arg_name) 476 try: 477 return args[index] 478 except IndexError: 479 try: 480 # The argument can use a default value. Reverse the default value 481 # so argument with default value can be counted from the last to 482 # the first. 483 return argspec.defaults[::-1][len(argspec.args) - index - 1] 484 except IndexError: 485 raise KeyError('Argument %s is not given a value. argspec: %s, ' 486 'args:%s, kwargs:%s' % 487 (arg_name, argspec, args, kwargs)) 488 489 490def has_systemd(): 491 """Check if the host is running systemd. 492 493 @return: True if the host uses systemd, otherwise returns False. 494 """ 495 return os.path.basename(os.readlink('/proc/1/exe')) == 'systemd' 496 497 498def version_match(build_version, release_version, update_url=''): 499 """Compare release versino from lsb-release with cros-version label. 500 501 build_version is a string based on build name. It is prefixed with builder 502 info and branch ID, e.g., lumpy-release/R43-6809.0.0. It may not include 503 builder info, e.g., lumpy-release, in which case, update_url shall be passed 504 in to determine if the build is a trybot or pgo-generate build. 505 release_version is retrieved from lsb-release. 506 These two values might not match exactly. 507 508 The method is designed to compare version for following 6 scenarios with 509 samples of build version and expected release version: 510 1. trybot non-release build (paladin, pre-cq or test-ap build). 511 build version: trybot-lumpy-paladin/R27-3837.0.0-b123 512 release version: 3837.0.2013_03_21_1340 513 514 2. trybot release build. 515 build version: trybot-lumpy-release/R27-3837.0.0-b456 516 release version: 3837.0.0 517 518 3. buildbot official release build. 519 build version: lumpy-release/R27-3837.0.0 520 release version: 3837.0.0 521 522 4. non-official paladin rc build. 523 build version: lumpy-paladin/R27-3878.0.0-rc7 524 release version: 3837.0.0-rc7 525 526 5. chrome-perf build. 527 build version: lumpy-chrome-perf/R28-3837.0.0-b2996 528 release version: 3837.0.0 529 530 6. pgo-generate build. 531 build version: lumpy-release-pgo-generate/R28-3837.0.0-b2996 532 release version: 3837.0.0-pgo-generate 533 534 TODO: This logic has a bug if a trybot paladin build failed to be 535 installed in a DUT running an older trybot paladin build with same 536 platform number, but different build number (-b###). So to conclusively 537 determine if a tryjob paladin build is imaged successfully, we may need 538 to find out the date string from update url. 539 540 @param build_version: Build name for cros version, e.g. 541 peppy-release/R43-6809.0.0 or R43-6809.0.0 542 @param release_version: Release version retrieved from lsb-release, 543 e.g., 6809.0.0 544 @param update_url: Update url which include the full builder information. 545 Default is set to empty string. 546 547 @return: True if the values match, otherwise returns False. 548 """ 549 # If the build is from release, CQ or PFQ builder, cros-version label must 550 # be ended with release version in lsb-release. 551 if build_version.endswith(release_version): 552 return True 553 554 # Remove R#- and -b# at the end of build version 555 stripped_version = re.sub(r'(R\d+-|-b\d+)', '', build_version) 556 # Trim the builder info, e.g., trybot-lumpy-paladin/ 557 stripped_version = stripped_version.split('/')[-1] 558 559 is_trybot_non_release_build = ( 560 re.match(r'.*trybot-.+-(paladin|pre-cq|test-ap|toolchain)', 561 build_version) or 562 re.match(r'.*trybot-.+-(paladin|pre-cq|test-ap|toolchain)', 563 update_url)) 564 565 # Replace date string with 0 in release_version 566 release_version_no_date = re.sub(r'\d{4}_\d{2}_\d{2}_\d+', '0', 567 release_version) 568 has_date_string = release_version != release_version_no_date 569 570 is_pgo_generate_build = ( 571 re.match(r'.+-pgo-generate', build_version) or 572 re.match(r'.+-pgo-generate', update_url)) 573 574 # Remove |-pgo-generate| in release_version 575 release_version_no_pgo = release_version.replace('-pgo-generate', '') 576 has_pgo_generate = release_version != release_version_no_pgo 577 578 if is_trybot_non_release_build: 579 if not has_date_string: 580 logging.error('A trybot paladin or pre-cq build is expected. ' 581 'Version "%s" is not a paladin or pre-cq build.', 582 release_version) 583 return False 584 return stripped_version == release_version_no_date 585 elif is_pgo_generate_build: 586 if not has_pgo_generate: 587 logging.error('A pgo-generate build is expected. Version ' 588 '"%s" is not a pgo-generate build.', 589 release_version) 590 return False 591 return stripped_version == release_version_no_pgo 592 else: 593 if has_date_string: 594 logging.error('Unexpected date found in a non trybot paladin or ' 595 'pre-cq build.') 596 return False 597 # Versioned build, i.e., rc or release build. 598 return stripped_version == release_version 599 600 601def get_real_user(): 602 """Get the real user that runs the script. 603 604 The function check environment variable SUDO_USER for the user if the 605 script is run with sudo. Otherwise, it returns the value of environment 606 variable USER. 607 608 @return: The user name that runs the script. 609 610 """ 611 user = os.environ.get('SUDO_USER') 612 if not user: 613 user = os.environ.get('USER') 614 return user 615 616 617def get_service_pid(service_name): 618 """Return pid of service. 619 620 @param service_name: string name of service. 621 622 @return: pid or 0 if service is not running. 623 """ 624 if has_systemd(): 625 # systemctl show prints 'MainPID=0' if the service is not running. 626 cmd_result = base_utils.run('systemctl show -p MainPID %s' % 627 service_name, ignore_status=True) 628 return int(cmd_result.stdout.split('=')[1]) 629 else: 630 cmd_result = base_utils.run('status %s' % service_name, 631 ignore_status=True) 632 if 'start/running' in cmd_result.stdout: 633 return int(cmd_result.stdout.split()[3]) 634 return 0 635 636 637def control_service(service_name, action='start', ignore_status=True): 638 """Controls a service. It can be used to start, stop or restart 639 a service. 640 641 @param service_name: string service to be restarted. 642 643 @param action: string choice of action to control command. 644 645 @param ignore_status: boolean ignore if system command fails. 646 647 @return: status code of the executed command. 648 """ 649 if action not in ('start', 'stop', 'restart'): 650 raise ValueError('Unknown action supplied as parameter.') 651 652 control_cmd = action + ' ' + service_name 653 if has_systemd(): 654 control_cmd = 'systemctl ' + control_cmd 655 return base_utils.system(control_cmd, ignore_status=ignore_status) 656 657 658def restart_service(service_name, ignore_status=True): 659 """Restarts a service 660 661 @param service_name: string service to be restarted. 662 663 @param ignore_status: boolean ignore if system command fails. 664 665 @return: status code of the executed command. 666 """ 667 return control_service(service_name, action='restart', ignore_status=ignore_status) 668 669 670def start_service(service_name, ignore_status=True): 671 """Starts a service 672 673 @param service_name: string service to be started. 674 675 @param ignore_status: boolean ignore if system command fails. 676 677 @return: status code of the executed command. 678 """ 679 return control_service(service_name, action='start', ignore_status=ignore_status) 680 681 682def stop_service(service_name, ignore_status=True): 683 """Stops a service 684 685 @param service_name: string service to be stopped. 686 687 @param ignore_status: boolean ignore if system command fails. 688 689 @return: status code of the executed command. 690 """ 691 return control_service(service_name, action='stop', ignore_status=ignore_status) 692 693 694def sudo_require_password(): 695 """Test if the process can run sudo command without using password. 696 697 @return: True if the process needs password to run sudo command. 698 699 """ 700 try: 701 base_utils.run('sudo -n true') 702 return False 703 except error.CmdError: 704 logging.warn('sudo command requires password.') 705 return True 706 707 708def is_in_container(): 709 """Check if the process is running inside a container. 710 711 @return: True if the process is running inside a container, otherwise False. 712 """ 713 result = base_utils.run('grep -q "/lxc/" /proc/1/cgroup', 714 verbose=False, ignore_status=True) 715 return result.exit_status == 0 716 717 718def is_flash_installed(): 719 """ 720 The Adobe Flash binary is only distributed with internal builds. 721 """ 722 return (os.path.exists('/opt/google/chrome/pepper/libpepflashplayer.so') 723 and os.path.exists('/opt/google/chrome/pepper/pepper-flash.info')) 724 725 726def verify_flash_installed(): 727 """ 728 The Adobe Flash binary is only distributed with internal builds. 729 Warn users of public builds of the extra dependency. 730 """ 731 if not is_flash_installed(): 732 raise error.TestNAError('No Adobe Flash binary installed.') 733 734 735def is_in_same_subnet(ip_1, ip_2, mask_bits=24): 736 """Check if two IP addresses are in the same subnet with given mask bits. 737 738 The two IP addresses are string of IPv4, e.g., '192.168.0.3'. 739 740 @param ip_1: First IP address to compare. 741 @param ip_2: Second IP address to compare. 742 @param mask_bits: Number of mask bits for subnet comparison. Default to 24. 743 744 @return: True if the two IP addresses are in the same subnet. 745 746 """ 747 mask = ((2L<<mask_bits-1) -1)<<(32-mask_bits) 748 ip_1_num = struct.unpack('!I', socket.inet_aton(ip_1))[0] 749 ip_2_num = struct.unpack('!I', socket.inet_aton(ip_2))[0] 750 return ip_1_num & mask == ip_2_num & mask 751 752 753def get_ip_address(hostname): 754 """Get the IP address of given hostname. 755 756 @param hostname: Hostname of a DUT. 757 758 @return: The IP address of given hostname. None if failed to resolve 759 hostname. 760 """ 761 try: 762 if hostname: 763 return socket.gethostbyname(hostname) 764 except socket.gaierror as e: 765 logging.error('Failed to get IP address of %s, error: %s.', hostname, e) 766 767 768def get_servers_in_same_subnet(host_ip, mask_bits, servers=None, 769 server_ip_map=None): 770 """Get the servers in the same subnet of the given host ip. 771 772 @param host_ip: The IP address of a dut to look for devserver. 773 @param mask_bits: Number of mask bits. 774 @param servers: A list of servers to be filtered by subnet specified by 775 host_ip and mask_bits. 776 @param server_ip_map: A map between the server name and its IP address. 777 The map can be pre-built for better performance, e.g., when 778 allocating a drone for an agent task. 779 780 @return: A list of servers in the same subnet of the given host ip. 781 782 """ 783 matched_servers = [] 784 if not servers and not server_ip_map: 785 raise ValueError('Either `servers` or `server_ip_map` must be given.') 786 if not servers: 787 servers = server_ip_map.keys() 788 # Make sure server_ip_map is an empty dict if it's not set. 789 if not server_ip_map: 790 server_ip_map = {} 791 for server in servers: 792 server_ip = server_ip_map.get(server, get_ip_address(server)) 793 if server_ip and is_in_same_subnet(server_ip, host_ip, mask_bits): 794 matched_servers.append(server) 795 return matched_servers 796 797 798def get_restricted_subnet(hostname, restricted_subnets=RESTRICTED_SUBNETS): 799 """Get the restricted subnet of given hostname. 800 801 @param hostname: Name of the host to look for matched restricted subnet. 802 @param restricted_subnets: A list of restricted subnets, default is set to 803 RESTRICTED_SUBNETS. 804 805 @return: A tuple of (subnet_ip, mask_bits), which defines a restricted 806 subnet. 807 """ 808 host_ip = get_ip_address(hostname) 809 if not host_ip: 810 return 811 for subnet_ip, mask_bits in restricted_subnets: 812 if is_in_same_subnet(subnet_ip, host_ip, mask_bits): 813 return subnet_ip, mask_bits 814 815 816def get_wireless_ssid(hostname): 817 """Get the wireless ssid based on given hostname. 818 819 The method tries to locate the wireless ssid in the same subnet of given 820 hostname first. If none is found, it returns the default setting in 821 CLIENT/wireless_ssid. 822 823 @param hostname: Hostname of the test device. 824 825 @return: wireless ssid for the test device. 826 """ 827 default_ssid = CONFIG.get_config_value('CLIENT', 'wireless_ssid', 828 default=None) 829 host_ip = get_ip_address(hostname) 830 if not host_ip: 831 return default_ssid 832 833 # Get all wireless ssid in the global config. 834 ssids = CONFIG.get_config_value_regex('CLIENT', WIRELESS_SSID_PATTERN) 835 836 # There could be multiple subnet matches, pick the one with most strict 837 # match, i.e., the one with highest maskbit. 838 matched_ssid = default_ssid 839 matched_maskbit = -1 840 for key, value in ssids.items(): 841 # The config key filtered by regex WIRELESS_SSID_PATTERN has a format of 842 # wireless_ssid_[subnet_ip]/[maskbit], for example: 843 # wireless_ssid_192.168.0.1/24 844 # Following line extract the subnet ip and mask bit from the key name. 845 match = re.match(WIRELESS_SSID_PATTERN, key) 846 subnet_ip, maskbit = match.groups() 847 maskbit = int(maskbit) 848 if (is_in_same_subnet(subnet_ip, host_ip, maskbit) and 849 maskbit > matched_maskbit): 850 matched_ssid = value 851 matched_maskbit = maskbit 852 return matched_ssid 853 854 855def parse_launch_control_build(build_name): 856 """Get branch, target, build_id from the given Launch Control build_name. 857 858 @param build_name: Name of a Launch Control build, should be formated as 859 branch/target/build_id 860 861 @return: Tuple of branch, target, build_id 862 @raise ValueError: If the build_name is not correctly formated. 863 """ 864 branch, target, build_id = build_name.split('/') 865 return branch, target, build_id 866 867 868def parse_android_target(target): 869 """Get board and build type from the given target. 870 871 @param target: Name of an Android build target, e.g., shamu-eng. 872 873 @return: Tuple of board, build_type 874 @raise ValueError: If the target is not correctly formated. 875 """ 876 board, build_type = target.split('-') 877 return board, build_type 878 879 880def parse_launch_control_target(target): 881 """Parse the build target and type from a Launch Control target. 882 883 The Launch Control target has the format of build_target-build_type, e.g., 884 shamu-eng or dragonboard-userdebug. This method extracts the build target 885 and type from the target name. 886 887 @param target: Name of a Launch Control target, e.g., shamu-eng. 888 889 @return: (build_target, build_type), e.g., ('shamu', 'userdebug') 890 """ 891 match = re.match('(?P<build_target>.+)-(?P<build_type>[^-]+)', target) 892 if match: 893 return match.group('build_target'), match.group('build_type') 894 else: 895 return None, None 896 897 898def is_launch_control_build(build): 899 """Check if a given build is a Launch Control build. 900 901 @param build: Name of a build, e.g., 902 ChromeOS build: daisy-release/R50-1234.0.0 903 Launch Control build: git_mnc_release/shamu-eng 904 905 @return: True if the build name matches the pattern of a Launch Control 906 build, False otherwise. 907 """ 908 try: 909 _, target, _ = parse_launch_control_build(build) 910 build_target, _ = parse_launch_control_target(target) 911 if build_target: 912 return True 913 except ValueError: 914 # parse_launch_control_build or parse_launch_control_target failed. 915 pass 916 return False 917 918 919def which(exec_file): 920 """Finds an executable file. 921 922 If the file name contains a path component, it is checked as-is. 923 Otherwise, we check with each of the path components found in the system 924 PATH prepended. This behavior is similar to the 'which' command-line tool. 925 926 @param exec_file: Name or path to desired executable. 927 928 @return: An actual path to the executable, or None if not found. 929 """ 930 if os.path.dirname(exec_file): 931 return exec_file if os.access(exec_file, os.X_OK) else None 932 sys_path = os.environ.get('PATH') 933 prefix_list = sys_path.split(os.pathsep) if sys_path else [] 934 for prefix in prefix_list: 935 path = os.path.join(prefix, exec_file) 936 if os.access(path, os.X_OK): 937 return path 938 939 940class TimeoutError(error.TestError): 941 """Error raised when we time out when waiting on a condition.""" 942 pass 943 944 945def poll_for_condition(condition, 946 exception=None, 947 timeout=10, 948 sleep_interval=0.1, 949 desc=None): 950 """Polls until a condition becomes true. 951 952 @param condition: function taking no args and returning bool 953 @param exception: exception to throw if condition doesn't become true 954 @param timeout: maximum number of seconds to wait 955 @param sleep_interval: time to sleep between polls 956 @param desc: description of default TimeoutError used if 'exception' is 957 None 958 959 @return The true value that caused the poll loop to terminate. 960 961 @raise 'exception' arg if supplied; TimeoutError otherwise 962 """ 963 start_time = time.time() 964 while True: 965 value = condition() 966 if value: 967 return value 968 if time.time() + sleep_interval - start_time > timeout: 969 if exception: 970 logging.error(exception) 971 raise exception 972 973 if desc: 974 desc = 'Timed out waiting for condition: ' + desc 975 else: 976 desc = 'Timed out waiting for unnamed condition' 977 logging.error(desc) 978 raise TimeoutError(desc) 979 980 time.sleep(sleep_interval) 981 982 983class metrics_mock(stats_es_mock.mock_class_base): 984 """mock class for metrics in case chromite is not installed.""" 985 pass 986