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 port = internal_port_from_config("RailsAPI")
335 env = os.environ.copy()
336 env['RAILS_ENV'] = 'test'
337 env['ARVADOS_RAILS_LOG_TO_STDOUT'] = '1'
338 env.pop('ARVADOS_WEBSOCKETS', None)
339 env.pop('ARVADOS_TEST_API_HOST', None)
340 env.pop('ARVADOS_API_HOST', None)
341 env.pop('ARVADOS_API_HOST_INSECURE', None)
342 env.pop('ARVADOS_API_TOKEN', None)
343 logf = open(_logfilename('railsapi'), WRITE_MODE)
344 railsapi = subprocess.Popen(
346 'passenger', 'start', '-p{}'.format(port),
347 '--pid-file', pid_file,
348 '--log-file', '/dev/stdout',
350 '--ssl-certificate', 'tmp/self-signed.pem',
351 '--ssl-certificate-key', 'tmp/self-signed.key'],
352 env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
354 if not leave_running_atexit:
355 atexit.register(kill_server_pid, pid_file, passenger_root=api_src_dir)
357 my_api_host = "127.0.0.1:"+str(port)
358 os.environ['ARVADOS_API_HOST'] = my_api_host
360 # Make sure the server has written its pid file and started
361 # listening on its TCP port
362 _wait_until_port_listens(port)
363 find_server_pid(pid_file)
366 os.chdir(restore_cwd)
369 """Reset the test server to fixture state.
371 This resets the ARVADOS_TEST_API_HOST provided by a parent process
372 if any, otherwise the server started by run().
374 It also resets ARVADOS_* environment vars to point to the test
375 server with admin credentials.
377 existing_api_host = os.environ.get('ARVADOS_TEST_API_HOST', my_api_host)
378 token = auth_token('admin')
379 httpclient = httplib2.Http(ca_certs=os.path.join(
380 SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem'))
382 'https://{}/database/reset'.format(existing_api_host),
384 headers={'Authorization': 'OAuth2 {}'.format(token), 'Connection':'close'})
386 os.environ['ARVADOS_API_HOST_INSECURE'] = 'true'
387 os.environ['ARVADOS_API_TOKEN'] = token
388 os.environ['ARVADOS_API_HOST'] = existing_api_host
390 def stop(force=False):
391 """Stop the API server, if one is running.
393 If force==False, kill it only if we started it ourselves. (This
394 supports the use case where a Python test suite calls run(), but
395 run() just uses the ARVADOS_TEST_API_HOST provided by the parent
396 process, and the test suite cleans up after itself by calling
397 stop(). In this case the test server provided by the parent
398 process should be left alone.)
400 If force==True, kill it even if we didn't start it
401 ourselves. (This supports the use case in __main__, where "run"
402 and "stop" happen in different processes.)
405 if force or my_api_host is not None:
406 kill_server_pid(_pidfile('api'))
410 with open(os.environ["ARVADOS_CONFIG"]) as f:
411 return yaml.safe_load(f)
413 def internal_port_from_config(service, idx=0):
415 sorted(list(get_config()["Clusters"]["zzzzz"]["Services"][service]["InternalURLs"].keys()))[idx]).
416 netloc.split(":")[1])
418 def external_port_from_config(service):
419 return int(urlparse(get_config()["Clusters"]["zzzzz"]["Services"][service]["ExternalURL"]).netloc.split(":")[1])
421 def run_controller():
422 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
425 logf = open(_logfilename('controller'), WRITE_MODE)
426 port = internal_port_from_config("Controller")
427 controller = subprocess.Popen(
428 ["arvados-server", "controller"],
429 stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
430 with open(_pidfile('controller'), 'w') as f:
431 f.write(str(controller.pid))
432 _wait_until_port_listens(port)
435 def stop_controller():
436 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
438 kill_server_pid(_pidfile('controller'))
441 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
444 port = internal_port_from_config("Websocket")
445 logf = open(_logfilename('ws'), WRITE_MODE)
446 ws = subprocess.Popen(
447 ["arvados-server", "ws"],
448 stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
449 with open(_pidfile('ws'), 'w') as f:
451 _wait_until_port_listens(port)
455 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
457 kill_server_pid(_pidfile('ws'))
459 def _start_keep(n, blob_signing=False):
460 datadir = os.path.join(TEST_TMPDIR, "keep%d.data"%n)
461 if os.path.exists(datadir):
462 shutil.rmtree(datadir)
464 port = internal_port_from_config("Keepstore", idx=n)
466 # Currently, if there are multiple InternalURLs for a single host,
467 # the only way to tell a keepstore process which one it's supposed
468 # to listen on is to supply a redacted version of the config, with
469 # the other InternalURLs removed.
470 conf = os.path.join(TEST_TMPDIR, "keep%d.yaml"%n)
471 confdata = get_config()
472 confdata['Clusters']['zzzzz']['Services']['Keepstore']['InternalURLs'] = {"http://127.0.0.1:%d"%port: {}}
473 confdata['Clusters']['zzzzz']['Collections']['BlobSigning'] = blob_signing
474 with open(conf, 'w') as f:
475 yaml.safe_dump(confdata, f)
476 keep_cmd = ["keepstore", "-config", conf]
478 with open(_logfilename('keep{}'.format(n)), WRITE_MODE) as logf:
479 with open('/dev/null') as _stdin:
480 child = subprocess.Popen(
481 keep_cmd, stdin=_stdin, stdout=logf, stderr=logf, close_fds=True)
483 print('child.pid is %d'%child.pid, file=sys.stderr)
484 with open(_pidfile('keep{}'.format(n)), 'w') as f:
485 f.write(str(child.pid))
487 _wait_until_port_listens(port)
491 def run_keep(num_servers=2, **kwargs):
492 stop_keep(num_servers)
496 host=os.environ['ARVADOS_API_HOST'],
497 token=os.environ['ARVADOS_API_TOKEN'],
500 for d in api.keep_services().list(filters=[['service_type','=','disk']]).execute()['items']:
501 api.keep_services().delete(uuid=d['uuid']).execute()
502 for d in api.keep_disks().list().execute()['items']:
503 api.keep_disks().delete(uuid=d['uuid']).execute()
505 for d in range(0, num_servers):
506 port = _start_keep(d, **kwargs)
507 svc = api.keep_services().create(body={'keep_service': {
508 'uuid': 'zzzzz-bi6l4-keepdisk{:07d}'.format(d),
509 'service_host': 'localhost',
510 'service_port': port,
511 'service_type': 'disk',
512 'service_ssl_flag': False,
514 api.keep_disks().create(body={
515 'keep_disk': {'keep_service_uuid': svc['uuid'] }
518 # If keepproxy and/or keep-web is running, send SIGHUP to make
519 # them discover the new keepstore services.
520 for svc in ('keepproxy', 'keep-web'):
521 pidfile = _pidfile('keepproxy')
522 if os.path.exists(pidfile):
524 with open(pidfile) as pid:
525 os.kill(int(pid.read()), signal.SIGHUP)
530 kill_server_pid(_pidfile('keep{}'.format(n)))
532 def stop_keep(num_servers=2):
533 for n in range(0, num_servers):
536 def run_keep_proxy():
537 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
538 os.environ["ARVADOS_KEEP_SERVICES"] = "http://localhost:{}".format(internal_port_from_config('Keepproxy'))
542 port = internal_port_from_config("Keepproxy")
543 env = os.environ.copy()
544 env['ARVADOS_API_TOKEN'] = auth_token('anonymous')
545 logf = open(_logfilename('keepproxy'), WRITE_MODE)
546 kp = subprocess.Popen(
547 ['keepproxy'], env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
549 with open(_pidfile('keepproxy'), 'w') as f:
551 _wait_until_port_listens(port)
553 print("Using API %s token %s" % (os.environ['ARVADOS_API_HOST'], auth_token('admin')), file=sys.stdout)
556 host=os.environ['ARVADOS_API_HOST'],
557 token=auth_token('admin'),
559 for d in api.keep_services().list(
560 filters=[['service_type','=','proxy']]).execute()['items']:
561 api.keep_services().delete(uuid=d['uuid']).execute()
562 api.keep_services().create(body={'keep_service': {
563 'service_host': 'localhost',
564 'service_port': port,
565 'service_type': 'proxy',
566 'service_ssl_flag': False,
568 os.environ["ARVADOS_KEEP_SERVICES"] = "http://localhost:{}".format(port)
569 _wait_until_port_listens(port)
571 def stop_keep_proxy():
572 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
574 kill_server_pid(_pidfile('keepproxy'))
576 def run_arv_git_httpd():
577 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
581 gitport = internal_port_from_config("GitHTTP")
582 env = os.environ.copy()
583 env.pop('ARVADOS_API_TOKEN', None)
584 logf = open(_logfilename('arv-git-httpd'), WRITE_MODE)
585 agh = subprocess.Popen(['arv-git-httpd'],
586 env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
587 with open(_pidfile('arv-git-httpd'), 'w') as f:
588 f.write(str(agh.pid))
589 _wait_until_port_listens(gitport)
591 def stop_arv_git_httpd():
592 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
594 kill_server_pid(_pidfile('arv-git-httpd'))
597 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
601 keepwebport = internal_port_from_config("WebDAV")
602 env = os.environ.copy()
603 logf = open(_logfilename('keep-web'), WRITE_MODE)
604 keepweb = subprocess.Popen(
606 env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
607 with open(_pidfile('keep-web'), 'w') as f:
608 f.write(str(keepweb.pid))
609 _wait_until_port_listens(keepwebport)
612 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
614 kill_server_pid(_pidfile('keep-web'))
617 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
621 nginxconf['LISTENHOST'] = 'localhost'
622 nginxconf['CONTROLLERPORT'] = internal_port_from_config("Controller")
623 nginxconf['CONTROLLERSSLPORT'] = external_port_from_config("Controller")
624 nginxconf['KEEPWEBPORT'] = internal_port_from_config("WebDAV")
625 nginxconf['KEEPWEBDLSSLPORT'] = external_port_from_config("WebDAVDownload")
626 nginxconf['KEEPWEBSSLPORT'] = external_port_from_config("WebDAV")
627 nginxconf['KEEPPROXYPORT'] = internal_port_from_config("Keepproxy")
628 nginxconf['KEEPPROXYSSLPORT'] = external_port_from_config("Keepproxy")
629 nginxconf['GITPORT'] = internal_port_from_config("GitHTTP")
630 nginxconf['GITSSLPORT'] = external_port_from_config("GitHTTP")
631 nginxconf['HEALTHPORT'] = internal_port_from_config("Health")
632 nginxconf['HEALTHSSLPORT'] = external_port_from_config("Health")
633 nginxconf['WSPORT'] = internal_port_from_config("Websocket")
634 nginxconf['WSSSLPORT'] = external_port_from_config("Websocket")
635 nginxconf['WORKBENCH1PORT'] = internal_port_from_config("Workbench1")
636 nginxconf['WORKBENCH1SSLPORT'] = external_port_from_config("Workbench1")
637 nginxconf['SSLCERT'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem')
638 nginxconf['SSLKEY'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.key')
639 nginxconf['ACCESSLOG'] = _logfilename('nginx_access')
640 nginxconf['ERRORLOG'] = _logfilename('nginx_error')
641 nginxconf['TMPDIR'] = TEST_TMPDIR
643 conftemplatefile = os.path.join(MY_DIRNAME, 'nginx.conf')
644 conffile = os.path.join(TEST_TMPDIR, 'nginx.conf')
645 with open(conffile, 'w') as f:
647 r'{{([A-Z]+[A-Z0-9]+)}}',
648 lambda match: str(nginxconf.get(match.group(1))),
649 open(conftemplatefile).read()))
651 env = os.environ.copy()
652 env['PATH'] = env['PATH']+':/sbin:/usr/sbin:/usr/local/sbin'
654 nginx = subprocess.Popen(
656 '-g', 'error_log stderr info;',
657 '-g', 'pid '+_pidfile('nginx')+';',
659 env=env, stdin=open('/dev/null'), stdout=sys.stderr)
660 _wait_until_port_listens(nginxconf['CONTROLLERSSLPORT'])
663 rails_api_port = find_available_port()
664 controller_port = find_available_port()
665 controller_external_port = find_available_port()
666 websocket_port = find_available_port()
667 websocket_external_port = find_available_port()
668 workbench1_port = find_available_port()
669 workbench1_external_port = find_available_port()
670 git_httpd_port = find_available_port()
671 git_httpd_external_port = find_available_port()
672 health_httpd_port = find_available_port()
673 health_httpd_external_port = find_available_port()
674 keepproxy_port = find_available_port()
675 keepproxy_external_port = find_available_port()
676 keepstore_ports = sorted([str(find_available_port()) for _ in range(0,4)])
677 keep_web_port = find_available_port()
678 keep_web_external_port = find_available_port()
679 keep_web_dl_port = find_available_port()
680 keep_web_dl_external_port = find_available_port()
682 configsrc = os.environ.get("CONFIGSRC", None)
684 clusterconf = os.path.join(configsrc, "config.yml")
685 print("Getting config from %s" % clusterconf, file=sys.stderr)
686 pgconnection = yaml.safe_load(open(clusterconf))["Clusters"]["zzzzz"]["PostgreSQL"]["Connection"]
688 # assume "arvados-server install -type test" has set up the
689 # conventional db credentials
691 "client_encoding": "utf8",
693 "dbname": "arvados_test",
695 "password": "insecure_arvados_test",
698 localhost = "127.0.0.1"
702 "https://%s:%s"%(localhost, rails_api_port): {},
706 "ExternalURL": "https://%s:%s" % (localhost, controller_external_port),
708 "http://%s:%s"%(localhost, controller_port): {},
712 "ExternalURL": "wss://%s:%s/websocket" % (localhost, websocket_external_port),
714 "http://%s:%s"%(localhost, websocket_port): {},
718 "ExternalURL": "https://%s:%s/" % (localhost, workbench1_external_port),
720 "http://%s:%s"%(localhost, workbench1_port): {},
724 "ExternalURL": "https://%s:%s" % (localhost, git_httpd_external_port),
726 "http://%s:%s"%(localhost, git_httpd_port): {}
730 "ExternalURL": "https://%s:%s" % (localhost, health_httpd_external_port),
732 "http://%s:%s"%(localhost, health_httpd_port): {}
737 "http://%s:%s"%(localhost, port): {} for port in keepstore_ports
741 "ExternalURL": "https://%s:%s" % (localhost, keepproxy_external_port),
743 "http://%s:%s"%(localhost, keepproxy_port): {},
747 "ExternalURL": "https://%s:%s" % (localhost, keep_web_external_port),
749 "http://%s:%s"%(localhost, keep_web_port): {},
753 "ExternalURL": "https://%s:%s" % (localhost, keep_web_dl_external_port),
755 "http://%s:%s"%(localhost, keep_web_dl_port): {},
759 "ExternalURL": "http://localhost:3002",
766 "ManagementToken": "e687950a23c3a9bceec28c6223a06c79",
767 "SystemRootToken": auth_token('system_user'),
769 "RequestTimeout": "30s",
770 "RailsSessionSecretToken": "e24205c490ac07e028fd5f8a692dcb398bcd654eff1aef5f9fe6891994b18483",
774 "ProviderAppID": "arvados-server",
775 "ProviderAppSecret": "608dbf356a327e2d0d4932b60161e212c2d8d8f5e25690d7b622f850a990cd33",
779 "LogLevel": ('info' if os.environ.get('ARVADOS_DEBUG', '') in ['','0'] else 'debug'),
782 "Connection": pgconnection,
787 "Services": services,
789 "AnonymousUserToken": auth_token('anonymous'),
790 "UserProfileNotificationAddress": "arvados@example.com",
793 "BlobSigningKey": "zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc",
794 "TrustAllContent": False,
795 "ForwardSlashNameSubstitution": "/",
796 "TrashSweepInterval": "-1s",
799 "Repositories": os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'git', 'test'),
803 "GitInternalDir": os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'internal.git'),
805 "SupportedDockerImageFormats": {"v1": {}},
808 "zzzzz-nyw5e-%015d"%n: {
810 "http://%s:%s" % (localhost, keepstore_ports[n]): {},
812 "Driver": "Directory",
813 "DriverParameters": {
814 "Root": os.path.join(TEST_TMPDIR, "keep%d.data"%n),
816 } for n in range(len(keepstore_ports))
822 conf = os.path.join(TEST_TMPDIR, 'arvados.yml')
823 with open(conf, 'w') as f:
824 yaml.safe_dump(config, f)
826 ex = "export ARVADOS_CONFIG="+conf
831 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
833 kill_server_pid(_pidfile('nginx'))
835 def _pidfile(program):
836 return os.path.join(TEST_TMPDIR, program + '.pid')
839 '''load a fixture yaml file'''
840 with open(os.path.join(SERVICES_SRC_DIR, 'api', "test", "fixtures",
844 trim_index = yaml_file.index("# Test Helper trims the rest of the file")
845 yaml_file = yaml_file[0:trim_index]
848 return yaml.safe_load(yaml_file)
850 def auth_token(token_name):
851 return fixture("api_client_authorizations")[token_name]["api_token"]
853 def authorize_with(token_name):
854 '''token_name is the symbolic name of the token from the api_client_authorizations fixture'''
855 arvados.config.settings()["ARVADOS_API_TOKEN"] = auth_token(token_name)
856 arvados.config.settings()["ARVADOS_API_HOST"] = os.environ.get("ARVADOS_API_HOST")
857 arvados.config.settings()["ARVADOS_API_HOST_INSECURE"] = "true"
859 class TestCaseWithServers(unittest.TestCase):
860 """TestCase to start and stop supporting Arvados servers.
862 Define any of MAIN_SERVER, KEEP_SERVER, and/or KEEP_PROXY_SERVER
863 class variables as a dictionary of keyword arguments. If you do,
864 setUpClass will start the corresponding servers by passing these
865 keyword arguments to the run, run_keep, and/or run_keep_server
866 functions, respectively. It will also set Arvados environment
867 variables to point to these servers appropriately. If you don't
868 run a Keep or Keep proxy server, setUpClass will set up a
869 temporary directory for Keep local storage, and set it as
872 tearDownClass will stop any servers started, and restore the
873 original environment.
878 KEEP_PROXY_SERVER = None
879 KEEP_WEB_SERVER = None
882 def _restore_dict(src, dest):
883 for key in list(dest.keys()):
890 cls._orig_environ = os.environ.copy()
891 cls._orig_config = arvados.config.settings().copy()
892 cls._cleanup_funcs = []
893 os.environ.pop('ARVADOS_KEEP_SERVICES', None)
894 os.environ.pop('ARVADOS_EXTERNAL_CLIENT', None)
895 for server_kwargs, start_func, stop_func in (
896 (cls.MAIN_SERVER, run, reset),
897 (cls.WS_SERVER, run_ws, stop_ws),
898 (cls.KEEP_SERVER, run_keep, stop_keep),
899 (cls.KEEP_PROXY_SERVER, run_keep_proxy, stop_keep_proxy),
900 (cls.KEEP_WEB_SERVER, run_keep_web, stop_keep_web)):
901 if server_kwargs is not None:
902 start_func(**server_kwargs)
903 cls._cleanup_funcs.append(stop_func)
904 if (cls.KEEP_SERVER is None) and (cls.KEEP_PROXY_SERVER is None):
905 cls.local_store = tempfile.mkdtemp()
906 os.environ['KEEP_LOCAL_STORE'] = cls.local_store
907 cls._cleanup_funcs.append(
908 lambda: shutil.rmtree(cls.local_store, ignore_errors=True))
910 os.environ.pop('KEEP_LOCAL_STORE', None)
911 arvados.config.initialize()
914 def tearDownClass(cls):
915 for clean_func in cls._cleanup_funcs:
917 cls._restore_dict(cls._orig_environ, os.environ)
918 cls._restore_dict(cls._orig_config, arvados.config.settings())
921 if __name__ == "__main__":
924 'start_ws', 'stop_ws',
925 'start_controller', 'stop_controller',
926 'start_keep', 'stop_keep',
927 'start_keep_proxy', 'stop_keep_proxy',
928 'start_keep-web', 'stop_keep-web',
929 'start_arv-git-httpd', 'stop_arv-git-httpd',
930 'start_nginx', 'stop_nginx', 'setup_config',
932 parser = argparse.ArgumentParser()
933 parser.add_argument('action', type=str, help="one of {}".format(actions))
934 parser.add_argument('--auth', type=str, metavar='FIXTURE_NAME', help='Print authorization info for given api_client_authorizations fixture')
935 parser.add_argument('--num-keep-servers', metavar='int', type=int, default=2, help="Number of keep servers desired")
936 parser.add_argument('--keep-blob-signing', action="store_true", help="Enable blob signing for keepstore servers")
938 args = parser.parse_args()
940 if args.action not in actions:
941 print("Unrecognized action '{}'. Actions are: {}.".
942 format(args.action, actions),
945 if args.action == 'start':
946 stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
947 run(leave_running_atexit=True)
948 host = os.environ['ARVADOS_API_HOST']
949 if args.auth is not None:
950 token = auth_token(args.auth)
951 print("export ARVADOS_API_TOKEN={}".format(pipes.quote(token)))
952 print("export ARVADOS_API_HOST={}".format(pipes.quote(host)))
953 print("export ARVADOS_API_HOST_INSECURE=true")
956 elif args.action == 'stop':
957 stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
958 elif args.action == 'start_ws':
960 elif args.action == 'stop_ws':
962 elif args.action == 'start_controller':
964 elif args.action == 'stop_controller':
966 elif args.action == 'start_keep':
967 run_keep(blob_signing=args.keep_blob_signing, num_servers=args.num_keep_servers)
968 elif args.action == 'stop_keep':
969 stop_keep(num_servers=args.num_keep_servers)
970 elif args.action == 'start_keep_proxy':
972 elif args.action == 'stop_keep_proxy':
974 elif args.action == 'start_arv-git-httpd':
976 elif args.action == 'stop_arv-git-httpd':
978 elif args.action == 'start_keep-web':
980 elif args.action == 'stop_keep-web':
982 elif args.action == 'start_nginx':
984 print("export ARVADOS_API_HOST=0.0.0.0:{}".format(external_port_from_config('Controller')))
985 elif args.action == 'stop_nginx':
987 elif args.action == 'setup_config':
990 raise Exception("action recognized but not implemented!?")