[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:
Yaqiang Zhu 2023-11-27 12:30:01 -05:00 committed by Ying Xie
parent e86ceaac90
commit f48e8b61cf
21 changed files with 506 additions and 119 deletions

View File

@ -13,13 +13,11 @@ ENV IMAGE_VERSION=$image_version
# Update apt's cache of available packages # Update apt's cache of available packages
RUN apt-get update RUN apt-get update
RUN apt-get install -y libjsoncpp-dev {%- if INCLUDE_DHCP_SERVER == "y" %}\ RUN apt-get install -y libjsoncpp-dev \
python3-dev \ python3-dev \
build-essential{%- endif %} build-essential
{% if INCLUDE_DHCP_SERVER == "y" -%}
RUN pip3 install psutil RUN pip3 install psutil
{%- endif %}
RUN apt-get install -y libjsoncpp-dev RUN apt-get install -y libjsoncpp-dev
@ -40,10 +38,8 @@ RUN apt-get install -y libjsoncpp-dev
{% endif %} {% endif %}
# Clean up # Clean up
{% if INCLUDE_DHCP_SERVER == "y" -%}
RUN apt-get remove -y build-essential \ RUN apt-get remove -y build-essential \
python3-dev python3-dev
{%- endif %}
RUN apt-get clean -y && \ RUN apt-get clean -y && \
apt-get autoclean -y && \ apt-get autoclean -y && \
apt-get autoremove -y && \ apt-get autoremove -y && \

View File

@ -1,18 +1,10 @@
[group:dhcp-relay] [group:dhcp-relay]
programs= programs=dhcprelayd,
{%- set relay_for_ipv6 = { 'flag': False } %} {%- set relay_for_ipv6 = { 'flag': False } %}
{%- set add_preceding_comma = { 'flag': False } %} {%- set add_preceding_comma = { 'flag': False } %}
{% if dhcp_server_ipv4_enabled %} {% if dhcp_server_ipv4_enabled %}
{% set _dummy = add_preceding_comma.update({'flag': True}) %}
dhcprelayd
{%- endif %} {%- endif %}
{% for vlan_name in VLAN_INTERFACE %} {% 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 %} {% 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}) %} {% set _dummy = relay_for_ipv6.update({'flag': True}) %}
{%- endif %} {%- endif %}

View File

@ -39,10 +39,6 @@ stderr_logfile=syslog
dependent_startup=true dependent_startup=true
dependent_startup_wait_for=rsyslogd:running 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 our configuration has VLANs... #}
{% if VLAN_INTERFACE %} {% if VLAN_INTERFACE %}
{# Count how many VLANs require a DHCP relay agent... #} {# 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 #} {# Create a program entry for each DHCP relay agent instance #}
{% set relay_for_ipv4 = { 'flag': False } %} {% set relay_for_ipv4 = { 'flag': False } %}
{% set relay_for_ipv6 = { 'flag': False } %} {% set relay_for_ipv6 = { 'flag': False } %}
{% if not dhcp_server_ipv4_enabled %}
{% for vlan_name in VLAN_INTERFACE %} {% for vlan_name in VLAN_INTERFACE %}
{% include 'dhcpv4-relay.agents.j2' %} {% include 'dhcpv4-relay.agents.j2' %}
{% endfor %} {% endfor %}
{% endif %}
{% include 'dhcpv6-relay.agents.j2' %} {% include 'dhcpv6-relay.agents.j2' %}
{% if not dhcp_server_ipv4_enabled %}
{% include 'dhcp-relay.monitors.j2' %} {% include 'dhcp-relay.monitors.j2' %}
{% else %} {% endif %}
{% endif %}
[program:dhcprelayd] [program:dhcprelayd]
command=/usr/local/bin/dhcprelayd command=/usr/local/bin/dhcprelayd
priority=3 priority=3
@ -82,6 +76,3 @@ stdout_logfile=syslog
stderr_logfile=syslog stderr_logfile=syslog
dependent_startup=true dependent_startup=true
dependent_startup_wait_for=start:exited dependent_startup_wait_for=start:exited
{% endif %}
{% endif %}
{% endif %}

View File

@ -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_PYTHON_WHEELS = $(SONIC_UTILITIES_PY3)
$(DOCKER_DHCP_RELAY)_INSTALL_DEBS = $(PYTHON3_SWSSCOMMON) $(DOCKER_DHCP_RELAY)_INSTALL_DEBS = $(PYTHON3_SWSSCOMMON)
ifeq ($(INCLUDE_DHCP_SERVER), y)
$(DOCKER_DHCP_RELAY)_PYTHON_WHEELS += $(SONIC_DHCP_SERVER_PY3) $(DOCKER_DHCP_RELAY)_PYTHON_WHEELS += $(SONIC_DHCP_SERVER_PY3)
endif
$(DOCKER_DHCP_RELAY)_VERSION = 1.0.0 $(DOCKER_DHCP_RELAY)_VERSION = 1.0.0
$(DOCKER_DHCP_RELAY)_PACKAGE_NAME = dhcp-relay $(DOCKER_DHCP_RELAY)_PACKAGE_NAME = dhcp-relay

View File

@ -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)_DEPENDS += $(SONIC_PY_COMMON_PY3)
$(SONIC_DHCP_SERVER_PY3)_DEBS_DEPENDS = $(LIBSWSSCOMMON) $(PYTHON3_SWSSCOMMON) $(SONIC_DHCP_SERVER_PY3)_DEBS_DEPENDS = $(LIBSWSSCOMMON) $(PYTHON3_SWSSCOMMON)
$(SONIC_DHCP_SERVER_PY3)_PYTHON_VERSION = 3 $(SONIC_DHCP_SERVER_PY3)_PYTHON_VERSION = 3
ifeq ($(INCLUDE_DHCP_SERVER), y)
SONIC_PYTHON_WHEELS += $(SONIC_DHCP_SERVER_PY3) SONIC_PYTHON_WHEELS += $(SONIC_DHCP_SERVER_PY3)
endif

