Add a enabled by files healthcheck plugin

Implements: blueprint enable-by-files-healthcheck
Change-Id: Ia8a54cbd38f2faf70bbe91032da36448f2fa2de7
This commit is contained in:
Abhishek Kekane 2024-05-20 16:04:36 +00:00 committed by Cyril Roelandt
parent e2ea8dc556
commit 01cd608104
9 changed files with 168 additions and 1 deletions

View File

@ -8,6 +8,9 @@
.. automodule:: oslo_middleware.healthcheck.disable_by_file .. automodule:: oslo_middleware.healthcheck.disable_by_file
:members: :members:
.. automodule:: oslo_middleware.healthcheck.enable_by_files
:members:
Available Plugins Available Plugins
------------------ ------------------

View File

@ -39,6 +39,7 @@ except ImportError:
greenlet = None greenlet = None
from oslo_middleware import base from oslo_middleware import base
from oslo_middleware.basic_auth import ConfigInvalid
from oslo_middleware.healthcheck import opts from oslo_middleware.healthcheck import opts
@ -397,6 +398,11 @@ Reason
for r in self._conf_get('allowed_source_ranges')] for r in self._conf_get('allowed_source_ranges')]
self._ignore_proxied_requests = self._conf_get( self._ignore_proxied_requests = self._conf_get(
'ignore_proxied_requests') 'ignore_proxied_requests')
# (abhishekk): Verify that if `enable_by_files` and
# `disable_by_file` backends are not enabled at same time.
self._verify_configured_plugins()
self._backends = stevedore.NamedExtensionManager( self._backends = stevedore.NamedExtensionManager(
self.NAMESPACE, self._conf_get('backends'), self.NAMESPACE, self._conf_get('backends'),
name_order=True, invoke_on_load=True, name_order=True, invoke_on_load=True,
@ -414,6 +420,16 @@ Reason
self._default_accept = 'text/plain' self._default_accept = 'text/plain'
self._ignore_path = False self._ignore_path = False
def _verify_configured_plugins(self):
backends = self._conf_get('backends')
desired_plugins = ['disable_by_file', 'enable_by_files']
if set(desired_plugins).issubset(set(backends)):
raise ConfigInvalid('`enable_by_files` and `disable_by_file`'
' healthcheck middleware should not be '
'configured at once.')
def _conf_get(self, key, group='healthcheck'): def _conf_get(self, key, group='healthcheck'):
return super(Healthcheck, self)._conf_get(key, group=group) return super(Healthcheck, self)._conf_get(key, group=group)

View File

@ -0,0 +1,60 @@
# Copyright 2024 Red Hat.
# 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 opts
from oslo_middleware.healthcheck import pluginbase
LOG = logging.getLogger(__name__)
class EnableByFilesHealthcheck(pluginbase.HealthcheckBaseExtension):
"""EnableByFilesHealthcheck healthcheck middleware plugin
This plugin checks presence of a file at a specified location.
Example of middleware configuration:
.. code-block:: ini
[app:healthcheck]
paste.app_factory = oslo_middleware:Healthcheck.app_factory
path = /healthcheck
backends = enable_by_files
enable_by_file_paths = /var/lib/glance/images/.marker,
/var/lib/glance/os_glance_staging_store/.marker
# set to True to enable detailed output, False is the default
detailed = False
"""
def __init__(self, *args, **kwargs):
super(EnableByFilesHealthcheck, self).__init__(*args, **kwargs)
self.oslo_conf.register_opts(opts.ENABLE_BY_FILES_OPTS,
group='healthcheck')
self.file_paths = self._conf_get('enable_by_file_paths')
def healthcheck(self, server_port):
for file_path in self.file_paths:
if not os.path.exists(file_path):
LOG.warning('EnableByFiles healthcheck middleware: Path %s '
'is not present', file_path)
return pluginbase.HealthcheckResult(
available=False, reason="FILE PATH MISSING",
details='File path %s is missing' % file_path)
return pluginbase.HealthcheckResult(
available=True, reason="OK",
details='Specified file paths are available')

View File

@ -57,3 +57,11 @@ DISABLE_BY_FILES_OPTS = [
'Expects a "port:path" list of strings. Used by ' 'Expects a "port:path" list of strings. Used by '
'DisableByFilesPortsHealthcheck plugin.'), 'DisableByFilesPortsHealthcheck plugin.'),
] ]
ENABLE_BY_FILES_OPTS = [
cfg.ListOpt('enable_by_file_paths',
default=[],
help='Check the presence of files. Used by '
'EnableByFilesHealthcheck plugin.'),
]

View File

@ -157,7 +157,8 @@ def list_opts_healthcheck():
return [ return [
('healthcheck', copy.deepcopy(healthcheck_opts.HEALTHCHECK_OPTS + ('healthcheck', copy.deepcopy(healthcheck_opts.HEALTHCHECK_OPTS +
healthcheck_opts.DISABLE_BY_FILE_OPTS + healthcheck_opts.DISABLE_BY_FILE_OPTS +
healthcheck_opts.DISABLE_BY_FILES_OPTS)) healthcheck_opts.DISABLE_BY_FILES_OPTS +
healthcheck_opts.ENABLE_BY_FILES_OPTS))
] ]

View File

