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