Add configurable HAProxy timeouts for metadata proxy
The HAProxy metadata proxy uses hardcoded timeout values (30-32s) in the METADATA_HAPROXY_GLOBAL template. When backend services such as dynamic vendordata take longer than these timeouts to respond, HAProxy terminates the connection before the response can be delivered to the instance. Add a new [metadata_haproxy] configuration group with options for timeout_connect, timeout_client, timeout_server, timeout_http_request, and timeout_http_keep_alive. Default values match the previous hardcoded values for backward compatibility. Closes-Bug: #2154609 Generated-By: claude-opus-4-6 (OpenCode) Change-Id: If3c7ee9ea16e13047e5ec8b8463d022abca745b5 Signed-off-by: flux.adam@gmail.com
This commit is contained in:
1 parent
5c095ee6e1
commit
fa2b68d907
13 files changed
+125
-17
No files matched your search
@@ -38,6 +38,9 @@ def register_opts(conf):
|
||||
meta_conf.register_meta_conf_opts(meta_conf.METADATA_RATE_LIMITING_OPTS,
|
||||
cfg=conf,
|
||||
group=meta_conf.RATE_LIMITING_GROUP)
|
||||
meta_conf.register_meta_conf_opts(meta_conf.METADATA_HAPROXY_OPTS,
|
||||
cfg=conf,
|
||||
group=meta_conf.HAPROXY_GROUP)
|
||||
config.register_interface_driver_opts_helper(conf)
|
||||
config.register_agent_state_opts_helper(conf)
|
||||
config.register_interface_opts(conf)
|
||||
|
||||
@@ -52,7 +52,7 @@ class HaproxyConfiguratorBase(metaclass=abc.ABCMeta):
|
||||
|
||||
def __init__(self, network_id, router_id, unix_socket_path, host, port,
|
||||
user, group, state_path, pid_file, rate_limiting_config,
|
||||
host_v6=None, bind_interface=None):
|
||||
haproxy_config, host_v6=None, bind_interface=None):
|
||||
self.network_id = network_id
|
||||
self.router_id = router_id
|
||||
if network_id is None and router_id is None:
|
||||
@@ -68,6 +68,7 @@ class HaproxyConfiguratorBase(metaclass=abc.ABCMeta):
|
||||
self.unix_socket_path = unix_socket_path
|
||||
self.pidfile = pid_file
|
||||
self.rate_limiting_config = rate_limiting_config
|
||||
self.haproxy_config = haproxy_config
|
||||
self.log_level = (
|
||||
'debug' if logging.is_debug_enabled(cfg.CONF) else 'info')
|
||||
# log-tag will cause entries to have the string pre-pended, so use
|
||||
@@ -127,6 +128,10 @@ class HaproxyConfiguratorBase(metaclass=abc.ABCMeta):
|
||||
'log_tag': self.log_tag,
|
||||
'bind_v6_line': '',
|
||||
}
|
||||
for key in ('timeout_connect', 'timeout_client',
|
||||
'timeout_server', 'timeout_http_request',
|
||||
'timeout_http_keep_alive'):
|
||||
cfg_info[key] = self.haproxy_config[key]
|
||||
if self.host_v6 and self.bind_interface:
|
||||
cfg_info['bind_v6_line'] = (
|
||||
'bind {}:{} interface {}'.format(
|
||||
@@ -233,6 +238,7 @@ class MetadataDriverBase(metaclass=abc.ABCMeta):
|
||||
conf.state_path,
|
||||
pid_file,
|
||||
conf.metadata_rate_limiting,
|
||||
conf.metadata_haproxy,
|
||||
bind_address_v6,
|
||||
bind_interface)
|
||||
|
||||
|
||||
@@ -110,6 +110,8 @@ class MetadataExtension(extension_manager.OVNAgentExtension,
|
||||
ovn_meta.register_meta_conf_opts(meta_conf.METADATA_PROXY_HANDLER_OPTS)
|
||||
ovn_meta.register_meta_conf_opts(meta_conf.METADATA_RATE_LIMITING_OPTS,
|
||||
group=meta_conf.RATE_LIMITING_GROUP)
|
||||
ovn_meta.register_meta_conf_opts(meta_conf.METADATA_HAPROXY_OPTS,
|
||||
group=meta_conf.HAPROXY_GROUP)
|
||||
|
||||
def initialize(self, *args):
|
||||
self._register_config_options()
|
||||
|
||||
@@ -34,6 +34,8 @@ def main():
|
||||
ovn_meta.register_meta_conf_opts(meta.METADATA_PROXY_HANDLER_OPTS)
|
||||
ovn_meta.register_meta_conf_opts(meta.METADATA_RATE_LIMITING_OPTS,
|
||||
group=meta.RATE_LIMITING_GROUP)
|
||||
ovn_meta.register_meta_conf_opts(meta.METADATA_HAPROXY_OPTS,
|
||||
group=meta.HAPROXY_GROUP)
|
||||
ovn_meta.register_meta_conf_opts(ovn_meta.OVS_OPTS, group='ovs')
|
||||
config.init(sys.argv[1:])
|
||||
config.setup_logging()
|
||||
|
||||
@@ -64,11 +64,11 @@ defaults
|
||||
option http-server-close
|
||||
option forwardfor
|
||||
retries 3
|
||||
timeout http-request 30s
|
||||
timeout connect 30s
|
||||
timeout client 32s
|
||||
timeout server 32s
|
||||
timeout http-keep-alive 30s
|
||||
timeout http-request %(timeout_http_request)ss
|
||||
timeout connect %(timeout_connect)ss
|
||||
timeout client %(timeout_client)ss
|
||||
timeout server %(timeout_server)ss
|
||||
timeout http-keep-alive %(timeout_http_keep_alive)ss
|
||||
"""
|
||||
|
||||
RATE_LIMITED_CONFIG_TEMPLATE = """
|
||||
|
||||
@@ -133,3 +133,6 @@ def register_agent_dhcp_opts(cfg=cfg.CONF):
|
||||
meta_conf.register_meta_conf_opts(meta_conf.METADATA_RATE_LIMITING_OPTS,
|
||||
cfg=cfg,
|
||||
group=meta_conf.RATE_LIMITING_GROUP)
|
||||
meta_conf.register_meta_conf_opts(meta_conf.METADATA_HAPROXY_OPTS,
|
||||
cfg=cfg,
|
||||
group=meta_conf.HAPROXY_GROUP)
|
||||
@@ -24,6 +24,7 @@ GROUP_MODE = 'group'
|
||||
ALL_MODE = 'all'
|
||||
SOCKET_MODES = (DEDUCE_MODE, USER_MODE, GROUP_MODE, ALL_MODE)
|
||||
RATE_LIMITING_GROUP = 'metadata_rate_limiting'
|
||||
HAPROXY_GROUP = 'metadata_haproxy'
|
||||
|
||||
SHARED_OPTS = [
|
||||
cfg.StrOpt('metadata_proxy_socket',
|
||||
@@ -143,5 +144,36 @@ METADATA_RATE_LIMITING_OPTS = [
|
||||
]
|
||||
|
||||
|
||||
METADATA_HAPROXY_OPTS = [
|
||||
cfg.IntOpt('timeout_connect',
|
||||
default=30,
|
||||
min=1,
|
||||
help=_('Maximum time in seconds to wait for a connection '
|
||||
'to the metadata proxy backend (Unix socket).')),
|
||||
cfg.IntOpt('timeout_client',
|
||||
default=32,
|
||||
min=1,
|
||||
help=_('Maximum inactivity time in seconds on the client '
|
||||
'(instance) side of the metadata proxy.')),
|
||||
cfg.IntOpt('timeout_server',
|
||||
default=32,
|
||||
min=1,
|
||||
help=_('Maximum inactivity time in seconds on the server '
|
||||
'(Nova metadata) side of the metadata proxy. Increase '
|
||||
'this if metadata responses take too long, for example '
|
||||
'when using dynamic vendordata.')),
|
||||
cfg.IntOpt('timeout_http_request',
|
||||
default=30,
|
||||
min=1,
|
||||
help=_('Maximum time in seconds to wait for a complete '
|
||||
'HTTP request from the client.')),
|
||||
cfg.IntOpt('timeout_http_keep_alive',
|
||||
default=30,
|
||||
min=1,
|
||||
help=_('Maximum time in seconds to wait for a new HTTP '
|
||||
'request on a keep-alive connection.')),
|
||||
]
|
||||
|
||||
|
||||
def register_meta_conf_opts(opts, cfg=cfg.CONF, group=None):
|
||||
cfg.register_opts(opts, group=group)
|
||||
@@ -49,7 +49,9 @@ def list_metadata_agent_opts():
|
||||
),
|
||||
('ovs', OVS_OPTS),
|
||||
(meta_conf.RATE_LIMITING_GROUP,
|
||||
meta_conf.METADATA_RATE_LIMITING_OPTS)
|
||||
meta_conf.METADATA_RATE_LIMITING_OPTS),
|
||||
(meta_conf.HAPROXY_GROUP,
|
||||
meta_conf.METADATA_HAPROXY_OPTS),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ def list_ovn_neutron_agent_opts():
|
||||
),
|
||||
(meta_conf.RATE_LIMITING_GROUP, meta_conf.METADATA_RATE_LIMITING_OPTS),
|
||||
('ovn_evpn', evpn_conf.EVPN_OPTS),
|
||||
(meta_conf.HAPROXY_GROUP, meta_conf.METADATA_HAPROXY_OPTS),
|
||||
]
|
||||
|
||||
|
||||
|
||||
+6
-2
@@ -240,7 +240,9 @@ def list_dhcp_agent_opts():
|
||||
neutron.conf.agent.dhcp.DNSMASQ_OPTS)
|
||||
),
|
||||
(meta_conf.RATE_LIMITING_GROUP,
|
||||
meta_conf.METADATA_RATE_LIMITING_OPTS)
|
||||
meta_conf.METADATA_RATE_LIMITING_OPTS),
|
||||
(meta_conf.HAPROXY_GROUP,
|
||||
meta_conf.METADATA_HAPROXY_OPTS),
|
||||
]
|
||||
|
||||
|
||||
@@ -258,7 +260,9 @@ def list_l3_agent_opts():
|
||||
('network_log',
|
||||
neutron.conf.services.logging.log_driver_opts),
|
||||
(meta_conf.RATE_LIMITING_GROUP,
|
||||
meta_conf.METADATA_RATE_LIMITING_OPTS)
|
||||
meta_conf.METADATA_RATE_LIMITING_OPTS),
|
||||
(meta_conf.HAPROXY_GROUP,
|
||||
meta_conf.METADATA_HAPROXY_OPTS),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -112,6 +112,9 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
||||
meta_conf.register_meta_conf_opts(
|
||||
meta_conf.METADATA_RATE_LIMITING_OPTS, cfg.CONF,
|
||||
group=meta_conf.RATE_LIMITING_GROUP)
|
||||
meta_conf.register_meta_conf_opts(
|
||||
meta_conf.METADATA_HAPROXY_OPTS, cfg.CONF,
|
||||
group=meta_conf.HAPROXY_GROUP)
|
||||
self.mock_conf_obsolete = mock.patch.object(
|
||||
driver_base.HaproxyConfiguratorBase,
|
||||
'is_config_file_obsolete').start()
|
||||
@@ -164,7 +167,8 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
||||
f.assert_not_called()
|
||||
|
||||
def _test_spawn_metadata_proxy(self, dad_failed=False, rate_limited=False,
|
||||
is_config_file_obsolete=False):
|
||||
is_config_file_obsolete=False,
|
||||
haproxy_timeouts=None):
|
||||
router_id = _uuid()
|
||||
router_ns = 'qrouter-%s' % router_id
|
||||
service_name = 'haproxy'
|
||||
@@ -249,7 +253,15 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
||||
'pidfile': self.PIDFILE,
|
||||
'log_level': 'debug',
|
||||
'log_tag': log_tag,
|
||||
'bind_v6_line': bind_v6_line}
|
||||
'bind_v6_line': bind_v6_line,
|
||||
'timeout_connect': 30,
|
||||
'timeout_client': 32,
|
||||
'timeout_server': 32,
|
||||
'timeout_http_request': 30,
|
||||
'timeout_http_keep_alive': 30}
|
||||
|
||||
if haproxy_timeouts:
|
||||
expected_params.update(haproxy_timeouts)
|
||||
|
||||
if dad_failed:
|
||||
mock_del.assert_called_once_with(self.METADATA_DEFAULT_IPV6,
|
||||
@@ -318,6 +330,15 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
||||
def test_spawn_metadata_proxy_no_matching_configurations(self):
|
||||
self._test_spawn_metadata_proxy(is_config_file_obsolete=True)
|
||||
|
||||
def test_spawn_metadata_proxy_custom_haproxy_timeouts(self):
|
||||
cfg.CONF.set_override('timeout_server', 120,
|
||||
group=meta_conf.HAPROXY_GROUP)
|
||||
cfg.CONF.set_override('timeout_client', 120,
|
||||
group=meta_conf.HAPROXY_GROUP)
|
||||
self._test_spawn_metadata_proxy(
|
||||
haproxy_timeouts={'timeout_server': 120,
|
||||
'timeout_client': 120})
|
||||
|
||||
@mock.patch.object(driver_base.LOG, 'error')
|
||||
def test_spawn_metadata_proxy_handles_process_exception(self, error_log):
|
||||
process_instance = mock.Mock(active=False)
|
||||
@@ -344,7 +365,7 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
||||
metadata_driver.HaproxyConfigurator, _uuid(),
|
||||
mock.ANY, mock.ANY, mock.ANY, mock.ANY,
|
||||
self.EUNAME, self.EGNAME, mock.ANY, mock.ANY,
|
||||
mock.ANY)
|
||||
mock.ANY, mock.ANY)
|
||||
|
||||
def test_create_config_file_wrong_group(self):
|
||||
with mock.patch('grp.getgrnam', side_effect=KeyError),\
|
||||
@@ -354,7 +375,7 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
||||
metadata_driver.HaproxyConfigurator, _uuid(),
|
||||
mock.ANY, mock.ANY, mock.ANY, mock.ANY,
|
||||
self.EUNAME, self.EGNAME, mock.ANY, mock.ANY,
|
||||
mock.ANY)
|
||||
mock.ANY, mock.ANY)
|
||||
|
||||
def test_destroy_monitored_metadata_proxy(self):
|
||||
mproxy_process = mock.Mock(active=False)
|
||||
|
||||
@@ -61,6 +61,9 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
||||
ovn_meta_conf.register_meta_conf_opts(
|
||||
meta_conf.METADATA_RATE_LIMITING_OPTS, cfg.CONF,
|
||||
group=meta_conf.RATE_LIMITING_GROUP)
|
||||
ovn_meta_conf.register_meta_conf_opts(
|
||||
meta_conf.METADATA_HAPROXY_OPTS, cfg.CONF,
|
||||
group=meta_conf.HAPROXY_GROUP)
|
||||
ovn_conf.register_opts()
|
||||
cfg.CONF.set_override('check_child_processes_interval', 0.1,
|
||||
group='AGENT')
|
||||
@@ -76,7 +79,17 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
||||
|
||||
return self._test_spawn_metadata_proxy(rate_limited=True)
|
||||
|
||||
def _test_spawn_metadata_proxy(self, rate_limited=False):
|
||||
def test_spawn_metadata_proxy_custom_haproxy_timeouts(self):
|
||||
cfg.CONF.set_override('timeout_server', 120,
|
||||
group=meta_conf.HAPROXY_GROUP)
|
||||
cfg.CONF.set_override('timeout_client', 120,
|
||||
group=meta_conf.HAPROXY_GROUP)
|
||||
return self._test_spawn_metadata_proxy(
|
||||
haproxy_timeouts={'timeout_server': 120,
|
||||
'timeout_client': 120})
|
||||
|
||||
def _test_spawn_metadata_proxy(self, rate_limited=False,
|
||||
haproxy_timeouts=None):
|
||||
datapath_id = _uuid()
|
||||
metadata_ns = metadata_agent.NS_PREFIX + datapath_id
|
||||
ip_class_path = 'neutron.agent.linux.ip_lib.IPWrapper'
|
||||
@@ -148,7 +161,15 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
||||
'pidfile': self.PIDFILE,
|
||||
'log_level': 'debug',
|
||||
'log_tag': log_tag,
|
||||
'bind_v6_line': bind_v6_line}
|
||||
'bind_v6_line': bind_v6_line,
|
||||
'timeout_connect': 30,
|
||||
'timeout_client': 32,
|
||||
'timeout_server': 32,
|
||||
'timeout_http_request': 30,
|
||||
'timeout_http_keep_alive': 30}
|
||||
|
||||
if haproxy_timeouts:
|
||||
expected_params.update(haproxy_timeouts)
|
||||
|
||||
if rate_limited:
|
||||
expected_params.update(self.RATE_LIMIT_CONFIG,
|
||||
@@ -211,7 +232,7 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
||||
metadata_driver.HaproxyConfigurator, mock.ANY,
|
||||
mock.ANY, mock.ANY, mock.ANY, mock.ANY,
|
||||
self.EUNAME, self.EGNAME, mock.ANY, mock.ANY,
|
||||
mock.ANY)
|
||||
mock.ANY, mock.ANY)
|
||||
|
||||
def test_create_config_file_wrong_group(self):
|
||||
with mock.patch('grp.getgrnam', side_effect=KeyError),\
|
||||
@@ -221,4 +242,4 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
||||
metadata_driver.HaproxyConfigurator, mock.ANY,
|
||||
mock.ANY, mock.ANY, mock.ANY, mock.ANY,
|
||||
self.EUNAME, self.EGNAME, mock.ANY, mock.ANY,
|
||||
mock.ANY)
|
||||
mock.ANY, mock.ANY)
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Added a new ``[metadata_haproxy]`` configuration group with options to
|
||||
control HAProxy timeout values for the metadata proxy. The following
|
||||
options are now configurable: ``timeout_connect`` (default 30s),
|
||||
``timeout_client`` (default 32s), ``timeout_server`` (default 32s),
|
||||
``timeout_http_request`` (default 30s), and ``timeout_http_keep_alive``
|
||||
(default 30s). Previously these values were hardcoded. Operators with
|
||||
slow metadata backends (e.g. dynamic vendordata) may need to increase
|
||||
``timeout_server`` to prevent premature connection termination.
|
||||
Reference in new issue
Block a user