Merge "Save the HAProxy state outside of its systemd unit" into stable/xena

This commit is contained in:
Zuul 2022-04-25 14:17:41 +00:00 committed by Gerrit Code Review
commit 5b4604c75b
7 changed files with 68 additions and 7 deletions

View File

@ -29,6 +29,7 @@ from werkzeug import exceptions
from octavia.amphorae.backends.agent.api_server import haproxy_compatibility
from octavia.amphorae.backends.agent.api_server import util
from octavia.amphorae.backends.utils import haproxy_query
from octavia.common import constants as consts
from octavia.common import utils as octavia_utils
@ -244,6 +245,17 @@ class Loadbalancer(object):
if action == consts.AMP_ACTION_RELOAD:
if consts.OFFLINE == self._check_haproxy_status(lb_id):
action = consts.AMP_ACTION_START
else:
# We first have to save the state when we reload
haproxy_state_file = util.state_file_path(lb_id)
stat_sock_file = util.haproxy_sock_path(lb_id)
lb_query = haproxy_query.HAProxyQuery(stat_sock_file)
if not lb_query.save_state(haproxy_state_file):
# We accept to reload haproxy even if the state_file is
# not generated, but we probably want to know about that
# failure!
LOG.warning('Failed to save haproxy-%s state!', lb_id)
cmd = ("/usr/sbin/service haproxy-{lb_id} {action}".format(
lb_id=lb_id, action=action))

View File

@ -13,7 +13,6 @@ Environment="CONFIG={{ haproxy_cfg }}" "USERCONFIG={{ haproxy_user_group_cfg }}"
ExecStartPre={{ haproxy_cmd }} -f $CONFIG -f $USERCONFIG -c -q -L {{ peer_name }}
ExecReload=/bin/sh -c "echo 'show servers state' | socat stdio unix-connect:{{ haproxy_socket }} > {{ haproxy_state_file }}"
ExecReload={{ haproxy_cmd }} -c -f $CONFIG -f $USERCONFIG -L {{ peer_name }}
ExecReload=/bin/kill -USR2 $MAINPID

View File

@ -15,10 +15,14 @@
import csv
import socket
from oslo_log import log as logging
from octavia.common import constants as consts
from octavia.common import utils as octavia_utils
from octavia.i18n import _
LOG = logging.getLogger(__name__)
class HAProxyQuery(object):
"""Class used for querying the HAProxy statistics socket.
@ -29,9 +33,9 @@ class HAProxyQuery(object):
"""
def __init__(self, stats_socket):
"""stats_socket
"""Initialize the class
Path to the HAProxy statistics socket file.
:param stats_socket: Path to the HAProxy statistics socket file.
"""
self.socket = stats_socket
@ -134,3 +138,24 @@ class HAProxyQuery(object):
final_results[line['pxname']]['members'][line['svname']] = (
line['status'])
return final_results
def save_state(self, state_file_path):
"""Save haproxy connection state to a file.
:param state_file_path: Absolute path to the state file
:returns: bool (True if success, False otherwise)
"""
try:
result = self._query('show servers state')
# No need for binary mode, the _query converts bytes to ascii.
with open(state_file_path, 'w', encoding='utf-8') as fh:
fh.write(result)
return True
except Exception as e:
# Catch any exception - may be socket issue, or write permission
# issue as well.
LOG.warning("Unable to save state: %(err)s %(output)s",
{'err': e, 'output': e.output})
return False

View File

@ -353,8 +353,10 @@ class TestServerTestCase(base.TestCase):
@mock.patch('octavia.amphorae.backends.agent.api_server.loadbalancer.'
'Loadbalancer._check_haproxy_status')
@mock.patch('subprocess.check_output')
def _test_reload(self, distro, mock_subprocess, mock_haproxy_status,
mock_vrrp, mock_exists, mock_listdir):
@mock.patch('octavia.amphorae.backends.utils.haproxy_query.HAProxyQuery')
def _test_reload(self, distro, mock_haproxy_query, mock_subprocess,
mock_haproxy_status, mock_vrrp, mock_exists,
mock_listdir):
self.assertIn(distro, [consts.UBUNTU, consts.CENTOS])

View File

@ -64,8 +64,9 @@ class ListenerTestCase(base.TestCase):
@mock.patch('octavia.amphorae.backends.agent.api_server.loadbalancer.'
'Loadbalancer._check_lb_exists')
@mock.patch('subprocess.check_output')
def test_start_stop_lb(self, mock_check_output, mock_lb_exists,
mock_path_exists, mock_vrrp_update,
@mock.patch('octavia.amphorae.backends.utils.haproxy_query.HAProxyQuery')
def test_start_stop_lb(self, mock_haproxy_query, mock_check_output,
mock_lb_exists, mock_path_exists, mock_vrrp_update,
mock_check_status):
listener_id = uuidutils.generate_uuid()

View File

@ -143,3 +143,19 @@ class QueryTestCase(base.TestCase):
'description': '', 'Release_date': '2014/07/25'},
self.q.show_info()
)
def test_save_state(self):
filename = 'state_file'
query_mock = mock.Mock()
query_mock.return_value = 'DATA'
self.q._query = query_mock
with mock.patch('builtins.open') as mock_open:
mock_fh = mock.MagicMock()
mock_open().__enter__.return_value = mock_fh
self.q.save_state(filename)
mock_fh.write.assert_called_once_with('DATA')

View File

@ -0,0 +1,6 @@
---
issues:
- |
When using a distribution with a recent SELinux release such as CentOS 8
Stream, PING health-monitor does not work as shell_exec_t calls are denied
by SELinux.