[dhcp_relay] Use dhcprelayd to manage critical processes (#17236)
Modify j2 template files in docker-dhcp-relay. Add dhcprelayd to group dhcp-relay instead of isc-dhcp-relay-VlanXXX, which would make dhcprelayd to become critical process. In dhcprelayd, subscribe FEATURE table to check whether dhcp_server feature is enabled. 2.1 If dhcp_server feature is disabled, means we need original dhcp_relay functionality, dhcprelayd would do nothing. Because dhcrelay/dhcpmon configuration is generated in supervisord configuration, they will automatically run. 2.2 If dhcp_server feature is enabled, dhcprelayd will stop dhcpmon/dhcrelay processes started by supervisord and subscribe dhcp_server related tables in config_db to start dhcpmon/dhcrelay processes. 2.3 While dhcprelayd running, it will regularly check feature status (by default per 5s) and would encounter below 4 state change about dhcp_server feature: A) disabled -> enabled In this scenario, dhcprelayd will subscribe dhcp_server related tables and stop dhcpmon/dhcrelay processes started by supervisord and start new pair of dhcpmon/dhcrelay processes. After this, dhcpmon/dhcrelay processes are totally managed by dhcprelayd. B) enabled -> enabled In this scenaro, dhcprelayd will monitor db changes in dhcp_server related tables to determine whether to restart dhcpmon/dhrelay processes. C) enabled -> disabled In this scenario, dhcprelayd would unsubscribe dhcp_server related tables and kill dhcpmon/dhcrelay processes started by itself. And then dhcprelayd will start dhcpmon/dhcrelay processes via supervisorctl. D) disabled -> disabled dhcprelayd will check whether dhcrelay processes running status consistent with supervisord configuration file. If they are not consistent, dhcprelayd will kill itself, then dhcp_relay container will stop because dhcprelayd is critical process.
This commit is contained in:
parent
49dd425603
commit
da80593ecb
@ -13,13 +13,11 @@ ENV IMAGE_VERSION=$image_version
|
||||
# Update apt's cache of available packages
|
||||
RUN apt-get update
|
||||
|
||||
RUN apt-get install -y libjsoncpp-dev {%- if INCLUDE_DHCP_SERVER == "y" %}\
|
||||
python3-dev \
|
||||
build-essential{%- endif %}
|
||||
RUN apt-get install -y libjsoncpp-dev \
|
||||
python3-dev \
|
||||
build-essential
|
||||
|
||||
{% if INCLUDE_DHCP_SERVER == "y" -%}
|
||||
RUN pip3 install psutil
|
||||
{%- endif %}
|
||||
|
||||
RUN apt-get install -y libjsoncpp-dev
|
||||
|
||||
@ -40,10 +38,8 @@ RUN apt-get install -y libjsoncpp-dev
|
||||
{% endif %}
|
||||
|
||||
# Clean up
|
||||
{% if INCLUDE_DHCP_SERVER == "y" -%}
|
||||
RUN apt-get remove -y build-essential \
|
||||
python3-dev
|
||||
{%- endif %}
|
||||
RUN apt-get clean -y && \
|
||||
apt-get autoclean -y && \
|
||||
apt-get autoremove -y && \
|
||||
|
@ -1,18 +1,10 @@
|
||||
[group:dhcp-relay]
|
||||
programs=
|
||||
programs=dhcprelayd,
|
||||
{%- set relay_for_ipv6 = { 'flag': False } %}
|
||||
{%- set add_preceding_comma = { 'flag': False } %}
|
||||
{% if dhcp_server_ipv4_enabled %}
|
||||
{% set _dummy = add_preceding_comma.update({'flag': True}) %}
|
||||
dhcprelayd
|
||||
{%- endif %}
|
||||
{% for vlan_name in VLAN_INTERFACE %}
|
||||
{# Append DHCPv4 agents #}
|
||||
{% if not dhcp_server_ipv4_enabled and VLAN and vlan_name in VLAN and 'dhcp_servers' in VLAN[vlan_name] and VLAN[vlan_name]['dhcp_servers']|length > 0 %}
|
||||
{% if add_preceding_comma.flag %},{% endif %}
|
||||
{% set _dummy = add_preceding_comma.update({'flag': True}) %}
|
||||
isc-dhcpv4-relay-{{ vlan_name }}
|
||||
{%- endif %}
|
||||
{% if DHCP_RELAY and vlan_name in DHCP_RELAY and DHCP_RELAY[vlan_name]['dhcpv6_servers']|length > 0 %}
|
||||
{% set _dummy = relay_for_ipv6.update({'flag': True}) %}
|
||||
{%- endif %}
|
||||
|
@ -39,10 +39,6 @@ stderr_logfile=syslog
|
||||
dependent_startup=true
|
||||
dependent_startup_wait_for=rsyslogd:running
|
||||
|
||||
{% set dhcp_server_ipv4_enabled = False %}
|
||||
{% if FEATURE and 'dhcp_server' in FEATURE and 'state' in FEATURE['dhcp_server'] and FEATURE['dhcp_server']['state'] == 'enabled' %}
|
||||
{% set dhcp_server_ipv4_enabled = True %}
|
||||
{% endif %}
|
||||
{# If our configuration has VLANs... #}
|
||||
{% if VLAN_INTERFACE %}
|
||||
{# Count how many VLANs require a DHCP relay agent... #}
|
||||
@ -63,16 +59,14 @@ dependent_startup_wait_for=rsyslogd:running
|
||||
{# Create a program entry for each DHCP relay agent instance #}
|
||||
{% set relay_for_ipv4 = { 'flag': False } %}
|
||||
{% set relay_for_ipv6 = { 'flag': False } %}
|
||||
{% if not dhcp_server_ipv4_enabled %}
|
||||
{% for vlan_name in VLAN_INTERFACE %}
|
||||
{% include 'dhcpv4-relay.agents.j2' %}
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
{% include 'dhcpv6-relay.agents.j2' %}
|
||||
{% if not dhcp_server_ipv4_enabled %}
|
||||
{% include 'dhcp-relay.monitors.j2' %}
|
||||
{% else %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
[program:dhcprelayd]
|
||||
command=/usr/local/bin/dhcprelayd
|
||||
priority=3
|
||||
@ -82,6 +76,3 @@ stdout_logfile=syslog
|
||||
stderr_logfile=syslog
|
||||
dependent_startup=true
|
||||
dependent_startup_wait_for=start:exited
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
@ -17,9 +17,7 @@ $(DOCKER_DHCP_RELAY)_LOAD_DOCKERS = $(DOCKER_CONFIG_ENGINE_BULLSEYE)
|
||||
|
||||
$(DOCKER_DHCP_RELAY)_INSTALL_PYTHON_WHEELS = $(SONIC_UTILITIES_PY3)
|
||||
$(DOCKER_DHCP_RELAY)_INSTALL_DEBS = $(PYTHON3_SWSSCOMMON)
|
||||
ifeq ($(INCLUDE_DHCP_SERVER), y)
|
||||
$(DOCKER_DHCP_RELAY)_PYTHON_WHEELS += $(SONIC_DHCP_SERVER_PY3)
|
||||
endif
|
||||
|
||||
$(DOCKER_DHCP_RELAY)_VERSION = 1.0.0
|
||||
$(DOCKER_DHCP_RELAY)_PACKAGE_NAME = dhcp-relay
|
||||
|
@ -5,6 +5,4 @@ $(SONIC_DHCP_SERVER_PY3)_SRC_PATH = $(SRC_PATH)/sonic-dhcp-server
|
||||
$(SONIC_DHCP_SERVER_PY3)_DEPENDS += $(SONIC_PY_COMMON_PY3)
|
||||
$(SONIC_DHCP_SERVER_PY3)_DEBS_DEPENDS = $(LIBSWSSCOMMON) $(PYTHON3_SWSSCOMMON)
|
||||
$(SONIC_DHCP_SERVER_PY3)_PYTHON_VERSION = 3
|
||||
ifeq ($(INCLUDE_DHCP_SERVER), y)
|
||||
SONIC_PYTHON_WHEELS += $(SONIC_DHCP_SERVER_PY3)
|
||||
endif
|
||||
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"FEATURE": {
|
||||
"dhcp_server": {
|
||||
"state": "enabled"
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
../py3/docker-dhcp-relay-enable-dhcp-server.supervisord.conf
|
@ -40,7 +40,7 @@ dependent_startup=true
|
||||
dependent_startup_wait_for=rsyslogd:running
|
||||
|
||||
[group:dhcp-relay]
|
||||
programs=isc-dhcpv4-relay-Vlan1000,dhcp6relay
|
||||
programs=dhcprelayd,dhcp6relay
|
||||
|
||||
[program:isc-dhcpv4-relay-Vlan1000]
|
||||
command=/usr/sbin/dhcrelay -d -m discard -a %%h:%%p %%P --name-alias-map-file /tmp/port-name-alias-map.txt -id Vlan1000 -iu Vlan2000 -iu PortChannel02 -iu PortChannel03 -iu PortChannel04 -iu PortChannel01 192.0.0.1 192.0.0.2
|
||||
@ -76,4 +76,12 @@ stderr_logfile=syslog
|
||||
dependent_startup=true
|
||||
dependent_startup_wait_for=isc-dhcpv4-relay-Vlan1000:running
|
||||
|
||||
|
||||
[program:dhcprelayd]
|
||||
command=/usr/local/bin/dhcprelayd
|
||||
priority=3
|
||||
autostart=false
|
||||
autorestart=false
|
||||
stdout_logfile=syslog
|
||||
stderr_logfile=syslog
|
||||
dependent_startup=true
|
||||
dependent_startup_wait_for=start:exited
|
||||
|
@ -40,7 +40,7 @@ dependent_startup=true
|
||||
dependent_startup_wait_for=rsyslogd:running
|
||||
|
||||
[group:dhcp-relay]
|
||||
programs=isc-dhcpv4-relay-Vlan1000,isc-dhcpv4-relay-Vlan2000,dhcp6relay
|
||||
programs=dhcprelayd,dhcp6relay
|
||||
|
||||
[program:isc-dhcpv4-relay-Vlan1000]
|
||||
command=/usr/sbin/dhcrelay -d -m discard -a %%h:%%p %%P --name-alias-map-file /tmp/port-name-alias-map.txt -id Vlan1000 -iu Vlan2000 -iu PortChannel02 -iu PortChannel03 -iu PortChannel04 -iu PortChannel01 192.0.0.1 192.0.0.2
|
||||
@ -96,4 +96,12 @@ stderr_logfile=syslog
|
||||
dependent_startup=true
|
||||
dependent_startup_wait_for=isc-dhcpv4-relay-Vlan2000:running
|
||||
|
||||
|
||||
[program:dhcprelayd]
|
||||
command=/usr/local/bin/dhcprelayd
|
||||
priority=3
|
||||
autostart=false
|
||||
autorestart=false
|
||||
stdout_logfile=syslog
|
||||
stderr_logfile=syslog
|
||||
dependent_startup=true
|
||||
dependent_startup_wait_for=start:exited
|
||||
|
@ -40,7 +40,7 @@ dependent_startup=true
|
||||
dependent_startup_wait_for=rsyslogd:running
|
||||
|
||||
[group:dhcp-relay]
|
||||
programs=isc-dhcpv4-relay-Vlan1000,dhcp6relay
|
||||
programs=dhcprelayd,dhcp6relay
|
||||
|
||||
[program:isc-dhcpv4-relay-Vlan1000]
|
||||
command=/usr/sbin/dhcrelay -d -m discard -a %%h:%%p %%P --name-alias-map-file /tmp/port-name-alias-map.txt -id Vlan1000 -iu Vlan2000 -iu PortChannel01 -iu PortChannel02 -iu PortChannel03 -iu PortChannel04 192.0.0.1 192.0.0.2
|
||||
@ -76,4 +76,12 @@ stderr_logfile=syslog
|
||||
dependent_startup=true
|
||||
dependent_startup_wait_for=isc-dhcpv4-relay-Vlan1000:running
|
||||
|
||||
|
||||
[program:dhcprelayd]
|
||||
command=/usr/local/bin/dhcprelayd
|
||||
priority=3
|
||||
autostart=false
|
||||
autorestart=false
|
||||
stdout_logfile=syslog
|
||||
stderr_logfile=syslog
|
||||
dependent_startup=true
|
||||
dependent_startup_wait_for=start:exited
|
||||
|
@ -40,7 +40,7 @@ dependent_startup=true
|
||||
dependent_startup_wait_for=rsyslogd:running
|
||||
|
||||
[group:dhcp-relay]
|
||||
programs=isc-dhcpv4-relay-Vlan1000,isc-dhcpv4-relay-Vlan2000,dhcp6relay
|
||||
programs=dhcprelayd,dhcp6relay
|
||||
|
||||
[program:isc-dhcpv4-relay-Vlan1000]
|
||||
command=/usr/sbin/dhcrelay -d -m discard -a %%h:%%p %%P --name-alias-map-file /tmp/port-name-alias-map.txt -id Vlan1000 -iu Vlan2000 -iu PortChannel01 -iu PortChannel02 -iu PortChannel03 -iu PortChannel04 192.0.0.1 192.0.0.2
|
||||
@ -96,4 +96,12 @@ stderr_logfile=syslog
|
||||
dependent_startup=true
|
||||
dependent_startup_wait_for=isc-dhcpv4-relay-Vlan2000:running
|
||||
|
||||
|
||||
[program:dhcprelayd]
|
||||
command=/usr/local/bin/dhcprelayd
|
||||
priority=3
|
||||
autostart=false
|
||||
autorestart=false
|
||||
stdout_logfile=syslog
|
||||
stderr_logfile=syslog
|
||||
dependent_startup=true
|
||||
dependent_startup_wait_for=start:exited
|
||||
|
@ -154,36 +154,16 @@ class TestJ2Files(TestCase):
|
||||
def test_dhcp_relay(self):
|
||||
# Test generation of wait_for_intf.sh
|
||||
dhc_sample_data = os.path.join(self.test_dir, "dhcp-relay-sample.json")
|
||||
enable_dhcp_server_sample_data = os.path.join(self.test_dir, "dhcp-relay-enable-dhcp-server-sample.json")
|
||||
template_path = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-dhcp-relay', 'wait_for_intf.sh.j2')
|
||||
argument = ['-m', self.t0_minigraph, '-j', dhc_sample_data, '-p', self.t0_port_config, '-t', template_path]
|
||||
self.run_script(argument, output_file=self.output_file)
|
||||
self.assertTrue(utils.cmp(os.path.join(self.test_dir, 'sample_output', utils.PYvX_DIR, 'wait_for_intf.sh'), self.output_file))
|
||||
|
||||
# Test generation of docker-dhcp-relay.supervisord.conf witout dhcp_server feature entry
|
||||
template_path = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-dhcp-relay', 'docker-dhcp-relay.supervisord.conf.j2')
|
||||
argument = ['-m', self.t0_minigraph, '-p', self.t0_port_config, '-t', template_path]
|
||||
self.run_script(argument, output_file=self.output_file)
|
||||
self.assertTrue(utils.cmp(os.path.join(self.test_dir, 'sample_output', utils.PYvX_DIR, 'docker-dhcp-relay.supervisord.conf'), self.output_file))
|
||||
|
||||
# Test generation of docker-dhcp-relay.supervisord.conf with disabled dhcp_server feature
|
||||
template_path = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-dhcp-relay',
|
||||
'docker-dhcp-relay.supervisord.conf.j2')
|
||||
argument = ['-m', self.t0_minigraph, '-j', dhc_sample_data, '-p', self.t0_port_config, '-t', template_path]
|
||||
self.run_script(argument, output_file=self.output_file)
|
||||
self.assertTrue(utils.cmp(os.path.join(self.test_dir, 'sample_output', utils.PYvX_DIR,
|
||||
'docker-dhcp-relay.supervisord.conf'), self.output_file))
|
||||
|
||||
# Test generation of docker-dhcp-relay.supervisord.conf with enabled dhcp_server feature
|
||||
template_path = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-dhcp-relay',
|
||||
'docker-dhcp-relay.supervisord.conf.j2')
|
||||
argument = ['-m', self.t0_minigraph, '-j', enable_dhcp_server_sample_data, '-p', self.t0_port_config, '-t',
|
||||
template_path]
|
||||
self.run_script(argument, output_file=self.output_file)
|
||||
self.assertTrue(utils.cmp(os.path.join(self.test_dir, 'sample_output', utils.PYvX_DIR,
|
||||
'docker-dhcp-relay-enable-dhcp-server.supervisord.conf'),
|
||||
self.output_file))
|
||||
|
||||
# Test generation of docker-dhcp-relay.supervisord.conf when a vlan is missing ip/ipv6 helpers
|
||||
template_path = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-dhcp-relay',
|
||||
'docker-dhcp-relay.supervisord.conf.j2')
|
||||
|
@ -12,6 +12,7 @@ DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS = "DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS"
|
||||
VLAN = "VLAN"
|
||||
VLAN_MEMBER = "VLAN_MEMBER"
|
||||
VLAN_INTERFACE = "VLAN_INTERFACE"
|
||||
FEATURE = "FEATURE"
|
||||
|
||||
|
||||
class ConfigDbEventChecker(object):
|
||||
@ -343,6 +344,60 @@ class VlanMemberTableEventChecker(ConfigDbEventChecker):
|
||||
return False
|
||||
|
||||
|
||||
class DhcpServerFeatureStateChecker(ConfigDbEventChecker):
|
||||
"""
|
||||
This event checker interested in dhcp_server feature state change in FEATURE table
|
||||
"""
|
||||
table_name = FEATURE
|
||||
|
||||
def __init__(self, sel, db):
|
||||
self.table_name = FEATURE
|
||||
ConfigDbEventChecker.__init__(self, sel, db)
|
||||
|
||||
def _get_parameter(self, db_snapshot):
|
||||
return ConfigDbEventChecker.get_parameter_by_name(db_snapshot, "dhcp_server_feature_enabled")
|
||||
|
||||
def _process_check(self, key, op, entry, dhcp_server_feature_enabled):
|
||||
if key != "dhcp_server":
|
||||
return False
|
||||
if op == "DEL":
|
||||
return dhcp_server_feature_enabled
|
||||
for field, value in entry:
|
||||
if field != "state":
|
||||
continue
|
||||
return value == "enabled" and not dhcp_server_feature_enabled or \
|
||||
value == "disabled" and dhcp_server_feature_enabled
|
||||
return False
|
||||
|
||||
|
||||
def _enable_monitor_checkers(checker_names, checker_dict):
|
||||
"""
|
||||
Enable checkers
|
||||
Args:
|
||||
checker_names: set of tables checker to be enable
|
||||
checker_dict: check_dict in monitor
|
||||
"""
|
||||
for checker in checker_names:
|
||||
if checker not in checker_dict:
|
||||
syslog.syslog(syslog.LOG_ERR, "Cannot find checker for {} in checker_dict".format(checker))
|
||||
continue
|
||||
checker_dict[checker].enable()
|
||||
|
||||
|
||||
def _disable_monitor_checkers(checker_names, checker_dict):
|
||||
"""
|
||||
Disable checkers
|
||||
Args:
|
||||
checker_names: set contains name of tables need to be disable
|
||||
checker_dict: check_dict in monitor
|
||||
"""
|
||||
for checker in checker_names:
|
||||
if checker not in checker_dict:
|
||||
syslog.syslog(syslog.LOG_ERR, "Cannot find checker for {} in checker_dict".format(checker))
|
||||
continue
|
||||
checker_dict[checker].disable()
|
||||
|
||||
|
||||
class DhcpRelaydDbMonitor(object):
|
||||
checker_dict = {}
|
||||
|
||||
@ -354,17 +409,21 @@ class DhcpRelaydDbMonitor(object):
|
||||
for checker in checkers:
|
||||
self.checker_dict[checker.get_class_name()] = checker
|
||||
|
||||
def enable_checker(self, checker_names):
|
||||
def enable_checkers(self, checker_names):
|
||||
"""
|
||||
Enable checkers
|
||||
Args:
|
||||
checker_names: set of tables checker to be enable
|
||||
"""
|
||||
for table in checker_names:
|
||||
if table not in self.checker_dict:
|
||||
syslog.syslog(syslog.LOG_ERR, "Cannot find checker for {} in checker_dict".format(table))
|
||||
continue
|
||||
self.checker_dict[table].enable()
|
||||
_enable_monitor_checkers(checker_names, self.checker_dict)
|
||||
|
||||
def disable_checkers(self, checker_names):
|
||||
"""
|
||||
Disable checkers
|
||||
Args:
|
||||
checker_names: set contains name of tables need to be disable
|
||||
"""
|
||||
_disable_monitor_checkers(checker_names, self.checker_dict)
|
||||
|
||||
def check_db_update(self, db_snapshot):
|
||||
"""
|
||||
@ -376,10 +435,13 @@ class DhcpRelaydDbMonitor(object):
|
||||
"""
|
||||
state, _ = self.sel.select(self.select_timeout)
|
||||
if state == swsscommon.Select.TIMEOUT or state != swsscommon.Select.OBJECT:
|
||||
return (False, False, False)
|
||||
return (self.checker_dict["DhcpServerTableIntfEnablementEventChecker"].check_update_event(db_snapshot),
|
||||
self.checker_dict["VlanTableEventChecker"].check_update_event(db_snapshot),
|
||||
self.checker_dict["VlanIntfTableEventChecker"].check_update_event(db_snapshot))
|
||||
return {}
|
||||
check_res = {}
|
||||
for name, checker in self.checker_dict.items():
|
||||
if not checker.is_enabled():
|
||||
continue
|
||||
check_res[name] = checker.check_update_event(db_snapshot)
|
||||
return check_res
|
||||
|
||||
|
||||
class DhcpServdDbMonitor(object):
|
||||
@ -399,11 +461,7 @@ class DhcpServdDbMonitor(object):
|
||||
Args:
|
||||
checker_names: set contains name of tables need to be disable
|
||||
"""
|
||||
for table in checker_names:
|
||||
if table not in self.checker_dict:
|
||||
syslog.syslog(syslog.LOG_ERR, "Cannot find checker for {} in checker_dict".format(table))
|
||||
continue
|
||||
self.checker_dict[table].disable()
|
||||
_disable_monitor_checkers(checker_names, self.checker_dict)
|
||||
|
||||
def enable_checkers(self, checker_names):
|
||||
"""
|
||||
@ -411,11 +469,7 @@ class DhcpServdDbMonitor(object):
|
||||
Args:
|
||||
checker_names: set contains name of tables need to be enable
|
||||
"""
|
||||
for table in checker_names:
|
||||
if table not in self.checker_dict:
|
||||
syslog.syslog(syslog.LOG_ERR, "Cannot find checker for {} in checker_dict".format(table))
|
||||
continue
|
||||
self.checker_dict[table].enable()
|
||||
_enable_monitor_checkers(checker_names, self.checker_dict)
|
||||
|
||||
def check_db_update(self, db_snapshot):
|
||||
"""
|
||||
|
@ -1,4 +1,5 @@
|
||||
import ipaddress
|
||||
import psutil
|
||||
import string
|
||||
from swsscommon import swsscommon
|
||||
|
||||
@ -147,3 +148,18 @@ def _parse_table_to_dict(table):
|
||||
new_entry[field] = value.split(",")
|
||||
ret[key] = new_entry
|
||||
return ret
|
||||
|
||||
|
||||
def get_target_process_cmds(process_name):
|
||||
"""
|
||||
Get running process cmds
|
||||
Args:
|
||||
process_name: name of process
|
||||
Returns:
|
||||
List of cmds list
|
||||
"""
|
||||
res = []
|
||||
for proc in psutil.process_iter():
|
||||
if proc.name() == process_name:
|
||||
res.append(proc.cmdline())
|
||||
return res
|
||||
|
@ -2,22 +2,27 @@
|
||||
# Currently if we run multiple dhcrelay processes, except for the last running process,
|
||||
# others will not relay dhcp_release packet.
|
||||
import psutil
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import syslog
|
||||
import time
|
||||
from swsscommon import swsscommon
|
||||
from dhcp_server.common.utils import DhcpDbConnector, terminate_proc
|
||||
from dhcp_server.common.utils import DhcpDbConnector, terminate_proc, get_target_process_cmds
|
||||
from dhcp_server.common.dhcp_db_monitor import DhcpRelaydDbMonitor, DhcpServerTableIntfEnablementEventChecker, \
|
||||
VlanTableEventChecker, VlanIntfTableEventChecker
|
||||
VlanTableEventChecker, VlanIntfTableEventChecker, DhcpServerFeatureStateChecker
|
||||
|
||||
REDIS_SOCK_PATH = "/var/run/redis/redis.sock"
|
||||
SUPERVISORD_CONF_PATH = "/etc/supervisor/conf.d/docker-dhcp-relay.supervisord.conf"
|
||||
DHCP_SERVER_IPV4_SERVER_IP = "DHCP_SERVER_IPV4_SERVER_IP"
|
||||
DHCP_SERVER_IPV4 = "DHCP_SERVER_IPV4"
|
||||
VLAN = "VLAN"
|
||||
DEFAULT_SELECT_TIMEOUT = 5000 # millisecond
|
||||
DHCP_SERVER_INTERFACE = "eth0"
|
||||
DEFAULT_CHECKER = ["DhcpServerTableIntfEnablementEventChecker", "VlanTableEventChecker", "VlanIntfTableEventChecker"]
|
||||
FEATURE_CHECKER = "DhcpServerFeatureStateChecker"
|
||||
DHCP_SERVER_CHECKER = "DhcpServerTableIntfEnablementEventChecker"
|
||||
VLAN_CHECKER = "VlanTableEventChecker"
|
||||
VLAN_INTF_CHECKER = "VlanIntfTableEventChecker"
|
||||
KILLED_OLD = 1
|
||||
NOT_KILLED = 2
|
||||
NOT_FOUND_PROC = 3
|
||||
@ -25,8 +30,11 @@ NOT_FOUND_PROC = 3
|
||||
|
||||
class DhcpRelayd(object):
|
||||
enabled_dhcp_interfaces = set()
|
||||
dhcp_server_feature_enabled = None
|
||||
dhcp_relay_supervisor_config = {}
|
||||
supervisord_conf_path = ""
|
||||
|
||||
def __init__(self, db_connector, db_monitor):
|
||||
def __init__(self, db_connector, db_monitor, supervisord_conf_path=SUPERVISORD_CONF_PATH):
|
||||
"""
|
||||
Args:
|
||||
db_connector: db connector obj
|
||||
@ -36,12 +44,21 @@ class DhcpRelayd(object):
|
||||
self.last_refresh_time = None
|
||||
self.dhcp_relayd_monitor = db_monitor
|
||||
self.enabled_dhcp_interfaces = set()
|
||||
self.dhcp_server_feature_enabled = None
|
||||
self.supervisord_conf_path = supervisord_conf_path
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Start function
|
||||
"""
|
||||
self.refresh_dhcrelay()
|
||||
self.dhcp_relay_supervisor_config = self._get_dhcp_relay_config()
|
||||
self.dhcp_server_feature_enabled = self._is_dhcp_server_enabled()
|
||||
# Sleep to wait dhcrelay process start
|
||||
time.sleep(5)
|
||||
if self.dhcp_server_feature_enabled:
|
||||
# If dhcp_server is enabled, need to stop related relay processes start by supervisord
|
||||
self._execute_supervisor_dhcp_relay_process("stop")
|
||||
self.dhcp_relayd_monitor.enable_checkers([DHCP_SERVER_CHECKER, VLAN_CHECKER, VLAN_INTF_CHECKER])
|
||||
|
||||
def refresh_dhcrelay(self, force_kill=False):
|
||||
"""
|
||||
@ -74,13 +91,112 @@ class DhcpRelayd(object):
|
||||
Wait function, check db change here
|
||||
"""
|
||||
while True:
|
||||
res = (self.dhcp_relayd_monitor.check_db_update({"enabled_dhcp_interfaces": self.enabled_dhcp_interfaces}))
|
||||
(dhcp_server_res, vlan_res, vlan_intf_res) = res
|
||||
# vlan ip change require kill old dhcp_relay related processes
|
||||
if vlan_intf_res:
|
||||
self.refresh_dhcrelay(True)
|
||||
elif dhcp_server_res or vlan_res:
|
||||
self.refresh_dhcrelay(False)
|
||||
check_param = {
|
||||
"enabled_dhcp_interfaces": self.enabled_dhcp_interfaces,
|
||||
"dhcp_server_feature_enabled": self.dhcp_server_feature_enabled
|
||||
}
|
||||
res = (self.dhcp_relayd_monitor.check_db_update(check_param))
|
||||
dhcp_feature_statue_changed = False
|
||||
if FEATURE_CHECKER in res:
|
||||
dhcp_feature_statue_changed = res[FEATURE_CHECKER]
|
||||
self.dhcp_server_feature_enabled = not self.dhcp_server_feature_enabled if dhcp_feature_statue_changed \
|
||||
else self.dhcp_server_feature_enabled
|
||||
# If dhcp_server feature is enabled, dhcprelayd will manage dhcpmon/dhcrelay process
|
||||
if self.dhcp_server_feature_enabled:
|
||||
# disabled -> enabled, we need to enable dhcp_server related checkers and do refresh processes
|
||||
if dhcp_feature_statue_changed:
|
||||
self.dhcp_relayd_monitor.enable_checkers([DHCP_SERVER_CHECKER, VLAN_CHECKER, VLAN_INTF_CHECKER])
|
||||
# Stop dhcrelay process
|
||||
self._execute_supervisor_dhcp_relay_process("stop")
|
||||
self.refresh_dhcrelay()
|
||||
# enabled -> enabled, just need to check dhcp_server related tables to see whether need to refresh
|
||||
else:
|
||||
# Check vlan_interface table change, if it changed, need to refresh with force kill
|
||||
if res.get(VLAN_INTF_CHECKER, False):
|
||||
self.refresh_dhcrelay(True)
|
||||
elif res.get(VLAN_CHECKER, False) or res.get(DHCP_SERVER_CHECKER, False):
|
||||
self.refresh_dhcrelay(False)
|
||||
|
||||
# If dhcp_server feature is disabled, dhcprelayd will checke whether dhcpmon/dhcrelay processes,
|
||||
# if they are not running as expected, dhcprelayd will kill itself to make dhcp_relay container restart.
|
||||
else:
|
||||
# enabled -> disabled, we need to disable dhcp_server related checkers and start dhcrelay/dhcpmon
|
||||
# processes follow supervisord configuration
|
||||
if dhcp_feature_statue_changed:
|
||||
self.dhcp_relayd_monitor.disable_checkers([DHCP_SERVER_CHECKER, VLAN_CHECKER, VLAN_INTF_CHECKER])
|
||||
self._kill_exist_relay_releated_process([], "dhcpmon", True)
|
||||
self._kill_exist_relay_releated_process([], "dhcrelay", True)
|
||||
self._execute_supervisor_dhcp_relay_process("start")
|
||||
# disabled -> disabled, to check whether dhcpmon/dhcrelay running status consistent with supervisord
|
||||
# configuration
|
||||
else:
|
||||
self._check_dhcp_relay_processes()
|
||||
|
||||
def _is_dhcp_server_enabled(self):
|
||||
"""
|
||||
Check whether dhcp_server feature is enabled via running config_db
|
||||
Returns:
|
||||
If dhcp_server feature is enabled, return True. Else, return False
|
||||
"""
|
||||
feature_table = self.db_connector.get_config_db_table("FEATURE")
|
||||
return feature_table.get("dhcp_server", {}).get("state", "disabled") == "enabled"
|
||||
|
||||
def _execute_supervisor_dhcp_relay_process(self, op):
|
||||
"""
|
||||
Start or stop relay releated processes managed by supervisord
|
||||
Args:
|
||||
op: string of operation, require to be "start" or "stop"
|
||||
"""
|
||||
if op not in ["stop", "start"]:
|
||||
syslog.syslog(syslog.LOG_ERR, "Error operation: {}".format(op))
|
||||
sys.exit(1)
|
||||
for program in self.dhcp_relay_supervisor_config.keys():
|
||||
cmds = ["supervisorctl", op, program]
|
||||
syslog.syslog(syslog.LOG_INFO, "Starting stop {} by: {}".format(program, cmds))
|
||||
res = subprocess.run(cmds, check=True)
|
||||
if res.returncode != 0:
|
||||
syslog.syslog(syslog.LOG_ERR, "Error in execute: {}".format(res))
|
||||
sys.exit(1)
|
||||
syslog.syslog(syslog.LOG_INFO, "Program {} stopped successfully".format(program))
|
||||
|
||||
def _check_dhcp_relay_processes(self):
|
||||
"""
|
||||
Check whether dhcrelay running as expected, if not, dhcprelayd will exit with code 1
|
||||
"""
|
||||
running_cmds = get_target_process_cmds("dhcrelay")
|
||||
running_cmds.sort()
|
||||
expected_cmds = [value for key, value in self.dhcp_relay_supervisor_config.items() if "isc-dhcpv4-relay" in key]
|
||||
expected_cmds.sort()
|
||||
if running_cmds != expected_cmds:
|
||||
syslog.syslog(syslog.LOG_ERR, "Running processes is not as expected! Runnning: {}. Expected: {}"
|
||||
.format(running_cmds, expected_cmds))
|
||||
sys.exit(1)
|
||||
|
||||
def _get_dhcp_relay_config(self):
|
||||
"""
|
||||
Get supervisord configuration for dhcrelay/dhcpmon
|
||||
Returns:
|
||||
Dict of cmds, sample:{
|
||||
'isc-dhcpv4-relay-Vlan1000': [
|
||||
'/usr/sbin/dhcrelay', '-d', '-m', 'discard', '-a', '%h:%p', '%P', '--name-alias-map-file',
|
||||
'/tmp/port-name-alias-map.txt', '-id', 'Vlan1000', '-iu', 'PortChannel101', '-iu',
|
||||
'PortChannel102', '-iu', 'PortChannel103', '-iu', 'PortChannel104', '192.0.0.1', '192.0.0.2',
|
||||
'192.0.0.3', '192.0.0.4'
|
||||
],
|
||||
'dhcpmon-Vlan1000': [
|
||||
'/usr/sbin/dhcpmon', '-id', 'Vlan1000', '-iu', 'PortChannel101', '-iu', 'PortChannel102', '-iu',
|
||||
'PortChannel103', '-iu', 'PortChannel104', '-im', 'eth0'
|
||||
]
|
||||
}
|
||||
"""
|
||||
res = {}
|
||||
with open(self.supervisord_conf_path, "r") as conf_file:
|
||||
content = conf_file.read()
|
||||
cmds = re.findall(r"\[program:((isc-dhcpv4-relay|dhcpmon)-.+)\]\ncommand=(.+)", content)
|
||||
for cmd in cmds:
|
||||
key = "dhcpmon:{}".format(cmd[0]) if "dhcpmon" in cmd[0] else cmd[0]
|
||||
res[key] = cmd[2].replace("%%", "%").split(" ")
|
||||
return res
|
||||
|
||||
def _start_dhcrelay_process(self, new_dhcp_interfaces, dhcp_server_ip, force_kill):
|
||||
# To check whether need to kill dhcrelay process
|
||||
@ -184,8 +300,9 @@ def main():
|
||||
checkers.append(DhcpServerTableIntfEnablementEventChecker(sel, dhcp_db_connector.config_db))
|
||||
checkers.append(VlanIntfTableEventChecker(sel, dhcp_db_connector.config_db))
|
||||
checkers.append(VlanTableEventChecker(sel, dhcp_db_connector.config_db))
|
||||
checkers.append(DhcpServerFeatureStateChecker(sel, dhcp_db_connector.config_db))
|
||||
db_monitor = DhcpRelaydDbMonitor(dhcp_db_connector, sel, checkers, DEFAULT_SELECT_TIMEOUT)
|
||||
db_monitor.enable_checker(DEFAULT_CHECKER)
|
||||
db_monitor.enable_checkers([FEATURE_CHECKER])
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector, db_monitor)
|
||||
dhcprelayd.start()
|
||||
dhcprelayd.wait()
|
||||
|
@ -108,3 +108,8 @@ def get_subscribe_table_tested_data(test_name):
|
||||
data["table"][i][2] = tuple(data["table"][i][2])
|
||||
data["table"][i] = tuple(data["table"][i])
|
||||
return tested_data
|
||||
|
||||
|
||||
class MockSubprocessRes(object):
|
||||
def __init__(self, returncode):
|
||||
self.returncode = returncode
|
||||
|
@ -242,5 +242,52 @@
|
||||
],
|
||||
"exp_res": false
|
||||
}
|
||||
],
|
||||
"test_feature_update": [
|
||||
{
|
||||
"table": [
|
||||
["dhcp_server", "SET", [["state", "disabled"]]]
|
||||
],
|
||||
"exp_res": {
|
||||
"pre_enabled": true,
|
||||
"pre_disabled": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["dhcp_server", "SET", [["state", "enabled"]]]
|
||||
],
|
||||
"exp_res": {
|
||||
"pre_enabled": false,
|
||||
"pre_disabled": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["dhcp_server", "SET", [["states", "enabled"]]]
|
||||
],
|
||||
"exp_res": {
|
||||
"pre_enabled": false,
|
||||
"pre_disabled": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["dhcp_server", "DEL", [[]]]
|
||||
],
|
||||
"exp_res": {
|
||||
"pre_enabled": true,
|
||||
"pre_disabled": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["dhcp_relay", "SET", [["state", "disabled"]]]
|
||||
],
|
||||
"exp_res": {
|
||||
"pre_enabled": false,
|
||||
"pre_disabled": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -39,11 +39,9 @@ stderr_logfile=syslog
|
||||
dependent_startup=true
|
||||
dependent_startup_wait_for=rsyslogd:running
|
||||
|
||||
[group:dhcp-relay]
|
||||
programs=dhcprelayd,dhcp6relay
|
||||
|
||||
[program:dhcp6relay]
|
||||
command=/usr/sbin/dhcp6relay
|
||||
[program:isc-dhcpv4-relay-Vlan1000]
|
||||
command=/usr/sbin/dhcrelay -d -m discard -a %%h:%%p %%P --name-alias-map-file /tmp/port-name-alias-map.txt -id Vlan1000 -iu PortChannel101 -iu PortChannel102 -iu PortChannel103 -iu PortChannel104 192.0.0.1 192.0.0.2 192.0.0.3 192.0.0.4
|
||||
priority=3
|
||||
autostart=false
|
||||
autorestart=false
|
||||
@ -60,5 +58,31 @@ autorestart=false
|
||||
stdout_logfile=syslog
|
||||
stderr_logfile=syslog
|
||||
dependent_startup=true
|
||||
dependent_startup_wait_for=isc-dhcpv4-relay-Vlan1000:running
|
||||
|
||||
[program:dhcp6relay]
|
||||
command=/usr/sbin/dhcp6relay
|
||||
priority=3
|
||||
autostart=false
|
||||
autorestart=false
|
||||
stdout_logfile=syslog
|
||||
stderr_logfile=syslog
|
||||
dependent_startup=true
|
||||
dependent_startup_wait_for=start:exited
|
||||
|
||||
[group:dhcp-relay]
|
||||
programs=dhcprelayd,dhcp6relay
|
||||
|
||||
|
||||
[group:dhcpmon]
|
||||
programs=dhcpmon-Vlan1000
|
||||
|
||||
[program:dhcpmon-Vlan1000]
|
||||
command=/usr/sbin/dhcpmon -id Vlan1000 -iu PortChannel101 -iu PortChannel102 -iu PortChannel103 -iu PortChannel104 -im eth0
|
||||
priority=4
|
||||
autostart=false
|
||||
autorestart=false
|
||||
stdout_logfile=syslog
|
||||
stderr_logfile=syslog
|
||||
dependent_startup=true
|
||||
dependent_startup_wait_for=isc-dhcpv4-relay-Vlan1000:running
|
@ -5,16 +5,18 @@ from common_utils import MockSubscribeTable, get_subscribe_table_tested_data, \
|
||||
from dhcp_server.common.dhcp_db_monitor import DhcpRelaydDbMonitor, DhcpServdDbMonitor, ConfigDbEventChecker, \
|
||||
DhcpServerTableIntfEnablementEventChecker, DhcpServerTableCfgChangeEventChecker, \
|
||||
DhcpPortTableEventChecker, DhcpRangeTableEventChecker, DhcpOptionTableEventChecker, \
|
||||
VlanTableEventChecker, VlanMemberTableEventChecker, VlanIntfTableEventChecker
|
||||
VlanTableEventChecker, VlanMemberTableEventChecker, VlanIntfTableEventChecker, DhcpServerFeatureStateChecker
|
||||
from dhcp_server.common.utils import DhcpDbConnector
|
||||
from swsscommon import swsscommon
|
||||
from unittest.mock import patch, ANY, PropertyMock, MagicMock
|
||||
|
||||
|
||||
@pytest.mark.parametrize("checker_enabled", [True, False])
|
||||
@pytest.mark.parametrize("select_result", [swsscommon.Select.TIMEOUT, swsscommon.Select.OBJECT])
|
||||
def test_dhcp_relayd_monitor_check_db_update(mock_swsscommon_dbconnector_init, select_result):
|
||||
def test_dhcp_relayd_monitor_check_db_update(mock_swsscommon_dbconnector_init, select_result, checker_enabled):
|
||||
with patch.object(DhcpServerTableIntfEnablementEventChecker, "check_update_event") \
|
||||
as mock_check_update_event, \
|
||||
patch.object(ConfigDbEventChecker, "is_enabled", return_value=checker_enabled), \
|
||||
patch.object(VlanTableEventChecker, "check_update_event") as mock_check_vlan_update, \
|
||||
patch.object(VlanIntfTableEventChecker, "check_update_event") as mock_check_vlan_intf_update, \
|
||||
patch.object(swsscommon.Select, "select", return_value=(select_result, None)), \
|
||||
@ -25,13 +27,13 @@ def test_dhcp_relayd_monitor_check_db_update(mock_swsscommon_dbconnector_init, s
|
||||
dhcp_relayd_db_monitor = DhcpRelaydDbMonitor(db_connector, swsscommon.Select(), checkers)
|
||||
tested_db_snapshot = {"enabled_dhcp_interfaces": "dummy"}
|
||||
dhcp_relayd_db_monitor.check_db_update(tested_db_snapshot)
|
||||
if select_result == swsscommon.Select.OBJECT:
|
||||
mock_check_update_event.assert_called_once_with(tested_db_snapshot)
|
||||
if select_result == swsscommon.Select.OBJECT and checker_enabled:
|
||||
mock_check_vlan_update.assert_called_once_with(tested_db_snapshot)
|
||||
mock_check_update_event.assert_called_once_with(tested_db_snapshot)
|
||||
mock_check_vlan_intf_update.assert_called_once_with(tested_db_snapshot)
|
||||
else:
|
||||
mock_check_update_event.assert_not_called()
|
||||
mock_check_vlan_update.assert_not_called()
|
||||
mock_check_update_event.assert_not_called()
|
||||
mock_check_vlan_intf_update.assert_not_called()
|
||||
|
||||
|
||||
@ -40,13 +42,27 @@ def test_dhcp_relayd_enable_checker(tables, mock_swsscommon_dbconnector_init):
|
||||
with patch.object(ConfigDbEventChecker, "enable") as mock_enable:
|
||||
db_connector = DhcpDbConnector()
|
||||
dhcp_relayd_db_monitor = DhcpRelaydDbMonitor(db_connector, None, [VlanTableEventChecker(None, None)])
|
||||
dhcp_relayd_db_monitor.enable_checker(set(tables))
|
||||
dhcp_relayd_db_monitor.enable_checkers(set(tables))
|
||||
if "VlanTableEventChecker" in tables:
|
||||
mock_enable.assert_called_once()
|
||||
else:
|
||||
mock_enable.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tables", [["VlanTableEventChecker"], ["dummy"]])
|
||||
def test_dhcp_relayd_disable_checker(tables, mock_swsscommon_dbconnector_init):
|
||||
with patch.object(ConfigDbEventChecker, "disable") as mock_disable:
|
||||
db_connector = DhcpDbConnector()
|
||||
checker = VlanTableEventChecker(None, None)
|
||||
checker.is_enabled = True
|
||||
dhcp_relayd_db_monitor = DhcpRelaydDbMonitor(db_connector, None, [checker])
|
||||
dhcp_relayd_db_monitor.disable_checkers(set(tables))
|
||||
if "VlanTableEventChecker" in tables:
|
||||
mock_disable.assert_called_once()
|
||||
else:
|
||||
mock_disable.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("select_result", [swsscommon.Select.TIMEOUT, swsscommon.Select.OBJECT])
|
||||
@pytest.mark.parametrize("is_checker_enabled", [True, False])
|
||||
def test_dhcp_servd_monitor_check_db_update(mock_swsscommon_dbconnector_init, select_result,
|
||||
@ -349,3 +365,24 @@ def test_vlan_member_table_checker(mock_swsscommon_dbconnector_init, tested_data
|
||||
assert check_res
|
||||
else:
|
||||
assert expected_res == check_res
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tested_db_snapshot", [{"dhcp_server_feature_enabled": True},
|
||||
{"dhcp_server_feature_enabled": False}, {}])
|
||||
@pytest.mark.parametrize("tested_data", get_subscribe_table_tested_data("test_feature_update"))
|
||||
@pytest.mark.parametrize("enabled", [True, False])
|
||||
def test_feature_table_checker(mock_swsscommon_dbconnector_init, tested_data, tested_db_snapshot, enabled):
|
||||
with patch.object(ConfigDbEventChecker, "enable"), \
|
||||
patch.object(ConfigDbEventChecker, "subscriber_state_table",
|
||||
return_value=MockSubscribeTable(tested_data["table"]), new_callable=PropertyMock), \
|
||||
patch.object(ConfigDbEventChecker, "enabled", return_value=enabled, new_callable=PropertyMock), \
|
||||
patch.object(sys, "exit"):
|
||||
sel = swsscommon.Select()
|
||||
db_event_checker = DhcpServerFeatureStateChecker(sel, MagicMock())
|
||||
check_res = db_event_checker.check_update_event(tested_db_snapshot)
|
||||
if "dhcp_server_feature_enabled" not in tested_db_snapshot:
|
||||
assert check_res
|
||||
else:
|
||||
expected_res = tested_data["exp_res"]["pre_enabled"] if tested_db_snapshot["dhcp_server_feature_enabled"] \
|
||||
else tested_data["exp_res"]["pre_disabled"]
|
||||
assert expected_res == check_res
|
||||
|
@ -3,21 +3,32 @@ import pytest
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from common_utils import mock_get_config_db_table, MockProc, MockPopen
|
||||
from common_utils import mock_get_config_db_table, MockProc, MockPopen, MockSubprocessRes, mock_exit_func
|
||||
from dhcp_server.common.utils import DhcpDbConnector
|
||||
from dhcp_server.common.dhcp_db_monitor import ConfigDbEventChecker
|
||||
from dhcp_server.common.dhcp_db_monitor import ConfigDbEventChecker, DhcpRelaydDbMonitor
|
||||
from dhcp_server.dhcprelayd.dhcprelayd import DhcpRelayd, KILLED_OLD, NOT_KILLED, NOT_FOUND_PROC
|
||||
from swsscommon import swsscommon
|
||||
from unittest.mock import patch, call
|
||||
from unittest.mock import patch, call, ANY, PropertyMock
|
||||
|
||||
|
||||
def test_start(mock_swsscommon_dbconnector_init):
|
||||
with patch.object(DhcpRelayd, "refresh_dhcrelay", return_value=None) as mock_refresh, \
|
||||
patch.object(ConfigDbEventChecker, "enable"):
|
||||
@pytest.mark.parametrize("dhcp_server_enabled", [True, False])
|
||||
def test_start(mock_swsscommon_dbconnector_init, dhcp_server_enabled):
|
||||
with patch.object(DhcpRelayd, "_get_dhcp_relay_config") as mock_get_config, \
|
||||
patch.object(DhcpRelayd, "_is_dhcp_server_enabled", return_value=dhcp_server_enabled) as mock_enabled, \
|
||||
patch.object(DhcpRelayd, "_execute_supervisor_dhcp_relay_process") as mock_execute, \
|
||||
patch.object(DhcpRelaydDbMonitor, "enable_checkers") as mock_enable_checkers, \
|
||||
patch.object(time, "sleep"):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector, None)
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector, DhcpRelaydDbMonitor)
|
||||
dhcprelayd.start()
|
||||
mock_refresh.assert_called_once_with()
|
||||
mock_get_config.assert_called_once_with()
|
||||
mock_enabled.assert_called_once_with()
|
||||
if dhcp_server_enabled:
|
||||
mock_execute.assert_called_once_with("stop")
|
||||
mock_enable_checkers.assert_called_once_with([ANY, ANY, ANY])
|
||||
else:
|
||||
mock_execute.assert_not_called()
|
||||
mock_enable_checkers.assert_not_called()
|
||||
|
||||
|
||||
def test_refresh_dhcrelay(mock_swsscommon_dbconnector_init):
|
||||
@ -131,3 +142,93 @@ def test_get_dhcp_server_ip(mock_swsscommon_dbconnector_init, mock_swsscommon_ta
|
||||
else:
|
||||
mock_exit.assert_called_once_with(1)
|
||||
mock_sleep.assert_has_calls([call(10) for _ in range(10)])
|
||||
|
||||
|
||||
tested_feature_table = [
|
||||
{
|
||||
"dhcp_server": {
|
||||
"delayed": "True"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dhcp_server": {
|
||||
"state": "enabled"
|
||||
}
|
||||
},
|
||||
{
|
||||
"dhcp_server": {
|
||||
"state": "disabled"
|
||||
}
|
||||
},
|
||||
{}
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("feature_table", tested_feature_table)
|
||||
def test_is_dhcp_server_enabled(mock_swsscommon_dbconnector_init, mock_swsscommon_table_init, feature_table):
|
||||
with patch.object(DhcpDbConnector, "get_config_db_table", return_value=feature_table):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector, None)
|
||||
res = dhcprelayd._is_dhcp_server_enabled()
|
||||
if "dhcp_server" in feature_table and "state" in feature_table["dhcp_server"] and \
|
||||
feature_table["dhcp_server"]["state"] == "enabled":
|
||||
assert res
|
||||
else:
|
||||
assert not res
|
||||
|
||||
|
||||
@pytest.mark.parametrize("op", ["stop", "start", "starts"])
|
||||
@pytest.mark.parametrize("return_code", [0, -1])
|
||||
def test_execute_supervisor_dhcp_relay_process(mock_swsscommon_dbconnector_init, mock_swsscommon_table_init, op,
|
||||
return_code):
|
||||
with patch.object(sys, "exit", side_effect=mock_exit_func) as mock_exit, \
|
||||
patch.object(subprocess, "run", return_value=MockSubprocessRes(return_code)) as mock_run, \
|
||||
patch.object(DhcpRelayd, "dhcp_relay_supervisor_config", return_value={"dhcpmon-Vlan1000": ""},
|
||||
new_callable=PropertyMock):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector, None)
|
||||
try:
|
||||
dhcprelayd._execute_supervisor_dhcp_relay_process(op)
|
||||
except SystemExit:
|
||||
mock_exit.assert_called_once_with(1)
|
||||
assert op == "starts" or return_code != 0
|
||||
else:
|
||||
mock_run.assert_called_once_with(["supervisorctl", op, "dhcpmon-Vlan1000"], check=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("target_cmds", [[["/usr/bin/dhcrelay"]], [["/usr/bin/dhcpmon"]]])
|
||||
def test_check_dhcp_relay_process(mock_swsscommon_dbconnector_init, mock_swsscommon_table_init, target_cmds):
|
||||
exp_config = {"isc-dhcpv4-relay-Vlan1000": ["/usr/bin/dhcrelay"]}
|
||||
with patch("dhcp_server.dhcprelayd.dhcprelayd.get_target_process_cmds", return_value=target_cmds), \
|
||||
patch.object(DhcpRelayd, "dhcp_relay_supervisor_config",
|
||||
return_value=exp_config, new_callable=PropertyMock), \
|
||||
patch.object(sys, "exit", mock_exit_func):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector, None)
|
||||
exp_cmds = [value for key, value in exp_config.items() if "isc-dhcpv4-relay" in key]
|
||||
exp_cmds.sort()
|
||||
try:
|
||||
dhcprelayd._check_dhcp_relay_processes()
|
||||
except SystemExit:
|
||||
assert exp_cmds != target_cmds
|
||||
else:
|
||||
assert exp_cmds == target_cmds
|
||||
|
||||
|
||||
def test_get_dhcp_relay_config(mock_swsscommon_dbconnector_init, mock_swsscommon_table_init):
|
||||
with patch.object(DhcpRelayd, "supervisord_conf_path", return_value="tests/test_data/supervisor.conf",
|
||||
new_callable=PropertyMock):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector, None)
|
||||
res = dhcprelayd._get_dhcp_relay_config()
|
||||
assert res == {
|
||||
"isc-dhcpv4-relay-Vlan1000": [
|
||||
"/usr/sbin/dhcrelay", "-d", "-m", "discard", "-a", "%h:%p", "%P", "--name-alias-map-file",
|
||||
"/tmp/port-name-alias-map.txt", "-id", "Vlan1000", "-iu", "PortChannel101", "-iu", "PortChannel102",
|
||||
"-iu", "PortChannel103", "-iu", "PortChannel104", "192.0.0.1", "192.0.0.2", "192.0.0.3", "192.0.0.4"
|
||||
],
|
||||
"dhcpmon:dhcpmon-Vlan1000": [
|
||||
"/usr/sbin/dhcpmon", "-id", "Vlan1000", "-iu", "PortChannel101", "-iu", "PortChannel102", "-iu",
|
||||
"PortChannel103", "-iu", "PortChannel104", "-im", "eth0"
|
||||
]
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
import dhcp_server.common.utils as utils
|
||||
import ipaddress
|
||||
import psutil
|
||||
import pytest
|
||||
from swsscommon import swsscommon
|
||||
from unittest.mock import patch, call
|
||||
from common_utils import MockProc
|
||||
from unittest.mock import patch, call, PropertyMock
|
||||
|
||||
interval_test_data = {
|
||||
"ordered_with_overlap": {
|
||||
@ -137,3 +139,8 @@ def convert_ip_address_intervals(intervals):
|
||||
def test_validate_ttr_type(test_data):
|
||||
res = utils.validate_str_type(test_data[0], test_data[1])
|
||||
assert res == test_data[2]
|
||||
|
||||
|
||||
def test_get_target_process_cmds():
|
||||
with patch.object(psutil, "process_iter", return_value=[MockProc("dhcrelay", 1), MockProc("dhcpmon", 2)], new_callable=PropertyMock):
|
||||
res = utils.get_target_process_cmds("dhcrelay")
|
Loading…
Reference in New Issue
Block a user