Merge branch 'master' into 14715-keepprox-config
[arvados.git] / sdk / python / tests / run_test_server.py
index 6687ca491a769140aa8c803a5fd2b1a6ce3b1850..e79b4843a268049e117d60e59a37a8de62e61fbb 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')):
@@ -181,22 +186,18 @@ def _wait_until_port_listens(port, timeout=10, warn=True):
     in seconds), print a warning on stderr before returning.
     """
     try:
-        subprocess.check_output(['which', 'lsof'])
+        subprocess.check_output(['which', 'netstat'])
     except subprocess.CalledProcessError:
-        print("WARNING: No `lsof` -- cannot wait for port to listen. "+
+        print("WARNING: No `netstat` -- cannot wait for port to listen. "+
               "Sleeping 0.5 and hoping for the best.",
               file=sys.stderr)
         time.sleep(0.5)
         return
     deadline = time.time() + timeout
     while time.time() < deadline:
-        try:
-            subprocess.check_output(
-                ['lsof', '-t', '-i', 'tcp:'+str(port)])
-        except subprocess.CalledProcessError:
-            time.sleep(0.1)
-            continue
-        return True
+        if re.search(r'\ntcp.*:'+str(port)+' .* LISTEN *\n', subprocess.check_output(['netstat', '-Wln']).decode()):
+            return True
+        time.sleep(0.1)
     if warn:
         print(
             "WARNING: Nothing is listening on port {} (waited {} seconds).".
@@ -317,15 +318,9 @@ 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()
+    port = internal_port_from_config("RailsAPI")
     env = os.environ.copy()
     env['RAILS_ENV'] = 'test'
-    env['ARVADOS_TEST_WSS_PORT'] = str(wss_port)
     env.pop('ARVADOS_WEBSOCKETS', None)
     env.pop('ARVADOS_TEST_API_HOST', None)
     env.pop('ARVADOS_API_HOST', None)
@@ -375,13 +370,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.
@@ -402,49 +395,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):
+    return int(urlparse(
+        list(get_config()["Clusters"]["zzzzz"]["Services"][service]["InternalURLs"].keys())[0]).
+               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:
-    HTTPRequestTimeout: 30s
-    PostgreSQL:
-      ConnectionPool: 32
-      Connection:
-        host: {}
-        dbname: {}
-        user: {}
-        password: {}
-    NodeProfiles:
-      "*":
-        "arvados-controller":
-          Listen: ":{}"
-        "arvados-api-server":
-          Listen: ":{}"
-          TLS: true
-          Insecure: true
-        """.format(
-            _dbconfig('host'),
-            _dbconfig('database'),
-            _dbconfig('username'),
-            _dbconfig('password'),
-            port,
-            rails_api_port,
-        ))
     logf = open(_logfilename('controller'), 'a')
+    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():
@@ -456,36 +430,13 @@ 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')))
+    port = internal_port_from_config("Websocket")
     logf = open(_logfilename('ws'), 'a')
-    ws = subprocess.Popen(
-        ["ws", "-config", conf],
+    ws = subprocess.Popen(["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():
@@ -504,9 +455,10 @@ def _start_keep(n, keep_args):
     for arg, val in keep_args.items():
         keep_cmd.append("{}={}".format(arg, val))
 
-    logf = open(_logfilename('keep{}'.format(n)), 'a')
-    kp0 = subprocess.Popen(
-        keep_cmd, stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
+    with open(_logfilename('keep{}'.format(n)), 'a') as logf:
+        with open('/dev/null') as _stdin:
+            kp0 = subprocess.Popen(
+                keep_cmd, stdin=_stdin, stdout=logf, stderr=logf, close_fds=True)
 
     with open(_pidfile('keep{}'.format(n)), 'w') as f:
         f.write(str(kp0.pid))
@@ -563,7 +515,8 @@ def run_keep(blob_signing_key=None, enforce_permissions=False, num_servers=2):
         pidfile = _pidfile('keepproxy')
         if os.path.exists(pidfile):
             try:
-                os.kill(int(open(pidfile).read()), signal.SIGHUP)
+                with open(pidfile) as pid:
+                    os.kill(int(pid.read()), signal.SIGHUP)
             except OSError:
                 os.remove(pidfile)
 
@@ -582,20 +535,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')
     kp = subprocess.Popen(
-        ['keepproxy',
-         '-pid='+_pidfile('keepproxy'),
-         '-listen=:{}'.format(port)],
-        env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
+        ['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'],
@@ -611,7 +566,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,18 +579,18 @@ def run_arv_git_httpd():
     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)],
         env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
     with open(_pidfile('arv-git-httpd'), 'w') as f:
         f.write(str(agh.pid))
-    _setport('arv-git-httpd', gitport)
     _wait_until_port_listens(gitport)
 
 def stop_arv_git_httpd():
@@ -649,19 +603,14 @@ def run_keep_web():
         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')
     keepweb = subprocess.Popen(
-        ['keep-web',
-         '-allow-anonymous',
-         '-attachment-only-host=download',
-         '-listen=:'+str(keepwebport)],
+        ['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():
@@ -674,17 +623,17 @@ 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['CONTROLLERPORT'] = internal_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['WSPORT'] = internal_port_from_config("Websocket")
+    nginxconf['WSSPORT'] = external_port_from_config("Websocket")
     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')
@@ -708,11 +657,108 @@ def run_nginx():
          '-g', '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'])
+
+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()
+    git_httpd_port = find_available_port()
+    git_httpd_external_port = find_available_port()
+    keepproxy_port = find_available_port()
+    keepproxy_external_port = find_available_port()
+    keep_web_port = find_available_port()
+    keep_web_external_port = find_available_port()
+    keep_web_dl_port = find_available_port()
+    keep_web_dl_external_port = find_available_port()
+
+    dbconf = os.path.join(os.environ["CONFIGSRC"], "config.yml")
+
+    print("Getting config from %s" % dbconf, file=sys.stderr)
+
+    pgconnection = yaml.safe_load(open(dbconf))["Clusters"]["zzzzz"]["PostgreSQL"]["Connection"]
+
+    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): {}
+            }
+        },
+        "GitHTTP": {
+            "ExternalURL": "https://%s:%s" % (localhost, git_httpd_external_port),
+            "InternalURLs": {
+                "http://%s:%s"%(localhost, git_httpd_port): {}
+            }
+        },
+        "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_dl_port): {}
+            }
+        }
+    }
+
+    config = {
+        "Clusters": {
+            "zzzzz": {
+                "EnableBetaController14287": ('14287' in os.environ.get('ARVADOS_EXPERIMENTAL', '')),
+                "ManagementToken": "e687950a23c3a9bceec28c6223a06c79",
+                "API": {
+                    "RequestTimeout": "30s"
+                },
+                "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')
+                },
+                "Collections": {
+                    "TrustAllContent": True
+                }
+            }
+        }
+    }
+
+    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:
@@ -722,44 +768,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:
-        return int(open(_portfile(program)).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",
@@ -852,7 +860,7 @@ if __name__ == "__main__":
         '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_nginx', 'stop_nginx', 'setup_config',
     ]
     parser = argparse.ArgumentParser()
     parser.add_argument('action', type=str, help="one of {}".format(actions))
@@ -906,8 +914,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!?")