5824: Use fifo2stderr for arv-git-httpd and keep-web logs, too.
[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     logf = open(_fifo2stderr('arv-git-httpd'), 'w')
451     agh = subprocess.Popen(
452         ['arv-git-httpd',
453          '-repo-root='+gitdir+'/test',
454          '-address=:'+str(gitport)],
455         env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
456     with open(_pidfile('arv-git-httpd'), 'w') as f:
457         f.write(str(agh.pid))
458     _setport('arv-git-httpd', gitport)
459     _wait_until_port_listens(gitport)
460
461 def stop_arv_git_httpd():
462     if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
463         return
464     kill_server_pid(_pidfile('arv-git-httpd'), wait=0)
465
466 def run_keep_web():
467     if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
468         return
469     stop_keep_web()
470
471     keepwebport = find_available_port()
472     env = os.environ.copy()
473     env['ARVADOS_API_TOKEN'] = auth_token('anonymous')
474     logf = open(_fifo2stderr('keep-web'), 'w')
475     keepweb = subprocess.Popen(
476         ['keep-web',
477          '-allow-anonymous',
478          '-attachment-only-host=localhost:'+str(keepwebport),
479          '-listen=:'+str(keepwebport)],
480         env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
481     with open(_pidfile('keep-web'), 'w') as f:
482         f.write(str(keepweb.pid))
483     _setport('keep-web', keepwebport)
484     _wait_until_port_listens(keepwebport)
485
486 def stop_keep_web():
487     if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
488         return
489     kill_server_pid(_pidfile('keep-web'), wait=0)
490
491 def run_nginx():
492     if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
493         return
494     nginxconf = {}
495     nginxconf['KEEPWEBPORT'] = _getport('keep-web')
496     nginxconf['KEEPWEBSSLPORT'] = find_available_port()
497     nginxconf['KEEPPROXYPORT'] = _getport('keepproxy')
498     nginxconf['KEEPPROXYSSLPORT'] = find_available_port()
499     nginxconf['GITPORT'] = _getport('arv-git-httpd')
500     nginxconf['GITSSLPORT'] = find_available_port()
501     nginxconf['SSLCERT'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem')
502     nginxconf['SSLKEY'] = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.key')
503     nginxconf['ACCESSLOG'] = _fifo2stderr('nginx_access_log')
504
505     conftemplatefile = os.path.join(MY_DIRNAME, 'nginx.conf')
506     conffile = os.path.join(TEST_TMPDIR, 'nginx.conf')
507     with open(conffile, 'w') as f:
508         f.write(re.sub(
509             r'{{([A-Z]+)}}',
510             lambda match: str(nginxconf.get(match.group(1))),
511             open(conftemplatefile).read()))
512
513     env = os.environ.copy()
514     env['PATH'] = env['PATH']+':/sbin:/usr/sbin:/usr/local/sbin'
515
516     nginx = subprocess.Popen(
517         ['nginx',
518          '-g', 'error_log stderr info;',
519          '-g', 'pid '+_pidfile('nginx')+';',
520          '-c', conffile],
521         env=env, stdin=open('/dev/null'), stdout=sys.stderr)
522     _setport('keep-web-ssl', nginxconf['KEEPWEBSSLPORT'])
523     _setport('keepproxy-ssl', nginxconf['KEEPPROXYSSLPORT'])
524     _setport('arv-git-httpd-ssl', nginxconf['GITSSLPORT'])
525
526 def stop_nginx():
527     if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
528         return
529     kill_server_pid(_pidfile('nginx'), wait=0)
530
531 def _pidfile(program):
532     return os.path.join(TEST_TMPDIR, program + '.pid')
533
534 def _portfile(program):
535     return os.path.join(TEST_TMPDIR, program + '.port')
536
537 def _setport(program, port):
538     with open(_portfile(program), 'w') as f:
539         f.write(str(port))
540
541 # Returns 9 if program is not up.
542 def _getport(program):
543     try:
544         return int(open(_portfile(program)).read())
545     except IOError:
546         return 9
547
548 def _apiconfig(key):
549     if _cached_config:
550         return _cached_config[key]
551     def _load(f, required=True):
552         fullpath = os.path.join(SERVICES_SRC_DIR, 'api', 'config', f)
553         if not required and not os.path.exists(fullpath):
554             return {}
555         return yaml.load(fullpath)
556     cdefault = _load('application.default.yml')
557     csite = _load('application.yml', required=False)
558     _cached_config = {}
559     for section in [cdefault.get('common',{}), cdefault.get('test',{}),
560                     csite.get('common',{}), csite.get('test',{})]:
561         _cached_config.update(section)
562     return _cached_config[key]
563
564 def fixture(fix):
565     '''load a fixture yaml file'''
566     with open(os.path.join(SERVICES_SRC_DIR, 'api', "test", "fixtures",
567                            fix + ".yml")) as f:
568         yaml_file = f.read()
569         try:
570           trim_index = yaml_file.index("# Test Helper trims the rest of the file")
571           yaml_file = yaml_file[0:trim_index]
572         except ValueError:
573           pass
574         return yaml.load(yaml_file)
575
576 def auth_token(token_name):
577     return fixture("api_client_authorizations")[token_name]["api_token"]
578
579 def authorize_with(token_name):
580     '''token_name is the symbolic name of the token from the api_client_authorizations fixture'''
581     arvados.config.settings()["ARVADOS_API_TOKEN"] = auth_token(token_name)
582     arvados.config.settings()["ARVADOS_API_HOST"] = os.environ.get("ARVADOS_API_HOST")
583     arvados.config.settings()["ARVADOS_API_HOST_INSECURE"] = "true"
584
585 class TestCaseWithServers(unittest.TestCase):
586     """TestCase to start and stop supporting Arvados servers.
587
588     Define any of MAIN_SERVER, KEEP_SERVER, and/or KEEP_PROXY_SERVER
589     class variables as a dictionary of keyword arguments.  If you do,
590     setUpClass will start the corresponding servers by passing these
591     keyword arguments to the run, run_keep, and/or run_keep_server
592     functions, respectively.  It will also set Arvados environment
593     variables to point to these servers appropriately.  If you don't
594     run a Keep or Keep proxy server, setUpClass will set up a
595     temporary directory for Keep local storage, and set it as
596     KEEP_LOCAL_STORE.
597
598     tearDownClass will stop any servers started, and restore the
599     original environment.
600     """
601     MAIN_SERVER = None
602     KEEP_SERVER = None
603     KEEP_PROXY_SERVER = None
604     KEEP_WEB_SERVER = None
605
606     @staticmethod
607     def _restore_dict(src, dest):
608         for key in dest.keys():
609             if key not in src:
610                 del dest[key]
611         dest.update(src)
612
613     @classmethod
614     def setUpClass(cls):
615         cls._orig_environ = os.environ.copy()
616         cls._orig_config = arvados.config.settings().copy()
617         cls._cleanup_funcs = []
618         os.environ.pop('ARVADOS_KEEP_PROXY', None)
619         os.environ.pop('ARVADOS_EXTERNAL_CLIENT', None)
620         for server_kwargs, start_func, stop_func in (
621                 (cls.MAIN_SERVER, run, reset),
622                 (cls.KEEP_SERVER, run_keep, stop_keep),
623                 (cls.KEEP_PROXY_SERVER, run_keep_proxy, stop_keep_proxy),
624                 (cls.KEEP_WEB_SERVER, run_keep_web, stop_keep_web)):
625             if server_kwargs is not None:
626                 start_func(**server_kwargs)
627                 cls._cleanup_funcs.append(stop_func)
628         if (cls.KEEP_SERVER is None) and (cls.KEEP_PROXY_SERVER is None):
629             cls.local_store = tempfile.mkdtemp()
630             os.environ['KEEP_LOCAL_STORE'] = cls.local_store
631             cls._cleanup_funcs.append(
632                 lambda: shutil.rmtree(cls.local_store, ignore_errors=True))
633         else:
634             os.environ.pop('KEEP_LOCAL_STORE', None)
635         arvados.config.initialize()
636
637     @classmethod
638     def tearDownClass(cls):
639         for clean_func in cls._cleanup_funcs:
640             clean_func()
641         cls._restore_dict(cls._orig_environ, os.environ)
642         cls._restore_dict(cls._orig_config, arvados.config.settings())
643
644
645 if __name__ == "__main__":
646     actions = [
647         'start', 'stop',
648         'start_keep', 'stop_keep',
649         'start_keep_proxy', 'stop_keep_proxy',
650         'start_keep-web', 'stop_keep-web',
651         'start_arv-git-httpd', 'stop_arv-git-httpd',
652         'start_nginx', 'stop_nginx',
653     ]
654     parser = argparse.ArgumentParser()
655     parser.add_argument('action', type=str, help="one of {}".format(actions))
656     parser.add_argument('--auth', type=str, metavar='FIXTURE_NAME', help='Print authorization info for given api_client_authorizations fixture')
657     parser.add_argument('--num-keep-servers', metavar='int', type=int, default=2, help="Number of keep servers desired")
658     parser.add_argument('--keep-enforce-permissions', action="store_true", help="Enforce keep permissions")
659
660     args = parser.parse_args()
661
662     if args.action not in actions:
663         print("Unrecognized action '{}'. Actions are: {}.".format(args.action, actions), file=sys.stderr)
664         sys.exit(1)
665     if args.action == 'start':
666         stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
667         run(leave_running_atexit=True)
668         host = os.environ['ARVADOS_API_HOST']
669         if args.auth is not None:
670             token = auth_token(args.auth)
671             print("export ARVADOS_API_TOKEN={}".format(pipes.quote(token)))
672             print("export ARVADOS_API_HOST={}".format(pipes.quote(host)))
673             print("export ARVADOS_API_HOST_INSECURE=true")
674         else:
675             print(host)
676     elif args.action == 'stop':
677         stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
678     elif args.action == 'start_keep':
679         run_keep(enforce_permissions=args.keep_enforce_permissions, num_servers=args.num_keep_servers)
680     elif args.action == 'stop_keep':
681         stop_keep()
682     elif args.action == 'start_keep_proxy':
683         run_keep_proxy()
684     elif args.action == 'stop_keep_proxy':
685         stop_keep_proxy()
686     elif args.action == 'start_arv-git-httpd':
687         run_arv_git_httpd()
688     elif args.action == 'stop_arv-git-httpd':
689         stop_arv_git_httpd()
690     elif args.action == 'start_keep-web':
691         run_keep_web()
692     elif args.action == 'stop_keep-web':
693         stop_keep_web()
694     elif args.action == 'start_nginx':
695         run_nginx()
696     elif args.action == 'stop_nginx':
697         stop_nginx()
698     else:
699         raise Exception("action recognized but not implemented!?")