1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: Apache-2.0
24 from urllib.parse import urlparse
26 MY_DIRNAME = os.path.dirname(os.path.realpath(__file__))
27 if __name__ == '__main__' and os.path.exists(
28 os.path.join(MY_DIRNAME, '..', 'arvados', '__init__.py')):
29 # We're being launched to support another test suite.
30 # Add the Python SDK source to the library path.
31 sys.path.insert(1, os.path.dirname(MY_DIRNAME))
36 # This module starts subprocesses and records them in pidfiles so they
37 # can be managed by other processes (incl. after this process
38 # exits). But if we don't keep a reference to each subprocess object
39 # somewhere, the subprocess destructor runs, and we get a lot of
40 # ResourceWarning noise in test logs. This is our bucket of subprocess
41 # objects whose destructors we don't want to run but are otherwise
43 _detachedSubprocesses = []
45 ARVADOS_DIR = os.path.realpath(os.path.join(MY_DIRNAME, '../../..'))
46 SERVICES_SRC_DIR = os.path.join(ARVADOS_DIR, 'services')
48 # Work around https://bugs.python.org/issue27805, should be no longer
49 # necessary from sometime in Python 3.8.x
50 if not os.environ.get('ARVADOS_DEBUG', ''):
55 if 'GOPATH' in os.environ:
56 # Add all GOPATH bin dirs to PATH -- but insert them after the
57 # ruby gems bin dir, to ensure "bundle" runs the Ruby bundler
58 # command, not the golang.org/x/tools/cmd/bundle command.
59 gopaths = os.environ['GOPATH'].split(':')
60 addbins = [os.path.join(path, 'bin') for path in gopaths]
62 for path in os.environ['PATH'].split(':'):
64 if os.path.exists(os.path.join(path, 'bundle')):
68 os.environ['PATH'] = ':'.join(newbins)
70 TEST_TMPDIR = os.path.join(ARVADOS_DIR, 'tmp')
71 if not os.path.exists(TEST_TMPDIR):
76 _cached_db_config = {}
77 _already_used_port = {}
79 def find_server_pid(PID_PATH, wait=10):
83 while (not good_pid) and (now <= timeout):
86 with open(PID_PATH, 'r') as f:
87 server_pid = int(f.read())
88 good_pid = (os.kill(server_pid, 0) is None)
89 except EnvironmentError:
98 def kill_server_pid(pidfile, wait=10, passenger_root=False):
99 # Must re-import modules in order to work during atexit
107 deadline = now + wait
110 # First try to shut down nicely
111 restore_cwd = os.getcwd()
112 os.chdir(passenger_root)
114 'bundle', 'exec', 'passenger', 'stop', '--pid-file', pidfile])
115 os.chdir(restore_cwd)
116 # Use up to half of the +wait+ period waiting for "passenger
117 # stop" to work. If the process hasn't exited by then, start
118 # sending TERM signals.
122 while now <= deadline and server_pid is None:
124 with open(pidfile, 'r') as f:
125 server_pid = int(f.read())
127 # No pidfile = nothing to kill.
129 except ValueError as error:
130 # Pidfile exists, but we can't parse it. Perhaps the
131 # server has created the file but hasn't written its PID
133 print("Parse error reading pidfile {}: {}".format(pidfile, error),
138 while now <= deadline:
140 exited, _ = os.waitpid(server_pid, os.WNOHANG)
142 _remove_pidfile(pidfile)
145 # already exited, or isn't our child process
149 os.kill(server_pid, signal.SIGTERM)
150 print("Sent SIGTERM to {} ({})".format(server_pid, pidfile),
152 except OSError as error:
153 if error.errno == errno.ESRCH:
154 # Thrown by os.getpgid() or os.kill() if the process
155 # does not exist, i.e., our work here is done.
156 _remove_pidfile(pidfile)
162 print("Server PID {} ({}) did not exit, giving up after {}s".
163 format(server_pid, pidfile, wait),
166 def _remove_pidfile(pidfile):
170 if os.path.lexists(pidfile):
173 def find_available_port():
174 """Return an IPv4 port number that is not in use right now.
176 We assume whoever needs to use the returned port is able to reuse
177 a recently used port without waiting for TIME_WAIT (see
178 SO_REUSEADDR / SO_REUSEPORT).
180 Some opportunity for races here, but it's better than choosing
181 something at random and not checking at all. If all of our servers
182 (hey Passenger) knew that listening on port 0 was a thing, the OS
183 would take care of the races, and this wouldn't be needed at all.
186 global _already_used_port
188 sock = socket.socket()
189 sock.bind(('0.0.0.0', 0))
190 port = sock.getsockname()[1]
192 if port not in _already_used_port:
193 _already_used_port[port] = True
196 def _wait_until_port_listens(port, timeout=10, warn=True):
197 """Wait for a process to start listening on the given port.
199 If nothing listens on the port within the specified timeout (given
200 in seconds), print a warning on stderr before returning.
203 subprocess.check_output(['which', 'netstat'])
204 except subprocess.CalledProcessError:
205 print("WARNING: No `netstat` -- cannot wait for port to listen. "+
206 "Sleeping 0.5 and hoping for the best.",
210 deadline = time.time() + timeout
211 while time.time() < deadline:
212 if re.search(r'\ntcp.*:'+str(port)+' .* LISTEN *\n', subprocess.check_output(['netstat', '-Wln']).decode()):
217 "WARNING: Nothing is listening on port {} (waited {} seconds).".
218 format(port, timeout),
222 def _logfilename(label):
223 """Set up a labelled log file, and return a path to write logs to.
225 Normally, the returned path is {tmpdir}/{label}.log.
227 In debug mode, logs are also written to stderr, with [label]
228 prepended to each line. The returned path is a FIFO.
230 +label+ should contain only alphanumerics: it is also used as part
231 of the FIFO filename.
234 logfilename = os.path.join(TEST_TMPDIR, label+'.log')
235 if not os.environ.get('ARVADOS_DEBUG', ''):
237 fifo = os.path.join(TEST_TMPDIR, label+'.fifo')
240 except OSError as error:
241 if error.errno != errno.ENOENT:
243 os.mkfifo(fifo, 0o700)
244 stdbuf = ['stdbuf', '-i0', '-oL', '-eL']
245 # open(fifo, 'r') would block waiting for someone to open the fifo
246 # for writing, so we need a separate cat process to open it for
248 cat = subprocess.Popen(
249 stdbuf+['cat', fifo],
250 stdin=subprocess.DEVNULL,
251 stdout=subprocess.PIPE)
252 _detachedSubprocesses.append(cat)
253 tee = subprocess.Popen(
254 stdbuf+['tee', '-a', logfilename],
256 stdout=subprocess.PIPE)
257 _detachedSubprocesses.append(tee)
258 sed = subprocess.Popen(
259 stdbuf+['sed', '-e', 's/^/['+label+'] /'],
262 _detachedSubprocesses.append(sed)
265 def _service_environ():
266 """Return an environment mapping suitable for running an arvados
268 env = dict(os.environ)
269 env['ARVADOS_USE_KEEP_ACCESSIBLE_API'] = 'true'
272 def run(leave_running_atexit=False):
273 """Ensure an API server is running, and ARVADOS_API_* env vars have
274 admin credentials for it.
276 If ARVADOS_TEST_API_HOST is set, a parent process has started a
277 test server for us to use: we just need to reset() it using the
280 If a previous call to run() started a new server process, and it
281 is still running, we just need to reset() it to fixture state and
284 If neither of those options work out, we'll really start a new
289 # Delete cached discovery documents.
291 # This will clear cached docs that belong to other processes (like
292 # concurrent test suites) even if they're still running. They should
293 # be able to tolerate that.
294 for fn in glob.glob(os.path.join(
295 str(arvados.http_cache('discovery')),
296 '*,arvados,v1,rest,*')):
299 pid_file = _pidfile('api')
300 pid_file_ok = find_server_pid(pid_file, 0)
302 existing_api_host = os.environ.get('ARVADOS_TEST_API_HOST', my_api_host)
303 if existing_api_host and pid_file_ok:
304 if existing_api_host == my_api_host:
308 # Fall through to shutdown-and-start case.
311 # Server was provided by parent. Can't recover if it's
315 # Before trying to start up our own server, call stop() to avoid
316 # "Phusion Passenger Standalone is already running on PID 12345".
317 # (If we've gotten this far, ARVADOS_TEST_API_HOST isn't set, so
318 # we know the server is ours to kill.)
321 restore_cwd = os.getcwd()
322 api_src_dir = os.path.join(SERVICES_SRC_DIR, 'api')
323 os.chdir(api_src_dir)
325 # Either we haven't started a server of our own yet, or it has
326 # died, or we have lost our credentials, or something else is
327 # preventing us from calling reset(). Start a new one.
329 if not os.path.exists('tmp'):
332 if not os.path.exists('tmp/api'):
333 os.makedirs('tmp/api')
335 if not os.path.exists('tmp/logs'):
336 os.makedirs('tmp/logs')
338 # Customizing the passenger config template is the only documented
339 # way to override the default passenger_stat_throttle_rate (10 s).
340 # In the testing environment, we want restart.txt to take effect
342 resdir = subprocess.check_output(['bundle', 'exec', 'passenger-config', 'about', 'resourcesdir']).decode().rstrip()
343 with open(resdir + '/templates/standalone/config.erb') as f:
345 newtemplate = re.sub(r'http \{', 'http {\n passenger_stat_throttle_rate 0;', template)
346 if newtemplate == template:
347 raise "template edit failed"
348 with open('tmp/passenger-nginx.conf.erb', 'w') as f:
351 port = internal_port_from_config("RailsAPI")
352 env = _service_environ()
353 env['RAILS_ENV'] = 'test'
354 env['ARVADOS_RAILS_LOG_TO_STDOUT'] = '1'
355 env.pop('ARVADOS_WEBSOCKETS', None)
356 env.pop('ARVADOS_TEST_API_HOST', None)
357 env.pop('ARVADOS_API_HOST', None)
358 env.pop('ARVADOS_API_HOST_INSECURE', None)
359 env.pop('ARVADOS_API_TOKEN', None)
360 logf = open(_logfilename('railsapi'), WRITE_MODE)
361 railsapi = subprocess.Popen(
363 'passenger', 'start', '-p{}'.format(port),
364 '--nginx-config-template', 'tmp/passenger-nginx.conf.erb',
365 '--no-friendly-error-pages',
366 '--disable-anonymous-telemetry',
367 '--disable-security-update-check',
368 '--pid-file', pid_file,
369 '--log-file', '/dev/stdout',
371 '--ssl-certificate', 'tmp/self-signed.pem',
372 '--ssl-certificate-key', 'tmp/self-signed.key'],
374 stdin=subprocess.DEVNULL,
377 _detachedSubprocesses.append(railsapi)
379 if not leave_running_atexit:
380 atexit.register(kill_server_pid, pid_file, passenger_root=api_src_dir)
382 my_api_host = "127.0.0.1:"+str(port)
383 os.environ['ARVADOS_API_HOST'] = my_api_host
385 # Make sure the server has written its pid file and started
386 # listening on its TCP port
387 _wait_until_port_listens(port)
388 find_server_pid(pid_file)
391 os.chdir(restore_cwd)
394 """Reset the test server to fixture state.
396 This resets the ARVADOS_TEST_API_HOST provided by a parent process
397 if any, otherwise the server started by run().
399 It also resets ARVADOS_* environment vars to point to the test
400 server with admin credentials.
402 existing_api_host = os.environ.get('ARVADOS_TEST_API_HOST', my_api_host)
403 token = auth_token('admin')
404 httpclient = httplib2.Http(ca_certs=os.path.join(
405 SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem'))
407 'https://{}/database/reset'.format(existing_api_host),
409 headers={'Authorization': 'Bearer {}'.format(token), 'Connection':'close'})
411 os.environ['ARVADOS_API_HOST_INSECURE'] = 'true'
412 os.environ['ARVADOS_API_TOKEN'] = token
413 os.environ['ARVADOS_API_HOST'] = existing_api_host
415 def stop(force=False):
416 """Stop the API server, if one is running.
418 If force==False, kill it only if we started it ourselves. (This
419 supports the use case where a Python test suite calls run(), but
420 run() just uses the ARVADOS_TEST_API_HOST provided by the parent
421 process, and the test suite cleans up after itself by calling
422 stop(). In this case the test server provided by the parent
423 process should be left alone.)
425 If force==True, kill it even if we didn't start it
426 ourselves. (This supports the use case in __main__, where "run"
427 and "stop" happen in different processes.)
430 if force or my_api_host is not None:
431 kill_server_pid(_pidfile('api'))
435 with open(os.environ["ARVADOS_CONFIG"]) as f:
436 return yaml.safe_load(f)
438 def internal_port_from_config(service, idx=0):
440 sorted(list(get_config()["Clusters"]["zzzzz"]["Services"][service]["InternalURLs"].keys()))[idx]).
441 netloc.split(":")[1])
443 def external_port_from_config(service):
444 return int(urlparse(get_config()["Clusters"]["zzzzz"]["Services"][service]["ExternalURL"]).netloc.split(":")[1])
446 def run_controller():
447 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
450 logf = open(_logfilename('controller'), WRITE_MODE)
451 port = internal_port_from_config("Controller")
452 controller = subprocess.Popen(
453 ["arvados-server", "controller"],
454 env=_service_environ(),
455 stdin=subprocess.DEVNULL,
459 _detachedSubprocesses.append(controller)
460 with open(_pidfile('controller'), 'w') as f:
461 f.write(str(controller.pid))
462 _wait_until_port_listens(port)
465 def stop_controller():
466 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
468 kill_server_pid(_pidfile('controller'))
471 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
474 port = internal_port_from_config("Websocket")
475 logf = open(_logfilename('ws'), WRITE_MODE)
476 ws = subprocess.Popen(
477 ["arvados-server", "ws"],
478 env=_service_environ(),
479 stdin=subprocess.DEVNULL,
483 _detachedSubprocesses.append(ws)
484 with open(_pidfile('ws'), 'w') as f:
486 _wait_until_port_listens(port)
490 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
492 kill_server_pid(_pidfile('ws'))
494 def _start_keep(n, blob_signing=False):
495 datadir = os.path.join(TEST_TMPDIR, "keep%d.data"%n)
496 if os.path.exists(datadir):
497 shutil.rmtree(datadir)
499 port = internal_port_from_config("Keepstore", idx=n)
501 # Currently, if there are multiple InternalURLs for a single host,
502 # the only way to tell a keepstore process which one it's supposed
503 # to listen on is to supply a redacted version of the config, with
504 # the other InternalURLs removed.
505 conf = os.path.join(TEST_TMPDIR, "keep%d.yaml"%n)
506 confdata = get_config()
507 confdata['Clusters']['zzzzz']['Services']['Keepstore']['InternalURLs'] = {"http://127.0.0.1:%d"%port: {}}
508 confdata['Clusters']['zzzzz']['Collections']['BlobSigning'] = blob_signing
509 with open(conf, 'w') as f:
510 yaml.safe_dump(confdata, f)
511 keep_cmd = ["arvados-server", "keepstore", "-config", conf]
513 with open(_logfilename('keep{}'.format(n)), WRITE_MODE) as logf:
514 child = subprocess.Popen(
516 env=_service_environ(),
517 stdin=subprocess.DEVNULL,
521 _detachedSubprocesses.append(child)
523 print('child.pid is %d'%child.pid, file=sys.stderr)
524 with open(_pidfile('keep{}'.format(n)), 'w') as f:
525 f.write(str(child.pid))
527 _wait_until_port_listens(port)
531 def run_keep(num_servers=2, **kwargs):
532 stop_keep(num_servers)
536 host=os.environ['ARVADOS_API_HOST'],
537 token=os.environ['ARVADOS_API_TOKEN'],
540 for d in api.keep_services().list(filters=[['service_type','=','disk']]).execute()['items']:
541 api.keep_services().delete(uuid=d['uuid']).execute()
543 for d in range(0, num_servers):
544 port = _start_keep(d, **kwargs)
545 svc = api.keep_services().create(body={'keep_service': {
546 'uuid': 'zzzzz-bi6l4-keepdisk{:07d}'.format(d),
547 'service_host': 'localhost',
548 'service_port': port,
549 'service_type': 'disk',
550 'service_ssl_flag': False,
553 # If keepproxy and/or keep-web is running, send SIGHUP to make
554 # them discover the new keepstore services.
555 for svc in ('keepproxy', 'keep-web'):
556 pidfile = _pidfile(svc)
557 if os.path.exists(pidfile):
559 with open(pidfile) as pid:
560 os.kill(int(pid.read()), signal.SIGHUP)
565 kill_server_pid(_pidfile('keep{}'.format(n)))
567 def stop_keep(num_servers=2):
568 for n in range(0, num_servers):
571 def run_keep_proxy():
572 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
573 os.environ["ARVADOS_KEEP_SERVICES"] = "http://localhost:{}".format(internal_port_from_config('Keepproxy'))
577 port = internal_port_from_config("Keepproxy")
578 env = _service_environ()
579 env['ARVADOS_API_TOKEN'] = auth_token('anonymous')
580 logf = open(_logfilename('keepproxy'), WRITE_MODE)
581 kp = subprocess.Popen(
582 ['arvados-server', 'keepproxy'],
584 stdin=subprocess.DEVNULL,
588 _detachedSubprocesses.append(kp)
590 with open(_pidfile('keepproxy'), 'w') as f:
592 _wait_until_port_listens(port)
594 print("Using API %s token %s" % (os.environ['ARVADOS_API_HOST'], auth_token('admin')), file=sys.stdout)
597 host=os.environ['ARVADOS_API_HOST'],
598 token=auth_token('admin'),
600 for d in api.keep_services().list(
601 filters=[['service_type','=','proxy']]).execute()['items']:
602 api.keep_services().delete(uuid=d['uuid']).execute()
603 api.keep_services().create(body={'keep_service': {
604 'service_host': 'localhost',
605 'service_port': port,
606 'service_type': 'proxy',
607 'service_ssl_flag': False,
609 os.environ["ARVADOS_KEEP_SERVICES"] = "http://localhost:{}".format(port)
610 _wait_until_port_listens(port)
612 def stop_keep_proxy():
613 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
615 kill_server_pid(_pidfile('keepproxy'))
618 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
622 keepwebport = internal_port_from_config("WebDAV")
623 logf = open(_logfilename('keep-web'), WRITE_MODE)
624 keepweb = subprocess.Popen(
625 ['arvados-server', 'keep-web'],
626 env=_service_environ(),
627 stdin=subprocess.DEVNULL,
630 _detachedSubprocesses.append(keepweb)
631 with open(_pidfile('keep-web'), 'w') as f:
632 f.write(str(keepweb.pid))
633 _wait_until_port_listens(keepwebport)
636 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
638 kill_server_pid(_pidfile('keep-web'))
641 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
645 nginxconf['UPSTREAMHOST'] = '127.0.0.1'
646 nginxconf['LISTENHOST'] = '127.0.0.1'
647 nginxconf['CONTROLLERPORT'] = internal_port_from_config("Controller")
648 nginxconf['ARVADOS_API_HOST'] = "0.0.0.0:" + str(external_port_from_config("Controller"))
649 nginxconf['CONTROLLERSSLPORT'] = external_port_from_config("Controller")
650 nginxconf['KEEPWEBPORT'] = internal_port_from_config("WebDAV")
651 nginxconf['KEEPWEBDLSSLPORT'] = external_port_from_config("WebDAVDownload")
652 nginxconf['KEEPWEBSSLPORT'] = external_port_from_config("WebDAV")
653 nginxconf['KEEPPROXYPORT'] = internal_port_from_config("Keepproxy")
654 nginxconf['KEEPPROXYSSLPORT'] = external_port_from_config("Keepproxy")
655 nginxconf['HEALTHPORT'] = internal_port_from_config("Health")
656 nginxconf['HEALTHSSLPORT'] = external_port_from_config("Health")
657 nginxconf['WSPORT'] = internal_port_from_config("Websocket")
658 nginxconf['WSSSLPORT'] = external_port_from_config("Websocket")
659 nginxconf['WORKBENCH1SSLPORT'] = external_port_from_config("Workbench1")
660 nginxconf['WORKBENCH2PORT'] = internal_port_from_config("Workbench2")
661 nginxconf['WORKBENCH2SSLPORT'] = external_port_from_config("Workbench2")
662 nginxconf['SSLCERT'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem')
663 nginxconf['SSLKEY'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.key')
664 nginxconf['ACCESSLOG'] = _logfilename('nginx_access')
665 nginxconf['ERRORLOG'] = _logfilename('nginx_error')
666 nginxconf['TMPDIR'] = TEST_TMPDIR + '/nginx'
667 nginxconf['INTERNALSUBNETS'] = '169.254.0.0/16 0;'
669 conftemplatefile = os.path.join(MY_DIRNAME, 'nginx.conf')
670 conffile = os.path.join(TEST_TMPDIR, 'nginx.conf')
671 with open(conffile, 'w') as f:
673 r'{{([A-Z]+[A-Z0-9]+)}}',
674 lambda match: str(nginxconf.get(match.group(1))),
675 open(conftemplatefile).read()))
677 env = os.environ.copy()
678 env['PATH'] = env['PATH']+':/sbin:/usr/sbin:/usr/local/sbin'
680 nginx = subprocess.Popen(
682 '-g', 'error_log stderr notice; pid '+_pidfile('nginx')+';',
685 stdin=subprocess.DEVNULL,
687 _detachedSubprocesses.append(nginx)
688 _wait_until_port_listens(nginxconf['CONTROLLERSSLPORT'])
691 rails_api_port = find_available_port()
692 controller_port = find_available_port()
693 controller_external_port = find_available_port()
694 websocket_port = find_available_port()
695 websocket_external_port = find_available_port()
696 workbench1_external_port = find_available_port()
697 workbench2_port = find_available_port()
698 workbench2_external_port = find_available_port()
699 health_httpd_port = find_available_port()
700 health_httpd_external_port = find_available_port()
701 keepproxy_port = find_available_port()
702 keepproxy_external_port = find_available_port()
703 keepstore_ports = sorted([str(find_available_port()) for _ in range(0,4)])
704 keep_web_port = find_available_port()
705 keep_web_external_port = find_available_port()
706 keep_web_dl_external_port = find_available_port()
708 configsrc = os.environ.get("CONFIGSRC", None)
710 clusterconf = os.path.join(configsrc, "config.yml")
711 print("Getting config from %s" % clusterconf, file=sys.stderr)
712 pgconnection = yaml.safe_load(open(clusterconf))["Clusters"]["zzzzz"]["PostgreSQL"]["Connection"]
714 # assume "arvados-server install -type test" has set up the
715 # conventional db credentials
717 "client_encoding": "utf8",
720 "dbname": "arvados_test",
722 "password": "insecure_arvados_test",
725 localhost = "127.0.0.1"
729 "https://%s:%s"%(localhost, rails_api_port): {},
733 "ExternalURL": "https://%s:%s" % (localhost, controller_external_port),
735 "http://%s:%s"%(localhost, controller_port): {},
739 "ExternalURL": "wss://%s:%s/websocket" % (localhost, websocket_external_port),
741 "http://%s:%s"%(localhost, websocket_port): {},
745 "ExternalURL": "https://%s:%s/" % (localhost, workbench1_external_port),
748 "ExternalURL": "https://%s:%s/" % (localhost, workbench2_external_port),
750 "http://%s:%s"%(localhost, workbench2_port): {},
754 "ExternalURL": "https://%s:%s" % (localhost, health_httpd_external_port),
756 "http://%s:%s"%(localhost, health_httpd_port): {}
761 "http://%s:%s"%(localhost, port): {} for port in keepstore_ports
765 "ExternalURL": "https://%s:%s" % (localhost, keepproxy_external_port),
767 "http://%s:%s"%(localhost, keepproxy_port): {},
771 "ExternalURL": "https://%s:%s" % (localhost, keep_web_external_port),
773 "http://%s:%s"%(localhost, keep_web_port): {},
777 "ExternalURL": "https://%s:%s" % (localhost, keep_web_dl_external_port),
779 "http://%s:%s"%(localhost, keep_web_port): {},
787 "ManagementToken": "e687950a23c3a9bceec28c6223a06c79",
788 "SystemRootToken": auth_token('system_user'),
790 "RequestTimeout": "30s",
791 "LockBeforeUpdate": True,
798 "Email": "alice@example.com",
805 # Hostname used by lib/controller/localdb/login_docker_test
806 # Other settings are the defaults for the
807 # bitnami/openldap Docker image it uses
808 "URL": "ldap://arvados-test-openldap:1389/",
810 "SearchBase": "dc=example,dc=org",
811 "SearchBindUser": "cn=admin,dc=example,dc=org",
812 "SearchBindPassword": "adminpassword",
816 # Without this specific DefaultEmailDomain, inserted users
817 # would prevent subsequent database/reset from working (see
818 # database_controller.rb).
819 "DefaultEmailDomain": "example.com",
823 "LogLevel": ('info' if os.environ.get('ARVADOS_DEBUG', '') in ['','0'] else 'debug'),
826 "Connection": pgconnection,
831 "Services": services,
833 "AnonymousUserToken": auth_token('anonymous'),
834 "UserProfileNotificationAddress": "arvados@example.com",
837 "CollectionVersioning": True,
838 "BlobSigningKey": "zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc",
839 "TrustAllContent": False,
840 "ForwardSlashNameSubstitution": "/",
841 "TrashSweepInterval": "-1s", # disable, otherwise test cases can't acquire dblock
844 "LocalKeepBlobBuffersPerVCPU": 0,
845 "SupportedDockerImageFormats": {"v1": {}},
852 "zzzzz-nyw5e-%015d"%n: {
854 "http://%s:%s" % (localhost, keepstore_ports[n]): {},
856 "Driver": "Directory",
857 "DriverParameters": {
858 "Root": os.path.join(TEST_TMPDIR, "keep%d.data"%n),
860 } for n in range(len(keepstore_ports))
866 conf = os.path.join(TEST_TMPDIR, 'arvados.yml')
867 with open(conf, 'w') as f:
868 yaml.safe_dump(config, f)
870 ex = "export ARVADOS_CONFIG="+conf
875 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
877 kill_server_pid(_pidfile('nginx'))
879 def _pidfile(program):
880 return os.path.join(TEST_TMPDIR, program + '.pid')
883 '''load a fixture yaml file'''
884 with open(os.path.join(SERVICES_SRC_DIR, 'api', "test", "fixtures",
888 trim_index = yaml_file.index("# Test Helper trims the rest of the file")
889 yaml_file = yaml_file[0:trim_index]
892 return yaml.safe_load(yaml_file)
894 def auth_token(token_name):
895 return fixture("api_client_authorizations")[token_name]["api_token"]
897 def authorize_with(token_name):
898 '''token_name is the symbolic name of the token from the api_client_authorizations fixture'''
899 arvados.config.settings()["ARVADOS_API_TOKEN"] = auth_token(token_name)
900 arvados.config.settings()["ARVADOS_API_HOST"] = os.environ.get("ARVADOS_API_HOST")
901 arvados.config.settings()["ARVADOS_API_HOST_INSECURE"] = "true"
903 class TestCaseWithServers(unittest.TestCase):
904 """TestCase to start and stop supporting Arvados servers.
906 Define any of MAIN_SERVER, KEEP_SERVER, and/or KEEP_PROXY_SERVER
907 class variables as a dictionary of keyword arguments. If you do,
908 setUpClass will start the corresponding servers by passing these
909 keyword arguments to the run, run_keep, and/or run_keep_server
910 functions, respectively. It will also set Arvados environment
911 variables to point to these servers appropriately. If you don't
912 run a Keep or Keep proxy server, setUpClass will set up a
913 temporary directory for Keep local storage, and set it as
916 tearDownClass will stop any servers started, and restore the
917 original environment.
922 KEEP_PROXY_SERVER = None
923 KEEP_WEB_SERVER = None
926 def _restore_dict(src, dest):
927 for key in list(dest.keys()):
934 cls._orig_environ = os.environ.copy()
935 cls._orig_config = arvados.config.settings().copy()
936 cls._cleanup_funcs = []
937 os.environ.pop('ARVADOS_KEEP_SERVICES', None)
938 for server_kwargs, start_func, stop_func in (
939 (cls.MAIN_SERVER, run, reset),
940 (cls.WS_SERVER, run_ws, stop_ws),
941 (cls.KEEP_SERVER, run_keep, stop_keep),
942 (cls.KEEP_PROXY_SERVER, run_keep_proxy, stop_keep_proxy),
943 (cls.KEEP_WEB_SERVER, run_keep_web, stop_keep_web)):
944 if server_kwargs is not None:
945 start_func(**server_kwargs)
946 cls._cleanup_funcs.append(stop_func)
947 if (cls.KEEP_SERVER is None) and (cls.KEEP_PROXY_SERVER is None):
948 cls.local_store = tempfile.mkdtemp()
949 os.environ['KEEP_LOCAL_STORE'] = cls.local_store
950 cls._cleanup_funcs.append(
951 lambda: shutil.rmtree(cls.local_store, ignore_errors=True))
953 os.environ.pop('KEEP_LOCAL_STORE', None)
954 arvados.config.initialize()
957 def tearDownClass(cls):
958 for clean_func in cls._cleanup_funcs:
960 cls._restore_dict(cls._orig_environ, os.environ)
961 cls._restore_dict(cls._orig_config, arvados.config.settings())
964 if __name__ == "__main__":
967 'start_ws', 'stop_ws',
968 'start_controller', 'stop_controller',
969 'start_keep', 'stop_keep',
970 'start_keep_proxy', 'stop_keep_proxy',
971 'start_keep-web', 'stop_keep-web',
972 'start_nginx', 'stop_nginx', 'setup_config',
974 parser = argparse.ArgumentParser()
979 help="one of %(choices)s",
981 parser.add_argument('--auth', type=str, metavar='FIXTURE_NAME', help='Print authorization info for given api_client_authorizations fixture')
982 parser.add_argument('--num-keep-servers', metavar='int', type=int, default=2, help="Number of keep servers desired")
983 parser.add_argument('--keep-blob-signing', action="store_true", help="Enable blob signing for keepstore servers")
985 args = parser.parse_args()
986 # Create a new process group so our child processes don't exit on
987 # ^C in run-tests.sh interactive mode.
989 if args.action == 'start':
990 stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
991 run(leave_running_atexit=True)
992 host = os.environ['ARVADOS_API_HOST']
993 if args.auth is not None:
994 token = auth_token(args.auth)
995 print("export ARVADOS_API_TOKEN={}".format(shlex.quote(token)))
996 print("export ARVADOS_API_HOST={}".format(shlex.quote(host)))
997 print("export ARVADOS_API_HOST_INSECURE=true")
998 print("export ARVADOS_USE_KEEP_ACCESSIBLE_API=true")
1001 elif args.action == 'stop':
1002 stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
1003 elif args.action == 'start_ws':
1005 elif args.action == 'stop_ws':
1007 elif args.action == 'start_controller':
1009 elif args.action == 'stop_controller':
1011 elif args.action == 'start_keep':
1012 run_keep(blob_signing=args.keep_blob_signing, num_servers=args.num_keep_servers)
1013 elif args.action == 'stop_keep':
1014 stop_keep(num_servers=args.num_keep_servers)
1015 elif args.action == 'start_keep_proxy':
1017 elif args.action == 'stop_keep_proxy':
1019 elif args.action == 'start_keep-web':
1021 elif args.action == 'stop_keep-web':
1023 elif args.action == 'start_nginx':
1025 print("export ARVADOS_API_HOST=0.0.0.0:{}".format(external_port_from_config('Controller')))
1026 elif args.action == 'stop_nginx':
1028 elif args.action == 'setup_config':
1031 raise Exception("action recognized but not implemented!?")