View File

@ -1,7 +0,0 @@
{
"FEATURE": {
"dhcp_server": {
"state": "enabled"
}
}
}

View File

@ -1 +0,0 @@
../py3/docker-dhcp-relay-enable-dhcp-server.supervisord.conf

View File

@ -40,7 +40,7 @@ dependent_startup=true
dependent_startup_wait_for=rsyslogd:running dependent_startup_wait_for=rsyslogd:running
[group:dhcp-relay] [group:dhcp-relay]
programs=isc-dhcpv4-relay-Vlan1000,dhcp6relay programs=dhcprelayd,dhcp6relay
[program:isc-dhcpv4-relay-Vlan1000] [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 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=true
dependent_startup_wait_for=isc-dhcpv4-relay-Vlan1000:running 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

View File

@ -40,7 +40,7 @@ dependent_startup=true
dependent_startup_wait_for=rsyslogd:running dependent_startup_wait_for=rsyslogd:running
[group:dhcp-relay] [group:dhcp-relay]
programs=isc-dhcpv4-relay-Vlan1000,isc-dhcpv4-relay-Vlan2000,dhcp6relay programs=dhcprelayd,dhcp6relay
[program:isc-dhcpv4-relay-Vlan1000] [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 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=true
dependent_startup_wait_for=isc-dhcpv4-relay-Vlan2000:running 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

View File

@ -40,7 +40,7 @@ dependent_startup=true
dependent_startup_wait_for=rsyslogd:running dependent_startup_wait_for=rsyslogd:running
[group:dhcp-relay] [group:dhcp-relay]
programs=isc-dhcpv4-relay-Vlan1000,dhcp6relay programs=dhcprelayd,dhcp6relay
[program:isc-dhcpv4-relay-Vlan1000] [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 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=true
dependent_startup_wait_for=isc-dhcpv4-relay-Vlan1000:running 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

View File

@ -40,7 +40,7 @@ dependent_startup=true
dependent_startup_wait_for=rsyslogd:running dependent_startup_wait_for=rsyslogd:running
[group:dhcp-relay] [group:dhcp-relay]
programs=isc-dhcpv4-relay-Vlan1000,isc-dhcpv4-relay-Vlan2000,dhcp6relay programs=dhcprelayd,dhcp6relay
[program:isc-dhcpv4-relay-Vlan1000] [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 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=true
dependent_startup_wait_for=isc-dhcpv4-relay-Vlan2000:running 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

View File

@ -154,36 +154,16 @@ class TestJ2Files(TestCase):
def test_dhcp_relay(self): def test_dhcp_relay(self):
# Test generation of wait_for_intf.sh # Test generation of wait_for_intf.sh
dhc_sample_data = os.path.join(self.test_dir, "dhcp-relay-sample.json") 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') 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] 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.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)) 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') 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] argument = ['-m', self.t0_minigraph, '-p', self.t0_port_config, '-t', template_path]
self.run_script(argument, output_file=self.output_file) 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)) 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 # 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', template_path = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-dhcp-relay',
'docker-dhcp-relay.supervisord.conf.j2') 'docker-dhcp-relay.supervisord.conf.j2')

