Update sdk/platform-tools to version 26.0.0.
[android_tools.git] / sdk / platform-tools / systrace / catapult / devil / devil / android / ports.py
blob1d4e5f21fef81fc4f0fd3e673044abde2578041b
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."""
7 import contextlib
8 import fcntl
9 import httplib
10 import logging
11 import os
12 import socket
13 import traceback
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
29 # flock.
30 def ResetTestServerPortAllocation():
31 """Resets the port allocation to start from TEST_SERVER_PORT_FIRST.
33 Returns:
34 Returns True if reset successes. Otherwise returns False.
35 """
36 try:
37 with open(_TEST_SERVER_PORT_FILE, 'w') as fp:
38 fp.write('%d' % _TEST_SERVER_PORT_FIRST)
39 return True
40 except Exception: # pylint: disable=broad-except
41 logger.exception('Error while resetting port allocation')
42 return False
45 def AllocateTestServerPort():
46 """Allocates a port incrementally.
48 Returns:
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.
51 """
52 port = 0
53 ports_tried = []
54 try:
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:
61 port = int(fp.read())
62 ports_tried.append(port)
63 while not IsHostPortAvailable(port):
64 port += 1
65 ports_tried.append(port)
66 if (port > _TEST_SERVER_PORT_LAST or
67 port < _TEST_SERVER_PORT_FIRST):
68 port = 0
69 else:
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')
74 finally:
75 if fp_lock:
76 fcntl.flock(fp_lock, fcntl.LOCK_UN)
77 fp_lock.close()
78 if port:
79 logger.info('Allocate port %d for test server.', port)
80 else:
81 logger.error('Could not allocate port for test server. '
82 'List of ports tried: %s', str(ports_tried))
83 return port
86 def IsHostPortAvailable(host_port):
87 """Checks whether the specified host port is available.
89 Args:
90 host_port: Port on host to check.
92 Returns:
93 True if the port on host is available, otherwise returns False.
94 """
95 s = socket.socket()
96 try:
97 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
98 s.bind(('', host_port))
99 s.close()
100 return True
101 except socket.error:
102 return False
105 def IsDevicePortUsed(device, device_port, state=''):
106 """Checks whether the specified device port is used or not.
108 Args:
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
112 means any state.
114 Returns:
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':
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
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.
138 Args:
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
144 command is 'GET'.
145 path: The path we use when connecting to HTTP server. The default path is
146 '/'.
147 expected_read: The content we expect to read from the response. The default
148 value is ''.
149 timeout: Timeout (in seconds) for each http connection. The default is 2s.
151 Returns:
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.
156 assert tries >= 1
157 for i in xrange(0, tries):
158 client_error = None
159 try:
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()
166 content = r.read()
167 if r.status == 200 and r.reason == 'OK' and content == expected_read:
168 return (True, '')
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')