Merge "[OVN] Check metadata HA proxy configuration before restart" into stable/2024.1
This commit is contained in:
commit
1563416ce6
@ -35,6 +35,7 @@ from oslo_utils import excutils
|
|||||||
from oslo_utils import fileutils
|
from oslo_utils import fileutils
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
|
from neutron.common import utils
|
||||||
from neutron.conf.agent import common as config
|
from neutron.conf.agent import common as config
|
||||||
from neutron.privileged.agent.linux import utils as priv_utils
|
from neutron.privileged.agent.linux import utils as priv_utils
|
||||||
from neutron import wsgi
|
from neutron import wsgi
|
||||||
@ -400,6 +401,19 @@ def delete_if_exists(path, run_as_root=False):
|
|||||||
fileutils.delete_if_exists(path)
|
fileutils.delete_if_exists(path)
|
||||||
|
|
||||||
|
|
||||||
|
def read_if_exists(path: str, run_as_root=False) -> str:
|
||||||
|
"""Return the content of a text file as a string
|
||||||
|
|
||||||
|
The output includes the empty lines too. If the file does not exist,
|
||||||
|
returns an empty string.
|
||||||
|
It could be called with elevated permissions (root).
|
||||||
|
"""
|
||||||
|
if run_as_root:
|
||||||
|
return priv_utils.read_file(path)
|
||||||
|
else:
|
||||||
|
return utils.read_file(path)
|
||||||
|
|
||||||
|
|
||||||
class UnixDomainHTTPConnection(httplib.HTTPConnection):
|
class UnixDomainHTTPConnection(httplib.HTTPConnection):
|
||||||
"""Connection class for HTTP over UNIX domain socket."""
|
"""Connection class for HTTP over UNIX domain socket."""
|
||||||
def __init__(self, host, port=None, strict=None, timeout=None,
|
def __init__(self, host, port=None, strict=None, timeout=None,
|
||||||
|
@ -46,7 +46,7 @@ listen listener
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class HaproxyConfiguratorBase(object):
|
class HaproxyConfiguratorBase(object, metaclass=abc.ABCMeta):
|
||||||
PROXY_CONFIG_DIR = None
|
PROXY_CONFIG_DIR = None
|
||||||
HEADER_CONFIG_TEMPLATE = None
|
HEADER_CONFIG_TEMPLATE = None
|
||||||
|
|
||||||
@ -76,9 +76,27 @@ class HaproxyConfiguratorBase(object):
|
|||||||
# /var/log/haproxy.log on Debian distros, instead of to syslog.
|
# /var/log/haproxy.log on Debian distros, instead of to syslog.
|
||||||
uuid = network_id or router_id
|
uuid = network_id or router_id
|
||||||
self.log_tag = "haproxy-{}-{}".format(METADATA_SERVICE_NAME, uuid)
|
self.log_tag = "haproxy-{}-{}".format(METADATA_SERVICE_NAME, uuid)
|
||||||
|
self._haproxy_cfg = ''
|
||||||
|
self._resource_id = None
|
||||||
|
self._create_config()
|
||||||
|
|
||||||
def create_config_file(self):
|
@property
|
||||||
"""Create the config file for haproxy."""
|
def haproxy_cfg(self) -> str:
|
||||||
|
return self._haproxy_cfg
|
||||||
|
|
||||||
|
@property
|
||||||
|
def resource_id(self) -> str:
|
||||||
|
return self._resource_id
|
||||||
|
|
||||||
|
def _create_config(self) -> None:
|
||||||
|
"""Create the configuration for haproxy, stored locally
|
||||||
|
|
||||||
|
This method creates a string with the HAProxy configuration, stored in
|
||||||
|
``self._haproxy_cfg``. It also stores the resource ID (network, router)
|
||||||
|
in ``self._resource_id``.
|
||||||
|
|
||||||
|
This method must be called once in the init method.
|
||||||
|
"""
|
||||||
# Need to convert uid/gid into username/group
|
# Need to convert uid/gid into username/group
|
||||||
try:
|
try:
|
||||||
username = pwd.getpwuid(int(self.user)).pw_name
|
username = pwd.getpwuid(int(self.user)).pw_name
|
||||||
@ -127,27 +145,49 @@ class HaproxyConfiguratorBase(object):
|
|||||||
cfg_info['res_type'] = 'Router'
|
cfg_info['res_type'] = 'Router'
|
||||||
cfg_info['res_id'] = self.router_id
|
cfg_info['res_id'] = self.router_id
|
||||||
cfg_info['res_type_del'] = 'Network'
|
cfg_info['res_type_del'] = 'Network'
|
||||||
|
self._resource_id = cfg_info['res_id']
|
||||||
|
self._haproxy_cfg = comm_meta.get_haproxy_config(
|
||||||
|
cfg_info, self.rate_limiting_config,
|
||||||
|
self.HEADER_CONFIG_TEMPLATE, _UNLIMITED_CONFIG_TEMPLATE)
|
||||||
|
|
||||||
haproxy_cfg = comm_meta.get_haproxy_config(cfg_info,
|
def create_config_file(self):
|
||||||
self.rate_limiting_config,
|
"""Read the configuration stored and write the configuration file"""
|
||||||
self.HEADER_CONFIG_TEMPLATE,
|
LOG.debug("haproxy_cfg = %s", self.haproxy_cfg)
|
||||||
_UNLIMITED_CONFIG_TEMPLATE)
|
|
||||||
|
|
||||||
LOG.debug("haproxy_cfg = %s", haproxy_cfg)
|
|
||||||
cfg_dir = self.get_config_path(self.state_path)
|
cfg_dir = self.get_config_path(self.state_path)
|
||||||
# uuid has to be included somewhere in the command line so that it can
|
# uuid has to be included somewhere in the command line so that it can
|
||||||
# be tracked by process_monitor.
|
# be tracked by process_monitor.
|
||||||
self.cfg_path = os.path.join(cfg_dir, "%s.conf" % cfg_info['res_id'])
|
self.cfg_path = os.path.join(cfg_dir, "%s.conf" % self.resource_id)
|
||||||
if not os.path.exists(cfg_dir):
|
if not os.path.exists(cfg_dir):
|
||||||
os.makedirs(cfg_dir)
|
os.makedirs(cfg_dir)
|
||||||
with open(self.cfg_path, "w") as cfg_file:
|
with open(self.cfg_path, "w") as cfg_file:
|
||||||
cfg_file.write(haproxy_cfg)
|
cfg_file.write(self.haproxy_cfg)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_config_path(cls, state_path):
|
def get_config_path(cls, state_path):
|
||||||
return os.path.join(state_path or cfg.CONF.state_path,
|
return os.path.join(state_path or cfg.CONF.state_path,
|
||||||
cls.PROXY_CONFIG_DIR)
|
cls.PROXY_CONFIG_DIR)
|
||||||
|
|
||||||
|
def read_config_file(self) -> str:
|
||||||
|
"""Return a string with the content of the configuration file"""
|
||||||
|
cfg_path = os.path.join(self.get_config_path(self.state_path),
|
||||||
|
'%s.conf' % self.resource_id)
|
||||||
|
return linux_utils.read_if_exists(str(cfg_path), run_as_root=True)
|
||||||
|
|
||||||
|
def is_config_file_obsolete(self) -> bool:
|
||||||
|
"""Compare the instance config and the config file content
|
||||||
|
|
||||||
|
Returns False if both configurations match. This check skips the
|
||||||
|
"pidfile" line because that is provided just before the process is
|
||||||
|
started.
|
||||||
|
"""
|
||||||
|
def trim_config(haproxy_cfg: str) -> list[str]:
|
||||||
|
return [line for line in haproxy_cfg.split('\n')
|
||||||
|
if not line.lstrip().startswith('pidfile')]
|
||||||
|
|
||||||
|
file_config = trim_config(self.read_config_file())
|
||||||
|
current_config = trim_config(self.haproxy_cfg)
|
||||||
|
return file_config != current_config
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def cleanup_config_file(cls, uuid, state_path):
|
def cleanup_config_file(cls, uuid, state_path):
|
||||||
"""Delete config file created when metadata proxy was spawned."""
|
"""Delete config file created when metadata proxy was spawned."""
|
||||||
@ -175,15 +215,15 @@ class MetadataDriverBase(object, metaclass=abc.ABCMeta):
|
|||||||
return user, group
|
return user, group
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_metadata_proxy_callback(cls, bind_address, port, conf,
|
def _get_haproxy_configurator(cls, bind_address, port, conf,
|
||||||
network_id=None, router_id=None,
|
network_id=None, router_id=None,
|
||||||
bind_address_v6=None,
|
bind_address_v6=None,
|
||||||
bind_interface=None):
|
bind_interface=None,
|
||||||
def callback(pid_file):
|
pid_file=''):
|
||||||
metadata_proxy_socket = conf.metadata_proxy_socket
|
metadata_proxy_socket = conf.metadata_proxy_socket
|
||||||
user, group = cls._get_metadata_proxy_user_group(conf)
|
user, group = cls._get_metadata_proxy_user_group(conf)
|
||||||
configurator = cls.haproxy_configurator()
|
configurator = cls.haproxy_configurator()
|
||||||
haproxy = configurator(network_id,
|
return configurator(network_id,
|
||||||
router_id,
|
router_id,
|
||||||
metadata_proxy_socket,
|
metadata_proxy_socket,
|
||||||
bind_address,
|
bind_address,
|
||||||
@ -195,10 +235,18 @@ class MetadataDriverBase(object, metaclass=abc.ABCMeta):
|
|||||||
conf.metadata_rate_limiting,
|
conf.metadata_rate_limiting,
|
||||||
bind_address_v6,
|
bind_address_v6,
|
||||||
bind_interface)
|
bind_interface)
|
||||||
haproxy.create_config_file()
|
|
||||||
proxy_cmd = [HAPROXY_SERVICE, '-f', haproxy.cfg_path]
|
|
||||||
|
|
||||||
return proxy_cmd
|
@classmethod
|
||||||
|
def _get_metadata_proxy_callback(cls, bind_address, port, conf,
|
||||||
|
network_id=None, router_id=None,
|
||||||
|
bind_address_v6=None,
|
||||||
|
bind_interface=None):
|
||||||
|
def callback(pid_file):
|
||||||
|
haproxy = cls._get_haproxy_configurator(
|
||||||
|
bind_address, port, conf, network_id, router_id,
|
||||||
|
bind_address_v6, bind_interface, pid_file)
|
||||||
|
haproxy.create_config_file()
|
||||||
|
return [HAPROXY_SERVICE, '-f', haproxy.cfg_path]
|
||||||
|
|
||||||
return callback
|
return callback
|
||||||
|
|
||||||
@ -238,6 +286,18 @@ class MetadataDriverBase(object, metaclass=abc.ABCMeta):
|
|||||||
# Do not use the address or interface when DAD fails
|
# Do not use the address or interface when DAD fails
|
||||||
bind_address_v6 = bind_interface = None
|
bind_address_v6 = bind_interface = None
|
||||||
|
|
||||||
|
# If the HAProxy running instance configuration is different from
|
||||||
|
# the one passed in this call, the HAProxy is stopped. The new
|
||||||
|
# configuration will be written to the disk and a new instance
|
||||||
|
# started.
|
||||||
|
haproxy_cfg = cls._get_haproxy_configurator(
|
||||||
|
bind_address, port, conf, network_id=network_id,
|
||||||
|
router_id=router_id, bind_address_v6=bind_address_v6,
|
||||||
|
bind_interface=bind_interface)
|
||||||
|
if haproxy_cfg.is_config_file_obsolete():
|
||||||
|
cls.destroy_monitored_metadata_proxy(
|
||||||
|
monitor, haproxy_cfg.resource_id, conf, ns_name)
|
||||||
|
|
||||||
uuid = network_id or router_id
|
uuid = network_id or router_id
|
||||||
callback = cls._get_metadata_proxy_callback(
|
callback = cls._get_metadata_proxy_callback(
|
||||||
bind_address, port, conf,
|
bind_address, port, conf,
|
||||||
|
@ -378,9 +378,6 @@ class MetadataAgent(object):
|
|||||||
resource_type='metadata')
|
resource_type='metadata')
|
||||||
self._sb_idl = None
|
self._sb_idl = None
|
||||||
self._post_fork_event = threading.Event()
|
self._post_fork_event = threading.Event()
|
||||||
# We'll restart all haproxy instances upon start so that they honor
|
|
||||||
# any potential changes in their configuration.
|
|
||||||
self.restarted_metadata_proxy_set = set()
|
|
||||||
self._chassis = None
|
self._chassis = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -834,11 +831,6 @@ class MetadataAgent(object):
|
|||||||
# Ensure the correct checksum in the metadata traffic.
|
# Ensure the correct checksum in the metadata traffic.
|
||||||
self._ensure_datapath_checksum(namespace)
|
self._ensure_datapath_checksum(namespace)
|
||||||
|
|
||||||
if net_name not in self.restarted_metadata_proxy_set:
|
|
||||||
metadata_driver.MetadataDriver.destroy_monitored_metadata_proxy(
|
|
||||||
self._process_monitor, net_name, self.conf, namespace)
|
|
||||||
self.restarted_metadata_proxy_set.add(net_name)
|
|
||||||
|
|
||||||
# Spawn metadata proxy if it's not already running.
|
# Spawn metadata proxy if it's not already running.
|
||||||
metadata_driver.MetadataDriver.spawn_monitored_metadata_proxy(
|
metadata_driver.MetadataDriver.spawn_monitored_metadata_proxy(
|
||||||
self._process_monitor, namespace, n_const.METADATA_PORT,
|
self._process_monitor, namespace, n_const.METADATA_PORT,
|
||||||
|
@ -1103,3 +1103,16 @@ def parse_permitted_ethertypes(permitted_ethertypes):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def read_file(path: str) -> str:
|
||||||
|
"""Return the content of a text file as a string
|
||||||
|
|
||||||
|
The output includes the empty lines too. If the file does not exist,
|
||||||
|
returns an empty string.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open(path) as file:
|
||||||
|
return file.read()
|
||||||
|
except FileNotFoundError:
|
||||||
|
return ''
|
||||||
|
@ -15,12 +15,14 @@
|
|||||||
import os
|
import os
|
||||||
from os import path
|
from os import path
|
||||||
import re
|
import re
|
||||||
|
import typing
|
||||||
|
|
||||||
from eventlet.green import subprocess
|
from eventlet.green import subprocess
|
||||||
from neutron_lib.utils import helpers
|
from neutron_lib.utils import helpers
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_utils import fileutils
|
from oslo_utils import fileutils
|
||||||
|
|
||||||
|
from neutron.common import utils
|
||||||
from neutron import privileged
|
from neutron import privileged
|
||||||
|
|
||||||
|
|
||||||
@ -52,6 +54,20 @@ def delete_if_exists(_path, remove=os.unlink):
|
|||||||
fileutils.delete_if_exists(_path, remove=remove)
|
fileutils.delete_if_exists(_path, remove=remove)
|
||||||
|
|
||||||
|
|
||||||
|
@privileged.default.entrypoint
|
||||||
|
def read_file(_path: str) -> str:
|
||||||
|
return utils.read_file(_path)
|
||||||
|
|
||||||
|
|
||||||
|
@privileged.default.entrypoint
|
||||||
|
def write_to_tempfile(content: bytes,
|
||||||
|
_path: typing.Optional[str] = None,
|
||||||
|
suffix: str = '',
|
||||||
|
prefix: str = 'tmp'):
|
||||||
|
return fileutils.write_to_tempfile(content, path=_path, suffix=suffix,
|
||||||
|
prefix=prefix)
|
||||||
|
|
||||||
|
|
||||||
@privileged.default.entrypoint
|
@privileged.default.entrypoint
|
||||||
def execute_process(cmd, _process_input, addl_env):
|
def execute_process(cmd, _process_input, addl_env):
|
||||||
obj, cmd = _create_process(cmd, addl_env=addl_env)
|
obj, cmd = _create_process(cmd, addl_env=addl_env)
|
||||||
|
@ -15,10 +15,15 @@
|
|||||||
import functools
|
import functools
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
from oslo_utils import fileutils
|
||||||
|
import testscenarios
|
||||||
|
|
||||||
from neutron.agent.common import async_process
|
from neutron.agent.common import async_process
|
||||||
from neutron.agent.linux import utils
|
from neutron.agent.linux import utils
|
||||||
from neutron.common import utils as common_utils
|
from neutron.common import utils as common_utils
|
||||||
|
from neutron.privileged.agent.linux import utils as priv_utils
|
||||||
from neutron.tests.functional.agent.linux import test_async_process
|
from neutron.tests.functional.agent.linux import test_async_process
|
||||||
from neutron.tests.functional import base as functional_base
|
from neutron.tests.functional import base as functional_base
|
||||||
|
|
||||||
@ -172,3 +177,39 @@ class TestFindChildPids(functional_base.BaseSudoTestCase):
|
|||||||
with open('/proc/sys/kernel/pid_max', 'r') as fd:
|
with open('/proc/sys/kernel/pid_max', 'r') as fd:
|
||||||
pid_max = int(fd.readline().strip())
|
pid_max = int(fd.readline().strip())
|
||||||
self.assertEqual([], utils.find_child_pids(pid_max))
|
self.assertEqual([], utils.find_child_pids(pid_max))
|
||||||
|
|
||||||
|
|
||||||
|
class ReadIfExists(testscenarios.WithScenarios,
|
||||||
|
functional_base.BaseSudoTestCase):
|
||||||
|
scenarios = [
|
||||||
|
('root', {'run_as_root': True}),
|
||||||
|
('non-root', {'run_as_root': False})]
|
||||||
|
|
||||||
|
FILE = """Test file
|
||||||
|
line 2
|
||||||
|
|
||||||
|
line 4
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _write_file(cls, path='/tmp', run_as_root=False):
|
||||||
|
content = cls.FILE.encode('ascii')
|
||||||
|
if run_as_root:
|
||||||
|
return priv_utils.write_to_tempfile(content, _path=path)
|
||||||
|
else:
|
||||||
|
return fileutils.write_to_tempfile(content, path=path)
|
||||||
|
|
||||||
|
def test_read_if_exists(self):
|
||||||
|
test_file_path = self._write_file(run_as_root=self.run_as_root)
|
||||||
|
content = utils.read_if_exists(test_file_path,
|
||||||
|
run_as_root=self.run_as_root)
|
||||||
|
file = self.FILE
|
||||||
|
self.assertEqual(file, content)
|
||||||
|
|
||||||
|
def test_read_if_exists_no_file(self):
|
||||||
|
temp_dir = tempfile.TemporaryDirectory()
|
||||||
|
content = utils.read_if_exists(
|
||||||
|
os.path.join(temp_dir.name, 'non-existing-file'),
|
||||||
|
run_as_root=self.run_as_root)
|
||||||
|
self.assertEqual('', content)
|
||||||
|
@ -37,6 +37,7 @@ from neutron.agent.linux import dhcp
|
|||||||
from neutron.agent.linux import interface
|
from neutron.agent.linux import interface
|
||||||
from neutron.agent.linux import utils as linux_utils
|
from neutron.agent.linux import utils as linux_utils
|
||||||
from neutron.agent.metadata import driver as metadata_driver
|
from neutron.agent.metadata import driver as metadata_driver
|
||||||
|
from neutron.agent.metadata import driver_base as metadata_driver_base
|
||||||
from neutron.common import config as common_config
|
from neutron.common import config as common_config
|
||||||
from neutron.common.ovn import constants as ovn_const
|
from neutron.common.ovn import constants as ovn_const
|
||||||
from neutron.common import utils
|
from neutron.common import utils
|
||||||
@ -841,6 +842,9 @@ class TestDhcpAgentEventHandler(base.BaseTestCase):
|
|||||||
mock.call(FAKE_NETWORK_UUID, cfg.CONF,
|
mock.call(FAKE_NETWORK_UUID, cfg.CONF,
|
||||||
ns_name=FAKE_NETWORK_DHCP_NS,
|
ns_name=FAKE_NETWORK_DHCP_NS,
|
||||||
callback=mock.ANY))
|
callback=mock.ANY))
|
||||||
|
mock.patch.object(metadata_driver_base.HaproxyConfiguratorBase,
|
||||||
|
'is_config_file_obsolete',
|
||||||
|
return_value=False).start()
|
||||||
self.plugin.get_network_info.return_value = network
|
self.plugin.get_network_info.return_value = network
|
||||||
process_instance = mock.Mock(active=False)
|
process_instance = mock.Mock(active=False)
|
||||||
with mock.patch.object(metadata_driver.MetadataDriver,
|
with mock.patch.object(metadata_driver.MetadataDriver,
|
||||||
@ -1036,7 +1040,9 @@ class TestDhcpAgentEventHandler(base.BaseTestCase):
|
|||||||
process_instance = mock.Mock(active=False)
|
process_instance = mock.Mock(active=False)
|
||||||
with mock.patch.object(metadata_driver.MetadataDriver,
|
with mock.patch.object(metadata_driver.MetadataDriver,
|
||||||
'_get_metadata_proxy_process_manager',
|
'_get_metadata_proxy_process_manager',
|
||||||
return_value=process_instance) as gmppm:
|
return_value=process_instance) as gmppm,\
|
||||||
|
mock.patch.object(metadata_driver_base.MetadataDriverBase,
|
||||||
|
'_get_haproxy_configurator'):
|
||||||
self.dhcp.enable_isolated_metadata_proxy(fake_network)
|
self.dhcp.enable_isolated_metadata_proxy(fake_network)
|
||||||
gmppm.assert_called_with(FAKE_NETWORK_UUID,
|
gmppm.assert_called_with(FAKE_NETWORK_UUID,
|
||||||
cfg.CONF,
|
cfg.CONF,
|
||||||
@ -1135,7 +1141,9 @@ class TestDhcpAgentEventHandler(base.BaseTestCase):
|
|||||||
network.ports = [dhcp_port_this_host, dhcp_port_other_host]
|
network.ports = [dhcp_port_this_host, dhcp_port_other_host]
|
||||||
self._test_enable_isolated_metadata_proxy_ipv6(network)
|
self._test_enable_isolated_metadata_proxy_ipv6(network)
|
||||||
|
|
||||||
def _test_disable_isolated_metadata_proxy(self, network):
|
@mock.patch.object(metadata_driver_base.HaproxyConfiguratorBase,
|
||||||
|
'is_config_file_obsolete', return_value=False)
|
||||||
|
def _test_disable_isolated_metadata_proxy(self, network, *args):
|
||||||
cfg.CONF.set_override('enable_metadata_network', True)
|
cfg.CONF.set_override('enable_metadata_network', True)
|
||||||
method_path = ('neutron.agent.metadata.driver.MetadataDriver'
|
method_path = ('neutron.agent.metadata.driver.MetadataDriver'
|
||||||
'.destroy_monitored_metadata_proxy')
|
'.destroy_monitored_metadata_proxy')
|
||||||
|
@ -58,6 +58,7 @@ from neutron.agent.linux import pd
|
|||||||
from neutron.agent.linux import ra
|
from neutron.agent.linux import ra
|
||||||
from neutron.agent.linux import utils as linux_utils
|
from neutron.agent.linux import utils as linux_utils
|
||||||
from neutron.agent.metadata import driver as metadata_driver
|
from neutron.agent.metadata import driver as metadata_driver
|
||||||
|
from neutron.agent.metadata import driver_base as metadata_driver_base
|
||||||
from neutron.agent import rpc as agent_rpc
|
from neutron.agent import rpc as agent_rpc
|
||||||
from neutron.conf.agent import common as agent_config
|
from neutron.conf.agent import common as agent_config
|
||||||
from neutron.conf.agent.l3 import config as l3_config
|
from neutron.conf.agent.l3 import config as l3_config
|
||||||
@ -298,7 +299,9 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
|
|||||||
eventlet.sleep(self.conf.ha_vrrp_advert_int + 2)
|
eventlet.sleep(self.conf.ha_vrrp_advert_int + 2)
|
||||||
self.assertFalse(agent._update_metadata_proxy.call_count)
|
self.assertFalse(agent._update_metadata_proxy.call_count)
|
||||||
|
|
||||||
def test_enqueue_state_change_l3_extension(self):
|
@mock.patch.object(metadata_driver_base.MetadataDriverBase,
|
||||||
|
'_get_haproxy_configurator')
|
||||||
|
def test_enqueue_state_change_l3_extension(self, mock_haproxy_conf):
|
||||||
self.conf.set_override('ha_vrrp_advert_int', 1)
|
self.conf.set_override('ha_vrrp_advert_int', 1)
|
||||||
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
||||||
router_dict = {'id': 'router_id', 'enable_ndp_proxy': True}
|
router_dict = {'id': 'router_id', 'enable_ndp_proxy': True}
|
||||||
@ -307,6 +310,9 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
|
|||||||
router_info.router = router_dict
|
router_info.router = router_dict
|
||||||
agent.router_info['router_id'] = router_info
|
agent.router_info['router_id'] = router_info
|
||||||
agent.l3_ext_manager.ha_state_change = mock.Mock()
|
agent.l3_ext_manager.ha_state_change = mock.Mock()
|
||||||
|
haproxy_cfg = mock.Mock()
|
||||||
|
haproxy_cfg.is_config_file_obsolete.return_value = False
|
||||||
|
mock_haproxy_conf.return_value = haproxy_cfg
|
||||||
with mock.patch('neutron.agent.linux.ip_lib.'
|
with mock.patch('neutron.agent.linux.ip_lib.'
|
||||||
'IpAddrCommand.wait_until_address_ready') as mock_wait:
|
'IpAddrCommand.wait_until_address_ready') as mock_wait:
|
||||||
mock_wait.return_value = True
|
mock_wait.return_value = True
|
||||||
|
@ -112,6 +112,9 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
|||||||
meta_conf.register_meta_conf_opts(
|
meta_conf.register_meta_conf_opts(
|
||||||
meta_conf.METADATA_RATE_LIMITING_OPTS, cfg.CONF,
|
meta_conf.METADATA_RATE_LIMITING_OPTS, cfg.CONF,
|
||||||
group=meta_conf.RATE_LIMITING_GROUP)
|
group=meta_conf.RATE_LIMITING_GROUP)
|
||||||
|
self.mock_conf_obsolete = mock.patch.object(
|
||||||
|
driver_base.HaproxyConfiguratorBase,
|
||||||
|
'is_config_file_obsolete').start()
|
||||||
|
|
||||||
def test_after_router_updated_called_on_agent_process_update(self):
|
def test_after_router_updated_called_on_agent_process_update(self):
|
||||||
with mock.patch.object(metadata_driver, 'after_router_updated') as f,\
|
with mock.patch.object(metadata_driver, 'after_router_updated') as f,\
|
||||||
@ -153,7 +156,8 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
|||||||
agent._process_updated_router(router)
|
agent._process_updated_router(router)
|
||||||
f.assert_not_called()
|
f.assert_not_called()
|
||||||
|
|
||||||
def _test_spawn_metadata_proxy(self, dad_failed=False, rate_limited=False):
|
def _test_spawn_metadata_proxy(self, dad_failed=False, rate_limited=False,
|
||||||
|
is_config_file_obsolete=False):
|
||||||
router_id = _uuid()
|
router_id = _uuid()
|
||||||
router_ns = 'qrouter-%s' % router_id
|
router_ns = 'qrouter-%s' % router_id
|
||||||
service_name = 'haproxy'
|
service_name = 'haproxy'
|
||||||
@ -163,6 +167,11 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
|||||||
cfg.CONF.set_override('metadata_proxy_group', self.EGNAME)
|
cfg.CONF.set_override('metadata_proxy_group', self.EGNAME)
|
||||||
cfg.CONF.set_override('metadata_proxy_socket', self.METADATA_SOCKET)
|
cfg.CONF.set_override('metadata_proxy_socket', self.METADATA_SOCKET)
|
||||||
cfg.CONF.set_override('debug', True)
|
cfg.CONF.set_override('debug', True)
|
||||||
|
self.mock_conf_obsolete.return_value = is_config_file_obsolete
|
||||||
|
if is_config_file_obsolete:
|
||||||
|
self.mock_destroy_haproxy = mock.patch.object(
|
||||||
|
driver_base.MetadataDriverBase,
|
||||||
|
'destroy_monitored_metadata_proxy').start()
|
||||||
|
|
||||||
with mock.patch(ip_class_path) as ip_mock,\
|
with mock.patch(ip_class_path) as ip_mock,\
|
||||||
mock.patch(
|
mock.patch(
|
||||||
@ -274,6 +283,10 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
|||||||
self.delete_if_exists.assert_called_once_with(
|
self.delete_if_exists.assert_called_once_with(
|
||||||
mock.ANY, run_as_root=True)
|
mock.ANY, run_as_root=True)
|
||||||
|
|
||||||
|
if is_config_file_obsolete:
|
||||||
|
self.mock_destroy_haproxy.assert_called_once_with(
|
||||||
|
agent.process_monitor, router_id, agent.conf, router_ns)
|
||||||
|
|
||||||
def test_spawn_metadata_proxy(self):
|
def test_spawn_metadata_proxy(self):
|
||||||
self._test_spawn_metadata_proxy()
|
self._test_spawn_metadata_proxy()
|
||||||
|
|
||||||
@ -294,6 +307,9 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
|||||||
def test_spawn_metadata_proxy_dad_failed(self):
|
def test_spawn_metadata_proxy_dad_failed(self):
|
||||||
self._test_spawn_metadata_proxy(dad_failed=True)
|
self._test_spawn_metadata_proxy(dad_failed=True)
|
||||||
|
|
||||||
|
def test_spawn_metadata_proxy_no_matching_configurations(self):
|
||||||
|
self._test_spawn_metadata_proxy(is_config_file_obsolete=True)
|
||||||
|
|
||||||
@mock.patch.object(driver_base.LOG, 'error')
|
@mock.patch.object(driver_base.LOG, 'error')
|
||||||
def test_spawn_metadata_proxy_handles_process_exception(self, error_log):
|
def test_spawn_metadata_proxy_handles_process_exception(self, error_log):
|
||||||
process_instance = mock.Mock(active=False)
|
process_instance = mock.Mock(active=False)
|
||||||
@ -316,29 +332,21 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
|||||||
|
|
||||||
def test_create_config_file_wrong_user(self):
|
def test_create_config_file_wrong_user(self):
|
||||||
with mock.patch('pwd.getpwnam', side_effect=KeyError):
|
with mock.patch('pwd.getpwnam', side_effect=KeyError):
|
||||||
config = metadata_driver.HaproxyConfigurator(_uuid(),
|
|
||||||
mock.ANY, mock.ANY,
|
|
||||||
mock.ANY, mock.ANY,
|
|
||||||
self.EUNAME,
|
|
||||||
self.EGNAME,
|
|
||||||
mock.ANY, mock.ANY,
|
|
||||||
mock.ANY)
|
|
||||||
self.assertRaises(comm_meta.InvalidUserOrGroupException,
|
self.assertRaises(comm_meta.InvalidUserOrGroupException,
|
||||||
config.create_config_file)
|
metadata_driver.HaproxyConfigurator, _uuid(),
|
||||||
|
mock.ANY, mock.ANY, mock.ANY, mock.ANY,
|
||||||
|
self.EUNAME, self.EGNAME, mock.ANY, mock.ANY,
|
||||||
|
mock.ANY)
|
||||||
|
|
||||||
def test_create_config_file_wrong_group(self):
|
def test_create_config_file_wrong_group(self):
|
||||||
with mock.patch('grp.getgrnam', side_effect=KeyError),\
|
with mock.patch('grp.getgrnam', side_effect=KeyError),\
|
||||||
mock.patch('pwd.getpwnam',
|
mock.patch('pwd.getpwnam',
|
||||||
return_value=test_utils.FakeUser(self.EUNAME)):
|
return_value=test_utils.FakeUser(self.EUNAME)):
|
||||||
config = metadata_driver.HaproxyConfigurator(_uuid(),
|
|
||||||
mock.ANY, mock.ANY,
|
|
||||||
mock.ANY, mock.ANY,
|
|
||||||
self.EUNAME,
|
|
||||||
self.EGNAME,
|
|
||||||
mock.ANY, mock.ANY,
|
|
||||||
mock.ANY)
|
|
||||||
self.assertRaises(comm_meta.InvalidUserOrGroupException,
|
self.assertRaises(comm_meta.InvalidUserOrGroupException,
|
||||||
config.create_config_file)
|
metadata_driver.HaproxyConfigurator, _uuid(),
|
||||||
|
mock.ANY, mock.ANY, mock.ANY, mock.ANY,
|
||||||
|
self.EUNAME, self.EGNAME, mock.ANY, mock.ANY,
|
||||||
|
mock.ANY)
|
||||||
|
|
||||||
def test_destroy_monitored_metadata_proxy(self):
|
def test_destroy_monitored_metadata_proxy(self):
|
||||||
mproxy_process = mock.Mock(active=False)
|
mproxy_process = mock.Mock(active=False)
|
||||||
|
@ -446,8 +446,7 @@ class TestMetadataAgent(base.BaseTestCase):
|
|||||||
ip_wrap, 'add_veth',
|
ip_wrap, 'add_veth',
|
||||||
return_value=[ip_lib.IPDevice('ip1'),
|
return_value=[ip_lib.IPDevice('ip1'),
|
||||||
ip_lib.IPDevice('ip2')]) as add_veth,\
|
ip_lib.IPDevice('ip2')]) as add_veth,\
|
||||||
mock.patch.object(
|
mock.patch.object(linux_utils, 'delete_if_exists'), \
|
||||||
linux_utils, 'delete_if_exists') as mock_delete,\
|
|
||||||
mock.patch.object(
|
mock.patch.object(
|
||||||
driver.MetadataDriver,
|
driver.MetadataDriver,
|
||||||
'spawn_monitored_metadata_proxy') as spawn_mdp, \
|
'spawn_monitored_metadata_proxy') as spawn_mdp, \
|
||||||
@ -488,7 +487,6 @@ class TestMetadataAgent(base.BaseTestCase):
|
|||||||
self.assertCountEqual(expected_call,
|
self.assertCountEqual(expected_call,
|
||||||
ip_addr_add_multiple.call_args.args[0])
|
ip_addr_add_multiple.call_args.args[0])
|
||||||
# Check that metadata proxy has been spawned
|
# Check that metadata proxy has been spawned
|
||||||
mock_delete.assert_called_once_with(mock.ANY, run_as_root=True)
|
|
||||||
spawn_mdp.assert_called_once_with(
|
spawn_mdp.assert_called_once_with(
|
||||||
mock.ANY, nemaspace_name, 80, mock.ANY,
|
mock.ANY, nemaspace_name, 80, mock.ANY,
|
||||||
bind_address=n_const.METADATA_V4_IP, network_id=net_name,
|
bind_address=n_const.METADATA_V4_IP, network_id=net_name,
|
||||||
|
@ -105,7 +105,10 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
|||||||
mock.patch(
|
mock.patch(
|
||||||
'neutron.agent.linux.ip_lib.'
|
'neutron.agent.linux.ip_lib.'
|
||||||
'IpAddrCommand.wait_until_address_ready',
|
'IpAddrCommand.wait_until_address_ready',
|
||||||
return_value=True):
|
return_value=True),\
|
||||||
|
mock.patch.object(driver_base.HaproxyConfiguratorBase,
|
||||||
|
'is_config_file_obsolete',
|
||||||
|
return_value=False):
|
||||||
cfg_file = os.path.join(
|
cfg_file = os.path.join(
|
||||||
metadata_driver.HaproxyConfigurator.get_config_path(
|
metadata_driver.HaproxyConfigurator.get_config_path(
|
||||||
agent.conf.state_path),
|
agent.conf.state_path),
|
||||||
@ -184,7 +187,9 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
|||||||
|
|
||||||
with mock.patch.object(metadata_driver.MetadataDriver,
|
with mock.patch.object(metadata_driver.MetadataDriver,
|
||||||
'_get_metadata_proxy_process_manager',
|
'_get_metadata_proxy_process_manager',
|
||||||
return_value=process_instance):
|
return_value=process_instance),\
|
||||||
|
mock.patch.object(driver_base.MetadataDriverBase,
|
||||||
|
'_get_haproxy_configurator'):
|
||||||
process_monitor = mock.Mock()
|
process_monitor = mock.Mock()
|
||||||
network_id = 123456
|
network_id = 123456
|
||||||
|
|
||||||
@ -201,22 +206,18 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
|||||||
|
|
||||||
def test_create_config_file_wrong_user(self):
|
def test_create_config_file_wrong_user(self):
|
||||||
with mock.patch('pwd.getpwnam', side_effect=KeyError):
|
with mock.patch('pwd.getpwnam', side_effect=KeyError):
|
||||||
config = metadata_driver.HaproxyConfigurator(mock.ANY, mock.ANY,
|
|
||||||
mock.ANY, mock.ANY,
|
|
||||||
mock.ANY, self.EUNAME,
|
|
||||||
self.EGNAME, mock.ANY,
|
|
||||||
mock.ANY, mock.ANY)
|
|
||||||
self.assertRaises(comm_meta.InvalidUserOrGroupException,
|
self.assertRaises(comm_meta.InvalidUserOrGroupException,
|
||||||
config.create_config_file)
|
metadata_driver.HaproxyConfigurator, mock.ANY,
|
||||||
|
mock.ANY, mock.ANY, mock.ANY, mock.ANY,
|
||||||
|
self.EUNAME, self.EGNAME, mock.ANY, mock.ANY,
|
||||||
|
mock.ANY)
|
||||||
|
|
||||||
def test_create_config_file_wrong_group(self):
|
def test_create_config_file_wrong_group(self):
|
||||||
with mock.patch('grp.getgrnam', side_effect=KeyError),\
|
with mock.patch('grp.getgrnam', side_effect=KeyError),\
|
||||||
mock.patch('pwd.getpwnam',
|
mock.patch('pwd.getpwnam',
|
||||||
return_value=test_utils.FakeUser(self.EUNAME)):
|
return_value=test_utils.FakeUser(self.EUNAME)):
|
||||||
config = metadata_driver.HaproxyConfigurator(mock.ANY, mock.ANY,
|
|
||||||
mock.ANY, mock.ANY,
|
|
||||||
mock.ANY, self.EUNAME,
|
|
||||||
self.EGNAME, mock.ANY,
|
|
||||||
mock.ANY, mock.ANY)
|
|
||||||
self.assertRaises(comm_meta.InvalidUserOrGroupException,
|
self.assertRaises(comm_meta.InvalidUserOrGroupException,
|
||||||
config.create_config_file)
|
metadata_driver.HaproxyConfigurator, mock.ANY,
|
||||||
|
mock.ANY, mock.ANY, mock.ANY, mock.ANY,
|
||||||
|
self.EUNAME, self.EGNAME, mock.ANY, mock.ANY,
|
||||||
|
mock.ANY)
|
||||||
|
Loading…
Reference in New Issue
Block a user