Fix PING health-monitor with recent haproxy releases

haproxy 2.2.x requires an "insecure-fork-wanted" global option when
using external-check.

This commit also fixes a function that builds the list of the features
available in haproxy based on its version.

Story 2009953
Task 44900

Change-Id: I35c5976c6bdb8828e54bcde00052622a7b6bc96a
This commit is contained in:
Gregory Thiemonge 2022-04-01 06:56:54 +02:00
parent 309c7e61fe
commit 1ac7818128
5 changed files with 156 additions and 3 deletions

View File

@ -796,6 +796,7 @@ AMP_NETNS_SVC_PREFIX = 'amphora-netns'
# Amphora Feature Compatibility # Amphora Feature Compatibility
HTTP_REUSE = 'has_http_reuse' HTTP_REUSE = 'has_http_reuse'
POOL_ALPN = 'has_pool_alpn' POOL_ALPN = 'has_pool_alpn'
INSECURE_FORK = 'requires_insecure_fork'
# TODO(johnsom) convert these to octavia_lib constants # TODO(johnsom) convert these to octavia_lib constants
# once octavia is transitioned to use octavia_lib # once octavia is transitioned to use octavia_lib

View File

@ -17,6 +17,7 @@ import re
import jinja2 import jinja2
from octavia_lib.common import constants as lib_consts from octavia_lib.common import constants as lib_consts
from oslo_utils import versionutils
from octavia.common.config import cfg from octavia.common.config import cfg
from octavia.common import constants from octavia.common import constants
@ -98,13 +99,17 @@ class JinjaTemplater(object):
# pair might be running an older amphora version. # pair might be running an older amphora version.
feature_compatibility = {} feature_compatibility = {}
version = ".".join(haproxy_versions)
# Is it newer than haproxy 1.5? # Is it newer than haproxy 1.5?
if not (int(haproxy_versions[0]) < 2 and int(haproxy_versions[1]) < 6): if versionutils.is_compatible("1.6.0", version, same_major=False):
feature_compatibility[constants.HTTP_REUSE] = True feature_compatibility[constants.HTTP_REUSE] = True
if not (int(haproxy_versions[0]) < 2 and int(haproxy_versions[1]) < 9): if versionutils.is_compatible("1.9.0", version, same_major=False):
feature_compatibility[constants.POOL_ALPN] = True feature_compatibility[constants.POOL_ALPN] = True
if int(haproxy_versions[0]) >= 2: if int(haproxy_versions[0]) >= 2:
feature_compatibility[lib_consts.PROTOCOL_PROMETHEUS] = True feature_compatibility[lib_consts.PROTOCOL_PROMETHEUS] = True
# haproxy 2.2 requires insecure-fork-wanted for PING healthchecks
if versionutils.is_compatible("2.2.0", version, same_major=False):
feature_compatibility[constants.INSECURE_FORK] = True
return self.render_loadbalancer_obj( return self.render_loadbalancer_obj(
host_amphora, listeners, tls_certs=tls_certs, host_amphora, listeners, tls_certs=tls_certs,
@ -173,6 +178,8 @@ class JinjaTemplater(object):
if listener.protocol == lib_consts.PROTOCOL_PROMETHEUS: if listener.protocol == lib_consts.PROTOCOL_PROMETHEUS:
prometheus_listener = True prometheus_listener = True
break break
require_insecure_fork = feature_compatibility.get(
constants.INSECURE_FORK)
enable_prometheus = prometheus_listener and feature_compatibility.get( enable_prometheus = prometheus_listener and feature_compatibility.get(
lib_consts.PROTOCOL_PROMETHEUS, False) lib_consts.PROTOCOL_PROMETHEUS, False)
return self._get_template().render( return self._get_template().render(
@ -185,7 +192,8 @@ class JinjaTemplater(object):
CONF.amphora_agent.administrative_log_facility, CONF.amphora_agent.administrative_log_facility,
'user_log_facility': CONF.amphora_agent.user_log_facility, 'user_log_facility': CONF.amphora_agent.user_log_facility,
'connection_logging': self.connection_logging, 'connection_logging': self.connection_logging,
'enable_prometheus': enable_prometheus}, 'enable_prometheus': enable_prometheus,
'require_insecure_fork': require_insecure_fork},
constants=constants, lib_consts=lib_consts) constants=constants, lib_consts=lib_consts)
def _transform_loadbalancer(self, host_amphora, loadbalancer, listeners, def _transform_loadbalancer(self, host_amphora, loadbalancer, listeners,

View File

@ -32,6 +32,9 @@ global
found_ns.found == false %} found_ns.found == false %}
{% set found_ns.found = true %} {% set found_ns.found = true %}
external-check external-check
{% if require_insecure_fork %}
insecure-fork-wanted
{% endif %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}

View File

