X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/b20590222beddb52c8c89294ed3a324c8c7190a2..4993b8b44022fd3dc73fcebf20f80d054bdf4370:/sdk/python/tests/run_test_server.py diff --git a/sdk/python/tests/run_test_server.py b/sdk/python/tests/run_test_server.py index ab1c7ffd1a..5fee9eb0ec 100644 --- a/sdk/python/tests/run_test_server.py +++ b/sdk/python/tests/run_test_server.py @@ -9,6 +9,7 @@ import random import re import shutil import signal +import socket import subprocess import string import sys @@ -94,28 +95,22 @@ def kill_server_pid(pidfile, wait=10, passenger_root=False): pass def find_available_port(): - """Return a port number that is not in use right now. + """Return an IPv4 port number that is not in use right now. + + We assume whoever needs to use the returned port is able to reuse + a recently used port without waiting for TIME_WAIT (see + SO_REUSEADDR / SO_REUSEPORT). Some opportunity for races here, but it's better than choosing something at random and not checking at all. If all of our servers (hey Passenger) knew that listening on port 0 was a thing, the OS would take care of the races, and this wouldn't be needed at all. """ - port = None - while port is None: - port = random.randint(20000, 40000) - port_hex = ':%04x ' % port - try: - with open('/proc/net/tcp', 'r') as f: - for line in f: - if 0 <= string.find(line, port_hex): - port = None - break - except OSError: - # This isn't going so well. Just use the random port. - pass - except IOError: - pass + + sock = socket.socket() + sock.bind(('0.0.0.0', 0)) + port = sock.getsockname()[1] + sock.close() return port def run(leave_running_atexit=False): @@ -138,28 +133,27 @@ def run(leave_running_atexit=False): # Delete cached discovery document. shutil.rmtree(arvados.http_cache('discovery')) - os.environ['ARVADOS_API_TOKEN'] = auth_token('admin') - os.environ['ARVADOS_API_HOST_INSECURE'] = 'true' - pid_file = os.path.join(SERVICES_SRC_DIR, 'api', SERVER_PID_PATH) pid_file_ok = find_server_pid(pid_file, 0) existing_api_host = os.environ.get('ARVADOS_TEST_API_HOST', my_api_host) if existing_api_host and pid_file_ok: - try: - os.environ['ARVADOS_API_HOST'] = existing_api_host - reset() - return - except: - pass + if existing_api_host == my_api_host: + try: + return reset() + except: + # Fall through to shutdown-and-start case. + pass + else: + # Server was provided by parent. Can't recover if it's + # unresettable. + return reset() # Before trying to start up our own server, call stop() to avoid # "Phusion Passenger Standalone is already running on PID 12345". - # We want to kill it if it's our own _or_ it's some stale - # left-over server. But if it's been deliberately provided to us - # by a parent process, we don't want to force-kill it. That'll - # just wreck things for the next test suite that tries to use it. - stop(force=('ARVADOS_TEST_API_HOST' not in os.environ)) + # (If we've gotten this far, ARVADOS_TEST_API_HOST isn't set, so + # we know the server is ours to kill.) + stop(force=True) restore_cwd = os.getcwd() api_src_dir = os.path.join(SERVICES_SRC_DIR, 'api') @@ -181,7 +175,8 @@ def run(leave_running_atexit=False): '-out', 'tmp/self-signed.pem', '-keyout', 'tmp/self-signed.key', '-days', '3650', - '-subj', '/CN=0.0.0.0']) + '-subj', '/CN=0.0.0.0'], + stdout=sys.stderr) port = find_available_port() env = os.environ.copy() @@ -222,6 +217,9 @@ def reset(): This resets the ARVADOS_TEST_API_HOST provided by a parent process if any, otherwise the server started by run(). + + It also resets ARVADOS_* environment vars to point to the test + server with admin credentials. """ existing_api_host = os.environ.get('ARVADOS_TEST_API_HOST', my_api_host) token = auth_token('admin') @@ -231,6 +229,9 @@ def reset(): 'https://{}/database/reset'.format(existing_api_host), 'POST', headers={'Authorization': 'OAuth2 {}'.format(token)}) + os.environ['ARVADOS_API_HOST_INSECURE'] = 'true' + os.environ['ARVADOS_API_HOST'] = existing_api_host + os.environ['ARVADOS_API_TOKEN'] = token def stop(force=False): """Stop the API server, if one is running.