@ -35,3 +35,17 @@ class TestPasteDeploymentEntryPoints(base.BaseTestCase):
factory_names = [extension.name for extension in em] factory_names = [extension.name for extension in em]
self.assertThat(factory_names, self.assertThat(factory_names,
matchers.ContainsAll(factory_classes)) matchers.ContainsAll(factory_classes))
def test_healthcheck_entry_points(self):
healthcheck_plugins = {
'disable_by_file': 'DisableByFileHealthcheck',
'disable_by_files_ports': 'DisableByFilesPortsHealthcheck',
'enable_by_files': 'EnableByFilesHealthcheck'
}
em = stevedore.ExtensionManager('oslo.middleware.healthcheck')
# Ensure all the healthcheck plugins are defined by their names
plugin_names = [extension.name for extension in em]
self.assertThat(plugin_names,
matchers.ContainsAll(healthcheck_plugins))

View File

@ -24,6 +24,7 @@ import requests
import webob.dec import webob.dec
import webob.exc import webob.exc
from oslo_middleware.basic_auth import ConfigInvalid
from oslo_middleware import healthcheck from oslo_middleware import healthcheck
from oslo_middleware.healthcheck import __main__ from oslo_middleware.healthcheck import __main__
@ -201,6 +202,63 @@ class HealthcheckTests(test_base.BaseTestCase):
server_port=81) server_port=81)
self.assertIn('disable_by_files_ports', self.app._backends.names()) self.assertIn('disable_by_files_ports', self.app._backends.names())
def test_enablefile_disablefile_configured(self):
conf = {'backends': 'disable_by_file,enable_by_files'}
self.assertRaises(ConfigInvalid,
healthcheck.Healthcheck, self.application, conf)
def test_enablefile_unconfigured(self):
conf = {'backends': 'enable_by_files'}
self._do_test(conf, expected_body=b'OK')
self.assertIn('enable_by_files', self.app._backends.names())
def test_enablefile_enabled(self):
filename = self.create_tempfiles([('.test', '.foobar')])[0]
conf = {'backends': 'enable_by_files',
'enable_by_file_paths': filename}
self._do_test(conf, expected_body=b'OK')
self.assertIn('enable_by_files', self.app._backends.names())
def test_enablefile_enabled_head(self):
filename = self.create_tempfiles([('.test', '.foobar')])[0]
conf = {'backends': 'enable_by_files',
'enable_by_file_paths': filename}
self._do_test(conf, expected_body=b'', method='HEAD',
expected_code=webob.exc.HTTPNoContent.code)
def test_enablefile_enabled_html_detailed(self):
filename = self.create_tempfiles([('.test', '.foobar')])[0]
conf = {'backends': 'enable_by_files',
'enable_by_file_paths': filename, 'detailed': True}
res = self._do_test_request(conf, accept="text/html")
self.assertIn(b'Result of 1 checks:', res.body)
self.assertIn(b'<TD>OK</TD>', res.body)
self.assertEqual(webob.exc.HTTPOk.code, res.status_int)
def test_enablefile_disabled(self):
conf = {'backends': 'enable_by_files',
'enable_by_file_paths': '.foobar'}
self._do_test(conf,
expected_code=webob.exc.HTTPServiceUnavailable.code,
expected_body=b'FILE PATH MISSING')
self.assertIn('enable_by_files', self.app._backends.names())
def test_enablefile_disabled_head(self):
conf = {'backends': 'enable_by_files',
'enable_by_file_paths': '.foobar'}
self._do_test(conf,
expected_code=webob.exc.HTTPServiceUnavailable.code,
expected_body=b'', method='HEAD')
self.assertIn('enable_by_files', self.app._backends.names())
def test_enablefile_disabled_html_detailed(self):
conf = {'backends': 'enable_by_files',
'enable_by_file_paths': '.foobar', 'detailed': True}
res = self._do_test_request(conf, accept="text/html")
self.assertIn(b'<TD>FILE PATH MISSING</TD>', res.body)
self.assertEqual(webob.exc.HTTPServiceUnavailable.code,
res.status_int)
def test_json_response(self): def test_json_response(self):
expected_body = jsonutils.dumps({'detailed': False, 'reasons': []}, expected_body = jsonutils.dumps({'detailed': False, 'reasons': []},
indent=4, indent=4,

View File

@ -0,0 +1,6 @@
---
features:
- |
The new ``enable_by_files`` healthcheck plugin has been added.
This plugin will help to check whether specified file paths in
``[healthcheck] enable_by_file_paths`` are present or not.

View File

@ -38,6 +38,7 @@ oslo.config.opts =
oslo.middleware.healthcheck = oslo.middleware.healthcheck =
disable_by_file = oslo_middleware.healthcheck.disable_by_file:DisableByFileHealthcheck disable_by_file = oslo_middleware.healthcheck.disable_by_file:DisableByFileHealthcheck
disable_by_files_ports = oslo_middleware.healthcheck.disable_by_file:DisableByFilesPortsHealthcheck disable_by_files_ports = oslo_middleware.healthcheck.disable_by_file:DisableByFilesPortsHealthcheck
enable_by_files = oslo_middleware.healthcheck.enable_by_files:EnableByFilesHealthcheck
paste.app_factory = paste.app_factory =
healthcheck = oslo_middleware:Healthcheck.app_factory healthcheck = oslo_middleware:Healthcheck.app_factory