X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/2f66d4cc05e9442a9bb69969744d0750a02a1ed4..8e7e7f4a4972c44f2f4a4692953bfbe35ebcdf84:/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 262b9d2a2c..787837b723 100644 --- a/sdk/python/tests/run_test_server.py +++ b/sdk/python/tests/run_test_server.py @@ -2,23 +2,18 @@ # # SPDX-License-Identifier: Apache-2.0 -from __future__ import print_function -from __future__ import division -from builtins import str -from builtins import range import argparse import atexit import errno import glob import httplib2 import os -import pipes import random import re +import shlex import shutil import signal import socket -import string import subprocess import sys import tempfile @@ -26,10 +21,7 @@ import time import unittest import yaml -try: - from urllib.parse import urlparse -except ImportError: - from urlparse import urlparse +from urllib.parse import urlparse MY_DIRNAME = os.path.dirname(os.path.realpath(__file__)) if __name__ == '__main__' and os.path.exists( @@ -41,8 +33,25 @@ if __name__ == '__main__' and os.path.exists( import arvados import arvados.config +# This module starts subprocesses and records them in pidfiles so they +# can be managed by other processes (incl. after this process +# exits). But if we don't keep a reference to each subprocess object +# somewhere, the subprocess destructor runs, and we get a lot of +# ResourceWarning noise in test logs. This is our bucket of subprocess +# objects whose destructors we don't want to run but are otherwise +# unneeded. +_detachedSubprocesses = [] + 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 @@ -65,6 +74,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() @@ -173,11 +183,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. @@ -235,14 +249,17 @@ def _logfilename(label): stdbuf+['cat', fifo], stdin=open('/dev/null'), stdout=subprocess.PIPE) + _detachedSubprocesses.append(cat) tee = subprocess.Popen( stdbuf+['tee', '-a', logfilename], stdin=cat.stdout, stdout=subprocess.PIPE) - subprocess.Popen( + _detachedSubprocesses.append(tee) + sed = subprocess.Popen( stdbuf+['sed', '-e', 's/^/['+label+'] /'], stdin=tee.stdout, stdout=sys.stderr) + _detachedSubprocesses.append(sed) return fifo def run(leave_running_atexit=False): @@ -318,6 +335,19 @@ def run(leave_running_atexit=False): os.makedirs(gitdir) subprocess.check_output(['tar', '-xC', gitdir, '-f', gittarball]) + # 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(r'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' @@ -327,16 +357,21 @@ def run(leave_running_atexit=False): env.pop('ARVADOS_API_HOST', None) env.pop('ARVADOS_API_HOST_INSECURE', None) env.pop('ARVADOS_API_TOKEN', None) - logf = open(_logfilename('railsapi'), 'a') + logf = open(_logfilename('railsapi'), WRITE_MODE) railsapi = subprocess.Popen( ['bundle', 'exec', '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', '/dev/stdout', '--ssl', '--ssl-certificate', 'tmp/self-signed.pem', '--ssl-certificate-key', 'tmp/self-signed.key'], env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf) + _detachedSubprocesses.append(railsapi) if not leave_running_atexit: atexit.register(kill_server_pid, pid_file, passenger_root=api_src_dir) @@ -409,11 +444,12 @@ def run_controller(): if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ: return stop_controller() - logf = open(_logfilename('controller'), 'a') + logf = open(_logfilename('controller'), WRITE_MODE) port = internal_port_from_config("Controller") controller = subprocess.Popen( ["arvados-server", "controller"], stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True) + _detachedSubprocesses.append(controller) with open(_pidfile('controller'), 'w') as f: f.write(str(controller.pid)) _wait_until_port_listens(port) @@ -429,9 +465,11 @@ def run_ws(): return stop_ws() port = internal_port_from_config("Websocket") - logf = open(_logfilename('ws'), 'a') - ws = subprocess.Popen(["ws"], + logf = open(_logfilename('ws'), WRITE_MODE) + ws = subprocess.Popen( + ["arvados-server", "ws"], stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True) + _detachedSubprocesses.append(ws) with open(_pidfile('ws'), 'w') as f: f.write(str(ws.pid)) _wait_until_port_listens(port) @@ -459,12 +497,13 @@ def _start_keep(n, blob_signing=False): confdata['Clusters']['zzzzz']['Collections']['BlobSigning'] = blob_signing with open(conf, 'w') as f: yaml.safe_dump(confdata, f) - keep_cmd = ["keepstore", "-config", conf] + 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: child = subprocess.Popen( keep_cmd, stdin=_stdin, stdout=logf, stderr=logf, close_fds=True) + _detachedSubprocesses.append(child) print('child.pid is %d'%child.pid, file=sys.stderr) with open(_pidfile('keep{}'.format(n)), 'w') as f: @@ -504,7 +543,7 @@ def run_keep(num_servers=2, **kwargs): # 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: @@ -528,9 +567,10 @@ def run_keep_proxy(): 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'], 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) + _detachedSubprocesses.append(kp) with open(_pidfile('keepproxy'), 'w') as f: f.write(str(kp.pid)) @@ -567,17 +607,18 @@ def run_arv_git_httpd(): 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'], + 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: + _detachedSubprocesses.append(agh) + with open(_pidfile('githttpd'), 'w') as f: f.write(str(agh.pid)) _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: @@ -586,10 +627,11 @@ def run_keep_web(): keepwebport = internal_port_from_config("WebDAV") env = os.environ.copy() - logf = open(_logfilename('keep-web'), 'a') + logf = open(_logfilename('keep-web'), WRITE_MODE) keepweb = subprocess.Popen( - ['keep-web'], + ['arvados-server', 'keep-web'], env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf) + _detachedSubprocesses.append(keepweb) with open(_pidfile('keep-web'), 'w') as f: f.write(str(keepweb.pid)) _wait_until_port_listens(keepwebport) @@ -604,8 +646,10 @@ def run_nginx(): return stop_nginx() nginxconf = {} - nginxconf['LISTENHOST'] = 'localhost' + nginxconf['UPSTREAMHOST'] = '127.0.0.1' + nginxconf['LISTENHOST'] = '127.0.0.1' 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") @@ -614,15 +658,19 @@ def run_nginx(): 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' + nginxconf['INTERNALSUBNETS'] = '169.254.0.0/16 0;' conftemplatefile = os.path.join(MY_DIRNAME, 'nginx.conf') conffile = os.path.join(TEST_TMPDIR, 'nginx.conf') @@ -637,10 +685,10 @@ 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) + _detachedSubprocesses.append(nginx) _wait_until_port_listens(nginxconf['CONTROLLERSSLPORT']) def setup_config(): @@ -649,23 +697,35 @@ def setup_config(): 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 xrange(0,4)]) + 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_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"] + 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 = { @@ -688,8 +748,11 @@ def setup_config(): }, "Workbench1": { "ExternalURL": "https://%s:%s/" % (localhost, workbench1_external_port), + }, + "Workbench2": { + "ExternalURL": "https://%s:%s/" % (localhost, workbench2_external_port), "InternalURLs": { - "http://%s:%s"%(localhost, workbench1_port): {}, + "http://%s:%s"%(localhost, workbench2_port): {}, }, }, "GitHTTP": { @@ -698,6 +761,12 @@ def setup_config(): "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 @@ -718,12 +787,9 @@ def setup_config(): "WebDAVDownload": { "ExternalURL": "https://%s:%s" % (localhost, keep_web_dl_external_port), "InternalURLs": { - "http://%s:%s"%(localhost, keep_web_dl_port): {}, + "http://%s:%s"%(localhost, keep_web_port): {}, }, }, - "SSO": { - "ExternalURL": "http://localhost:3002", - }, } config = { @@ -733,11 +799,18 @@ def setup_config(): "SystemRootToken": auth_token('system_user'), "API": { "RequestTimeout": "30s", - "RailsSessionSecretToken": "e24205c490ac07e028fd5f8a692dcb398bcd654eff1aef5f9fe6891994b18483", + "LockBeforeUpdate": True, }, "Login": { - "ProviderAppID": "arvados-server", - "ProviderAppSecret": "608dbf356a327e2d0d4932b60161e212c2d8d8f5e25690d7b622f850a990cd33", + "Test": { + "Enable": True, + "Users": { + "alice": { + "Email": "alice@example.com", + "Password": "xyzzy" + } + } + }, }, "SystemLogs": { "LogLevel": ('info' if os.environ.get('ARVADOS_DEBUG', '') in ['','0'] else 'debug'), @@ -754,6 +827,7 @@ def setup_config(): "UserProfileNotificationAddress": "arvados@example.com", }, "Collections": { + "CollectionVersioning": True, "BlobSigningKey": "zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc", "TrustAllContent": False, "ForwardSlashNameSubstitution": "/", @@ -766,7 +840,15 @@ def setup_config(): "JobsAPI": { "GitInternalDir": os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'internal.git'), }, + "LocalKeepBlobBuffersPerVCPU": 0, + "Logging": { + "SweepInterval": 0, # disable, otherwise test cases can't acquire dblock + }, "SupportedDockerImageFormats": {"v1": {}}, + "ShellAccess": { + "Admin": True, + "User": True, + }, }, "Volumes": { "zzzzz-nyw5e-%015d"%n: { @@ -855,7 +937,6 @@ class TestCaseWithServers(unittest.TestCase): cls._orig_config = arvados.config.settings().copy() cls._cleanup_funcs = [] os.environ.pop('ARVADOS_KEEP_SERVICES', None) - os.environ.pop('ARVADOS_EXTERNAL_CLIENT', None) for server_kwargs, start_func, stop_func in ( (cls.MAIN_SERVER, run, reset), (cls.WS_SERVER, run_ws, stop_ws), @@ -890,7 +971,7 @@ 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_githttpd', 'stop_githttpd', 'start_nginx', 'stop_nginx', 'setup_config', ] parser = argparse.ArgumentParser() @@ -906,14 +987,17 @@ if __name__ == "__main__": format(args.action, actions), file=sys.stderr) sys.exit(1) + # Create a new process group so our child processes don't exit on + # ^C in run-tests.sh interactive mode. + os.setpgid(0, 0) if args.action == 'start': stop(force=('ARVADOS_TEST_API_HOST' not in os.environ)) run(leave_running_atexit=True) host = os.environ['ARVADOS_API_HOST'] if args.auth is not None: token = auth_token(args.auth) - print("export ARVADOS_API_TOKEN={}".format(pipes.quote(token))) - print("export ARVADOS_API_HOST={}".format(pipes.quote(host))) + print("export ARVADOS_API_TOKEN={}".format(shlex.quote(token))) + print("export ARVADOS_API_HOST={}".format(shlex.quote(host))) print("export ARVADOS_API_HOST_INSECURE=true") else: print(host) @@ -935,9 +1019,9 @@ if __name__ == "__main__": 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()