def test_graceful_shutdown(self): pings = [] def end(): pings.append('app ends') subscribe(APP_ENDS, end) try: config = {'global.heartbeat_page': '__heartbeat__', 'global.debug_page': '__debug__', 'auth.backend': 'services.auth.dummy.DummyAuth', 'global.graceful_shutdown_interval': 1, 'global.hard_shutdown_interval': 1} urls = [] controllers = {} app = SyncServerApp(urls, controllers, config, auth_class=self.auth_class) # heartbeat should work request = make_request("/__heartbeat__") app(request) # let's "kill it" in a thread class Killer(threading.Thread): def __init__(self, app): threading.Thread.__init__(self) self.app = app def run(self): self.app._sigterm(None, None) killer = Killer(app) killer.start() sleep(0.2) # in the meantime, /heartbeat should return a 503 request = make_request("/__heartbeat__") self.assertRaises(HTTPServiceUnavailable, app, request) # but regular requests should still work request = make_request("/") app(request) # sleeping sleep(1.) # now / should 503 too request = make_request("/") self.assertRaises(HTTPServiceUnavailable, app, request) killer.join() finally: unsubscribe(APP_ENDS, end) # and we should have had a event ping self.assertEquals(pings, ['app ends'])
def test_host_config(self): request = make_request("/", method='POST', host='localhost') res = self.app(request) self.assertEqual(res.body, '2') request = make_request("/", method='POST', host='here') res = self.app(request) self.assertEqual(res.body, '1')
def test_notfound(self): # test that non-existent pages raise a 404. # this one has no match, so it will return early request = make_request("/nonexistent") self.assertEquals(self.app(request).status, "404 Not Found") # this one has a match, will raise from below the auth handler request = make_request("/missing") self.assertRaises(HTTPNotFound, self.app, request)
def test_heartbeat_debug_pages(self): config = { 'global.heartbeat_page': '__heartbeat__', 'global.debug_page': '__debug__', 'app.modules': ['metlog_loader'], 'metlog_loader.backend': 'services.metrics.MetlogLoader', 'metlog_loader.config': metlog_cfg_path, 'auth.backend': 'services.auth.dummy.DummyAuth' } urls = [] controllers = {} # testing the default configuration app = SyncServerApp(urls, controllers, config, auth_class=self.auth_class) # a heartbeat returns a 200 / empty body request = make_request("/__heartbeat__") res = app(request) self.assertEqual(res.status_int, 200) self.assertEqual(res.body, '') # we can get heartbeats with a HEAD call request = make_request("/__heartbeat__", method="HEAD") res = app(request) self.assertEqual(res.status_int, 200) self.assertEqual(res.body, '') # the debug page returns a 200 / info in the body request = make_request("/__debug__") res = app(request) self.assertEqual(res.status_int, 200) self.assertTrue("'REQUEST_METHOD': 'GET'" in res.body) # now let's create an app with extra heartbeating # and debug info class MyCoolApp(SyncServerApp): def _debug_server(self, request): return ['DEEBOOG'] def _check_server(self, request): raise HTTPServiceUnavailable() # testing that new app app = MyCoolApp(urls, controllers, config, auth_class=self.auth_class) # a heartbeat returns a 503 / empty body request = make_request("/__heartbeat__") self.assertRaises(HTTPServiceUnavailable, app, request) # the debug page returns a 200 / info in the body request = make_request("/__debug__") res = app(request) self.assertEqual(res.status_int, 200) self.assertTrue("DEEBOOG" in res.body)
def test_heartbeat_debug_pages(self): config = {'global.heartbeat_page': '__heartbeat__', 'global.debug_page': '__debug__', 'app.modules': ['metlog_loader'], 'metlog_loader.backend': 'services.metrics.MetlogLoader', 'metlog_loader.config': metlog_cfg_path, 'auth.backend': 'services.auth.dummy.DummyAuth'} urls = [] controllers = {} # testing the default configuration app = SyncServerApp(urls, controllers, config, auth_class=self.auth_class) # a heartbeat returns a 200 / empty body request = make_request("/__heartbeat__") res = app(request) self.assertEqual(res.status_int, 200) self.assertEqual(res.body, '') # we can get heartbeats with a HEAD call request = make_request("/__heartbeat__", method="HEAD") res = app(request) self.assertEqual(res.status_int, 200) self.assertEqual(res.body, '') # the debug page returns a 200 / info in the body request = make_request("/__debug__") res = app(request) self.assertEqual(res.status_int, 200) self.assertTrue("'REQUEST_METHOD': 'GET'" in res.body) # now let's create an app with extra heartbeating # and debug info class MyCoolApp(SyncServerApp): def _debug_server(self, request): return ['DEEBOOG'] def _check_server(self, request): raise HTTPServiceUnavailable() # testing that new app app = MyCoolApp(urls, controllers, config, auth_class=self.auth_class) # a heartbeat returns a 503 / empty body request = make_request("/__heartbeat__") self.assertRaises(HTTPServiceUnavailable, app, request) # the debug page returns a 200 / info in the body request = make_request("/__debug__") res = app(request) self.assertEqual(res.status_int, 200) self.assertTrue("DEEBOOG" in res.body)
def test_syncNode_checking(self): config = self.make_config({"auth.check_node": True}) auth = self.auth_class(config) # check() should pass for requests to "localhost" req = make_request("/1.0/tarek/info/collections", host="localhost") self.set_credentials(req, "user", "goodpwd") auth.check(req, {"auth": "True"}) # check() should fail if request to the wrong node req = make_request("/1.0/tarek/info/collections", host="badnode") self.set_credentials(req, "user", "goodpwd") self.assertRaises(HTTPException, auth.check, req, {"auth": "True"})
def test_acknowledge_method(self): config = self.make_config() auth = self.auth_class(config) # Actually there's not much to test here. # Just make sure it doesn't raise anything. req = make_request('/1.0/tarek/info/collections') resp = Response(status="200 OK", request=req) auth.acknowledge(req, resp) req = make_request('/1.0/tarek/info/collections') self.set_credentials(req, "user", "goodpwd") auth.check(req, {"auth": "True"}) resp = Response(status="200 OK", request=req) auth.acknowledge(req, resp)
def test_crash_id(self): # getting a 50x should generate a crash id request = make_request("/boom") try: self.app(request) except HTTPServiceUnavailable, err: self.assertTrue('application error: crash id' in str(err))
def test_addl_services_data(self): path = '/1.1/%s/info/collections' % self.username environ = {'HTTP_AUTHORIZATION': self.authorization} request = make_request(path, environ) request.user_agent = 'USER_AGENT' controller = self.app.controllers['storage'] wrapped_method = controller._get_collections_wrapped orig_inner = wrapped_method._fn._fn._fn data = {'foo': 'bar'} def services_data_wrapper(fn): from services.metrics import update_metlog_data def new_inner(*args, **kwargs): update_metlog_data(data) return fn(*args, **kwargs) return new_inner wrapped_method._fn._fn._fn = services_data_wrapper(orig_inner) self.app(request) sender = self.app.logger.sender msgs = list(sender.msgs)[-3:] msg2 = json.loads(msgs[2]) self.assertEqual(msg2.get('type'), 'services') expected = data.copy() expected['userid'] = self.userid expected['req_time'] = msg2['fields']['req_time'] self.assertEqual(msg2['fields'], expected) wrapped_method._fn._fn._fn = orig_inner
def test_events(self): pings = [] def starts(request): pings.append('starts') def ends(response): pings.append('ends') subscribe(REQUEST_STARTS, starts) subscribe(REQUEST_ENDS, ends) try: config = { 'global.heartbeat_page': '__heartbeat__', 'global.debug_page': '__debug__', 'auth.backend': 'services.auth.dummy.DummyAuth' } urls = [] controllers = {} app = SyncServerApp(urls, controllers, config, auth_class=self.auth_class) request = make_request("/user/__hearbeat__") app(request) finally: unsubscribe(REQUEST_STARTS, starts) unsubscribe(REQUEST_ENDS, ends) self.assertEquals(pings, ['starts', 'ends'])
def test_nosigclean(self): # check that we can deactivate sigterm/sigint hooks pings = [] def end(): pings.append('app ends') subscribe(APP_ENDS, end) try: config = { 'global.heartbeat_page': '__heartbeat__', 'global.debug_page': '__debug__', 'auth.backend': 'services.auth.dummy.DummyAuth', 'global.clean_shutdown': False } urls = [] controllers = {} app = SyncServerApp(urls, controllers, config, auth_class=self.auth_class) # heartbeat should work request = make_request("/__heartbeat__") app(request) finally: unsubscribe(APP_ENDS, end) # and we should have had no ping self.assertEquals(pings, [])
def test_events(self): pings = [] def starts(request): pings.append('starts') def ends(response): pings.append('ends') subscribe(REQUEST_STARTS, starts) subscribe(REQUEST_ENDS, ends) try: config = {'global.heartbeat_page': '__heartbeat__', 'global.debug_page': '__debug__', 'auth.backend': 'services.auth.dummy.DummyAuth'} urls = [] controllers = {} app = SyncServerApp(urls, controllers, config, auth_class=self.auth_class) request = make_request("/user/__hearbeat__") app(request) finally: unsubscribe(REQUEST_STARTS, starts) unsubscribe(REQUEST_ENDS, ends) self.assertEquals(pings, ['starts', 'ends'])
def test_nosigclean(self): # check that we can deactivate sigterm/sigint hooks pings = [] def end(): pings.append('app ends') subscribe(APP_ENDS, end) try: config = {'global.heartbeat_page': '__heartbeat__', 'global.debug_page': '__debug__', 'auth.backend': 'services.auth.dummy.DummyAuth', 'global.clean_shutdown': False} urls = [] controllers = {} app = SyncServerApp(urls, controllers, config, auth_class=self.auth_class) # heartbeat should work request = make_request("/__heartbeat__") app(request) finally: unsubscribe(APP_ENDS, end) # and we should have had no ping self.assertEquals(pings, [])
def test_auth(self): # it should ask us to authenticate using HTTP-Basic-Auth request = make_request("/secret", method='GET') try: self.app(request) except HTTPUnauthorized, error: self.assertEqual(error.headers['WWW-Authenticate'], 'Basic realm="Sync"')
def test_bad_utf8_password(self): config = self.make_config() auth = self.auth_class(config) password = u'И'.encode('cp866') token = 'tarek:%s' % password token = 'Basic ' + base64.b64encode(token) req = make_request('/1.0/tarek/info/collections', {'HTTP_AUTHORIZATION': token}) self.assertRaises(HTTPUnauthorized, auth.check, req, {"auth": "True"})
def test_obfuscation(self): req = make_request("/__debug__", _ENVIRON) controller = StandardController(None) def _more_secret(*args): return ['stuff', 'and', 'pymysql://*****:*****@localhost/sync'] controller._debug_server = _more_secret debug = controller._debug(req) # make sure we don't have any password left self.assertTrue('xxxx' not in debug.body)
def test_check_method(self): config = self.make_config() auth = self.auth_class(config) # check() should pass through requests where no auth is required req = make_request('/1.0/tarek/info/collections') auth.check(req, {}) # check() should demand auth when required by the match. req = make_request('/1.0/tarek/info/collections') self.assertRaises(HTTPException, auth.check, req, {"auth": "True"}) # check() should fail auth when the password is bad req = make_request('/1.0/tarek/info/collections') self.set_credentials(req, "user", "badpwd") self.assertRaises(HTTPException, auth.check, req, {"auth": "True"}) # check() should pass through when the password is good req = make_request('/1.0/tarek/info/collections') self.set_credentials(req, "user", "goodpwd") auth.check(req, {"auth": "True"}) self.assertEquals(req.user["username"], "user") self.assertEquals(req.user["userid"], 1) # check() should fail auth if username doesn't match the match match = {"auth": "True", "username": "******"} req = make_request('/1.0/tarek/info/collections') self.set_credentials(req, "user2", "goodpwd") self.assertRaises(HTTPException, auth.check, req, match) # check() should pass through if username matches the match match = {"auth": "True", "username": "******"} req = make_request('/1.0/tarek/info/collections') self.set_credentials(req, "user1", "goodpwd") auth.check(req, match)
def test_malformed_auth_headers(self): config = self.make_config() auth = self.auth_class(config) req = make_request('/1.0/tarekbad', {'HTTP_AUTHORIZATION': 'Basic ', 'REQUEST_METHOD': 'TEST', 'PATH_INFO': 'TEST'}) self.assertRaises(HTTPUnauthorized, auth.check, req, {"auth": "True"}) req = make_request('/1.0/tarekbad', {'HTTP_AUTHORIZATION': 'Basic invalid_b64', 'REQUEST_METHOD': 'TEST', 'PATH_INFO': 'TEST'}) self.assertRaises(HTTPUnauthorized, auth.check, req, {"auth": "True"}) req = make_request('/1.0/tarekbad', {'HTTP_AUTHORIZATION': 'Basic ' + base64.b64encode('malformed_creds'), 'REQUEST_METHOD': 'TEST', 'PATH_INFO': 'TEST'}) self.assertRaises(HTTPUnauthorized, auth.check, req, {"auth": "True"})
def test_authenticate_user(self): config = self.make_config() auth = self.auth_class(config) token = 'Basic ' + base64.b64encode('tarek:tarek') req = make_request('/1.0/tarek/info/collections', {}) res = auth.authenticate_user(req, {}) self.assertEquals(res, None) # authenticated by auth req = make_request('/1.0/tarek/info/collections', {'HTTP_AUTHORIZATION': token}) res = auth.authenticate_user(req, {}) self.assertEquals(res, 1) # weird tokens should not break the function bad_token1 = 'Basic ' + base64.b64encode('tarektarek') bad_token2 = 'Basic' + base64.b64encode('tarek:tarek') req = make_request('/1.0/tarek/info/collections', {'HTTP_AUTHORIZATION': bad_token1}) self.assertRaises(HTTPUnauthorized, auth.authenticate_user, req, config) req = make_request('/1.0/tarek/info/collections', {'HTTP_AUTHORIZATION': bad_token2}) self.assertRaises(HTTPUnauthorized, auth.authenticate_user, req, config) # check a bad request to an invalid user. req = make_request('/1.0/tarekbad', {'HTTP_AUTHORIZATION': 'Basic ' + base64.b64encode('tarekbad:tarek'), 'REQUEST_METHOD': 'TEST', 'PATH_INFO': 'TEST'}) self.assertRaises(HTTPUnauthorized, auth.authenticate_user, req, config)
def test_stats_go_out(self): path = '/1.1/%s/info/collections' % self.username environ = {'HTTP_AUTHORIZATION': self.authorization} request = make_request(path, environ) self.app(request) sender = self.app.logger.sender msgs = list(sender.msgs)[-3:] msg0 = json.loads(msgs[0]) self.assertEqual(msg0.get('type'), 'timer') msg1 = json.loads(msgs[1]) self.assertEqual(msg1.get('type'), 'counter') msg2 = json.loads(msgs[2]) self.assertEqual(msg2.get('type'), 'services') self.assertEqual(msg2['fields']['userid'], self.userid) self.assertTrue('req_time' in msg2['fields'])
def test_retry_after(self): config = {'global.retry_after': 60, 'auth.backend': 'services.auth.dummy.DummyAuth', 'app.modules': ['metlog_loader'], 'metlog_loader.backend': 'services.metrics.MetlogLoader', 'metlog_loader.config': metlog_cfg_path, } urls = [('GET', '/boom', 'foo', 'boom'), ('GET', '/boom2', 'foo', 'boom2'), ('GET', '/boom3', 'foo', 'boom3')] controllers = {'foo': _Foo} app = SyncServerApp(urls, controllers, config, auth_class=self.auth_class) request = make_request("/boom", method="GET", host="localhost") try: app(request) except HTTPServiceUnavailable, error: self.assertEqual(error.headers['Retry-After'], '10')
def test_retry_after(self): config = { 'global.retry_after': 60, 'auth.backend': 'services.auth.dummy.DummyAuth', 'app.modules': ['metlog_loader'], 'metlog_loader.backend': 'services.metrics.MetlogLoader', 'metlog_loader.config': metlog_cfg_path, } urls = [('GET', '/boom', 'foo', 'boom'), ('GET', '/boom2', 'foo', 'boom2'), ('GET', '/boom3', 'foo', 'boom3')] controllers = {'foo': _Foo} app = SyncServerApp(urls, controllers, config, auth_class=self.auth_class) request = make_request("/boom", method="GET", host="localhost") try: app(request) except HTTPServiceUnavailable, error: self.assertEqual(error.headers['Retry-After'], '10')
def test_unusual_passwords(self): config = self.make_config() auth = self.auth_class(config) # Check that passwords containing unusual characters still work. unusual_chars = u":!@= \t\n\N{GREEK SMALL LETTER ALPHA}\N{SNOWMAN}" for char in unusual_chars: # works at start of good pwd req = make_request('/1.0/tarek/info/collections') self.set_credentials(req, "user", char + "goodpwd") auth.check(req, {"auth": "True"}) # works at end of good pwd req = make_request('/1.0/tarek/info/collections') self.set_credentials(req, "user", "goodpwd" + char) auth.check(req, {"auth": "True"}) # works in middle of good pwd req = make_request('/1.0/tarek/info/collections') self.set_credentials(req, "user", "good" + char + "pwd") auth.check(req, {"auth": "True"}) # fails at start of bad pwd req = make_request('/1.0/tarek/info/collections') self.set_credentials(req, "user", char + "badpwd") self.assertRaises(HTTPException, auth.check, req, {"auth": "True"}) # fails at end of bad pwd req = make_request('/1.0/tarek/info/collections') self.set_credentials(req, "user", "badwd" + char) self.assertRaises(HTTPException, auth.check, req, {"auth": "True"}) # fails in middle of bad pwd req = make_request('/1.0/tarek/info/collections') self.set_credentials(req, "user", "bad" + char + "pwd") self.assertRaises(HTTPException, auth.check, req, {"auth": "True"})
def test_graceful_shutdown(self): pings = [] def end(): pings.append('app ends') subscribe(APP_ENDS, end) try: config = { 'global.heartbeat_page': '__heartbeat__', 'global.debug_page': '__debug__', 'auth.backend': 'services.auth.dummy.DummyAuth', 'global.graceful_shutdown_interval': 1, 'global.hard_shutdown_interval': 1 } urls = [] controllers = {} app = SyncServerApp(urls, controllers, config, auth_class=self.auth_class) # heartbeat should work request = make_request("/__heartbeat__") app(request) # let's "kill it" in a thread class Killer(threading.Thread): def __init__(self, app): threading.Thread.__init__(self) self.app = app def run(self): self.app._sigterm(None, None) killer = Killer(app) killer.start() sleep(0.2) # in the meantime, /heartbeat should return a 503 request = make_request("/__heartbeat__") self.assertRaises(HTTPServiceUnavailable, app, request) # but regular requests should still work request = make_request("/") app(request) # sleeping sleep(1.) # now / should 503 too request = make_request("/") self.assertRaises(HTTPServiceUnavailable, app, request) killer.join() finally: unsubscribe(APP_ENDS, end) # and we should have had a event ping self.assertEquals(pings, ['app ends'])
def test_methodnotallowed(self): request = make_request("/", method="OST") self.assertEquals(self.app(request).status, "405 Method Not Allowed")
def test_user(self): # the debug page returns a the right username in the body request = make_request("/user/testuser") res = self.app(request) self.assertEqual(res.status_int, 200) self.assertTrue("|testuser|" in res.body)
('GET', '/boom3', 'foo', 'boom3')] controllers = {'foo': _Foo} app = SyncServerApp(urls, controllers, config, auth_class=self.auth_class) request = make_request("/boom", method="GET", host="localhost") try: app(request) except HTTPServiceUnavailable, error: self.assertEqual(error.headers['Retry-After'], '10') else: raise AssertionError() # default retry_after value request = make_request("/boom2", method="GET", host="localhost") try: app(request) except HTTPServiceUnavailable, error: self.assertEqual(error.headers['Retry-After'], '60') else: raise AssertionError() # no retry-after (set to -1) request = make_request("/boom3", method="GET", host="localhost") logger = app.logger old = logger.error errors = [] def _error(msg): errors.append(msg)
def test_auth(self): """Test authentication using the specific auth class.""" # we don't have any auth, this should just work request = make_request("/secret", method='GET') res = self.app(request) self.assertEqual(res.body, 'here')
controllers = {'foo': _Foo} app = SyncServerApp(urls, controllers, config, auth_class=self.auth_class) request = make_request("/boom", method="GET", host="localhost") try: app(request) except HTTPServiceUnavailable, error: self.assertEqual(error.headers['Retry-After'], '10') else: raise AssertionError() # default retry_after value request = make_request("/boom2", method="GET", host="localhost") try: app(request) except HTTPServiceUnavailable, error: self.assertEqual(error.headers['Retry-After'], '60') else: raise AssertionError() # no retry-after (set to -1) request = make_request("/boom3", method="GET", host="localhost") logger = app.logger old = logger.error errors = [] def _error(msg): errors.append(msg)
def test_route_match_with_empty_path(self): request = make_request("", method="OST") self.assertEquals(self.app(request).status, "404 Not Found")