Fixup conntrackd support
This contains several fixups for which did not make it into the original change before it got merged: * Removal of the GPL licensed shell script. This is replaced by Python code in neutron-keepalived-state-change. This code was rewritten from scratch and is Apache licensed. * Addition of the conntrackd package to bindep.txt for tests. This properly documents this new test requirement. * Several typo and other small fixes suggested by Ihar Hrachyshka. * Common pgrep utility function as suggested by lajoskatona. Closes-Bug: #2092907 Change-Id: Ica1c38b3a635d60a08f48272fcda2034b0323d10
This commit is contained in:
parent
90e29e852e
commit
9a0814c760
@ -21,5 +21,7 @@ mysql-server [platform:dpkg !platform:debian test]
|
|||||||
|
|
||||||
haproxy
|
haproxy
|
||||||
keepalived
|
keepalived
|
||||||
|
conntrackd [platform:dpkg test conntrackd]
|
||||||
|
conntrack-tools [platform:rpm test conntrackd]
|
||||||
|
|
||||||
iproute-tc [platform:rpm]
|
iproute-tc [platform:rpm]
|
||||||
|
@ -207,9 +207,6 @@ class HaRouter(router.RouterInfo):
|
|||||||
interface_name = self.get_ha_device_name()
|
interface_name = self.get_ha_device_name()
|
||||||
subnets = self.ha_port.get('subnets', [])
|
subnets = self.ha_port.get('subnets', [])
|
||||||
ha_port_cidrs = [subnet['cidr'] for subnet in subnets]
|
ha_port_cidrs = [subnet['cidr'] for subnet in subnets]
|
||||||
notify_script = (self.agent_conf.ha_conntrackd_enabled and
|
|
||||||
self.conntrackd_manager.get_ha_script_path() or
|
|
||||||
None)
|
|
||||||
instance = keepalived.KeepalivedInstance(
|
instance = keepalived.KeepalivedInstance(
|
||||||
'BACKUP',
|
'BACKUP',
|
||||||
interface_name,
|
interface_name,
|
||||||
@ -221,7 +218,6 @@ class HaRouter(router.RouterInfo):
|
|||||||
vrrp_health_check_interval=(
|
vrrp_health_check_interval=(
|
||||||
self.agent_conf.ha_vrrp_health_check_interval),
|
self.agent_conf.ha_vrrp_health_check_interval),
|
||||||
ha_conf_dir=self.keepalived_manager.get_conf_dir(),
|
ha_conf_dir=self.keepalived_manager.get_conf_dir(),
|
||||||
notify_script=notify_script,
|
|
||||||
)
|
)
|
||||||
instance.track_interfaces.append(interface_name)
|
instance.track_interfaces.append(interface_name)
|
||||||
|
|
||||||
@ -475,6 +471,13 @@ class HaRouter(router.RouterInfo):
|
|||||||
'--state_path=%s' % self.agent_conf.state_path,
|
'--state_path=%s' % self.agent_conf.state_path,
|
||||||
'--user=%s' % os.geteuid(),
|
'--user=%s' % os.geteuid(),
|
||||||
'--group=%s' % os.getegid()]
|
'--group=%s' % os.getegid()]
|
||||||
|
|
||||||
|
if self.agent_conf.ha_conntrackd_enabled:
|
||||||
|
cmd.append('--enable_conntrackd')
|
||||||
|
|
||||||
|
if self.agent_conf.debug:
|
||||||
|
cmd.append('--debug')
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
return callback
|
return callback
|
||||||
|
@ -29,6 +29,7 @@ from neutron.agent.linux import utils as agent_utils
|
|||||||
from neutron.common import config
|
from neutron.common import config
|
||||||
from neutron.common import utils as common_utils
|
from neutron.common import utils as common_utils
|
||||||
from neutron.conf.agent import common as agent_config
|
from neutron.conf.agent import common as agent_config
|
||||||
|
from neutron.conf.agent.l3 import ha as ha_conf
|
||||||
from neutron.conf.agent.l3 import keepalived
|
from neutron.conf.agent.l3 import keepalived
|
||||||
from neutron import privileged
|
from neutron import privileged
|
||||||
|
|
||||||
@ -49,12 +50,13 @@ class KeepalivedUnixDomainConnection(agent_utils.UnixDomainHTTPConnection):
|
|||||||
|
|
||||||
class MonitorDaemon(daemon.Daemon):
|
class MonitorDaemon(daemon.Daemon):
|
||||||
def __init__(self, pidfile, router_id, user, group, namespace, conf_dir,
|
def __init__(self, pidfile, router_id, user, group, namespace, conf_dir,
|
||||||
interface, cidr):
|
interface, cidr, ha_conntrackd_enabled):
|
||||||
self.router_id = router_id
|
self.router_id = router_id
|
||||||
self.namespace = namespace
|
self.namespace = namespace
|
||||||
self.conf_dir = conf_dir
|
self.conf_dir = conf_dir
|
||||||
self.interface = interface
|
self.interface = interface
|
||||||
self.cidr = cidr
|
self.cidr = cidr
|
||||||
|
self.ha_conntrackd_enabled = ha_conntrackd_enabled
|
||||||
self.monitor = None
|
self.monitor = None
|
||||||
self.event_stop = threading.Event()
|
self.event_stop = threading.Event()
|
||||||
self.event_started = threading.Event()
|
self.event_started = threading.Event()
|
||||||
@ -85,8 +87,6 @@ class MonitorDaemon(daemon.Daemon):
|
|||||||
target=self.read_queue,
|
target=self.read_queue,
|
||||||
args=(self.queue, self.event_stop, self.event_started))
|
args=(self.queue, self.event_stop, self.event_started))
|
||||||
self._thread_initial_state.start()
|
self._thread_initial_state.start()
|
||||||
self._thread_ip_monitor.start()
|
|
||||||
self._thread_read_queue.start()
|
|
||||||
|
|
||||||
# NOTE(ralonsoh): if the initial status is not read in a defined
|
# NOTE(ralonsoh): if the initial status is not read in a defined
|
||||||
# timeout, "backup" state is set.
|
# timeout, "backup" state is set.
|
||||||
@ -95,8 +95,14 @@ class MonitorDaemon(daemon.Daemon):
|
|||||||
LOG.warning('Timeout reading the initial status of router %s, '
|
LOG.warning('Timeout reading the initial status of router %s, '
|
||||||
'state is set to "backup".', self.router_id)
|
'state is set to "backup".', self.router_id)
|
||||||
self.write_state_change('backup')
|
self.write_state_change('backup')
|
||||||
|
if self.ha_conntrackd_enabled:
|
||||||
|
self.sync_conntrack('backup')
|
||||||
self.notify_agent('backup')
|
self.notify_agent('backup')
|
||||||
|
|
||||||
|
# NOTE(gaudenz): Only starte these threads after the initial status is
|
||||||
|
# set because otherwise the initial status thread sometimes hangs.
|
||||||
|
self._thread_ip_monitor.start()
|
||||||
|
self._thread_read_queue.start()
|
||||||
self._thread_read_queue.join()
|
self._thread_read_queue.join()
|
||||||
|
|
||||||
def read_queue(self, _queue, event_stop, event_started):
|
def read_queue(self, _queue, event_stop, event_started):
|
||||||
@ -115,6 +121,8 @@ class MonitorDaemon(daemon.Daemon):
|
|||||||
else:
|
else:
|
||||||
new_state = 'backup'
|
new_state = 'backup'
|
||||||
self.write_state_change(new_state)
|
self.write_state_change(new_state)
|
||||||
|
if self.ha_conntrackd_enabled:
|
||||||
|
self.sync_conntrack(new_state)
|
||||||
self.notify_agent(new_state)
|
self.notify_agent(new_state)
|
||||||
|
|
||||||
def handle_initial_state(self):
|
def handle_initial_state(self):
|
||||||
@ -132,6 +140,8 @@ class MonitorDaemon(daemon.Daemon):
|
|||||||
|
|
||||||
if not self.initial_state:
|
if not self.initial_state:
|
||||||
self.write_state_change(state)
|
self.write_state_change(state)
|
||||||
|
if self.ha_conntrackd_enabled:
|
||||||
|
self.sync_conntrack(state)
|
||||||
self.notify_agent(state)
|
self.notify_agent(state)
|
||||||
except Exception:
|
except Exception:
|
||||||
if not self.initial_state:
|
if not self.initial_state:
|
||||||
@ -160,6 +170,53 @@ class MonitorDaemon(daemon.Daemon):
|
|||||||
|
|
||||||
LOG.debug('Notified agent router %s, state %s', self.router_id, state)
|
LOG.debug('Notified agent router %s, state %s', self.router_id, state)
|
||||||
|
|
||||||
|
def conntrackd(self, option):
|
||||||
|
execute = ip_lib.IPWrapper(namespace=self.namespace).netns.execute
|
||||||
|
cmd = ['conntrackd', '-C', f'{self.conf_dir}/conntrackd.conf', option]
|
||||||
|
|
||||||
|
LOG.debug('Executing "%s" on router %s', ' '.join(cmd), self.router_id)
|
||||||
|
execute(
|
||||||
|
cmd,
|
||||||
|
run_as_root=True,
|
||||||
|
# Errors are still logged, but should not crash the daemon
|
||||||
|
check_exit_code=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def sync_conntrack(self, state):
|
||||||
|
if state == 'primary':
|
||||||
|
self.sync_conntrack_primary()
|
||||||
|
elif state == 'backup':
|
||||||
|
self.sync_conntrack_backup()
|
||||||
|
else:
|
||||||
|
LOG.error(f'Unknown state "{state}".')
|
||||||
|
|
||||||
|
def sync_conntrack_primary(self):
|
||||||
|
|
||||||
|
# commit the external cache into the kernel table
|
||||||
|
self.conntrackd('-c')
|
||||||
|
|
||||||
|
# flush the internal and the external caches
|
||||||
|
self.conntrackd('-f')
|
||||||
|
|
||||||
|
# resynchronize my internal cache to the kernel table
|
||||||
|
self.conntrackd('-R')
|
||||||
|
|
||||||
|
# send a bulk update to backups
|
||||||
|
self.conntrackd('-B')
|
||||||
|
|
||||||
|
LOG.debug('Synced connection tracking state on primary for router %s',
|
||||||
|
self.router_id)
|
||||||
|
|
||||||
|
def sync_conntrack_backup(self):
|
||||||
|
# shorten kernel conntrack timers to remove the zombie entries.
|
||||||
|
self.conntrackd('-t')
|
||||||
|
|
||||||
|
# request resynchronization with primary firewall replica (if any)
|
||||||
|
self.conntrackd('-n')
|
||||||
|
|
||||||
|
LOG.debug('Synced connection tracking state on backup for router %s',
|
||||||
|
self.router_id)
|
||||||
|
|
||||||
def handle_sigterm(self, signum, frame):
|
def handle_sigterm(self, signum, frame):
|
||||||
self.event_stop.set()
|
self.event_stop.set()
|
||||||
self._thread_read_queue.join(timeout=5)
|
self._thread_read_queue.join(timeout=5)
|
||||||
@ -178,6 +235,7 @@ def configure(conf):
|
|||||||
def main():
|
def main():
|
||||||
keepalived.register_cli_l3_agent_keepalived_opts()
|
keepalived.register_cli_l3_agent_keepalived_opts()
|
||||||
keepalived.register_l3_agent_keepalived_opts()
|
keepalived.register_l3_agent_keepalived_opts()
|
||||||
|
ha_conf.register_l3_agent_ha_opts()
|
||||||
agent_config.register_root_helper()
|
agent_config.register_root_helper()
|
||||||
configure(cfg.CONF)
|
configure(cfg.CONF)
|
||||||
MonitorDaemon(cfg.CONF.pid_file,
|
MonitorDaemon(cfg.CONF.pid_file,
|
||||||
@ -187,4 +245,5 @@ def main():
|
|||||||
cfg.CONF.namespace,
|
cfg.CONF.namespace,
|
||||||
cfg.CONF.conf_dir,
|
cfg.CONF.conf_dir,
|
||||||
cfg.CONF.monitor_interface,
|
cfg.CONF.monitor_interface,
|
||||||
cfg.CONF.monitor_cidr).start()
|
cfg.CONF.monitor_cidr,
|
||||||
|
cfg.CONF.enable_conntrackd).start()
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import stat
|
|
||||||
|
|
||||||
import jinja2
|
import jinja2
|
||||||
|
|
||||||
@ -32,6 +31,9 @@ from neutron.common import utils as common_utils
|
|||||||
CONNTRACKD_SERVICE_NAME = 'conntrackd'
|
CONNTRACKD_SERVICE_NAME = 'conntrackd'
|
||||||
SIGTERM_TIMEOUT = 5
|
SIGTERM_TIMEOUT = 5
|
||||||
|
|
||||||
|
# Unix socket path length is limited to 107 characters in the
|
||||||
|
# conntrackd source code. See UNIX_PATH_MAX constant in include/local.h
|
||||||
|
UNIX_PATH_MAX = 107
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -77,135 +79,6 @@ Sync {
|
|||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
|
|
||||||
HA_SCRIPT_TEMPLATE = jinja2.Template(
|
|
||||||
"""#!/usr/bin/env sh
|
|
||||||
#
|
|
||||||
# (C) 2006-2011 by Pablo Neira Ayuso <pablo@netfilter.org>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Description:
|
|
||||||
#
|
|
||||||
# This is the script for primary-backup setups for keepalived
|
|
||||||
# (http://www.keepalived.org). You may adapt it to make it work with other
|
|
||||||
# high-availability managers.
|
|
||||||
#
|
|
||||||
# Do not forget to include the required modifications to your keepalived.conf
|
|
||||||
# file to invoke this script during keepalived's state transitions.
|
|
||||||
#
|
|
||||||
# Contributions to improve this script are welcome :).
|
|
||||||
#
|
|
||||||
|
|
||||||
CONNTRACKD_BIN=/usr/sbin/conntrackd
|
|
||||||
CONNTRACKD_LOCK={{ lock }}
|
|
||||||
CONNTRACKD_CONFIG={{ config }}
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
primary)
|
|
||||||
#
|
|
||||||
# commit the external cache into the kernel table
|
|
||||||
#
|
|
||||||
$CONNTRACKD_BIN -C $CONNTRACKD_CONFIG -c
|
|
||||||
if [ $? -eq 1 ]
|
|
||||||
then
|
|
||||||
logger "ERROR: failed to invoke conntrackd -c"
|
|
||||||
fi
|
|
||||||
|
|
||||||
#
|
|
||||||
# flush the internal and the external caches
|
|
||||||
#
|
|
||||||
$CONNTRACKD_BIN -C $CONNTRACKD_CONFIG -f
|
|
||||||
if [ $? -eq 1 ]
|
|
||||||
then
|
|
||||||
logger "ERROR: failed to invoke conntrackd -f"
|
|
||||||
fi
|
|
||||||
|
|
||||||
#
|
|
||||||
# resynchronize my internal cache to the kernel table
|
|
||||||
#
|
|
||||||
$CONNTRACKD_BIN -C $CONNTRACKD_CONFIG -R
|
|
||||||
if [ $? -eq 1 ]
|
|
||||||
then
|
|
||||||
logger "ERROR: failed to invoke conntrackd -R"
|
|
||||||
fi
|
|
||||||
|
|
||||||
#
|
|
||||||
# send a bulk update to backups
|
|
||||||
#
|
|
||||||
$CONNTRACKD_BIN -C $CONNTRACKD_CONFIG -B
|
|
||||||
if [ $? -eq 1 ]
|
|
||||||
then
|
|
||||||
logger "ERROR: failed to invoke conntrackd -B"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
backup)
|
|
||||||
#
|
|
||||||
# is conntrackd running? request some statistics to check it
|
|
||||||
#
|
|
||||||
$CONNTRACKD_BIN -C $CONNTRACKD_CONFIG -s
|
|
||||||
if [ $? -eq 1 ]
|
|
||||||
then
|
|
||||||
#
|
|
||||||
# something's wrong, do we have a lock file?
|
|
||||||
#
|
|
||||||
if [ -f $CONNTRACKD_LOCK ]
|
|
||||||
then
|
|
||||||
logger "WARNING: conntrackd was not cleanly stopped."
|
|
||||||
logger "If you suspect that it has crashed:"
|
|
||||||
logger "1) Enable coredumps"
|
|
||||||
logger "2) Try to reproduce the problem"
|
|
||||||
logger "3) Post the coredump to netfilter-devel@vger.kernel.org"
|
|
||||||
rm -f $CONNTRACKD_LOCK
|
|
||||||
fi
|
|
||||||
$CONNTRACKD_BIN -C $CONNTRACKD_CONFIG -d
|
|
||||||
if [ $? -eq 1 ]
|
|
||||||
then
|
|
||||||
logger "ERROR: cannot launch conntrackd"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
#
|
|
||||||
# shorten kernel conntrack timers to remove the zombie entries.
|
|
||||||
#
|
|
||||||
$CONNTRACKD_BIN -C $CONNTRACKD_CONFIG -t
|
|
||||||
if [ $? -eq 1 ]
|
|
||||||
then
|
|
||||||
logger "ERROR: failed to invoke conntrackd -t"
|
|
||||||
fi
|
|
||||||
|
|
||||||
#
|
|
||||||
# request resynchronization with master firewall replica (if any)
|
|
||||||
# Note: this does nothing in the alarm approach.
|
|
||||||
#
|
|
||||||
$CONNTRACKD_BIN -C $CONNTRACKD_CONFIG -n
|
|
||||||
if [ $? -eq 1 ]
|
|
||||||
then
|
|
||||||
logger "ERROR: failed to invoke conntrackd -n"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
fault)
|
|
||||||
#
|
|
||||||
# shorten kernel conntrack timers to remove the zombie entries.
|
|
||||||
#
|
|
||||||
$CONNTRACKD_BIN -C $CONNTRACKD_CONFIG -t
|
|
||||||
if [ $? -eq 1 ]
|
|
||||||
then
|
|
||||||
logger "ERROR: failed to invoke conntrackd -t"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
logger "ERROR: unknown state transition"
|
|
||||||
echo "Usage: primary-backup.sh {primary|backup|fault}"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
class ConntrackdManager:
|
class ConntrackdManager:
|
||||||
"""Wrapper for conntrackd.
|
"""Wrapper for conntrackd.
|
||||||
@ -225,18 +98,8 @@ class ConntrackdManager:
|
|||||||
self.ha_iface = ha_iface
|
self.ha_iface = ha_iface
|
||||||
self.namespace = namespace
|
self.namespace = namespace
|
||||||
|
|
||||||
def build_ha_script(self):
|
|
||||||
ha_script_content = HA_SCRIPT_TEMPLATE.render(
|
|
||||||
lock=self.get_lockfile_path(),
|
|
||||||
config=self.get_conffile_path(),
|
|
||||||
)
|
|
||||||
ha_script_path = self.get_ha_script_path()
|
|
||||||
|
|
||||||
file_utils.replace_file(ha_script_path, ha_script_content)
|
|
||||||
os.chmod(ha_script_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
|
|
||||||
|
|
||||||
def get_full_config_file_path(self, filename, maxlen=255):
|
def get_full_config_file_path(self, filename, maxlen=255):
|
||||||
# Maximum PATH lenght for most paths in conntrackd is limited to
|
# Maximum PATH length for most paths in conntrackd is limited to
|
||||||
# 255 characters.
|
# 255 characters.
|
||||||
conf_dir = self.get_conf_dir()
|
conf_dir = self.get_conf_dir()
|
||||||
ensure_tree(conf_dir, 0o755)
|
ensure_tree(conf_dir, 0o755)
|
||||||
@ -254,11 +117,7 @@ class ConntrackdManager:
|
|||||||
confs_dir = os.path.abspath(
|
confs_dir = os.path.abspath(
|
||||||
os.path.normpath(self.agent_conf.ha_confs_path))
|
os.path.normpath(self.agent_conf.ha_confs_path))
|
||||||
|
|
||||||
conf_dir = os.path.join(confs_dir, self.resource_id)
|
return os.path.join(confs_dir, self.resource_id)
|
||||||
return conf_dir
|
|
||||||
|
|
||||||
def get_ha_script_path(self):
|
|
||||||
return self.get_full_config_file_path('primary-backup.sh')
|
|
||||||
|
|
||||||
def get_pid_file_path(self):
|
def get_pid_file_path(self):
|
||||||
return self.get_full_config_file_path('conntrackd.pid')
|
return self.get_full_config_file_path('conntrackd.pid')
|
||||||
@ -267,9 +126,10 @@ class ConntrackdManager:
|
|||||||
return self.get_full_config_file_path('conntrackd.lock')
|
return self.get_full_config_file_path('conntrackd.lock')
|
||||||
|
|
||||||
def get_ctlfile_path(self):
|
def get_ctlfile_path(self):
|
||||||
# Unix socket path length is limited to 107 characters in the
|
return self.get_full_config_file_path(
|
||||||
# conntrackd source code. See UNIX_PATH_MAX constant in include/local.h
|
'conntrackd.ctl',
|
||||||
return self.get_full_config_file_path('conntrackd.ctl', maxlen=107)
|
maxlen=UNIX_PATH_MAX,
|
||||||
|
)
|
||||||
|
|
||||||
def get_conffile_path(self):
|
def get_conffile_path(self):
|
||||||
return self.get_full_config_file_path('conntrackd.conf')
|
return self.get_full_config_file_path('conntrackd.conf')
|
||||||
@ -279,13 +139,15 @@ class ConntrackdManager:
|
|||||||
pid_file = self.get_pid_file_path()
|
pid_file = self.get_pid_file_path()
|
||||||
|
|
||||||
cmd = 'conntrackd -d -C %s' % config_path
|
cmd = 'conntrackd -d -C %s' % config_path
|
||||||
pid = utils.find_pid_by_cmd(cmd)
|
pid = utils.pgrep(cmd)
|
||||||
|
|
||||||
|
if not pid:
|
||||||
|
raise RuntimeError(_('No process for "%s" found.' % cmd))
|
||||||
|
|
||||||
file_utils.replace_file(pid_file, pid)
|
file_utils.replace_file(pid_file, pid)
|
||||||
|
|
||||||
def spawn(self):
|
def spawn(self):
|
||||||
config_path = self.output_config_file()
|
config_path = self.output_config_file()
|
||||||
self.build_ha_script()
|
|
||||||
|
|
||||||
def callback(pidfile):
|
def callback(pidfile):
|
||||||
cmd = ['conntrackd', '-d',
|
cmd = ['conntrackd', '-d',
|
||||||
@ -335,7 +197,7 @@ class ConntrackdManager:
|
|||||||
if not pm.active:
|
if not pm.active:
|
||||||
return
|
return
|
||||||
|
|
||||||
# First try to stop conntrackd by using it's own control command
|
# First try to stop conntrackd by using its own control command
|
||||||
config_path = self.get_conffile_path()
|
config_path = self.get_conffile_path()
|
||||||
cmd = ['conntrackd', '-C', config_path, '-k']
|
cmd = ['conntrackd', '-C', config_path, '-k']
|
||||||
utils.execute(cmd, run_as_root=True)
|
utils.execute(cmd, run_as_root=True)
|
||||||
|
@ -70,7 +70,6 @@ class ProcessManager(MonitoredProcess):
|
|||||||
self.default_cmd_callback = default_cmd_callback
|
self.default_cmd_callback = default_cmd_callback
|
||||||
self.default_pre_cmd_callback = default_pre_cmd_callback
|
self.default_pre_cmd_callback = default_pre_cmd_callback
|
||||||
self.default_post_cmd_callback = default_post_cmd_callback
|
self.default_post_cmd_callback = default_post_cmd_callback
|
||||||
self.cmd_addl_env = cmd_addl_env
|
|
||||||
self.pids_path = pids_path or self.conf.external_pids
|
self.pids_path = pids_path or self.conf.external_pids
|
||||||
self.pid_file = pid_file
|
self.pid_file = pid_file
|
||||||
self.run_as_root = run_as_root or self.namespace is not None
|
self.run_as_root = run_as_root or self.namespace is not None
|
||||||
|
@ -189,7 +189,7 @@ class KeepalivedInstance:
|
|||||||
advert_int=None, mcast_src_ip=None, nopreempt=False,
|
advert_int=None, mcast_src_ip=None, nopreempt=False,
|
||||||
garp_primary_delay=GARP_PRIMARY_DELAY,
|
garp_primary_delay=GARP_PRIMARY_DELAY,
|
||||||
vrrp_health_check_interval=0,
|
vrrp_health_check_interval=0,
|
||||||
ha_conf_dir=None, notify_script=None):
|
ha_conf_dir=None):
|
||||||
self.name = 'VR_%s' % vrouter_id
|
self.name = 'VR_%s' % vrouter_id
|
||||||
|
|
||||||
if state not in VALID_STATES:
|
if state not in VALID_STATES:
|
||||||
@ -201,7 +201,6 @@ class KeepalivedInstance:
|
|||||||
self.priority = priority
|
self.priority = priority
|
||||||
self.nopreempt = nopreempt
|
self.nopreempt = nopreempt
|
||||||
self.advert_int = advert_int
|
self.advert_int = advert_int
|
||||||
self.notify_script = notify_script
|
|
||||||
self.mcast_src_ip = mcast_src_ip
|
self.mcast_src_ip = mcast_src_ip
|
||||||
self.garp_primary_delay = garp_primary_delay
|
self.garp_primary_delay = garp_primary_delay
|
||||||
self.track_interfaces = []
|
self.track_interfaces = []
|
||||||
@ -318,13 +317,6 @@ class KeepalivedInstance:
|
|||||||
' priority %s' % self.priority,
|
' priority %s' % self.priority,
|
||||||
' garp_master_delay %s' % self.garp_primary_delay])
|
' garp_master_delay %s' % self.garp_primary_delay])
|
||||||
|
|
||||||
if self.notify_script:
|
|
||||||
config.extend([
|
|
||||||
' notify_master "%s primary"' % self.notify_script,
|
|
||||||
' notify_backup "%s backup"' % self.notify_script,
|
|
||||||
' notify_fault "%s fault"' % self.notify_script,
|
|
||||||
])
|
|
||||||
|
|
||||||
if self.nopreempt:
|
if self.nopreempt:
|
||||||
config.append(' nopreempt')
|
config.append(' nopreempt')
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ import shlex
|
|||||||
import socket
|
import socket
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
import typing
|
||||||
|
|
||||||
import eventlet
|
import eventlet
|
||||||
from eventlet.green import subprocess
|
from eventlet.green import subprocess
|
||||||
@ -35,7 +36,6 @@ from oslo_utils import excutils
|
|||||||
from oslo_utils import fileutils
|
from oslo_utils import fileutils
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
from neutron._i18n import _
|
|
||||||
from neutron.api import wsgi
|
from neutron.api import wsgi
|
||||||
from neutron.common import utils
|
from neutron.common import utils
|
||||||
from neutron.conf.agent import common as config
|
from neutron.conf.agent import common as config
|
||||||
@ -182,18 +182,20 @@ def find_child_pids(pid, recursive=False):
|
|||||||
return child_pids
|
return child_pids
|
||||||
|
|
||||||
|
|
||||||
def find_pid_by_cmd(cmd):
|
def pgrep(
|
||||||
"""Retrieve a list of the pids by their cmd."""
|
command: str,
|
||||||
pids = execute(['pgrep', '-f', cmd], log_fail_as_error=False).split()
|
entire_command_line: bool = True
|
||||||
|
) -> typing.Optional[str]:
|
||||||
|
cmd = ['pgrep']
|
||||||
|
if entire_command_line:
|
||||||
|
cmd += ['-f']
|
||||||
|
cmd += [command]
|
||||||
|
try:
|
||||||
|
result = execute(cmd).strip()
|
||||||
|
except exceptions.ProcessExecutionError:
|
||||||
|
return None
|
||||||
|
|
||||||
if len(pids) > 1:
|
return result if result else None
|
||||||
raise RuntimeError(
|
|
||||||
_('%i processes for "%s" found.' % (len(pids), cmd))
|
|
||||||
)
|
|
||||||
if pids == []:
|
|
||||||
raise RuntimeError(_('No process for "%s" found.' % cmd))
|
|
||||||
|
|
||||||
return pids[0]
|
|
||||||
|
|
||||||
|
|
||||||
def find_parent_pid(pid):
|
def find_parent_pid(pid):
|
||||||
|
@ -25,6 +25,9 @@ CLI_OPTS = [
|
|||||||
cfg.StrOpt('monitor_interface', help=_('Interface to monitor')),
|
cfg.StrOpt('monitor_interface', help=_('Interface to monitor')),
|
||||||
cfg.StrOpt('monitor_cidr', help=_('CIDR to monitor')),
|
cfg.StrOpt('monitor_cidr', help=_('CIDR to monitor')),
|
||||||
cfg.StrOpt('pid_file', help=_('Path to PID file for this process')),
|
cfg.StrOpt('pid_file', help=_('Path to PID file for this process')),
|
||||||
|
cfg.BoolOpt('enable_conntrackd',
|
||||||
|
help=_('Enable conntrackd support'),
|
||||||
|
default=False),
|
||||||
cfg.StrOpt('user', help=_('User (uid or name) running this process '
|
cfg.StrOpt('user', help=_('User (uid or name) running this process '
|
||||||
'after its initialization')),
|
'after its initialization')),
|
||||||
cfg.StrOpt('group', help=_('Group (gid or name) running this process '
|
cfg.StrOpt('group', help=_('Group (gid or name) running this process '
|
||||||
|
@ -17,16 +17,13 @@ import math
|
|||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import signal
|
import signal
|
||||||
import typing
|
|
||||||
|
|
||||||
from neutron_lib.agent import topics
|
from neutron_lib.agent import topics
|
||||||
from neutron_lib import constants
|
from neutron_lib import constants
|
||||||
from neutron_lib import context
|
from neutron_lib import context
|
||||||
from neutron_lib import exceptions
|
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
|
|
||||||
import neutron
|
import neutron
|
||||||
from neutron.agent.linux import utils as linux_utils
|
|
||||||
from neutron.db import agents_db
|
from neutron.db import agents_db
|
||||||
|
|
||||||
HOST = 'localhost'
|
HOST = 'localhost'
|
||||||
@ -262,19 +259,3 @@ class TestTimer:
|
|||||||
|
|
||||||
if self._alarm_fn:
|
if self._alarm_fn:
|
||||||
self._alarm_fn(timeout)
|
self._alarm_fn(timeout)
|
||||||
|
|
||||||
|
|
||||||
def pgrep(
|
|
||||||
command: str,
|
|
||||||
entire_command_line: bool = True
|
|
||||||
) -> typing.Optional[str]:
|
|
||||||
cmd = ['pgrep']
|
|
||||||
if entire_command_line:
|
|
||||||
cmd += ['-f']
|
|
||||||
cmd += [command]
|
|
||||||
try:
|
|
||||||
result = linux_utils.execute(cmd)
|
|
||||||
except exceptions.ProcessExecutionError:
|
|
||||||
return
|
|
||||||
|
|
||||||
return result[0] if result else None
|
|
||||||
|
@ -71,7 +71,7 @@ class NeutronConfigFixture(ConfigFixture):
|
|||||||
'host': self._generate_host(),
|
'host': self._generate_host(),
|
||||||
# Enable conntrackd for tests to get full test coverage
|
# Enable conntrackd for tests to get full test coverage
|
||||||
'ha_conntrackd_enabled': 'True',
|
'ha_conntrackd_enabled': 'True',
|
||||||
# Conntrackd only supports 107 characters for it's control
|
# Conntrackd only supports 107 characters for its control
|
||||||
# socket path. Thus the "state_path" should not be nested in
|
# socket path. Thus the "state_path" should not be nested in
|
||||||
# a temporary directory to avoid the final path being too long.
|
# a temporary directory to avoid the final path being too long.
|
||||||
'state_path': self.temp_dir,
|
'state_path': self.temp_dir,
|
||||||
|
@ -62,9 +62,6 @@ vrrp_instance VR_1 {
|
|||||||
virtual_router_id 1
|
virtual_router_id 1
|
||||||
priority 50
|
priority 50
|
||||||
garp_master_delay 60
|
garp_master_delay 60
|
||||||
notify_master "%(conf_dir)s/primary-backup.sh primary"
|
|
||||||
notify_backup "%(conf_dir)s/primary-backup.sh backup"
|
|
||||||
notify_fault "%(conf_dir)s/primary-backup.sh fault"
|
|
||||||
nopreempt
|
nopreempt
|
||||||
advert_int 2
|
advert_int 2
|
||||||
track_interface {
|
track_interface {
|
||||||
@ -480,10 +477,6 @@ class L3AgentTestFramework(base.BaseSudoTestCase):
|
|||||||
|
|
||||||
def get_expected_keepalive_configuration(self, router):
|
def get_expected_keepalive_configuration(self, router):
|
||||||
ha_device_name = router.get_ha_device_name()
|
ha_device_name = router.get_ha_device_name()
|
||||||
conf_dir = os.path.join(
|
|
||||||
self.agent.conf.ha_confs_path,
|
|
||||||
router.router_id,
|
|
||||||
)
|
|
||||||
external_port = router.get_ex_gw_port()
|
external_port = router.get_ex_gw_port()
|
||||||
ex_port_ipv6 = ip_lib.get_ipv6_lladdr(external_port['mac_address'])
|
ex_port_ipv6 = ip_lib.get_ipv6_lladdr(external_port['mac_address'])
|
||||||
ex_device_name = router.get_external_device_name(
|
ex_device_name = router.get_external_device_name(
|
||||||
@ -502,7 +495,6 @@ class L3AgentTestFramework(base.BaseSudoTestCase):
|
|||||||
'email_from': keepalived.KEEPALIVED_EMAIL_FROM,
|
'email_from': keepalived.KEEPALIVED_EMAIL_FROM,
|
||||||
'router_id': keepalived.KEEPALIVED_ROUTER_ID,
|
'router_id': keepalived.KEEPALIVED_ROUTER_ID,
|
||||||
'ha_device_name': ha_device_name,
|
'ha_device_name': ha_device_name,
|
||||||
'conf_dir': conf_dir,
|
|
||||||
'ex_device_name': ex_device_name,
|
'ex_device_name': ex_device_name,
|
||||||
'external_device_cidr': external_device_cidr,
|
'external_device_cidr': external_device_cidr,
|
||||||
'internal_device_name': internal_device_name,
|
'internal_device_name': internal_device_name,
|
||||||
|
@ -71,7 +71,8 @@ class TestMonitorDaemon(base.BaseLoggingTestCase):
|
|||||||
def _callback(self, *args):
|
def _callback(self, *args):
|
||||||
return self.cmd_opts
|
return self.cmd_opts
|
||||||
|
|
||||||
def _generate_cmd_opts(self, monitor_interface=None, cidr=None):
|
def _generate_cmd_opts(self, monitor_interface=None, cidr=None,
|
||||||
|
conntrackd=True):
|
||||||
monitor_interface = monitor_interface or self.router.port.name
|
monitor_interface = monitor_interface or self.router.port.name
|
||||||
cidr = cidr or self.cidr
|
cidr = cidr or self.cidr
|
||||||
self.cmd_opts = [
|
self.cmd_opts = [
|
||||||
@ -89,6 +90,9 @@ class TestMonitorDaemon(base.BaseLoggingTestCase):
|
|||||||
'--debug',
|
'--debug',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if conntrackd:
|
||||||
|
self.cmd_opts.append('--enable_conntrackd')
|
||||||
|
|
||||||
def _search_in_file(self, file_name, text):
|
def _search_in_file(self, file_name, text):
|
||||||
def text_in_file():
|
def text_in_file():
|
||||||
try:
|
try:
|
||||||
@ -113,27 +117,46 @@ class TestMonitorDaemon(base.BaseLoggingTestCase):
|
|||||||
|
|
||||||
def test_read_queue_change_state(self):
|
def test_read_queue_change_state(self):
|
||||||
self._run_monitor()
|
self._run_monitor()
|
||||||
msg = 'Wrote router %s state %s'
|
msg_state = 'Wrote router %s state %s'
|
||||||
|
msg_conntrack = 'Synced connection tracking state on %s for router %s'
|
||||||
|
|
||||||
self.router.port.addr.add(self.cidr)
|
self.router.port.addr.add(self.cidr)
|
||||||
self._search_in_file(self.log_file, msg % (self.router_id, 'primary'))
|
self._search_in_file(self.log_file,
|
||||||
|
msg_state % (self.router_id, 'primary'))
|
||||||
|
self._search_in_file(self.log_file,
|
||||||
|
msg_conntrack % ('primary', self.router_id))
|
||||||
|
|
||||||
self.router.port.addr.delete(self.cidr)
|
self.router.port.addr.delete(self.cidr)
|
||||||
self._search_in_file(self.log_file, msg % (self.router_id, 'backup'))
|
self._search_in_file(self.log_file,
|
||||||
|
msg_state % (self.router_id, 'backup'))
|
||||||
|
self._search_in_file(self.log_file,
|
||||||
|
msg_conntrack % ('backup', self.router_id))
|
||||||
|
|
||||||
def test_handle_initial_state_backup(self):
|
def test_handle_initial_state_backup(self):
|
||||||
# No tracked IP (self.cidr) is configured in the monitored interface
|
# No tracked IP (self.cidr) is configured in the monitored interface
|
||||||
# (self.router.port)
|
# (self.router.port)
|
||||||
self._run_monitor()
|
self._run_monitor()
|
||||||
|
|
||||||
msg = 'Initial status of router {} is {}'.format(
|
msg = 'Initial status of router {} is {}'.format(
|
||||||
self.router_id, 'backup')
|
self.router_id, 'backup')
|
||||||
self._search_in_file(self.log_file, msg)
|
self._search_in_file(self.log_file, msg)
|
||||||
|
|
||||||
|
msg = ('Synced connection tracking state on backup for router %s' %
|
||||||
|
self.router_id)
|
||||||
|
self._search_in_file(self.log_file, msg)
|
||||||
|
|
||||||
def test_handle_initial_state_primary(self):
|
def test_handle_initial_state_primary(self):
|
||||||
self.router.port.addr.add(self.cidr)
|
self.router.port.addr.add(self.cidr)
|
||||||
self._run_monitor()
|
self._run_monitor()
|
||||||
|
|
||||||
msg = 'Initial status of router {} is {}'.format(
|
msg = 'Initial status of router {} is {}'.format(
|
||||||
self.router_id, 'primary')
|
self.router_id, 'primary')
|
||||||
self._search_in_file(self.log_file, msg)
|
self._search_in_file(self.log_file, msg)
|
||||||
|
|
||||||
|
msg = ('Synced connection tracking state on primary for router %s' %
|
||||||
|
self.router_id)
|
||||||
|
self._search_in_file(self.log_file, msg)
|
||||||
|
|
||||||
def test_handle_initial_state_backup_error_reading_initial_status(self):
|
def test_handle_initial_state_backup_error_reading_initial_status(self):
|
||||||
# By passing this wrong IP address, the thread "_thread_initial_state"
|
# By passing this wrong IP address, the thread "_thread_initial_state"
|
||||||
# will fail generating an exception (caught inside the called method).
|
# will fail generating an exception (caught inside the called method).
|
||||||
@ -153,3 +176,14 @@ class TestMonitorDaemon(base.BaseLoggingTestCase):
|
|||||||
msg = 'Initial status of router {} is {}'.format(
|
msg = 'Initial status of router {} is {}'.format(
|
||||||
self.router_id, 'backup')
|
self.router_id, 'backup')
|
||||||
self._search_in_file(self.log_file, msg)
|
self._search_in_file(self.log_file, msg)
|
||||||
|
|
||||||
|
def test_conntrackd_disabled(self):
|
||||||
|
self._generate_cmd_opts(conntrackd=False)
|
||||||
|
self._run_monitor()
|
||||||
|
|
||||||
|
# Wait for initial notification. This ensures the first update cycle is
|
||||||
|
# completed
|
||||||
|
self._search_in_file(self.log_file, 'Notified agent router')
|
||||||
|
|
||||||
|
# Check there are no conntrack messages
|
||||||
|
self.assertNotIn('conntrack', open(self.log_file).read())
|
||||||
|
@ -22,11 +22,10 @@ import psutil
|
|||||||
import tenacity
|
import tenacity
|
||||||
|
|
||||||
from neutron.agent.linux import utils
|
from neutron.agent.linux import utils
|
||||||
from neutron.tests.common import helpers
|
|
||||||
|
|
||||||
|
|
||||||
def _kill_process_if_exists(command: str) -> None:
|
def _kill_process_if_exists(command: str) -> None:
|
||||||
_pid = helpers.pgrep(command)
|
_pid = utils.pgrep(command)
|
||||||
if _pid:
|
if _pid:
|
||||||
utils.kill_process(_pid, signal.SIGKILL)
|
utils.kill_process(_pid, signal.SIGKILL)
|
||||||
|
|
||||||
|
@ -92,22 +92,10 @@ class KeepalivedGetFreeRangeTestCase(KeepalivedBaseTestCase):
|
|||||||
|
|
||||||
class KeepalivedConfBaseMixin:
|
class KeepalivedConfBaseMixin:
|
||||||
|
|
||||||
def _get_conntrackd_manager(self):
|
|
||||||
conntrackd_manager = mock.Mock()
|
|
||||||
conntrackd_manager.get_ha_script_path.return_value = '/tmp/ha.sh'
|
|
||||||
return conntrackd_manager
|
|
||||||
|
|
||||||
def _get_config(self, track=True):
|
def _get_config(self, track=True):
|
||||||
config = keepalived.KeepalivedConf()
|
config = keepalived.KeepalivedConf()
|
||||||
conntrackd_manager = self._get_conntrackd_manager()
|
|
||||||
|
|
||||||
notify_script = (cfg.CONF.ha_conntrackd_enabled and
|
|
||||||
conntrackd_manager.get_ha_script_path() or
|
|
||||||
None)
|
|
||||||
|
|
||||||
instance1 = keepalived.KeepalivedInstance(
|
instance1 = keepalived.KeepalivedInstance(
|
||||||
'MASTER', 'eth0', 1, ['169.254.192.0/18'],
|
'MASTER', 'eth0', 1, ['169.254.192.0/18'],
|
||||||
notify_script=notify_script,
|
|
||||||
advert_int=5)
|
advert_int=5)
|
||||||
instance1.set_authentication('AH', 'pass123')
|
instance1.set_authentication('AH', 'pass123')
|
||||||
instance1.track_interfaces.append("eth0")
|
instance1.track_interfaces.append("eth0")
|
||||||
@ -136,7 +124,6 @@ class KeepalivedConfBaseMixin:
|
|||||||
|
|
||||||
instance2 = keepalived.KeepalivedInstance(
|
instance2 = keepalived.KeepalivedInstance(
|
||||||
'MASTER', 'eth4', 2, ['169.254.192.0/18'],
|
'MASTER', 'eth4', 2, ['169.254.192.0/18'],
|
||||||
notify_script=notify_script,
|
|
||||||
mcast_src_ip='224.0.0.1')
|
mcast_src_ip='224.0.0.1')
|
||||||
instance2.track_interfaces.append("eth4")
|
instance2.track_interfaces.append("eth4")
|
||||||
|
|
||||||
@ -163,9 +150,6 @@ class KeepalivedConfTestCase(KeepalivedBaseTestCase,
|
|||||||
virtual_router_id 1
|
virtual_router_id 1
|
||||||
priority 50
|
priority 50
|
||||||
garp_master_delay 60
|
garp_master_delay 60
|
||||||
notify_master "/tmp/ha.sh primary"
|
|
||||||
notify_backup "/tmp/ha.sh backup"
|
|
||||||
notify_fault "/tmp/ha.sh fault"
|
|
||||||
advert_int 5
|
advert_int 5
|
||||||
authentication {
|
authentication {
|
||||||
auth_type AH
|
auth_type AH
|
||||||
@ -193,9 +177,6 @@ class KeepalivedConfTestCase(KeepalivedBaseTestCase,
|
|||||||
virtual_router_id 2
|
virtual_router_id 2
|
||||||
priority 50
|
priority 50
|
||||||
garp_master_delay 60
|
garp_master_delay 60
|
||||||
notify_master "/tmp/ha.sh primary"
|
|
||||||
notify_backup "/tmp/ha.sh backup"
|
|
||||||
notify_fault "/tmp/ha.sh fault"
|
|
||||||
mcast_src_ip 224.0.0.1
|
mcast_src_ip 224.0.0.1
|
||||||
track_interface {
|
track_interface {
|
||||||
eth4
|
eth4
|
||||||
@ -243,17 +224,6 @@ class KeepalivedConfTestCase(KeepalivedBaseTestCase,
|
|||||||
current_vips = sorted(instance.get_existing_vip_ip_addresses('eth2'))
|
current_vips = sorted(instance.get_existing_vip_ip_addresses('eth2'))
|
||||||
self.assertEqual(['192.168.2.0/24', '192.168.3.0/24'], current_vips)
|
self.assertEqual(['192.168.2.0/24', '192.168.3.0/24'], current_vips)
|
||||||
|
|
||||||
def test_config_generation_no_conntrackd(self):
|
|
||||||
# Disable conntrackd support
|
|
||||||
self.config(ha_conntrackd_enabled=False)
|
|
||||||
|
|
||||||
config = self._get_config()
|
|
||||||
|
|
||||||
# Assert no notification scripts are configured
|
|
||||||
self.assertNotIn('notify_master', config.get_config_str())
|
|
||||||
self.assertNotIn('notify_backup', config.get_config_str())
|
|
||||||
self.assertNotIn('notify_fault', config.get_config_str())
|
|
||||||
|
|
||||||
|
|
||||||
class KeepalivedStateExceptionTestCase(KeepalivedBaseTestCase):
|
class KeepalivedStateExceptionTestCase(KeepalivedBaseTestCase):
|
||||||
def test_state_exception(self):
|
def test_state_exception(self):
|
||||||
@ -261,11 +231,11 @@ class KeepalivedStateExceptionTestCase(KeepalivedBaseTestCase):
|
|||||||
self.assertRaises(keepalived.InvalidInstanceStateException,
|
self.assertRaises(keepalived.InvalidInstanceStateException,
|
||||||
keepalived.KeepalivedInstance,
|
keepalived.KeepalivedInstance,
|
||||||
invalid_vrrp_state, 'eth0', 33,
|
invalid_vrrp_state, 'eth0', 33,
|
||||||
['169.254.192.0/18'], None)
|
['169.254.192.0/18'])
|
||||||
|
|
||||||
invalid_auth_type = 'into a club'
|
invalid_auth_type = 'into a club'
|
||||||
instance = keepalived.KeepalivedInstance('MASTER', 'eth0', 1,
|
instance = keepalived.KeepalivedInstance('MASTER', 'eth0', 1,
|
||||||
['169.254.192.0/18'], None)
|
['169.254.192.0/18'])
|
||||||
self.assertRaises(keepalived.InvalidAuthenticationTypeException,
|
self.assertRaises(keepalived.InvalidAuthenticationTypeException,
|
||||||
instance.set_authentication,
|
instance.set_authentication,
|
||||||
invalid_auth_type, 'some_password')
|
invalid_auth_type, 'some_password')
|
||||||
@ -350,8 +320,7 @@ class KeepalivedInstanceTestCase(KeepalivedBaseTestCase,
|
|||||||
KeepalivedConfBaseMixin):
|
KeepalivedConfBaseMixin):
|
||||||
def test_get_primary_vip(self):
|
def test_get_primary_vip(self):
|
||||||
instance = keepalived.KeepalivedInstance('MASTER', 'ha0', 42,
|
instance = keepalived.KeepalivedInstance('MASTER', 'ha0', 42,
|
||||||
['169.254.192.0/18'],
|
['169.254.192.0/18'])
|
||||||
None)
|
|
||||||
self.assertEqual('169.254.0.42/24', instance.get_primary_vip())
|
self.assertEqual('169.254.0.42/24', instance.get_primary_vip())
|
||||||
|
|
||||||
def _test_remove_addresses_by_interface(self, track=True):
|
def _test_remove_addresses_by_interface(self, track=True):
|
||||||
@ -369,9 +338,6 @@ class KeepalivedInstanceTestCase(KeepalivedBaseTestCase,
|
|||||||
virtual_router_id 1
|
virtual_router_id 1
|
||||||
priority 50
|
priority 50
|
||||||
garp_master_delay 60
|
garp_master_delay 60
|
||||||
notify_master "/tmp/ha.sh primary"
|
|
||||||
notify_backup "/tmp/ha.sh backup"
|
|
||||||
notify_fault "/tmp/ha.sh fault"
|
|
||||||
advert_int 5
|
advert_int 5
|
||||||
authentication {{
|
authentication {{
|
||||||
auth_type AH
|
auth_type AH
|
||||||
@ -396,9 +362,6 @@ class KeepalivedInstanceTestCase(KeepalivedBaseTestCase,
|
|||||||
virtual_router_id 2
|
virtual_router_id 2
|
||||||
priority 50
|
priority 50
|
||||||
garp_master_delay 60
|
garp_master_delay 60
|
||||||
notify_master "/tmp/ha.sh primary"
|
|
||||||
notify_backup "/tmp/ha.sh backup"
|
|
||||||
notify_fault "/tmp/ha.sh fault"
|
|
||||||
mcast_src_ip 224.0.0.1
|
mcast_src_ip 224.0.0.1
|
||||||
track_interface {{
|
track_interface {{
|
||||||
eth4
|
eth4
|
||||||
@ -441,18 +404,13 @@ class KeepalivedInstanceTestCase(KeepalivedBaseTestCase,
|
|||||||
virtual_router_id 1
|
virtual_router_id 1
|
||||||
priority 50
|
priority 50
|
||||||
garp_master_delay 60
|
garp_master_delay 60
|
||||||
notify_master "/tmp/ha.sh primary"
|
|
||||||
notify_backup "/tmp/ha.sh backup"
|
|
||||||
notify_fault "/tmp/ha.sh fault"
|
|
||||||
virtual_ipaddress {
|
virtual_ipaddress {
|
||||||
169.254.0.1/24 dev eth0
|
169.254.0.1/24 dev eth0
|
||||||
}
|
}
|
||||||
}""")
|
}""")
|
||||||
|
|
||||||
conntrackd_manager = self._get_conntrackd_manager()
|
|
||||||
instance = keepalived.KeepalivedInstance(
|
instance = keepalived.KeepalivedInstance(
|
||||||
'MASTER', 'eth0', VRRP_ID, ['169.254.192.0/18'],
|
'MASTER', 'eth0', VRRP_ID, ['169.254.192.0/18'],
|
||||||
notify_script=conntrackd_manager.get_ha_script_path(),
|
|
||||||
)
|
)
|
||||||
self.assertEqual(expected, os.linesep.join(instance.build_config()))
|
self.assertEqual(expected, os.linesep.join(instance.build_config()))
|
||||||
|
|
||||||
@ -472,18 +430,13 @@ class KeepalivedInstanceTestCase(KeepalivedBaseTestCase,
|
|||||||
virtual_router_id 1
|
virtual_router_id 1
|
||||||
priority 50
|
priority 50
|
||||||
garp_master_delay 60
|
garp_master_delay 60
|
||||||
notify_master "/tmp/ha.sh primary"
|
|
||||||
notify_backup "/tmp/ha.sh backup"
|
|
||||||
notify_fault "/tmp/ha.sh fault"
|
|
||||||
virtual_ipaddress {
|
virtual_ipaddress {
|
||||||
169.254.0.1/24 dev eth0
|
169.254.0.1/24 dev eth0
|
||||||
}
|
}
|
||||||
}""")
|
}""")
|
||||||
|
|
||||||
conntrackd_manager = self._get_conntrackd_manager()
|
|
||||||
instance = keepalived.KeepalivedInstance(
|
instance = keepalived.KeepalivedInstance(
|
||||||
'MASTER', 'eth0', VRRP_ID, ['169.254.192.0/18'],
|
'MASTER', 'eth0', VRRP_ID, ['169.254.192.0/18'],
|
||||||
notify_script=conntrackd_manager.get_ha_script_path(),
|
|
||||||
)
|
)
|
||||||
instance.track_script = keepalived.KeepalivedTrackScript(
|
instance.track_script = keepalived.KeepalivedTrackScript(
|
||||||
VRRP_INTERVAL, '/etc/ha_confs/qrouter-x', VRRP_ID)
|
VRRP_INTERVAL, '/etc/ha_confs/qrouter-x', VRRP_ID)
|
||||||
@ -500,7 +453,7 @@ class KeepalivedVipAddressTestCase(KeepalivedBaseTestCase):
|
|||||||
|
|
||||||
def test_add_vip_idempotent(self):
|
def test_add_vip_idempotent(self):
|
||||||
instance = keepalived.KeepalivedInstance('MASTER', 'eth0', 1,
|
instance = keepalived.KeepalivedInstance('MASTER', 'eth0', 1,
|
||||||
['169.254.192.0/18'], None)
|
['169.254.192.0/18'])
|
||||||
instance.add_vip('192.168.222.1/32', 'eth11', None)
|
instance.add_vip('192.168.222.1/32', 'eth11', None)
|
||||||
instance.add_vip('192.168.222.1/32', 'eth12', 'link')
|
instance.add_vip('192.168.222.1/32', 'eth12', 'link')
|
||||||
self.assertEqual(1, len(instance.vips))
|
self.assertEqual(1, len(instance.vips))
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
features:
|
features:
|
||||||
- |
|
- |
|
||||||
HA routers can now run "conntrackd" in addition to "keepalived" to
|
HA routers can now run "conntrackd" in addition to "keepalived" to
|
||||||
synchronize connection tracking states accross router instances. This
|
synchronize connection tracking states across router instances. This
|
||||||
ensures that established connections survive a HA router failover. L3 agent
|
ensures that established connections survive a HA router failover. L3 agent
|
||||||
hosts must have the "conntrackd" binary installed.
|
hosts must have the "conntrackd" binary installed.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user