18794: Merge branch 'main'
[arvados.git] / sdk / python / tests / run_test_server.py
index 0edfab15183e1d848c5787620a5f1f9ab6e896dd..76893ac84217d9fbd576723a19e9aaaa06be72db 100644 (file)
@@ -26,6 +26,11 @@ import time
 import unittest
 import yaml
 
+try:
+    from urllib.parse import urlparse
+except ImportError:
+    from urlparse import urlparse
+
 MY_DIRNAME = os.path.dirname(os.path.realpath(__file__))
 if __name__ == '__main__' and os.path.exists(
       os.path.join(MY_DIRNAME, '..', 'arvados', '__init__.py')):
@@ -38,6 +43,14 @@ import arvados.config
 
 ARVADOS_DIR = os.path.realpath(os.path.join(MY_DIRNAME, '../../..'))
 SERVICES_SRC_DIR = os.path.join(ARVADOS_DIR, 'services')
+
+# Work around https://bugs.python.org/issue27805, should be no longer
+# necessary from sometime in Python 3.8.x
+if not os.environ.get('ARVADOS_DEBUG', ''):
+    WRITE_MODE = 'a'
+else:
+    WRITE_MODE = 'w'
+
 if 'GOPATH' in os.environ:
     # Add all GOPATH bin dirs to PATH -- but insert them after the
     # ruby gems bin dir, to ensure "bundle" runs the Ruby bundler
@@ -60,6 +73,7 @@ if not os.path.exists(TEST_TMPDIR):
 my_api_host = None
 _cached_config = {}
 _cached_db_config = {}
+_already_used_port = {}
 
 def find_server_pid(PID_PATH, wait=10):
     now = time.time()
@@ -168,11 +182,15 @@ def find_available_port():
     would take care of the races, and this wouldn't be needed at all.
     """
 
-    sock = socket.socket()
-    sock.bind(('0.0.0.0', 0))
-    port = sock.getsockname()[1]
-    sock.close()
-    return port
+    global _already_used_port
+    while True:
+        sock = socket.socket()
+        sock.bind(('0.0.0.0', 0))
+        port = sock.getsockname()[1]
+        sock.close()
+        if port not in _already_used_port:
+            _already_used_port[port] = True
+            return port
 
 def _wait_until_port_listens(port, timeout=10, warn=True):
     """Wait for a process to start listening on the given port.
@@ -313,44 +331,53 @@ def run(leave_running_atexit=False):
         os.makedirs(gitdir)
     subprocess.check_output(['tar', '-xC', gitdir, '-f', gittarball])
 
-    # The nginx proxy isn't listening here yet, but we need to choose
-    # the wss:// port now so we can write the API server config file.
-    wss_port = find_available_port()
-    _setport('wss', wss_port)
-
-    port = find_available_port()
+    # Customizing the passenger config template is the only documented
+    # way to override the default passenger_stat_throttle_rate (10 s).
+    # In the testing environment, we want restart.txt to take effect
+    # immediately.
+    resdir = subprocess.check_output(['bundle', 'exec', 'passenger-config', 'about', 'resourcesdir']).decode().rstrip()
+    with open(resdir + '/templates/standalone/config.erb') as f:
+        template = f.read()
+    newtemplate = re.sub('http {', 'http {\n        passenger_stat_throttle_rate 0;', template)
+    if newtemplate == template:
+        raise "template edit failed"
+    with open('tmp/passenger-nginx.conf.erb', 'w') as f:
+        f.write(newtemplate)
+
+    port = internal_port_from_config("RailsAPI")
     env = os.environ.copy()
     env['RAILS_ENV'] = 'test'
-    env['ARVADOS_TEST_WSS_PORT'] = str(wss_port)
+    env['ARVADOS_RAILS_LOG_TO_STDOUT'] = '1'
     env.pop('ARVADOS_WEBSOCKETS', None)
     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(
+    logf = open(_logfilename('railsapi'), WRITE_MODE)
+    railsapi = subprocess.Popen(
         ['bundle', 'exec',
-         'passenger', 'start', '-d', '-p{}'.format(port),
+         'passenger', 'start', '-p{}'.format(port),
+         '--nginx-config-template', 'tmp/passenger-nginx.conf.erb',
+        '--no-friendly-error-pages',
+        '--disable-anonymous-telemetry',
+        '--disable-security-update-check',
          '--pid-file', pid_file,
-         '--log-file', os.path.join(os.getcwd(), 'log/test.log'),
+         '--log-file', '/dev/stdout',
          '--ssl',
          '--ssl-certificate', 'tmp/self-signed.pem',
          '--ssl-certificate-key', 'tmp/self-signed.key'],
-        env=env)
+        env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
 
     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)
