diff --git a/dockers/docker-dhcp-relay/Dockerfile.j2 b/dockers/docker-dhcp-relay/Dockerfile.j2 index ddcc23819b..8b0753f288 100644 --- a/dockers/docker-dhcp-relay/Dockerfile.j2 +++ b/dockers/docker-dhcp-relay/Dockerfile.j2 @@ -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 && \ diff --git a/dockers/docker-dhcp-relay/dhcp-relay.programs.j2 b/dockers/docker-dhcp-relay/dhcp-relay.programs.j2 index b1295a803b..94f6adf765 100644 --- a/dockers/docker-dhcp-relay/dhcp-relay.programs.j2 +++ b/dockers/docker-dhcp-relay/dhcp-relay.programs.j2 @@ -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 %} diff --git a/dockers/docker-dhcp-relay/docker-dhcp-relay.supervisord.conf.j2 b/dockers/docker-dhcp-relay/docker-dhcp-relay.supervisord.conf.j2 index 6868fa566d..308997575a 100644 --- a/dockers/docker-dhcp-relay/docker-dhcp-relay.supervisord.conf.j2 +++ b/dockers/docker-dhcp-relay/docker-dhcp-relay.supervisord.conf.j2 @@ -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 %} \ No newline at end of file diff --git a/rules/docker-dhcp-relay.mk b/rules/docker-dhcp-relay.mk index 6e2e5d5434..82b4533950 100644 --- a/rules/docker-dhcp-relay.mk +++ b/rules/docker-dhcp-relay.mk @@ -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 diff --git a/rules/sonic-dhcp-server.mk b/rules/sonic-dhcp-server.mk index 941127204c..1ef05545b9 100644 --- a/rules/sonic-dhcp-server.mk +++ b/rules/sonic-dhcp-server.mk @@ -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 diff --git a/src/sonic-config-engine/tests/dhcp-relay-enable-dhcp-server-sample.json b/src/sonic-config-engine/tests/dhcp-relay-enable-dhcp-server-sample.json deleted file mode 100644 index c74b0dec36..0000000000 --- a/src/sonic-config-engine/tests/dhcp-relay-enable-dhcp-server-sample.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "FEATURE": { - "dhcp_server": { - "state": "enabled" - } - } -} diff --git a/src/sonic-config-engine/tests/sample_output/py2/docker-dhcp-relay-enable-dhcp-server.supervisord.conf b/src/sonic-config-engine/tests/sample_output/py2/docker-dhcp-relay-enable-dhcp-server.supervisord.conf deleted file mode 120000 index f814d764a9..0000000000 --- a/src/sonic-config-engine/tests/sample_output/py2/docker-dhcp-relay-enable-dhcp-server.supervisord.conf +++ /dev/null @@ -1 +0,0 @@ -../py3/docker-dhcp-relay-enable-dhcp-server.supervisord.conf \ No newline at end of file diff --git a/src/sonic-config-engine/tests/sample_output/py2/docker-dhcp-relay-no-ip-helper.supervisord.conf b/src/sonic-config-engine/tests/sample_output/py2/docker-dhcp-relay-no-ip-helper.supervisord.conf index a12bbef319..c5882ad148 100644 --- a/src/sonic-config-engine/tests/sample_output/py2/docker-dhcp-relay-no-ip-helper.supervisord.conf +++ b/src/sonic-config-engine/tests/sample_output/py2/docker-dhcp-relay-no-ip-helper.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 diff --git a/src/sonic-config-engine/tests/sample_output/py2/docker-dhcp-relay.supervisord.conf b/src/sonic-config-engine/tests/sample_output/py2/docker-dhcp-relay.supervisord.conf index 6ea09feb7c..d2b6e4a563 100644 --- a/src/sonic-config-engine/tests/sample_output/py2/docker-dhcp-relay.supervisord.conf +++ b/src/sonic-config-engine/tests/sample_output/py2/docker-dhcp-relay.supervisord.conf @@ -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 diff --git a/src/sonic-config-engine/tests/sample_output/py3/docker-dhcp-relay-no-ip-helper.supervisord.conf b/src/sonic-config-engine/tests/sample_output/py3/docker-dhcp-relay-no-ip-helper.supervisord.conf index 7d43878f26..80a26d8e66 100644 --- a/src/sonic-config-engine/tests/sample_output/py3/docker-dhcp-relay-no-ip-helper.supervisord.conf +++ b/src/sonic-config-engine/tests/sample_output/py3/docker-dhcp-relay-no-ip-helper.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 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 diff --git a/src/sonic-config-engine/tests/sample_output/py3/docker-dhcp-relay.supervisord.conf b/src/sonic-config-engine/tests/sample_output/py3/docker-dhcp-relay.supervisord.conf index 5cdee13b3c..dd8371b594 100644 --- a/src/sonic-config-engine/tests/sample_output/py3/docker-dhcp-relay.supervisord.conf +++ b/src/sonic-config-engine/tests/sample_output/py3/docker-dhcp-relay.supervisord.conf @@ -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 diff --git a/src/sonic-config-engine/tests/test_j2files.py b/src/sonic-config-engine/tests/test_j2files.py index 97ad63441f..125668f24a 100644 --- a/src/sonic-config-engine/tests/test_j2files.py +++ b/src/sonic-config-engine/tests/test_j2files.py @@ -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') diff --git a/src/sonic-dhcp-server/dhcp_server/common/dhcp_db_monitor.py b/src/sonic-dhcp-server/dhcp_server/common/dhcp_db_monitor.py index 799acabbcc..e2caab826f 100644 --- a/src/sonic-dhcp-server/dhcp_server/common/dhcp_db_monitor.py +++ b/src/sonic-dhcp-server/dhcp_server/common/dhcp_db_monitor.py @@ -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): """ diff --git a/src/sonic-dhcp-server/dhcp_server/common/utils.py b/src/sonic-dhcp-server/dhcp_server/common/utils.py index 1b3268683c..c92936bfc1 100644 --- a/src/sonic-dhcp-server/dhcp_server/common/utils.py +++ b/src/sonic-dhcp-server/dhcp_server/common/utils.py @@ -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 diff --git a/src/sonic-dhcp-server/dhcp_server/dhcprelayd/dhcprelayd.py b/src/sonic-dhcp-server/dhcp_server/dhcprelayd/dhcprelayd.py index fcf32fd716..6eded1c350 100644 --- a/src/sonic-dhcp-server/dhcp_server/dhcprelayd/dhcprelayd.py +++ b/src/sonic-dhcp-server/dhcp_server/dhcprelayd/dhcprelayd.py @@ -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() diff --git a/src/sonic-dhcp-server/tests/common_utils.py b/src/sonic-dhcp-server/tests/common_utils.py index 1b3323c7e1..bf88117293 100644 --- a/src/sonic-dhcp-server/tests/common_utils.py +++ b/src/sonic-dhcp-server/tests/common_utils.py @@ -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 diff --git a/src/sonic-dhcp-server/tests/test_data/dhcp_db_monitor_test_data.json b/src/sonic-dhcp-server/tests/test_data/dhcp_db_monitor_test_data.json index cbc132ecd7..db3b328fa8 100644 --- a/src/sonic-dhcp-server/tests/test_data/dhcp_db_monitor_test_data.json +++ b/src/sonic-dhcp-server/tests/test_data/dhcp_db_monitor_test_data.json @@ -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 + } + } ] } \ No newline at end of file diff --git a/src/sonic-config-engine/tests/sample_output/py3/docker-dhcp-relay-enable-dhcp-server.supervisord.conf b/src/sonic-dhcp-server/tests/test_data/supervisor.conf similarity index 61% rename from src/sonic-config-engine/tests/sample_output/py3/docker-dhcp-relay-enable-dhcp-server.supervisord.conf rename to src/sonic-dhcp-server/tests/test_data/supervisor.conf index 350e302ce9..e958fa35fb 100644 --- a/src/sonic-config-engine/tests/sample_output/py3/docker-dhcp-relay-enable-dhcp-server.supervisord.conf +++ b/src/sonic-dhcp-server/tests/test_data/supervisor.conf @@ -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 diff --git a/src/sonic-dhcp-server/tests/test_dhcp_db_monitor.py b/src/sonic-dhcp-server/tests/test_dhcp_db_monitor.py index d6830b274e..a51988145e 100644 --- a/src/sonic-dhcp-server/tests/test_dhcp_db_monitor.py +++ b/src/sonic-dhcp-server/tests/test_dhcp_db_monitor.py @@ -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 diff --git a/src/sonic-dhcp-server/tests/test_dhcprelayd.py b/src/sonic-dhcp-server/tests/test_dhcprelayd.py index 7681f5d3a3..1ff0d24023 100644 --- a/src/sonic-dhcp-server/tests/test_dhcprelayd.py +++ b/src/sonic-dhcp-server/tests/test_dhcprelayd.py @@ -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" + ] + } diff --git a/src/sonic-dhcp-server/tests/test_utils.py b/src/sonic-dhcp-server/tests/test_utils.py index a8f4feb36a..129f2faf7f 100644 --- a/src/sonic-dhcp-server/tests/test_utils.py +++ b/src/sonic-dhcp-server/tests/test_utils.py @@ -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") \ No newline at end of file