1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: Apache-2.0
5 from __future__ import print_function
6 from __future__ import division
7 from builtins import str
8 from builtins import range
30 from urllib.parse import urlparse
32 from urlparse import urlparse
34 MY_DIRNAME = os.path.dirname(os.path.realpath(__file__))
35 if __name__ == '__main__' and os.path.exists(
36 os.path.join(MY_DIRNAME, '..', 'arvados', '__init__.py')):
37 # We're being launched to support another test suite.
38 # Add the Python SDK source to the library path.
39 sys.path.insert(1, os.path.dirname(MY_DIRNAME))
44 ARVADOS_DIR = os.path.realpath(os.path.join(MY_DIRNAME, '../../..'))
45 SERVICES_SRC_DIR = os.path.join(ARVADOS_DIR, 'services')
47 # Work around https://bugs.python.org/issue27805, should be no longer
48 # necessary from sometime in Python 3.8.x
49 if not os.environ.get('ARVADOS_DEBUG', ''):
54 if 'GOPATH' in os.environ:
55 # Add all GOPATH bin dirs to PATH -- but insert them after the
56 # ruby gems bin dir, to ensure "bundle" runs the Ruby bundler
57 # command, not the golang.org/x/tools/cmd/bundle command.
58 gopaths = os.environ['GOPATH'].split(':')
59 addbins = [os.path.join(path, 'bin') for path in gopaths]
61 for path in os.environ['PATH'].split(':'):
63 if os.path.exists(os.path.join(path, 'bundle')):
67 os.environ['PATH'] = ':'.join(newbins)
69 TEST_TMPDIR = os.path.join(ARVADOS_DIR, 'tmp')
70 if not os.path.exists(TEST_TMPDIR):
75 _cached_db_config = {}
76 _already_used_port = {}
78 def find_server_pid(PID_PATH, wait=10):
82 while (not good_pid) and (now <= timeout):
85 with open(PID_PATH, 'r') as f:
86 server_pid = int(f.read())
87 good_pid = (os.kill(server_pid, 0) is None)
88 except EnvironmentError:
97 def kill_server_pid(pidfile, wait=10, passenger_root=False):
98 # Must re-import modules in order to work during atexit
106 deadline = now + wait
109 # First try to shut down nicely
110 restore_cwd = os.getcwd()
111 os.chdir(passenger_root)
113 'bundle', 'exec', 'passenger', 'stop', '--pid-file', pidfile])
114 os.chdir(restore_cwd)
115 # Use up to half of the +wait+ period waiting for "passenger
116 # stop" to work. If the process hasn't exited by then, start
117 # sending TERM signals.
121 while now <= deadline and server_pid is None:
123 with open(pidfile, 'r') as f:
124 server_pid = int(f.read())
126 # No pidfile = nothing to kill.
128 except ValueError as error:
129 # Pidfile exists, but we can't parse it. Perhaps the
130 # server has created the file but hasn't written its PID
132 print("Parse error reading pidfile {}: {}".format(pidfile, error),
137 while now <= deadline:
139 exited, _ = os.waitpid(server_pid, os.WNOHANG)
141 _remove_pidfile(pidfile)
144 # already exited, or isn't our child process
148 os.kill(server_pid, signal.SIGTERM)
149 print("Sent SIGTERM to {} ({})".format(server_pid, pidfile),
151 except OSError as error:
152 if error.errno == errno.ESRCH:
153 # Thrown by os.getpgid() or os.kill() if the process
154 # does not exist, i.e., our work here is done.
155 _remove_pidfile(pidfile)
161 print("Server PID {} ({}) did not exit, giving up after {}s".
162 format(server_pid, pidfile, wait),
165 def _remove_pidfile(pidfile):
169 if os.path.lexists(pidfile):
172 def find_available_port():
173 """Return an IPv4 port number that is not in use right now.
175 We assume whoever needs to use the returned port is able to reuse
176 a recently used port without waiting for TIME_WAIT (see
177 SO_REUSEADDR / SO_REUSEPORT).
179 Some opportunity for races here, but it's better than choosing
180 something at random and not checking at all. If all of our servers
181 (hey Passenger) knew that listening on port 0 was a thing, the OS
182 would take care of the races, and this wouldn't be needed at all.
185 global _already_used_port
187 sock = socket.socket()
188 sock.bind(('0.0.0.0', 0))
189 port = sock.getsockname()[1]
191 if port not in _already_used_port:
192 _already_used_port[port] = True
195 def _wait_until_port_listens(port, timeout=10, warn=True):
196 """Wait for a process to start listening on the given port.
198 If nothing listens on the port within the specified timeout (given
199 in seconds), print a warning on stderr before returning.
202 subprocess.check_output(['which', 'netstat'])
203 except subprocess.CalledProcessError:
204 print("WARNING: No `netstat` -- cannot wait for port to listen. "+
205 "Sleeping 0.5 and hoping for the best.",
209 deadline = time.time() + timeout
210 while time.time() < deadline:
211 if re.search(r'\ntcp.*:'+str(port)+' .* LISTEN *\n', subprocess.check_output(['netstat', '-Wln']).decode()):
216 "WARNING: Nothing is listening on port {} (waited {} seconds).".
217 format(port, timeout),
221 def _logfilename(label):
222 """Set up a labelled log file, and return a path to write logs to.
224 Normally, the returned path is {tmpdir}/{label}.log.
226 In debug mode, logs are also written to stderr, with [label]
227 prepended to each line. The returned path is a FIFO.
229 +label+ should contain only alphanumerics: it is also used as part
230 of the FIFO filename.
233 logfilename = os.path.join(TEST_TMPDIR, label+'.log')
234 if not os.environ.get('ARVADOS_DEBUG', ''):
236 fifo = os.path.join(TEST_TMPDIR, label+'.fifo')
239 except OSError as error:
240 if error.errno != errno.ENOENT:
242 os.mkfifo(fifo, 0o700)
243 stdbuf = ['stdbuf', '-i0', '-oL', '-eL']
244 # open(fifo, 'r') would block waiting for someone to open the fifo
245 # for writing, so we need a separate cat process to open it for
247 cat = subprocess.Popen(
248 stdbuf+['cat', fifo],
249 stdin=open('/dev/null'),
250 stdout=subprocess.PIPE)
251 tee = subprocess.Popen(
252 stdbuf+['tee', '-a', logfilename],
254 stdout=subprocess.PIPE)
256 stdbuf+['sed', '-e', 's/^/['+label+'] /'],
261 def run(leave_running_atexit=False):
262 """Ensure an API server is running, and ARVADOS_API_* env vars have
263 admin credentials for it.
265 If ARVADOS_TEST_API_HOST is set, a parent process has started a
266 test server for us to use: we just need to reset() it using the
269 If a previous call to run() started a new server process, and it
270 is still running, we just need to reset() it to fixture state and
273 If neither of those options work out, we'll really start a new
278 # Delete cached discovery documents.
280 # This will clear cached docs that belong to other processes (like
281 # concurrent test suites) even if they're still running. They should
282 # be able to tolerate that.
283 for fn in glob.glob(os.path.join(
284 str(arvados.http_cache('discovery')),
285 '*,arvados,v1,rest,*')):
288 pid_file = _pidfile('api')
289 pid_file_ok = find_server_pid(pid_file, 0)
291 existing_api_host = os.environ.get('ARVADOS_TEST_API_HOST', my_api_host)
292 if existing_api_host and pid_file_ok:
293 if existing_api_host == my_api_host:
297 # Fall through to shutdown-and-start case.
300 # Server was provided by parent. Can't recover if it's
304 # Before trying to start up our own server, call stop() to avoid
305 # "Phusion Passenger Standalone is already running on PID 12345".
306 # (If we've gotten this far, ARVADOS_TEST_API_HOST isn't set, so
307 # we know the server is ours to kill.)
310 restore_cwd = os.getcwd()
311 api_src_dir = os.path.join(SERVICES_SRC_DIR, 'api')
312 os.chdir(api_src_dir)
314 # Either we haven't started a server of our own yet, or it has
315 # died, or we have lost our credentials, or something else is
316 # preventing us from calling reset(). Start a new one.
318 if not os.path.exists('tmp'):
321 if not os.path.exists('tmp/api'):
322 os.makedirs('tmp/api')
324 if not os.path.exists('tmp/logs'):
325 os.makedirs('tmp/logs')
327 # Install the git repository fixtures.
328 gitdir = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'git')
329 gittarball = os.path.join(SERVICES_SRC_DIR, 'api', 'test', 'test.git.tar')
330 if not os.path.isdir(gitdir):
332 subprocess.check_output(['tar', '-xC', gitdir, '-f', gittarball])
334 # Customizing the passenger config template is the only documented
335 # way to override the default passenger_stat_throttle_rate (10 s).
336 # In the testing environment, we want restart.txt to take effect
338 resdir = subprocess.check_output(['bundle', 'exec', 'passenger-config', 'about', 'resourcesdir']).decode().rstrip()
339 with open(resdir + '/templates/standalone/config.erb') as f:
341 newtemplate = re.sub('http {', 'http {\n passenger_stat_throttle_rate 0;', template)
342 if newtemplate == template:
343 raise "template edit failed"
344 with open('tmp/passenger-nginx.conf.erb', 'w') as f:
347 port = internal_port_from_config("RailsAPI")
348 env = os.environ.copy()
349 env['RAILS_ENV'] = 'test'
350 env['ARVADOS_RAILS_LOG_TO_STDOUT'] = '1'
351 env.pop('ARVADOS_WEBSOCKETS', None)
352 env.pop('ARVADOS_TEST_API_HOST', None)
353 env.pop('ARVADOS_API_HOST', None)
354 env.pop('ARVADOS_API_HOST_INSECURE', None)
355 env.pop('ARVADOS_API_TOKEN', None)
356 logf = open(_logfilename('railsapi'), WRITE_MODE)
357 railsapi = subprocess.Popen(
359 'passenger', 'start', '-p{}'.format(port),
360 '--nginx-config-template', 'tmp/passenger-nginx.conf.erb',
361 '--no-friendly-error-pages',
362 '--disable-anonymous-telemetry',
363 '--disable-security-update-check',
364 '--pid-file', pid_file,
365 '--log-file', '/dev/stdout',
367 '--ssl-certificate', 'tmp/self-signed.pem',
368 '--ssl-certificate-key', 'tmp/self-signed.key'],
369 env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
371 if not leave_running_atexit:
372 atexit.register(kill_server_pid, pid_file, passenger_root=api_src_dir)
374 my_api_host = "127.0.0.1:"+str(port)
375 os.environ['ARVADOS_API_HOST'] = my_api_host
377 # Make sure the server has written its pid file and started
378 # listening on its TCP port
379 _wait_until_port_listens(port)
380 find_server_pid(pid_file)
383 os.chdir(restore_cwd)
386 """Reset the test server to fixture state.
388 This resets the ARVADOS_TEST_API_HOST provided by a parent process
389 if any, otherwise the server started by run().
391 It also resets ARVADOS_* environment vars to point to the test
392 server with admin credentials.
394 existing_api_host = os.environ.get('ARVADOS_TEST_API_HOST', my_api_host)
395 token = auth_token('admin')
396 httpclient = httplib2.Http(ca_certs=os.path.join(
397 SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem'))
399 'https://{}/database/reset'.format(existing_api_host),
401 headers={'Authorization': 'OAuth2 {}'.format(token), 'Connection':'close'})
403 os.environ['ARVADOS_API_HOST_INSECURE'] = 'true'
404 os.environ['ARVADOS_API_TOKEN'] = token
405 os.environ['ARVADOS_API_HOST'] = existing_api_host
407 def stop(force=False):
408 """Stop the API server, if one is running.
410 If force==False, kill it only if we started it ourselves. (This
411 supports the use case where a Python test suite calls run(), but
412 run() just uses the ARVADOS_TEST_API_HOST provided by the parent
413 process, and the test suite cleans up after itself by calling
414 stop(). In this case the test server provided by the parent
415 process should be left alone.)
417 If force==True, kill it even if we didn't start it
418 ourselves. (This supports the use case in __main__, where "run"
419 and "stop" happen in different processes.)
422 if force or my_api_host is not None:
423 kill_server_pid(_pidfile('api'))
427 with open(os.environ["ARVADOS_CONFIG"]) as f:
428 return yaml.safe_load(f)
430 def internal_port_from_config(service, idx=0):
432 sorted(list(get_config()["Clusters"]["zzzzz"]["Services"][service]["InternalURLs"].keys()))[idx]).
433 netloc.split(":")[1])
435 def external_port_from_config(service):
436 return int(urlparse(get_config()["Clusters"]["zzzzz"]["Services"][service]["ExternalURL"]).netloc.split(":")[1])
438 def run_controller():
439 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
442 logf = open(_logfilename('controller'), WRITE_MODE)
443 port = internal_port_from_config("Controller")
444 controller = subprocess.Popen(
445 ["arvados-server", "controller"],
446 stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
447 with open(_pidfile('controller'), 'w') as f:
448 f.write(str(controller.pid))
449 _wait_until_port_listens(port)
452 def stop_controller():
453 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
455 kill_server_pid(_pidfile('controller'))
458 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
461 port = internal_port_from_config("Websocket")
462 logf = open(_logfilename('ws'), WRITE_MODE)
463 ws = subprocess.Popen(
464 ["arvados-server", "ws"],
465 stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
466 with open(_pidfile('ws'), 'w') as f:
468 _wait_until_port_listens(port)
472 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
474 kill_server_pid(_pidfile('ws'))
476 def _start_keep(n, blob_signing=False):
477 datadir = os.path.join(TEST_TMPDIR, "keep%d.data"%n)
478 if os.path.exists(datadir):
479 shutil.rmtree(datadir)
481 port = internal_port_from_config("Keepstore", idx=n)
483 # Currently, if there are multiple InternalURLs for a single host,
484 # the only way to tell a keepstore process which one it's supposed
485 # to listen on is to supply a redacted version of the config, with
486 # the other InternalURLs removed.
487 conf = os.path.join(TEST_TMPDIR, "keep%d.yaml"%n)
488 confdata = get_config()
489 confdata['Clusters']['zzzzz']['Services']['Keepstore']['InternalURLs'] = {"http://127.0.0.1:%d"%port: {}}
490 confdata['Clusters']['zzzzz']['Collections']['BlobSigning'] = blob_signing
491 with open(conf, 'w') as f:
492 yaml.safe_dump(confdata, f)
493 keep_cmd = ["arvados-server", "keepstore", "-config", conf]
495 with open(_logfilename('keep{}'.format(n)), WRITE_MODE) as logf:
496 with open('/dev/null') as _stdin:
497 child = subprocess.Popen(
498 keep_cmd, stdin=_stdin, stdout=logf, stderr=logf, close_fds=True)
500 print('child.pid is %d'%child.pid, file=sys.stderr)
501 with open(_pidfile('keep{}'.format(n)), 'w') as f:
502 f.write(str(child.pid))
504 _wait_until_port_listens(port)
508 def run_keep(num_servers=2, **kwargs):
509 stop_keep(num_servers)
513 host=os.environ['ARVADOS_API_HOST'],
514 token=os.environ['ARVADOS_API_TOKEN'],
517 for d in api.keep_services().list(filters=[['service_type','=','disk']]).execute()['items']:
518 api.keep_services().delete(uuid=d['uuid']).execute()
519 for d in api.keep_disks().list().execute()['items']:
520 api.keep_disks().delete(uuid=d['uuid']).execute()
522 for d in range(0, num_servers):
523 port = _start_keep(d, **kwargs)
524 svc = api.keep_services().create(body={'keep_service': {
525 'uuid': 'zzzzz-bi6l4-keepdisk{:07d}'.format(d),
526 'service_host': 'localhost',
527 'service_port': port,
528 'service_type': 'disk',
529 'service_ssl_flag': False,
531 api.keep_disks().create(body={
532 'keep_disk': {'keep_service_uuid': svc['uuid'] }
535 # If keepproxy and/or keep-web is running, send SIGHUP to make
536 # them discover the new keepstore services.
537 for svc in ('keepproxy', 'keep-web'):
538 pidfile = _pidfile(svc)
539 if os.path.exists(pidfile):
541 with open(pidfile) as pid:
542 os.kill(int(pid.read()), signal.SIGHUP)
547 kill_server_pid(_pidfile('keep{}'.format(n)))
549 def stop_keep(num_servers=2):
550 for n in range(0, num_servers):
553 def run_keep_proxy():
554 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
555 os.environ["ARVADOS_KEEP_SERVICES"] = "http://localhost:{}".format(internal_port_from_config('Keepproxy'))
559 port = internal_port_from_config("Keepproxy")
560 env = os.environ.copy()
561 env['ARVADOS_API_TOKEN'] = auth_token('anonymous')
562 logf = open(_logfilename('keepproxy'), WRITE_MODE)
563 kp = subprocess.Popen(
564 ['arvados-server', 'keepproxy'], env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
566 with open(_pidfile('keepproxy'), 'w') as f:
568 _wait_until_port_listens(port)
570 print("Using API %s token %s" % (os.environ['ARVADOS_API_HOST'], auth_token('admin')), file=sys.stdout)
573 host=os.environ['ARVADOS_API_HOST'],
574 token=auth_token('admin'),
576 for d in api.keep_services().list(
577 filters=[['service_type','=','proxy']]).execute()['items']:
578 api.keep_services().delete(uuid=d['uuid']).execute()
579 api.keep_services().create(body={'keep_service': {
580 'service_host': 'localhost',
581 'service_port': port,
582 'service_type': 'proxy',
583 'service_ssl_flag': False,
585 os.environ["ARVADOS_KEEP_SERVICES"] = "http://localhost:{}".format(port)
586 _wait_until_port_listens(port)
588 def stop_keep_proxy():
589 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
591 kill_server_pid(_pidfile('keepproxy'))
593 def run_arv_git_httpd():
594 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
598 gitport = internal_port_from_config("GitHTTP")
599 env = os.environ.copy()
600 env.pop('ARVADOS_API_TOKEN', None)
601 logf = open(_logfilename('githttpd'), WRITE_MODE)
602 agh = subprocess.Popen(['arvados-server', 'git-httpd'],
603 env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
604 with open(_pidfile('githttpd'), 'w') as f:
605 f.write(str(agh.pid))
606 _wait_until_port_listens(gitport)
608 def stop_arv_git_httpd():
609 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
611 kill_server_pid(_pidfile('githttpd'))
614 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
618 keepwebport = internal_port_from_config("WebDAV")
619 env = os.environ.copy()
620 logf = open(_logfilename('keep-web'), WRITE_MODE)
621 keepweb = subprocess.Popen(
622 ['arvados-server', 'keep-web'],
623 env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
624 with open(_pidfile('keep-web'), 'w') as f:
625 f.write(str(keepweb.pid))
626 _wait_until_port_listens(keepwebport)
629 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
631 kill_server_pid(_pidfile('keep-web'))
634 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
638 nginxconf['UPSTREAMHOST'] = 'localhost'
639 nginxconf['LISTENHOST'] = 'localhost'
640 nginxconf['CONTROLLERPORT'] = internal_port_from_config("Controller")
641 nginxconf['ARVADOS_API_HOST'] = "0.0.0.0:" + str(external_port_from_config("Controller"))
642 nginxconf['CONTROLLERSSLPORT'] = external_port_from_config("Controller")
643 nginxconf['KEEPWEBPORT'] = internal_port_from_config("WebDAV")
644 nginxconf['KEEPWEBDLSSLPORT'] = external_port_from_config("WebDAVDownload")
645 nginxconf['KEEPWEBSSLPORT'] = external_port_from_config("WebDAV")
646 nginxconf['KEEPPROXYPORT'] = internal_port_from_config("Keepproxy")
647 nginxconf['KEEPPROXYSSLPORT'] = external_port_from_config("Keepproxy")
648 nginxconf['GITPORT'] = internal_port_from_config("GitHTTP")
649 nginxconf['GITSSLPORT'] = external_port_from_config("GitHTTP")
650 nginxconf['HEALTHPORT'] = internal_port_from_config("Health")
651 nginxconf['HEALTHSSLPORT'] = external_port_from_config("Health")
652 nginxconf['WSPORT'] = internal_port_from_config("Websocket")
653 nginxconf['WSSSLPORT'] = external_port_from_config("Websocket")
654 nginxconf['WORKBENCH1PORT'] = internal_port_from_config("Workbench1")
655 nginxconf['WORKBENCH1SSLPORT'] = external_port_from_config("Workbench1")
656 nginxconf['WORKBENCH2PORT'] = internal_port_from_config("Workbench2")
657 nginxconf['WORKBENCH2SSLPORT'] = external_port_from_config("Workbench2")
658 nginxconf['SSLCERT'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem')
659 nginxconf['SSLKEY'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.key')
660 nginxconf['ACCESSLOG'] = _logfilename('nginx_access')
661 nginxconf['ERRORLOG'] = _logfilename('nginx_error')
662 nginxconf['TMPDIR'] = TEST_TMPDIR + '/nginx'
663 nginxconf['INTERNALSUBNETS'] = '169.254.0.0/16 0;'
665 conftemplatefile = os.path.join(MY_DIRNAME, 'nginx.conf')
666 conffile = os.path.join(TEST_TMPDIR, 'nginx.conf')
667 with open(conffile, 'w') as f:
669 r'{{([A-Z]+[A-Z0-9]+)}}',
670 lambda match: str(nginxconf.get(match.group(1))),
671 open(conftemplatefile).read()))
673 env = os.environ.copy()
674 env['PATH'] = env['PATH']+':/sbin:/usr/sbin:/usr/local/sbin'
676 nginx = subprocess.Popen(
678 '-g', 'error_log stderr info; pid '+_pidfile('nginx')+';',
680 env=env, stdin=open('/dev/null'), stdout=sys.stderr)
681 _wait_until_port_listens(nginxconf['CONTROLLERSSLPORT'])
684 rails_api_port = find_available_port()
685 controller_port = find_available_port()
686 controller_external_port = find_available_port()
687 websocket_port = find_available_port()
688 websocket_external_port = find_available_port()
689 workbench1_port = find_available_port()
690 workbench1_external_port = find_available_port()
691 workbench2_port = find_available_port()
692 workbench2_external_port = find_available_port()
693 git_httpd_port = find_available_port()
694 git_httpd_external_port = find_available_port()
695 health_httpd_port = find_available_port()
696 health_httpd_external_port = find_available_port()
697 keepproxy_port = find_available_port()
698 keepproxy_external_port = find_available_port()
699 keepstore_ports = sorted([str(find_available_port()) for _ in range(0,4)])
700 keep_web_port = find_available_port()
701 keep_web_external_port = find_available_port()
702 keep_web_dl_external_port = find_available_port()
704 configsrc = os.environ.get("CONFIGSRC", None)
706 clusterconf = os.path.join(configsrc, "config.yml")
707 print("Getting config from %s" % clusterconf, file=sys.stderr)
708 pgconnection = yaml.safe_load(open(clusterconf))["Clusters"]["zzzzz"]["PostgreSQL"]["Connection"]
710 # assume "arvados-server install -type test" has set up the
711 # conventional db credentials
713 "client_encoding": "utf8",
715 "dbname": "arvados_test",
717 "password": "insecure_arvados_test",
720 localhost = "127.0.0.1"
724 "https://%s:%s"%(localhost, rails_api_port): {},
728 "ExternalURL": "https://%s:%s" % (localhost, controller_external_port),
730 "http://%s:%s"%(localhost, controller_port): {},
734 "ExternalURL": "wss://%s:%s/websocket" % (localhost, websocket_external_port),
736 "http://%s:%s"%(localhost, websocket_port): {},
740 "ExternalURL": "https://%s:%s/" % (localhost, workbench1_external_port),
742 "http://%s:%s"%(localhost, workbench1_port): {},
746 "ExternalURL": "https://%s:%s/" % (localhost, workbench2_external_port),
748 "http://%s:%s"%(localhost, workbench2_port): {},
752 "ExternalURL": "https://%s:%s" % (localhost, git_httpd_external_port),
754 "http://%s:%s"%(localhost, git_httpd_port): {}
758 "ExternalURL": "https://%s:%s" % (localhost, health_httpd_external_port),
760 "http://%s:%s"%(localhost, health_httpd_port): {}
765 "http://%s:%s"%(localhost, port): {} for port in keepstore_ports
769 "ExternalURL": "https://%s:%s" % (localhost, keepproxy_external_port),
771 "http://%s:%s"%(localhost, keepproxy_port): {},
775 "ExternalURL": "https://%s:%s" % (localhost, keep_web_external_port),
777 "http://%s:%s"%(localhost, keep_web_port): {},
781 "ExternalURL": "https://%s:%s" % (localhost, keep_web_dl_external_port),
783 "http://%s:%s"%(localhost, keep_web_port): {},
791 "ManagementToken": "e687950a23c3a9bceec28c6223a06c79",
792 "SystemRootToken": auth_token('system_user'),
794 "RequestTimeout": "30s",
801 "Email": "alice@example.com",
808 "LogLevel": ('info' if os.environ.get('ARVADOS_DEBUG', '') in ['','0'] else 'debug'),
811 "Connection": pgconnection,
816 "Services": services,
818 "AnonymousUserToken": auth_token('anonymous'),
819 "UserProfileNotificationAddress": "arvados@example.com",
822 "CollectionVersioning": True,
823 "BlobSigningKey": "zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc",
824 "TrustAllContent": False,
825 "ForwardSlashNameSubstitution": "/",
826 "TrashSweepInterval": "-1s",
829 "Repositories": os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'git', 'test'),
833 "GitInternalDir": os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'internal.git'),
835 "LocalKeepBlobBuffersPerVCPU": 0,
836 "SupportedDockerImageFormats": {"v1": {}},
843 "zzzzz-nyw5e-%015d"%n: {
845 "http://%s:%s" % (localhost, keepstore_ports[n]): {},
847 "Driver": "Directory",
848 "DriverParameters": {
849 "Root": os.path.join(TEST_TMPDIR, "keep%d.data"%n),
851 } for n in range(len(keepstore_ports))
857 conf = os.path.join(TEST_TMPDIR, 'arvados.yml')
858 with open(conf, 'w') as f:
859 yaml.safe_dump(config, f)
861 ex = "export ARVADOS_CONFIG="+conf
866 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
868 kill_server_pid(_pidfile('nginx'))
870 def _pidfile(program):
871 return os.path.join(TEST_TMPDIR, program + '.pid')
874 '''load a fixture yaml file'''
875 with open(os.path.join(SERVICES_SRC_DIR, 'api', "test", "fixtures",
879 trim_index = yaml_file.index("# Test Helper trims the rest of the file")
880 yaml_file = yaml_file[0:trim_index]
883 return yaml.safe_load(yaml_file)
885 def auth_token(token_name):
886 return fixture("api_client_authorizations")[token_name]["api_token"]
888 def authorize_with(token_name):
889 '''token_name is the symbolic name of the token from the api_client_authorizations fixture'''
890 arvados.config.settings()["ARVADOS_API_TOKEN"] = auth_token(token_name)
891 arvados.config.settings()["ARVADOS_API_HOST"] = os.environ.get("ARVADOS_API_HOST")
892 arvados.config.settings()["ARVADOS_API_HOST_INSECURE"] = "true"
894 class TestCaseWithServers(unittest.TestCase):
895 """TestCase to start and stop supporting Arvados servers.
897 Define any of MAIN_SERVER, KEEP_SERVER, and/or KEEP_PROXY_SERVER
898 class variables as a dictionary of keyword arguments. If you do,
899 setUpClass will start the corresponding servers by passing these
900 keyword arguments to the run, run_keep, and/or run_keep_server
901 functions, respectively. It will also set Arvados environment
902 variables to point to these servers appropriately. If you don't
903 run a Keep or Keep proxy server, setUpClass will set up a
904 temporary directory for Keep local storage, and set it as
907 tearDownClass will stop any servers started, and restore the
908 original environment.
913 KEEP_PROXY_SERVER = None
914 KEEP_WEB_SERVER = None
917 def _restore_dict(src, dest):
918 for key in list(dest.keys()):
925 cls._orig_environ = os.environ.copy()
926 cls._orig_config = arvados.config.settings().copy()
927 cls._cleanup_funcs = []
928 os.environ.pop('ARVADOS_KEEP_SERVICES', None)
929 os.environ.pop('ARVADOS_EXTERNAL_CLIENT', None)
930 for server_kwargs, start_func, stop_func in (
931 (cls.MAIN_SERVER, run, reset),
932 (cls.WS_SERVER, run_ws, stop_ws),
933 (cls.KEEP_SERVER, run_keep, stop_keep),
934 (cls.KEEP_PROXY_SERVER, run_keep_proxy, stop_keep_proxy),
935 (cls.KEEP_WEB_SERVER, run_keep_web, stop_keep_web)):
936 if server_kwargs is not None:
937 start_func(**server_kwargs)
938 cls._cleanup_funcs.append(stop_func)
939 if (cls.KEEP_SERVER is None) and (cls.KEEP_PROXY_SERVER is None):
940 cls.local_store = tempfile.mkdtemp()
941 os.environ['KEEP_LOCAL_STORE'] = cls.local_store
942 cls._cleanup_funcs.append(
943 lambda: shutil.rmtree(cls.local_store, ignore_errors=True))
945 os.environ.pop('KEEP_LOCAL_STORE', None)
946 arvados.config.initialize()
949 def tearDownClass(cls):
950 for clean_func in cls._cleanup_funcs:
952 cls._restore_dict(cls._orig_environ, os.environ)
953 cls._restore_dict(cls._orig_config, arvados.config.settings())
956 if __name__ == "__main__":
959 'start_ws', 'stop_ws',
960 'start_controller', 'stop_controller',
961 'start_keep', 'stop_keep',
962 'start_keep_proxy', 'stop_keep_proxy',
963 'start_keep-web', 'stop_keep-web',
964 'start_githttpd', 'stop_githttpd',
965 'start_nginx', 'stop_nginx', 'setup_config',
967 parser = argparse.ArgumentParser()
968 parser.add_argument('action', type=str, help="one of {}".format(actions))
969 parser.add_argument('--auth', type=str, metavar='FIXTURE_NAME', help='Print authorization info for given api_client_authorizations fixture')
970 parser.add_argument('--num-keep-servers', metavar='int', type=int, default=2, help="Number of keep servers desired")
971 parser.add_argument('--keep-blob-signing', action="store_true", help="Enable blob signing for keepstore servers")
973 args = parser.parse_args()
975 if args.action not in actions:
976 print("Unrecognized action '{}'. Actions are: {}.".
977 format(args.action, actions),
980 # Create a new process group so our child processes don't exit on
981 # ^C in run-tests.sh interactive mode.
983 if args.action == 'start':
984 stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
985 run(leave_running_atexit=True)
986 host = os.environ['ARVADOS_API_HOST']
987 if args.auth is not None:
988 token = auth_token(args.auth)
989 print("export ARVADOS_API_TOKEN={}".format(pipes.quote(token)))
990 print("export ARVADOS_API_HOST={}".format(pipes.quote(host)))
991 print("export ARVADOS_API_HOST_INSECURE=true")
994 elif args.action == 'stop':
995 stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
996 elif args.action == 'start_ws':
998 elif args.action == 'stop_ws':
1000 elif args.action == 'start_controller':
1002 elif args.action == 'stop_controller':
1004 elif args.action == 'start_keep':
1005 run_keep(blob_signing=args.keep_blob_signing, num_servers=args.num_keep_servers)
1006 elif args.action == 'stop_keep':
1007 stop_keep(num_servers=args.num_keep_servers)
1008 elif args.action == 'start_keep_proxy':
1010 elif args.action == 'stop_keep_proxy':
1012 elif args.action == 'start_githttpd':
1014 elif args.action == 'stop_githttpd':
1015 stop_arv_git_httpd()
1016 elif args.action == 'start_keep-web':
1018 elif args.action == 'stop_keep-web':
1020 elif args.action == 'start_nginx':
1022 print("export ARVADOS_API_HOST=0.0.0.0:{}".format(external_port_from_config('Controller')))
1023 elif args.action == 'stop_nginx':
1025 elif args.action == 'setup_config':
1028 raise Exception("action recognized but not implemented!?")