Skip to content

Commit

Permalink
[dhcp_relay] Use dhcprelayd to manage critical processes (#17236)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
yaqiangz authored Nov 27, 2023
1 parent 49dd425 commit da80593
Show file tree
Hide file tree
Showing 21 changed files with 506 additions and 119 deletions.
10 changes: 3 additions & 7 deletions dockers/docker-dhcp-relay/Dockerfile.j2
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 && \
Expand Down
10 changes: 1 addition & 9 deletions dockers/docker-dhcp-relay/dhcp-relay.programs.j2
Original file line number Diff line number Diff line change
@@ -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 %}
Expand Down
13 changes: 2 additions & 11 deletions dockers/docker-dhcp-relay/docker-dhcp-relay.supervisord.conf.j2
Original file line number Diff line number Diff line change
Expand Up @@ -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... #}
Expand All @@ -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
Expand All @@ -82,6 +76,3 @@ stdout_logfile=syslog
stderr_logfile=syslog
dependent_startup=true
dependent_startup_wait_for=start:exited
{% endif %}
{% endif %}
{% endif %}
2 changes: 0 additions & 2 deletions rules/docker-dhcp-relay.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions rules/sonic-dhcp-server.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
20 changes: 0 additions & 20 deletions src/sonic-config-engine/tests/test_j2files.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,36 +154,16 @@ def test_ports_json(self):
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')
Expand Down
94 changes: 74 additions & 20 deletions src/sonic-dhcp-server/dhcp_server/common/dhcp_db_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
VLAN = "VLAN"
VLAN_MEMBER = "VLAN_MEMBER"
VLAN_INTERFACE = "VLAN_INTERFACE"
FEATURE = "FEATURE"


class ConfigDbEventChecker(object):
Expand Down Expand Up @@ -343,6 +344,60 @@ def _process_check(self, key, op, entry, enabled_dhcp_interfaces):
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 = {}

Expand All @@ -354,17 +409,21 @@ def __init__(self, db_connector, sel, checkers, select_timeout=DEFAULT_SELECT_TI
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):
"""
Expand All @@ -376,10 +435,13 @@ def check_db_update(self, db_snapshot):
"""
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):
Expand All @@ -399,23 +461,15 @@ def disable_checkers(self, checker_names):
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):
"""
Enable checkers
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):
"""
Expand Down
Loading

0 comments on commit da80593

Please sign in to comment.