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