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 = {}
77 def find_server_pid(PID_PATH, wait=10):
81 while (not good_pid) and (now <= timeout):
84 with open(PID_PATH, 'r') as f:
85 server_pid = int(f.read())
86 good_pid = (os.kill(server_pid, 0) is None)
87 except EnvironmentError:
96 def kill_server_pid(pidfile, wait=10, passenger_root=False):
97 # Must re-import modules in order to work during atexit
105 deadline = now + wait
108 # First try to shut down nicely
109 restore_cwd = os.getcwd()
110 os.chdir(passenger_root)
112 'bundle', 'exec', 'passenger', 'stop', '--pid-file', pidfile])
113 os.chdir(restore_cwd)
114 # Use up to half of the +wait+ period waiting for "passenger
115 # stop" to work. If the process hasn't exited by then, start
116 # sending TERM signals.
120 while now <= deadline and server_pid is None:
122 with open(pidfile, 'r') as f:
123 server_pid = int(f.read())
125 # No pidfile = nothing to kill.
127 except ValueError as error:
128 # Pidfile exists, but we can't parse it. Perhaps the
129 # server has created the file but hasn't written its PID
131 print("Parse error reading pidfile {}: {}".format(pidfile, error),
136 while now <= deadline:
138 exited, _ = os.waitpid(server_pid, os.WNOHANG)
140 _remove_pidfile(pidfile)
143 # already exited, or isn't our child process
147 os.kill(server_pid, signal.SIGTERM)
148 print("Sent SIGTERM to {} ({})".format(server_pid, pidfile),
150 except OSError as error:
151 if error.errno == errno.ESRCH:
152 # Thrown by os.getpgid() or os.kill() if the process
153 # does not exist, i.e., our work here is done.
154 _remove_pidfile(pidfile)
160 print("Server PID {} ({}) did not exit, giving up after {}s".
161 format(server_pid, pidfile, wait),
164 def _remove_pidfile(pidfile):
168 if os.path.lexists(pidfile):
171 def find_available_port():
172 """Return an IPv4 port number that is not in use right now.
174 We assume whoever needs to use the returned port is able to reuse
175 a recently used port without waiting for TIME_WAIT (see
176 SO_REUSEADDR / SO_REUSEPORT).
178 Some opportunity for races here, but it's better than choosing
179 something at random and not checking at all. If all of our servers
180 (hey Passenger) knew that listening on port 0 was a thing, the OS
181 would take care of the races, and this wouldn't be needed at all.
184 sock = socket.socket()
185 sock.bind(('0.0.0.0', 0))
186 port = sock.getsockname()[1]
190 def _wait_until_port_listens(port, timeout=10, warn=True):
191 """Wait for a process to start listening on the given port.
193 If nothing listens on the port within the specified timeout (given
194 in seconds), print a warning on stderr before returning.
197 subprocess.check_output(['which', 'netstat'])
198 except subprocess.CalledProcessError:
199 print("WARNING: No `netstat` -- cannot wait for port to listen. "+
200 "Sleeping 0.5 and hoping for the best.",
204 deadline = time.time() + timeout
205 while time.time() < deadline:
206 if re.search(r'\ntcp.*:'+str(port)+' .* LISTEN *\n', subprocess.check_output(['netstat', '-Wln']).decode()):
211 "WARNING: Nothing is listening on port {} (waited {} seconds).".
212 format(port, timeout),
216 def _logfilename(label):
217 """Set up a labelled log file, and return a path to write logs to.
219 Normally, the returned path is {tmpdir}/{label}.log.
221 In debug mode, logs are also written to stderr, with [label]
222 prepended to each line. The returned path is a FIFO.
224 +label+ should contain only alphanumerics: it is also used as part
225 of the FIFO filename.
228 logfilename = os.path.join(TEST_TMPDIR, label+'.log')
229 if not os.environ.get('ARVADOS_DEBUG', ''):
231 fifo = os.path.join(TEST_TMPDIR, label+'.fifo')
234 except OSError as error:
235 if error.errno != errno.ENOENT:
237 os.mkfifo(fifo, 0o700)
238 stdbuf = ['stdbuf', '-i0', '-oL', '-eL']
239 # open(fifo, 'r') would block waiting for someone to open the fifo
240 # for writing, so we need a separate cat process to open it for
242 cat = subprocess.Popen(
243 stdbuf+['cat', fifo],
244 stdin=open('/dev/null'),
245 stdout=subprocess.PIPE)
246 tee = subprocess.Popen(
247 stdbuf+['tee', '-a', logfilename],
249 stdout=subprocess.PIPE)
251 stdbuf+['sed', '-e', 's/^/['+label+'] /'],
256 def run(leave_running_atexit=False):
257 """Ensure an API server is running, and ARVADOS_API_* env vars have
258 admin credentials for it.
260 If ARVADOS_TEST_API_HOST is set, a parent process has started a
261 test server for us to use: we just need to reset() it using the
264 If a previous call to run() started a new server process, and it
265 is still running, we just need to reset() it to fixture state and
268 If neither of those options work out, we'll really start a new
273 # Delete cached discovery documents.
275 # This will clear cached docs that belong to other processes (like
276 # concurrent test suites) even if they're still running. They should
277 # be able to tolerate that.
278 for fn in glob.glob(os.path.join(
279 str(arvados.http_cache('discovery')),
280 '*,arvados,v1,rest,*')):
283 pid_file = _pidfile('api')
284 pid_file_ok = find_server_pid(pid_file, 0)
286 existing_api_host = os.environ.get('ARVADOS_TEST_API_HOST', my_api_host)
287 if existing_api_host and pid_file_ok:
288 if existing_api_host == my_api_host:
292 # Fall through to shutdown-and-start case.
295 # Server was provided by parent. Can't recover if it's
299 # Before trying to start up our own server, call stop() to avoid
300 # "Phusion Passenger Standalone is already running on PID 12345".
301 # (If we've gotten this far, ARVADOS_TEST_API_HOST isn't set, so
302 # we know the server is ours to kill.)
305 restore_cwd = os.getcwd()
306 api_src_dir = os.path.join(SERVICES_SRC_DIR, 'api')
307 os.chdir(api_src_dir)
309 # Either we haven't started a server of our own yet, or it has
310 # died, or we have lost our credentials, or something else is
311 # preventing us from calling reset(). Start a new one.
313 if not os.path.exists('tmp'):
316 if not os.path.exists('tmp/api'):
317 os.makedirs('tmp/api')
319 if not os.path.exists('tmp/logs'):
320 os.makedirs('tmp/logs')
322 # Install the git repository fixtures.
323 gitdir = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'git')
324 gittarball = os.path.join(SERVICES_SRC_DIR, 'api', 'test', 'test.git.tar')
325 if not os.path.isdir(gitdir):
327 subprocess.check_output(['tar', '-xC', gitdir, '-f', gittarball])
329 port = internal_port_from_config("RailsAPI")
330 env = os.environ.copy()
331 env['RAILS_ENV'] = 'test'
332 env['ARVADOS_RAILS_LOG_TO_STDOUT'] = '1'
333 env.pop('ARVADOS_WEBSOCKETS', None)
334 env.pop('ARVADOS_TEST_API_HOST', None)
335 env.pop('ARVADOS_API_HOST', None)
336 env.pop('ARVADOS_API_HOST_INSECURE', None)
337 env.pop('ARVADOS_API_TOKEN', None)
338 logf = open(_logfilename('railsapi'), WRITE_MODE)
339 railsapi = subprocess.Popen(
341 'passenger', 'start', '-p{}'.format(port),
342 '--pid-file', pid_file,
343 '--log-file', '/dev/stdout',
345 '--ssl-certificate', 'tmp/self-signed.pem',
346 '--ssl-certificate-key', 'tmp/self-signed.key'],
347 env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
349 if not leave_running_atexit:
350 atexit.register(kill_server_pid, pid_file, passenger_root=api_src_dir)
352 my_api_host = "127.0.0.1:"+str(port)
353 os.environ['ARVADOS_API_HOST'] = my_api_host
355 # Make sure the server has written its pid file and started
356 # listening on its TCP port
357 _wait_until_port_listens(port)
358 find_server_pid(pid_file)
361 os.chdir(restore_cwd)
364 """Reset the test server to fixture state.
366 This resets the ARVADOS_TEST_API_HOST provided by a parent process
367 if any, otherwise the server started by run().
369 It also resets ARVADOS_* environment vars to point to the test
370 server with admin credentials.
372 existing_api_host = os.environ.get('ARVADOS_TEST_API_HOST', my_api_host)
373 token = auth_token('admin')
374 httpclient = httplib2.Http(ca_certs=os.path.join(
375 SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem'))
377 'https://{}/database/reset'.format(existing_api_host),
379 headers={'Authorization': 'OAuth2 {}'.format(token), 'Connection':'close'})
381 os.environ['ARVADOS_API_HOST_INSECURE'] = 'true'
382 os.environ['ARVADOS_API_TOKEN'] = token
383 os.environ['ARVADOS_API_HOST'] = existing_api_host
385 def stop(force=False):
386 """Stop the API server, if one is running.
388 If force==False, kill it only if we started it ourselves. (This
389 supports the use case where a Python test suite calls run(), but
390 run() just uses the ARVADOS_TEST_API_HOST provided by the parent
391 process, and the test suite cleans up after itself by calling
392 stop(). In this case the test server provided by the parent
393 process should be left alone.)
395 If force==True, kill it even if we didn't start it
396 ourselves. (This supports the use case in __main__, where "run"
397 and "stop" happen in different processes.)
400 if force or my_api_host is not None:
401 kill_server_pid(_pidfile('api'))
405 with open(os.environ["ARVADOS_CONFIG"]) as f:
406 return yaml.safe_load(f)
408 def internal_port_from_config(service, idx=0):
410 sorted(list(get_config()["Clusters"]["zzzzz"]["Services"][service]["InternalURLs"].keys()))[idx]).
411 netloc.split(":")[1])
413 def external_port_from_config(service):
414 return int(urlparse(get_config()["Clusters"]["zzzzz"]["Services"][service]["ExternalURL"]).netloc.split(":")[1])
416 def run_controller():
417 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
420 logf = open(_logfilename('controller'), WRITE_MODE)
421 port = internal_port_from_config("Controller")
422 controller = subprocess.Popen(
423 ["arvados-server", "controller"],
424 stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
425 with open(_pidfile('controller'), 'w') as f:
426 f.write(str(controller.pid))
427 _wait_until_port_listens(port)
430 def stop_controller():
431 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
433 kill_server_pid(_pidfile('controller'))
436 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
439 port = internal_port_from_config("Websocket")
440 logf = open(_logfilename('ws'), WRITE_MODE)
441 ws = subprocess.Popen(
442 ["arvados-server", "ws"],
443 stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
444 with open(_pidfile('ws'), 'w') as f:
446 _wait_until_port_listens(port)
450 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
452 kill_server_pid(_pidfile('ws'))
454 def _start_keep(n, blob_signing=False):
455 datadir = os.path.join(TEST_TMPDIR, "keep%d.data"%n)
456 if os.path.exists(datadir):
457 shutil.rmtree(datadir)
459 port = internal_port_from_config("Keepstore", idx=n)
461 # Currently, if there are multiple InternalURLs for a single host,
462 # the only way to tell a keepstore process which one it's supposed
463 # to listen on is to supply a redacted version of the config, with
464 # the other InternalURLs removed.
465 conf = os.path.join(TEST_TMPDIR, "keep%d.yaml"%n)
466 confdata = get_config()
467 confdata['Clusters']['zzzzz']['Services']['Keepstore']['InternalURLs'] = {"http://127.0.0.1:%d"%port: {}}
468 confdata['Clusters']['zzzzz']['Collections']['BlobSigning'] = blob_signing
469 with open(conf, 'w') as f:
470 yaml.safe_dump(confdata, f)
471 keep_cmd = ["keepstore", "-config", conf]
473 with open(_logfilename('keep{}'.format(n)), WRITE_MODE) as logf:
474 with open('/dev/null') as _stdin:
475 child = subprocess.Popen(
476 keep_cmd, stdin=_stdin, stdout=logf, stderr=logf, close_fds=True)
478 print('child.pid is %d'%child.pid, file=sys.stderr)
479 with open(_pidfile('keep{}'.format(n)), 'w') as f:
480 f.write(str(child.pid))
482 _wait_until_port_listens(port)
486 def run_keep(num_servers=2, **kwargs):
487 stop_keep(num_servers)
491 host=os.environ['ARVADOS_API_HOST'],
492 token=os.environ['ARVADOS_API_TOKEN'],
495 for d in api.keep_services().list(filters=[['service_type','=','disk']]).execute()['items']:
496 api.keep_services().delete(uuid=d['uuid']).execute()
497 for d in api.keep_disks().list().execute()['items']:
498 api.keep_disks().delete(uuid=d['uuid']).execute()
500 for d in range(0, num_servers):
501 port = _start_keep(d, **kwargs)
502 svc = api.keep_services().create(body={'keep_service': {
503 'uuid': 'zzzzz-bi6l4-keepdisk{:07d}'.format(d),
504 'service_host': 'localhost',
505 'service_port': port,
506 'service_type': 'disk',
507 'service_ssl_flag': False,
509 api.keep_disks().create(body={
510 'keep_disk': {'keep_service_uuid': svc['uuid'] }
513 # If keepproxy and/or keep-web is running, send SIGHUP to make
514 # them discover the new keepstore services.
515 for svc in ('keepproxy', 'keep-web'):
516 pidfile = _pidfile('keepproxy')
517 if os.path.exists(pidfile):
519 with open(pidfile) as pid:
520 os.kill(int(pid.read()), signal.SIGHUP)
525 kill_server_pid(_pidfile('keep{}'.format(n)))
527 def stop_keep(num_servers=2):
528 for n in range(0, num_servers):
531 def run_keep_proxy():
532 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
533 os.environ["ARVADOS_KEEP_SERVICES"] = "http://localhost:{}".format(internal_port_from_config('Keepproxy'))
537 port = internal_port_from_config("Keepproxy")
538 env = os.environ.copy()
539 env['ARVADOS_API_TOKEN'] = auth_token('anonymous')
540 logf = open(_logfilename('keepproxy'), WRITE_MODE)
541 kp = subprocess.Popen(
542 ['keepproxy'], env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
544 with open(_pidfile('keepproxy'), 'w') as f:
546 _wait_until_port_listens(port)
548 print("Using API %s token %s" % (os.environ['ARVADOS_API_HOST'], auth_token('admin')), file=sys.stdout)
551 host=os.environ['ARVADOS_API_HOST'],
552 token=auth_token('admin'),
554 for d in api.keep_services().list(
555 filters=[['service_type','=','proxy']]).execute()['items']:
556 api.keep_services().delete(uuid=d['uuid']).execute()
557 api.keep_services().create(body={'keep_service': {
558 'service_host': 'localhost',
559 'service_port': port,
560 'service_type': 'proxy',
561 'service_ssl_flag': False,
563 os.environ["ARVADOS_KEEP_SERVICES"] = "http://localhost:{}".format(port)
564 _wait_until_port_listens(port)
566 def stop_keep_proxy():
567 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
569 kill_server_pid(_pidfile('keepproxy'))
571 def run_arv_git_httpd():
572 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
576 gitport = internal_port_from_config("GitHTTP")
577 env = os.environ.copy()
578 env.pop('ARVADOS_API_TOKEN', None)
579 logf = open(_logfilename('arv-git-httpd'), WRITE_MODE)
580 agh = subprocess.Popen(['arv-git-httpd'],
581 env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
582 with open(_pidfile('arv-git-httpd'), 'w') as f:
583 f.write(str(agh.pid))
584 _wait_until_port_listens(gitport)
586 def stop_arv_git_httpd():
587 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
589 kill_server_pid(_pidfile('arv-git-httpd'))
592 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
596 keepwebport = internal_port_from_config("WebDAV")
597 env = os.environ.copy()
598 logf = open(_logfilename('keep-web'), WRITE_MODE)
599 keepweb = subprocess.Popen(
601 env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
602 with open(_pidfile('keep-web'), 'w') as f:
603 f.write(str(keepweb.pid))
604 _wait_until_port_listens(keepwebport)
607 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
609 kill_server_pid(_pidfile('keep-web'))
612 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
616 nginxconf['LISTENHOST'] = 'localhost'
617 nginxconf['CONTROLLERPORT'] = internal_port_from_config("Controller")
618 nginxconf['CONTROLLERSSLPORT'] = external_port_from_config("Controller")
619 nginxconf['KEEPWEBPORT'] = internal_port_from_config("WebDAV")
620 nginxconf['KEEPWEBDLSSLPORT'] = external_port_from_config("WebDAVDownload")
621 nginxconf['KEEPWEBSSLPORT'] = external_port_from_config("WebDAV")
622 nginxconf['KEEPPROXYPORT'] = internal_port_from_config("Keepproxy")
623 nginxconf['KEEPPROXYSSLPORT'] = external_port_from_config("Keepproxy")
624 nginxconf['GITPORT'] = internal_port_from_config("GitHTTP")
625 nginxconf['GITSSLPORT'] = external_port_from_config("GitHTTP")
626 nginxconf['HEALTHPORT'] = internal_port_from_config("Health")
627 nginxconf['HEALTHSSLPORT'] = external_port_from_config("Health")
628 nginxconf['WSPORT'] = internal_port_from_config("Websocket")
629 nginxconf['WSSSLPORT'] = external_port_from_config("Websocket")
630 nginxconf['WORKBENCH1PORT'] = internal_port_from_config("Workbench1")
631 nginxconf['WORKBENCH1SSLPORT'] = external_port_from_config("Workbench1")
632 nginxconf['SSLCERT'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem')
633 nginxconf['SSLKEY'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.key')
634 nginxconf['ACCESSLOG'] = _logfilename('nginx_access')
635 nginxconf['ERRORLOG'] = _logfilename('nginx_error')
636 nginxconf['TMPDIR'] = TEST_TMPDIR
638 conftemplatefile = os.path.join(MY_DIRNAME, 'nginx.conf')
639 conffile = os.path.join(TEST_TMPDIR, 'nginx.conf')
640 with open(conffile, 'w') as f:
642 r'{{([A-Z]+[A-Z0-9]+)}}',
643 lambda match: str(nginxconf.get(match.group(1))),
644 open(conftemplatefile).read()))
646 env = os.environ.copy()
647 env['PATH'] = env['PATH']+':/sbin:/usr/sbin:/usr/local/sbin'
649 nginx = subprocess.Popen(
651 '-g', 'error_log stderr info;',
652 '-g', 'pid '+_pidfile('nginx')+';',
654 env=env, stdin=open('/dev/null'), stdout=sys.stderr)
655 _wait_until_port_listens(nginxconf['CONTROLLERSSLPORT'])
658 rails_api_port = find_available_port()
659 controller_port = find_available_port()
660 controller_external_port = find_available_port()
661 websocket_port = find_available_port()
662 websocket_external_port = find_available_port()
663 workbench1_port = find_available_port()
664 workbench1_external_port = find_available_port()
665 git_httpd_port = find_available_port()
666 git_httpd_external_port = find_available_port()
667 health_httpd_port = find_available_port()
668 health_httpd_external_port = find_available_port()
669 keepproxy_port = find_available_port()
670 keepproxy_external_port = find_available_port()
671 keepstore_ports = sorted([str(find_available_port()) for _ in range(0,4)])
672 keep_web_port = find_available_port()
673 keep_web_external_port = find_available_port()
674 keep_web_dl_port = find_available_port()
675 keep_web_dl_external_port = find_available_port()
677 configsrc = os.environ.get("CONFIGSRC", None)
679 clusterconf = os.path.join(configsrc, "config.yml")
680 print("Getting config from %s" % clusterconf, file=sys.stderr)
681 pgconnection = yaml.safe_load(open(clusterconf))["Clusters"]["zzzzz"]["PostgreSQL"]["Connection"]
683 # assume "arvados-server install -type test" has set up the
684 # conventional db credentials
686 "client_encoding": "utf8",
688 "dbname": "arvados_test",
690 "password": "insecure_arvados_test",
693 localhost = "127.0.0.1"
697 "https://%s:%s"%(localhost, rails_api_port): {},
701 "ExternalURL": "https://%s:%s" % (localhost, controller_external_port),
703 "http://%s:%s"%(localhost, controller_port): {},
707 "ExternalURL": "wss://%s:%s/websocket" % (localhost, websocket_external_port),
709 "http://%s:%s"%(localhost, websocket_port): {},
713 "ExternalURL": "https://%s:%s/" % (localhost, workbench1_external_port),
715 "http://%s:%s"%(localhost, workbench1_port): {},
719 "ExternalURL": "https://%s:%s" % (localhost, git_httpd_external_port),
721 "http://%s:%s"%(localhost, git_httpd_port): {}
725 "ExternalURL": "https://%s:%s" % (localhost, health_httpd_external_port),
727 "http://%s:%s"%(localhost, health_httpd_port): {}
732 "http://%s:%s"%(localhost, port): {} for port in keepstore_ports
736 "ExternalURL": "https://%s:%s" % (localhost, keepproxy_external_port),
738 "http://%s:%s"%(localhost, keepproxy_port): {},
742 "ExternalURL": "https://%s:%s" % (localhost, keep_web_external_port),
744 "http://%s:%s"%(localhost, keep_web_port): {},
748 "ExternalURL": "https://%s:%s" % (localhost, keep_web_dl_external_port),
750 "http://%s:%s"%(localhost, keep_web_dl_port): {},
754 "ExternalURL": "http://localhost:3002",
761 "ManagementToken": "e687950a23c3a9bceec28c6223a06c79",
762 "SystemRootToken": auth_token('system_user'),
764 "RequestTimeout": "30s",
768 "ProviderAppID": "arvados-server",
769 "ProviderAppSecret": "608dbf356a327e2d0d4932b60161e212c2d8d8f5e25690d7b622f850a990cd33",
773 "LogLevel": ('info' if os.environ.get('ARVADOS_DEBUG', '') in ['','0'] else 'debug'),
776 "Connection": pgconnection,
781 "Services": services,
783 "AnonymousUserToken": auth_token('anonymous'),
784 "UserProfileNotificationAddress": "arvados@example.com",
787 "BlobSigningKey": "zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc",
788 "TrustAllContent": False,
789 "ForwardSlashNameSubstitution": "/",
790 "TrashSweepInterval": "-1s",
793 "Repositories": os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'git', 'test'),
797 "GitInternalDir": os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'internal.git'),
799 "SupportedDockerImageFormats": {"v1": {}},
802 "zzzzz-nyw5e-%015d"%n: {
804 "http://%s:%s" % (localhost, keepstore_ports[n]): {},
806 "Driver": "Directory",
807 "DriverParameters": {
808 "Root": os.path.join(TEST_TMPDIR, "keep%d.data"%n),
810 } for n in range(len(keepstore_ports))
816 conf = os.path.join(TEST_TMPDIR, 'arvados.yml')
817 with open(conf, 'w') as f:
818 yaml.safe_dump(config, f)
820 ex = "export ARVADOS_CONFIG="+conf
825 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
827 kill_server_pid(_pidfile('nginx'))
829 def _pidfile(program):
830 return os.path.join(TEST_TMPDIR, program + '.pid')
833 '''load a fixture yaml file'''
834 with open(os.path.join(SERVICES_SRC_DIR, 'api', "test", "fixtures",
838 trim_index = yaml_file.index("# Test Helper trims the rest of the file")
839 yaml_file = yaml_file[0:trim_index]
842 return yaml.safe_load(yaml_file)
844 def auth_token(token_name):
845 return fixture("api_client_authorizations")[token_name]["api_token"]
847 def authorize_with(token_name):
848 '''token_name is the symbolic name of the token from the api_client_authorizations fixture'''
849 arvados.config.settings()["ARVADOS_API_TOKEN"] = auth_token(token_name)
850 arvados.config.settings()["ARVADOS_API_HOST"] = os.environ.get("ARVADOS_API_HOST")
851 arvados.config.settings()["ARVADOS_API_HOST_INSECURE"] = "true"
853 class TestCaseWithServers(unittest.TestCase):
854 """TestCase to start and stop supporting Arvados servers.
856 Define any of MAIN_SERVER, KEEP_SERVER, and/or KEEP_PROXY_SERVER
857 class variables as a dictionary of keyword arguments. If you do,
858 setUpClass will start the corresponding servers by passing these
859 keyword arguments to the run, run_keep, and/or run_keep_server
860 functions, respectively. It will also set Arvados environment
861 variables to point to these servers appropriately. If you don't
862 run a Keep or Keep proxy server, setUpClass will set up a
863 temporary directory for Keep local storage, and set it as
866 tearDownClass will stop any servers started, and restore the
867 original environment.
872 KEEP_PROXY_SERVER = None
873 KEEP_WEB_SERVER = None
876 def _restore_dict(src, dest):
877 for key in list(dest.keys()):
884 cls._orig_environ = os.environ.copy()
885 cls._orig_config = arvados.config.settings().copy()
886 cls._cleanup_funcs = []
887 os.environ.pop('ARVADOS_KEEP_SERVICES', None)
888 os.environ.pop('ARVADOS_EXTERNAL_CLIENT', None)
889 for server_kwargs, start_func, stop_func in (
890 (cls.MAIN_SERVER, run, reset),
891 (cls.WS_SERVER, run_ws, stop_ws),
892 (cls.KEEP_SERVER, run_keep, stop_keep),
893 (cls.KEEP_PROXY_SERVER, run_keep_proxy, stop_keep_proxy),
894 (cls.KEEP_WEB_SERVER, run_keep_web, stop_keep_web)):
895 if server_kwargs is not None:
896 start_func(**server_kwargs)
897 cls._cleanup_funcs.append(stop_func)
898 if (cls.KEEP_SERVER is None) and (cls.KEEP_PROXY_SERVER is None):
899 cls.local_store = tempfile.mkdtemp()
900 os.environ['KEEP_LOCAL_STORE'] = cls.local_store
901 cls._cleanup_funcs.append(
902 lambda: shutil.rmtree(cls.local_store, ignore_errors=True))
904 os.environ.pop('KEEP_LOCAL_STORE', None)
905 arvados.config.initialize()
908 def tearDownClass(cls):
909 for clean_func in cls._cleanup_funcs:
911 cls._restore_dict(cls._orig_environ, os.environ)
912 cls._restore_dict(cls._orig_config, arvados.config.settings())
915 if __name__ == "__main__":
918 'start_ws', 'stop_ws',
919 'start_controller', 'stop_controller',
920 'start_keep', 'stop_keep',
921 'start_keep_proxy', 'stop_keep_proxy',
922 'start_keep-web', 'stop_keep-web',
923 'start_arv-git-httpd', 'stop_arv-git-httpd',
924 'start_nginx', 'stop_nginx', 'setup_config',
926 parser = argparse.ArgumentParser()
927 parser.add_argument('action', type=str, help="one of {}".format(actions))
928 parser.add_argument('--auth', type=str, metavar='FIXTURE_NAME', help='Print authorization info for given api_client_authorizations fixture')
929 parser.add_argument('--num-keep-servers', metavar='int', type=int, default=2, help="Number of keep servers desired")
930 parser.add_argument('--keep-blob-signing', action="store_true", help="Enable blob signing for keepstore servers")
932 args = parser.parse_args()
934 if args.action not in actions:
935 print("Unrecognized action '{}'. Actions are: {}.".
936 format(args.action, actions),
939 if args.action == 'start':
940 stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
941 run(leave_running_atexit=True)
942 host = os.environ['ARVADOS_API_HOST']
943 if args.auth is not None:
944 token = auth_token(args.auth)
945 print("export ARVADOS_API_TOKEN={}".format(pipes.quote(token)))
946 print("export ARVADOS_API_HOST={}".format(pipes.quote(host)))
947 print("export ARVADOS_API_HOST_INSECURE=true")
950 elif args.action == 'stop':
951 stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
952 elif args.action == 'start_ws':
954 elif args.action == 'stop_ws':
956 elif args.action == 'start_controller':
958 elif args.action == 'stop_controller':
960 elif args.action == 'start_keep':
961 run_keep(blob_signing=args.keep_blob_signing, num_servers=args.num_keep_servers)
962 elif args.action == 'stop_keep':
963 stop_keep(num_servers=args.num_keep_servers)
964 elif args.action == 'start_keep_proxy':
966 elif args.action == 'stop_keep_proxy':
968 elif args.action == 'start_arv-git-httpd':
970 elif args.action == 'stop_arv-git-httpd':
972 elif args.action == 'start_keep-web':
974 elif args.action == 'stop_keep-web':
976 elif args.action == 'start_nginx':
978 print("export ARVADOS_API_HOST=0.0.0.0:{}".format(external_port_from_config('Controller')))
979 elif args.action == 'stop_nginx':
981 elif args.action == 'setup_config':
984 raise Exception("action recognized but not implemented!?")