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 # Install the git repository fixtures.
332 gitdir = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'git')
333 gittarball = os.path.join(SERVICES_SRC_DIR, 'api', 'test', 'test.git.tar')
334 if not os.path.isdir(gitdir):
336 subprocess.check_output(['tar', '-xC', gitdir, '-f', gittarball])
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 = os.environ.copy()
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'],
373 env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
374 _detachedSubprocesses.append(railsapi)
376 if not leave_running_atexit:
377 atexit.register(kill_server_pid, pid_file, passenger_root=api_src_dir)
379 my_api_host = "127.0.0.1:"+str(port)
380 os.environ['ARVADOS_API_HOST'] = my_api_host
382 # Make sure the server has written its pid file and started
383 # listening on its TCP port
384 _wait_until_port_listens(port)
385 find_server_pid(pid_file)
388 os.chdir(restore_cwd)
391 """Reset the test server to fixture state.
393 This resets the ARVADOS_TEST_API_HOST provided by a parent process
394 if any, otherwise the server started by run().
396 It also resets ARVADOS_* environment vars to point to the test
397 server with admin credentials.
399 existing_api_host = os.environ.get('ARVADOS_TEST_API_HOST', my_api_host)
400 token = auth_token('admin')
401 httpclient = httplib2.Http(ca_certs=os.path.join(
402 SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem'))
404 'https://{}/database/reset'.format(existing_api_host),
406 headers={'Authorization': 'OAuth2 {}'.format(token), 'Connection':'close'})
408 os.environ['ARVADOS_API_HOST_INSECURE'] = 'true'
409 os.environ['ARVADOS_API_TOKEN'] = token
410 os.environ['ARVADOS_API_HOST'] = existing_api_host
412 def stop(force=False):
413 """Stop the API server, if one is running.
415 If force==False, kill it only if we started it ourselves. (This
416 supports the use case where a Python test suite calls run(), but
417 run() just uses the ARVADOS_TEST_API_HOST provided by the parent
418 process, and the test suite cleans up after itself by calling
419 stop(). In this case the test server provided by the parent
420 process should be left alone.)
422 If force==True, kill it even if we didn't start it
423 ourselves. (This supports the use case in __main__, where "run"
424 and "stop" happen in different processes.)
427 if force or my_api_host is not None:
428 kill_server_pid(_pidfile('api'))
432 with open(os.environ["ARVADOS_CONFIG"]) as f:
433 return yaml.safe_load(f)
435 def internal_port_from_config(service, idx=0):
437 sorted(list(get_config()["Clusters"]["zzzzz"]["Services"][service]["InternalURLs"].keys()))[idx]).
438 netloc.split(":")[1])
440 def external_port_from_config(service):
441 return int(urlparse(get_config()["Clusters"]["zzzzz"]["Services"][service]["ExternalURL"]).netloc.split(":")[1])
443 def run_controller():
444 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
447 logf = open(_logfilename('controller'), WRITE_MODE)
448 port = internal_port_from_config("Controller")
449 controller = subprocess.Popen(
450 ["arvados-server", "controller"],
451 stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
452 _detachedSubprocesses.append(controller)
453 with open(_pidfile('controller'), 'w') as f:
454 f.write(str(controller.pid))
455 _wait_until_port_listens(port)
458 def stop_controller():
459 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
461 kill_server_pid(_pidfile('controller'))
464 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
467 port = internal_port_from_config("Websocket")
468 logf = open(_logfilename('ws'), WRITE_MODE)
469 ws = subprocess.Popen(
470 ["arvados-server", "ws"],
471 stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
472 _detachedSubprocesses.append(ws)
473 with open(_pidfile('ws'), 'w') as f:
475 _wait_until_port_listens(port)
479 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
481 kill_server_pid(_pidfile('ws'))
483 def _start_keep(n, blob_signing=False):
484 datadir = os.path.join(TEST_TMPDIR, "keep%d.data"%n)
485 if os.path.exists(datadir):
486 shutil.rmtree(datadir)
488 port = internal_port_from_config("Keepstore", idx=n)
490 # Currently, if there are multiple InternalURLs for a single host,
491 # the only way to tell a keepstore process which one it's supposed
492 # to listen on is to supply a redacted version of the config, with
493 # the other InternalURLs removed.
494 conf = os.path.join(TEST_TMPDIR, "keep%d.yaml"%n)
495 confdata = get_config()
496 confdata['Clusters']['zzzzz']['Services']['Keepstore']['InternalURLs'] = {"http://127.0.0.1:%d"%port: {}}
497 confdata['Clusters']['zzzzz']['Collections']['BlobSigning'] = blob_signing
498 with open(conf, 'w') as f:
499 yaml.safe_dump(confdata, f)
500 keep_cmd = ["arvados-server", "keepstore", "-config", conf]
502 with open(_logfilename('keep{}'.format(n)), WRITE_MODE) as logf:
503 with open('/dev/null') as _stdin:
504 child = subprocess.Popen(
505 keep_cmd, stdin=_stdin, stdout=logf, stderr=logf, close_fds=True)
506 _detachedSubprocesses.append(child)
508 print('child.pid is %d'%child.pid, file=sys.stderr)
509 with open(_pidfile('keep{}'.format(n)), 'w') as f:
510 f.write(str(child.pid))
512 _wait_until_port_listens(port)
516 def run_keep(num_servers=2, **kwargs):
517 stop_keep(num_servers)
521 host=os.environ['ARVADOS_API_HOST'],
522 token=os.environ['ARVADOS_API_TOKEN'],
525 for d in api.keep_services().list(filters=[['service_type','=','disk']]).execute()['items']:
526 api.keep_services().delete(uuid=d['uuid']).execute()
527 for d in api.keep_disks().list().execute()['items']:
528 api.keep_disks().delete(uuid=d['uuid']).execute()
530 for d in range(0, num_servers):
531 port = _start_keep(d, **kwargs)
532 svc = api.keep_services().create(body={'keep_service': {
533 'uuid': 'zzzzz-bi6l4-keepdisk{:07d}'.format(d),
534 'service_host': 'localhost',
535 'service_port': port,
536 'service_type': 'disk',
537 'service_ssl_flag': False,
539 api.keep_disks().create(body={
540 'keep_disk': {'keep_service_uuid': svc['uuid'] }
543 # If keepproxy and/or keep-web is running, send SIGHUP to make
544 # them discover the new keepstore services.
545 for svc in ('keepproxy', 'keep-web'):
546 pidfile = _pidfile(svc)
547 if os.path.exists(pidfile):
549 with open(pidfile) as pid:
550 os.kill(int(pid.read()), signal.SIGHUP)
555 kill_server_pid(_pidfile('keep{}'.format(n)))
557 def stop_keep(num_servers=2):
558 for n in range(0, num_servers):
561 def run_keep_proxy():
562 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
563 os.environ["ARVADOS_KEEP_SERVICES"] = "http://localhost:{}".format(internal_port_from_config('Keepproxy'))
567 port = internal_port_from_config("Keepproxy")
568 env = os.environ.copy()
569 env['ARVADOS_API_TOKEN'] = auth_token('anonymous')
570 logf = open(_logfilename('keepproxy'), WRITE_MODE)
571 kp = subprocess.Popen(
572 ['arvados-server', 'keepproxy'], env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
573 _detachedSubprocesses.append(kp)
575 with open(_pidfile('keepproxy'), 'w') as f:
577 _wait_until_port_listens(port)
579 print("Using API %s token %s" % (os.environ['ARVADOS_API_HOST'], auth_token('admin')), file=sys.stdout)
582 host=os.environ['ARVADOS_API_HOST'],
583 token=auth_token('admin'),
585 for d in api.keep_services().list(
586 filters=[['service_type','=','proxy']]).execute()['items']:
587 api.keep_services().delete(uuid=d['uuid']).execute()
588 api.keep_services().create(body={'keep_service': {
589 'service_host': 'localhost',
590 'service_port': port,
591 'service_type': 'proxy',
592 'service_ssl_flag': False,
594 os.environ["ARVADOS_KEEP_SERVICES"] = "http://localhost:{}".format(port)
595 _wait_until_port_listens(port)
597 def stop_keep_proxy():
598 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
600 kill_server_pid(_pidfile('keepproxy'))
602 def run_arv_git_httpd():
603 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
607 gitport = internal_port_from_config("GitHTTP")
608 env = os.environ.copy()
609 env.pop('ARVADOS_API_TOKEN', None)
610 logf = open(_logfilename('githttpd'), WRITE_MODE)
611 agh = subprocess.Popen(['arvados-server', 'git-httpd'],
612 env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
613 _detachedSubprocesses.append(agh)
614 with open(_pidfile('githttpd'), 'w') as f:
615 f.write(str(agh.pid))
616 _wait_until_port_listens(gitport)
618 def stop_arv_git_httpd():
619 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
621 kill_server_pid(_pidfile('githttpd'))
624 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
628 keepwebport = internal_port_from_config("WebDAV")
629 env = os.environ.copy()
630 logf = open(_logfilename('keep-web'), WRITE_MODE)
631 keepweb = subprocess.Popen(
632 ['arvados-server', 'keep-web'],
633 env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
634 _detachedSubprocesses.append(keepweb)
635 with open(_pidfile('keep-web'), 'w') as f:
636 f.write(str(keepweb.pid))
637 _wait_until_port_listens(keepwebport)
640 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
642 kill_server_pid(_pidfile('keep-web'))
645 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
649 nginxconf['UPSTREAMHOST'] = '127.0.0.1'
650 nginxconf['LISTENHOST'] = '127.0.0.1'
651 nginxconf['CONTROLLERPORT'] = internal_port_from_config("Controller")
652 nginxconf['ARVADOS_API_HOST'] = "0.0.0.0:" + str(external_port_from_config("Controller"))
653 nginxconf['CONTROLLERSSLPORT'] = external_port_from_config("Controller")
654 nginxconf['KEEPWEBPORT'] = internal_port_from_config("WebDAV")
655 nginxconf['KEEPWEBDLSSLPORT'] = external_port_from_config("WebDAVDownload")
656 nginxconf['KEEPWEBSSLPORT'] = external_port_from_config("WebDAV")
657 nginxconf['KEEPPROXYPORT'] = internal_port_from_config("Keepproxy")
658 nginxconf['KEEPPROXYSSLPORT'] = external_port_from_config("Keepproxy")
659 nginxconf['GITPORT'] = internal_port_from_config("GitHTTP")
660 nginxconf['GITSSLPORT'] = external_port_from_config("GitHTTP")
661 nginxconf['HEALTHPORT'] = internal_port_from_config("Health")
662 nginxconf['HEALTHSSLPORT'] = external_port_from_config("Health")
663 nginxconf['WSPORT'] = internal_port_from_config("Websocket")
664 nginxconf['WSSSLPORT'] = external_port_from_config("Websocket")
665 nginxconf['WORKBENCH1SSLPORT'] = external_port_from_config("Workbench1")
666 nginxconf['WORKBENCH2PORT'] = internal_port_from_config("Workbench2")
667 nginxconf['WORKBENCH2SSLPORT'] = external_port_from_config("Workbench2")
668 nginxconf['SSLCERT'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem')
669 nginxconf['SSLKEY'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.key')
670 nginxconf['ACCESSLOG'] = _logfilename('nginx_access')
671 nginxconf['ERRORLOG'] = _logfilename('nginx_error')
672 nginxconf['TMPDIR'] = TEST_TMPDIR + '/nginx'
673 nginxconf['INTERNALSUBNETS'] = '169.254.0.0/16 0;'
675 conftemplatefile = os.path.join(MY_DIRNAME, 'nginx.conf')
676 conffile = os.path.join(TEST_TMPDIR, 'nginx.conf')
677 with open(conffile, 'w') as f:
679 r'{{([A-Z]+[A-Z0-9]+)}}',
680 lambda match: str(nginxconf.get(match.group(1))),
681 open(conftemplatefile).read()))
683 env = os.environ.copy()
684 env['PATH'] = env['PATH']+':/sbin:/usr/sbin:/usr/local/sbin'
686 nginx = subprocess.Popen(
688 '-g', 'error_log stderr info; pid '+_pidfile('nginx')+';',
690 env=env, stdin=open('/dev/null'), stdout=sys.stderr)
691 _detachedSubprocesses.append(nginx)
692 _wait_until_port_listens(nginxconf['CONTROLLERSSLPORT'])
695 rails_api_port = find_available_port()
696 controller_port = find_available_port()
697 controller_external_port = find_available_port()
698 websocket_port = find_available_port()
699 websocket_external_port = find_available_port()
700 workbench1_external_port = find_available_port()
701 workbench2_port = find_available_port()
702 workbench2_external_port = find_available_port()
703 git_httpd_port = find_available_port()
704 git_httpd_external_port = find_available_port()
705 health_httpd_port = find_available_port()
706 health_httpd_external_port = find_available_port()
707 keepproxy_port = find_available_port()
708 keepproxy_external_port = find_available_port()
709 keepstore_ports = sorted([str(find_available_port()) for _ in range(0,4)])
710 keep_web_port = find_available_port()
711 keep_web_external_port = find_available_port()
712 keep_web_dl_external_port = find_available_port()
714 configsrc = os.environ.get("CONFIGSRC", None)
716 clusterconf = os.path.join(configsrc, "config.yml")
717 print("Getting config from %s" % clusterconf, file=sys.stderr)
718 pgconnection = yaml.safe_load(open(clusterconf))["Clusters"]["zzzzz"]["PostgreSQL"]["Connection"]
720 # assume "arvados-server install -type test" has set up the
721 # conventional db credentials
723 "client_encoding": "utf8",
725 "dbname": "arvados_test",
727 "password": "insecure_arvados_test",
730 localhost = "127.0.0.1"
734 "https://%s:%s"%(localhost, rails_api_port): {},
738 "ExternalURL": "https://%s:%s" % (localhost, controller_external_port),
740 "http://%s:%s"%(localhost, controller_port): {},
744 "ExternalURL": "wss://%s:%s/websocket" % (localhost, websocket_external_port),
746 "http://%s:%s"%(localhost, websocket_port): {},
750 "ExternalURL": "https://%s:%s/" % (localhost, workbench1_external_port),
753 "ExternalURL": "https://%s:%s/" % (localhost, workbench2_external_port),
755 "http://%s:%s"%(localhost, workbench2_port): {},
759 "ExternalURL": "https://%s:%s" % (localhost, git_httpd_external_port),
761 "http://%s:%s"%(localhost, git_httpd_port): {}
765 "ExternalURL": "https://%s:%s" % (localhost, health_httpd_external_port),
767 "http://%s:%s"%(localhost, health_httpd_port): {}
772 "http://%s:%s"%(localhost, port): {} for port in keepstore_ports
776 "ExternalURL": "https://%s:%s" % (localhost, keepproxy_external_port),
778 "http://%s:%s"%(localhost, keepproxy_port): {},
782 "ExternalURL": "https://%s:%s" % (localhost, keep_web_external_port),
784 "http://%s:%s"%(localhost, keep_web_port): {},
788 "ExternalURL": "https://%s:%s" % (localhost, keep_web_dl_external_port),
790 "http://%s:%s"%(localhost, keep_web_port): {},
798 "ManagementToken": "e687950a23c3a9bceec28c6223a06c79",
799 "SystemRootToken": auth_token('system_user'),
801 "RequestTimeout": "30s",
802 "LockBeforeUpdate": True,
809 "Email": "alice@example.com",
816 "LogLevel": ('info' if os.environ.get('ARVADOS_DEBUG', '') in ['','0'] else 'debug'),
819 "Connection": pgconnection,
824 "Services": services,
826 "AnonymousUserToken": auth_token('anonymous'),
827 "UserProfileNotificationAddress": "arvados@example.com",
830 "CollectionVersioning": True,
831 "BlobSigningKey": "zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc",
832 "TrustAllContent": False,
833 "ForwardSlashNameSubstitution": "/",
834 "TrashSweepInterval": "-1s",
837 "Repositories": os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'git', 'test'),
841 "GitInternalDir": os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'internal.git'),
843 "LocalKeepBlobBuffersPerVCPU": 0,
845 "SweepInterval": 0, # disable, otherwise test cases can't acquire dblock
847 "SupportedDockerImageFormats": {"v1": {}},
854 "zzzzz-nyw5e-%015d"%n: {
856 "http://%s:%s" % (localhost, keepstore_ports[n]): {},
858 "Driver": "Directory",
859 "DriverParameters": {
860 "Root": os.path.join(TEST_TMPDIR, "keep%d.data"%n),
862 } for n in range(len(keepstore_ports))
868 conf = os.path.join(TEST_TMPDIR, 'arvados.yml')
869 with open(conf, 'w') as f:
870 yaml.safe_dump(config, f)
872 ex = "export ARVADOS_CONFIG="+conf
877 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
879 kill_server_pid(_pidfile('nginx'))
881 def _pidfile(program):
882 return os.path.join(TEST_TMPDIR, program + '.pid')
885 '''load a fixture yaml file'''
886 with open(os.path.join(SERVICES_SRC_DIR, 'api', "test", "fixtures",
890 trim_index = yaml_file.index("# Test Helper trims the rest of the file")
891 yaml_file = yaml_file[0:trim_index]
894 return yaml.safe_load(yaml_file)
896 def auth_token(token_name):
897 return fixture("api_client_authorizations")[token_name]["api_token"]
899 def authorize_with(token_name):
900 '''token_name is the symbolic name of the token from the api_client_authorizations fixture'''
901 arvados.config.settings()["ARVADOS_API_TOKEN"] = auth_token(token_name)
902 arvados.config.settings()["ARVADOS_API_HOST"] = os.environ.get("ARVADOS_API_HOST")
903 arvados.config.settings()["ARVADOS_API_HOST_INSECURE"] = "true"
905 class TestCaseWithServers(unittest.TestCase):
906 """TestCase to start and stop supporting Arvados servers.
908 Define any of MAIN_SERVER, KEEP_SERVER, and/or KEEP_PROXY_SERVER
909 class variables as a dictionary of keyword arguments. If you do,
910 setUpClass will start the corresponding servers by passing these
911 keyword arguments to the run, run_keep, and/or run_keep_server
912 functions, respectively. It will also set Arvados environment
913 variables to point to these servers appropriately. If you don't
914 run a Keep or Keep proxy server, setUpClass will set up a
915 temporary directory for Keep local storage, and set it as
918 tearDownClass will stop any servers started, and restore the
919 original environment.
924 KEEP_PROXY_SERVER = None
925 KEEP_WEB_SERVER = None
928 def _restore_dict(src, dest):
929 for key in list(dest.keys()):
936 cls._orig_environ = os.environ.copy()
937 cls._orig_config = arvados.config.settings().copy()
938 cls._cleanup_funcs = []
939 os.environ.pop('ARVADOS_KEEP_SERVICES', None)
940 for server_kwargs, start_func, stop_func in (
941 (cls.MAIN_SERVER, run, reset),
942 (cls.WS_SERVER, run_ws, stop_ws),
943 (cls.KEEP_SERVER, run_keep, stop_keep),
944 (cls.KEEP_PROXY_SERVER, run_keep_proxy, stop_keep_proxy),
945 (cls.KEEP_WEB_SERVER, run_keep_web, stop_keep_web)):
946 if server_kwargs is not None:
947 start_func(**server_kwargs)
948 cls._cleanup_funcs.append(stop_func)
949 if (cls.KEEP_SERVER is None) and (cls.KEEP_PROXY_SERVER is None):
950 cls.local_store = tempfile.mkdtemp()
951 os.environ['KEEP_LOCAL_STORE'] = cls.local_store
952 cls._cleanup_funcs.append(
953 lambda: shutil.rmtree(cls.local_store, ignore_errors=True))
955 os.environ.pop('KEEP_LOCAL_STORE', None)
956 arvados.config.initialize()
959 def tearDownClass(cls):
960 for clean_func in cls._cleanup_funcs:
962 cls._restore_dict(cls._orig_environ, os.environ)
963 cls._restore_dict(cls._orig_config, arvados.config.settings())
966 if __name__ == "__main__":
969 'start_ws', 'stop_ws',
970 'start_controller', 'stop_controller',
971 'start_keep', 'stop_keep',
972 'start_keep_proxy', 'stop_keep_proxy',
973 'start_keep-web', 'stop_keep-web',
974 'start_githttpd', 'stop_githttpd',
975 'start_nginx', 'stop_nginx', 'setup_config',
977 parser = argparse.ArgumentParser()
978 parser.add_argument('action', type=str, help="one of {}".format(actions))
979 parser.add_argument('--auth', type=str, metavar='FIXTURE_NAME', help='Print authorization info for given api_client_authorizations fixture')
980 parser.add_argument('--num-keep-servers', metavar='int', type=int, default=2, help="Number of keep servers desired")
981 parser.add_argument('--keep-blob-signing', action="store_true", help="Enable blob signing for keepstore servers")
983 args = parser.parse_args()
985 if args.action not in actions:
986 print("Unrecognized action '{}'. Actions are: {}.".
987 format(args.action, actions),
990 # Create a new process group so our child processes don't exit on
991 # ^C in run-tests.sh interactive mode.
993 if args.action == 'start':
994 stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
995 run(leave_running_atexit=True)
996 host = os.environ['ARVADOS_API_HOST']
997 if args.auth is not None:
998 token = auth_token(args.auth)
999 print("export ARVADOS_API_TOKEN={}".format(shlex.quote(token)))
1000 print("export ARVADOS_API_HOST={}".format(shlex.quote(host)))
1001 print("export ARVADOS_API_HOST_INSECURE=true")
1004 elif args.action == 'stop':
1005 stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
1006 elif args.action == 'start_ws':
1008 elif args.action == 'stop_ws':
1010 elif args.action == 'start_controller':
1012 elif args.action == 'stop_controller':
1014 elif args.action == 'start_keep':
1015 run_keep(blob_signing=args.keep_blob_signing, num_servers=args.num_keep_servers)
1016 elif args.action == 'stop_keep':
1017 stop_keep(num_servers=args.num_keep_servers)
1018 elif args.action == 'start_keep_proxy':
1020 elif args.action == 'stop_keep_proxy':
1022 elif args.action == 'start_githttpd':
1024 elif args.action == 'stop_githttpd':
1025 stop_arv_git_httpd()
1026 elif args.action == 'start_keep-web':
1028 elif args.action == 'stop_keep-web':
1030 elif args.action == 'start_nginx':
1032 print("export ARVADOS_API_HOST=0.0.0.0:{}".format(external_port_from_config('Controller')))
1033 elif args.action == 'stop_nginx':
1035 elif args.action == 'setup_config':
1038 raise Exception("action recognized but not implemented!?")