Fix health monitor type "PING" to use icmp ping

Currently with Octavia, if the user specifies a health monitor of type
"PING" we are still using a TCP connect to check for health.
This patch fixes that to actually ping the member to validate health.

Change-Id: I8a67efb7113ffa49b2805b37c3855373b17e5789
Story: 2001280
Task: 5826
This commit is contained in:
Michael Johnson
2017-11-08 15:04:59 -08:00
parent 62c398c5cb
commit 2897b340f7
9 changed files with 226 additions and 2 deletions

View File

@ -0,0 +1,32 @@
#!/bin/bash
# Copyright 2017 Rackspace, US Inc.
# 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.
set -eu
set -o pipefail
ping_cmd=$(command -v ping)
ping6_cmd=$(command -v ping6)
cat > /var/lib/octavia/ping-wrapper.sh <<EOF
#!/bin/bash
if [[ \$HAPROXY_SERVER_ADDR =~ ":" ]]; then
$ping6_cmd -q -n -w 1 -c 1 \$HAPROXY_SERVER_ADDR > /dev/null 2>&1
else
$ping_cmd -q -n -w 1 -c 1 \$HAPROXY_SERVER_ADDR > /dev/null 2>&1
fi
EOF
chmod 755 /var/lib/octavia/ping-wrapper.sh

View File

@ -0,0 +1,54 @@
# Copyright 2017 Rackspace, US Inc.
#
# 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 re
import subprocess
LOG = logging.getLogger(__name__)
def get_haproxy_versions():
"""Get major and minor version number from haproxy
:returns major_version: The major version digit
:returns minor_version: The minor version digit
"""
cmd = "haproxy -v"
version = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
version_re = re.search('.*version (.+?)\.(.+?)\..*',
version.decode('utf-8'))
major_version = int(version_re.group(1))
minor_version = int(version_re.group(2))
return major_version, minor_version
def process_cfg_for_version_compat(haproxy_cfg):
major, minor = get_haproxy_versions()
# Versions less than 1.6 do not support external health checks
# Removed those configuration times
if major < 2 and minor < 6:
LOG.warning("Found %(major)s.%(minor)s version of haproxy. "
"Disabling external checks. Health monitor of type "
"PING will revert to TCP.",
{'major': major, 'minor': minor})
haproxy_cfg = re.sub(r" * ?.*external-check ?.*\s", "", haproxy_cfg)
return haproxy_cfg

View File

@ -28,6 +28,7 @@ import six
import webob
from werkzeug import exceptions
from octavia.amphorae.backends.agent.api_server import haproxy_compatibility
from octavia.amphorae.backends.agent.api_server import osutils
from octavia.amphorae.backends.agent.api_server import util
from octavia.amphorae.backends.utils import haproxy_query as query
@ -124,6 +125,10 @@ class Listener(object):
# result an error when haproxy starts.
new_config = re.sub(r"\s+group\s.+", "", s_io.getvalue())
# Handle any haproxy version compatibility issues
new_config = haproxy_compatibility.process_cfg_for_version_compat(
new_config)
with os.fdopen(os.open(name, flags, mode), 'w') as file:
file.write(new_config)

View File

@ -24,6 +24,7 @@ global
{% if loadbalancer.global_connection_limit is defined %}
maxconn {{ loadbalancer.global_connection_limit }}
{% endif %}
external-check
defaults
log global

View File

