1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: Apache-2.0
5 from __future__ import print_function
6 from __future__ import division
7 from builtins import str
8 from builtins import range
29 MY_DIRNAME = os.path.dirname(os.path.realpath(__file__))
30 if __name__ == '__main__' and os.path.exists(
31 os.path.join(MY_DIRNAME, '..', 'arvados', '__init__.py')):
32 # We're being launched to support another test suite.
33 # Add the Python SDK source to the library path.
34 sys.path.insert(1, os.path.dirname(MY_DIRNAME))
39 ARVADOS_DIR = os.path.realpath(os.path.join(MY_DIRNAME, '../../..'))
40 SERVICES_SRC_DIR = os.path.join(ARVADOS_DIR, 'services')
41 if 'GOPATH' in os.environ:
42 # Add all GOPATH bin dirs to PATH -- but insert them after the
43 # ruby gems bin dir, to ensure "bundle" runs the Ruby bundler
44 # command, not the golang.org/x/tools/cmd/bundle command.
45 gopaths = os.environ['GOPATH'].split(':')
46 addbins = [os.path.join(path, 'bin') for path in gopaths]
48 for path in os.environ['PATH'].split(':'):
50 if os.path.exists(os.path.join(path, 'bundle')):
54 os.environ['PATH'] = ':'.join(newbins)
56 TEST_TMPDIR = os.path.join(ARVADOS_DIR, 'tmp')
57 if not os.path.exists(TEST_TMPDIR):
62 _cached_db_config = {}
64 def find_server_pid(PID_PATH, wait=10):
68 while (not good_pid) and (now <= timeout):
71 with open(PID_PATH, 'r') as f:
72 server_pid = int(f.read())
73 good_pid = (os.kill(server_pid, 0) is None)
74 except EnvironmentError:
83 def kill_server_pid(pidfile, wait=10, passenger_root=False):
84 # Must re-import modules in order to work during atexit
95 # First try to shut down nicely
96 restore_cwd = os.getcwd()
97 os.chdir(passenger_root)
99 'bundle', 'exec', 'passenger', 'stop', '--pid-file', pidfile])
100 os.chdir(restore_cwd)
101 # Use up to half of the +wait+ period waiting for "passenger
102 # stop" to work. If the process hasn't exited by then, start
103 # sending TERM signals.
107 while now <= deadline and server_pid is None:
109 with open(pidfile, 'r') as f:
110 server_pid = int(f.read())
112 # No pidfile = nothing to kill.
114 except ValueError as error:
115 # Pidfile exists, but we can't parse it. Perhaps the
116 # server has created the file but hasn't written its PID
118 print("Parse error reading pidfile {}: {}".format(pidfile, error),
123 while now <= deadline:
125 exited, _ = os.waitpid(server_pid, os.WNOHANG)
127 _remove_pidfile(pidfile)
130 # already exited, or isn't our child process
134 os.kill(server_pid, signal.SIGTERM)
135 print("Sent SIGTERM to {} ({})".format(server_pid, pidfile),
137 except OSError as error:
138 if error.errno == errno.ESRCH:
139 # Thrown by os.getpgid() or os.kill() if the process
140 # does not exist, i.e., our work here is done.
141 _remove_pidfile(pidfile)
147 print("Server PID {} ({}) did not exit, giving up after {}s".
148 format(server_pid, pidfile, wait),
151 def _remove_pidfile(pidfile):
155 if os.path.lexists(pidfile):
158 def find_available_port():
159 """Return an IPv4 port number that is not in use right now.
161 We assume whoever needs to use the returned port is able to reuse
162 a recently used port without waiting for TIME_WAIT (see
163 SO_REUSEADDR / SO_REUSEPORT).
165 Some opportunity for races here, but it's better than choosing
166 something at random and not checking at all. If all of our servers
167 (hey Passenger) knew that listening on port 0 was a thing, the OS
168 would take care of the races, and this wouldn't be needed at all.
171 sock = socket.socket()
172 sock.bind(('0.0.0.0', 0))
173 port = sock.getsockname()[1]
177 def _wait_until_port_listens(port, timeout=10, warn=True):
178 """Wait for a process to start listening on the given port.
180 If nothing listens on the port within the specified timeout (given
181 in seconds), print a warning on stderr before returning.
184 subprocess.check_output(['which', 'netstat'])
185 except subprocess.CalledProcessError:
186 print("WARNING: No `netstat` -- cannot wait for port to listen. "+
187 "Sleeping 0.5 and hoping for the best.",
191 deadline = time.time() + timeout
192 while time.time() < deadline:
193 if re.search(r'\ntcp.*:'+str(port)+' .* LISTEN *\n', subprocess.check_output(['netstat', '-Wln']).decode()):
198 "WARNING: Nothing is listening on port {} (waited {} seconds).".
199 format(port, timeout),
203 def _logfilename(label):
204 """Set up a labelled log file, and return a path to write logs to.
206 Normally, the returned path is {tmpdir}/{label}.log.
208 In debug mode, logs are also written to stderr, with [label]
209 prepended to each line. The returned path is a FIFO.
211 +label+ should contain only alphanumerics: it is also used as part
212 of the FIFO filename.
215 logfilename = os.path.join(TEST_TMPDIR, label+'.log')
216 if not os.environ.get('ARVADOS_DEBUG', ''):
218 fifo = os.path.join(TEST_TMPDIR, label+'.fifo')
221 except OSError as error:
222 if error.errno != errno.ENOENT:
224 os.mkfifo(fifo, 0o700)
225 stdbuf = ['stdbuf', '-i0', '-oL', '-eL']
226 # open(fifo, 'r') would block waiting for someone to open the fifo
227 # for writing, so we need a separate cat process to open it for
229 cat = subprocess.Popen(
230 stdbuf+['cat', fifo],
231 stdin=open('/dev/null'),
232 stdout=subprocess.PIPE)
233 tee = subprocess.Popen(
234 stdbuf+['tee', '-a', logfilename],
236 stdout=subprocess.PIPE)
238 stdbuf+['sed', '-e', 's/^/['+label+'] /'],
243 def run(leave_running_atexit=False):
244 """Ensure an API server is running, and ARVADOS_API_* env vars have
245 admin credentials for it.
247 If ARVADOS_TEST_API_HOST is set, a parent process has started a
248 test server for us to use: we just need to reset() it using the
251 If a previous call to run() started a new server process, and it
252 is still running, we just need to reset() it to fixture state and
255 If neither of those options work out, we'll really start a new
260 # Delete cached discovery documents.
262 # This will clear cached docs that belong to other processes (like
263 # concurrent test suites) even if they're still running. They should
264 # be able to tolerate that.
265 for fn in glob.glob(os.path.join(
266 str(arvados.http_cache('discovery')),
267 '*,arvados,v1,rest,*')):
270 pid_file = _pidfile('api')
271 pid_file_ok = find_server_pid(pid_file, 0)
273 existing_api_host = os.environ.get('ARVADOS_TEST_API_HOST', my_api_host)
274 if existing_api_host and pid_file_ok:
275 if existing_api_host == my_api_host:
279 # Fall through to shutdown-and-start case.
282 # Server was provided by parent. Can't recover if it's
286 # Before trying to start up our own server, call stop() to avoid
287 # "Phusion Passenger Standalone is already running on PID 12345".
288 # (If we've gotten this far, ARVADOS_TEST_API_HOST isn't set, so
289 # we know the server is ours to kill.)
292 restore_cwd = os.getcwd()
293 api_src_dir = os.path.join(SERVICES_SRC_DIR, 'api')
294 os.chdir(api_src_dir)
296 # Either we haven't started a server of our own yet, or it has
297 # died, or we have lost our credentials, or something else is
298 # preventing us from calling reset(). Start a new one.
300 if not os.path.exists('tmp'):
303 if not os.path.exists('tmp/api'):
304 os.makedirs('tmp/api')
306 if not os.path.exists('tmp/logs'):
307 os.makedirs('tmp/logs')
309 # Install the git repository fixtures.
310 gitdir = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'git')
311 gittarball = os.path.join(SERVICES_SRC_DIR, 'api', 'test', 'test.git.tar')
312 if not os.path.isdir(gitdir):
314 subprocess.check_output(['tar', '-xC', gitdir, '-f', gittarball])
316 # The nginx proxy isn't listening here yet, but we need to choose
317 # the wss:// port now so we can write the API server config file.
318 wss_port = find_available_port()
319 _setport('wss', wss_port)
321 port = find_available_port()
322 env = os.environ.copy()
323 env['RAILS_ENV'] = 'test'
324 env['ARVADOS_TEST_WSS_PORT'] = str(wss_port)
325 env.pop('ARVADOS_WEBSOCKETS', None)
326 env.pop('ARVADOS_TEST_API_HOST', None)
327 env.pop('ARVADOS_API_HOST', None)
328 env.pop('ARVADOS_API_HOST_INSECURE', None)
329 env.pop('ARVADOS_API_TOKEN', None)
330 start_msg = subprocess.check_output(
332 'passenger', 'start', '-d', '-p{}'.format(port),
333 '--pid-file', pid_file,
334 '--log-file', os.path.join(os.getcwd(), 'log/test.log'),
336 '--ssl-certificate', 'tmp/self-signed.pem',
337 '--ssl-certificate-key', 'tmp/self-signed.key'],
340 if not leave_running_atexit:
341 atexit.register(kill_server_pid, pid_file, passenger_root=api_src_dir)
343 match = re.search(r'Accessible via: https://(.*?)/', start_msg)
346 "Passenger did not report endpoint: {}".format(start_msg))
347 my_api_host = match.group(1)
348 os.environ['ARVADOS_API_HOST'] = my_api_host
350 # Make sure the server has written its pid file and started
351 # listening on its TCP port
352 find_server_pid(pid_file)
353 _wait_until_port_listens(port)
356 os.chdir(restore_cwd)
359 """Reset the test server to fixture state.
361 This resets the ARVADOS_TEST_API_HOST provided by a parent process
362 if any, otherwise the server started by run().
364 It also resets ARVADOS_* environment vars to point to the test
365 server with admin credentials.
367 existing_api_host = os.environ.get('ARVADOS_TEST_API_HOST', my_api_host)
368 token = auth_token('admin')
369 httpclient = httplib2.Http(ca_certs=os.path.join(
370 SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem'))
372 'https://{}/database/reset'.format(existing_api_host),
374 headers={'Authorization': 'OAuth2 {}'.format(token)})
375 os.environ['ARVADOS_API_HOST_INSECURE'] = 'true'
376 os.environ['ARVADOS_API_TOKEN'] = token
377 if _wait_until_port_listens(_getport('controller-ssl'), timeout=0.5, warn=False):
378 os.environ['ARVADOS_API_HOST'] = '0.0.0.0:'+str(_getport('controller-ssl'))
380 os.environ['ARVADOS_API_HOST'] = existing_api_host
382 def stop(force=False):
383 """Stop the API server, if one is running.
385 If force==False, kill it only if we started it ourselves. (This
386 supports the use case where a Python test suite calls run(), but
387 run() just uses the ARVADOS_TEST_API_HOST provided by the parent
388 process, and the test suite cleans up after itself by calling
389 stop(). In this case the test server provided by the parent
390 process should be left alone.)
392 If force==True, kill it even if we didn't start it
393 ourselves. (This supports the use case in __main__, where "run"
394 and "stop" happen in different processes.)
397 if force or my_api_host is not None:
398 kill_server_pid(_pidfile('api'))
401 def run_controller():
402 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
405 rails_api_port = int(string.split(os.environ.get('ARVADOS_TEST_API_HOST', my_api_host), ':')[-1])
406 port = find_available_port()
407 conf = os.path.join(TEST_TMPDIR, 'arvados.yml')
408 with open(conf, 'w') as f:
412 EnableBetaController14287: {beta14287}
413 ManagementToken: e687950a23c3a9bceec28c6223a06c79
418 HTTPRequestTimeout: 30s
431 "http://localhost:{controllerport}": {{}}
434 "https://localhost:{railsport}": {{}}
436 beta14287=('true' if '14287' in os.environ.get('ARVADOS_EXPERIMENTAL', '') else 'false'),
437 loglevel=('info' if os.environ.get('ARVADOS_DEBUG', '') in ['','0'] else 'debug'),
438 dbhost=_dbconfig('host'),
439 dbname=_dbconfig('dbname'),
440 dbuser=_dbconfig('user'),
441 dbpass=_dbconfig('password'),
443 railsport=rails_api_port,
445 logf = open(_logfilename('controller'), 'a')
446 controller = subprocess.Popen(
447 ["arvados-server", "controller", "-config", conf],
448 stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
449 with open(_pidfile('controller'), 'w') as f:
450 f.write(str(controller.pid))
451 _wait_until_port_listens(port)
452 _setport('controller', port)
455 def stop_controller():
456 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
458 kill_server_pid(_pidfile('controller'))
461 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
464 port = find_available_port()
465 conf = os.path.join(TEST_TMPDIR, 'ws.yml')
466 with open(conf, 'w') as f:
479 """.format(os.environ['ARVADOS_API_HOST'],
481 ('info' if os.environ.get('ARVADOS_DEBUG', '') in ['','0'] else 'debug'),
485 _dbconfig('password')))
486 logf = open(_logfilename('ws'), 'a')
487 ws = subprocess.Popen(
488 ["ws", "-config", conf],
489 stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
490 with open(_pidfile('ws'), 'w') as f:
492 _wait_until_port_listens(port)
497 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
499 kill_server_pid(_pidfile('ws'))
501 def _start_keep(n, keep_args):
502 keep0 = tempfile.mkdtemp()
503 port = find_available_port()
504 keep_cmd = ["keepstore",
505 "-volume={}".format(keep0),
506 "-listen=:{}".format(port),
507 "-pid="+_pidfile('keep{}'.format(n))]
509 for arg, val in keep_args.items():
510 keep_cmd.append("{}={}".format(arg, val))
512 with open(_logfilename('keep{}'.format(n)), 'a') as logf:
513 with open('/dev/null') as _stdin:
514 kp0 = subprocess.Popen(
515 keep_cmd, stdin=_stdin, stdout=logf, stderr=logf, close_fds=True)
517 with open(_pidfile('keep{}'.format(n)), 'w') as f:
518 f.write(str(kp0.pid))
520 with open("{}/keep{}.volume".format(TEST_TMPDIR, n), 'w') as f:
523 _wait_until_port_listens(port)
527 def run_keep(blob_signing_key=None, enforce_permissions=False, num_servers=2):
528 stop_keep(num_servers)
531 if not blob_signing_key:
532 blob_signing_key = 'zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc'
533 with open(os.path.join(TEST_TMPDIR, "keep.blob_signing_key"), "w") as f:
534 keep_args['-blob-signing-key-file'] = f.name
535 f.write(blob_signing_key)
536 keep_args['-enforce-permissions'] = str(enforce_permissions).lower()
537 with open(os.path.join(TEST_TMPDIR, "keep.data-manager-token-file"), "w") as f:
538 keep_args['-data-manager-token-file'] = f.name
539 f.write(auth_token('data_manager'))
540 keep_args['-never-delete'] = 'false'
544 host=os.environ['ARVADOS_API_HOST'],
545 token=os.environ['ARVADOS_API_TOKEN'],
548 for d in api.keep_services().list(filters=[['service_type','=','disk']]).execute()['items']:
549 api.keep_services().delete(uuid=d['uuid']).execute()
550 for d in api.keep_disks().list().execute()['items']:
551 api.keep_disks().delete(uuid=d['uuid']).execute()
553 for d in range(0, num_servers):
554 port = _start_keep(d, keep_args)
555 svc = api.keep_services().create(body={'keep_service': {
556 'uuid': 'zzzzz-bi6l4-keepdisk{:07d}'.format(d),
557 'service_host': 'localhost',
558 'service_port': port,
559 'service_type': 'disk',
560 'service_ssl_flag': False,
562 api.keep_disks().create(body={
563 'keep_disk': {'keep_service_uuid': svc['uuid'] }
566 # If keepproxy and/or keep-web is running, send SIGHUP to make
567 # them discover the new keepstore services.
568 for svc in ('keepproxy', 'keep-web'):
569 pidfile = _pidfile('keepproxy')
570 if os.path.exists(pidfile):
572 with open(pidfile) as pid:
573 os.kill(int(pid.read()), signal.SIGHUP)
578 kill_server_pid(_pidfile('keep{}'.format(n)))
579 if os.path.exists("{}/keep{}.volume".format(TEST_TMPDIR, n)):
580 with open("{}/keep{}.volume".format(TEST_TMPDIR, n), 'r') as r:
581 shutil.rmtree(r.read(), True)
582 os.unlink("{}/keep{}.volume".format(TEST_TMPDIR, n))
583 if os.path.exists(os.path.join(TEST_TMPDIR, "keep.blob_signing_key")):
584 os.remove(os.path.join(TEST_TMPDIR, "keep.blob_signing_key"))
586 def stop_keep(num_servers=2):
587 for n in range(0, num_servers):
590 def run_keep_proxy():
591 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
592 os.environ["ARVADOS_KEEP_SERVICES"] = "http://localhost:{}".format(_getport('keepproxy'))
596 port = find_available_port()
597 env = os.environ.copy()
598 env['ARVADOS_API_TOKEN'] = auth_token('anonymous')
599 logf = open(_logfilename('keepproxy'), 'a')
600 kp = subprocess.Popen(
602 '-pid='+_pidfile('keepproxy'),
603 '-listen=:{}'.format(port)],
604 env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
608 host=os.environ['ARVADOS_API_HOST'],
609 token=auth_token('admin'),
611 for d in api.keep_services().list(
612 filters=[['service_type','=','proxy']]).execute()['items']:
613 api.keep_services().delete(uuid=d['uuid']).execute()
614 api.keep_services().create(body={'keep_service': {
615 'service_host': 'localhost',
616 'service_port': port,
617 'service_type': 'proxy',
618 'service_ssl_flag': False,
620 os.environ["ARVADOS_KEEP_SERVICES"] = "http://localhost:{}".format(port)
621 _setport('keepproxy', port)
622 _wait_until_port_listens(port)
624 def stop_keep_proxy():
625 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
627 kill_server_pid(_pidfile('keepproxy'))
629 def run_arv_git_httpd():
630 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
634 gitdir = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'git')
635 gitport = find_available_port()
636 env = os.environ.copy()
637 env.pop('ARVADOS_API_TOKEN', None)
638 logf = open(_logfilename('arv-git-httpd'), 'a')
639 agh = subprocess.Popen(
641 '-repo-root='+gitdir+'/test',
642 '-management-token=e687950a23c3a9bceec28c6223a06c79',
643 '-address=:'+str(gitport)],
644 env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
645 with open(_pidfile('arv-git-httpd'), 'w') as f:
646 f.write(str(agh.pid))
647 _setport('arv-git-httpd', gitport)
648 _wait_until_port_listens(gitport)
650 def stop_arv_git_httpd():
651 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
653 kill_server_pid(_pidfile('arv-git-httpd'))
656 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
660 keepwebport = find_available_port()
661 env = os.environ.copy()
662 env['ARVADOS_API_TOKEN'] = auth_token('anonymous')
663 logf = open(_logfilename('keep-web'), 'a')
664 keepweb = subprocess.Popen(
667 '-attachment-only-host=download',
668 '-management-token=e687950a23c3a9bceec28c6223a06c79',
669 '-listen=:'+str(keepwebport)],
670 env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
671 with open(_pidfile('keep-web'), 'w') as f:
672 f.write(str(keepweb.pid))
673 _setport('keep-web', keepwebport)
674 _wait_until_port_listens(keepwebport)
677 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
679 kill_server_pid(_pidfile('keep-web'))
682 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
686 nginxconf['CONTROLLERPORT'] = _getport('controller')
687 nginxconf['CONTROLLERSSLPORT'] = find_available_port()
688 nginxconf['KEEPWEBPORT'] = _getport('keep-web')
689 nginxconf['KEEPWEBDLSSLPORT'] = find_available_port()
690 nginxconf['KEEPWEBSSLPORT'] = find_available_port()
691 nginxconf['KEEPPROXYPORT'] = _getport('keepproxy')
692 nginxconf['KEEPPROXYSSLPORT'] = find_available_port()
693 nginxconf['GITPORT'] = _getport('arv-git-httpd')
694 nginxconf['GITSSLPORT'] = find_available_port()
695 nginxconf['WSPORT'] = _getport('ws')
696 nginxconf['WSSPORT'] = _getport('wss')
697 nginxconf['SSLCERT'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem')
698 nginxconf['SSLKEY'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.key')
699 nginxconf['ACCESSLOG'] = _logfilename('nginx_access')
700 nginxconf['ERRORLOG'] = _logfilename('nginx_error')
701 nginxconf['TMPDIR'] = TEST_TMPDIR
703 conftemplatefile = os.path.join(MY_DIRNAME, 'nginx.conf')
704 conffile = os.path.join(TEST_TMPDIR, 'nginx.conf')
705 with open(conffile, 'w') as f:
708 lambda match: str(nginxconf.get(match.group(1))),
709 open(conftemplatefile).read()))
711 env = os.environ.copy()
712 env['PATH'] = env['PATH']+':/sbin:/usr/sbin:/usr/local/sbin'
714 nginx = subprocess.Popen(
716 '-g', 'error_log stderr info;',
717 '-g', 'pid '+_pidfile('nginx')+';',
719 env=env, stdin=open('/dev/null'), stdout=sys.stderr)
720 _setport('controller-ssl', nginxconf['CONTROLLERSSLPORT'])
721 _setport('keep-web-dl-ssl', nginxconf['KEEPWEBDLSSLPORT'])
722 _setport('keep-web-ssl', nginxconf['KEEPWEBSSLPORT'])
723 _setport('keepproxy-ssl', nginxconf['KEEPPROXYSSLPORT'])
724 _setport('arv-git-httpd-ssl', nginxconf['GITSSLPORT'])
727 if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
729 kill_server_pid(_pidfile('nginx'))
731 def _pidfile(program):
732 return os.path.join(TEST_TMPDIR, program + '.pid')
734 def _portfile(program):
735 return os.path.join(TEST_TMPDIR, program + '.port')
737 def _setport(program, port):
738 with open(_portfile(program), 'w') as f:
741 # Returns 9 if program is not up.
742 def _getport(program):
744 with open(_portfile(program)) as prog:
745 return int(prog.read())
750 global _cached_db_config
751 if not _cached_db_config:
752 if "ARVADOS_CONFIG" in os.environ:
753 _cached_db_config = list(yaml.safe_load(open(os.environ["ARVADOS_CONFIG"]))["Clusters"].values())[0]["PostgreSQL"]["Connection"]
755 _cached_db_config = yaml.safe_load(open(os.path.join(
756 SERVICES_SRC_DIR, 'api', 'config', 'database.yml')))["test"]
757 _cached_db_config["dbname"] = _cached_db_config["database"]
758 _cached_db_config["user"] = _cached_db_config["username"]
759 return _cached_db_config[key]
762 global _cached_config
764 return _cached_config[key]
765 def _load(f, required=True):
766 fullpath = os.path.join(SERVICES_SRC_DIR, 'api', 'config', f)
767 if not required and not os.path.exists(fullpath):
769 return yaml.safe_load(fullpath)
770 cdefault = _load('application.default.yml')
771 csite = _load('application.yml', required=False)
773 for section in [cdefault.get('common',{}), cdefault.get('test',{}),
774 csite.get('common',{}), csite.get('test',{})]:
775 _cached_config.update(section)
776 return _cached_config[key]
779 '''load a fixture yaml file'''
780 with open(os.path.join(SERVICES_SRC_DIR, 'api', "test", "fixtures",
784 trim_index = yaml_file.index("# Test Helper trims the rest of the file")
785 yaml_file = yaml_file[0:trim_index]
788 return yaml.safe_load(yaml_file)
790 def auth_token(token_name):
791 return fixture("api_client_authorizations")[token_name]["api_token"]
793 def authorize_with(token_name):
794 '''token_name is the symbolic name of the token from the api_client_authorizations fixture'''
795 arvados.config.settings()["ARVADOS_API_TOKEN"] = auth_token(token_name)
796 arvados.config.settings()["ARVADOS_API_HOST"] = os.environ.get("ARVADOS_API_HOST")
797 arvados.config.settings()["ARVADOS_API_HOST_INSECURE"] = "true"
799 class TestCaseWithServers(unittest.TestCase):
800 """TestCase to start and stop supporting Arvados servers.
802 Define any of MAIN_SERVER, KEEP_SERVER, and/or KEEP_PROXY_SERVER
803 class variables as a dictionary of keyword arguments. If you do,
804 setUpClass will start the corresponding servers by passing these
805 keyword arguments to the run, run_keep, and/or run_keep_server
806 functions, respectively. It will also set Arvados environment
807 variables to point to these servers appropriately. If you don't
808 run a Keep or Keep proxy server, setUpClass will set up a
809 temporary directory for Keep local storage, and set it as
812 tearDownClass will stop any servers started, and restore the
813 original environment.
818 KEEP_PROXY_SERVER = None
819 KEEP_WEB_SERVER = None
822 def _restore_dict(src, dest):
823 for key in list(dest.keys()):
830 cls._orig_environ = os.environ.copy()
831 cls._orig_config = arvados.config.settings().copy()
832 cls._cleanup_funcs = []
833 os.environ.pop('ARVADOS_KEEP_SERVICES', None)
834 os.environ.pop('ARVADOS_EXTERNAL_CLIENT', None)
835 for server_kwargs, start_func, stop_func in (
836 (cls.MAIN_SERVER, run, reset),
837 (cls.WS_SERVER, run_ws, stop_ws),
838 (cls.KEEP_SERVER, run_keep, stop_keep),
839 (cls.KEEP_PROXY_SERVER, run_keep_proxy, stop_keep_proxy),
840 (cls.KEEP_WEB_SERVER, run_keep_web, stop_keep_web)):
841 if server_kwargs is not None:
842 start_func(**server_kwargs)
843 cls._cleanup_funcs.append(stop_func)
844 if (cls.KEEP_SERVER is None) and (cls.KEEP_PROXY_SERVER is None):
845 cls.local_store = tempfile.mkdtemp()
846 os.environ['KEEP_LOCAL_STORE'] = cls.local_store
847 cls._cleanup_funcs.append(
848 lambda: shutil.rmtree(cls.local_store, ignore_errors=True))
850 os.environ.pop('KEEP_LOCAL_STORE', None)
851 arvados.config.initialize()
854 def tearDownClass(cls):
855 for clean_func in cls._cleanup_funcs:
857 cls._restore_dict(cls._orig_environ, os.environ)
858 cls._restore_dict(cls._orig_config, arvados.config.settings())
861 if __name__ == "__main__":
864 'start_ws', 'stop_ws',
865 'start_controller', 'stop_controller',
866 'start_keep', 'stop_keep',
867 'start_keep_proxy', 'stop_keep_proxy',
868 'start_keep-web', 'stop_keep-web',
869 'start_arv-git-httpd', 'stop_arv-git-httpd',
870 'start_nginx', 'stop_nginx',
872 parser = argparse.ArgumentParser()
873 parser.add_argument('action', type=str, help="one of {}".format(actions))
874 parser.add_argument('--auth', type=str, metavar='FIXTURE_NAME', help='Print authorization info for given api_client_authorizations fixture')
875 parser.add_argument('--num-keep-servers', metavar='int', type=int, default=2, help="Number of keep servers desired")
876 parser.add_argument('--keep-enforce-permissions', action="store_true", help="Enforce keep permissions")
878 args = parser.parse_args()
880 if args.action not in actions:
881 print("Unrecognized action '{}'. Actions are: {}.".
882 format(args.action, actions),
885 if args.action == 'start':
886 stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
887 run(leave_running_atexit=True)
888 host = os.environ['ARVADOS_API_HOST']
889 if args.auth is not None:
890 token = auth_token(args.auth)
891 print("export ARVADOS_API_TOKEN={}".format(pipes.quote(token)))
892 print("export ARVADOS_API_HOST={}".format(pipes.quote(host)))
893 print("export ARVADOS_API_HOST_INSECURE=true")
896 elif args.action == 'stop':
897 stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
898 elif args.action == 'start_ws':
900 elif args.action == 'stop_ws':
902 elif args.action == 'start_controller':
904 elif args.action == 'stop_controller':
906 elif args.action == 'start_keep':
907 run_keep(enforce_permissions=args.keep_enforce_permissions, num_servers=args.num_keep_servers)
908 elif args.action == 'stop_keep':
909 stop_keep(num_servers=args.num_keep_servers)
910 elif args.action == 'start_keep_proxy':
912 elif args.action == 'stop_keep_proxy':
914 elif args.action == 'start_arv-git-httpd':
916 elif args.action == 'stop_arv-git-httpd':
918 elif args.action == 'start_keep-web':
920 elif args.action == 'stop_keep-web':
922 elif args.action == 'start_nginx':
924 print("export ARVADOS_API_HOST=0.0.0.0:{}".format(_getport('controller-ssl')))
925 elif args.action == 'stop_nginx':
928 raise Exception("action recognized but not implemented!?")