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