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.
5 """Functions that deal with local and device ports."""
15 logger
= logging
.getLogger(__name__
)
17 # The net test server is started from port 10201.
18 _TEST_SERVER_PORT_FIRST
= 10201
19 _TEST_SERVER_PORT_LAST
= 30000
20 # A file to record next valid port of test server.
21 _TEST_SERVER_PORT_FILE
= '/tmp/test_server_port'
22 _TEST_SERVER_PORT_LOCKFILE
= '/tmp/test_server_port.lock'
25 # The following two methods are used to allocate the port source for various
26 # types of test servers. Because some net-related tests can be run on shards at
27 # same time, it's important to have a mechanism to allocate the port
28 # process-safe. In here, we implement the safe port allocation by leveraging
30 def ResetTestServerPortAllocation():
31 """Resets the port allocation to start from TEST_SERVER_PORT_FIRST.
34 Returns True if reset successes. Otherwise returns False.
37 with
open(_TEST_SERVER_PORT_FILE
, 'w') as fp
:
38 fp
.write('%d' % _TEST_SERVER_PORT_FIRST
)
40 except Exception: # pylint: disable=broad-except
41 logger
.exception('Error while resetting port allocation')
45 def AllocateTestServerPort():
46 """Allocates a port incrementally.
49 Returns a valid port which should be in between TEST_SERVER_PORT_FIRST and
50 TEST_SERVER_PORT_LAST. Returning 0 means no more valid port can be used.
55 fp_lock
= open(_TEST_SERVER_PORT_LOCKFILE
, 'w')
56 fcntl
.flock(fp_lock
, fcntl
.LOCK_EX
)
57 # Get current valid port and calculate next valid port.
58 if not os
.path
.exists(_TEST_SERVER_PORT_FILE
):
59 ResetTestServerPortAllocation()
60 with
open(_TEST_SERVER_PORT_FILE
, 'r+') as fp
:
62 ports_tried
.append(port
)
63 while not IsHostPortAvailable(port
):
65 ports_tried
.append(port
)
66 if (port
> _TEST_SERVER_PORT_LAST
or
67 port
< _TEST_SERVER_PORT_FIRST
):
70 fp
.seek(0, os
.SEEK_SET
)
71 fp
.write('%d' % (port
+ 1))
72 except Exception: # pylint: disable=broad-except
73 logger
.exception('Error while allocating port')
76 fcntl
.flock(fp_lock
, fcntl
.LOCK_UN
)
79 logger
.info('Allocate port %d for test server.', port
)
81 logger
.error('Could not allocate port for test server. '
82 'List of ports tried: %s', str(ports_tried
))
86 def IsHostPortAvailable(host_port
):
87 """Checks whether the specified host port is available.
90 host_port: Port on host to check.
93 True if the port on host is available, otherwise returns False.
97 s
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEADDR
, 1)
98 s
.bind(('', host_port
))
105 def IsDevicePortUsed(device
, device_port
, state
=''):
106 """Checks whether the specified device port is used or not.
109 device: A DeviceUtils instance.
110 device_port: Port on device we want to check.
111 state: String of the specified state. Default is empty string, which
115 True if the port on device is already used, otherwise returns False.
117 base_urls
= ('127.0.0.1:%d' % device_port
, 'localhost:%d' % device_port
)
118 netstat_results
= device
.RunShellCommand(
119 ['netstat', '-a'], check_return
=True, 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':
125 if len(connect_results
) < 6:
126 raise Exception('Unexpected format while parsing netstat line: ' +
128 is_state_match
= connect_results
[5] == state
if state
else True
129 if connect_results
[3] in base_urls
and is_state_match
:
134 def IsHttpServerConnectable(host
, port
, tries
=3, command
='GET', path
='/',
135 expected_read
='', timeout
=2):
136 """Checks whether the specified http server is ready to serve request or not.
139 host: Host name of the HTTP server.
140 port: Port number of the HTTP server.
141 tries: How many times we want to test the connection. The default value is
143 command: The http command we use to connect to HTTP server. The default
145 path: The path we use when connecting to HTTP server. The default path is
147 expected_read: The content we expect to read from the response. The default
149 timeout: Timeout (in seconds) for each http connection. The default is 2s.
152 Tuple of (connect status, client error). connect status is a boolean value
153 to indicate whether the server is connectable. client_error is the error
154 message the server returns when connect status is false.
157 for i
in xrange(0, tries
):
160 with contextlib
.closing(httplib
.HTTPConnection(
161 host
, port
, timeout
=timeout
)) as http
:
162 # Output some debug information when we have tried more than 2 times.
163 http
.set_debuglevel(i
>= 2)
164 http
.request(command
, path
)
165 r
= http
.getresponse()
167 if r
.status
== 200 and r
.reason
== 'OK' and content
== expected_read
:
169 client_error
= ('Bad response: %s %s version %s\n ' %
170 (r
.status
, r
.reason
, r
.version
) +
171 '\n '.join([': '.join(h
) for h
in r
.getheaders()]))
172 except (httplib
.HTTPException
, socket
.error
) as e
:
173 # Probably too quick connecting: try again.
174 exception_error_msgs
= traceback
.format_exception_only(type(e
), e
)
175 if exception_error_msgs
:
176 client_error
= ''.join(exception_error_msgs
)
177 # Only returns last client_error.
178 return (False, client_error
or 'Timeout')