View File

@ -12,6 +12,7 @@ DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS = "DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS"
VLAN = "VLAN" VLAN = "VLAN"
VLAN_MEMBER = "VLAN_MEMBER" VLAN_MEMBER = "VLAN_MEMBER"
VLAN_INTERFACE = "VLAN_INTERFACE" VLAN_INTERFACE = "VLAN_INTERFACE"
FEATURE = "FEATURE"
class ConfigDbEventChecker(object): class ConfigDbEventChecker(object):
@ -343,6 +344,60 @@ class VlanMemberTableEventChecker(ConfigDbEventChecker):
return False 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): class DhcpRelaydDbMonitor(object):
checker_dict = {} checker_dict = {}
@ -354,17 +409,21 @@ class DhcpRelaydDbMonitor(object):
for checker in checkers: for checker in checkers:
self.checker_dict[checker.get_class_name()] = checker self.checker_dict[checker.get_class_name()] = checker
def enable_checker(self, checker_names): def enable_checkers(self, checker_names):
""" """
Enable checkers Enable checkers
Args: Args:
checker_names: set of tables checker to be enable checker_names: set of tables checker to be enable
""" """
for table in checker_names: _enable_monitor_checkers(checker_names, self.checker_dict)
if table not in self.checker_dict:
syslog.syslog(syslog.LOG_ERR, "Cannot find checker for {} in checker_dict".format(table)) def disable_checkers(self, checker_names):
continue """
self.checker_dict[table].enable() 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): def check_db_update(self, db_snapshot):
""" """
@ -376,10 +435,13 @@ class DhcpRelaydDbMonitor(object):
""" """
state, _ = self.sel.select(self.select_timeout) state, _ = self.sel.select(self.select_timeout)
if state == swsscommon.Select.TIMEOUT or state != swsscommon.Select.OBJECT: if state == swsscommon.Select.TIMEOUT or state != swsscommon.Select.OBJECT:
return (False, False, False) return {}
return (self.checker_dict["DhcpServerTableIntfEnablementEventChecker"].check_update_event(db_snapshot), check_res = {}
self.checker_dict["VlanTableEventChecker"].check_update_event(db_snapshot), for name, checker in self.checker_dict.items():
self.checker_dict["VlanIntfTableEventChecker"].check_update_event(db_snapshot)) if not checker.is_enabled():
continue
check_res[name] = checker.check_update_event(db_snapshot)
return check_res
class DhcpServdDbMonitor(object): class DhcpServdDbMonitor(object):
@ -399,11 +461,7 @@ class DhcpServdDbMonitor(object):
Args: Args:
checker_names: set contains name of tables need to be disable checker_names: set contains name of tables need to be disable
""" """
for table in checker_names: _disable_monitor_checkers(checker_names, self.checker_dict)
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()
def enable_checkers(self, checker_names): def enable_checkers(self, checker_names):
""" """
@ -411,11 +469,7 @@ class DhcpServdDbMonitor(object):
Args: Args:
checker_names: set contains name of tables need to be enable checker_names: set contains name of tables need to be enable
""" """
for table in checker_names: _enable_monitor_checkers(checker_names, self.checker_dict)
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()
def check_db_update(self, db_snapshot): def check_db_update(self, db_snapshot):
""" """

View File

@ -1,4 +1,5 @@
import ipaddress import ipaddress
import psutil
import string import string
from swsscommon import swsscommon from swsscommon import swsscommon
@ -147,3 +148,18 @@ def _parse_table_to_dict(table):
new_entry[field] = value.split(",") new_entry[field] = value.split(",")
ret[key] = new_entry ret[key] = new_entry
return ret 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

View File

@ -2,22 +2,27 @@
# Currently if we run multiple dhcrelay processes, except for the last running process, # Currently if we run multiple dhcrelay processes, except for the last running process,
# others will not relay dhcp_release packet. # others will not relay dhcp_release packet.
import psutil import psutil
import re
import subprocess import subprocess
import sys import sys
import syslog import syslog
import time import time
from swsscommon import swsscommon 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, \ from dhcp_server.common.dhcp_db_monitor import DhcpRelaydDbMonitor, DhcpServerTableIntfEnablementEventChecker, \
VlanTableEventChecker, VlanIntfTableEventChecker VlanTableEventChecker, VlanIntfTableEventChecker, DhcpServerFeatureStateChecker
REDIS_SOCK_PATH = "/var/run/redis/redis.sock" 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_SERVER_IP = "DHCP_SERVER_IPV4_SERVER_IP"
DHCP_SERVER_IPV4 = "DHCP_SERVER_IPV4" DHCP_SERVER_IPV4 = "DHCP_SERVER_IPV4"
VLAN = "VLAN" VLAN = "VLAN"
DEFAULT_SELECT_TIMEOUT = 5000 # millisecond DEFAULT_SELECT_TIMEOUT = 5000 # millisecond
DHCP_SERVER_INTERFACE = "eth0" 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 KILLED_OLD = 1
NOT_KILLED = 2 NOT_KILLED = 2
NOT_FOUND_PROC = 3 NOT_FOUND_PROC = 3
@ -25,8 +30,11 @@ NOT_FOUND_PROC = 3
class DhcpRelayd(object): class DhcpRelayd(object):
enabled_dhcp_interfaces = set() 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: Args:
db_connector: db connector obj db_connector: db connector obj
@ -36,12 +44,21 @@ class DhcpRelayd(object):
self.last_refresh_time = None self.last_refresh_time = None
self.dhcp_relayd_monitor = db_monitor self.dhcp_relayd_monitor = db_monitor
self.enabled_dhcp_interfaces = set() self.enabled_dhcp_interfaces = set()
self.dhcp_server_feature_enabled = None
self.supervisord_conf_path = supervisord_conf_path
def start(self): def start(self):
""" """
Start function 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): def refresh_dhcrelay(self, force_kill=False):
""" """
@ -74,13 +91,112 @@ class DhcpRelayd(object):
Wait function, check db change here Wait function, check db change here
""" """
while True: while True:
res = (self.dhcp_relayd_monitor.check_db_update({"enabled_dhcp_interfaces": self.enabled_dhcp_interfaces})) check_param = {
(dhcp_server_res, vlan_res, vlan_intf_res) = res "enabled_dhcp_interfaces": self.enabled_dhcp_interfaces,
# vlan ip change require kill old dhcp_relay related processes "dhcp_server_feature_enabled": self.dhcp_server_feature_enabled
if vlan_intf_res: }
self.refresh_dhcrelay(True) res = (self.dhcp_relayd_monitor.check_db_update(check_param))
elif dhcp_server_res or vlan_res: dhcp_feature_statue_changed = False
self.refresh_dhcrelay(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): def _start_dhcrelay_process(self, new_dhcp_interfaces, dhcp_server_ip, force_kill):
# To check whether need to kill dhcrelay process # 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(DhcpServerTableIntfEnablementEventChecker(sel, dhcp_db_connector.config_db))
checkers.append(VlanIntfTableEventChecker(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(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 = 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 = DhcpRelayd(dhcp_db_connector, db_monitor)
dhcprelayd.start() dhcprelayd.start()
dhcprelayd.wait() dhcprelayd.wait()

View File

@ -108,3 +108,8 @@ def get_subscribe_table_tested_data(test_name):
data["table"][i][2] = tuple(data["table"][i][2]) data["table"][i][2] = tuple(data["table"][i][2])
data["table"][i] = tuple(data["table"][i]) data["table"][i] = tuple(data["table"][i])
return tested_data return tested_data
class MockSubprocessRes(object):
def __init__(self, returncode):
self.returncode = returncode

View File

@ -242,5 +242,52 @@
], ],
"exp_res": false "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
}
}
] ]
} }

View File

@ -39,11 +39,9 @@ stderr_logfile=syslog
dependent_startup=true dependent_startup=true
dependent_startup_wait_for=rsyslogd:running dependent_startup_wait_for=rsyslogd:running
[group:dhcp-relay]
programs=dhcprelayd,dhcp6relay
[program:dhcp6relay] [program:isc-dhcpv4-relay-Vlan1000]
command=/usr/sbin/dhcp6relay 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 priority=3
autostart=false autostart=false
autorestart=false autorestart=false
@ -60,5 +58,31 @@ autorestart=false
stdout_logfile=syslog stdout_logfile=syslog
stderr_logfile=syslog stderr_logfile=syslog
dependent_startup=true 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 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

View File

@ -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, \ from dhcp_server.common.dhcp_db_monitor import DhcpRelaydDbMonitor, DhcpServdDbMonitor, ConfigDbEventChecker, \
DhcpServerTableIntfEnablementEventChecker, DhcpServerTableCfgChangeEventChecker, \ DhcpServerTableIntfEnablementEventChecker, DhcpServerTableCfgChangeEventChecker, \
DhcpPortTableEventChecker, DhcpRangeTableEventChecker, DhcpOptionTableEventChecker, \ DhcpPortTableEventChecker, DhcpRangeTableEventChecker, DhcpOptionTableEventChecker, \
VlanTableEventChecker, VlanMemberTableEventChecker, VlanIntfTableEventChecker VlanTableEventChecker, VlanMemberTableEventChecker, VlanIntfTableEventChecker, DhcpServerFeatureStateChecker
from dhcp_server.common.utils import DhcpDbConnector from dhcp_server.common.utils import DhcpDbConnector
from swsscommon import swsscommon from swsscommon import swsscommon
from unittest.mock import patch, ANY, PropertyMock, MagicMock 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]) @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") \ with patch.object(DhcpServerTableIntfEnablementEventChecker, "check_update_event") \
as mock_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(VlanTableEventChecker, "check_update_event") as mock_check_vlan_update, \
patch.object(VlanIntfTableEventChecker, "check_update_event") as mock_check_vlan_intf_update, \ patch.object(VlanIntfTableEventChecker, "check_update_event") as mock_check_vlan_intf_update, \
patch.object(swsscommon.Select, "select", return_value=(select_result, None)), \ 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) dhcp_relayd_db_monitor = DhcpRelaydDbMonitor(db_connector, swsscommon.Select(), checkers)
tested_db_snapshot = {"enabled_dhcp_interfaces": "dummy"} tested_db_snapshot = {"enabled_dhcp_interfaces": "dummy"}
dhcp_relayd_db_monitor.check_db_update(tested_db_snapshot) dhcp_relayd_db_monitor.check_db_update(tested_db_snapshot)
if select_result == swsscommon.Select.OBJECT: if select_result == swsscommon.Select.OBJECT and checker_enabled:
mock_check_update_event.assert_called_once_with(tested_db_snapshot)
mock_check_vlan_update.assert_called_once_with(tested_db_snapshot) 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) mock_check_vlan_intf_update.assert_called_once_with(tested_db_snapshot)
else: else:
mock_check_update_event.assert_not_called()
mock_check_vlan_update.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() 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: with patch.object(ConfigDbEventChecker, "enable") as mock_enable:
db_connector = DhcpDbConnector() db_connector = DhcpDbConnector()
dhcp_relayd_db_monitor = DhcpRelaydDbMonitor(db_connector, None, [VlanTableEventChecker(None, None)]) 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: if "VlanTableEventChecker" in tables:
mock_enable.assert_called_once() mock_enable.assert_called_once()
else: else:
mock_enable.assert_not_called() 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("select_result", [swsscommon.Select.TIMEOUT, swsscommon.Select.OBJECT])
@pytest.mark.parametrize("is_checker_enabled", [True, False]) @pytest.mark.parametrize("is_checker_enabled", [True, False])
def test_dhcp_servd_monitor_check_db_update(mock_swsscommon_dbconnector_init, select_result, 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 assert check_res
else: else:
assert expected_res == check_res 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

View File

@ -3,21 +3,32 @@ import pytest
import subprocess import subprocess
import sys import sys
import time 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.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 dhcp_server.dhcprelayd.dhcprelayd import DhcpRelayd, KILLED_OLD, NOT_KILLED, NOT_FOUND_PROC
from swsscommon import swsscommon 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): @pytest.mark.parametrize("dhcp_server_enabled", [True, False])
with patch.object(DhcpRelayd, "refresh_dhcrelay", return_value=None) as mock_refresh, \ def test_start(mock_swsscommon_dbconnector_init, dhcp_server_enabled):
patch.object(ConfigDbEventChecker, "enable"): 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() dhcp_db_connector = DhcpDbConnector()
dhcprelayd = DhcpRelayd(dhcp_db_connector, None) dhcprelayd = DhcpRelayd(dhcp_db_connector, DhcpRelaydDbMonitor)
dhcprelayd.start() 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): 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: else:
mock_exit.assert_called_once_with(1) mock_exit.assert_called_once_with(1)
mock_sleep.assert_has_calls([call(10) for _ in range(10)]) 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"
]
}

View File

@ -1,8 +1,10 @@
import dhcp_server.common.utils as utils import dhcp_server.common.utils as utils
import ipaddress import ipaddress
import psutil
import pytest import pytest
from swsscommon import swsscommon 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 = { interval_test_data = {
"ordered_with_overlap": { "ordered_with_overlap": {
@ -137,3 +139,8 @@ def convert_ip_address_intervals(intervals):
def test_validate_ttr_type(test_data): def test_validate_ttr_type(test_data):
res = utils.validate_str_type(test_data[0], test_data[1]) res = utils.validate_str_type(test_data[0], test_data[1])
assert res == test_data[2] 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")