Merge branch 'master' into 3153-auto-setup-user
[arvados.git] / sdk / python / tests / run_test_server.py
1 #!/usr/bin/env python
2
3 import argparse
4 import os
5 import shutil
6 import signal
7 import subprocess
8 import sys
9 import tempfile
10 import time
11 import unittest
12 import yaml
13
14 MY_DIRNAME = os.path.dirname(os.path.realpath(__file__))
15 if __name__ == '__main__' and os.path.exists(
16       os.path.join(MY_DIRNAME, '..', 'arvados', '__init__.py')):
17     # We're being launched to support another test suite.
18     # Add the Python SDK source to the library path.
19     sys.path.insert(1, os.path.dirname(MY_DIRNAME))
20
21 import arvados.api
22 import arvados.config
23
24 SERVICES_SRC_DIR = os.path.join(MY_DIRNAME, '../../../services')
25 SERVER_PID_PATH = 'tmp/pids/webrick-test.pid'
26 WEBSOCKETS_SERVER_PID_PATH = 'tmp/pids/passenger-test.pid'
27 if 'GOPATH' in os.environ:
28     gopaths = os.environ['GOPATH'].split(':')
29     gobins = [os.path.join(path, 'bin') for path in gopaths]
30     os.environ['PATH'] = ':'.join(gobins) + ':' + os.environ['PATH']
31
32 def find_server_pid(PID_PATH, wait=10):
33     now = time.time()
34     timeout = now + wait
35     good_pid = False
36     while (not good_pid) and (now <= timeout):
37         time.sleep(0.2)
38         try:
39             with open(PID_PATH, 'r') as f:
40                 server_pid = int(f.read())
41             good_pid = (os.kill(server_pid, 0) == None)
42         except IOError:
43             good_pid = False
44         except OSError:
45             good_pid = False
46         now = time.time()
47
48     if not good_pid:
49         return None
50
51     return server_pid
52
53 def kill_server_pid(PID_PATH, wait=10):
54     try:
55         now = time.time()
56         timeout = now + wait
57         with open(PID_PATH, 'r') as f:
58             server_pid = int(f.read())
59         while now <= timeout:
60             os.kill(server_pid, signal.SIGTERM) == None
61             os.getpgid(server_pid) # throw OSError if no such pid
62             now = time.time()
63             time.sleep(0.1)
64     except IOError:
65         good_pid = False
66     except OSError:
67         good_pid = False
68
69 def run(websockets=False, reuse_server=False):
70     cwd = os.getcwd()
71     os.chdir(os.path.join(SERVICES_SRC_DIR, 'api'))
72
73     if websockets:
74         pid_file = WEBSOCKETS_SERVER_PID_PATH
75     else:
76         pid_file = SERVER_PID_PATH
77
78     test_pid = find_server_pid(pid_file, 0)
79
80     if test_pid == None or not reuse_server:
81         # do not try to run both server variants at once
82         stop()
83
84         # delete cached discovery document
85         shutil.rmtree(arvados.http_cache('discovery'))
86
87         # Setup database
88         os.environ["RAILS_ENV"] = "test"
89         subprocess.call(['bundle', 'exec', 'rake', 'tmp:cache:clear'])
90         subprocess.call(['bundle', 'exec', 'rake', 'db:test:load'])
91         subprocess.call(['bundle', 'exec', 'rake', 'db:fixtures:load'])
92
93         if websockets:
94             os.environ["ARVADOS_WEBSOCKETS"] = "true"
95             subprocess.call(['openssl', 'req', '-new', '-x509', '-nodes',
96                              '-out', './self-signed.pem',
97                              '-keyout', './self-signed.key',
98                              '-days', '3650',
99                              '-subj', '/CN=localhost'])
100             subprocess.call(['bundle', 'exec',
101                              'passenger', 'start', '-d', '-p3333',
102                              '--pid-file',
103                              os.path.join(os.getcwd(), WEBSOCKETS_SERVER_PID_PATH),
104                              '--ssl',
105                              '--ssl-certificate', 'self-signed.pem',
106                              '--ssl-certificate-key', 'self-signed.key'])
107             os.environ["ARVADOS_API_HOST"] = "127.0.0.1:3333"
108         else:
109             subprocess.call(['bundle', 'exec', 'rails', 'server', '-d',
110                              '--pid',
111                              os.path.join(os.getcwd(), SERVER_PID_PATH),
112                              '-p3001'])
113             os.environ["ARVADOS_API_HOST"] = "127.0.0.1:3001"
114
115         pid = find_server_pid(SERVER_PID_PATH)
116
117     os.environ["ARVADOS_API_HOST_INSECURE"] = "true"
118     os.environ["ARVADOS_API_TOKEN"] = ""
119     os.chdir(cwd)
120
121 def stop():
122     cwd = os.getcwd()
123     os.chdir(os.path.join(SERVICES_SRC_DIR, 'api'))
124
125     kill_server_pid(WEBSOCKETS_SERVER_PID_PATH, 0)
126     kill_server_pid(SERVER_PID_PATH, 0)
127
128     try:
129         os.unlink('self-signed.pem')
130     except:
131         pass
132
133     try:
134         os.unlink('self-signed.key')
135     except:
136         pass
137
138     os.chdir(cwd)
139
140 def _start_keep(n, keep_args):
141     keep0 = tempfile.mkdtemp()
142     keep_cmd = ["keepstore",
143                 "-volumes={}".format(keep0),
144                 "-listen=:{}".format(25107+n),
145                 "-pid={}".format("tests/tmp/keep{}.pid".format(n))]
146
147     for arg, val in keep_args.iteritems():
148         keep_cmd.append("{}={}".format(arg, val))
149
150     kp0 = subprocess.Popen(keep_cmd)
151     with open("tests/tmp/keep{}.pid".format(n), 'w') as f:
152         f.write(str(kp0.pid))
153
154     with open("tests/tmp/keep{}.volume".format(n), 'w') as f:
155         f.write(keep0)
156
157 def run_keep(blob_signing_key=None, enforce_permissions=False):
158     stop_keep()
159
160     if not os.path.exists("tests/tmp"):
161         os.mkdir("tests/tmp")
162
163     keep_args = {}
164     if blob_signing_key:
165         with open("tests/tmp/keep.blob_signing_key", "w") as f:
166             f.write(blob_signing_key)
167         keep_args['--permission-key-file'] = 'tests/tmp/keep.blob_signing_key'
168     if enforce_permissions:
169         keep_args['--enforce-permissions'] = 'true'
170
171     _start_keep(0, keep_args)
172     _start_keep(1, keep_args)
173
174     os.environ["ARVADOS_API_HOST"] = "127.0.0.1:3001"
175     os.environ["ARVADOS_API_HOST_INSECURE"] = "true"
176
177     authorize_with("admin")
178     api = arvados.api('v1', cache=False)
179     for d in api.keep_services().list().execute()['items']:
180         api.keep_services().delete(uuid=d['uuid']).execute()
181     for d in api.keep_disks().list().execute()['items']:
182         api.keep_disks().delete(uuid=d['uuid']).execute()
183
184     s1 = api.keep_services().create(body={"keep_service": {"service_host": "localhost",  "service_port": 25107, "service_type": "disk"} }).execute()
185     s2 = api.keep_services().create(body={"keep_service": {"service_host": "localhost",  "service_port": 25108, "service_type": "disk"} }).execute()
186     api.keep_disks().create(body={"keep_disk": {"keep_service_uuid": s1["uuid"] } }).execute()
187     api.keep_disks().create(body={"keep_disk": {"keep_service_uuid": s2["uuid"] } }).execute()
188
189 def _stop_keep(n):
190     kill_server_pid("tests/tmp/keep{}.pid".format(n), 0)
191     if os.path.exists("tests/tmp/keep{}.volume".format(n)):
192         with open("tests/tmp/keep{}.volume".format(n), 'r') as r:
193             shutil.rmtree(r.read(), True)
194         os.unlink("tests/tmp/keep{}.volume".format(n))
195     if os.path.exists("tests/tmp/keep.blob_signing_key"):
196         os.remove("tests/tmp/keep.blob_signing_key")
197
198 def stop_keep():
199     _stop_keep(0)
200     _stop_keep(1)
201
202 def run_keep_proxy(auth):
203     stop_keep_proxy()
204
205     if not os.path.exists("tests/tmp"):
206         os.mkdir("tests/tmp")
207
208     os.environ["ARVADOS_API_HOST"] = "127.0.0.1:3001"
209     os.environ["ARVADOS_API_HOST_INSECURE"] = "true"
210     os.environ["ARVADOS_API_TOKEN"] = fixture("api_client_authorizations")[auth]["api_token"]
211
212     kp0 = subprocess.Popen(["keepproxy",
213                             "-pid=tests/tmp/keepproxy.pid",
214                             "-listen=:{}".format(25101)])
215
216     authorize_with("admin")
217     api = arvados.api('v1', cache=False)
218     api.keep_services().create(body={"keep_service": {"service_host": "localhost",  "service_port": 25101, "service_type": "proxy"} }).execute()
219
220     os.environ["ARVADOS_KEEP_PROXY"] = "http://localhost:25101"
221
222 def stop_keep_proxy():
223     kill_server_pid("tests/tmp/keepproxy.pid", 0)
224
225 def fixture(fix):
226     '''load a fixture yaml file'''
227     with open(os.path.join(SERVICES_SRC_DIR, 'api', "test", "fixtures",
228                            fix + ".yml")) as f:
229         return yaml.load(f.read())
230
231 def authorize_with(token):
232     '''token is the symbolic name of the token from the api_client_authorizations fixture'''
233     arvados.config.settings()["ARVADOS_API_TOKEN"] = fixture("api_client_authorizations")[token]["api_token"]
234     arvados.config.settings()["ARVADOS_API_HOST"] = os.environ.get("ARVADOS_API_HOST")
235     arvados.config.settings()["ARVADOS_API_HOST_INSECURE"] = "true"
236
237 class TestCaseWithServers(unittest.TestCase):
238     """TestCase to start and stop supporting Arvados servers.
239
240     Define any of MAIN_SERVER, KEEP_SERVER, and/or KEEP_PROXY_SERVER
241     class variables as a dictionary of keyword arguments.  If you do,
242     setUpClass will start the corresponding servers by passing these
243     keyword arguments to the run, run_keep, and/or run_keep_server
244     functions, respectively.  It will also set Arvados environment
245     variables to point to these servers appropriately.  If you don't
246     run a Keep or Keep proxy server, setUpClass will set up a
247     temporary directory for Keep local storage, and set it as
248     KEEP_LOCAL_STORE.
249
250     tearDownClass will stop any servers started, and restore the
251     original environment.
252     """
253     MAIN_SERVER = None
254     KEEP_SERVER = None
255     KEEP_PROXY_SERVER = None
256
257     @staticmethod
258     def _restore_dict(src, dest):
259         for key in dest.keys():
260             if key not in src:
261                 del dest[key]
262         dest.update(src)
263
264     @classmethod
265     def setUpClass(cls):
266         cls._orig_environ = os.environ.copy()
267         cls._orig_config = arvados.config.settings().copy()
268         cls._cleanup_funcs = []
269         for server_kwargs, start_func, stop_func in (
270               (cls.MAIN_SERVER, run, stop),
271               (cls.KEEP_SERVER, run_keep, stop_keep),
272               (cls.KEEP_PROXY_SERVER, run_keep_proxy, stop_keep_proxy)):
273             if server_kwargs is not None:
274                 start_func(**server_kwargs)
275                 cls._cleanup_funcs.append(stop_func)
276         os.environ.pop('ARVADOS_EXTERNAL_CLIENT', None)
277         if cls.KEEP_PROXY_SERVER is None:
278             os.environ.pop('ARVADOS_KEEP_PROXY', None)
279         if (cls.KEEP_SERVER is None) and (cls.KEEP_PROXY_SERVER is None):
280             cls.local_store = tempfile.mkdtemp()
281             os.environ['KEEP_LOCAL_STORE'] = cls.local_store
282             cls._cleanup_funcs.append(
283                 lambda: shutil.rmtree(cls.local_store, ignore_errors=True))
284         else:
285             os.environ.pop('KEEP_LOCAL_STORE', None)
286         arvados.config.initialize()
287
288     @classmethod
289     def tearDownClass(cls):
290         for clean_func in cls._cleanup_funcs:
291             clean_func()
292         cls._restore_dict(cls._orig_environ, os.environ)
293         cls._restore_dict(cls._orig_config, arvados.config.settings())
294
295
296 if __name__ == "__main__":
297     parser = argparse.ArgumentParser()
298     parser.add_argument('action', type=str, help='''one of "start", "stop", "start_keep", "stop_keep"''')
299     parser.add_argument('--websockets', action='store_true', default=False)
300     parser.add_argument('--reuse', action='store_true', default=False)
301     parser.add_argument('--auth', type=str, help='Print authorization info for given api_client_authorizations fixture')
302     args = parser.parse_args()
303
304     if args.action == 'start':
305         run(websockets=args.websockets, reuse_server=args.reuse)
306         if args.auth != None:
307             authorize_with(args.auth)
308             print("export ARVADOS_API_HOST={}".format(arvados.config.settings()["ARVADOS_API_HOST"]))
309             print("export ARVADOS_API_TOKEN={}".format(arvados.config.settings()["ARVADOS_API_TOKEN"]))
310             print("export ARVADOS_API_HOST_INSECURE={}".format(arvados.config.settings()["ARVADOS_API_HOST_INSECURE"]))
311     elif args.action == 'stop':
312         stop()
313     elif args.action == 'start_keep':
314         run_keep()
315     elif args.action == 'stop_keep':
316         stop_keep()
317     elif args.action == 'start_keep_proxy':
318         run_keep_proxy("admin")
319     elif args.action == 'stop_keep_proxy':
320         stop_keep_proxy()
321     else:
322         print('Unrecognized action "{}", actions are "start", "stop", "start_keep", "stop_keep"'.format(args.action))