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