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 run(leave_running_atexit=False):
266 """Ensure an API server is running, and ARVADOS_API_* env vars have
267 admin credentials for it.
269 If ARVADOS_TEST_API_HOST is set, a parent process has started a
270 test server for us to use: we just need to reset() it using the
273 If a previous call to run() started a new server process, and it
274 is still running, we just need to reset() it to fixture state and
277 If neither of those options work out, we'll really start a new
282 # Delete cached discovery documents.
284 # This will clear cached docs that belong to other processes (like
285 # concurrent test suites) even if they're still running. They should
286 # be able to tolerate that.
287 for fn in glob.glob(os.path.join(
288 str(arvados.http_cache('discovery')),
289 '*,arvados,v1,rest,*')):
292 pid_file = _pidfile('api')
293 pid_file_ok = find_server_pid(pid_file, 0)
295 existing_api_host = os.environ.get('ARVADOS_TEST_API_HOST', my_api_host)
296 if existing_api_host and pid_file_ok:
297 if existing_api_host == my_api_host:
301 # Fall through to shutdown-and-start case.
304 # Server was provided by parent. Can't recover if it's
308 # Before trying to start up our own server, call stop() to avoid
309 # "Phusion Passenger Standalone is already running on PID 12345".
310 # (If we've gotten this far, ARVADOS_TEST_API_HOST isn't set, so
311 # we know the server is ours to kill.)
314 restore_cwd = os.getcwd()
315 api_src_dir = os.path.join(SERVICES_SRC_DIR, 'api')
316 os.chdir(api_src_dir)
318 # Either we haven't started a server of our own yet, or it has
319 # died, or we have lost our credentials, or something else is
320 # preventing us from calling reset(). Start a new one.
322 if not os.path.exists('tmp'):
325 if not os.path.exists('tmp/api'):
326 os.makedirs('tmp/api')
328 if not os.path.exists('tmp/logs'):
329 os.makedirs('tmp/logs')
331 # Customizing the passenger config template is the only documented
332 # way to override the default passenger_stat_throttle_rate (10 s).
333 # In the testing environment, we want restart.txt to take effect
335 resdir = subprocess.check_output(['bundle', 'exec', 'passenger-config', 'about', 'resourcesdir']).decode().rstrip()
336 with open(resdir + '/templates/standalone/config.erb') as f:
338 newtemplate = re.sub(r'http \{', 'http {\n passenger_stat_throttle_rate 0;', template)
339 if newtemplate == template:
340 raise "template edit failed"
341 with open('tmp/passenger-nginx.conf.erb', 'w') as f:
344 port = internal_port_from_config("RailsAPI")
345 env = os.environ.copy()
346 env['RAILS_ENV'] = 'test'
347 env['ARVADOS_RAILS_LOG_TO_STDOUT'] = '1'
348 env.pop('ARVADOS_WEBSOCKETS', None)
349 env.pop('ARVADOS_TEST_API_HOST', None)
350 env.pop('ARVADOS_API_HOST', None)
351 env.pop('ARVADOS_API_HOST_INSECURE', None)
352 env.pop('ARVADOS_API_TOKEN', None)
353 logf = open(_logfilename('railsapi'), WRITE_MODE)
354 railsapi = subprocess.Popen(
356 'passenger', 'start', '-p{}'.format(port),
357 '--nginx-config-template', 'tmp/passenger-nginx.conf.erb',
358 '--no-friendly-error-pages',
359 '--disable-anonymous-telemetry',
360 '--disable-security-update-check',
361 '--pid-file', pid_file,
362 '--log-file', '/dev/stdout',
364 '--ssl-certificate', 'tmp/self-signed.pem',
365 '--ssl-certificate-key', 'tmp/self-signed.key'],
366 env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
367 _detachedSubprocesses.append(railsapi)
369 if not leave_running_atexit:
370 atexit.register(kill_server_pid, pid_file, passenger_root=api_src_dir)
372 my_api_host = "127.0.0.1:"+str(port)
373 os.environ['ARVADOS_API_HOST'] = my_api_host
375 # Make sure the server has written its pid file and started
376 # listening on its TCP port
377 _wait_until_port_listens(port)
378 find_server_pid(pid_file)
381 os.chdir(restore_cwd)
384 """Reset the test server to fixture state.
386 This resets the ARVADOS_TEST_API_HOST provided by a parent process
387 if any, otherwise the server started by run().
389 It also resets ARVADOS_* environment vars to point to the test
390 server with admin credentials.
392 existing_api_host = os.environ.get('ARVADOS_TEST_API_HOST', my_api_host)
393 token = auth_token('admin')
394 httpclient = httplib2.Http(ca_certs=os.path.join(
395 SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem'))
397 'https://{}/database/reset'.format(existing_api_host),
399 headers={'Authorization': 'OAuth2 {}'.format(token), 'Connection':'close'})
401 os.environ['ARVADOS_API_HOST_INSECURE'] = 'true'
402 os.environ['ARVADOS_API_TOKEN'] = token
403 os.environ['ARVADOS_API_HOST'] = existing_api_host
405 def stop(force=False):
406 """Stop the API server, if one is running.
408 If force==False, kill it only if we started it ourselves. (This
409 supports the use case where a Python test suite calls run(), but
410 run() just uses the ARVADOS_TEST_API_HOST provided by the parent
411 process, and the test suite cleans up after itself by calling
412 stop(). In this case the test server provided by the parent
413 process should be left alone.)
415 If force==True, kill it even if we didn't start it
416 ourselves. (This supports the use case in __main__, where "run"
417 and "stop" happen in different processes.)
420 if force or my_api_host is not None:
421 kill_server_pid(_pidfile('api'))
425 with open(os.environ["ARVADOS_CONFIG"]) as f:
426 return yaml.safe_load(f)
428 def internal_port_from_config(service, idx=0):
430 sorted(list(get_config()["Clusters"]["zzzzz"]["Services"][service]["InternalURLs"].keys()))[idx]).
431 netloc.split(":")[1])
433 def external_port_from_config(service):
434 return int(urlparse(get_config()["Clusters"]["zzzzz"]["Services"][service]["ExternalURL"]).netloc.split(":")[1])
436 def run_controller():
437 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
440 logf = open(_logfilename('controller'), WRITE_MODE)
441 port = internal_port_from_config("Controller")
442 controller = subprocess.Popen(
443 ["arvados-server", "controller"],
444 stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
445 _detachedSubprocesses.append(controller)
446 with open(_pidfile('controller'), 'w') as f:
447 f.write(str(controller.pid))
448 _wait_until_port_listens(port)
451 def stop_controller():
452 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
454 kill_server_pid(_pidfile('controller'))
457 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
460 port = internal_port_from_config("Websocket")
461 logf = open(_logfilename('ws'), WRITE_MODE)
462 ws = subprocess.Popen(
463 ["arvados-server", "ws"],
464 stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
465 _detachedSubprocesses.append(ws)
466 with open(_pidfile('ws'), 'w') as f:
468 _wait_until_port_listens(port)
472 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
474 kill_server_pid(_pidfile('ws'))
476 def _start_keep(n, blob_signing=False):
477 datadir = os.path.join(TEST_TMPDIR, "keep%d.data"%n)
478 if os.path.exists(datadir):
479 shutil.rmtree(datadir)
481 port = internal_port_from_config("Keepstore", idx=n)
483 # Currently, if there are multiple InternalURLs for a single host,
484 # the only way to tell a keepstore process which one it's supposed
485 # to listen on is to supply a redacted version of the config, with
486 # the other InternalURLs removed.
487 conf = os.path.join(TEST_TMPDIR, "keep%d.yaml"%n)
488 confdata = get_config()
489 confdata['Clusters']['zzzzz']['Services']['Keepstore']['InternalURLs'] = {"http://127.0.0.1:%d"%port: {}}
490 confdata['Clusters']['zzzzz']['Collections']['BlobSigning'] = blob_signing
491 with open(conf, 'w') as f:
492 yaml.safe_dump(confdata, f)
493 keep_cmd = ["arvados-server", "keepstore", "-config", conf]
495 with open(_logfilename('keep{}'.format(n)), WRITE_MODE) as logf:
496 with open('/dev/null') as _stdin:
497 child = subprocess.Popen(
498 keep_cmd, stdin=_stdin, stdout=logf, stderr=logf, close_fds=True)
499 _detachedSubprocesses.append(child)
501 print('child.pid is %d'%child.pid, file=sys.stderr)
502 with open(_pidfile('keep{}'.format(n)), 'w') as f:
503 f.write(str(child.pid))
505 _wait_until_port_listens(port)
509 def run_keep(num_servers=2, **kwargs):
510 stop_keep(num_servers)
514 host=os.environ['ARVADOS_API_HOST'],
515 token=os.environ['ARVADOS_API_TOKEN'],
518 for d in api.keep_services().list(filters=[['service_type','=','disk']]).execute()['items']:
519 api.keep_services().delete(uuid=d['uuid']).execute()
521 for d in range(0, num_servers):
522 port = _start_keep(d, **kwargs)
523 svc = api.keep_services().create(body={'keep_service': {
524 'uuid': 'zzzzz-bi6l4-keepdisk{:07d}'.format(d),
525 'service_host': 'localhost',
526 'service_port': port,
527 'service_type': 'disk',
528 'service_ssl_flag': False,
531 # If keepproxy and/or keep-web is running, send SIGHUP to make
532 # them discover the new keepstore services.
533 for svc in ('keepproxy', 'keep-web'):
534 pidfile = _pidfile(svc)
535 if os.path.exists(pidfile):
537 with open(pidfile) as pid:
538 os.kill(int(pid.read()), signal.SIGHUP)
543 kill_server_pid(_pidfile('keep{}'.format(n)))
545 def stop_keep(num_servers=2):
546 for n in range(0, num_servers):
549 def run_keep_proxy():
550 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
551 os.environ["ARVADOS_KEEP_SERVICES"] = "http://localhost:{}".format(internal_port_from_config('Keepproxy'))
555 port = internal_port_from_config("Keepproxy")
556 env = os.environ.copy()
557 env['ARVADOS_API_TOKEN'] = auth_token('anonymous')
558 logf = open(_logfilename('keepproxy'), WRITE_MODE)
559 kp = subprocess.Popen(
560 ['arvados-server', 'keepproxy'], env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
561 _detachedSubprocesses.append(kp)
563 with open(_pidfile('keepproxy'), 'w') as f:
565 _wait_until_port_listens(port)
567 print("Using API %s token %s" % (os.environ['ARVADOS_API_HOST'], auth_token('admin')), file=sys.stdout)
570 host=os.environ['ARVADOS_API_HOST'],
571 token=auth_token('admin'),
573 for d in api.keep_services().list(
574 filters=[['service_type','=','proxy']]).execute()['items']:
575 api.keep_services().delete(uuid=d['uuid']).execute()
576 api.keep_services().create(body={'keep_service': {
577 'service_host': 'localhost',
578 'service_port': port,
579 'service_type': 'proxy',
580 'service_ssl_flag': False,
582 os.environ["ARVADOS_KEEP_SERVICES"] = "http://localhost:{}".format(port)
583 _wait_until_port_listens(port)
585 def stop_keep_proxy():
586 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
588 kill_server_pid(_pidfile('keepproxy'))
591 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
595 keepwebport = internal_port_from_config("WebDAV")
596 env = os.environ.copy()
597 logf = open(_logfilename('keep-web'), WRITE_MODE)
598 keepweb = subprocess.Popen(
599 ['arvados-server', 'keep-web'],
600 env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
601 _detachedSubprocesses.append(keepweb)
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['UPSTREAMHOST'] = '127.0.0.1'
617 nginxconf['LISTENHOST'] = '127.0.0.1'
618 nginxconf['CONTROLLERPORT'] = internal_port_from_config("Controller")
619 nginxconf['ARVADOS_API_HOST'] = "0.0.0.0:" + str(external_port_from_config("Controller"))
620 nginxconf['CONTROLLERSSLPORT'] = external_port_from_config("Controller")
621 nginxconf['KEEPWEBPORT'] = internal_port_from_config("WebDAV")
622 nginxconf['KEEPWEBDLSSLPORT'] = external_port_from_config("WebDAVDownload")
623 nginxconf['KEEPWEBSSLPORT'] = external_port_from_config("WebDAV")
624 nginxconf['KEEPPROXYPORT'] = internal_port_from_config("Keepproxy")
625 nginxconf['KEEPPROXYSSLPORT'] = external_port_from_config("Keepproxy")
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['WORKBENCH1SSLPORT'] = external_port_from_config("Workbench1")
631 nginxconf['WORKBENCH2PORT'] = internal_port_from_config("Workbench2")
632 nginxconf['WORKBENCH2SSLPORT'] = external_port_from_config("Workbench2")
633 nginxconf['SSLCERT'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem')
634 nginxconf['SSLKEY'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.key')
635 nginxconf['ACCESSLOG'] = _logfilename('nginx_access')
636 nginxconf['ERRORLOG'] = _logfilename('nginx_error')
637 nginxconf['TMPDIR'] = TEST_TMPDIR + '/nginx'
638 nginxconf['INTERNALSUBNETS'] = '169.254.0.0/16 0;'
640 conftemplatefile = os.path.join(MY_DIRNAME, 'nginx.conf')
641 conffile = os.path.join(TEST_TMPDIR, 'nginx.conf')
642 with open(conffile, 'w') as f:
644 r'{{([A-Z]+[A-Z0-9]+)}}',
645 lambda match: str(nginxconf.get(match.group(1))),
646 open(conftemplatefile).read()))
648 env = os.environ.copy()
649 env['PATH'] = env['PATH']+':/sbin:/usr/sbin:/usr/local/sbin'
651 nginx = subprocess.Popen(
653 '-g', 'error_log stderr notice; pid '+_pidfile('nginx')+';',
655 env=env, stdin=open('/dev/null'), stdout=sys.stderr)
656 _detachedSubprocesses.append(nginx)
657 _wait_until_port_listens(nginxconf['CONTROLLERSSLPORT'])
660 rails_api_port = find_available_port()
661 controller_port = find_available_port()
662 controller_external_port = find_available_port()
663 websocket_port = find_available_port()
664 websocket_external_port = find_available_port()
665 workbench1_external_port = find_available_port()
666 workbench2_port = find_available_port()
667 workbench2_external_port = find_available_port()
668 health_httpd_port = find_available_port()
669 health_httpd_external_port = find_available_port()
670 keepproxy_port = find_available_port()
671 keepproxy_external_port = find_available_port()
672 keepstore_ports = sorted([str(find_available_port()) for _ in range(0,4)])
673 keep_web_port = find_available_port()
674 keep_web_external_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),
716 "ExternalURL": "https://%s:%s/" % (localhost, workbench2_external_port),
718 "http://%s:%s"%(localhost, workbench2_port): {},
722 "ExternalURL": "https://%s:%s" % (localhost, health_httpd_external_port),
724 "http://%s:%s"%(localhost, health_httpd_port): {}
729 "http://%s:%s"%(localhost, port): {} for port in keepstore_ports
733 "ExternalURL": "https://%s:%s" % (localhost, keepproxy_external_port),
735 "http://%s:%s"%(localhost, keepproxy_port): {},
739 "ExternalURL": "https://%s:%s" % (localhost, keep_web_external_port),
741 "http://%s:%s"%(localhost, keep_web_port): {},
745 "ExternalURL": "https://%s:%s" % (localhost, keep_web_dl_external_port),
747 "http://%s:%s"%(localhost, keep_web_port): {},
755 "ManagementToken": "e687950a23c3a9bceec28c6223a06c79",
756 "SystemRootToken": auth_token('system_user'),
758 "RequestTimeout": "30s",
759 "LockBeforeUpdate": True,
760 "UseKeepServicesTable": True,
767 "Email": "alice@example.com",
774 "LogLevel": ('info' if os.environ.get('ARVADOS_DEBUG', '') in ['','0'] else 'debug'),
777 "Connection": pgconnection,
782 "Services": services,
784 "AnonymousUserToken": auth_token('anonymous'),
785 "UserProfileNotificationAddress": "arvados@example.com",
788 "CollectionVersioning": True,
789 "BlobSigningKey": "zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc",
790 "TrustAllContent": False,
791 "ForwardSlashNameSubstitution": "/",
792 "TrashSweepInterval": "-1s", # disable, otherwise test cases can't acquire dblock
795 "LocalKeepBlobBuffersPerVCPU": 0,
796 "SupportedDockerImageFormats": {"v1": {}},
803 "zzzzz-nyw5e-%015d"%n: {
805 "http://%s:%s" % (localhost, keepstore_ports[n]): {},
807 "Driver": "Directory",
808 "DriverParameters": {
809 "Root": os.path.join(TEST_TMPDIR, "keep%d.data"%n),
811 } for n in range(len(keepstore_ports))
817 conf = os.path.join(TEST_TMPDIR, 'arvados.yml')
818 with open(conf, 'w') as f:
819 yaml.safe_dump(config, f)
821 ex = "export ARVADOS_CONFIG="+conf
826 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
828 kill_server_pid(_pidfile('nginx'))
830 def _pidfile(program):
831 return os.path.join(TEST_TMPDIR, program + '.pid')
834 '''load a fixture yaml file'''
835 with open(os.path.join(SERVICES_SRC_DIR, 'api', "test", "fixtures",
839 trim_index = yaml_file.index("# Test Helper trims the rest of the file")
840 yaml_file = yaml_file[0:trim_index]
843 return yaml.safe_load(yaml_file)
845 def auth_token(token_name):
846 return fixture("api_client_authorizations")[token_name]["api_token"]
848 def authorize_with(token_name):
849 '''token_name is the symbolic name of the token from the api_client_authorizations fixture'''
850 arvados.config.settings()["ARVADOS_API_TOKEN"] = auth_token(token_name)
851 arvados.config.settings()["ARVADOS_API_HOST"] = os.environ.get("ARVADOS_API_HOST")
852 arvados.config.settings()["ARVADOS_API_HOST_INSECURE"] = "true"
854 class TestCaseWithServers(unittest.TestCase):
855 """TestCase to start and stop supporting Arvados servers.
857 Define any of MAIN_SERVER, KEEP_SERVER, and/or KEEP_PROXY_SERVER
858 class variables as a dictionary of keyword arguments. If you do,
859 setUpClass will start the corresponding servers by passing these
860 keyword arguments to the run, run_keep, and/or run_keep_server
861 functions, respectively. It will also set Arvados environment
862 variables to point to these servers appropriately. If you don't
863 run a Keep or Keep proxy server, setUpClass will set up a
864 temporary directory for Keep local storage, and set it as
867 tearDownClass will stop any servers started, and restore the
868 original environment.
873 KEEP_PROXY_SERVER = None
874 KEEP_WEB_SERVER = None
877 def _restore_dict(src, dest):
878 for key in list(dest.keys()):
885 cls._orig_environ = os.environ.copy()
886 cls._orig_config = arvados.config.settings().copy()
887 cls._cleanup_funcs = []
888 os.environ.pop('ARVADOS_KEEP_SERVICES', 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_nginx', 'stop_nginx', 'setup_config',
925 parser = argparse.ArgumentParser()
926 parser.add_argument('action', type=str, help="one of {}".format(actions))
927 parser.add_argument('--auth', type=str, metavar='FIXTURE_NAME', help='Print authorization info for given api_client_authorizations fixture')
928 parser.add_argument('--num-keep-servers', metavar='int', type=int, default=2, help="Number of keep servers desired")
929 parser.add_argument('--keep-blob-signing', action="store_true", help="Enable blob signing for keepstore servers")
931 args = parser.parse_args()
933 if args.action not in actions:
934 print("Unrecognized action '{}'. Actions are: {}.".
935 format(args.action, actions),
938 # Create a new process group so our child processes don't exit on
939 # ^C in run-tests.sh interactive mode.
941 if args.action == 'start':
942 stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
943 run(leave_running_atexit=True)
944 host = os.environ['ARVADOS_API_HOST']
945 if args.auth is not None:
946 token = auth_token(args.auth)
947 print("export ARVADOS_API_TOKEN={}".format(shlex.quote(token)))
948 print("export ARVADOS_API_HOST={}".format(shlex.quote(host)))
949 print("export ARVADOS_API_HOST_INSECURE=true")
952 elif args.action == 'stop':
953 stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
954 elif args.action == 'start_ws':
956 elif args.action == 'stop_ws':
958 elif args.action == 'start_controller':
960 elif args.action == 'stop_controller':
962 elif args.action == 'start_keep':
963 run_keep(blob_signing=args.keep_blob_signing, num_servers=args.num_keep_servers)
964 elif args.action == 'stop_keep':
965 stop_keep(num_servers=args.num_keep_servers)
966 elif args.action == 'start_keep_proxy':
968 elif args.action == 'stop_keep_proxy':
970 elif args.action == 'start_keep-web':
972 elif args.action == 'stop_keep-web':
974 elif args.action == 'start_nginx':
976 print("export ARVADOS_API_HOST=0.0.0.0:{}".format(external_port_from_config('Controller')))
977 elif args.action == 'stop_nginx':
979 elif args.action == 'setup_config':
982 raise Exception("action recognized but not implemented!?")