Add a disabled by ports -> files healthcheck plugin

Since most openstack applications do not run on a single
port it is useful to be able to have a single healthcheck
plugin that can respond to requests for different ports with
different files; this way for example keystone admin port can
be disabled while its public port can be kept active (without
having to run two different applications, one for the admin
endpoint and one for the public endpoint).

Change-Id: I0bafb5a2091e54c9f01f24812438296b75afaf63
This commit is contained in:
Joshua Harlow 2015-10-22 17:15:24 -07:00
parent 4503d21268
commit ff3cfe054b
5 changed files with 94 additions and 6 deletions

View File

@ -343,7 +343,8 @@ class Healthcheck(base.ConfigurableMiddleware):
def process_request(self, req):
if req.path != self._path:
return None
results = [ext.obj.healthcheck() for ext in self._backends]
results = [ext.obj.healthcheck(req.server_port)
for ext in self._backends]
healthy = self._are_results_healthy(results)
if req.method == "HEAD":
functor = self._make_head_response

View File

@ -22,6 +22,57 @@ from oslo_middleware.healthcheck import pluginbase
LOG = logging.getLogger(__name__)
class DisableByFilesPortsHealthcheck(pluginbase.HealthcheckBaseExtension):
"""DisableByFilesPorts healthcheck middleware plugin
This plugin checks presence of a file that is provided for a application
running on a certain port to report if the service is unavailable
or not.
Example of middleware configuration:
.. code-block:: ini
[filter:healthcheck]
paste.filter_factory = oslo_middleware:Healthcheck.factory
path = /healthcheck
backends = disable_by_files_ports
disable_by_file_paths = 5000:/var/run/keystone/healthcheck_disable, \
35357:/var/run/keystone/admin_healthcheck_disable
"""
def __init__(self, conf):
super(DisableByFilesPortsHealthcheck, self).__init__(conf)
self.status_files = {}
self.status_files.update(
self._iter_paths_ports(self.conf.get('disable_by_file_paths')))
@staticmethod
def _iter_paths_ports(paths):
if paths:
for port_path in paths.split(","):
port_path = port_path.strip()
if port_path:
port, path = port_path.split(":")
port = int(port)
yield (port, path)
def healthcheck(self, server_port):
path = self.status_files.get(server_port)
if not path:
LOG.warning(_LW('DisableByFilesPorts healthcheck middleware'
' enabled without disable_by_file_paths set'
' for port %s') % server_port)
return pluginbase.HealthcheckResult(available=True,
reason="OK")
else:
if not os.path.exists(path):
return pluginbase.HealthcheckResult(available=True,
reason="OK")
else:
return pluginbase.HealthcheckResult(available=False,
reason="DISABLED BY FILE")
class DisableByFileHealthcheck(pluginbase.HealthcheckBaseExtension):
"""DisableByFile healthcheck middleware plugin
@ -39,7 +90,7 @@ class DisableByFileHealthcheck(pluginbase.HealthcheckBaseExtension):
disable_by_file_path = /var/run/nova/healthcheck_disable
"""
def healthcheck(self):
def healthcheck(self, server_port):
path = self.conf.get('disable_by_file_path')
if path is None:
LOG.warning(_LW('DisableByFile healthcheck middleware enabled '

View File

@ -28,7 +28,7 @@ class HealthcheckBaseExtension(object):
self.conf = conf
@abc.abstractmethod
def healthcheck():
def healthcheck(self, server_port):
"""method called by the healthcheck middleware
return: HealthcheckResult object

View File

@ -29,18 +29,21 @@ class HealthcheckTests(test_base.BaseTestCase):
return 'Hello, World!!!'
def _do_test_request(self, conf={}, path='/healthcheck',
accept='text/plain', method='GET'):
accept='text/plain', method='GET',
server_port=80):
self.app = healthcheck.Healthcheck(self.application, conf)
req = webob.Request.blank(path, accept=accept, method=method)
req.server_port = server_port
res = req.get_response(self.app)
return res
def _do_test(self, conf={}, path='/healthcheck',
expected_code=webob.exc.HTTPOk.code,
expected_body=b'', accept='text/plain',
method='GET'):
method='GET', server_port=80):
res = self._do_test_request(conf=conf, path=path,
accept=accept, method=method)
accept=accept, method=method,
server_port=server_port)
self.assertEqual(expected_code, res.status_int)
self.assertEqual(expected_body, res.body)
@ -125,3 +128,35 @@ class HealthcheckTests(test_base.BaseTestCase):
expected_code=webob.exc.HTTPServiceUnavailable.code,
expected_body=b'DISABLED BY FILE\nDISABLED BY FILE')
self.assertIn('disable_by_file', self.app._backends.names())
def test_disable_by_port_file(self):
filename = self.create_tempfiles([('test', 'foobar')])[0]
conf = {'backends': 'disable_by_files_ports',
'disable_by_file_paths': "80:%s" % filename}
self._do_test(conf,
expected_code=webob.exc.HTTPServiceUnavailable.code,
expected_body=b'DISABLED BY FILE')
self.assertIn('disable_by_files_ports', self.app._backends.names())
def test_no_disable_by_port_file(self):
filename = self.create_tempfiles([('test', 'foobar')])[0]
conf = {'backends': 'disable_by_files_ports',
'disable_by_file_paths': "8000:%s" % filename}
self._do_test(conf,
expected_code=webob.exc.HTTPOk.code,
expected_body=b'OK')
self.assertIn('disable_by_files_ports', self.app._backends.names())
def test_disable_by_port_many_files(self):
filename = self.create_tempfiles([('test', 'foobar')])[0]
filename2 = self.create_tempfiles([('test2', 'foobar2')])[0]
conf = {'backends': 'disable_by_files_ports',
'disable_by_file_paths': "80:%s,81:%s" % (filename, filename2)}
self._do_test(conf,
expected_code=webob.exc.HTTPServiceUnavailable.code,
expected_body=b'DISABLED BY FILE')
self._do_test(conf,
expected_code=webob.exc.HTTPServiceUnavailable.code,
expected_body=b'DISABLED BY FILE',
server_port=81)
self.assertIn('disable_by_files_ports', self.app._backends.names())

View File

@ -36,6 +36,7 @@ oslo.config.opts =
oslo.middleware.healthcheck =
disable_by_file = oslo_middleware.healthcheck.disable_by_file:DisableByFileHealthcheck
disable_by_files_ports = oslo_middleware.healthcheck.disable_by_file:DisableByFilesPortsHealthcheck
[build_sphinx]
source-dir = doc/source