+ If neither of those options work out, we'll really start a new
+ server.
+ """
+ global my_api_host
+
+ # Delete cached discovery document.
+ shutil.rmtree(arvados.http_cache('discovery'))
+
+ 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:
+ 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".
+ # (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')
+ os.chdir(api_src_dir)
+
+ # Either we haven't started a server of our own yet, or it has
+ # died, or we have lost our credentials, or something else is
+ # preventing us from calling reset(). Start a new one.
+
+ if not os.path.exists('tmp/self-signed.pem'):
+ # We assume here that either passenger reports its listening
+ # address as https:/0.0.0.0:port/. If it reports "127.0.0.1"
+ # then the certificate won't match the host and reset() will
+ # fail certificate verification. If it reports "localhost",
+ # clients (notably Python SDK's websocket client) might
+ # resolve localhost as ::1 and then fail to connect.
+ subprocess.check_call([
+ 'openssl', 'req', '-new', '-x509', '-nodes',
+ '-out', 'tmp/self-signed.pem',
+ '-keyout', 'tmp/self-signed.key',
+ '-days', '3650',
+ '-subj', '/CN=0.0.0.0'],
+ stdout=sys.stderr)
+
+ port = find_available_port()
+ env = os.environ.copy()
+ env['RAILS_ENV'] = 'test'
+ env['ARVADOS_WEBSOCKETS'] = 'yes'
+ env.pop('ARVADOS_TEST_API_HOST', None)
+ env.pop('ARVADOS_API_HOST', None)
+ env.pop('ARVADOS_API_HOST_INSECURE', None)
+ env.pop('ARVADOS_API_TOKEN', None)
+ start_msg = subprocess.check_output(
+ ['bundle', 'exec',
+ 'passenger', 'start', '-d', '-p{}'.format(port),
+ '--pid-file', os.path.join(os.getcwd(), pid_file),
+ '--log-file', os.path.join(os.getcwd(), 'log/test.log'),
+ '--ssl',
+ '--ssl-certificate', 'tmp/self-signed.pem',
+ '--ssl-certificate-key', 'tmp/self-signed.key'],
+ env=env)
+
+ if not leave_running_atexit:
+ atexit.register(kill_server_pid, pid_file, passenger_root=api_src_dir)
+
+ match = re.search(r'Accessible via: https://(.*?)/', start_msg)
+ if not match:
+ raise Exception(
+ "Passenger did not report endpoint: {}".format(start_msg))
+ my_api_host = match.group(1)
+ os.environ['ARVADOS_API_HOST'] = my_api_host
+
+ # Make sure the server has written its pid file before continuing
+ find_server_pid(pid_file)
+
+ reset()
+ os.chdir(restore_cwd)
+
+def reset():
+ """Reset the test server to fixture state.
+
+ 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')
+ httpclient = httplib2.Http(ca_certs=os.path.join(
+ SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem'))
+ httpclient.request(
+ '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.
+
+ If force==False, kill it only if we started it ourselves. (This
+ supports the use case where a Python test suite calls run(), but
+ run() just uses the ARVADOS_TEST_API_HOST provided by the parent
+ process, and the test suite cleans up after itself by calling
+ stop(). In this case the test server provided by the parent
+ process should be left alone.)
+
+ If force==True, kill it even if we didn't start it
+ ourselves. (This supports the use case in __main__, where "run"
+ and "stop" happen in different processes.)
+ """
+ global my_api_host
+ if force or my_api_host is not None:
+ kill_server_pid(os.path.join(SERVICES_SRC_DIR, 'api', SERVER_PID_PATH))
+ my_api_host = None