diff --git a/autopush/http.py b/autopush/http.py index 4cf0a7e4..68dcf44e 100644 --- a/autopush/http.py +++ b/autopush/http.py @@ -44,6 +44,7 @@ RouterHandler, ) from autopush.websocket import PushServerProtocol # noqa +from autopush.web.dockerflow import VersionHandler, LBHeartbeatHandler APHandlers = Sequence[Tuple[str, Type[BaseHandler]]] CycloneLogger = Callable[[BaseHandler], None] @@ -61,6 +62,10 @@ class BaseHTTPFactory(cyclone.web.Application): health_ap_handlers = ( (r"^/status", StatusHandler), (r"^/health", HealthHandler), + # DockerFlow checks + (r"^/__version__", VersionHandler), + (r"^/__heartbeat__", StatusHandler), + (r"^/__lbheartbeat__", LBHeartbeatHandler), ) def __init__(self, diff --git a/autopush/tests/test_health.py b/autopush/tests/test_health.py index e45bff2c..cd7e638c 100644 --- a/autopush/tests/test_health.py +++ b/autopush/tests/test_health.py @@ -1,7 +1,8 @@ import json +import pytest import twisted.internet.base -from mock import Mock +from mock import Mock, patch from twisted.internet.defer import inlineCallbacks from twisted.logger import globalLogPublisher from twisted.trial import unittest @@ -15,6 +16,7 @@ from autopush.tests.client import Client from autopush.tests.support import TestingLogObserver from autopush.web.health import HealthHandler, StatusHandler +from autopush.web.dockerflow import LBHeartbeatHandler, VersionHandler import autopush.tests @@ -99,3 +101,68 @@ def test_status(self): "status": "OK", "version": __version__ }) + + +class DockerflowTestCase(unittest.TestCase): + def setUp(self): + self.timeout = 4 + twisted.internet.base.DelayedCall.debug = True + + conf = AutopushConfig( + hostname="localhost", + statsd_host=None, + ) + + # ignore logging + logs = TestingLogObserver() + begin_or_register(logs) + self.addCleanup(globalLogPublisher.removeObserver, logs) + + lb_app = EndpointHTTPFactory.for_handler( + LBHeartbeatHandler, conf, db=None + ) + ver_app = EndpointHTTPFactory.for_handler( + VersionHandler, conf, db=None + ) + self.lb_client = Client(lb_app) + self.ver_client = Client(ver_app) + + @inlineCallbacks + def test_lbheartbeat(self): + resp = yield self.lb_client.get("/__lbheartbeat__") + assert resp.get_status() == 200 + + @patch('autopush.web.dockerflow.open') + def test_version(self, mopen): + version = """{ + "source" : "https://github.com/mozilla-services/autopush", + "version": "devel", + "commit" : "", + "build" : "" +} +""" + reader = Mock() + reader.read.return_value = version + mopen.__enter__.return_value = reader + mopen.return_value = mopen + resp = yield self.ver_client.get("/__version__") + assert resp.get_status() == 200 + assert resp.content == version + + @patch('autopush.web.dockerflow.open') + def test_bad_version(self, mopen): + reader = Mock() + reader.read.side_effect = IOError + mopen.__enter__.return_value = reader + mopen.return_value = mopen + conf = AutopushConfig( + hostname="localhost", + statsd_host=None, + ) + self.request_mock = Mock() + bad_ver = VersionHandler( + EndpointHTTPFactory(conf, db=None, routers=None), + self.request_mock + ) + with pytest.raises(IOError): + bad_ver._get_version() diff --git a/autopush/web/dockerflow.py b/autopush/web/dockerflow.py new file mode 100644 index 00000000..bc275002 --- /dev/null +++ b/autopush/web/dockerflow.py @@ -0,0 +1,48 @@ +"""DockerFlow endpoints""" + +import cyclone.web + +from twisted.internet.threads import deferToThread +from autopush.web.base import BaseWebHandler + + +class VersionHandler(BaseWebHandler): + + def _get_version(self): + self.set_header("Content-Type", "application/json") + try: + with open("version.json") as vers: + self.write(vers.read()) + except IOError as ex: + # Trap the exception here because it can be converted to + # a generic AssertionError failure, and context gets lost. + self.log.error( + "Could not display version.json file content {}".format( + ex + )) + raise + + def _error(self, failure): + failure.trap(AssertionError, IOError) + self.set_status(500) + self.write("") + + def get(self): + d = deferToThread(self._get_version) + d.addBoth(self.finish) + d.addErrback(self._error) + return d + + def authenticate_peer_cert(self): + pass + + +class LBHeartbeatHandler(BaseWebHandler): + + @cyclone.web.asynchronous + def get(self): + self.write("") + self.finish() + + def authenticate_peer_cert(self): + pass diff --git a/version.json b/version.json new file mode 100644 index 00000000..f0c32d7d --- /dev/null +++ b/version.json @@ -0,0 +1,6 @@ +{ + "source" : "https://github.com/mozilla-services/autopush", + "version": "devel", + "commit" : "", + "build" : "" +}