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