20 MY_DIRNAME = os.path.dirname(os.path.realpath(__file__))
21 if __name__ == '__main__' and os.path.exists(
22 os.path.join(MY_DIRNAME, '..', 'arvados', '__init__.py')):
23 # We're being launched to support another test suite.
24 # Add the Python SDK source to the library path.
25 sys.path.insert(1, os.path.dirname(MY_DIRNAME))
30 ARVADOS_DIR = os.path.realpath(os.path.join(MY_DIRNAME, '../../..'))
31 SERVICES_SRC_DIR = os.path.join(ARVADOS_DIR, 'services')
32 SERVER_PID_PATH = 'tmp/pids/test-server.pid'
33 if 'GOPATH' in os.environ:
34 gopaths = os.environ['GOPATH'].split(':')
35 gobins = [os.path.join(path, 'bin') for path in gopaths]
36 os.environ['PATH'] = ':'.join(gobins) + ':' + os.environ['PATH']
38 TEST_TMPDIR = os.path.join(ARVADOS_DIR, 'tmp')
39 if not os.path.exists(TEST_TMPDIR):
44 def find_server_pid(PID_PATH, wait=10):
48 while (not good_pid) and (now <= timeout):
51 with open(PID_PATH, 'r') as f:
52 server_pid = int(f.read())
53 good_pid = (os.kill(server_pid, 0) is None)
65 def kill_server_pid(pidfile, wait=10, passenger_root=False):
66 # Must re-import modules in order to work during atexit
73 # First try to shut down nicely
74 restore_cwd = os.getcwd()
75 os.chdir(passenger_root)
77 'bundle', 'exec', 'passenger', 'stop', '--pid-file', pidfile])
81 with open(pidfile, 'r') as f:
82 server_pid = int(f.read())
84 if not passenger_root or timeout - now < wait / 2:
85 # Half timeout has elapsed. Start sending SIGTERM
86 os.kill(server_pid, signal.SIGTERM)
87 # Raise OSError if process has disappeared
88 os.getpgid(server_pid)
96 def find_available_port():
97 """Return a port number that is not in use right now.
99 Some opportunity for races here, but it's better than choosing
100 something at random and not checking at all. If all of our servers
101 (hey Passenger) knew that listening on port 0 was a thing, the OS
102 would take care of the races, and this wouldn't be needed at all.
106 port = random.randint(20000, 40000)
107 port_hex = ':%04x ' % port
109 with open('/proc/net/tcp', 'r') as f:
111 if 0 <= string.find(line, port_hex):
115 # This isn't going so well. Just use the random port.
121 def run(leave_running_atexit=False):
122 """Ensure an API server is running, and ARVADOS_API_* env vars have
123 admin credentials for it.
125 If ARVADOS_TEST_API_HOST is set, a parent process has started a
126 test server for us to use: we just need to reset() it using the
129 If a previous call to run() started a new server process, and it
130 is still running, we just need to reset() it to fixture state and
133 If neither of those options work out, we'll really start a new
138 # Delete cached discovery document.
139 shutil.rmtree(arvados.http_cache('discovery'))
141 os.environ['ARVADOS_API_TOKEN'] = auth_token('admin')
142 os.environ['ARVADOS_API_HOST_INSECURE'] = 'true'
144 pid_file = os.path.join(SERVICES_SRC_DIR, 'api', SERVER_PID_PATH)
145 pid_file_ok = find_server_pid(pid_file, 0)
147 existing_api_host = os.environ.get('ARVADOS_TEST_API_HOST', my_api_host)
148 if existing_api_host and pid_file_ok:
150 os.environ['ARVADOS_API_HOST'] = existing_api_host
156 # Before trying to start up our own server, call stop() to avoid
157 # "Phusion Passenger Standalone is already running on PID 12345".
158 # We want to kill it if it's our own _or_ it's some stale
159 # left-over server. But if it's been deliberately provided to us
160 # by a parent process, we don't want to force-kill it. That'll
161 # just wreck things for the next test suite that tries to use it.
162 stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
164 restore_cwd = os.getcwd()
165 api_src_dir = os.path.join(SERVICES_SRC_DIR, 'api')
166 os.chdir(api_src_dir)
168 # Either we haven't started a server of our own yet, or it has
169 # died, or we have lost our credentials, or something else is
170 # preventing us from calling reset(). Start a new one.
172 if not os.path.exists('tmp/self-signed.pem'):
173 # We assume here that either passenger reports its listening
174 # address as https:/0.0.0.0:port/. If it reports "127.0.0.1"
175 # then the certificate won't match the host and reset() will
176 # fail certificate verification. If it reports "localhost",
177 # clients (notably Python SDK's websocket client) might
178 # resolve localhost as ::1 and then fail to connect.
179 subprocess.check_call([
180 'openssl', 'req', '-new', '-x509', '-nodes',
181 '-out', 'tmp/self-signed.pem',
182 '-keyout', 'tmp/self-signed.key',
184 '-subj', '/CN=0.0.0.0'])
186 port = find_available_port()
187 env = os.environ.copy()
188 env['RAILS_ENV'] = 'test'
189 env['ARVADOS_WEBSOCKETS'] = 'yes'
190 env.pop('ARVADOS_TEST_API_HOST', None)
191 env.pop('ARVADOS_API_HOST', None)
192 env.pop('ARVADOS_API_HOST_INSECURE', None)
193 env.pop('ARVADOS_API_TOKEN', None)
194 start_msg = subprocess.check_output(
196 'passenger', 'start', '-d', '-p{}'.format(port),
197 '--pid-file', os.path.join(os.getcwd(), pid_file),
198 '--log-file', os.path.join(os.getcwd(), 'log/test.log'),
200 '--ssl-certificate', 'tmp/self-signed.pem',
201 '--ssl-certificate-key', 'tmp/self-signed.key'],
204 if not leave_running_atexit:
205 atexit.register(kill_server_pid, pid_file, passenger_root=api_src_dir)
207 match = re.search(r'Accessible via: https://(.*?)/', start_msg)
210 "Passenger did not report endpoint: {}".format(start_msg))
211 my_api_host = match.group(1)
212 os.environ['ARVADOS_API_HOST'] = my_api_host
214 # Make sure the server has written its pid file before continuing
215 find_server_pid(pid_file)
218 os.chdir(restore_cwd)
221 """Reset the test server to fixture state.
223 This resets the ARVADOS_TEST_API_HOST provided by a parent process
224 if any, otherwise the server started by run().
226 existing_api_host = os.environ.get('ARVADOS_TEST_API_HOST', my_api_host)
227 token = auth_token('admin')
228 httpclient = httplib2.Http(ca_certs=os.path.join(
229 SERVICES_SRC_DIR, 'api', 'tmp', 'self-signed.pem'))
231 'https://{}/database/reset'.format(existing_api_host),
233 headers={'Authorization': 'OAuth2 {}'.format(token)})
235 def stop(force=False):
236 """Stop the API server, if one is running.
238 If force==False, kill it only if we started it ourselves. (This
239 supports the use case where a Python test suite calls run(), but
240 run() just uses the ARVADOS_TEST_API_HOST provided by the parent
241 process, and the test suite cleans up after itself by calling
242 stop(). In this case the test server provided by the parent
243 process should be left alone.)
245 If force==True, kill it even if we didn't start it
246 ourselves. (This supports the use case in __main__, where "run"
247 and "stop" happen in different processes.)
250 if force or my_api_host is not None:
251 kill_server_pid(os.path.join(SERVICES_SRC_DIR, 'api', SERVER_PID_PATH))
254 def _start_keep(n, keep_args):
255 keep0 = tempfile.mkdtemp()
256 port = find_available_port()
257 keep_cmd = ["keepstore",
258 "-volumes={}".format(keep0),
259 "-listen=:{}".format(port),
260 "-pid={}".format("{}/keep{}.pid".format(TEST_TMPDIR, n))]
262 for arg, val in keep_args.iteritems():
263 keep_cmd.append("{}={}".format(arg, val))
265 kp0 = subprocess.Popen(keep_cmd)
266 with open("{}/keep{}.pid".format(TEST_TMPDIR, n), 'w') as f:
267 f.write(str(kp0.pid))
269 with open("{}/keep{}.volume".format(TEST_TMPDIR, n), 'w') as f:
274 def run_keep(blob_signing_key=None, enforce_permissions=False):
279 with open(os.path.join(TEST_TMPDIR, "keep.blob_signing_key"), "w") as f:
280 keep_args['--permission-key-file'] = f.name
281 f.write(blob_signing_key)
282 if enforce_permissions:
283 keep_args['--enforce-permissions'] = 'true'
287 host=os.environ['ARVADOS_API_HOST'],
288 token=os.environ['ARVADOS_API_TOKEN'],
290 for d in api.keep_services().list().execute()['items']:
291 api.keep_services().delete(uuid=d['uuid']).execute()
292 for d in api.keep_disks().list().execute()['items']:
293 api.keep_disks().delete(uuid=d['uuid']).execute()
295 for d in range(0, 2):
296 port = _start_keep(d, keep_args)
297 svc = api.keep_services().create(body={'keep_service': {
298 'uuid': 'zzzzz-bi6l4-keepdisk{:07d}'.format(d),
299 'service_host': 'localhost',
300 'service_port': port,
301 'service_type': 'disk',
302 'service_ssl_flag': False,
304 api.keep_disks().create(body={
305 'keep_disk': {'keep_service_uuid': svc['uuid'] }
309 kill_server_pid("{}/keep{}.pid".format(TEST_TMPDIR, n), 0)
310 if os.path.exists("{}/keep{}.volume".format(TEST_TMPDIR, n)):
311 with open("{}/keep{}.volume".format(TEST_TMPDIR, n), 'r') as r:
312 shutil.rmtree(r.read(), True)
313 os.unlink("{}/keep{}.volume".format(TEST_TMPDIR, n))
314 if os.path.exists(os.path.join(TEST_TMPDIR, "keep.blob_signing_key")):
315 os.remove(os.path.join(TEST_TMPDIR, "keep.blob_signing_key"))
321 def run_keep_proxy():
324 admin_token = auth_token('admin')
325 port = find_available_port()
326 env = os.environ.copy()
327 env['ARVADOS_API_TOKEN'] = admin_token
328 kp = subprocess.Popen(
330 '-pid={}/keepproxy.pid'.format(TEST_TMPDIR),
331 '-listen=:{}'.format(port)],
336 host=os.environ['ARVADOS_API_HOST'],
339 for d in api.keep_services().list(
340 filters=[['service_type','=','proxy']]).execute()['items']:
341 api.keep_services().delete(uuid=d['uuid']).execute()
342 api.keep_services().create(body={'keep_service': {
343 'service_host': 'localhost',
344 'service_port': port,
345 'service_type': 'proxy',
346 'service_ssl_flag': False,
348 os.environ["ARVADOS_KEEP_PROXY"] = "http://localhost:{}".format(port)
350 def stop_keep_proxy():
351 kill_server_pid(os.path.join(TEST_TMPDIR, "keepproxy.pid"), 0)
354 '''load a fixture yaml file'''
355 with open(os.path.join(SERVICES_SRC_DIR, 'api', "test", "fixtures",
359 trim_index = yaml_file.index("# Test Helper trims the rest of the file")
360 yaml_file = yaml_file[0:trim_index]
363 return yaml.load(yaml_file)
365 def auth_token(token_name):
366 return fixture("api_client_authorizations")[token_name]["api_token"]
368 def authorize_with(token_name):
369 '''token_name is the symbolic name of the token from the api_client_authorizations fixture'''
370 arvados.config.settings()["ARVADOS_API_TOKEN"] = auth_token(token_name)
371 arvados.config.settings()["ARVADOS_API_HOST"] = os.environ.get("ARVADOS_API_HOST")
372 arvados.config.settings()["ARVADOS_API_HOST_INSECURE"] = "true"
374 class TestCaseWithServers(unittest.TestCase):
375 """TestCase to start and stop supporting Arvados servers.
377 Define any of MAIN_SERVER, KEEP_SERVER, and/or KEEP_PROXY_SERVER
378 class variables as a dictionary of keyword arguments. If you do,
379 setUpClass will start the corresponding servers by passing these
380 keyword arguments to the run, run_keep, and/or run_keep_server
381 functions, respectively. It will also set Arvados environment
382 variables to point to these servers appropriately. If you don't
383 run a Keep or Keep proxy server, setUpClass will set up a
384 temporary directory for Keep local storage, and set it as
387 tearDownClass will stop any servers started, and restore the
388 original environment.
392 KEEP_PROXY_SERVER = None
395 def _restore_dict(src, dest):
396 for key in dest.keys():
403 cls._orig_environ = os.environ.copy()
404 cls._orig_config = arvados.config.settings().copy()
405 cls._cleanup_funcs = []
406 os.environ.pop('ARVADOS_KEEP_PROXY', None)
407 os.environ.pop('ARVADOS_EXTERNAL_CLIENT', None)
408 for server_kwargs, start_func, stop_func in (
409 (cls.MAIN_SERVER, run, reset),
410 (cls.KEEP_SERVER, run_keep, stop_keep),
411 (cls.KEEP_PROXY_SERVER, run_keep_proxy, stop_keep_proxy)):
412 if server_kwargs is not None:
413 start_func(**server_kwargs)
414 cls._cleanup_funcs.append(stop_func)
415 if (cls.KEEP_SERVER is None) and (cls.KEEP_PROXY_SERVER is None):
416 cls.local_store = tempfile.mkdtemp()
417 os.environ['KEEP_LOCAL_STORE'] = cls.local_store
418 cls._cleanup_funcs.append(
419 lambda: shutil.rmtree(cls.local_store, ignore_errors=True))
421 os.environ.pop('KEEP_LOCAL_STORE', None)
422 arvados.config.initialize()
425 def tearDownClass(cls):
426 for clean_func in cls._cleanup_funcs:
428 cls._restore_dict(cls._orig_environ, os.environ)
429 cls._restore_dict(cls._orig_config, arvados.config.settings())
432 if __name__ == "__main__":
433 actions = ['start', 'stop',
434 'start_keep', 'stop_keep',
435 'start_keep_proxy', 'stop_keep_proxy']
436 parser = argparse.ArgumentParser()
437 parser.add_argument('action', type=str, help="one of {}".format(actions))
438 parser.add_argument('--auth', type=str, metavar='FIXTURE_NAME', help='Print authorization info for given api_client_authorizations fixture')
439 args = parser.parse_args()
441 if args.action == 'start':
442 stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
443 run(leave_running_atexit=True)
444 host = os.environ['ARVADOS_API_HOST']
445 if args.auth is not None:
446 token = auth_token(args.auth)
447 print("export ARVADOS_API_TOKEN={}".format(pipes.quote(token)))
448 print("export ARVADOS_API_HOST={}".format(pipes.quote(host)))
449 print("export ARVADOS_API_HOST_INSECURE=true")
452 elif args.action == 'stop':
453 stop(force=('ARVADOS_TEST_API_HOST' not in os.environ))
454 elif args.action == 'start_keep':
456 elif args.action == 'stop_keep':
458 elif args.action == 'start_keep_proxy':
460 elif args.action == 'stop_keep_proxy':
463 print("Unrecognized action '{}'. Actions are: {}.".format(args.action, actions))