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"""Functions that deal with local and device ports.""" 5 6import contextlib 7import fcntl 8import httplib 9import logging 10import os 11import socket 12import traceback 13 14logger = logging.getLogger(__name__) 15 16# The net test server is started from port 10201. 17_TEST_SERVER_PORT_FIRST = 10201 18_TEST_SERVER_PORT_LAST = 30000 19# A file to record next valid port of test server. 20_TEST_SERVER_PORT_FILE = '/tmp/test_server_port' 21_TEST_SERVER_PORT_LOCKFILE = '/tmp/test_server_port.lock' 22 23 24# The following two methods are used to allocate the port source for various 25# types of test servers. Because some net-related tests can be run on shards at 26# same time, it's important to have a mechanism to allocate the port 27# process-safe. In here, we implement the safe port allocation by leveraging 28# flock. 29def ResetTestServerPortAllocation(): 30 """Resets the port allocation to start from TEST_SERVER_PORT_FIRST. 31 32 Returns: 33 Returns True if reset successes. Otherwise returns False. 34 """ 35 try: 36 with open(_TEST_SERVER_PORT_FILE, 'w') as fp: 37 fp.write('%d' % _TEST_SERVER_PORT_FIRST) 38 return True 39 except Exception: # pylint: disable=broad-except 40 logger.exception('Error while resetting port allocation') 41 return False 42 43 44def AllocateTestServerPort(): 45 """Allocates a port incrementally. 46 47 Returns: 48 Returns a valid port which should be in between TEST_SERVER_PORT_FIRST and 49 TEST_SERVER_PORT_LAST. Returning 0 means no more valid port can be used. 50 """ 51 port = 0 52 ports_tried = [] 53 try: 54 fp_lock = open(_TEST_SERVER_PORT_LOCKFILE, 'w') 55 fcntl.flock(fp_lock, fcntl.LOCK_EX) 56 # Get current valid port and calculate next valid port. 57 if not os.path.exists(_TEST_SERVER_PORT_FILE): 58 ResetTestServerPortAllocation() 59 with open(_TEST_SERVER_PORT_FILE, 'r+') as fp: 60 port = int(fp.read()) 61 ports_tried.append(port) 62 while not IsHostPortAvailable(port): 63 port += 1 64 ports_tried.append(port) 65 if port > _TEST_SERVER_PORT_LAST or port < _TEST_SERVER_PORT_FIRST: 66 port = 0 67 else: 68 fp.seek(0, os.SEEK_SET) 69 fp.write('%d' % (port + 1)) 70 except Exception: # pylint: disable=broad-except 71 logger.exception('Error while allocating port') 72 finally: 73 if fp_lock: 74 fcntl.flock(fp_lock, fcntl.LOCK_UN) 75 fp_lock.close() 76 if port: 77 logger.info('Allocate port %d for test server.', port) 78 else: 79 logger.error( 80 'Could not allocate port for test server. ' 81 'List of ports tried: %s', str(ports_tried)) 82 return port 83 84 85def IsHostPortAvailable(host_port): 86 """Checks whether the specified host port is available. 87 88 Args: 89 host_port: Port on host to check. 90 91 Returns: 92 True if the port on host is available, otherwise returns False. 93 """ 94 s = socket.socket() 95 try: 96 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 97 s.bind(('', host_port)) 98 s.close() 99 return True 100 except socket.error: 101 return False 102 103 104def IsDevicePortUsed(device, device_port, state=''): 105 """Checks whether the specified device port is used or not. 106 107 Args: 108 device: A DeviceUtils instance. 109 device_port: Port on device we want to check. 110 state: String of the specified state. Default is empty string, which 111 means any state. 112 113 Returns: 114 True if the port on device is already used, otherwise returns False. 115 """ 116 base_urls = ('127.0.0.1:%d' % device_port, 'localhost:%d' % device_port) 117 netstat_results = device.RunShellCommand(['netstat', '-an'], 118 check_return=True, 119 large_output=True) 120 for single_connect in netstat_results: 121 # Column 3 is the local address which we want to check with. 122 connect_results = single_connect.split() 123 if connect_results[0] != 'tcp': 124 continue 125 if len(connect_results) < 6: 126 raise Exception('Unexpected format while parsing netstat line: ' + 127 single_connect) 128 is_state_match = connect_results[5] == state if state else True 129 if connect_results[3] in base_urls and is_state_match: 130 return True 131 return False 132 133 134def IsHttpServerConnectable(host, 135 port, 136 tries=3, 137 command='GET', 138 path='/', 139 expected_read='', 140 timeout=2): 141 """Checks whether the specified http server is ready to serve request or not. 142 143 Args: 144 host: Host name of the HTTP server. 145 port: Port number of the HTTP server. 146 tries: How many times we want to test the connection. The default value is 147 3. 148 command: The http command we use to connect to HTTP server. The default 149 command is 'GET'. 150 path: The path we use when connecting to HTTP server. The default path is 151 '/'. 152 expected_read: The content we expect to read from the response. The default 153 value is ''. 154 timeout: Timeout (in seconds) for each http connection. The default is 2s. 155 156 Returns: 157 Tuple of (connect status, client error). connect status is a boolean value 158 to indicate whether the server is connectable. client_error is the error 159 message the server returns when connect status is false. 160 """ 161 assert tries >= 1 162 for i in range(0, tries): 163 client_error = None 164 try: 165 with contextlib.closing( 166 httplib.HTTPConnection(host, port, timeout=timeout)) as http: 167 # Output some debug information when we have tried more than 2 times. 168 http.set_debuglevel(i >= 2) 169 http.request(command, path) 170 r = http.getresponse() 171 content = r.read() 172 if r.status == 200 and r.reason == 'OK' and content == expected_read: 173 return (True, '') 174 client_error = ('Bad response: %s %s version %s\n ' % 175 (r.status, r.reason, r.version) + '\n '.join( 176 [': '.join(h) for h in r.getheaders()])) 177 except (httplib.HTTPException, socket.error) as e: 178 # Probably too quick connecting: try again. 179 exception_error_msgs = traceback.format_exception_only(type(e), e) 180 if exception_error_msgs: 181 client_error = ''.join(exception_error_msgs) 182 # Only returns last client_error. 183 return (False, client_error or 'Timeout') 184