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 '--no-compile-runtime',
365 '--no-install-runtime',
366 '--pid-file', pid_file,
367 '--log-file', '/dev/stdout',
369 '--ssl-certificate', 'tmp/self-signed.pem',
370 '--ssl-certificate-key', 'tmp/self-signed.key'],
371 env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
373 if not leave_running_atexit:
374 atexit.register(kill_server_pid, pid_file, passenger_root=api_src_dir)
376 my_api_host = "127.0.0.1:"+str(port)
377 os.environ['ARVADOS_API_HOST'] = my_api_host
379 # Make sure the server has written its pid file and started
380 # listening on its TCP port
381 _wait_until_port_listens(port)
382 find_server_pid(pid_file)
385 os.chdir(restore_cwd)
388 """Reset the test server to fixture state.
390 This resets the ARVADOS_TEST_API_HOST provided by a parent process
391 if any, otherwise the server started by run().
393 It also resets ARVADOS_* environment vars to point to the test
394 server with admin credentials.
396 existing_api_host = os.environ.get('ARVADOS_TEST_API_HOST', my_api_host)
397 token = auth_token('admin')
398 httpclient = httplib2.Http(ca_certs=os.path.join(
399 SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem'))
401 'https://{}/database/reset'.format(existing_api_host),
403 headers={'Authorization': 'OAuth2 {}'.format(token), 'Connection':'close'})
405 os.environ['ARVADOS_API_HOST_INSECURE'] = 'true'
406 os.environ['ARVADOS_API_TOKEN'] = token
407 os.environ['ARVADOS_API_HOST'] = existing_api_host
409 def stop(force=False):
410 """Stop the API server, if one is running.
412 If force==False, kill it only if we started it ourselves. (This
413 supports the use case where a Python test suite calls run(), but
414 run() just uses the ARVADOS_TEST_API_HOST provided by the parent
415 process, and the test suite cleans up after itself by calling
416 stop(). In this case the test server provided by the parent
417 process should be left alone.)
419 If force==True, kill it even if we didn't start it
420 ourselves. (This supports the use case in __main__, where "run"
421 and "stop" happen in different processes.)
424 if force or my_api_host is not None:
425 kill_server_pid(_pidfile('api'))
429 with open(os.environ["ARVADOS_CONFIG"]) as f:
430 return yaml.safe_load(f)
432 def internal_port_from_config(service, idx=0):
434 sorted(list(get_config()["Clusters"]["zzzzz"]["Services"][service]["InternalURLs"].keys()))[idx]).
435 netloc.split(":")[1])
437 def external_port_from_config(service):
438 return int(urlparse(get_config()["Clusters"]["zzzzz"]["Services"][service]["ExternalURL"]).netloc.split(":")[1])
440 def run_controller():
441 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
444 logf = open(_logfilename('controller'), WRITE_MODE)
445 port = internal_port_from_config("Controller")
446 controller = subprocess.Popen(
447 ["arvados-server", "controller"],
448 stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
449 with open(_pidfile('controller'), 'w') as f:
450 f.write(str(controller.pid))
451 _wait_until_port_listens(port)
454 def stop_controller():
455 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
457 kill_server_pid(_pidfile('controller'))
460 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
463 port = internal_port_from_config("Websocket")
464 logf = open(_logfilename('ws'), WRITE_MODE)
465 ws = subprocess.Popen(
466 ["arvados-server", "ws"],
467 stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
468 with open(_pidfile('ws'), 'w') as f:
470 _wait_until_port_listens(port)
474 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
476 kill_server_pid(_pidfile('ws'))
478 def _start_keep(n, blob_signing=False):
479 datadir = os.path.join(TEST_TMPDIR, "keep%d.data"%n)
480 if os.path.exists(datadir):
481 shutil.rmtree(datadir)
483 port = internal_port_from_config("Keepstore", idx=n)
485 # Currently, if there are multiple InternalURLs for a single host,
486 # the only way to tell a keepstore process which one it's supposed
487 # to listen on is to supply a redacted version of the config, with
488 # the other InternalURLs removed.
489 conf = os.path.join(TEST_TMPDIR, "keep%d.yaml"%n)
490 confdata = get_config()
491 confdata['Clusters']['zzzzz']['Services']['Keepstore']['InternalURLs'] = {"http://127.0.0.1:%d"%port: {}}
492 confdata['Clusters']['zzzzz']['Collections']['BlobSigning'] = blob_signing
493 with open(conf, 'w') as f:
494 yaml.safe_dump(confdata, f)
495 keep_cmd = ["arvados-server", "keepstore", "-config", conf]
497 with open(_logfilename('keep{}'.format(n)), WRITE_MODE) as logf:
498 with open('/dev/null') as _stdin:
499 child = subprocess.Popen(
500 keep_cmd, stdin=_stdin, stdout=logf, stderr=logf, close_fds=True)
502 print('child.pid is %d'%child.pid, file=sys.stderr)
503 with open(_pidfile('keep{}'.format(n)), 'w') as f:
504 f.write(str(child.pid))
506 _wait_until_port_listens(port)
510 def run_keep(num_servers=2, **kwargs):
511 stop_keep(num_servers)
515 host=os.environ['ARVADOS_API_HOST'],
516 token=os.environ['ARVADOS_API_TOKEN'],
519 for d in api.keep_services().list(filters=[['service_type','=','disk']]).execute()['items']:
520 api.keep_services().delete(uuid=d['uuid']).execute()
521 for d in api.keep_disks().list().execute()['items']:
522 api.keep_disks().delete(uuid=d['uuid']).execute()
524 for d in range(0, num_servers):
525 port = _start_keep(d, **kwargs)
526 svc = api.keep_services().create(body={'keep_service': {
527 'uuid': 'zzzzz-bi6l4-keepdisk{:07d}'.format(d),
528 'service_host': 'localhost',
529 'service_port': port,
530 'service_type': 'disk',
531 'service_ssl_flag': False,
533 api.keep_disks().create(body={
534 'keep_disk': {'keep_service_uuid': svc['uuid'] }
537 # If keepproxy and/or keep-web is running, send SIGHUP to make
538 # them discover the new keepstore services.
539 for svc in ('keepproxy', 'keep-web'):
540 pidfile = _pidfile('keepproxy')
541 if os.path.exists(pidfile):
543 with open(pidfile) as pid:
544 os.kill(int(pid.read()), signal.SIGHUP)
549 kill_server_pid(_pidfile('keep{}'.format(n)))
551 def stop_keep(num_servers=2):
552 for n in range(0, num_servers):
555 def run_keep_proxy():
556 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
557 os.environ["ARVADOS_KEEP_SERVICES"] = "http://localhost:{}".format(internal_port_from_config('Keepproxy'))
561 port = internal_port_from_config("Keepproxy")
562 env = os.environ.copy()
563 env['ARVADOS_API_TOKEN'] = auth_token('anonymous')
564 logf = open(_logfilename('keepproxy'), WRITE_MODE)
565 kp = subprocess.Popen(
566 ['keepproxy'], env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
568 with open(_pidfile('keepproxy'), 'w') as f:
570 _wait_until_port_listens(port)
572 print("Using API %s token %s" % (os.environ['ARVADOS_API_HOST'], auth_token('admin')), file=sys.stdout)
575 host=os.environ['ARVADOS_API_HOST'],
576 token=auth_token('admin'),
578 for d in api.keep_services().list(
579 filters=[['service_type','=','proxy']]).execute()['items']:
580 api.keep_services().delete(uuid=d['uuid']).execute()
581 api.keep_services().create(body={'keep_service': {
582 'service_host': 'localhost',
583 'service_port': port,
584 'service_type': 'proxy',
585 'service_ssl_flag': False,
587 os.environ["ARVADOS_KEEP_SERVICES"] = "http://localhost:{}".format(port)
588 _wait_until_port_listens(port)
590 def stop_keep_proxy():
591 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
593 kill_server_pid(_pidfile('keepproxy'))
595 def run_arv_git_httpd():
596 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
600 gitport = internal_port_from_config("GitHTTP")
601 env = os.environ.copy()
602 env.pop('ARVADOS_API_TOKEN', None)
603 logf = open(_logfilename('arv-git-httpd'), WRITE_MODE)
604 agh = subprocess.Popen(['arv-git-httpd'],
605 env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
606 with open(_pidfile('arv-git-httpd'), 'w') as f:
607 f.write(str(agh.pid))
608 _wait_until_port_listens(gitport)
610 def stop_arv_git_httpd():
611 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
613 kill_server_pid(_pidfile('arv-git-httpd'))
616 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
620 keepwebport = internal_port_from_config("WebDAV")
621 env = os.environ.copy()
622 logf = open(_logfilename('keep-web'), WRITE_MODE)
623 keepweb = subprocess.Popen(
625 env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
626 with open(_pidfile('keep-web'), 'w') as f:
627 f.write(str(keepweb.pid))
628 _wait_until_port_listens(keepwebport)
631 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
633 kill_server_pid(_pidfile('keep-web'))
636 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
640 nginxconf['LISTENHOST'] = 'localhost'
641 nginxconf['CONTROLLERPORT'] = internal_port_from_config("Controller")
642 nginxconf['ARVADOS_API_HOST'] = "0.0.0.0:" + str(external_port_from_config("Controller"))
643 nginxconf['CONTROLLERSSLPORT'] = external_port_from_config("Controller")
644 nginxconf['KEEPWEBPORT'] = internal_port_from_config("WebDAV")
645 nginxconf['KEEPWEBDLSSLPORT'] = external_port_from_config("WebDAVDownload")
646 nginxconf['KEEPWEBSSLPORT'] = external_port_from_config("WebDAV")
647 nginxconf['KEEPPROXYPORT'] = internal_port_from_config("Keepproxy")
648 nginxconf['KEEPPROXYSSLPORT'] = external_port_from_config("Keepproxy")
649 nginxconf['GITPORT'] = internal_port_from_config("GitHTTP")
650 nginxconf['GITSSLPORT'] = external_port_from_config("GitHTTP")
651 nginxconf['HEALTHPORT'] = internal_port_from_config("Health")
652 nginxconf['HEALTHSSLPORT'] = external_port_from_config("Health")
653 nginxconf['WSPORT'] = internal_port_from_config("Websocket")
654 nginxconf['WSSSLPORT'] = external_port_from_config("Websocket")
655 nginxconf['WORKBENCH1PORT'] = internal_port_from_config("Workbench1")
656 nginxconf['WORKBENCH1SSLPORT'] = external_port_from_config("Workbench1")
657 nginxconf['WORKBENCH2PORT'] = internal_port_from_config("Workbench2")
658 nginxconf['WORKBENCH2SSLPORT'] = external_port_from_config("Workbench2")
659 nginxconf['SSLCERT'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem')
660 nginxconf['SSLKEY'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.key')
661 nginxconf['ACCESSLOG'] = _logfilename('nginx_access')
662 nginxconf['ERRORLOG'] = _logfilename('nginx_error')
663 nginxconf['TMPDIR'] = TEST_TMPDIR + '/nginx'
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_port = find_available_port()
703 keep_web_dl_external_port = find_available_port()
705 configsrc = os.environ.get("CONFIGSRC", None)
707 clusterconf = os.path.join(configsrc, "config.yml")
708 print("Getting config from %s" % clusterconf, file=sys.stderr)
709 pgconnection = yaml.safe_load(open(clusterconf))["Clusters"]["zzzzz"]["PostgreSQL"]["Connection"]
711 # assume "arvados-server install -type test" has set up the
712 # conventional db credentials
714 "client_encoding": "utf8",
716 "dbname": "arvados_test",
718 "password": "insecure_arvados_test",
721 localhost = "127.0.0.1"
725 "https://%s:%s"%(localhost, rails_api_port): {},
729 "ExternalURL": "https://%s:%s" % (localhost, controller_external_port),
731 "http://%s:%s"%(localhost, controller_port): {},
735 "ExternalURL": "wss://%s:%s/websocket" % (localhost, websocket_external_port),
737 "http://%s:%s"%(localhost, websocket_port): {},
741 "ExternalURL": "https://%s:%s/" % (localhost, workbench1_external_port),
743 "http://%s:%s"%(localhost, workbench1_port): {},
747 "ExternalURL": "https://%s:%s/" % (localhost, workbench2_external_port),
749 "http://%s:%s"%(localhost, workbench2_port): {},
753 "ExternalURL": "https://%s:%s" % (localhost, git_httpd_external_port),
755 "http://%s:%s"%(localhost, git_httpd_port): {}
759 "ExternalURL": "https://%s:%s" % (localhost, health_httpd_external_port),
761 "http://%s:%s"%(localhost, health_httpd_port): {}
766 "http://%s:%s"%(localhost, port): {} for port in keepstore_ports
770 "ExternalURL": "https://%s:%s" % (localhost, keepproxy_external_port),
772 "http://%s:%s"%(localhost, keepproxy_port): {},
776 "ExternalURL": "https://%s:%s" % (localhost, keep_web_external_port),
778 "http://%s:%s"%(localhost, keep_web_port): {},
782 "ExternalURL": "https://%s:%s" % (localhost, keep_web_dl_external_port),
784 "http://%s:%s"%(localhost, keep_web_dl_port): {},
792 "ManagementToken": "e687950a23c3a9bceec28c6223a06c79",
793 "SystemRootToken": auth_token('system_user'),
795 "RequestTimeout": "30s",
802 "Email": "alice@example.com",
809 "LogLevel": ('info' if os.environ.get('ARVADOS_DEBUG', '') in ['','0'] else 'debug'),
812 "Connection": pgconnection,
817 "Services": services,
819 "AnonymousUserToken": auth_token('anonymous'),
820 "UserProfileNotificationAddress": "arvados@example.com",
823 "CollectionVersioning": True,
824 "BlobSigningKey": "zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc",
825 "TrustAllContent": False,
826 "ForwardSlashNameSubstitution": "/",
827 "TrashSweepInterval": "-1s",
830 "Repositories": os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'git', 'test'),
834 "GitInternalDir": os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'internal.git'),
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_arv-git-httpd', 'stop_arv-git-httpd',
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 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_arv-git-httpd':
1011 elif args.action == 'stop_arv-git-httpd':
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!?")