+    my_api_host = "127.0.0.1:"+str(port)
     os.environ['ARVADOS_API_HOST'] = my_api_host
 
     # Make sure the server has written its pid file and started
     # listening on its TCP port
-    find_server_pid(pid_file)
     _wait_until_port_listens(port)
+    find_server_pid(pid_file)
 
     reset()
     os.chdir(restore_cwd)
@@ -371,13 +398,11 @@ def reset():
     httpclient.request(
         'https://{}/database/reset'.format(existing_api_host),
         'POST',
-        headers={'Authorization': 'OAuth2 {}'.format(token)})
+        headers={'Authorization': 'OAuth2 {}'.format(token), 'Connection':'close'})
+
     os.environ['ARVADOS_API_HOST_INSECURE'] = 'true'
     os.environ['ARVADOS_API_TOKEN'] = token
-    if _wait_until_port_listens(_getport('controller-ssl'), timeout=0.5, warn=False):
-        os.environ['ARVADOS_API_HOST'] = '0.0.0.0:'+str(_getport('controller-ssl'))
-    else:
-        os.environ['ARVADOS_API_HOST'] = existing_api_host
+    os.environ['ARVADOS_API_HOST'] = existing_api_host
 
 def stop(force=False):
     """Stop the API server, if one is running.
@@ -398,52 +423,30 @@ def stop(force=False):
         kill_server_pid(_pidfile('api'))
         my_api_host = None
 
+def get_config():
+    with open(os.environ["ARVADOS_CONFIG"]) as f:
+        return yaml.safe_load(f)
+
+def internal_port_from_config(service, idx=0):
+    return int(urlparse(
+        sorted(list(get_config()["Clusters"]["zzzzz"]["Services"][service]["InternalURLs"].keys()))[idx]).
+               netloc.split(":")[1])
+
+def external_port_from_config(service):
+    return int(urlparse(get_config()["Clusters"]["zzzzz"]["Services"][service]["ExternalURL"]).netloc.split(":")[1])
+
 def run_controller():
     if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
         return
     stop_controller()
-    rails_api_port = int(string.split(os.environ.get('ARVADOS_TEST_API_HOST', my_api_host), ':')[-1])
-    port = find_available_port()
-    conf = os.path.join(TEST_TMPDIR, 'arvados.yml')
-    with open(conf, 'w') as f:
-        f.write("""
-Clusters:
-  zzzzz:
-    ManagementToken: e687950a23c3a9bceec28c6223a06c79
-    API:
-      RequestTimeout: 30s
-    PostgreSQL:
-      ConnectionPool: 32
-      Connection:
-        host: {dbhost}
-        dbname: {dbname}
-        user: {dbuser}
-        password: {dbpass}
-    TLS:
-      Insecure: true
-    Services:
-      Controller:
-        InternalURLs:
-          "http://localhost:{controllerport}": {{}}
-      RailsAPI:
-        InternalURLs:
-          "https://localhost:{railsport}": {{}}
-        """.format(
-            dbhost=_dbconfig('host'),
-            dbname=_dbconfig('database'),
-            dbuser=_dbconfig('username'),
-            dbpass=_dbconfig('password'),
-            controllerport=port,
-            railsport=rails_api_port,
-        ))
-    logf = open(_logfilename('controller'), 'a')
+    logf = open(_logfilename('controller'), WRITE_MODE)
+    port = internal_port_from_config("Controller")
     controller = subprocess.Popen(
-        ["arvados-server", "controller", "-config", conf],
+        ["arvados-server", "controller"],
         stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
     with open(_pidfile('controller'), 'w') as f:
         f.write(str(controller.pid))
     _wait_until_port_listens(port)
-    _setport('controller', port)
     return port
 
 def stop_controller():
@@ -455,36 +458,14 @@ def run_ws():
     if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
         return
     stop_ws()
-    port = find_available_port()
-    conf = os.path.join(TEST_TMPDIR, 'ws.yml')
-    with open(conf, 'w') as f:
-        f.write("""
-Client:
-  APIHost: {}
-  Insecure: true
-Listen: :{}
-LogLevel: {}
-Postgres:
-  host: {}
-  dbname: {}
-  user: {}
-  password: {}
-  sslmode: require
-        """.format(os.environ['ARVADOS_API_HOST'],
-                   port,
-                   ('info' if os.environ.get('ARVADOS_DEBUG', '') in ['','0'] else 'debug'),
-                   _dbconfig('host'),
-                   _dbconfig('database'),
-                   _dbconfig('username'),
-                   _dbconfig('password')))
-    logf = open(_logfilename('ws'), 'a')
+    port = internal_port_from_config("Websocket")
+    logf = open(_logfilename('ws'), WRITE_MODE)
     ws = subprocess.Popen(
-        ["ws", "-config", conf],
+        ["arvados-server", "ws"],
         stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
     with open(_pidfile('ws'), 'w') as f:
         f.write(str(ws.pid))
     _wait_until_port_listens(port)
-    _setport('ws', port)
     return port
 
 def stop_ws():
@@ -492,47 +473,41 @@ def stop_ws():
         return
     kill_server_pid(_pidfile('ws'))
 
-def _start_keep(n, keep_args):
-    keep0 = tempfile.mkdtemp()
-    port = find_available_port()
-    keep_cmd = ["keepstore",
-                "-volume={}".format(keep0),
-                "-listen=:{}".format(port),
-                "-pid="+_pidfile('keep{}'.format(n))]
-
-    for arg, val in keep_args.items():
-        keep_cmd.append("{}={}".format(arg, val))
+def _start_keep(n, blob_signing=False):
+    datadir = os.path.join(TEST_TMPDIR, "keep%d.data"%n)
+    if os.path.exists(datadir):
+        shutil.rmtree(datadir)
+    os.mkdir(datadir)
+    port = internal_port_from_config("Keepstore", idx=n)
+
+    # Currently, if there are multiple InternalURLs for a single host,
+    # the only way to tell a keepstore process which one it's supposed
+    # to listen on is to supply a redacted version of the config, with
+    # the other InternalURLs removed.
+    conf = os.path.join(TEST_TMPDIR, "keep%d.yaml"%n)
+    confdata = get_config()
+    confdata['Clusters']['zzzzz']['Services']['Keepstore']['InternalURLs'] = {"http://127.0.0.1:%d"%port: {}}
+    confdata['Clusters']['zzzzz']['Collections']['BlobSigning'] = blob_signing
+    with open(conf, 'w') as f:
+        yaml.safe_dump(confdata, f)
+    keep_cmd = ["arvados-server", "keepstore", "-config", conf]
 
-    with open(_logfilename('keep{}'.format(n)), 'a') as logf:
+    with open(_logfilename('keep{}'.format(n)), WRITE_MODE) as logf:
         with open('/dev/null') as _stdin:
-            kp0 = subprocess.Popen(
+            child = subprocess.Popen(
                 keep_cmd, stdin=_stdin, stdout=logf, stderr=logf, close_fds=True)
 
+    print('child.pid is %d'%child.pid, file=sys.stderr)
     with open(_pidfile('keep{}'.format(n)), 'w') as f:
-        f.write(str(kp0.pid))
-
-    with open("{}/keep{}.volume".format(TEST_TMPDIR, n), 'w') as f:
-        f.write(keep0)
+        f.write(str(child.pid))
 
     _wait_until_port_listens(port)
 
     return port
 
-def run_keep(blob_signing_key=None, enforce_permissions=False, num_servers=2):
+def run_keep(num_servers=2, **kwargs):
     stop_keep(num_servers)
 
-    keep_args = {}
-    if not blob_signing_key:
-        blob_signing_key = 'zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc'
-    with open(os.path.join(TEST_TMPDIR, "keep.blob_signing_key"), "w") as f:
-        keep_args['-blob-signing-key-file'] = f.name
-        f.write(blob_signing_key)
-    keep_args['-enforce-permissions'] = str(enforce_permissions).lower()
-    with open(os.path.join(TEST_TMPDIR, "keep.data-manager-token-file"), "w") as f:
-        keep_args['-data-manager-token-file'] = f.name
-        f.write(auth_token('data_manager'))
-    keep_args['-never-delete'] = 'false'
-
     api = arvados.api(
         version='v1',
         host=os.environ['ARVADOS_API_HOST'],
@@ -545,7 +520,7 @@ def run_keep(blob_signing_key=None, enforce_permissions=False, num_servers=2):
         api.keep_disks().delete(uuid=d['uuid']).execute()
 
     for d in range(0, num_servers):
-        port = _start_keep(d, keep_args)
+        port = _start_keep(d, **kwargs)
         svc = api.keep_services().create(body={'keep_service': {
             'uuid': 'zzzzz-bi6l4-keepdisk{:07d}'.format(d),
             'service_host': 'localhost',
@@ -560,7 +535,7 @@ def run_keep(blob_signing_key=None, enforce_permissions=False, num_servers=2):
     # If keepproxy and/or keep-web is running, send SIGHUP to make
     # them discover the new keepstore services.
     for svc in ('keepproxy', 'keep-web'):
-        pidfile = _pidfile('keepproxy')
+        pidfile = _pidfile(svc)
         if os.path.exists(pidfile):
             try:
                 with open(pidfile) as pid:
@@ -570,12 +545,6 @@ def run_keep(blob_signing_key=None, enforce_permissions=False, num_servers=2):
 
 def _stop_keep(n):
     kill_server_pid(_pidfile('keep{}'.format(n)))
-    if os.path.exists("{}/keep{}.volume".format(TEST_TMPDIR, n)):
-        with open("{}/keep{}.volume".format(TEST_TMPDIR, n), 'r') as r:
-            shutil.rmtree(r.read(), True)
-        os.unlink("{}/keep{}.volume".format(TEST_TMPDIR, n))
-    if os.path.exists(os.path.join(TEST_TMPDIR, "keep.blob_signing_key")):
-        os.remove(os.path.join(TEST_TMPDIR, "keep.blob_signing_key"))
 
 def stop_keep(num_servers=2):
     for n in range(0, num_servers):
@@ -583,20 +552,22 @@ def stop_keep(num_servers=2):
 
 def run_keep_proxy():
     if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
-        os.environ["ARVADOS_KEEP_SERVICES"] = "http://localhost:{}".format(_getport('keepproxy'))
+        os.environ["ARVADOS_KEEP_SERVICES"] = "http://localhost:{}".format(internal_port_from_config('Keepproxy'))
         return
     stop_keep_proxy()
 
-    port = find_available_port()
+    port = internal_port_from_config("Keepproxy")
     env = os.environ.copy()
     env['ARVADOS_API_TOKEN'] = auth_token('anonymous')
-    logf = open(_logfilename('keepproxy'), 'a')
+    logf = open(_logfilename('keepproxy'), WRITE_MODE)
     kp = subprocess.Popen(
-        ['keepproxy',
-         '-pid='+_pidfile('keepproxy'),
-         '-listen=:{}'.format(port)],
-        env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
+        ['arvados-server', 'keepproxy'], env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
+
+    with open(_pidfile('keepproxy'), 'w') as f:
+        f.write(str(kp.pid))
+    _wait_until_port_listens(port)
 
+    print("Using API %s token %s" % (os.environ['ARVADOS_API_HOST'], auth_token('admin')), file=sys.stdout)
     api = arvados.api(
         version='v1',
         host=os.environ['ARVADOS_API_HOST'],
@@ -612,7 +583,6 @@ def run_keep_proxy():
         'service_ssl_flag': False,
     }}).execute()
     os.environ["ARVADOS_KEEP_SERVICES"] = "http://localhost:{}".format(port)
-    _setport('keepproxy', port)
     _wait_until_port_listens(port)
 
 def stop_keep_proxy():
@@ -625,46 +595,34 @@ def run_arv_git_httpd():
         return
     stop_arv_git_httpd()
 
-    gitdir = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'git')
-    gitport = find_available_port()
+    gitport = internal_port_from_config("GitHTTP")
     env = os.environ.copy()
     env.pop('ARVADOS_API_TOKEN', None)
-    logf = open(_logfilename('arv-git-httpd'), 'a')
-    agh = subprocess.Popen(
-        ['arv-git-httpd',
-         '-repo-root='+gitdir+'/test',
-         '-management-token=e687950a23c3a9bceec28c6223a06c79',
-         '-address=:'+str(gitport)],
+    logf = open(_logfilename('githttpd'), WRITE_MODE)
+    agh = subprocess.Popen(['arvados-server', 'git-httpd'],
         env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
-    with open(_pidfile('arv-git-httpd'), 'w') as f:
+    with open(_pidfile('githttpd'), 'w') as f:
         f.write(str(agh.pid))
-    _setport('arv-git-httpd', gitport)
     _wait_until_port_listens(gitport)
 
 def stop_arv_git_httpd():
     if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
         return
-    kill_server_pid(_pidfile('arv-git-httpd'))
+    kill_server_pid(_pidfile('githttpd'))
 
 def run_keep_web():
     if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
         return
     stop_keep_web()
 
-    keepwebport = find_available_port()
+    keepwebport = internal_port_from_config("WebDAV")
     env = os.environ.copy()
-    env['ARVADOS_API_TOKEN'] = auth_token('anonymous')
-    logf = open(_logfilename('keep-web'), 'a')
+    logf = open(_logfilename('keep-web'), WRITE_MODE)
     keepweb = subprocess.Popen(
-        ['keep-web',
-         '-allow-anonymous',
-         '-attachment-only-host=download',
-         '-management-token=e687950a23c3a9bceec28c6223a06c79',
-         '-listen=:'+str(keepwebport)],
+        ['arvados-server', 'keep-web'],
         env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
     with open(_pidfile('keep-web'), 'w') as f:
         f.write(str(keepweb.pid))
-    _setport('keep-web', keepwebport)
     _wait_until_port_listens(keepwebport)
 
 def stop_keep_web():
@@ -677,28 +635,36 @@ def run_nginx():
         return
     stop_nginx()
     nginxconf = {}
-    nginxconf['CONTROLLERPORT'] = _getport('controller')
-    nginxconf['CONTROLLERSSLPORT'] = find_available_port()
-    nginxconf['KEEPWEBPORT'] = _getport('keep-web')
-    nginxconf['KEEPWEBDLSSLPORT'] = find_available_port()
-    nginxconf['KEEPWEBSSLPORT'] = find_available_port()
-    nginxconf['KEEPPROXYPORT'] = _getport('keepproxy')
-    nginxconf['KEEPPROXYSSLPORT'] = find_available_port()
-    nginxconf['GITPORT'] = _getport('arv-git-httpd')
-    nginxconf['GITSSLPORT'] = find_available_port()
-    nginxconf['WSPORT'] = _getport('ws')
-    nginxconf['WSSPORT'] = _getport('wss')
+    nginxconf['LISTENHOST'] = 'localhost'
+    nginxconf['CONTROLLERPORT'] = internal_port_from_config("Controller")
+    nginxconf['ARVADOS_API_HOST'] = "0.0.0.0:" + str(external_port_from_config("Controller"))
+    nginxconf['CONTROLLERSSLPORT'] = external_port_from_config("Controller")
+    nginxconf['KEEPWEBPORT'] = internal_port_from_config("WebDAV")
+    nginxconf['KEEPWEBDLSSLPORT'] = external_port_from_config("WebDAVDownload")
+    nginxconf['KEEPWEBSSLPORT'] = external_port_from_config("WebDAV")
+    nginxconf['KEEPPROXYPORT'] = internal_port_from_config("Keepproxy")
+    nginxconf['KEEPPROXYSSLPORT'] = external_port_from_config("Keepproxy")
+    nginxconf['GITPORT'] = internal_port_from_config("GitHTTP")
+    nginxconf['GITSSLPORT'] = external_port_from_config("GitHTTP")
+    nginxconf['HEALTHPORT'] = internal_port_from_config("Health")
+    nginxconf['HEALTHSSLPORT'] = external_port_from_config("Health")
+    nginxconf['WSPORT'] = internal_port_from_config("Websocket")
+    nginxconf['WSSSLPORT'] = external_port_from_config("Websocket")
+    nginxconf['WORKBENCH1PORT'] = internal_port_from_config("Workbench1")
+    nginxconf['WORKBENCH1SSLPORT'] = external_port_from_config("Workbench1")
+    nginxconf['WORKBENCH2PORT'] = internal_port_from_config("Workbench2")
+    nginxconf['WORKBENCH2SSLPORT'] = external_port_from_config("Workbench2")
     nginxconf['SSLCERT'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem')
     nginxconf['SSLKEY'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.key')
     nginxconf['ACCESSLOG'] = _logfilename('nginx_access')
     nginxconf['ERRORLOG'] = _logfilename('nginx_error')
-    nginxconf['TMPDIR'] = TEST_TMPDIR
+    nginxconf['TMPDIR'] = TEST_TMPDIR + '/nginx'
 
     conftemplatefile = os.path.join(MY_DIRNAME, 'nginx.conf')
     conffile = os.path.join(TEST_TMPDIR, 'nginx.conf')
     with open(conffile, 'w') as f:
         f.write(re.sub(
-            r'{{([A-Z]+)}}',
+            r'{{([A-Z]+[A-Z0-9]+)}}',
             lambda match: str(nginxconf.get(match.group(1))),
             open(conftemplatefile).read()))
 
@@ -707,15 +673,191 @@ def run_nginx():
 
     nginx = subprocess.Popen(
         ['nginx',
-         '-g', 'error_log stderr info;',
-         '-g', 'pid '+_pidfile('nginx')+';',
+         '-g', 'error_log stderr info; pid '+_pidfile('nginx')+';',
          '-c', conffile],
         env=env, stdin=open('/dev/null'), stdout=sys.stderr)
-    _setport('controller-ssl', nginxconf['CONTROLLERSSLPORT'])
-    _setport('keep-web-dl-ssl', nginxconf['KEEPWEBDLSSLPORT'])
-    _setport('keep-web-ssl', nginxconf['KEEPWEBSSLPORT'])
-    _setport('keepproxy-ssl', nginxconf['KEEPPROXYSSLPORT'])
-    _setport('arv-git-httpd-ssl', nginxconf['GITSSLPORT'])
+    _wait_until_port_listens(nginxconf['CONTROLLERSSLPORT'])
+
+def setup_config():
+    rails_api_port = find_available_port()
+    controller_port = find_available_port()
+    controller_external_port = find_available_port()
+    websocket_port = find_available_port()
+    websocket_external_port = find_available_port()
+    workbench1_port = find_available_port()
+    workbench1_external_port = find_available_port()
+    workbench2_port = find_available_port()
+    workbench2_external_port = find_available_port()
+    git_httpd_port = find_available_port()
+    git_httpd_external_port = find_available_port()
+    health_httpd_port = find_available_port()
+    health_httpd_external_port = find_available_port()
+    keepproxy_port = find_available_port()
+    keepproxy_external_port = find_available_port()
+    keepstore_ports = sorted([str(find_available_port()) for _ in range(0,4)])
+    keep_web_port = find_available_port()
+    keep_web_external_port = find_available_port()
+    keep_web_dl_external_port = find_available_port()
+
+    configsrc = os.environ.get("CONFIGSRC", None)
+    if configsrc:
+        clusterconf = os.path.join(configsrc, "config.yml")
+        print("Getting config from %s" % clusterconf, file=sys.stderr)
+        pgconnection = yaml.safe_load(open(clusterconf))["Clusters"]["zzzzz"]["PostgreSQL"]["Connection"]
+    else:
+        # assume "arvados-server install -type test" has set up the
+        # conventional db credentials
+        pgconnection = {
+           "client_encoding": "utf8",
+           "host": "localhost",
+           "dbname": "arvados_test",
+           "user": "arvados",
+           "password": "insecure_arvados_test",
+        }
+
+    localhost = "127.0.0.1"
+    services = {
+        "RailsAPI": {
+            "InternalURLs": {
+                "https://%s:%s"%(localhost, rails_api_port): {},
+            },
+        },
+        "Controller": {
+            "ExternalURL": "https://%s:%s" % (localhost, controller_external_port),
+            "InternalURLs": {
+                "http://%s:%s"%(localhost, controller_port): {},
+            },
+        },
+        "Websocket": {
+            "ExternalURL": "wss://%s:%s/websocket" % (localhost, websocket_external_port),
+            "InternalURLs": {
+                "http://%s:%s"%(localhost, websocket_port): {},
+            },
+        },
+        "Workbench1": {
+            "ExternalURL": "https://%s:%s/" % (localhost, workbench1_external_port),
+            "InternalURLs": {
+                "http://%s:%s"%(localhost, workbench1_port): {},
+            },
+        },
+        "Workbench2": {
+            "ExternalURL": "https://%s:%s/" % (localhost, workbench2_external_port),
+            "InternalURLs": {
+                "http://%s:%s"%(localhost, workbench2_port): {},
+            },
+        },
+        "GitHTTP": {
+            "ExternalURL": "https://%s:%s" % (localhost, git_httpd_external_port),
+            "InternalURLs": {
+                "http://%s:%s"%(localhost, git_httpd_port): {}
+            },
+        },
+        "Health": {
+            "ExternalURL": "https://%s:%s" % (localhost, health_httpd_external_port),
+            "InternalURLs": {
+                "http://%s:%s"%(localhost, health_httpd_port): {}
+            },
+        },
+        "Keepstore": {
+            "InternalURLs": {
+                "http://%s:%s"%(localhost, port): {} for port in keepstore_ports
+            },
+        },
+        "Keepproxy": {
+            "ExternalURL": "https://%s:%s" % (localhost, keepproxy_external_port),
+            "InternalURLs": {
+                "http://%s:%s"%(localhost, keepproxy_port): {},
+            },
+        },
+        "WebDAV": {
+            "ExternalURL": "https://%s:%s" % (localhost, keep_web_external_port),
+            "InternalURLs": {
+                "http://%s:%s"%(localhost, keep_web_port): {},
+            },
+        },
+        "WebDAVDownload": {
+            "ExternalURL": "https://%s:%s" % (localhost, keep_web_dl_external_port),
+            "InternalURLs": {
+                "http://%s:%s"%(localhost, keep_web_port): {},
+            },
+        },
+    }
+
+    config = {
+        "Clusters": {
+            "zzzzz": {
+                "ManagementToken": "e687950a23c3a9bceec28c6223a06c79",
+                "SystemRootToken": auth_token('system_user'),
+                "API": {
+                    "RequestTimeout": "30s",
+                },
+                "Login": {
+                    "Test": {
+                        "Enable": True,
+                        "Users": {
+                            "alice": {
+                                "Email": "alice@example.com",
+                                "Password": "xyzzy"
+                            }
+                        }
+                    },
+                },
+                "SystemLogs": {
+                    "LogLevel": ('info' if os.environ.get('ARVADOS_DEBUG', '') in ['','0'] else 'debug'),
+                },
+                "PostgreSQL": {
+                    "Connection": pgconnection,
+                },
+                "TLS": {
+                    "Insecure": True,
+                },
+                "Services": services,
+                "Users": {
+                    "AnonymousUserToken": auth_token('anonymous'),
+                    "UserProfileNotificationAddress": "arvados@example.com",
+                },
+                "Collections": {
+                    "CollectionVersioning": True,
+                    "BlobSigningKey": "zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc",
+                    "TrustAllContent": False,
+                    "ForwardSlashNameSubstitution": "/",
+                    "TrashSweepInterval": "-1s",
+                },
+                "Git": {
+                    "Repositories": os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'git', 'test'),
+                },
+                "Containers": {
+                    "JobsAPI": {
+                        "GitInternalDir": os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'internal.git'),
+                    },
+                    "SupportedDockerImageFormats": {"v1": {}},
+                    "ShellAccess": {
+                        "Admin": True,
+                        "User": True,
+                    },
+                },
+                "Volumes": {
+                    "zzzzz-nyw5e-%015d"%n: {
+                        "AccessViaHosts": {
+                            "http://%s:%s" % (localhost, keepstore_ports[n]): {},
+                        },
+                        "Driver": "Directory",
+                        "DriverParameters": {
+                            "Root": os.path.join(TEST_TMPDIR, "keep%d.data"%n),
+                        },
+                    } for n in range(len(keepstore_ports))
+                },
+            },
+        },
+    }
+
+    conf = os.path.join(TEST_TMPDIR, 'arvados.yml')
+    with open(conf, 'w') as f:
+        yaml.safe_dump(config, f)
+
+    ex = "export ARVADOS_CONFIG="+conf
+    print(ex)
+
 
 def stop_nginx():
     if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
@@ -725,45 +867,6 @@ def stop_nginx():
 def _pidfile(program):
     return os.path.join(TEST_TMPDIR, program + '.pid')
 
-def _portfile(program):
-    return os.path.join(TEST_TMPDIR, program + '.port')
-
-def _setport(program, port):
-    with open(_portfile(program), 'w') as f:
-        f.write(str(port))
-
-# Returns 9 if program is not up.
-def _getport(program):
-    try:
-        with open(_portfile(program)) as prog:
-            return int(prog.read())
-    except IOError:
-        return 9
-
-def _dbconfig(key):
-    global _cached_db_config
-    if not _cached_db_config:
-        _cached_db_config = yaml.safe_load(open(os.path.join(
-            SERVICES_SRC_DIR, 'api', 'config', 'database.yml')))
-    return _cached_db_config['test'][key]
-
-def _apiconfig(key):
-    global _cached_config
-    if _cached_config:
-        return _cached_config[key]
-    def _load(f, required=True):
-        fullpath = os.path.join(SERVICES_SRC_DIR, 'api', 'config', f)
-        if not required and not os.path.exists(fullpath):
-            return {}
-        return yaml.safe_load(fullpath)
-    cdefault = _load('application.default.yml')
-    csite = _load('application.yml', required=False)
-    _cached_config = {}
-    for section in [cdefault.get('common',{}), cdefault.get('test',{}),
-                    csite.get('common',{}), csite.get('test',{})]:
-        _cached_config.update(section)
-    return _cached_config[key]
-
 def fixture(fix):
     '''load a fixture yaml file'''
     with open(os.path.join(SERVICES_SRC_DIR, 'api', "test", "fixtures",
@@ -855,14 +958,14 @@ if __name__ == "__main__":
         'start_keep', 'stop_keep',
         'start_keep_proxy', 'stop_keep_proxy',
         'start_keep-web', 'stop_keep-web',
-        'start_arv-git-httpd', 'stop_arv-git-httpd',
-        'start_nginx', 'stop_nginx',
+        'start_githttpd', 'stop_githttpd',
+        'start_nginx', 'stop_nginx', 'setup_config',
     ]
     parser = argparse.ArgumentParser()
     parser.add_argument('action', type=str, help="one of {}".format(actions))
     parser.add_argument('--auth', type=str, metavar='FIXTURE_NAME', help='Print authorization info for given api_client_authorizations fixture')
     parser.add_argument('--num-keep-servers', metavar='int', type=int, default=2, help="Number of keep servers desired")
-    parser.add_argument('--keep-enforce-permissions', action="store_true", help="Enforce keep permissions")
+    parser.add_argument('--keep-blob-signing', action="store_true", help="Enable blob signing for keepstore servers")
 
     args = parser.parse_args()
 
@@ -893,16 +996,16 @@ if __name__ == "__main__":
     elif args.action == 'stop_controller':
         stop_controller()
     elif args.action == 'start_keep':
-        run_keep(enforce_permissions=args.keep_enforce_permissions, num_servers=args.num_keep_servers)
+        run_keep(blob_signing=args.keep_blob_signing, num_servers=args.num_keep_servers)
     elif args.action == 'stop_keep':
         stop_keep(num_servers=args.num_keep_servers)
     elif args.action == 'start_keep_proxy':
         run_keep_proxy()
     elif args.action == 'stop_keep_proxy':
         stop_keep_proxy()
-    elif args.action == 'start_arv-git-httpd':
+    elif args.action == 'start_githttpd':
         run_arv_git_httpd()
-    elif args.action == 'stop_arv-git-httpd':
+    elif args.action == 'stop_githttpd':
         stop_arv_git_httpd()
     elif args.action == 'start_keep-web':
         run_keep_web()
@@ -910,8 +1013,10 @@ if __name__ == "__main__":
         stop_keep_web()
     elif args.action == 'start_nginx':
         run_nginx()
-        print("export ARVADOS_API_HOST=0.0.0.0:{}".format(_getport('controller-ssl')))
+        print("export ARVADOS_API_HOST=0.0.0.0:{}".format(external_port_from_config('Controller')))
     elif args.action == 'stop_nginx':
         stop_nginx()
+    elif args.action == 'setup_config':
+        setup_config()
     else:
         raise Exception("action recognized but not implemented!?")