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=open('/dev/null'),
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=open('/dev/null'),
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=open('/dev/null'),
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=open('/dev/null'),
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 with open('/dev/null') as _stdin:
515 child = subprocess.Popen(
517 env=_service_environ(),
522 _detachedSubprocesses.append(child)
524 print('child.pid is %d'%child.pid, file=sys.stderr)
525 with open(_pidfile('keep{}'.format(n)), 'w') as f:
526 f.write(str(child.pid))
528 _wait_until_port_listens(port)
532 def run_keep(num_servers=2, **kwargs):
533 stop_keep(num_servers)
537 host=os.environ['ARVADOS_API_HOST'],
538 token=os.environ['ARVADOS_API_TOKEN'],
541 for d in api.keep_services().list(filters=[['service_type','=','disk']]).execute()['items']:
542 api.keep_services().delete(uuid=d['uuid']).execute()
544 for d in range(0, num_servers):
545 port = _start_keep(d, **kwargs)
546 svc = api.keep_services().create(body={'keep_service': {
547 'uuid': 'zzzzz-bi6l4-keepdisk{:07d}'.format(d),
548 'service_host': 'localhost',
549 'service_port': port,
550 'service_type': 'disk',
551 'service_ssl_flag': False,
554 # If keepproxy and/or keep-web is running, send SIGHUP to make
555 # them discover the new keepstore services.
556 for svc in ('keepproxy', 'keep-web'):
557 pidfile = _pidfile(svc)
558 if os.path.exists(pidfile):
560 with open(pidfile) as pid:
561 os.kill(int(pid.read()), signal.SIGHUP)
566 kill_server_pid(_pidfile('keep{}'.format(n)))
568 def stop_keep(num_servers=2):
569 for n in range(0, num_servers):
572 def run_keep_proxy():
573 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
574 os.environ["ARVADOS_KEEP_SERVICES"] = "http://localhost:{}".format(internal_port_from_config('Keepproxy'))
578 port = internal_port_from_config("Keepproxy")
579 env = _service_environ()
580 env['ARVADOS_API_TOKEN'] = auth_token('anonymous')
581 logf = open(_logfilename('keepproxy'), WRITE_MODE)
582 kp = subprocess.Popen(
583 ['arvados-server', 'keepproxy'],
585 stdin=open('/dev/null'),
589 _detachedSubprocesses.append(kp)
591 with open(_pidfile('keepproxy'), 'w') as f:
593 _wait_until_port_listens(port)
595 print("Using API %s token %s" % (os.environ['ARVADOS_API_HOST'], auth_token('admin')), file=sys.stdout)
598 host=os.environ['ARVADOS_API_HOST'],
599 token=auth_token('admin'),
601 for d in api.keep_services().list(
602 filters=[['service_type','=','proxy']]).execute()['items']:
603 api.keep_services().delete(uuid=d['uuid']).execute()
604 api.keep_services().create(body={'keep_service': {
605 'service_host': 'localhost',
606 'service_port': port,
607 'service_type': 'proxy',
608 'service_ssl_flag': False,
610 os.environ["ARVADOS_KEEP_SERVICES"] = "http://localhost:{}".format(port)
611 _wait_until_port_listens(port)
613 def stop_keep_proxy():
614 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
616 kill_server_pid(_pidfile('keepproxy'))
619 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
623 keepwebport = internal_port_from_config("WebDAV")
624 logf = open(_logfilename('keep-web'), WRITE_MODE)
625 keepweb = subprocess.Popen(
626 ['arvados-server', 'keep-web'],
627 env=_service_environ(),
628 stdin=open('/dev/null'),
631 _detachedSubprocesses.append(keepweb)
632 with open(_pidfile('keep-web'), 'w') as f:
633 f.write(str(keepweb.pid))
634 _wait_until_port_listens(keepwebport)
637 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
639 kill_server_pid(_pidfile('keep-web'))
642 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
646 nginxconf['UPSTREAMHOST'] = '127.0.0.1'
647 nginxconf['LISTENHOST'] = '127.0.0.1'
648 nginxconf['CONTROLLERPORT'] = internal_port_from_config("Controller")
649 nginxconf['ARVADOS_API_HOST'] = "0.0.0.0:" + str(external_port_from_config("Controller"))
650 nginxconf['CONTROLLERSSLPORT'] = external_port_from_config("Controller")
651 nginxconf['KEEPWEBPORT'] = internal_port_from_config("WebDAV")
652 nginxconf['KEEPWEBDLSSLPORT'] = external_port_from_config("WebDAVDownload")
653 nginxconf['KEEPWEBSSLPORT'] = external_port_from_config("WebDAV")
654 nginxconf['KEEPPROXYPORT'] = internal_port_from_config("Keepproxy")
655 nginxconf['KEEPPROXYSSLPORT'] = external_port_from_config("Keepproxy")
656 nginxconf['HEALTHPORT'] = internal_port_from_config("Health")
657 nginxconf['HEALTHSSLPORT'] = external_port_from_config("Health")
658 nginxconf['WSPORT'] = internal_port_from_config("Websocket")
659 nginxconf['WSSSLPORT'] = external_port_from_config("Websocket")
660 nginxconf['WORKBENCH1SSLPORT'] = external_port_from_config("Workbench1")
661 nginxconf['WORKBENCH2PORT'] = internal_port_from_config("Workbench2")
662 nginxconf['WORKBENCH2SSLPORT'] = external_port_from_config("Workbench2")
663 nginxconf['SSLCERT'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem')
664 nginxconf['SSLKEY'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.key')
665 nginxconf['ACCESSLOG'] = _logfilename('nginx_access')
666 nginxconf['ERRORLOG'] = _logfilename('nginx_error')
667 nginxconf['TMPDIR'] = TEST_TMPDIR + '/nginx'
668 nginxconf['INTERNALSUBNETS'] = '169.254.0.0/16 0;'
670 conftemplatefile = os.path.join(MY_DIRNAME, 'nginx.conf')
671 conffile = os.path.join(TEST_TMPDIR, 'nginx.conf')
672 with open(conffile, 'w') as f:
674 r'{{([A-Z]+[A-Z0-9]+)}}',
675 lambda match: str(nginxconf.get(match.group(1))),
676 open(conftemplatefile).read()))
678 env = os.environ.copy()
679 env['PATH'] = env['PATH']+':/sbin:/usr/sbin:/usr/local/sbin'
681 nginx = subprocess.Popen(
683 '-g', 'error_log stderr notice; pid '+_pidfile('nginx')+';',
685 env=env, stdin=open('/dev/null'), stdout=sys.stderr)
686 _detachedSubprocesses.append(nginx)
687 _wait_until_port_listens(nginxconf['CONTROLLERSSLPORT'])
690 rails_api_port = find_available_port()
691 controller_port = find_available_port()
692 controller_external_port = find_available_port()
693 websocket_port = find_available_port()
694 websocket_external_port = find_available_port()
695 workbench1_external_port = find_available_port()
696 workbench2_port = find_available_port()
697 workbench2_external_port = find_available_port()
698 health_httpd_port = find_available_port()
699 health_httpd_external_port = find_available_port()
700 keepproxy_port = find_available_port()
701 keepproxy_external_port = find_available_port()
702 keepstore_ports = sorted([str(find_available_port()) for _ in range(0,4)])
703 keep_web_port = find_available_port()
704 keep_web_external_port = find_available_port()
705 keep_web_dl_external_port = find_available_port()
707 configsrc = os.environ.get("CONFIGSRC", None)
709 clusterconf = os.path.join(configsrc, "config.yml")
710 print("Getting config from %s" % clusterconf, file=sys.stderr)
711 pgconnection = yaml.safe_load(open(clusterconf))["Clusters"]["zzzzz"]["PostgreSQL"]["Connection"]
713 # assume "arvados-server install -type test" has set up the
714 # conventional db credentials
716 "client_encoding": "utf8",
718 "dbname": "arvados_test",
720 "password": "insecure_arvados_test",
723 localhost = "127.0.0.1"
727 "https://%s:%s"%(localhost, rails_api_port): {},
731 "ExternalURL": "https://%s:%s" % (localhost, controller_external_port),
733 "http://%s:%s"%(localhost, controller_port): {},
737 "ExternalURL": "wss://%s:%s/websocket" % (localhost, websocket_external_port),
739 "http://%s:%s"%(localhost, websocket_port): {},
743 "ExternalURL": "https://%s:%s/" % (localhost, workbench1_external_port),
746 "ExternalURL": "https://%s:%s/" % (localhost, workbench2_external_port),
748 "http://%s:%s"%(localhost, workbench2_port): {},
752 "ExternalURL": "https://%s:%s" % (localhost, health_httpd_external_port),
754 "http://%s:%s"%(localhost, health_httpd_port): {}
759 "http://%s:%s"%(localhost, port): {} for port in keepstore_ports
763 "ExternalURL": "https://%s:%s" % (localhost, keepproxy_external_port),
765 "http://%s:%s"%(localhost, keepproxy_port): {},
769 "ExternalURL": "https://%s:%s" % (localhost, keep_web_external_port),
771 "http://%s:%s"%(localhost, keep_web_port): {},
775 "ExternalURL": "https://%s:%s" % (localhost, keep_web_dl_external_port),
777 "http://%s:%s"%(localhost, keep_web_port): {},
785 "ManagementToken": "e687950a23c3a9bceec28c6223a06c79",
786 "SystemRootToken": auth_token('system_user'),
788 "RequestTimeout": "30s",
789 "LockBeforeUpdate": True,
796 "Email": "alice@example.com",
803 "LogLevel": ('info' if os.environ.get('ARVADOS_DEBUG', '') in ['','0'] else 'debug'),
806 "Connection": pgconnection,
811 "Services": services,
813 "AnonymousUserToken": auth_token('anonymous'),
814 "UserProfileNotificationAddress": "arvados@example.com",
817 "CollectionVersioning": True,
818 "BlobSigningKey": "zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc",
819 "TrustAllContent": False,
820 "ForwardSlashNameSubstitution": "/",
821 "TrashSweepInterval": "-1s", # disable, otherwise test cases can't acquire dblock
824 "LocalKeepBlobBuffersPerVCPU": 0,
825 "SupportedDockerImageFormats": {"v1": {}},
832 "zzzzz-nyw5e-%015d"%n: {
834 "http://%s:%s" % (localhost, keepstore_ports[n]): {},
836 "Driver": "Directory",
837 "DriverParameters": {
838 "Root": os.path.join(TEST_TMPDIR, "keep%d.data"%n),
840 } for n in range(len(keepstore_ports))
846 conf = os.path.join(TEST_TMPDIR, 'arvados.yml')
847 with open(conf, 'w') as f:
848 yaml.safe_dump(config, f)
850 ex = "export ARVADOS_CONFIG="+conf
855 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
857 kill_server_pid(_pidfile('nginx'))
859 def _pidfile(program):
860 return os.path.join(TEST_TMPDIR, program + '.pid')
863 '''load a fixture yaml file'''
864 with open(os.path.join(SERVICES_SRC_DIR, 'api', "test", "fixtures",
868 trim_index = yaml_file.index("# Test Helper trims the rest of the file")
869 yaml_file = yaml_file[0:trim_index]
872 return yaml.safe_load(yaml_file)
874 def auth_token(token_name):
875 return fixture("api_client_authorizations")[token_name]["api_token"]
877 def authorize_with(token_name):
878 '''token_name is the symbolic name of the token from the api_client_authorizations fixture'''
879 arvados.config.settings()["ARVADOS_API_TOKEN"] = auth_token(token_name)
880 arvados.config.settings()["ARVADOS_API_HOST"] = os.environ.get("ARVADOS_API_HOST")
881 arvados.config.settings()["ARVADOS_API_HOST_INSECURE"] = "true"
883 class TestCaseWithServers(unittest.TestCase):
884 """TestCase to start and stop supporting Arvados servers.
886 Define any of MAIN_SERVER, KEEP_SERVER, and/or KEEP_PROXY_SERVER
887 class variables as a dictionary of keyword arguments. If you do,
888 setUpClass will start the corresponding servers by passing these
889 keyword arguments to the run, run_keep, and/or run_keep_server
890 functions, respectively. It will also set Arvados environment
891 variables to point to these servers appropriately. If you don't
892 run a Keep or Keep proxy server, setUpClass will set up a
893 temporary directory for Keep local storage, and set it as
896 tearDownClass will stop any servers started, and restore the
897 original environment.
902 KEEP_PROXY_SERVER = None
903 KEEP_WEB_SERVER = None
906 def _restore_dict(src, dest):
907 for key in list(dest.keys()):
914 cls._orig_environ = os.environ.copy()
915 cls._orig_config = arvados.config.settings().copy()
916 cls._cleanup_funcs = []
917 os.environ.pop('ARVADOS_KEEP_SERVICES', None)
918 for server_kwargs, start_func, stop_func in (
919 (cls.MAIN_SERVER, run, reset),
920 (cls.WS_SERVER, run_ws, stop_ws),
921 (cls.KEEP_SERVER, run_keep, stop_keep),
922 (cls.KEEP_PROXY_SERVER, run_keep_proxy, stop_keep_proxy),
923 (cls.KEEP_WEB_SERVER, run_keep_web, stop_keep_web)):
924 if server_kwargs is not None:
925 start_func(**server_kwargs)
926 cls._cleanup_funcs.append(stop_func)
927 if (cls.KEEP_SERVER is None) and (cls.KEEP_PROXY_SERVER is None):
928 cls.local_store = tempfile.mkdtemp()
929 os.environ['KEEP_LOCAL_STORE'] = cls.local_store
930 cls._cleanup_funcs.append(
931 lambda: shutil.rmtree(cls.local_store, ignore_errors=True))
933 os.environ.pop('KEEP_LOCAL_STORE', None)
934 arvados.config.initialize()
937 def tearDownClass(cls):
938 for clean_func in cls._cleanup_funcs:
940 cls._restore_dict(cls._orig_environ, os.environ)
941 cls._restore_dict(cls._orig_config, arvados.config.settings())
944 if __name__ == "__main__":
947 'start_ws', 'stop_ws',
948 'start_controller', 'stop_controller',
949 'start_keep', 'stop_keep',
950 'start_keep_proxy', 'stop_keep_proxy',
951 'start_keep-web', 'stop_keep-web',
952 'start_nginx', 'stop_nginx', 'setup_config',
954 parser = argparse.ArgumentParser()
955 parser.add_argument('action', type=str, help="one of {}".format(actions))
956 parser.add_argument('--auth', type=str, metavar='FIXTURE_NAME', help='Print authorization info for given api_client_authorizations fixture')
957 parser.add_argument('--num-keep-servers', metavar='int', type=int, default=2, help="Number of keep servers desired")
958 parser.add_argument('--keep-blob-signing', action="store_true", help="Enable blob signing for keepstore servers")
960 args = parser.parse_args()
962 if args.action not in actions:
963 print("Unrecognized action '{}'. Actions are: {}.".
964 format(args.action, actions),
967 # Create a new process group so our child processes don't exit on
968 # ^C in run-tests.sh interactive mode.
970 if args.action == 'start':
971 stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
972 run(leave_running_atexit=True)
973 host = os.environ['ARVADOS_API_HOST']
974 if args.auth is not None:
975 token = auth_token(args.auth)
976 print("export ARVADOS_API_TOKEN={}".format(shlex.quote(token)))
977 print("export ARVADOS_API_HOST={}".format(shlex.quote(host)))
978 print("export ARVADOS_API_HOST_INSECURE=true")
979 print("export ARVADOS_USE_KEEP_ACCESSIBLE_API=true")
982 elif args.action == 'stop':
983 stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
984 elif args.action == 'start_ws':
986 elif args.action == 'stop_ws':
988 elif args.action == 'start_controller':
990 elif args.action == 'stop_controller':
992 elif args.action == 'start_keep':
993 run_keep(blob_signing=args.keep_blob_signing, num_servers=args.num_keep_servers)
994 elif args.action == 'stop_keep':
995 stop_keep(num_servers=args.num_keep_servers)
996 elif args.action == 'start_keep_proxy':
998 elif args.action == 'stop_keep_proxy':
1000 elif args.action == 'start_keep-web':
1002 elif args.action == 'stop_keep-web':
1004 elif args.action == 'start_nginx':
1006 print("export ARVADOS_API_HOST=0.0.0.0:{}".format(external_port_from_config('Controller')))
1007 elif args.action == 'stop_nginx':
1009 elif args.action == 'setup_config':
1012 raise Exception("action recognized but not implemented!?")