Merge branch 'master' into 7167-keep-rsync-test-setup
[arvados.git] / sdk / python / tests / run_test_server.py
1 #!/usr/bin/env python
2
3 from __future__ import print_function
4 import argparse
5 import atexit
6 import httplib2
7 import os
8 import pipes
9 import random
10 import re
11 import shutil
12 import signal
13 import socket
14 import subprocess
15 import string
16 import sys
17 import tempfile
18 import time
19 import unittest
20 import yaml
21
22 MY_DIRNAME = os.path.dirname(os.path.realpath(__file__))
23 if __name__ == '__main__' and os.path.exists(
24       os.path.join(MY_DIRNAME, '..', 'arvados', '__init__.py')):
25     # We're being launched to support another test suite.
26     # Add the Python SDK source to the library path.
27     sys.path.insert(1, os.path.dirname(MY_DIRNAME))
28
29 import arvados
30 import arvados.config
31
32 ARVADOS_DIR = os.path.realpath(os.path.join(MY_DIRNAME, '../../..'))
33 SERVICES_SRC_DIR = os.path.join(ARVADOS_DIR, 'services')
34 SERVER_PID_PATH = 'tmp/pids/test-server.pid'
35 if 'GOPATH' in os.environ:
36     gopaths = os.environ['GOPATH'].split(':')
37     gobins = [os.path.join(path, 'bin') for path in gopaths]
38     os.environ['PATH'] = ':'.join(gobins) + ':' + os.environ['PATH']
39
40 TEST_TMPDIR = os.path.join(ARVADOS_DIR, 'tmp')
41 if not os.path.exists(TEST_TMPDIR):
42     os.mkdir(TEST_TMPDIR)
43
44 my_api_host = None
45 _cached_config = {}
46 keep_existing = None
47
48 def find_server_pid(PID_PATH, wait=10):
49     now = time.time()
50     timeout = now + wait
51     good_pid = False
52     while (not good_pid) and (now <= timeout):
53         time.sleep(0.2)
54         try:
55             with open(PID_PATH, 'r') as f:
56                 server_pid = int(f.read())
57             good_pid = (os.kill(server_pid, 0) is None)
58         except IOError:
59             good_pid = False
60         except OSError:
61             good_pid = False
62         now = time.time()
63
64     if not good_pid:
65         return None
66
67     return server_pid
68
69 def kill_server_pid(pidfile, wait=10, passenger_root=False):
70     # Must re-import modules in order to work during atexit
71     import os
72     import signal
73     import subprocess
74     import time
75     try:
76         if passenger_root:
77             # First try to shut down nicely
78             restore_cwd = os.getcwd()
79             os.chdir(passenger_root)
80             subprocess.call([
81                 'bundle', 'exec', 'passenger', 'stop', '--pid-file', pidfile])
82             os.chdir(restore_cwd)
83         now = time.time()
84         timeout = now + wait
85         with open(pidfile, 'r') as f:
86             server_pid = int(f.read())
87         while now <= timeout:
88             if not passenger_root or timeout - now < wait / 2:
89                 # Half timeout has elapsed. Start sending SIGTERM
90                 os.kill(server_pid, signal.SIGTERM)
91             # Raise OSError if process has disappeared
92             os.getpgid(server_pid)
93             time.sleep(0.1)
94             now = time.time()
95     except IOError:
96         pass
97     except OSError:
98         pass
99
100 def find_available_port():
101     """Return an IPv4 port number that is not in use right now.
102
103     We assume whoever needs to use the returned port is able to reuse
104     a recently used port without waiting for TIME_WAIT (see
105     SO_REUSEADDR / SO_REUSEPORT).
106
107     Some opportunity for races here, but it's better than choosing
108     something at random and not checking at all. If all of our servers
109     (hey Passenger) knew that listening on port 0 was a thing, the OS
110     would take care of the races, and this wouldn't be needed at all.
111     """
112
113     sock = socket.socket()
114     sock.bind(('0.0.0.0', 0))
115     port = sock.getsockname()[1]
116     sock.close()
117     return port
118
119 def _wait_until_port_listens(port, timeout=10):
120     """Wait for a process to start listening on the given port.
121
122     If nothing listens on the port within the specified timeout (given
123     in seconds), print a warning on stderr before returning.
124     """
125     try:
126         subprocess.check_output(['which', 'lsof'])
127     except subprocess.CalledProcessError:
128         print("WARNING: No `lsof` -- cannot wait for port to listen. "+
129               "Sleeping 0.5 and hoping for the best.")
130         time.sleep(0.5)
131         return
132     deadline = time.time() + timeout
133     while time.time() < deadline:
134         try:
135             subprocess.check_output(
136                 ['lsof', '-t', '-i', 'tcp:'+str(port)])
137         except subprocess.CalledProcessError:
138             time.sleep(0.1)
139             continue
140         return
141     print(
142         "WARNING: Nothing is listening on port {} (waited {} seconds).".
143         format(port, timeout),
144         file=sys.stderr)
145
146 def run(leave_running_atexit=False):
147     """Ensure an API server is running, and ARVADOS_API_* env vars have
148     admin credentials for it.
149
150     If ARVADOS_TEST_API_HOST is set, a parent process has started a
151     test server for us to use: we just need to reset() it using the
152     admin token fixture.
153
154     If a previous call to run() started a new server process, and it
155     is still running, we just need to reset() it to fixture state and
156     return.
157
158     If neither of those options work out, we'll really start a new
159     server.
160     """
161     global my_api_host
162
163     # Delete cached discovery document.
164     shutil.rmtree(arvados.http_cache('discovery'))
165
166     pid_file = os.path.join(SERVICES_SRC_DIR, 'api', SERVER_PID_PATH)
167     pid_file_ok = find_server_pid(pid_file, 0)
168
169     existing_api_host = os.environ.get('ARVADOS_TEST_API_HOST', my_api_host)
170     if existing_api_host and pid_file_ok:
171         if existing_api_host == my_api_host:
172             try:
173                 return reset()
174             except:
175                 # Fall through to shutdown-and-start case.
176                 pass
177         else:
178             # Server was provided by parent. Can't recover if it's
179             # unresettable.
180             return reset()
181
182     # Before trying to start up our own server, call stop() to avoid
183     # "Phusion Passenger Standalone is already running on PID 12345".
184     # (If we've gotten this far, ARVADOS_TEST_API_HOST isn't set, so
185     # we know the server is ours to kill.)
186     stop(force=True)
187
188     restore_cwd = os.getcwd()
189     api_src_dir = os.path.join(SERVICES_SRC_DIR, 'api')
190     os.chdir(api_src_dir)
191
192     # Either we haven't started a server of our own yet, or it has
193     # died, or we have lost our credentials, or something else is
194     # preventing us from calling reset(). Start a new one.
195
196     if not os.path.exists('tmp'):
197         os.makedirs('tmp')
198
199     if not os.path.exists('tmp/api'):
200         os.makedirs('tmp/api')
201
202     if not os.path.exists('tmp/logs'):
203         os.makedirs('tmp/logs')
204
205     if not os.path.exists('tmp/self-signed.pem'):
206         # We assume here that either passenger reports its listening
207         # address as https:/0.0.0.0:port/. If it reports "127.0.0.1"
208         # then the certificate won't match the host and reset() will
209         # fail certificate verification. If it reports "localhost",
210         # clients (notably Python SDK's websocket client) might
211         # resolve localhost as ::1 and then fail to connect.
212         subprocess.check_call([
213             'openssl', 'req', '-new', '-x509', '-nodes',
214             '-out', 'tmp/self-signed.pem',
215             '-keyout', 'tmp/self-signed.key',
216             '-days', '3650',
217             '-subj', '/CN=0.0.0.0'],
218         stdout=sys.stderr)
219
220     # Install the git repository fixtures.
221     gitdir = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'git')
222     gittarball = os.path.join(SERVICES_SRC_DIR, 'api', 'test', 'test.git.tar')
223     if not os.path.isdir(gitdir):
224         os.makedirs(gitdir)
225     subprocess.check_output(['tar', '-xC', gitdir, '-f', gittarball])
226
227     port = find_available_port()
228     env = os.environ.copy()
229     env['RAILS_ENV'] = 'test'
230     env['ARVADOS_WEBSOCKETS'] = 'yes'
231     env.pop('ARVADOS_TEST_API_HOST', None)
232     env.pop('ARVADOS_API_HOST', None)
233     env.pop('ARVADOS_API_HOST_INSECURE', None)
234     env.pop('ARVADOS_API_TOKEN', None)
235     start_msg = subprocess.check_output(
236         ['bundle', 'exec',
237          'passenger', 'start', '-d', '-p{}'.format(port),
238          '--pid-file', os.path.join(os.getcwd(), pid_file),
239          '--log-file', os.path.join(os.getcwd(), 'log/test.log'),
240          '--ssl',
241          '--ssl-certificate', 'tmp/self-signed.pem',
242          '--ssl-certificate-key', 'tmp/self-signed.key'],
243         env=env)
244
245     if not leave_running_atexit:
246         atexit.register(kill_server_pid, pid_file, passenger_root=api_src_dir)
247
248     match = re.search(r'Accessible via: https://(.*?)/', start_msg)
249     if not match:
250         raise Exception(
251             "Passenger did not report endpoint: {}".format(start_msg))
252     my_api_host = match.group(1)
253     os.environ['ARVADOS_API_HOST'] = my_api_host
254
255     # Make sure the server has written its pid file and started
256     # listening on its TCP port
257     find_server_pid(pid_file)
258     _wait_until_port_listens(port)
259
260     reset()
261     os.chdir(restore_cwd)
262
263 def reset():
264     """Reset the test server to fixture state.
265
266     This resets the ARVADOS_TEST_API_HOST provided by a parent process
267     if any, otherwise the server started by run().
268
269     It also resets ARVADOS_* environment vars to point to the test
270     server with admin credentials.
271     """
272     existing_api_host = os.environ.get('ARVADOS_TEST_API_HOST', my_api_host)
273     token = auth_token('admin')
274     httpclient = httplib2.Http(ca_certs=os.path.join(
275         SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem'))
276     httpclient.request(
277         'https://{}/database/reset'.format(existing_api_host),
278         'POST',
279         headers={'Authorization': 'OAuth2 {}'.format(token)})
280     os.environ['ARVADOS_API_HOST_INSECURE'] = 'true'
281     os.environ['ARVADOS_API_HOST'] = existing_api_host
282     os.environ['ARVADOS_API_TOKEN'] = token
283
284 def stop(force=False):
285     """Stop the API server, if one is running.
286
287     If force==False, kill it only if we started it ourselves. (This
288     supports the use case where a Python test suite calls run(), but
289     run() just uses the ARVADOS_TEST_API_HOST provided by the parent
290     process, and the test suite cleans up after itself by calling
291     stop(). In this case the test server provided by the parent
292     process should be left alone.)
293
294     If force==True, kill it even if we didn't start it
295     ourselves. (This supports the use case in __main__, where "run"
296     and "stop" happen in different processes.)
297     """
298     global my_api_host
299     if force or my_api_host is not None:
300         kill_server_pid(os.path.join(SERVICES_SRC_DIR, 'api', SERVER_PID_PATH))
301         my_api_host = None
302
303 def _start_keep(n, keep_args):
304     keep0 = tempfile.mkdtemp()
305     port = find_available_port()
306     keep_cmd = ["keepstore",
307                 "-volume={}".format(keep0),
308                 "-listen=:{}".format(port),
309                 "-pid="+_pidfile('keep{}'.format(n))]
310
311     for arg, val in keep_args.iteritems():
312         keep_cmd.append("{}={}".format(arg, val))
313
314     logf = open(os.path.join(TEST_TMPDIR, 'keep{}.log'.format(n)), 'a+')
315     kp0 = subprocess.Popen(
316         keep_cmd, stdin=open('/dev/null'), stdout=logf, stderr=logf, close_fds=True)
317     with open(_pidfile('keep{}'.format(n)), 'w') as f:
318         f.write(str(kp0.pid))
319
320     with open("{}/keep{}.volume".format(TEST_TMPDIR, n), 'w') as f:
321         f.write(keep0)
322
323     _wait_until_port_listens(port)
324
325     return port
326
327 def run_keep(blob_signing_key=None, enforce_permissions=False):
328     if keep_existing is None:
329       stop_keep()
330
331     keep_args = {}
332     if not blob_signing_key:
333         blob_signing_key = 'zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc'
334     with open(os.path.join(TEST_TMPDIR, "keep.blob_signing_key"), "w") as f:
335         keep_args['-blob-signing-key-file'] = f.name
336         f.write(blob_signing_key)
337     if enforce_permissions:
338         keep_args['-enforce-permissions'] = 'true'
339     with open(os.path.join(TEST_TMPDIR, "keep.data-manager-token-file"), "w") as f:
340         keep_args['-data-manager-token-file'] = f.name
341         f.write(os.environ['ARVADOS_API_TOKEN'])
342     keep_args['-never-delete'] = 'false'
343
344     api = arvados.api(
345         version='v1',
346         host=os.environ['ARVADOS_API_HOST'],
347         token=os.environ['ARVADOS_API_TOKEN'],
348         insecure=True)
349
350     for d in api.keep_services().list().execute()['items']:
351         api.keep_services().delete(uuid=d['uuid']).execute()
352     for d in api.keep_disks().list().execute()['items']:
353         api.keep_disks().delete(uuid=d['uuid']).execute()
354
355     start_index = 0
356     end_index = 2
357     if keep_existing is not None:
358         start_index = 2
359         end_index = 3
360     for d in range(start_index, end_index):
361         port = _start_keep(d, keep_args)
362         svc = api.keep_services().create(body={'keep_service': {
363             'uuid': 'zzzzz-bi6l4-keepdisk{:07d}'.format(d),
364             'service_host': 'localhost',
365             'service_port': port,
366             'service_type': 'disk',
367             'service_ssl_flag': False,
368         }}).execute()
369         api.keep_disks().create(body={
370             'keep_disk': {'keep_service_uuid': svc['uuid'] }
371         }).execute()
372
373 def _stop_keep(n):
374     kill_server_pid(_pidfile('keep{}'.format(n)), 0)
375     if os.path.exists("{}/keep{}.volume".format(TEST_TMPDIR, n)):
376         with open("{}/keep{}.volume".format(TEST_TMPDIR, n), 'r') as r:
377             shutil.rmtree(r.read(), True)
378         os.unlink("{}/keep{}.volume".format(TEST_TMPDIR, n))
379     if os.path.exists(os.path.join(TEST_TMPDIR, "keep.blob_signing_key")):
380         os.remove(os.path.join(TEST_TMPDIR, "keep.blob_signing_key"))
381
382 def stop_keep():
383     _stop_keep(0)
384     _stop_keep(1)
385     # We may have created an additional keep servers when keep_existing is used
386     _stop_keep(2)
387
388 def run_keep_proxy():
389     if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
390         return
391     stop_keep_proxy()
392
393     admin_token = auth_token('admin')
394     port = find_available_port()
395     env = os.environ.copy()
396     env['ARVADOS_API_TOKEN'] = admin_token
397     kp = subprocess.Popen(
398         ['keepproxy',
399          '-pid='+_pidfile('keepproxy'),
400          '-listen=:{}'.format(port)],
401         env=env, stdin=open('/dev/null'), stdout=sys.stderr)
402
403     api = arvados.api(
404         version='v1',
405         host=os.environ['ARVADOS_API_HOST'],
406         token=admin_token,
407         insecure=True)
408     for d in api.keep_services().list(
409             filters=[['service_type','=','proxy']]).execute()['items']:
410         api.keep_services().delete(uuid=d['uuid']).execute()
411     api.keep_services().create(body={'keep_service': {
412         'service_host': 'localhost',
413         'service_port': port,
414         'service_type': 'proxy',
415         'service_ssl_flag': False,
416     }}).execute()
417     os.environ["ARVADOS_KEEP_PROXY"] = "http://localhost:{}".format(port)
418     _setport('keepproxy', port)
419     _wait_until_port_listens(port)
420
421 def stop_keep_proxy():
422     if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
423         return
424     kill_server_pid(_pidfile('keepproxy'), wait=0)
425
426 def run_arv_git_httpd():
427     if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
428         return
429     stop_arv_git_httpd()
430
431     gitdir = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'git')
432     gitport = find_available_port()
433     env = os.environ.copy()
434     env.pop('ARVADOS_API_TOKEN', None)
435     agh = subprocess.Popen(
436         ['arv-git-httpd',
437          '-repo-root='+gitdir+'/test',
438          '-address=:'+str(gitport)],
439         env=env, stdin=open('/dev/null'), stdout=sys.stderr)
440     with open(_pidfile('arv-git-httpd'), 'w') as f:
441         f.write(str(agh.pid))
442     _setport('arv-git-httpd', gitport)
443     _wait_until_port_listens(gitport)
444
445 def stop_arv_git_httpd():
446     if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
447         return
448     kill_server_pid(_pidfile('arv-git-httpd'), wait=0)
449
450 def run_nginx():
451     if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
452         return
453     nginxconf = {}
454     nginxconf['KEEPPROXYPORT'] = _getport('keepproxy')
455     nginxconf['KEEPPROXYSSLPORT'] = find_available_port()
456     nginxconf['GITPORT'] = _getport('arv-git-httpd')
457     nginxconf['GITSSLPORT'] = find_available_port()
458     nginxconf['SSLCERT'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem')
459     nginxconf['SSLKEY'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.key')
460
461     conftemplatefile = os.path.join(MY_DIRNAME, 'nginx.conf')
462     conffile = os.path.join(TEST_TMPDIR, 'nginx.conf')
463     with open(conffile, 'w') as f:
464         f.write(re.sub(
465             r'{{([A-Z]+)}}',
466             lambda match: str(nginxconf.get(match.group(1))),
467             open(conftemplatefile).read()))
468
469     env = os.environ.copy()
470     env['PATH'] = env['PATH']+':/sbin:/usr/sbin:/usr/local/sbin'
471     nginx = subprocess.Popen(
472         ['nginx',
473          '-g', 'error_log stderr info;',
474          '-g', 'pid '+_pidfile('nginx')+';',
475          '-c', conffile],
476         env=env, stdin=open('/dev/null'), stdout=sys.stderr)
477     _setport('keepproxy-ssl', nginxconf['KEEPPROXYSSLPORT'])
478     _setport('arv-git-httpd-ssl', nginxconf['GITSSLPORT'])
479
480 def stop_nginx():
481     if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
482         return
483     kill_server_pid(_pidfile('nginx'), wait=0)
484
485 def _pidfile(program):
486     return os.path.join(TEST_TMPDIR, program + '.pid')
487
488 def _portfile(program):
489     return os.path.join(TEST_TMPDIR, program + '.port')
490
491 def _setport(program, port):
492     with open(_portfile(program), 'w') as f:
493         f.write(str(port))
494
495 # Returns 9 if program is not up.
496 def _getport(program):
497     try:
498         return int(open(_portfile(program)).read())
499     except IOError:
500         return 9
501
502 def _apiconfig(key):
503     if _cached_config:
504         return _cached_config[key]
505     def _load(f, required=True):
506         fullpath = os.path.join(SERVICES_SRC_DIR, 'api', 'config', f)
507         if not required and not os.path.exists(fullpath):
508             return {}
509         return yaml.load(fullpath)
510     cdefault = _load('application.default.yml')
511     csite = _load('application.yml', required=False)
512     _cached_config = {}
513     for section in [cdefault.get('common',{}), cdefault.get('test',{}),
514                     csite.get('common',{}), csite.get('test',{})]:
515         _cached_config.update(section)
516     return _cached_config[key]
517
518 def fixture(fix):
519     '''load a fixture yaml file'''
520     with open(os.path.join(SERVICES_SRC_DIR, 'api', "test", "fixtures",
521                            fix + ".yml")) as f:
522         yaml_file = f.read()
523         try:
524           trim_index = yaml_file.index("# Test Helper trims the rest of the file")
525           yaml_file = yaml_file[0:trim_index]
526         except ValueError:
527           pass
528         return yaml.load(yaml_file)
529
530 def auth_token(token_name):
531     return fixture("api_client_authorizations")[token_name]["api_token"]
532
533 def authorize_with(token_name):
534     '''token_name is the symbolic name of the token from the api_client_authorizations fixture'''
535     arvados.config.settings()["ARVADOS_API_TOKEN"] = auth_token(token_name)
536     arvados.config.settings()["ARVADOS_API_HOST"] = os.environ.get("ARVADOS_API_HOST")
537     arvados.config.settings()["ARVADOS_API_HOST_INSECURE"] = "true"
538
539 class TestCaseWithServers(unittest.TestCase):
540     """TestCase to start and stop supporting Arvados servers.
541
542     Define any of MAIN_SERVER, KEEP_SERVER, and/or KEEP_PROXY_SERVER
543     class variables as a dictionary of keyword arguments.  If you do,
544     setUpClass will start the corresponding servers by passing these
545     keyword arguments to the run, run_keep, and/or run_keep_server
546     functions, respectively.  It will also set Arvados environment
547     variables to point to these servers appropriately.  If you don't
548     run a Keep or Keep proxy server, setUpClass will set up a
549     temporary directory for Keep local storage, and set it as
550     KEEP_LOCAL_STORE.
551
552     tearDownClass will stop any servers started, and restore the
553     original environment.
554     """
555     MAIN_SERVER = None
556     KEEP_SERVER = None
557     KEEP_PROXY_SERVER = None
558
559     @staticmethod
560     def _restore_dict(src, dest):
561         for key in dest.keys():
562             if key not in src:
563                 del dest[key]
564         dest.update(src)
565
566     @classmethod
567     def setUpClass(cls):
568         cls._orig_environ = os.environ.copy()
569         cls._orig_config = arvados.config.settings().copy()
570         cls._cleanup_funcs = []
571         os.environ.pop('ARVADOS_KEEP_PROXY', None)
572         os.environ.pop('ARVADOS_EXTERNAL_CLIENT', None)
573         for server_kwargs, start_func, stop_func in (
574                 (cls.MAIN_SERVER, run, reset),
575                 (cls.KEEP_SERVER, run_keep, stop_keep),
576                 (cls.KEEP_PROXY_SERVER, run_keep_proxy, stop_keep_proxy)):
577             if server_kwargs is not None:
578                 start_func(**server_kwargs)
579                 cls._cleanup_funcs.append(stop_func)
580         if (cls.KEEP_SERVER is None) and (cls.KEEP_PROXY_SERVER is None):
581             cls.local_store = tempfile.mkdtemp()
582             os.environ['KEEP_LOCAL_STORE'] = cls.local_store
583             cls._cleanup_funcs.append(
584                 lambda: shutil.rmtree(cls.local_store, ignore_errors=True))
585         else:
586             os.environ.pop('KEEP_LOCAL_STORE', None)
587         arvados.config.initialize()
588
589     @classmethod
590     def tearDownClass(cls):
591         for clean_func in cls._cleanup_funcs:
592             clean_func()
593         cls._restore_dict(cls._orig_environ, os.environ)
594         cls._restore_dict(cls._orig_config, arvados.config.settings())
595
596
597 if __name__ == "__main__":
598     actions = [
599         'start', 'stop',
600         'start_keep', 'stop_keep',
601         'start_keep_proxy', 'stop_keep_proxy',
602         'start_arv-git-httpd', 'stop_arv-git-httpd',
603         'start_nginx', 'stop_nginx',
604     ]
605     parser = argparse.ArgumentParser()
606     parser.add_argument('action', type=str, help="one of {}".format(actions))
607     parser.add_argument('--auth', type=str, metavar='FIXTURE_NAME', help='Print authorization info for given api_client_authorizations fixture')
608     parser.add_argument('--keep_existing', type=str, help="Used to add additional keep servers, without terminating existing servers")
609     args = parser.parse_args()
610
611     keep_existing = args.keep_existing
612
613     if args.action not in actions:
614         print("Unrecognized action '{}'. Actions are: {}.".format(args.action, actions), file=sys.stderr)
615         sys.exit(1)
616     if args.action == 'start':
617         stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
618         run(leave_running_atexit=True)
619         host = os.environ['ARVADOS_API_HOST']
620         if args.auth is not None:
621             token = auth_token(args.auth)
622             print("export ARVADOS_API_TOKEN={}".format(pipes.quote(token)))
623             print("export ARVADOS_API_HOST={}".format(pipes.quote(host)))
624             print("export ARVADOS_API_HOST_INSECURE=true")
625         else:
626             print(host)
627     elif args.action == 'stop':
628         stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
629     elif args.action == 'start_keep':
630         run_keep()
631     elif args.action == 'stop_keep':
632         stop_keep()
633     elif args.action == 'start_keep_proxy':
634         run_keep_proxy()
635     elif args.action == 'stop_keep_proxy':
636         stop_keep_proxy()
637     elif args.action == 'start_arv-git-httpd':
638         run_arv_git_httpd()
639     elif args.action == 'stop_arv-git-httpd':
640         stop_arv_git_httpd()
641     elif args.action == 'start_nginx':
642         run_nginx()
643     elif args.action == 'stop_nginx':
644         stop_nginx()
645     else:
646         raise Exception("action recognized but not implemented!?")