@ -15,6 +15,7 @@
import copy import copy
import os import os
from unittest import mock
from octavia_lib.common import constants as lib_consts from octavia_lib.common import constants as lib_consts
from oslo_config import cfg from oslo_config import cfg
@ -771,6 +772,37 @@ class TestHaproxyCfg(base.TestCase):
self.assertEqual(sample_configs_combined.sample_base_expected_config( self.assertEqual(sample_configs_combined.sample_base_expected_config(
backend=be, global_opts=go), rendered_obj) backend=be, global_opts=go), rendered_obj)
def test_render_template_ping_monitor_http_insecure_fork(self):
be = ("backend sample_pool_id_1:sample_listener_id_1\n"
" mode http\n"
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" load-server-state-from-file global\n"
" timeout check 31s\n"
" option external-check\n"
" external-check command /var/lib/octavia/ping-wrapper.sh\n"
" fullconn {maxconn}\n"
" option allbackups\n"
" timeout connect 5000\n"
" timeout server 50000\n"
" server sample_member_id_1 10.0.0.99:82 "
"weight 13 check inter 30s fall 3 rise 2 "
"cookie sample_member_id_1\n"
" server sample_member_id_2 10.0.0.98:82 "
"weight 13 check inter 30s fall 3 rise 2 "
"cookie sample_member_id_2\n\n").format(
maxconn=constants.HAPROXY_DEFAULT_MAXCONN)
go = (f" maxconn {constants.HAPROXY_DEFAULT_MAXCONN}\n"
" external-check\n insecure-fork-wanted\n\n")
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs_combined.sample_amphora_tuple(),
[sample_configs_combined.sample_listener_tuple(
proto='HTTP', monitor_proto='PING')],
feature_compatibility={
"requires_insecure_fork": True})
self.assertEqual(sample_configs_combined.sample_base_expected_config(
backend=be, global_opts=go), rendered_obj)
def test_render_template_no_monitor_https(self): def test_render_template_no_monitor_https(self):
fe = ("frontend sample_listener_id_1\n" fe = ("frontend sample_listener_id_1\n"
" maxconn {maxconn}\n" " maxconn {maxconn}\n"
@ -1758,3 +1790,106 @@ class TestHaproxyCfg(base.TestCase):
sample_configs_combined.sample_base_expected_config( sample_configs_combined.sample_base_expected_config(
frontend=fe, backend=be), frontend=fe, backend=be),
rendered_obj) rendered_obj)
@mock.patch("octavia.common.jinja.haproxy.combined_listeners.jinja_cfg."
"JinjaTemplater.render_loadbalancer_obj")
def test_build_config(self, mock_render_loadbalancer_obj):
mock_amp = mock.Mock()
mock_listeners = mock.Mock()
mock_tls_certs = mock.Mock()
mock_socket_path = mock.Mock()
j_cfg = jinja_cfg.JinjaTemplater()
j_cfg.build_config(mock_amp, mock_listeners, mock_tls_certs,
haproxy_versions=("0", "7", "0"),
socket_path=mock_socket_path)
expected_fc = {}
mock_render_loadbalancer_obj.assert_called_once_with(
mock_amp, mock_listeners, tls_certs=mock_tls_certs,
socket_path=mock_socket_path,
feature_compatibility=expected_fc)
mock_render_loadbalancer_obj.reset_mock()
j_cfg.build_config(mock_amp, mock_listeners, mock_tls_certs,
haproxy_versions=("1", "6", "0"),
socket_path=mock_socket_path)
expected_fc = {
constants.HTTP_REUSE: True
}
mock_render_loadbalancer_obj.assert_called_once_with(
mock_amp, mock_listeners, tls_certs=mock_tls_certs,
socket_path=mock_socket_path,
feature_compatibility=expected_fc)
mock_render_loadbalancer_obj.reset_mock()
j_cfg.build_config(mock_amp, mock_listeners, mock_tls_certs,
haproxy_versions=("1", "9", "0"),
socket_path=mock_socket_path)
expected_fc = {
constants.HTTP_REUSE: True,
constants.POOL_ALPN: True
}
mock_render_loadbalancer_obj.assert_called_once_with(
mock_amp, mock_listeners, tls_certs=mock_tls_certs,
socket_path=mock_socket_path,
feature_compatibility=expected_fc)
mock_render_loadbalancer_obj.reset_mock()
j_cfg.build_config(mock_amp, mock_listeners, mock_tls_certs,
haproxy_versions=("2", "1", "1"),
socket_path=mock_socket_path)
expected_fc = {
constants.HTTP_REUSE: True,
constants.POOL_ALPN: True,
lib_consts.PROTOCOL_PROMETHEUS: True
}
mock_render_loadbalancer_obj.assert_called_once_with(
mock_amp, mock_listeners, tls_certs=mock_tls_certs,
socket_path=mock_socket_path,
feature_compatibility=expected_fc)
mock_render_loadbalancer_obj.reset_mock()
j_cfg.build_config(mock_amp, mock_listeners, mock_tls_certs,
haproxy_versions=("2", "2", "1"),
socket_path=mock_socket_path)
expected_fc = {
constants.HTTP_REUSE: True,
constants.POOL_ALPN: True,
lib_consts.PROTOCOL_PROMETHEUS: True,
constants.INSECURE_FORK: True
}
mock_render_loadbalancer_obj.assert_called_once_with(
mock_amp, mock_listeners, tls_certs=mock_tls_certs,
socket_path=mock_socket_path,
feature_compatibility=expected_fc)
mock_render_loadbalancer_obj.reset_mock()
j_cfg.build_config(mock_amp, mock_listeners, mock_tls_certs,
haproxy_versions=("2", "4", "0"),
socket_path=mock_socket_path)
mock_render_loadbalancer_obj.assert_called_once_with(
mock_amp, mock_listeners, tls_certs=mock_tls_certs,
socket_path=mock_socket_path,
feature_compatibility=expected_fc)
mock_render_loadbalancer_obj.reset_mock()
j_cfg.build_config(mock_amp, mock_listeners, mock_tls_certs,
haproxy_versions=("3", "1", "0"),
socket_path=mock_socket_path)
mock_render_loadbalancer_obj.assert_called_once_with(
mock_amp, mock_listeners, tls_certs=mock_tls_certs,
socket_path=mock_socket_path,
feature_compatibility=expected_fc)

View File

@ -0,0 +1,6 @@
---
fixes:
- |
Fix PING health-monitors with recent haproxy releases (>=2.2), haproxy now
requires an additional "insecure-fork-wanted" option to authorize the
Octavia PING healthcheck.