summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMehdi Abaakouk <mehdi.abaakouk@enovance.com>2014-11-27 18:05:24 +0100
committerMehdi Abaakouk <mehdi.abaakouk@enovance.com>2015-01-19 10:32:18 +0100
commitedabbd1619029c74c56134e1570736d1f71e1f21 (patch)
tree55ae002624e03425d0c3dc08ebc30f9e2100e44f
parent020fd92f2d789828cd7de06928c65233f4e6b87f (diff)
downloadoslo-middleware-edabbd1619029c74c56134e1570736d1f71e1f21.tar.gz
Add healthcheck middleware
Implements blueprint oslo-middleware-healthcheck Change-Id: Id19a47c07ff4fbf954ab188b7186361ed9ef213a
-rw-r--r--oslo_middleware/healthcheck/__init__.py74
-rw-r--r--oslo_middleware/healthcheck/disable_by_file.py38
-rw-r--r--oslo_middleware/healthcheck/pluginbase.py35
-rw-r--r--oslo_middleware/tests/test_healthcheck.py85
-rw-r--r--requirements.txt1
-rw-r--r--setup.cfg3
6 files changed, 236 insertions, 0 deletions
diff --git a/oslo_middleware/healthcheck/__init__.py b/oslo_middleware/healthcheck/__init__.py
new file mode 100644
index 0000000..559770a
--- /dev/null
+++ b/oslo_middleware/healthcheck/__init__.py
@@ -0,0 +1,74 @@
+# Copyright 2011 OpenStack Foundation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import stevedore
+import webob.dec
+import webob.exc
+import webob.response
+
+from oslo_middleware import base
+
+
+class Healthcheck(base.Middleware):
+ """Helper class that returns debug information.
+
+ Can be inserted into any WSGI application chain to get information about
+ the request and response.
+ """
+
+ NAMESPACE = "oslo.middleware.healthcheck"
+
+ @classmethod
+ def factory(cls, global_conf, **local_conf):
+ """Factory method for paste.deploy."""
+ conf = global_conf.copy()
+ conf.update(local_conf)
+
+ def healthcheck_filter(app):
+ return cls(app, conf)
+ return cls
+
+ def __init__(self, application, conf):
+ super(Healthcheck, self).__init__(application)
+ self._path = conf.get('path', '/healthcheck')
+ self._backend_names = []
+ backends = conf.get('backends')
+ if backends:
+ self._backend_names = backends.split(',')
+
+ self._backends = stevedore.NamedExtensionManager(
+ self.NAMESPACE, self._backend_names,
+ name_order=True, invoke_on_load=True,
+ invoke_args=(conf,))
+
+ @webob.dec.wsgify
+ def process_request(self, req):
+ if req.path != self._path:
+ return None
+
+ healthy = True
+ reasons = []
+ for ext in self._backends:
+ result = ext.obj.healthcheck()
+ healthy &= result.available
+ if result.reason:
+ reasons.append(result.reason)
+
+ return webob.response.Response(
+ status=(webob.exc.HTTPOk.code if healthy
+ else webob.exc.HTTPServiceUnavailable.code),
+ body='\n'.join(reasons),
+ content_type="text/plain",
+ )
diff --git a/oslo_middleware/healthcheck/disable_by_file.py b/oslo_middleware/healthcheck/disable_by_file.py
new file mode 100644
index 0000000..7e964a4
--- /dev/null
+++ b/oslo_middleware/healthcheck/disable_by_file.py
@@ -0,0 +1,38 @@
+# Copyright 2011 OpenStack Foundation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import logging
+import os
+
+from oslo_middleware.healthcheck import pluginbase
+from oslo_middleware.i18n import _LW
+
+LOG = logging.getLogger(__name__)
+
+
+class DisableByFileHealthcheck(pluginbase.HealthcheckBaseExtension):
+ def healthcheck(self):
+ path = self.conf.get('disable_by_file_path')
+ if path is None:
+ LOG.warning(_LW('DisableByFile healthcheck middleware enabled '
+ 'without disable_by_file_path set'))
+ return pluginbase.HealthcheckResult(available=True,
+ reason="")
+ elif not os.path.exists(path):
+ return pluginbase.HealthcheckResult(available=True,
+ reason="")
+ else:
+ return pluginbase.HealthcheckResult(available=False,
+ reason="DISABLED BY FILE")
diff --git a/oslo_middleware/healthcheck/pluginbase.py b/oslo_middleware/healthcheck/pluginbase.py
new file mode 100644
index 0000000..8c6e9d4
--- /dev/null
+++ b/oslo_middleware/healthcheck/pluginbase.py
@@ -0,0 +1,35 @@
+# Copyright 2011 OpenStack Foundation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import abc
+import collections
+
+import six
+
+HealthcheckResult = collections.namedtuple(
+ 'HealthcheckResult', ['available', 'reason'], verbose=True)
+
+
+@six.add_metaclass(abc.ABCMeta)
+class HealthcheckBaseExtension(object):
+ def __init__(self, conf):
+ self.conf = conf
+
+ @abc.abstractmethod
+ def healthcheck():
+ """method called by the healthcheck middleware
+
+ return: HealthcheckResult object
+ """
diff --git a/oslo_middleware/tests/test_healthcheck.py b/oslo_middleware/tests/test_healthcheck.py
new file mode 100644
index 0000000..46d964c
--- /dev/null
+++ b/oslo_middleware/tests/test_healthcheck.py
@@ -0,0 +1,85 @@
+# Copyright (c) 2013 NEC Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import mock
+from oslotest import base as test_base
+import webob.dec
+import webob.exc
+
+from oslo_middleware import healthcheck
+
+
+class HealthcheckTests(test_base.BaseTestCase):
+
+ @staticmethod
+ @webob.dec.wsgify
+ def application(req):
+ return 'Hello, World!!!'
+
+ def _do_test(self, conf={}, path='/healthcheck',
+ expected_code=webob.exc.HTTPOk.code,
+ expected_body=b''):
+ self.app = healthcheck.Healthcheck(self.application, conf)
+ req = webob.Request.blank(path)
+ res = req.get_response(self.app)
+ self.assertEqual(expected_code, res.status_int)
+ self.assertEqual(expected_body, res.body)
+
+ def test_default_path_match(self):
+ self._do_test()
+
+ def test_default_path_not_match(self):
+ self._do_test(path='/toto', expected_body=b'Hello, World!!!')
+
+ def test_configured_path_match(self):
+ conf = {'path': '/hidden_healthcheck'}
+ self._do_test(conf, path='/hidden_healthcheck')
+
+ def test_configured_path_not_match(self):
+ conf = {'path': '/hidden_healthcheck'}
+ self._do_test(conf, path='/toto', expected_body=b'Hello, World!!!')
+
+ @mock.patch('logging.warn')
+ def test_disablefile_unconfigured(self, fake_warn):
+ conf = {'backends': 'disable_by_file'}
+ self._do_test(conf, expected_body=b'')
+ self.assertIn('disable_by_file', self.app._backends.names())
+ fake_warn.assert_called_once('DisableByFile healthcheck middleware '
+ 'enabled without disable_by_file_path '
+ 'set')
+
+ def test_disablefile_enabled(self):
+ conf = {'backends': 'disable_by_file',
+ 'disable_by_file_path': '/foobar'}
+ self._do_test(conf, expected_body=b'')
+ self.assertIn('disable_by_file', self.app._backends.names())
+
+ def test_disablefile_disabled(self):
+ filename = self.create_tempfiles([('test', 'foobar')])[0]
+ conf = {'backends': 'disable_by_file',
+ 'disable_by_file_path': filename}
+ self._do_test(conf,
+ expected_code=webob.exc.HTTPServiceUnavailable.code,
+ expected_body=b'DISABLED BY FILE')
+ self.assertIn('disable_by_file', self.app._backends.names())
+
+ def test_two_backends(self):
+ filename = self.create_tempfiles([('test', 'foobar')])[0]
+ conf = {'backends': 'disable_by_file,disable_by_file',
+ 'disable_by_file_path': filename}
+ self._do_test(conf,
+ expected_code=webob.exc.HTTPServiceUnavailable.code,
+ expected_body=b'DISABLED BY FILE\nDISABLED BY FILE')
+ self.assertIn('disable_by_file', self.app._backends.names())
diff --git a/requirements.txt b/requirements.txt
index 1b66bf0..90f75b5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,4 +8,5 @@ oslo.config>=1.4.0 # Apache-2.0
oslo.context>=0.1.0 # Apache-2.0
oslo.i18n>=1.0.0 # Apache-2.0
six>=1.7.0
+stevedore>=1.1.0 # Apache-2.0
WebOb>=1.2.3
diff --git a/setup.cfg b/setup.cfg
index b44853b..601ed3b 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -30,6 +30,9 @@ namespace_packages =
oslo.config.opts =
oslo.middleware = oslo_middleware.opts:list_opts
+oslo.middleware.healthcheck =
+ disable_by_file = oslo_middleware.healthcheck.disable_by_file:DisableByFileHealthcheck
+
[build_sphinx]
source-dir = doc/source
build-dir = doc/build