@ -227,6 +227,10 @@ backend {{ pool.id }}
{% if pool.health_monitor.type == constants.HEALTH_MONITOR_TLS_HELLO %}
option ssl-hello-chk
{% endif %}
{% if pool.health_monitor.type == constants.HEALTH_MONITOR_PING %}
option external-check
external-check command /var/lib/octavia/ping-wrapper.sh
{% endif %}
{% endif %}
{% if pool.protocol.lower() == constants.PROTOCOL_HTTP.lower() %}
{% if listener.insert_headers.get('X-Forwarded-For',

View File

@ -82,16 +82,20 @@ class TestServerTestCase(base.TestCase):
self._test_haproxy(consts.INIT_UPSTART, consts.UBUNTU,
mock_init_system)
@mock.patch('octavia.amphorae.backends.agent.api_server.'
'haproxy_compatibility.get_haproxy_versions')
@mock.patch('os.path.exists')
@mock.patch('os.makedirs')
@mock.patch('os.rename')
@mock.patch('subprocess.check_output')
def _test_haproxy(self, init_system, distro, mock_init_system,
mock_subprocess, mock_rename,
mock_makedirs, mock_exists):
mock_makedirs, mock_exists, mock_get_version):
self.assertIn(distro, [consts.UBUNTU, consts.CENTOS])
mock_get_version.return_value = [1, 6]
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
mock_exists.return_value = True
file_name = '/var/lib/octavia/123/haproxy.cfg.new'

View File

@ -0,0 +1,106 @@
# Copyright 2017 Rackspace, US Inc.
#
# 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 octavia.amphorae.backends.agent.api_server import haproxy_compatibility
import octavia.tests.unit.base as base
from octavia.tests.unit.common.sample_configs import sample_configs
class HAProxyCompatTestCase(base.TestCase):
def setUp(self):
super(HAProxyCompatTestCase, self).setUp()
self.old_haproxy_global = (
"# Configuration for test-lb\n"
"global\n"
" daemon\n"
" user nobody\n"
" group nogroup\n"
" log /dev/log local0\n"
" log /dev/log local1 notice\n"
" stats socket /var/lib/octavia/sample_listener_id_1.sock"
" mode 0666 level user\n"
" maxconn 98\n\n"
"defaults\n"
" log global\n"
" retries 3\n"
" option redispatch\n"
" timeout connect 5000\n"
" timeout client 50000\n"
" timeout server 50000\n\n\n\n"
"frontend sample_listener_id_1\n"
" option httplog\n"
" maxconn 98\n"
" bind 10.0.0.2:80\n"
" mode http\n"
" default_backend sample_pool_id_1\n\n")
self.backend_without_external = (
"backend sample_pool_id_1\n"
" mode http\n"
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31\n"
" option httpchk GET /index.html\n"
" http-check expect rstatus 418\n"
" fullconn 98\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")
self.backend_with_external = (
"backend sample_pool_id_1\n"
" mode http\n"
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31\n"
" option httpchk GET /index.html\n"
" http-check expect rstatus 418\n"
" fullconn 98\n"
" option external-check\n"
" external-check command /var/lib/octavia/ping-wrapper.sh\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")
@mock.patch('subprocess.check_output')
def test_get_haproxy_versions(self, mock_process):
mock_process.return_value = (
b"THIS-App version 1.6.3 2099/10/12\n"
b"Some other data here <test@example.com>\n")
major, minor = haproxy_compatibility.get_haproxy_versions()
self.assertEqual(1, major)
self.assertEqual(6, minor)
@mock.patch('octavia.amphorae.backends.agent.api_server.'
'haproxy_compatibility.get_haproxy_versions')
def test_process_cfg_for_version_compat(self, mock_get_version):
# Test 1.6 version path, no change to config expected
mock_get_version.return_value = [1, 6]
test_config = sample_configs.sample_base_expected_config(
backend=self.backend_with_external)
result_config = haproxy_compatibility.process_cfg_for_version_compat(
test_config)
self.assertEqual(test_config, result_config)
# Test 1.5 version path, external-check should be removed
mock_get_version.return_value = [1, 5]
test_config = sample_configs.sample_base_expected_config(
backend=self.backend_with_external)
result_config = haproxy_compatibility.process_cfg_for_version_compat(
test_config)
expected_config = (self.old_haproxy_global +
self.backend_without_external)
self.assertEqual(expected_config, result_config)

View File

@ -727,7 +727,8 @@ def sample_base_expected_config(frontend=None, backend=None, peers=None):
" log /dev/log local1 notice\n"
" stats socket /var/lib/octavia/sample_listener_id_1.sock"
" mode 0666 level user\n"
" maxconn 98\n\n"
" maxconn 98\n"
" external-check\n\n"
"defaults\n"
" log global\n"
" retries 3\n"

View File

@ -0,0 +1,17 @@
---
issues:
- |
Amphora images with HAProxy older than 1.6 (CentOS 7, etc.) will still
use health monitor type TCP when PING is selected by the user.
upgrade:
- |
Amphora will need to be updated to a new image with this version of the
agent and ping-wrapper.sh script prior to updating the Octavia controllers.
If a load balancer is using a health monitor of type PING with an
amphora image that has not been updated, the next configuration change to
the load balancer will cause it to go into an ERROR state until it is
failed over to an updated image.
fixes:
- |
Fixed an issue where health monitors of type PING were really doing a
TCP health check.