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['LISTENHOST'] = 'localhost'
639 nginxconf['CONTROLLERPORT'] = internal_port_from_config("Controller")
640 nginxconf['ARVADOS_API_HOST'] = "0.0.0.0:" + str(external_port_from_config("Controller"))
641 nginxconf['CONTROLLERSSLPORT'] = external_port_from_config("Controller")
642 nginxconf['KEEPWEBPORT'] = internal_port_from_config("WebDAV")
643 nginxconf['KEEPWEBDLSSLPORT'] = external_port_from_config("WebDAVDownload")
644 nginxconf['KEEPWEBSSLPORT'] = external_port_from_config("WebDAV")
645 nginxconf['KEEPPROXYPORT'] = internal_port_from_config("Keepproxy")
646 nginxconf['KEEPPROXYSSLPORT'] = external_port_from_config("Keepproxy")
647 nginxconf['GITPORT'] = internal_port_from_config("GitHTTP")
648 nginxconf['GITSSLPORT'] = external_port_from_config("GitHTTP")
649 nginxconf['HEALTHPORT'] = internal_port_from_config("Health")
650 nginxconf['HEALTHSSLPORT'] = external_port_from_config("Health")
651 nginxconf['WSPORT'] = internal_port_from_config("Websocket")
652 nginxconf['WSSSLPORT'] = external_port_from_config("Websocket")
653 nginxconf['WORKBENCH1PORT'] = internal_port_from_config("Workbench1")
654 nginxconf['WORKBENCH1SSLPORT'] = external_port_from_config("Workbench1")
655 nginxconf['WORKBENCH2PORT'] = internal_port_from_config("Workbench2")
656 nginxconf['WORKBENCH2SSLPORT'] = external_port_from_config("Workbench2")
657 nginxconf['SSLCERT'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem')
658 nginxconf['SSLKEY'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.key')
659 nginxconf['ACCESSLOG'] = _logfilename('nginx_access')
660 nginxconf['ERRORLOG'] = _logfilename('nginx_error')
661 nginxconf['TMPDIR'] = TEST_TMPDIR + '/nginx'
663 conftemplatefile = os.path.join(MY_DIRNAME, 'nginx.conf')
664 conffile = os.path.join(TEST_TMPDIR, 'nginx.conf')
665 with open(conffile, 'w') as f:
667 r'{{([A-Z]+[A-Z0-9]+)}}',
668 lambda match: str(nginxconf.get(match.group(1))),
669 open(conftemplatefile).read()))
671 env = os.environ.copy()
672 env['PATH'] = env['PATH']+':/sbin:/usr/sbin:/usr/local/sbin'
674 nginx = subprocess.Popen(
676 '-g', 'error_log stderr info; pid '+_pidfile('nginx')+';',
678 env=env, stdin=open('/dev/null'), stdout=sys.stderr)
679 _wait_until_port_listens(nginxconf['CONTROLLERSSLPORT'])
682 rails_api_port = find_available_port()
683 controller_port = find_available_port()
684 controller_external_port = find_available_port()
685 websocket_port = find_available_port()
686 websocket_external_port = find_available_port()
687 workbench1_port = find_available_port()
688 workbench1_external_port = find_available_port()
689 workbench2_port = find_available_port()
690 workbench2_external_port = find_available_port()
691 git_httpd_port = find_available_port()
692 git_httpd_external_port = find_available_port()
693 health_httpd_port = find_available_port()
694 health_httpd_external_port = find_available_port()
695 keepproxy_port = find_available_port()
696 keepproxy_external_port = find_available_port()
697 keepstore_ports = sorted([str(find_available_port()) for _ in range(0,4)])
698 keep_web_port = find_available_port()
699 keep_web_external_port = find_available_port()
700 keep_web_dl_external_port = find_available_port()
702 configsrc = os.environ.get("CONFIGSRC", None)
704 clusterconf = os.path.join(configsrc, "config.yml")
705 print("Getting config from %s" % clusterconf, file=sys.stderr)
706 pgconnection = yaml.safe_load(open(clusterconf))["Clusters"]["zzzzz"]["PostgreSQL"]["Connection"]
708 # assume "arvados-server install -type test" has set up the
709 # conventional db credentials
711 "client_encoding": "utf8",
713 "dbname": "arvados_test",
715 "password": "insecure_arvados_test",
718 localhost = "127.0.0.1"
722 "https://%s:%s"%(localhost, rails_api_port): {},
726 "ExternalURL": "https://%s:%s" % (localhost, controller_external_port),
728 "http://%s:%s"%(localhost, controller_port): {},
732 "ExternalURL": "wss://%s:%s/websocket" % (localhost, websocket_external_port),
734 "http://%s:%s"%(localhost, websocket_port): {},
738 "ExternalURL": "https://%s:%s/" % (localhost, workbench1_external_port),
740 "http://%s:%s"%(localhost, workbench1_port): {},
744 "ExternalURL": "https://%s:%s/" % (localhost, workbench2_external_port),
746 "http://%s:%s"%(localhost, workbench2_port): {},
750 "ExternalURL": "https://%s:%s" % (localhost, git_httpd_external_port),
752 "http://%s:%s"%(localhost, git_httpd_port): {}
756 "ExternalURL": "https://%s:%s" % (localhost, health_httpd_external_port),
758 "http://%s:%s"%(localhost, health_httpd_port): {}
763 "http://%s:%s"%(localhost, port): {} for port in keepstore_ports
767 "ExternalURL": "https://%s:%s" % (localhost, keepproxy_external_port),
769 "http://%s:%s"%(localhost, keepproxy_port): {},
773 "ExternalURL": "https://%s:%s" % (localhost, keep_web_external_port),
775 "http://%s:%s"%(localhost, keep_web_port): {},
779 "ExternalURL": "https://%s:%s" % (localhost, keep_web_dl_external_port),
781 "http://%s:%s"%(localhost, keep_web_port): {},
789 "ManagementToken": "e687950a23c3a9bceec28c6223a06c79",
790 "SystemRootToken": auth_token('system_user'),
792 "RequestTimeout": "30s",
799 "Email": "alice@example.com",
806 "LogLevel": ('info' if os.environ.get('ARVADOS_DEBUG', '') in ['','0'] else 'debug'),
809 "Connection": pgconnection,
814 "Services": services,
816 "AnonymousUserToken": auth_token('anonymous'),
817 "UserProfileNotificationAddress": "arvados@example.com",
820 "CollectionVersioning": True,
821 "BlobSigningKey": "zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc",
822 "TrustAllContent": False,
823 "ForwardSlashNameSubstitution": "/",
824 "TrashSweepInterval": "-1s",
827 "Repositories": os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'git', 'test'),
831 "GitInternalDir": os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'internal.git'),
833 "SupportedDockerImageFormats": {"v1": {}},
840 "zzzzz-nyw5e-%015d"%n: {
842 "http://%s:%s" % (localhost, keepstore_ports[n]): {},
844 "Driver": "Directory",
845 "DriverParameters": {
846 "Root": os.path.join(TEST_TMPDIR, "keep%d.data"%n),
848 } for n in range(len(keepstore_ports))
854 conf = os.path.join(TEST_TMPDIR, 'arvados.yml')
855 with open(conf, 'w') as f:
856 yaml.safe_dump(config, f)
858 ex = "export ARVADOS_CONFIG="+conf
863 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
865 kill_server_pid(_pidfile('nginx'))
867 def _pidfile(program):
868 return os.path.join(TEST_TMPDIR, program + '.pid')
871 '''load a fixture yaml file'''
872 with open(os.path.join(SERVICES_SRC_DIR, 'api', "test", "fixtures",
876 trim_index = yaml_file.index("# Test Helper trims the rest of the file")
877 yaml_file = yaml_file[0:trim_index]
880 return yaml.safe_load(yaml_file)
882 def auth_token(token_name):
883 return fixture("api_client_authorizations")[token_name]["api_token"]
885 def authorize_with(token_name):
886 '''token_name is the symbolic name of the token from the api_client_authorizations fixture'''
887 arvados.config.settings()["ARVADOS_API_TOKEN"] = auth_token(token_name)
888 arvados.config.settings()["ARVADOS_API_HOST"] = os.environ.get("ARVADOS_API_HOST")
889 arvados.config.settings()["ARVADOS_API_HOST_INSECURE"] = "true"
891 class TestCaseWithServers(unittest.TestCase):
892 """TestCase to start and stop supporting Arvados servers.
894 Define any of MAIN_SERVER, KEEP_SERVER, and/or KEEP_PROXY_SERVER
895 class variables as a dictionary of keyword arguments. If you do,
896 setUpClass will start the corresponding servers by passing these
897 keyword arguments to the run, run_keep, and/or run_keep_server
898 functions, respectively. It will also set Arvados environment
899 variables to point to these servers appropriately. If you don't
900 run a Keep or Keep proxy server, setUpClass will set up a
901 temporary directory for Keep local storage, and set it as
904 tearDownClass will stop any servers started, and restore the
905 original environment.
910 KEEP_PROXY_SERVER = None
911 KEEP_WEB_SERVER = None
914 def _restore_dict(src, dest):
915 for key in list(dest.keys()):
922 cls._orig_environ = os.environ.copy()
923 cls._orig_config = arvados.config.settings().copy()
924 cls._cleanup_funcs = []
925 os.environ.pop('ARVADOS_KEEP_SERVICES', None)
926 os.environ.pop('ARVADOS_EXTERNAL_CLIENT', None)
927 for server_kwargs, start_func, stop_func in (
928 (cls.MAIN_SERVER, run, reset),
929 (cls.WS_SERVER, run_ws, stop_ws),
930 (cls.KEEP_SERVER, run_keep, stop_keep),
931 (cls.KEEP_PROXY_SERVER, run_keep_proxy, stop_keep_proxy),
932 (cls.KEEP_WEB_SERVER, run_keep_web, stop_keep_web)):
933 if server_kwargs is not None:
934 start_func(**server_kwargs)
935 cls._cleanup_funcs.append(stop_func)
936 if (cls.KEEP_SERVER is None) and (cls.KEEP_PROXY_SERVER is None):
937 cls.local_store = tempfile.mkdtemp()
938 os.environ['KEEP_LOCAL_STORE'] = cls.local_store
939 cls._cleanup_funcs.append(
940 lambda: shutil.rmtree(cls.local_store, ignore_errors=True))
942 os.environ.pop('KEEP_LOCAL_STORE', None)
943 arvados.config.initialize()
946 def tearDownClass(cls):
947 for clean_func in cls._cleanup_funcs:
949 cls._restore_dict(cls._orig_environ, os.environ)
950 cls._restore_dict(cls._orig_config, arvados.config.settings())
953 if __name__ == "__main__":
956 'start_ws', 'stop_ws',
957 'start_controller', 'stop_controller',
958 'start_keep', 'stop_keep',
959 'start_keep_proxy', 'stop_keep_proxy',
960 'start_keep-web', 'stop_keep-web',
961 'start_githttpd', 'stop_githttpd',
962 'start_nginx', 'stop_nginx', 'setup_config',
964 parser = argparse.ArgumentParser()
965 parser.add_argument('action', type=str, help="one of {}".format(actions))
966 parser.add_argument('--auth', type=str, metavar='FIXTURE_NAME', help='Print authorization info for given api_client_authorizations fixture')
967 parser.add_argument('--num-keep-servers', metavar='int', type=int, default=2, help="Number of keep servers desired")
968 parser.add_argument('--keep-blob-signing', action="store_true", help="Enable blob signing for keepstore servers")
970 args = parser.parse_args()
972 if args.action not in actions:
973 print("Unrecognized action '{}'. Actions are: {}.".
974 format(args.action, actions),
977 # Create a new process group so our child processes don't exit on
978 # ^C in run-tests.sh interactive mode.
980 if args.action == 'start':
981 stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
982 run(leave_running_atexit=True)
983 host = os.environ['ARVADOS_API_HOST']
984 if args.auth is not None:
985 token = auth_token(args.auth)
986 print("export ARVADOS_API_TOKEN={}".format(pipes.quote(token)))
987 print("export ARVADOS_API_HOST={}".format(pipes.quote(host)))
988 print("export ARVADOS_API_HOST_INSECURE=true")
991 elif args.action == 'stop':
992 stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
993 elif args.action == 'start_ws':
995 elif args.action == 'stop_ws':
997 elif args.action == 'start_controller':
999 elif args.action == 'stop_controller':
1001 elif args.action == 'start_keep':
1002 run_keep(blob_signing=args.keep_blob_signing, num_servers=args.num_keep_servers)
1003 elif args.action == 'stop_keep':
1004 stop_keep(num_servers=args.num_keep_servers)
1005 elif args.action == 'start_keep_proxy':
1007 elif args.action == 'stop_keep_proxy':
1009 elif args.action == 'start_githttpd':
1011 elif args.action == 'stop_githttpd':
1012 stop_arv_git_httpd()
1013 elif args.action == 'start_keep-web':
1015 elif args.action == 'stop_keep-web':
1017 elif args.action == 'start_nginx':
1019 print("export ARVADOS_API_HOST=0.0.0.0:{}".format(external_port_from_config('Controller')))
1020 elif args.action == 'stop_nginx':
1022 elif args.action == 'setup_config':
1025 raise Exception("action recognized but not implemented!?")