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