OLD | NEW |
(Empty) | |
| 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 |
| 5 """Functions that deals with local and device ports.""" |
| 6 |
| 7 import contextlib |
| 8 import fcntl |
| 9 import httplib |
| 10 import logging |
| 11 import os |
| 12 import re |
| 13 import socket |
| 14 import traceback |
| 15 |
| 16 import cmd_helper |
| 17 import constants |
| 18 |
| 19 |
| 20 #The following two methods are used to allocate the port source for various |
| 21 # types of test servers. Because some net relates tests can be run on shards |
| 22 # at same time, it's important to have a mechanism to allocate the port process |
| 23 # safe. In here, we implement the safe port allocation by leveraging flock. |
| 24 def ResetTestServerPortAllocation(): |
| 25 """Reset the port allocation to start from TEST_SERVER_PORT_FIRST. |
| 26 |
| 27 Returns: |
| 28 Returns True if reset successes. Otherwise returns False. |
| 29 """ |
| 30 try: |
| 31 with open(constants.TEST_SERVER_PORT_FILE, 'w') as fp: |
| 32 fp.write('%d' % constants.TEST_SERVER_PORT_FIRST) |
| 33 if os.path.exists(constants.TEST_SERVER_PORT_LOCKFILE): |
| 34 os.unlink(constants.TEST_SERVER_PORT_LOCKFILE) |
| 35 return True |
| 36 except Exception as e: |
| 37 logging.error(e) |
| 38 return False |
| 39 |
| 40 |
| 41 def AllocateTestServerPort(): |
| 42 """Allocate a port incrementally. |
| 43 |
| 44 Returns: |
| 45 Returns a valid port which should be in between TEST_SERVER_PORT_FIRST and |
| 46 TEST_SERVER_PORT_LAST. Returning 0 means no more valid port can be used. |
| 47 """ |
| 48 port = 0 |
| 49 try: |
| 50 fp_lock = open(constants.TEST_SERVER_PORT_LOCKFILE, 'w') |
| 51 fcntl.flock(fp_lock, fcntl.LOCK_EX) |
| 52 # Get current valid port and calculate next valid port. |
| 53 assert os.path.exists(constants.TEST_SERVER_PORT_FILE) |
| 54 with open(constants.TEST_SERVER_PORT_FILE, 'r+') as fp: |
| 55 port = int(fp.read()) |
| 56 while IsHostPortUsed(port): |
| 57 port += 1 |
| 58 if (port > constants.TEST_SERVER_PORT_LAST or |
| 59 port < constants.TEST_SERVER_PORT_FIRST): |
| 60 port = 0 |
| 61 else: |
| 62 fp.seek(0, os.SEEK_SET) |
| 63 fp.write('%d' % (port + 1)) |
| 64 except Exception as e: |
| 65 logging.info(e) |
| 66 finally: |
| 67 if fp_lock: |
| 68 fcntl.flock(fp_lock, fcntl.LOCK_UN) |
| 69 fp_lock.close() |
| 70 logging.info('Allocate port %d for test server.', port) |
| 71 return port |
| 72 |
| 73 |
| 74 def IsHostPortUsed(host_port): |
| 75 """Checks whether the specified host port is used or not. |
| 76 |
| 77 Uses -n -P to inhibit the conversion of host/port numbers to host/port names. |
| 78 |
| 79 Args: |
| 80 host_port: Port on host we want to check. |
| 81 |
| 82 Returns: |
| 83 True if the port on host is already used, otherwise returns False. |
| 84 """ |
| 85 port_info = '(127\.0\.0\.1)|(localhost)\:%d' % host_port |
| 86 # TODO(jnd): Find a better way to filter the port. |
| 87 re_port = re.compile(port_info, re.MULTILINE) |
| 88 if re_port.findall(cmd_helper.GetCmdOutput(['lsof', '-nPi:%d' % host_port])): |
| 89 return True |
| 90 return False |
| 91 |
| 92 |
| 93 def IsDevicePortUsed(adb, device_port, state=''): |
| 94 """Checks whether the specified device port is used or not. |
| 95 |
| 96 Args: |
| 97 adb: Instance of AndroidCommands for talking to the device. |
| 98 device_port: Port on device we want to check. |
| 99 state: String of the specified state. Default is empty string, which |
| 100 means any state. |
| 101 |
| 102 Returns: |
| 103 True if the port on device is already used, otherwise returns False. |
| 104 """ |
| 105 base_url = '127.0.0.1:%d' % device_port |
| 106 netstat_results = adb.RunShellCommand('netstat', log_result=False) |
| 107 for single_connect in netstat_results: |
| 108 # Column 3 is the local address which we want to check with. |
| 109 connect_results = single_connect.split() |
| 110 is_state_match = connect_results[5] == state if state else True |
| 111 if connect_results[3] == base_url and is_state_match: |
| 112 return True |
| 113 return False |
| 114 |
| 115 |
| 116 def IsHttpServerConnectable(host, port, tries=3, command='GET', path='/', |
| 117 expected_read='', timeout=2): |
| 118 """Checks whether the specified http server is ready to serve request or not. |
| 119 |
| 120 Args: |
| 121 host: Host name of the HTTP server. |
| 122 port: Port number of the HTTP server. |
| 123 tries: How many times we want to test the connection. The default value is |
| 124 3. |
| 125 command: The http command we use to connect to HTTP server. The default |
| 126 command is 'GET'. |
| 127 path: The path we use when connecting to HTTP server. The default path is |
| 128 '/'. |
| 129 expected_read: The content we expect to read from the response. The default |
| 130 value is ''. |
| 131 timeout: Timeout (in seconds) for each http connection. The default is 2s. |
| 132 |
| 133 Returns: |
| 134 Tuple of (connect status, client error). connect status is a boolean value |
| 135 to indicate whether the server is connectable. client_error is the error |
| 136 message the server returns when connect status is false. |
| 137 """ |
| 138 assert tries >= 1 |
| 139 for i in xrange(0, tries): |
| 140 client_error = None |
| 141 try: |
| 142 with contextlib.closing(httplib.HTTPConnection( |
| 143 host, port, timeout=timeout)) as http: |
| 144 # Output some debug information when we have tried more than 2 times. |
| 145 http.set_debuglevel(i >= 2) |
| 146 http.request(command, path) |
| 147 r = http.getresponse() |
| 148 content = r.read() |
| 149 if r.status == 200 and r.reason == 'OK' and content == expected_read: |
| 150 return (True, '') |
| 151 client_error = ('Bad response: %s %s version %s\n ' % |
| 152 (r.status, r.reason, r.version) + |
| 153 '\n '.join([': '.join(h) for h in r.getheaders()])) |
| 154 except (httplib.HTTPException, socket.error) as e: |
| 155 # Probably too quick connecting: try again. |
| 156 exception_error_msgs = traceback.format_exception_only(type(e), e) |
| 157 if exception_error_msgs: |
| 158 client_error = ''.join(exception_error_msgs) |
| 159 # Only returns last client_error. |
| 160 return (False, client_error or 'Timeout') |
OLD | NEW |