ports.py 5.8 KB

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