From 19cf71dc79bcba91132dcd769eeb99e18b09b754 Mon Sep 17 00:00:00 2001 From: Gabriel Cocenza Date: Fri, 17 Feb 2023 11:38:13 -0300 Subject: [PATCH] Add support for HAProxy L7 checks This change add several configuration options to enable HTTP checks to the HAProxy configuration, instead of the default TCP connection checks (which continue to be the default). It also enables /healthcheck endpoint for heat-api and heat-cfn-api on openstack releases >= queens. Closes-Bug: #1880610 Change-Id: I94c9418c82cdddd5a5d9ed400ab47889bfb225b1 --- hooks/heat_context.py | 13 +++++++++++++ templates/queens/api-paste.ini | 18 ++++++++++-------- tox.ini | 2 +- unit_tests/test_heat_context.py | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 9 deletions(-) diff --git a/hooks/heat_context.py b/hooks/heat_context.py index cf98646..70bbb79 100644 --- a/hooks/heat_context.py +++ b/hooks/heat_context.py @@ -23,6 +23,7 @@ from charmhelpers.core.hookenv import ( from charmhelpers.contrib.hahelpers.cluster import ( determine_apache_port, determine_api_port, + https, ) HEAT_PATH = '/var/lib/heat/' @@ -89,12 +90,24 @@ class HeatHAProxyContext(context.OSContextGenerator): apache_cfn_port = determine_apache_port(haproxy_cfn_port, singlenode_mode=True) + healthcheck = [{ + 'option': 'httpchk GET /healthcheck', + 'http-check': 'expect status 200', + }] + + backend_options = { + 'heat_api': healthcheck, + 'heat_cfn_api': healthcheck + } + ctxt = { 'service_ports': {'heat_api': [haproxy_port, apache_port], 'heat_cfn_api': [haproxy_cfn_port, apache_cfn_port]}, 'api_listen_port': api_port, 'api_cfn_listen_port': api_cfn_port, + 'backend_options': backend_options, + 'https': https(), } return ctxt diff --git a/templates/queens/api-paste.ini b/templates/queens/api-paste.ini index e01847c..dca3433 100644 --- a/templates/queens/api-paste.ini +++ b/templates/queens/api-paste.ini @@ -1,7 +1,7 @@ # heat-api pipeline [pipeline:heat-api] -pipeline = cors request_id faultwrap http_proxy_to_wsgi versionnegotiation osprofiler authurl authtoken context apiv1app +pipeline = healthcheck cors request_id faultwrap http_proxy_to_wsgi versionnegotiation osprofiler authurl authtoken context apiv1app # heat-api pipeline for standalone heat # ie. uses alternative auth backend that authenticates users against keystone @@ -12,7 +12,7 @@ pipeline = cors request_id faultwrap http_proxy_to_wsgi versionnegotiation ospro # flavor = standalone # [pipeline:heat-api-standalone] -pipeline = cors request_id faultwrap http_proxy_to_wsgi versionnegotiation authurl authpassword context apiv1app +pipeline = healthcheck cors request_id faultwrap http_proxy_to_wsgi versionnegotiation authurl authpassword context apiv1app # heat-api pipeline for custom cloud backends # i.e. in heat.conf: @@ -20,32 +20,32 @@ pipeline = cors request_id faultwrap http_proxy_to_wsgi versionnegotiation authu # flavor = custombackend # [pipeline:heat-api-custombackend] -pipeline = cors request_id faultwrap versionnegotiation context custombackendauth apiv1app +pipeline = healthcheck cors request_id faultwrap versionnegotiation context custombackendauth apiv1app # To enable, in heat.conf: # [paste_deploy] # flavor = noauth # [pipeline:heat-api-noauth] -pipeline = cors request_id faultwrap http_proxy_to_wsgi versionnegotiation noauth context apiv1app +pipeline = healthcheck cors request_id faultwrap http_proxy_to_wsgi versionnegotiation noauth context apiv1app # heat-api-cfn pipeline [pipeline:heat-api-cfn] -pipeline = cors http_proxy_to_wsgi cfnversionnegotiation osprofiler ec2authtoken authtoken context apicfnv1app +pipeline = healthcheck cors http_proxy_to_wsgi cfnversionnegotiation osprofiler ec2authtoken authtoken context apicfnv1app # heat-api-cfn pipeline for standalone heat # relies exclusively on authenticating with ec2 signed requests [pipeline:heat-api-cfn-standalone] -pipeline = cors http_proxy_to_wsgi cfnversionnegotiation ec2authtoken context apicfnv1app +pipeline = healthcheck cors http_proxy_to_wsgi cfnversionnegotiation ec2authtoken context apicfnv1app # heat-api-cloudwatch pipeline [pipeline:heat-api-cloudwatch] -pipeline = cors versionnegotiation osprofiler ec2authtoken authtoken context apicwapp +pipeline = healthcheck cors versionnegotiation osprofiler ec2authtoken authtoken context apicwapp # heat-api-cloudwatch pipeline for standalone heat # relies exclusively on authenticating with ec2 signed requests [pipeline:heat-api-cloudwatch-standalone] -pipeline = cors versionnegotiation ec2authtoken context apicwapp +pipeline = healthcheck cors versionnegotiation ec2authtoken context apicwapp [app:apiv1app] paste.app_factory = heat.common.wsgi:app_factory @@ -115,3 +115,5 @@ paste.filter_factory = oslo_middleware.request_id:RequestId.factory [filter:osprofiler] paste.filter_factory = osprofiler.web:WsgiMiddleware.factory +[filter:healthcheck] +paste.filter_factory = oslo_middleware:Healthcheck.factory diff --git a/tox.ini b/tox.ini index ae4d124..2cb6ca1 100644 --- a/tox.ini +++ b/tox.ini @@ -25,7 +25,7 @@ setenv = VIRTUAL_ENV={envdir} commands = stestr run --slowest {posargs} allowlist_externals = charmcraft - rename.sh + {toxinidir}/rename.sh passenv = HOME TERM diff --git a/unit_tests/test_heat_context.py b/unit_tests/test_heat_context.py index e3b1e3b..b51dd67 100644 --- a/unit_tests/test_heat_context.py +++ b/unit_tests/test_heat_context.py @@ -78,6 +78,38 @@ class TestHeatContext(CharmTestCase): self.test_config.set('max-stacks-per-tenant', '999') self.assertEqual(heat_context.QuotaConfigurationContext()(), expected) + @patch('charmhelpers.contrib.hahelpers.cluster.https') + @patch('heat_context.https') + def test_haproxy_context(self, mock_https, mock_ch_https): + for https_mode in [False, True]: + api_cfn_listen_port = 7990 + api_listen_port = 7994 + if https_mode: + api_cfn_listen_port = 7980 + api_listen_port = 7984 + mock_https.return_value = https_mode + mock_ch_https.return_value = https_mode + haproxy_context = heat_context.HeatHAProxyContext() + healthcheck = [{ + 'option': 'httpchk GET /healthcheck', + 'http-check': 'expect status 200', + }] + backend_options = { + 'heat_api': healthcheck, + 'heat_cfn_api': healthcheck + } + expected = { + 'service_ports': { + 'heat_api': [8004, 7994], + 'heat_cfn_api': [8000, 7990] + }, + 'api_listen_port': api_listen_port, + 'api_cfn_listen_port': api_cfn_listen_port, + 'backend_options': backend_options, + 'https': https_mode, + } + self.assertEqual(expected, haproxy_context()) + class HeatPluginContextTest(CharmTestCase):