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