[dhcp_server] Add dhcprelayd for dhcp_server feature (#16947)
Add support in dhcp_relay container for dhcp_server_ipv4 feature. HLD: sonic-net/SONiC#1282
This commit is contained in:
parent
c85c12bc75
commit
274d320443
@ -13,6 +13,14 @@ 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 %}
|
||||
|
||||
{% if INCLUDE_DHCP_SERVER == "y" -%}
|
||||
RUN pip3 install psutil
|
||||
{%- endif %}
|
||||
|
||||
RUN apt-get install -y libjsoncpp-dev
|
||||
|
||||
{% if docker_dhcp_relay_debs.strip() -%}
|
||||
@ -23,7 +31,19 @@ RUN apt-get install -y libjsoncpp-dev
|
||||
{{ install_debian_packages(docker_dhcp_relay_debs.split(' ')) }}
|
||||
{%- endif %}
|
||||
|
||||
{% if docker_dhcp_relay_whls.strip() %}
|
||||
# Copy locally-built Python wheel dependencies
|
||||
{{ copy_files("python-wheels/", docker_dhcp_relay_whls.split(' '), "/python-wheels/") }}
|
||||
|
||||
# Install locally-built Python wheel dependencies
|
||||
{{ install_python_wheels(docker_dhcp_relay_whls.split(' ')) }}
|
||||
{% 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 && \
|
||||
|
@ -45,6 +45,46 @@ COMMON_TEST_DATA = [
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"ipv4_with_disabled_dhcp_server_with_header",
|
||||
{
|
||||
"config_db": {
|
||||
"VLAN": {
|
||||
"Vlan1000": {
|
||||
"dhcp_servers": [
|
||||
"192.0.0.1",
|
||||
"192.0.0.2"
|
||||
]
|
||||
}
|
||||
},
|
||||
"FEATURE": {
|
||||
"dhcp_server": {
|
||||
"state": "disabled"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"ipv4_with_enabled_dhcp_server_with_header",
|
||||
{
|
||||
"config_db": {
|
||||
"VLAN": {
|
||||
"Vlan1000": {
|
||||
"dhcp_servers": [
|
||||
"192.0.0.1",
|
||||
"192.0.0.2"
|
||||
]
|
||||
}
|
||||
},
|
||||
"FEATURE": {
|
||||
"dhcp_server": {
|
||||
"state": "enabled"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
|
@ -224,6 +224,36 @@ class TestConfigDhcpRelay(object):
|
||||
db.cfgdb.set_entry.assert_called_once_with(config_db_table, "Vlan1000",
|
||||
expected_dhcp_relay_del_config_db_output[ip_version])
|
||||
|
||||
def test_config_add_del_dhcp_relay_with_enable_dhcp_server(self, mock_cfgdb):
|
||||
runner = CliRunner()
|
||||
db = Db()
|
||||
db.cfgdb = mock_cfgdb
|
||||
ip_version = "ipv4"
|
||||
test_ip = IP_VER_TEST_PARAM_MAP[ip_version]["ips"][0]
|
||||
|
||||
with mock.patch("utilities_common.cli.run_command"), \
|
||||
mock.patch.object(dhcp_relay, "is_dhcp_server_enabled", return_value=True):
|
||||
# add new dhcp relay
|
||||
result = runner.invoke(dhcp_relay.dhcp_relay.commands[ip_version]
|
||||
.commands[IP_VER_TEST_PARAM_MAP[ip_version]["command"]]
|
||||
.commands["add"], ["1000", test_ip], obj=db)
|
||||
print(result.exit_code)
|
||||
print(result.output)
|
||||
assert result.exit_code == 0
|
||||
assert "Cannot change ipv4 dhcp_relay configuration when dhcp_server feature is enabled" in result.output
|
||||
|
||||
db.cfgdb.set_entry.reset_mock()
|
||||
# del dhcp relay
|
||||
with mock.patch("utilities_common.cli.run_command"), \
|
||||
mock.patch.object(dhcp_relay, "is_dhcp_server_enabled", return_value=True):
|
||||
result = runner.invoke(dhcp_relay.dhcp_relay.commands[ip_version]
|
||||
.commands[IP_VER_TEST_PARAM_MAP[ip_version]["command"]]
|
||||
.commands["del"], ["1000", test_ip], obj=db)
|
||||
print(result.exit_code)
|
||||
print(result.output)
|
||||
assert result.exit_code == 0
|
||||
assert "Cannot change ipv4 dhcp_relay configuration when dhcp_server feature is enabled" in result.output
|
||||
|
||||
def test_config_add_del_multiple_dhcp_relay(self, mock_cfgdb, ip_version):
|
||||
runner = CliRunner()
|
||||
db = Db()
|
||||
|
@ -1,5 +1,6 @@
|
||||
import pytest
|
||||
import sys
|
||||
import click
|
||||
import os
|
||||
sys.path.append('../cli/show/plugins/')
|
||||
import show_dhcp_relay as show
|
||||
@ -35,6 +36,14 @@ expected_ipv4_table_with_header = """\
|
||||
+-------------+----------------------+
|
||||
"""
|
||||
|
||||
expected_ipv4_table_with_enabled_dhcp_server_with_header = """\
|
||||
+-------------+----------------------+
|
||||
| Interface | DHCP Relay Address |
|
||||
+=============+======================+
|
||||
| Vlan1000 | N/A |
|
||||
+-------------+----------------------+
|
||||
"""
|
||||
|
||||
expected_ipv6_table_without_header = """\
|
||||
-------- ------------
|
||||
Vlan1000 fc02:2000::1
|
||||
@ -86,12 +95,18 @@ def test_plugin_registration():
|
||||
assert 'DHCP Helper Address' in dict(vlan.VlanBrief.COLUMNS)
|
||||
|
||||
|
||||
def test_dhcp_relay_column_output():
|
||||
@pytest.mark.parametrize("feature_table", [{}, {"dhcp_server": {"state": "disabled"}},
|
||||
{"dhcp_server": {"state": "enabled"}}, {"dhcp_server": {}}])
|
||||
def test_dhcp_relay_column_output(feature_table):
|
||||
ctx = (
|
||||
({'Vlan1001': {'dhcp_servers': ['192.0.0.1', '192.168.0.2']}}, {}, {}),
|
||||
(),
|
||||
(MockDb({"FEATURE": feature_table})),
|
||||
)
|
||||
assert show.get_dhcp_helper_address(ctx, 'Vlan1001') == '192.0.0.1\n192.168.0.2'
|
||||
if "dhcp_server" in feature_table and "state" in feature_table["dhcp_server"] and \
|
||||
feature_table["dhcp_server"]["state"] == "enabled":
|
||||
assert show.get_dhcp_helper_address(ctx, 'Vlan1001') == 'N/A'
|
||||
else:
|
||||
assert show.get_dhcp_helper_address(ctx, 'Vlan1001') == '192.0.0.1\n192.168.0.2'
|
||||
|
||||
|
||||
@parameterized.expand(COMMON_TEST_DATA)
|
||||
@ -103,7 +118,7 @@ def test_show_dhcp_relay(test_name, test_data, fs):
|
||||
config_db = MockConfigDb()
|
||||
ip_version = "ipv4" if "ipv4" in test_name else "ipv6"
|
||||
table = config_db.get_table(IP_VER_TEST_PARAM_MAP[ip_version]["table"])
|
||||
if test_name == "ipv4_with_header":
|
||||
if test_name in ["ipv4_with_header", "ipv4_with_disabled_dhcp_server_with_header"]:
|
||||
result = show.get_dhcp_relay_data_with_header(table, IP_VER_TEST_PARAM_MAP[ip_version]["entry"])
|
||||
expected_output = expected_ipv4_table_with_header
|
||||
elif test_name == "ipv6_with_header":
|
||||
@ -112,6 +127,9 @@ def test_show_dhcp_relay(test_name, test_data, fs):
|
||||
elif test_name == "ipv6_without_header":
|
||||
result = show.get_data(table, "Vlan1000")
|
||||
expected_output = expected_ipv6_table_without_header
|
||||
elif test_name == "ipv4_with_enabled_dhcp_server_with_header":
|
||||
result = show.get_dhcp_relay_data_with_header(table, IP_VER_TEST_PARAM_MAP[ip_version]["entry"], True)
|
||||
expected_output = expected_ipv4_table_with_enabled_dhcp_server_with_header
|
||||
assert result == expected_output
|
||||
|
||||
|
||||
@ -153,3 +171,31 @@ def test_show_multi_dhcp_relay(test_name, test_data, fs):
|
||||
else:
|
||||
expected_output = expected_ipv6_table_multi_with_header
|
||||
assert result == expected_output
|
||||
|
||||
|
||||
def test_show_dhcp_relay_ipv4_counter_with_enabled_dhcp_server():
|
||||
with mock.patch.object(show, "is_dhcp_server_enabled", return_value=True), \
|
||||
mock.patch.object(swsscommon.ConfigDBConnector, "connect", return_value=None), \
|
||||
mock.patch.object(swsscommon.ConfigDBConnector, "get_table", return_value=None), \
|
||||
mock.patch.object(click, "echo", return_value=None) as mock_echo:
|
||||
show.ipv4_counters("Etherner1")
|
||||
expected_param = "Unsupport to check dhcp_relay ipv4 counter when dhcp_server feature is enabled"
|
||||
mock_echo.assert_called_once_with(expected_param)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("enable_dhcp_server", [True, False])
|
||||
def test_is_dhcp_server_enabled(enable_dhcp_server):
|
||||
result = show.is_dhcp_server_enabled({"dhcp_server": {"state": "enabled" if enable_dhcp_server else "disabled"}})
|
||||
assert result == enable_dhcp_server
|
||||
|
||||
|
||||
class MockDb(object):
|
||||
class MockCfgDb(object):
|
||||
def __init__(self, mock_cfgdb):
|
||||
self.mock_cfgdb = mock_cfgdb
|
||||
|
||||
def get_table(self, table_name):
|
||||
return self.mock_cfgdb.get(table_name, {})
|
||||
|
||||
def __init__(self, mock_cfgdb):
|
||||
self.cfgdb = self.MockCfgDb(mock_cfgdb)
|
||||
|
@ -116,6 +116,11 @@ def del_dhcp_relay(vid, dhcp_relay_ips, db, ip_version):
|
||||
ctx.fail("Restart service dhcp_relay failed with error {}".format(e))
|
||||
|
||||
|
||||
def is_dhcp_server_enabled(db):
|
||||
dhcp_server_feature_entry = db.cfgdb.get_entry("FEATURE", "dhcp_server")
|
||||
return "state" in dhcp_server_feature_entry and dhcp_server_feature_entry["state"] == "enabled"
|
||||
|
||||
|
||||
@click.group(cls=clicommon.AbbreviationGroup, name="dhcp_relay")
|
||||
def dhcp_relay():
|
||||
"""config DHCP_Relay information"""
|
||||
@ -163,6 +168,9 @@ def dhcp_relay_ipv4_helper():
|
||||
@click.argument("dhcp_relay_helpers", nargs=-1, required=True)
|
||||
@clicommon.pass_db
|
||||
def add_dhcp_relay_ipv4_helper(db, vid, dhcp_relay_helpers):
|
||||
if is_dhcp_server_enabled(db):
|
||||
click.echo("Cannot change ipv4 dhcp_relay configuration when dhcp_server feature is enabled")
|
||||
return
|
||||
add_dhcp_relay(vid, dhcp_relay_helpers, db, IPV4)
|
||||
|
||||
|
||||
@ -171,6 +179,9 @@ def add_dhcp_relay_ipv4_helper(db, vid, dhcp_relay_helpers):
|
||||
@click.argument("dhcp_relay_helpers", nargs=-1, required=True)
|
||||
@clicommon.pass_db
|
||||
def del_dhcp_relay_ipv4_helper(db, vid, dhcp_relay_helpers):
|
||||
if is_dhcp_server_enabled(db):
|
||||
click.echo("Cannot change ipv4 dhcp_relay configuration when dhcp_server feature is enabled")
|
||||
return
|
||||
del_dhcp_relay(vid, dhcp_relay_helpers, db, IPV4)
|
||||
|
||||
|
||||
@ -207,6 +218,9 @@ def add_vlan_dhcp_relay_destination(db, vid, dhcp_relay_destination_ips):
|
||||
click.echo("{} is already a DHCP relay destination for {}".format(ip_addr, vlan_name))
|
||||
continue
|
||||
if clicommon.ipaddress_type(ip_addr) == 4:
|
||||
if is_dhcp_server_enabled(db):
|
||||
click.echo("Cannot change dhcp_relay configuration when dhcp_server feature is enabled")
|
||||
return
|
||||
dhcp_servers.append(ip_addr)
|
||||
else:
|
||||
dhcpv6_servers.append(ip_addr)
|
||||
@ -253,6 +267,9 @@ def del_vlan_dhcp_relay_destination(db, vid, dhcp_relay_destination_ips):
|
||||
if (ip_addr not in dhcp_servers) and (ip_addr not in dhcpv6_servers):
|
||||
ctx.fail("{} is not a DHCP relay destination for {}".format(ip_addr, vlan_name))
|
||||
if clicommon.ipaddress_type(ip_addr) == 4:
|
||||
if is_dhcp_server_enabled(db):
|
||||
click.echo("Cannot change dhcp_relay configuration when dhcp_server feature is enabled")
|
||||
return
|
||||
dhcp_servers.remove(ip_addr)
|
||||
else:
|
||||
dhcpv6_servers.remove(ip_addr)
|
||||
|
@ -32,13 +32,15 @@ config_db = ConfigDBConnector()
|
||||
|
||||
|
||||
def get_dhcp_helper_address(ctx, vlan):
|
||||
cfg, _ = ctx
|
||||
cfg, db = ctx
|
||||
vlan_dhcp_helper_data, _, _ = cfg
|
||||
vlan_config = vlan_dhcp_helper_data.get(vlan)
|
||||
if not vlan_config:
|
||||
return ""
|
||||
|
||||
dhcp_helpers = vlan_config.get('dhcp_servers', [])
|
||||
feature_data = db.cfgdb.get_table("FEATURE")
|
||||
dhcp_server_enabled = is_dhcp_server_enabled(feature_data)
|
||||
dhcp_helpers = ["N/A"] if dhcp_server_enabled else vlan_config.get('dhcp_servers', [])
|
||||
|
||||
return '\n'.join(natsorted(dhcp_helpers))
|
||||
|
||||
@ -96,6 +98,11 @@ def dhcp4relay_counters():
|
||||
|
||||
|
||||
def ipv4_counters(interface):
|
||||
config_db.connect()
|
||||
feature_tbl = config_db.get_table("FEATURE")
|
||||
if is_dhcp_server_enabled(feature_tbl):
|
||||
click.echo("Unsupport to check dhcp_relay ipv4 counter when dhcp_server feature is enabled")
|
||||
return
|
||||
counter = DHCPv4_Counter()
|
||||
counter_intf = counter.get_interface()
|
||||
|
||||
@ -193,7 +200,7 @@ def dhcp_relay_helper():
|
||||
pass
|
||||
|
||||
|
||||
def get_dhcp_relay_data_with_header(table_data, entry_name):
|
||||
def get_dhcp_relay_data_with_header(table_data, entry_name, dhcp_server_enabled=False):
|
||||
vlan_relay = {}
|
||||
vlans = table_data.keys()
|
||||
for vlan in vlans:
|
||||
@ -203,8 +210,11 @@ def get_dhcp_relay_data_with_header(table_data, entry_name):
|
||||
continue
|
||||
|
||||
vlan_relay[vlan] = []
|
||||
for address in dhcp_relay_data:
|
||||
vlan_relay[vlan].append(address)
|
||||
if dhcp_server_enabled:
|
||||
vlan_relay[vlan].append("N/A")
|
||||
else:
|
||||
for address in dhcp_relay_data:
|
||||
vlan_relay[vlan].append(address)
|
||||
|
||||
dhcp_relay_vlan_keys = vlan_relay.keys()
|
||||
relay_address_list = ["\n".join(vlan_relay[key]) for key in dhcp_relay_vlan_keys]
|
||||
@ -212,6 +222,13 @@ def get_dhcp_relay_data_with_header(table_data, entry_name):
|
||||
return tabulate(data, tablefmt='grid', stralign='right', headers='keys') + '\n'
|
||||
|
||||
|
||||
def is_dhcp_server_enabled(feature_tbl):
|
||||
if feature_tbl is not None and "dhcp_server" in feature_tbl and "state" in feature_tbl["dhcp_server"] and \
|
||||
feature_tbl["dhcp_server"]["state"] == "enabled":
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_dhcp_relay(table_name, entry_name, with_header):
|
||||
if config_db is None:
|
||||
return
|
||||
@ -221,8 +238,13 @@ def get_dhcp_relay(table_name, entry_name, with_header):
|
||||
if table_data is None:
|
||||
return
|
||||
|
||||
dhcp_server_enabled = False
|
||||
if table_name == VLAN:
|
||||
feature_tbl = config_db.get_table("FEATURE")
|
||||
dhcp_server_enabled = is_dhcp_server_enabled(feature_tbl)
|
||||
|
||||
if with_header:
|
||||
output = get_dhcp_relay_data_with_header(table_data, entry_name)
|
||||
output = get_dhcp_relay_data_with_header(table_data, entry_name, dhcp_server_enabled)
|
||||
print(output)
|
||||
else:
|
||||
vlans = config_db.get_keys(table_name)
|
||||
|
@ -2,9 +2,13 @@
|
||||
programs=
|
||||
{%- 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 VLAN and vlan_name in VLAN and 'dhcp_servers' in VLAN[vlan_name] and VLAN[vlan_name]['dhcp_servers']|length > 0 %}
|
||||
{% 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 }}
|
||||
|
@ -39,6 +39,10 @@ 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... #}
|
||||
@ -56,15 +60,28 @@ dependent_startup_wait_for=rsyslogd:running
|
||||
{% if ipv4_num_relays.count > 0 or ipv6_num_relays.count > 0 %}
|
||||
{% include 'dhcp-relay.programs.j2' %}
|
||||
|
||||
|
||||
{# 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 %}
|
||||
[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
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
@ -3,3 +3,6 @@
|
||||
{% for port, config in PORT.items() %}
|
||||
{{- port }} {% if "alias" in config %}{{ config["alias"] }}{% else %}{{ port }}{% endif %} {{- "\n" -}}
|
||||
{% endfor -%}
|
||||
{% for pc, config in PORTCHANNEL.items() %}
|
||||
{{- pc }} {{ pc }} {{- "\n" -}}
|
||||
{% endfor -%}
|
||||
|
@ -60,4 +60,4 @@ autorestart=false
|
||||
stdout_logfile=syslog
|
||||
stderr_logfile=syslog
|
||||
dependent_startup=true
|
||||
dependent_startup_wait_for=start:exited
|
||||
dependent_startup_wait_for=dhcpservd:running
|
||||
|
@ -17,6 +17,9 @@ $(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
|
||||
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"FEATURE": {
|
||||
"dhcp_server": {
|
||||
"state": "enabled"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,10 @@
|
||||
{
|
||||
"VLAN_INTERFACE": {
|
||||
"Vlan1000|fc02:2000::2/24": {}
|
||||
},
|
||||
"FEATURE": {
|
||||
"dhcp_server": {
|
||||
"state": "disabled"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
../py3/docker-dhcp-relay-enable-dhcp-server.supervisord.conf
|
@ -42,7 +42,6 @@ dependent_startup_wait_for=rsyslogd:running
|
||||
[group:dhcp-relay]
|
||||
programs=isc-dhcpv4-relay-Vlan1000,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
|
||||
priority=3
|
||||
|
@ -42,7 +42,6 @@ dependent_startup_wait_for=rsyslogd:running
|
||||
[group:dhcp-relay]
|
||||
programs=isc-dhcpv4-relay-Vlan1000,isc-dhcpv4-relay-Vlan2000,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
|
||||
priority=3
|
||||
|
@ -0,0 +1,64 @@
|
||||
[supervisord]
|
||||
logfile_maxbytes=1MB
|
||||
logfile_backups=2
|
||||
nodaemon=true
|
||||
|
||||
[eventlistener:dependent-startup]
|
||||
command=python3 -m supervisord_dependent_startup
|
||||
autostart=true
|
||||
autorestart=unexpected
|
||||
startretries=0
|
||||
exitcodes=0,3
|
||||
events=PROCESS_STATE
|
||||
buffer_size=1024
|
||||
|
||||
[eventlistener:supervisor-proc-exit-listener]
|
||||
command=/usr/bin/supervisor-proc-exit-listener --container-name dhcp_relay
|
||||
events=PROCESS_STATE_EXITED,PROCESS_STATE_RUNNING
|
||||
autostart=true
|
||||
autorestart=unexpected
|
||||
buffer_size=1024
|
||||
|
||||
[program:rsyslogd]
|
||||
command=/usr/sbin/rsyslogd -n -iNONE
|
||||
priority=1
|
||||
autostart=false
|
||||
autorestart=false
|
||||
stdout_logfile=syslog
|
||||
stderr_logfile=syslog
|
||||
dependent_startup=true
|
||||
|
||||
[program:start]
|
||||
command=/usr/bin/start.sh
|
||||
priority=2
|
||||
autostart=false
|
||||
autorestart=false
|
||||
startsecs=0
|
||||
stdout_logfile=syslog
|
||||
stderr_logfile=syslog
|
||||
dependent_startup=true
|
||||
dependent_startup_wait_for=rsyslogd:running
|
||||
|
||||
[group:dhcp-relay]
|
||||
programs=dhcprelayd,dhcp6relay
|
||||
|
||||
[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
|
||||
|
||||
[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
|
||||
|
@ -42,7 +42,6 @@ dependent_startup_wait_for=rsyslogd:running
|
||||
[group:dhcp-relay]
|
||||
programs=isc-dhcpv4-relay-Vlan1000,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
|
||||
priority=3
|
||||
|
@ -42,7 +42,6 @@ dependent_startup_wait_for=rsyslogd:running
|
||||
[group:dhcp-relay]
|
||||
programs=isc-dhcpv4-relay-Vlan1000,isc-dhcpv4-relay-Vlan2000,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
|
||||
priority=3
|
||||
|
@ -154,22 +154,43 @@ 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
|
||||
# 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')
|
||||
template_path = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-dhcp-relay',
|
||||
'docker-dhcp-relay.supervisord.conf.j2')
|
||||
argument = ['-m', self.no_ip_helper_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-no-ip-helper.supervisord.conf'), self.output_file))
|
||||
self.assertTrue(utils.cmp(os.path.join(self.test_dir, 'sample_output', utils.PYvX_DIR,
|
||||
'docker-dhcp-relay-no-ip-helper.supervisord.conf'), self.output_file))
|
||||
|
||||
def test_radv(self):
|
||||
# Test generation of radvd.conf with multiple ipv6 prefixes
|
||||
|
131
src/sonic-dhcp-server/dhcp_server/common/dhcp_db_monitor.py
Normal file
131
src/sonic-dhcp-server/dhcp_server/common/dhcp_db_monitor.py
Normal file
@ -0,0 +1,131 @@
|
||||
import ipaddress
|
||||
import syslog
|
||||
from abc import abstractmethod
|
||||
from swsscommon import swsscommon
|
||||
|
||||
DHCP_SERVER_IPV4 = "DHCP_SERVER_IPV4"
|
||||
DHCP_SERVER_IPV4_PORT = "DHCP_SERVER_IPV4_PORT"
|
||||
DHCP_SERVER_IPV4_RANGE = "DHCP_SERVER_IPV4_RANGE"
|
||||
VLAN = "VLAN"
|
||||
VLAN_MEMBER = "VLAN_MEMBER"
|
||||
VLAN_INTERFACE = "VLAN_INTERFACE"
|
||||
DEFAULT_SELECT_TIMEOUT = 5000 # millisecond
|
||||
|
||||
|
||||
class DhcpDbMonitor(object):
|
||||
def __init__(self, db_connector, select_timeout=DEFAULT_SELECT_TIMEOUT):
|
||||
self.db_connector = db_connector
|
||||
self.sel = swsscommon.Select()
|
||||
self.select_timeout = select_timeout
|
||||
|
||||
@abstractmethod
|
||||
def subscribe_table(self):
|
||||
"""
|
||||
Subcribe db table to monitor
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def _do_check(self):
|
||||
"""
|
||||
Check whether interested table content changed
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def check_db_update(self, check_param):
|
||||
"""
|
||||
Fetch db and check update
|
||||
"""
|
||||
state, _ = self.sel.select(self.select_timeout)
|
||||
if state == swsscommon.Select.TIMEOUT or state != swsscommon.Select.OBJECT:
|
||||
return False
|
||||
return self._do_check(check_param)
|
||||
|
||||
|
||||
class DhcpRelaydDbMonitor(DhcpDbMonitor):
|
||||
subscribe_dhcp_server_table = None
|
||||
subscribe_vlan_table = None
|
||||
subscribe_vlan_intf_table = None
|
||||
|
||||
def subscribe_table(self):
|
||||
self.subscribe_dhcp_server_table = swsscommon.SubscriberStateTable(self.db_connector.config_db,
|
||||
DHCP_SERVER_IPV4)
|
||||
self.subscribe_vlan_table = swsscommon.SubscriberStateTable(self.db_connector.config_db, VLAN)
|
||||
self.subscribe_vlan_intf_table = swsscommon.SubscriberStateTable(self.db_connector.config_db, VLAN_INTERFACE)
|
||||
# Subscribe dhcp_server_ipv4 and vlan/vlan_interface table. No need to subscribe vlan_member table
|
||||
self.sel.addSelectable(self.subscribe_dhcp_server_table)
|
||||
self.sel.addSelectable(self.subscribe_vlan_table)
|
||||
self.sel.addSelectable(self.subscribe_vlan_intf_table)
|
||||
|
||||
def _do_check(self, check_param):
|
||||
if "enabled_dhcp_interfaces" not in check_param:
|
||||
syslog.syslog(syslog.LOG_ERR, "Cannot get enabled_dhcp_interfaces")
|
||||
return (True, True, True)
|
||||
enabled_dhcp_interfaces = check_param["enabled_dhcp_interfaces"]
|
||||
return (self._check_dhcp_server_update(enabled_dhcp_interfaces),
|
||||
self._check_vlan_update(enabled_dhcp_interfaces),
|
||||
self._check_vlan_intf_update(enabled_dhcp_interfaces))
|
||||
|
||||
def _check_dhcp_server_update(self, enabled_dhcp_interfaces):
|
||||
"""
|
||||
Check dhcp_server_ipv4 table
|
||||
Args:
|
||||
enabled_dhcp_interfaces: DHCP interface that enabled dhcp_server
|
||||
Returns:
|
||||
Whether need to refresh
|
||||
"""
|
||||
need_refresh = False
|
||||
while self.subscribe_dhcp_server_table.hasData():
|
||||
key, op, entry = self.subscribe_dhcp_server_table.pop()
|
||||
if op == "SET":
|
||||
for field, value in entry:
|
||||
if field != "state":
|
||||
continue
|
||||
# Only if new state is not consistent with old state, we need to refresh
|
||||
if key in enabled_dhcp_interfaces and value == "disabled":
|
||||
need_refresh = True
|
||||
elif key not in enabled_dhcp_interfaces and value == "enabled":
|
||||
need_refresh = True
|
||||
# For del operation, we can skip disabled change
|
||||
if op == "DEL":
|
||||
if key in enabled_dhcp_interfaces:
|
||||
need_refresh = True
|
||||
return need_refresh
|
||||
|
||||
def _check_vlan_update(self, enabled_dhcp_interfaces):
|
||||
"""
|
||||
Check vlan table
|
||||
Args:
|
||||
enabled_dhcp_interfaces: DHCP interface that enabled dhcp_server
|
||||
Returns:
|
||||
Whether need to refresh
|
||||
"""
|
||||
need_refresh = False
|
||||
while self.subscribe_vlan_table.hasData():
|
||||
key, op, _ = self.subscribe_vlan_table.pop()
|
||||
# For vlan doesn't have related dhcp entry, not need to refresh dhcrelay process
|
||||
if key not in enabled_dhcp_interfaces:
|
||||
continue
|
||||
need_refresh = True
|
||||
return need_refresh
|
||||
|
||||
def _check_vlan_intf_update(self, enabled_dhcp_interfaces):
|
||||
"""
|
||||
Check vlan_interface table
|
||||
Args:
|
||||
enabled_dhcp_interfaces: DHCP interface that enabled dhcp_server
|
||||
Returns:
|
||||
Whether need to refresh
|
||||
"""
|
||||
need_refresh = False
|
||||
while self.subscribe_vlan_intf_table.hasData():
|
||||
key, _, _ = self.subscribe_vlan_intf_table.pop()
|
||||
splits = key.split("|")
|
||||
vlan_name = splits[0]
|
||||
ip_address = splits[1].split("/")[0] if len(splits) > 1 else None
|
||||
if vlan_name not in enabled_dhcp_interfaces:
|
||||
continue
|
||||
if ip_address is None or ipaddress.ip_address(ip_address).version != 4:
|
||||
continue
|
||||
need_refresh = True
|
||||
return need_refresh
|
@ -56,6 +56,16 @@ def get_entry(table, entry_name):
|
||||
return dict(entry)
|
||||
|
||||
|
||||
def terminate_proc(proc):
|
||||
"""
|
||||
Terminate process, to make sure it exit successfully
|
||||
Args:
|
||||
proc: Process object in psutil
|
||||
"""
|
||||
proc.terminate()
|
||||
proc.wait()
|
||||
|
||||
|
||||
def merge_intervals(intervals):
|
||||
"""
|
||||
Merge ip range intervals.
|
191
src/sonic-dhcp-server/dhcp_server/dhcprelayd/dhcprelayd.py
Normal file
191
src/sonic-dhcp-server/dhcp_server/dhcprelayd/dhcprelayd.py
Normal file
@ -0,0 +1,191 @@
|
||||
# TODO Add support for running different dhcrelay processes for each dhcp interface
|
||||
# Currently if we run multiple dhcrelay processes, except for the last running process,
|
||||
# others will not relay dhcp_release packet.
|
||||
import psutil
|
||||
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.dhcp_db_monitor import DhcpRelaydDbMonitor
|
||||
|
||||
REDIS_SOCK_PATH = "/var/run/redis/redis.sock"
|
||||
DHCP_SERVER_IPV4_SERVER_IP = "DHCP_SERVER_IPV4_SERVER_IP"
|
||||
DHCP_SERVER_IPV4 = "DHCP_SERVER_IPV4"
|
||||
VLAN = "VLAN"
|
||||
VLAN_INTERFACE = "VLAN_INTERFACE"
|
||||
DEFAULT_SELECT_TIMEOUT = 5000 # millisecond
|
||||
DHCP_SERVER_INTERFACE = "eth0"
|
||||
DEFAULT_REFRESH_INTERVAL = 2
|
||||
|
||||
KILLED_OLD = 1
|
||||
NOT_KILLED = 2
|
||||
NOT_FOUND_PROC = 3
|
||||
|
||||
|
||||
class DhcpRelayd(object):
|
||||
sel = None
|
||||
enabled_dhcp_interfaces = set()
|
||||
|
||||
def __init__(self, db_connector, select_timeout=DEFAULT_SELECT_TIMEOUT):
|
||||
"""
|
||||
Args:
|
||||
db_connector: db connector obj
|
||||
select_timeout: timeout setting for subscribe db change
|
||||
"""
|
||||
self.db_connector = db_connector
|
||||
self.last_refresh_time = None
|
||||
self.dhcp_relayd_monitor = DhcpRelaydDbMonitor(db_connector, select_timeout)
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Start function
|
||||
"""
|
||||
self.refresh_dhcrelay()
|
||||
self.dhcp_relayd_monitor.subscribe_table()
|
||||
|
||||
def refresh_dhcrelay(self, force_kill=False):
|
||||
"""
|
||||
To refresh dhcrelay/dhcpmon process (start or restart)
|
||||
"""
|
||||
syslog.syslog(syslog.LOG_INFO, "Start to refresh dhcrelay related processes")
|
||||
dhcp_server_ip = self._get_dhcp_server_ip()
|
||||
dhcp_server_ipv4_table = self.db_connector.get_config_db_table(DHCP_SERVER_IPV4)
|
||||
vlan_table = self.db_connector.get_config_db_table(VLAN)
|
||||
|
||||
dhcp_interfaces = set()
|
||||
self.enabled_dhcp_interfaces = set()
|
||||
for dhcp_interface, config in dhcp_server_ipv4_table.items():
|
||||
# Reason for add to enabled_dhcp_interfaces firstly is for below scenario:
|
||||
# Firstly vlan 1000 is not in vlan table but enabled in dhcp_server table, then add vlan1000 to vlan table
|
||||
# we need to refresh
|
||||
if config["state"] == "enabled":
|
||||
dhcp_interfaces.add(dhcp_interface)
|
||||
self.enabled_dhcp_interfaces.add(dhcp_interface)
|
||||
if dhcp_interface not in vlan_table:
|
||||
dhcp_interfaces.discard(dhcp_interface)
|
||||
continue
|
||||
self._start_dhcrelay_process(dhcp_interfaces, dhcp_server_ip, force_kill)
|
||||
self._start_dhcpmon_process(dhcp_interfaces, force_kill)
|
||||
|
||||
def wait(self):
|
||||
"""
|
||||
Wait function, check db change here
|
||||
"""
|
||||
while True:
|
||||
res = (self.dhcp_relayd_monitor.check_db_update({"enabled_dhcp_interfaces": self.enabled_dhcp_interfaces}))
|
||||
# Select timeout or no successful
|
||||
if isinstance(res, bool):
|
||||
continue
|
||||
(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)
|
||||
|
||||
def _start_dhcrelay_process(self, new_dhcp_interfaces, dhcp_server_ip, force_kill):
|
||||
# To check whether need to kill dhcrelay process
|
||||
kill_res = self._kill_exist_relay_releated_process(new_dhcp_interfaces, "dhcrelay", force_kill)
|
||||
if kill_res == NOT_KILLED:
|
||||
# Means old running status consistent with the new situation, no need to run new
|
||||
return
|
||||
|
||||
# No need to start new dhcrelay process
|
||||
if len(new_dhcp_interfaces) == 0:
|
||||
return
|
||||
|
||||
cmds = ["/usr/sbin/dhcrelay", "-d", "-m", "discard", "-a", "%h:%p", "%P", "--name-alias-map-file",
|
||||
"/tmp/port-name-alias-map.txt"]
|
||||
for dhcp_interface in new_dhcp_interfaces:
|
||||
cmds += ["-id", dhcp_interface]
|
||||
cmds += ["-iu", "docker0", dhcp_server_ip]
|
||||
popen_res = subprocess.Popen(cmds)
|
||||
# To make sure process start successfully not in zombie status
|
||||
proc = psutil.Process(popen_res.pid)
|
||||
time.sleep(1)
|
||||
if proc.status() == psutil.STATUS_ZOMBIE:
|
||||
syslog.syslog(syslog.LOG_ERR, "Failed to start dhcrelay process with: {}".format(cmds))
|
||||
terminate_proc(proc)
|
||||
sys.exit(1)
|
||||
|
||||
syslog.syslog(syslog.LOG_INFO, "dhcrelay process started successfully, cmds: {}".format(cmds))
|
||||
|
||||
def _start_dhcpmon_process(self, new_dhcp_interfaces, force_kill):
|
||||
# To check whether need to kill dhcrelay process
|
||||
kill_res = self._kill_exist_relay_releated_process(new_dhcp_interfaces, "dhcpmon", force_kill)
|
||||
if kill_res == NOT_KILLED:
|
||||
# Means old running status consistent with the new situation, no need to run new
|
||||
return
|
||||
|
||||
# No need to start new dhcrelay process
|
||||
if len(new_dhcp_interfaces) == 0:
|
||||
return
|
||||
|
||||
pids_cmds = {}
|
||||
for dhcp_interface in new_dhcp_interfaces:
|
||||
cmds = ["/usr/sbin/dhcpmon", "-id", dhcp_interface, "-iu", "docker0", "-im", "eth0"]
|
||||
popen_res = subprocess.Popen(cmds)
|
||||
pids_cmds[popen_res.pid] = cmds
|
||||
time.sleep(1)
|
||||
# To make sure process start successfully not in zombie status
|
||||
for pid, cmds in pids_cmds.items():
|
||||
proc = psutil.Process(pid)
|
||||
if proc.status() == psutil.STATUS_ZOMBIE:
|
||||
syslog.syslog(syslog.LOG_ERR, "Faild to start dhcpmon process: {}".format(cmds))
|
||||
terminate_proc(proc)
|
||||
else:
|
||||
syslog.syslog(syslog.LOG_INFO, "dhcpmon process started successfully, cmds: {}".format(cmds))
|
||||
|
||||
def _kill_exist_relay_releated_process(self, new_dhcp_interfaces, process_name, force_kill):
|
||||
old_dhcp_interfaces = set()
|
||||
# Because in system there maybe more than 1 dhcpmon processes are running, so we need list to store
|
||||
target_procs = []
|
||||
|
||||
# Get old dhcrelay process and get old dhcp interfaces
|
||||
for proc in psutil.process_iter():
|
||||
if proc.name() == process_name:
|
||||
cmds = proc.cmdline()
|
||||
index = 0
|
||||
target_procs.append(proc)
|
||||
while index < len(cmds):
|
||||
if cmds[index] == "-id":
|
||||
old_dhcp_interfaces.add(cmds[index + 1])
|
||||
index += 2
|
||||
else:
|
||||
index += 1
|
||||
if len(target_procs) == 0:
|
||||
return NOT_FOUND_PROC
|
||||
|
||||
# No need to kill
|
||||
if not force_kill and (process_name == "dhcrelay" and old_dhcp_interfaces == new_dhcp_interfaces or
|
||||
process_name == "dhcpmon" and old_dhcp_interfaces == (new_dhcp_interfaces)):
|
||||
return NOT_KILLED
|
||||
for proc in target_procs:
|
||||
terminate_proc(proc)
|
||||
syslog.syslog(syslog.LOG_INFO, "Kill process: {}".format(process_name))
|
||||
return KILLED_OLD
|
||||
|
||||
def _get_dhcp_server_ip(self):
|
||||
dhcp_server_ip_table = swsscommon.Table(self.db_connector.state_db, DHCP_SERVER_IPV4_SERVER_IP)
|
||||
for _ in range(10):
|
||||
state, ip = dhcp_server_ip_table.hget(DHCP_SERVER_INTERFACE, "ip")
|
||||
if state:
|
||||
return ip
|
||||
else:
|
||||
syslog.syslog(syslog.LOG_INFO, "Cannot get dhcp server ip")
|
||||
time.sleep(10)
|
||||
syslog.syslog(syslog.LOG_ERR, "Cannot get dhcp_server ip from state_db")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
dhcp_db_connector = DhcpDbConnector(redis_sock=REDIS_SOCK_PATH)
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector)
|
||||
dhcprelayd.start()
|
||||
dhcprelayd.wait()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,12 +1,11 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import ipaddress
|
||||
import json
|
||||
import os
|
||||
import syslog
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from .dhcp_server_utils import merge_intervals
|
||||
from dhcp_server.common.utils import merge_intervals
|
||||
|
||||
PORT_MAP_PATH = "/tmp/port-name-alias-map.txt"
|
||||
UNICODE_TYPE = str
|
@ -1,5 +1,3 @@
|
||||
import ipaddress
|
||||
import json
|
||||
import signal
|
||||
import syslog
|
||||
import threading
|
||||
@ -66,12 +64,13 @@ class LeaseHanlder(object):
|
||||
old_lease_table = self.db_connector.get_state_db_table(DHCP_SERVER_IPV4_LEASE)
|
||||
old_lease_key = set(old_lease_table.keys())
|
||||
|
||||
# 1.1 If start time equal to end time, means lease has been released
|
||||
# 1.1 If start time equal to end time or lease expired, means lease has been released
|
||||
# 1.1.1 If current lease table has this old lease, delete it
|
||||
# 1.1.2 Else skip
|
||||
# 1.2 Else, means lease valid, save it.
|
||||
for key, value in new_lease.items():
|
||||
if value["lease_start"] == value["lease_end"]:
|
||||
unix_time = datetime.now().timestamp()
|
||||
if value["lease_start"] == value["lease_end"] or unix_time >= int(value["lease_end"]):
|
||||
if key in old_lease_key:
|
||||
self.db_connector.state_db.delete("{}|{}".format(DHCP_SERVER_IPV4_LEASE, key))
|
||||
continue
|
@ -2,14 +2,19 @@
|
||||
import psutil
|
||||
import signal
|
||||
import time
|
||||
import sys
|
||||
import syslog
|
||||
from .dhcp_cfggen import DhcpServCfgGenerator
|
||||
from .dhcp_lease import LeaseManager
|
||||
from .dhcp_server_utils import DhcpDbConnector
|
||||
from dhcp_server.common.utils import DhcpDbConnector
|
||||
|
||||
KEA_DHCP4_CONFIG = "/etc/kea/kea-dhcp4.conf"
|
||||
KEA_DHCP4_PROC_NAME = "kea-dhcp4"
|
||||
KEA_LEASE_FILE_PATH = "/tmp/kea-lease.csv"
|
||||
REDIS_SOCK_PATH = "/var/run/redis/redis.sock"
|
||||
DHCP_SERVER_IPV4_SERVER_IP = "DHCP_SERVER_IPV4_SERVER_IP"
|
||||
DHCP_SERVER_INTERFACE = "eth0"
|
||||
AF_INET = 2
|
||||
|
||||
|
||||
class DhcpServd(object):
|
||||
@ -37,8 +42,27 @@ class DhcpServd(object):
|
||||
# After refresh kea-config, we need to SIGHUP kea-dhcp4 process to read new config
|
||||
self._notify_kea_dhcp4_proc()
|
||||
|
||||
def _update_dhcp_server_ip(self):
|
||||
"""
|
||||
Add ip address of "eth0" inside dhcp_server container as dhcp_server_ip into state_db
|
||||
"""
|
||||
dhcp_server_ip = None
|
||||
for _ in range(10):
|
||||
dhcp_interface = psutil.net_if_addrs().get(DHCP_SERVER_INTERFACE, [])
|
||||
for address in dhcp_interface:
|
||||
if address.family == AF_INET:
|
||||
dhcp_server_ip = address.address
|
||||
self.db_connector.state_db.hset("{}|{}".format(DHCP_SERVER_IPV4_SERVER_IP, DHCP_SERVER_INTERFACE),
|
||||
"ip", dhcp_server_ip)
|
||||
return
|
||||
else:
|
||||
time.sleep(5)
|
||||
syslog.syslog(syslog.LOG_INFO, "Cannot get ip address of {}".format(DHCP_SERVER_INTERFACE))
|
||||
sys.exit(1)
|
||||
|
||||
def start(self):
|
||||
self.dump_dhcp4_config()
|
||||
self._update_dhcp_server_ip()
|
||||
lease_manager = LeaseManager(self.db_connector, KEA_LEASE_FILE_PATH)
|
||||
lease_manager.start()
|
||||
|
@ -1,18 +1,12 @@
|
||||
from setuptools import setup
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
dependencies = [
|
||||
"psutil",
|
||||
"coverage"
|
||||
"psutil"
|
||||
]
|
||||
|
||||
test_deps = [
|
||||
"pytest"
|
||||
]
|
||||
|
||||
py_modules = [
|
||||
"dhcp_server_utils",
|
||||
"dhcp_cfggen",
|
||||
"dhcp_lease"
|
||||
"pytest",
|
||||
"freezegun"
|
||||
]
|
||||
|
||||
setup(
|
||||
@ -29,14 +23,16 @@ setup(
|
||||
"wheel",
|
||||
],
|
||||
packages=[
|
||||
"dhcp_server"
|
||||
"dhcp_server.common",
|
||||
"dhcp_server.dhcpservd",
|
||||
"dhcp_server.dhcprelayd"
|
||||
],
|
||||
entry_points={
|
||||
"console_scripts": [
|
||||
"dhcpservd = dhcp_server.dhcpservd:main"
|
||||
"dhcprelayd = dhcp_server.dhcprelayd.dhcprelayd:main",
|
||||
"dhcpservd = dhcp_server.dhcpservd.dhcpservd:main"
|
||||
]
|
||||
},
|
||||
py_modules=py_modules,
|
||||
classifiers=[
|
||||
"Intended Audience :: Developers",
|
||||
"Operating System :: Linux",
|
||||
|
@ -1,6 +1,9 @@
|
||||
import heapq
|
||||
import json
|
||||
import psutil
|
||||
|
||||
MOCK_CONFIG_DB_PATH = "tests/test_data/mock_config_db.json"
|
||||
MOCK_STATE_DB_PATH = "tests/test_data/mock_state_db.json"
|
||||
|
||||
|
||||
class MockConfigDb(object):
|
||||
@ -10,3 +13,75 @@ class MockConfigDb(object):
|
||||
|
||||
def get_config_db_table(self, table_name):
|
||||
return self.config_db.get(table_name, {})
|
||||
|
||||
|
||||
class MockSelect(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def select(self, timeout):
|
||||
return None, None
|
||||
|
||||
|
||||
class MockSubscribeTable(object):
|
||||
def __init__(self, tables):
|
||||
self.stack = []
|
||||
for item in tables:
|
||||
heapq.heappush(self.stack, item)
|
||||
# if table_name == "DHCP_SERVER_IPV4":
|
||||
# heapq.heappush(self.stack, ("Vlan1000", "SET", (("state", "enabled"),)))
|
||||
# heapq.heappush(self.stack, ("Vlan1000", "SET", (("customized_options", "option1"), ("state", "enabled"),)))
|
||||
# heapq.heappush(self.stack, ("Vlan2000", "SET", (("state", "enabled"),)))
|
||||
# heapq.heappush(self.stack, ("Vlan1000", "DEL", ()))
|
||||
# heapq.heappush(self.stack, ("Vlan2000", "DEL", ()))
|
||||
# if table_name == "VLAN":
|
||||
# heapq.heappush(self.stack, ("Vlan1000", "SET", (("vlanid", "1000"),)))
|
||||
# heapq.heappush(self.stack, ("Vlan1001", "SET", (("vlanid", "1001"),)))
|
||||
# heapq.heappush(self.stack, ("Vlan1001", "DEL", (("vlanid", "1001"),)))
|
||||
# heapq.heappush(self.stack, ("Vlan1002", "SET", (("vlanid", "1002"),)))
|
||||
# heapq.heappush(self.stack, ("Vlan2000", "SET", (("vlanid", "2000"),)))
|
||||
|
||||
def pop(self):
|
||||
res = heapq.heappop(self.stack)
|
||||
return res
|
||||
|
||||
def hasData(self):
|
||||
return len(self.stack) != 0
|
||||
|
||||
|
||||
def mock_get_config_db_table(table_name):
|
||||
mock_config_db = MockConfigDb()
|
||||
return mock_config_db.get_config_db_table(table_name)
|
||||
|
||||
|
||||
class MockProc(object):
|
||||
def __init__(self, name, pid=None, status=psutil.STATUS_RUNNING):
|
||||
self.proc_name = name
|
||||
self.pid = pid
|
||||
|
||||
def name(self):
|
||||
return self.proc_name
|
||||
|
||||
def send_signal(self, sig_num):
|
||||
pass
|
||||
|
||||
def cmdline(self):
|
||||
if self.proc_name == "dhcrelay":
|
||||
return ["/usr/sbin/dhcrelay", "-d", "-m", "discard", "-a", "%h:%p", "%P", "--name-alias-map-file",
|
||||
"/tmp/port-name-alias-map.txt", "-id", "Vlan1000", "-iu", "docker0", "240.127.1.2"]
|
||||
if self.proc_name == "dhcpmon":
|
||||
return ["/usr/sbin/dhcpmon", "-id", "Vlan1000", "-iu", "docker0", "-im", "eth0"]
|
||||
|
||||
def terminate(self):
|
||||
pass
|
||||
|
||||
def wait(self):
|
||||
pass
|
||||
|
||||
def status(self):
|
||||
return self.status
|
||||
|
||||
|
||||
class MockPopen(object):
|
||||
def __init__(self, pid):
|
||||
self.pid = pid
|
||||
|
@ -1,30 +1,38 @@
|
||||
import pytest
|
||||
import dhcp_server.dhcp_server_utils as dhcp_server_utils
|
||||
import dhcp_server.common.utils as utils
|
||||
import os
|
||||
import sys
|
||||
from unittest.mock import patch, PropertyMock
|
||||
from dhcp_server.dhcp_cfggen import DhcpServCfgGenerator
|
||||
from dhcp_server.dhcpservd.dhcp_cfggen import DhcpServCfgGenerator
|
||||
|
||||
test_path = os.path.dirname(os.path.abspath(__file__))
|
||||
modules_path = os.path.dirname(test_path)
|
||||
sys.path.insert(0, test_path)
|
||||
sys.path.insert(0, modules_path)
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_swsscommon_dbconnector_init():
|
||||
with patch.object(dhcp_server_utils.swsscommon.DBConnector, "__init__", return_value=None) as mock_dbconnector_init:
|
||||
with patch.object(utils.swsscommon.DBConnector, "__init__", return_value=None) as mock_dbconnector_init:
|
||||
yield mock_dbconnector_init
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_swsscommon_table_init():
|
||||
with patch.object(dhcp_server_utils.swsscommon.Table, "__init__", return_value=None) as mock_table_init:
|
||||
with patch.object(utils.swsscommon.Table, "__init__", return_value=None) as mock_table_init:
|
||||
yield mock_table_init
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_get_render_template():
|
||||
with patch("dhcp_server.dhcp_cfggen.DhcpServCfgGenerator._get_render_template", return_value=None) as mock_template:
|
||||
with patch("dhcp_server.dhcpservd.dhcp_cfggen.DhcpServCfgGenerator._get_render_template",
|
||||
return_value=None) as mock_template:
|
||||
yield mock_template
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_parse_port_map_alias(scope="function"):
|
||||
with patch("dhcp_server.dhcp_cfggen.DhcpServCfgGenerator._parse_port_map_alias", return_value=None) as mock_map, \
|
||||
with patch("dhcp_server.dhcpservd.dhcp_cfggen.DhcpServCfgGenerator._parse_port_map_alias",
|
||||
return_value=None) as mock_map, \
|
||||
patch.object(DhcpServCfgGenerator, "port_alias_map", return_value={"Ethernet24": "etp7", "Ethernet28": "etp8"},
|
||||
new_callable=PropertyMock), \
|
||||
patch.object(DhcpServCfgGenerator, "lease_update_script_path", return_value="/etc/kea/lease_update.sh",
|
||||
@ -32,4 +40,3 @@ def mock_parse_port_map_alias(scope="function"):
|
||||
patch.object(DhcpServCfgGenerator, "lease_path", return_value="/tmp/kea-lease.csv", new_callable=PropertyMock):
|
||||
yield mock_map
|
||||
|
||||
|
||||
|
@ -6,4 +6,5 @@ address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostn
|
||||
192.168.0.131,10:70:fd:b6:13:17,,3600,1694000915,1,0,0,7626dced293e,0,,1
|
||||
192.168.0.2,10:70:fd:b6:13:00,,0,1693997305,1,0,0,7626dced293e,0,,0
|
||||
193.168.2.2,10:70:fd:b6:13:15,,3600,1693999305,1,0,0,7626dced293e,0,,0
|
||||
193.168.2.3,10:70:fd:b6:13:20,,3600,1693999305,1,0,0,7626dced293e,0,,0
|
||||
193.168.2.3,10:70:fd:b6:13:20,,3600,1693999305,1,0,0,7626dced293e,0,,0
|
||||
193.168.0.132,10:70:fd:b6:13:18,,3600,1697610805,1,0,0,7626dced293e,0,,0
|
|
@ -4,6 +4,10 @@
|
||||
"hostname": "sonic-host"
|
||||
}
|
||||
},
|
||||
"VLAN": {
|
||||
"Vlan1000": {},
|
||||
"Vlan2000": {}
|
||||
},
|
||||
"VLAN_INTERFACE": {
|
||||
"Vlan1000|192.168.0.1/21": {
|
||||
"NULL": "NULL"
|
||||
@ -77,6 +81,17 @@
|
||||
"mode": "PORT",
|
||||
"netmask": "255.255.255.0",
|
||||
"state": "enabled"
|
||||
},
|
||||
"Vlan5000": {
|
||||
"customized_options": [
|
||||
"option60",
|
||||
"option223"
|
||||
],
|
||||
"gateway": "192.168.4.1",
|
||||
"lease_time": "900",
|
||||
"mode": "PORT",
|
||||
"netmask": "255.255.255.0",
|
||||
"state": "disabled"
|
||||
}
|
||||
},
|
||||
"DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS": {
|
||||
|
@ -3,9 +3,8 @@ import ipaddress
|
||||
import json
|
||||
import pytest
|
||||
from common_utils import MockConfigDb
|
||||
from dhcp_server.dhcp_server_utils import DhcpDbConnector
|
||||
from dhcp_server.dhcp_cfggen import DhcpServCfgGenerator
|
||||
from unittest.mock import patch, MagicMock
|
||||
from dhcp_server.common.utils import DhcpDbConnector
|
||||
from dhcp_server.dhcpservd.dhcp_cfggen import DhcpServCfgGenerator
|
||||
|
||||
expected_dhcp_config = {
|
||||
"Dhcp4": {
|
||||
|
219
src/sonic-dhcp-server/tests/test_dhcp_db_monitor.py
Normal file
219
src/sonic-dhcp-server/tests/test_dhcp_db_monitor.py
Normal file
@ -0,0 +1,219 @@
|
||||
import pytest
|
||||
from common_utils import MockSubscribeTable
|
||||
from dhcp_server.common.dhcp_db_monitor import DhcpDbMonitor, DhcpRelaydDbMonitor
|
||||
from dhcp_server.common.utils import DhcpDbConnector
|
||||
from swsscommon import swsscommon
|
||||
from unittest.mock import patch, call, ANY, PropertyMock
|
||||
|
||||
tested_subscribe_dhcp_server_table = [
|
||||
{
|
||||
"table": [
|
||||
("Vlan1000", "SET", (("customized_options", "option1"), ("state", "enabled"),))
|
||||
],
|
||||
"exp_res": False
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan2000", "SET", (("state", "enabled"),))
|
||||
],
|
||||
"exp_res": True
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan1000", "DEL", ())
|
||||
],
|
||||
"exp_res": True
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan2000", "DEL", ())
|
||||
],
|
||||
"exp_res": False
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan2000", "DEL", ()),
|
||||
("Vlan1000", "DEL", ())
|
||||
],
|
||||
"exp_res": True
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan3000", "SET", (("state", "enabled"),))
|
||||
],
|
||||
"exp_res": True
|
||||
}
|
||||
]
|
||||
tested_subscribe_vlan_table = [
|
||||
{
|
||||
"table": [
|
||||
("Vlan1000", "SET", (("vlanid", "1000"),))
|
||||
],
|
||||
"exp_res": True
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan1001", "SET", (("vlanid", "1001"),))
|
||||
],
|
||||
"exp_res": False
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan1000", "SET", (("vlanid", "1000"),)),
|
||||
("Vlan1002", "SET", (("vlanid", "1002"),))
|
||||
],
|
||||
"exp_res": True
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan1001", "DEL", ())
|
||||
],
|
||||
"exp_res": False
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan1000", "DEL", ())
|
||||
],
|
||||
"exp_res": True
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan1000", "SET", (("vlanid", "1000"),)),
|
||||
("Vlan1001", "DEL", ())
|
||||
],
|
||||
"exp_res": True
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan1003", "SET", (("vlanid", "1003"),))
|
||||
],
|
||||
"exp_res": False
|
||||
}
|
||||
]
|
||||
tested_subscribe_vlan_intf_table = [
|
||||
{
|
||||
"table": [
|
||||
("Vlan1000", "SET", ())
|
||||
],
|
||||
"exp_res": False
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan1000|192.168.0.1/24", "SET", ())
|
||||
],
|
||||
"exp_res": True
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan1000|fc02::8/64", "SET", ())
|
||||
],
|
||||
"exp_res": False
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan2000|192.168.0.1/24", "SET", ())
|
||||
],
|
||||
"exp_res": False
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan1001|192.168.0.1/24", "SET", ())
|
||||
],
|
||||
"exp_res": False
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("select_result", [swsscommon.Select.TIMEOUT, swsscommon.Select.OBJECT])
|
||||
def test_dhcp_db_monitor(mock_swsscommon_dbconnector_init, select_result):
|
||||
db_connector = DhcpDbConnector()
|
||||
dhcp_db_monitor = DhcpDbMonitor(db_connector)
|
||||
try:
|
||||
dhcp_db_monitor.subscribe_table()
|
||||
except NotImplementedError:
|
||||
pass
|
||||
try:
|
||||
dhcp_db_monitor._do_check()
|
||||
except NotImplementedError:
|
||||
pass
|
||||
with patch.object(DhcpDbMonitor, "_do_check", return_value=None) as mock_do_check, \
|
||||
patch.object(swsscommon.Select, "select", return_value=(select_result, None)):
|
||||
dhcp_db_monitor.check_db_update("mock_param")
|
||||
if select_result == swsscommon.Select.TIMEOUT:
|
||||
mock_do_check.assert_not_called()
|
||||
elif select_result == swsscommon.Select.OBJECT:
|
||||
mock_do_check.assert_called_once_with("mock_param")
|
||||
|
||||
|
||||
def test_dhcp_relayd_monitor_subscribe_table(mock_swsscommon_dbconnector_init):
|
||||
with patch.object(swsscommon, "SubscriberStateTable", side_effect=mock_subscriber_state_table) as mock_subscribe, \
|
||||
patch.object(swsscommon.Select, "addSelectable", return_value=None) as mock_add_select:
|
||||
db_connector = DhcpDbConnector()
|
||||
dhcp_relayd_db_monitor = DhcpRelaydDbMonitor(db_connector)
|
||||
dhcp_relayd_db_monitor.subscribe_table()
|
||||
mock_subscribe.assert_has_calls([
|
||||
call(ANY, "DHCP_SERVER_IPV4"),
|
||||
call(ANY, "VLAN"),
|
||||
call(ANY, "VLAN_INTERFACE")
|
||||
])
|
||||
mock_add_select.assert_has_calls([
|
||||
call("DHCP_SERVER_IPV4"),
|
||||
call("VLAN"),
|
||||
call("VLAN_INTERFACE")
|
||||
])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("check_param", [{}, {"enabled_dhcp_interfaces": "dummy"}])
|
||||
def test_dhcp_relayd_monitor_do_check(mock_swsscommon_dbconnector_init, check_param):
|
||||
with patch.object(DhcpRelaydDbMonitor, "_check_dhcp_server_update") as mock_check_dhcp_server_update, \
|
||||
patch.object(DhcpRelaydDbMonitor, "_check_vlan_update") as mock_check_vlan_update, \
|
||||
patch.object(DhcpRelaydDbMonitor, "_check_vlan_intf_update") as mock_check_vlan_intf_update:
|
||||
db_connector = DhcpDbConnector()
|
||||
dhcp_relayd_db_monitor = DhcpRelaydDbMonitor(db_connector)
|
||||
dhcp_relayd_db_monitor._do_check(check_param)
|
||||
if "enabled_dhcp_interfaces" in check_param:
|
||||
mock_check_dhcp_server_update.assert_called_once_with("dummy")
|
||||
mock_check_vlan_update.assert_called_once_with("dummy")
|
||||
mock_check_vlan_intf_update.assert_called_once_with("dummy")
|
||||
else:
|
||||
mock_check_dhcp_server_update.assert_not_called()
|
||||
mock_check_vlan_update.assert_not_called()
|
||||
mock_check_vlan_intf_update.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dhcp_server_table_update", tested_subscribe_dhcp_server_table)
|
||||
def test_dhcp_relayd_monitor_check_dhcp_server_update(mock_swsscommon_dbconnector_init, dhcp_server_table_update):
|
||||
tested_table = dhcp_server_table_update["table"]
|
||||
with patch.object(DhcpRelaydDbMonitor, "subscribe_dhcp_server_table",
|
||||
return_value=MockSubscribeTable(tested_table),
|
||||
new_callable=PropertyMock):
|
||||
db_connector = DhcpDbConnector()
|
||||
dhcp_relayd_db_monitor = DhcpRelaydDbMonitor(db_connector)
|
||||
check_res = dhcp_relayd_db_monitor._check_dhcp_server_update(set(["Vlan1000"]))
|
||||
assert check_res == dhcp_server_table_update["exp_res"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("vlan_table_update", tested_subscribe_vlan_table)
|
||||
def test_dhcp_relayd_monitor_check_vlan_update(mock_swsscommon_dbconnector_init, vlan_table_update):
|
||||
tested_table = vlan_table_update["table"]
|
||||
with patch.object(DhcpRelaydDbMonitor, "subscribe_vlan_table", return_value=MockSubscribeTable(tested_table),
|
||||
new_callable=PropertyMock):
|
||||
db_connector = DhcpDbConnector()
|
||||
dhcp_relayd_db_monitor = DhcpRelaydDbMonitor(db_connector)
|
||||
check_res = dhcp_relayd_db_monitor._check_vlan_update(set(["Vlan1000"]))
|
||||
assert check_res == vlan_table_update["exp_res"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("vlan_intf_table_update", tested_subscribe_vlan_intf_table)
|
||||
def test_dhcp_relayd_monitor_check_vlan_intf_update(mock_swsscommon_dbconnector_init, vlan_intf_table_update):
|
||||
tested_table = vlan_intf_table_update["table"]
|
||||
with patch.object(DhcpRelaydDbMonitor, "subscribe_vlan_intf_table", return_value=MockSubscribeTable(tested_table),
|
||||
new_callable=PropertyMock):
|
||||
db_connector = DhcpDbConnector()
|
||||
dhcp_relayd_db_monitor = DhcpRelaydDbMonitor(db_connector)
|
||||
check_res = dhcp_relayd_db_monitor._check_vlan_intf_update(set(["Vlan1000"]))
|
||||
assert check_res == vlan_intf_table_update["exp_res"]
|
||||
|
||||
|
||||
def mock_subscriber_state_table(db, table_name):
|
||||
return table_name
|
@ -1,5 +1,6 @@
|
||||
from dhcp_server.dhcp_server_utils import DhcpDbConnector
|
||||
from dhcp_server.dhcp_lease import KeaDhcp4LeaseHandler, LeaseHanlder
|
||||
from dhcp_server.common.utils import DhcpDbConnector
|
||||
from dhcp_server.dhcpservd.dhcp_lease import KeaDhcp4LeaseHandler, LeaseHanlder
|
||||
from freezegun import freeze_time
|
||||
from swsscommon import swsscommon
|
||||
from unittest.mock import patch, call, MagicMock
|
||||
|
||||
@ -14,6 +15,11 @@ expected_lease = {
|
||||
"lease_end": "1694000915",
|
||||
"ip": "192.168.0.131"
|
||||
},
|
||||
"Vlan1000|10:70:fd:b6:13:18": {
|
||||
"lease_start": "1697607205",
|
||||
"lease_end": "1697610805",
|
||||
"ip": "193.168.0.132"
|
||||
},
|
||||
"Vlan2000|10:70:fd:b6:13:15": {
|
||||
"lease_start": "1693995705",
|
||||
"lease_end": "1693999305",
|
||||
@ -24,6 +30,7 @@ expected_fdb_info = {
|
||||
"10:70:fd:b6:13:00": "Vlan1000",
|
||||
"10:70:fd:b6:13:15": "Vlan2000",
|
||||
"10:70:fd:b6:13:17": "Vlan1000",
|
||||
"10:70:fd:b6:13:18": "Vlan1000"
|
||||
}
|
||||
|
||||
|
||||
@ -43,8 +50,6 @@ def test_read_kea_lease(mock_swsscommon_dbconnector_init):
|
||||
kea_lease_handler = KeaDhcp4LeaseHandler(db_connector, lease_file="tests/test_data/kea-lease.csv")
|
||||
# Verify whether lease information read is as expected
|
||||
lease = kea_lease_handler._read()
|
||||
print(lease)
|
||||
print(expected_lease)
|
||||
assert lease == expected_lease
|
||||
|
||||
|
||||
@ -52,9 +57,10 @@ def test_get_fdb_info(mock_swsscommon_dbconnector_init):
|
||||
mock_fdb_table = {
|
||||
"Vlan2000:10:70:fd:b6:13:15": {"port": "Ethernet31", "type": "dynamic"},
|
||||
"Vlan1000:10:70:fd:b6:13:00": {"port": "Ethernet32", "type": "dynamic"},
|
||||
"Vlan1000:10:70:fd:b6:13:17": {"port": "Ethernet32", "type": "dynamic"}
|
||||
"Vlan1000:10:70:fd:b6:13:17": {"port": "Ethernet33", "type": "dynamic"},
|
||||
"Vlan1000:10:70:fd:b6:13:18": {"port": "Ethernet34", "type": "dynamic"}
|
||||
}
|
||||
with patch("dhcp_server.dhcp_server_utils.DhcpDbConnector.get_state_db_table", return_value=mock_fdb_table):
|
||||
with patch("dhcp_server.common.utils.DhcpDbConnector.get_state_db_table", return_value=mock_fdb_table):
|
||||
db_connector = DhcpDbConnector()
|
||||
kea_lease_handler = KeaDhcp4LeaseHandler(db_connector, lease_file="tests/test_data/kea-lease.csv")
|
||||
# Verify whether lease information read is as expected
|
||||
@ -62,13 +68,21 @@ def test_get_fdb_info(mock_swsscommon_dbconnector_init):
|
||||
assert fdb_info == expected_fdb_info
|
||||
|
||||
|
||||
# Cannot mock built-in/extension type function(datetime.datetime.timestamp), need to free time
|
||||
@freeze_time("2023-09-08")
|
||||
def test_update_kea_lease(mock_swsscommon_dbconnector_init, mock_swsscommon_table_init):
|
||||
tested_lease = expected_lease
|
||||
mock_lease_table = {
|
||||
"Vlan1000|aa:bb:cc:dd:ee:ff": {},
|
||||
"Vlan1000|10:70:fd:b6:13:00": {},
|
||||
"Vlan1000|10:70:fd:b6:13:17": {},
|
||||
"Vlan1000|10:70:fd:b6:13:18": {}
|
||||
}
|
||||
with patch.object(swsscommon.Table, "getKeys"), \
|
||||
patch.object(swsscommon.DBConnector, "hset") as mock_hset, \
|
||||
patch.object(KeaDhcp4LeaseHandler, "_read", MagicMock(return_value=tested_lease)), \
|
||||
patch.object(DhcpDbConnector, "get_state_db_table",
|
||||
return_value={"Vlan1000|aa:bb:cc:dd:ee:ff": {}, "Vlan1000|10:70:fd:b6:13:00": {}}), \
|
||||
return_value=mock_lease_table), \
|
||||
patch.object(swsscommon.DBConnector, "delete") as mock_delete, \
|
||||
patch("time.sleep", return_value=None) as mock_sleep:
|
||||
db_connector = DhcpDbConnector()
|
||||
@ -77,14 +91,15 @@ def test_update_kea_lease(mock_swsscommon_dbconnector_init, mock_swsscommon_tabl
|
||||
# Verify that old key was deleted
|
||||
mock_delete.assert_has_calls([
|
||||
call("DHCP_SERVER_IPV4_LEASE|Vlan1000|10:70:fd:b6:13:00"),
|
||||
call("DHCP_SERVER_IPV4_LEASE|Vlan1000|10:70:fd:b6:13:17"),
|
||||
call("DHCP_SERVER_IPV4_LEASE|Vlan1000|aa:bb:cc:dd:ee:ff")
|
||||
])
|
||||
# Verify that lease has been updated, to be noted that lease for "192.168.0.2" didn't been updated because
|
||||
# lease_start equals to lease_end
|
||||
mock_hset.assert_has_calls([
|
||||
call('DHCP_SERVER_IPV4_LEASE|Vlan1000|10:70:fd:b6:13:17', 'lease_start', '1693997315'),
|
||||
call('DHCP_SERVER_IPV4_LEASE|Vlan1000|10:70:fd:b6:13:17', 'lease_end', '1694000915'),
|
||||
call('DHCP_SERVER_IPV4_LEASE|Vlan1000|10:70:fd:b6:13:17', 'ip', '192.168.0.131')
|
||||
call("DHCP_SERVER_IPV4_LEASE|Vlan1000|10:70:fd:b6:13:18", "lease_start", "1697607205"),
|
||||
call("DHCP_SERVER_IPV4_LEASE|Vlan1000|10:70:fd:b6:13:18", "lease_end", "1697610805"),
|
||||
call("DHCP_SERVER_IPV4_LEASE|Vlan1000|10:70:fd:b6:13:18", "ip", "193.168.0.132")
|
||||
])
|
||||
kea_lease_handler.update_lease()
|
||||
mock_sleep.assert_called_once_with(2)
|
||||
|
129
src/sonic-dhcp-server/tests/test_dhcprelayd.py
Normal file
129
src/sonic-dhcp-server/tests/test_dhcprelayd.py
Normal file
@ -0,0 +1,129 @@
|
||||
import psutil
|
||||
import pytest
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from common_utils import mock_get_config_db_table, MockProc, MockPopen
|
||||
from dhcp_server.common.utils import DhcpDbConnector
|
||||
from dhcp_server.dhcprelayd.dhcprelayd import DhcpRelayd, KILLED_OLD, NOT_KILLED, NOT_FOUND_PROC
|
||||
from dhcp_server.common.dhcp_db_monitor import DhcpRelaydDbMonitor
|
||||
from swsscommon import swsscommon
|
||||
from unittest.mock import patch, call
|
||||
|
||||
|
||||
def test_start(mock_swsscommon_dbconnector_init):
|
||||
with patch.object(DhcpRelayd, "refresh_dhcrelay", return_value=None) as mock_refresh, \
|
||||
patch.object(DhcpRelaydDbMonitor, "subscribe_table", return_value=None) as mock_subscribe:
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector)
|
||||
dhcprelayd.start()
|
||||
mock_refresh.assert_called_once_with()
|
||||
mock_subscribe.assert_called_once_with()
|
||||
|
||||
|
||||
def test_refresh_dhcrelay(mock_swsscommon_dbconnector_init):
|
||||
with patch.object(DhcpRelayd, "_get_dhcp_server_ip", return_value="240.127.1.2"), \
|
||||
patch.object(DhcpDbConnector, "get_config_db_table", side_effect=mock_get_config_db_table), \
|
||||
patch.object(DhcpRelayd, "_start_dhcrelay_process", return_value=None), \
|
||||
patch.object(DhcpRelayd, "_start_dhcpmon_process", return_value=None):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector)
|
||||
dhcprelayd.refresh_dhcrelay()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("new_dhcp_interfaces", [[], ["Vlan1000"], ["Vlan1000", "Vlan2000"]])
|
||||
@pytest.mark.parametrize("kill_res", [KILLED_OLD, NOT_KILLED, NOT_FOUND_PROC])
|
||||
@pytest.mark.parametrize("proc_status", [psutil.STATUS_ZOMBIE, psutil.STATUS_RUNNING])
|
||||
def test_start_dhcrelay_process(mock_swsscommon_dbconnector_init, new_dhcp_interfaces, kill_res, proc_status):
|
||||
with patch.object(DhcpRelayd, "_kill_exist_relay_releated_process", return_value=kill_res), \
|
||||
patch.object(subprocess, "Popen", return_value=MockPopen(999)) as mock_popen, \
|
||||
patch.object(time, "sleep"), \
|
||||
patch("dhcp_server.dhcprelayd.dhcprelayd.terminate_proc", return_value=None) as mock_terminate, \
|
||||
patch.object(psutil.Process, "__init__", return_value=None), \
|
||||
patch.object(psutil.Process, "status", return_value=proc_status), \
|
||||
patch.object(sys, "exit") as mock_exit:
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector)
|
||||
dhcprelayd._start_dhcrelay_process(new_dhcp_interfaces, "240.127.1.2", False)
|
||||
if len(new_dhcp_interfaces) == 0 or kill_res == NOT_KILLED:
|
||||
mock_popen.assert_not_called()
|
||||
else:
|
||||
call_param = ["/usr/sbin/dhcrelay", "-d", "-m", "discard", "-a", "%h:%p", "%P", "--name-alias-map-file",
|
||||
"/tmp/port-name-alias-map.txt"]
|
||||
for interface in new_dhcp_interfaces:
|
||||
call_param += ["-id", interface]
|
||||
call_param += ["-iu", "docker0", "240.127.1.2"]
|
||||
mock_popen.assert_called_once_with(call_param)
|
||||
if len(new_dhcp_interfaces) != 0 and kill_res != NOT_KILLED and proc_status == psutil.STATUS_ZOMBIE:
|
||||
mock_terminate.assert_called_once()
|
||||
mock_exit.assert_called_once_with(1)
|
||||
else:
|
||||
mock_terminate.assert_not_called()
|
||||
mock_exit.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("new_dhcp_interfaces_list", [[], ["Vlan1000"], ["Vlan1000", "Vlan2000"]])
|
||||
@pytest.mark.parametrize("kill_res", [KILLED_OLD, NOT_KILLED, NOT_FOUND_PROC])
|
||||
@pytest.mark.parametrize("proc_status", [psutil.STATUS_ZOMBIE, psutil.STATUS_RUNNING])
|
||||
def test_start_dhcpmon_process(mock_swsscommon_dbconnector_init, new_dhcp_interfaces_list, kill_res, proc_status):
|
||||
new_dhcp_interfaces = set(new_dhcp_interfaces_list)
|
||||
with patch.object(DhcpRelayd, "_kill_exist_relay_releated_process", return_value=kill_res), \
|
||||
patch.object(subprocess, "Popen", return_value=MockPopen(999)) as mock_popen, \
|
||||
patch.object(time, "sleep"), \
|
||||
patch("dhcp_server.dhcprelayd.dhcprelayd.terminate_proc", return_value=None) as mock_terminate, \
|
||||
patch.object(psutil.Process, "__init__", return_value=None), \
|
||||
patch.object(psutil.Process, "status", return_value=proc_status):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector)
|
||||
dhcprelayd._start_dhcpmon_process(new_dhcp_interfaces, False)
|
||||
if len(new_dhcp_interfaces) == 0 or kill_res == NOT_KILLED:
|
||||
mock_popen.assert_not_called()
|
||||
else:
|
||||
calls = []
|
||||
for interface in new_dhcp_interfaces:
|
||||
call_param = ["/usr/sbin/dhcpmon", "-id", interface, "-iu", "docker0", "-im", "eth0"]
|
||||
calls.append(call(call_param))
|
||||
mock_popen.assert_has_calls(calls)
|
||||
if len(new_dhcp_interfaces) != 0 and kill_res != NOT_KILLED and proc_status == psutil.STATUS_ZOMBIE:
|
||||
mock_terminate.assert_called_once()
|
||||
else:
|
||||
mock_terminate.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("new_dhcp_interfaces_list", [[], ["Vlan1000"], ["Vlan1000", "Vlan2000"]])
|
||||
@pytest.mark.parametrize("process_name", ["dhcrelay", "dhcpmon"])
|
||||
@pytest.mark.parametrize("running_procs", [[], ["dhcrelay"], ["dhcpmon"], ["dhcrelay", "dhcpmon"]])
|
||||
@pytest.mark.parametrize("force_kill", [True, False])
|
||||
def test_kill_exist_relay_releated_process(mock_swsscommon_dbconnector_init, new_dhcp_interfaces_list, process_name,
|
||||
running_procs, force_kill):
|
||||
new_dhcp_interfaces = set(new_dhcp_interfaces_list)
|
||||
process_iter_ret = []
|
||||
for running_proc in running_procs:
|
||||
process_iter_ret.append(MockProc(running_proc))
|
||||
with patch.object(psutil, "process_iter", return_value=process_iter_ret):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector)
|
||||
res = dhcprelayd._kill_exist_relay_releated_process(new_dhcp_interfaces, process_name, force_kill)
|
||||
if force_kill and process_name in running_procs:
|
||||
assert res == KILLED_OLD
|
||||
elif new_dhcp_interfaces_list == ["Vlan1000"] and process_name in running_procs:
|
||||
assert res == NOT_KILLED
|
||||
elif process_name not in running_procs:
|
||||
assert res == NOT_FOUND_PROC
|
||||
elif new_dhcp_interfaces_list != ["Vlan1000"]:
|
||||
assert res == KILLED_OLD
|
||||
|
||||
|
||||
@pytest.mark.parametrize("get_res", [(1, "240.127.1.2"), (0, None)])
|
||||
def test_get_dhcp_server_ip(mock_swsscommon_dbconnector_init, mock_swsscommon_table_init, get_res):
|
||||
with patch.object(swsscommon.Table, "hget", return_value=get_res), \
|
||||
patch.object(time, "sleep") as mock_sleep, \
|
||||
patch.object(sys, "exit") as mock_exit:
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector)
|
||||
ret = dhcprelayd._get_dhcp_server_ip()
|
||||
if get_res[0] == 1:
|
||||
assert ret == get_res[1]
|
||||
else:
|
||||
mock_exit.assert_called_once_with(1)
|
||||
mock_sleep.assert_has_calls([call(10) for _ in range(10)])
|
@ -1,15 +1,22 @@
|
||||
import pytest
|
||||
import psutil
|
||||
import signal
|
||||
from dhcp_server.dhcp_server_utils import DhcpDbConnector
|
||||
from dhcp_server.dhcp_cfggen import DhcpServCfgGenerator
|
||||
from dhcp_server.dhcpservd import DhcpServd
|
||||
import sys
|
||||
import time
|
||||
from common_utils import MockProc
|
||||
from dhcp_server.common.utils import DhcpDbConnector
|
||||
from dhcp_server.dhcpservd.dhcp_cfggen import DhcpServCfgGenerator
|
||||
from dhcp_server.dhcpservd.dhcpservd import DhcpServd
|
||||
from swsscommon import swsscommon
|
||||
from unittest.mock import patch, call, MagicMock
|
||||
|
||||
AF_INET = 2
|
||||
AF_INET6 = 10
|
||||
|
||||
|
||||
def test_dump_dhcp4_config(mock_swsscommon_dbconnector_init):
|
||||
with patch("dhcp_server.dhcp_cfggen.DhcpServCfgGenerator.generate", return_value="dummy_config") as mock_generate, \
|
||||
patch("dhcp_server.dhcpservd.DhcpServd._notify_kea_dhcp4_proc", MagicMock()) as mock_notify_kea_dhcp4_proc:
|
||||
with patch("dhcp_server.dhcpservd.dhcp_cfggen.DhcpServCfgGenerator.generate", return_value="dummy_config") as mock_generate, \
|
||||
patch("dhcp_server.dhcpservd.dhcpservd.DhcpServd._notify_kea_dhcp4_proc", MagicMock()) as mock_notify_kea_dhcp4_proc:
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcp_cfg_generator = DhcpServCfgGenerator(dhcp_db_connector,
|
||||
port_map_path="tests/test_data/port-name-alias-map.txt",
|
||||
@ -40,21 +47,45 @@ def test_notify_kea_dhcp4_proc(process_list, mock_swsscommon_dbconnector_init, m
|
||||
mock_send_signal.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mock_intf", [True, False])
|
||||
def test_update_dhcp_server_ip(mock_swsscommon_dbconnector_init, mock_parse_port_map_alias, mock_get_render_template,
|
||||
mock_intf):
|
||||
mock_interface = {} if not mock_intf else {
|
||||
"eth0": [
|
||||
MockIntf(AF_INET6, "fd00::2"),
|
||||
MockIntf(AF_INET, "240.127.1.2")
|
||||
]
|
||||
}
|
||||
with patch.object(psutil, "net_if_addrs", return_value=mock_interface), \
|
||||
patch.object(swsscommon.DBConnector, "hset") as mock_hset, \
|
||||
patch.object(time, "sleep") as mock_sleep, \
|
||||
patch.object(sys, "exit") as mock_exit:
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcp_cfg_generator = DhcpServCfgGenerator(dhcp_db_connector)
|
||||
dhcpservd = DhcpServd(dhcp_cfg_generator, dhcp_db_connector)
|
||||
dhcpservd._update_dhcp_server_ip()
|
||||
if mock_intf:
|
||||
mock_hset.assert_has_calls([
|
||||
call("DHCP_SERVER_IPV4_SERVER_IP|eth0", "ip", "240.127.1.2")
|
||||
])
|
||||
else:
|
||||
mock_hset.assert_not_called()
|
||||
mock_exit.assert_called_once_with(1)
|
||||
mock_sleep.assert_has_calls([call(5) for _ in range(10)])
|
||||
|
||||
|
||||
def test_start(mock_swsscommon_dbconnector_init, mock_parse_port_map_alias, mock_get_render_template):
|
||||
with patch.object(DhcpServd, "dump_dhcp4_config") as mock_dump:
|
||||
with patch.object(DhcpServd, "dump_dhcp4_config") as mock_dump, \
|
||||
patch.object(DhcpServd, "_update_dhcp_server_ip") as mock_update_dhcp_server_ip:
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcp_cfg_generator = DhcpServCfgGenerator(dhcp_db_connector)
|
||||
dhcpservd = DhcpServd(dhcp_cfg_generator, dhcp_db_connector)
|
||||
dhcpservd.start()
|
||||
mock_dump.assert_called_once_with()
|
||||
mock_update_dhcp_server_ip.assert_called_once_with()
|
||||
|
||||
|
||||
class MockProc(object):
|
||||
def __init__(self, name):
|
||||
self.proc_name = name
|
||||
|
||||
def name(self):
|
||||
return self.proc_name
|
||||
|
||||
def send_signal(self, sig_num):
|
||||
pass
|
||||
class MockIntf(object):
|
||||
def __init__(self, family, address):
|
||||
self.family = family
|
||||
self.address = address
|
||||
|
@ -1,4 +1,4 @@
|
||||
import dhcp_server.dhcp_server_utils as dhcp_server_utils
|
||||
import dhcp_server.common.utils as utils
|
||||
import ipaddress
|
||||
import pytest
|
||||
from swsscommon import swsscommon
|
||||
@ -29,7 +29,7 @@ interval_test_data = {
|
||||
|
||||
|
||||
def test_construct_without_sock(mock_swsscommon_dbconnector_init):
|
||||
dhcp_server_utils.DhcpDbConnector()
|
||||
utils.DhcpDbConnector()
|
||||
mock_swsscommon_dbconnector_init.assert_has_calls([
|
||||
call(swsscommon.CONFIG_DB, "127.0.0.1", 6379, 0),
|
||||
call(swsscommon.STATE_DB, "127.0.0.1", 6379, 0)
|
||||
@ -38,7 +38,7 @@ def test_construct_without_sock(mock_swsscommon_dbconnector_init):
|
||||
|
||||
def test_construct_sock(mock_swsscommon_dbconnector_init):
|
||||
redis_sock = "/var/run/redis/redis.sock"
|
||||
dhcp_db_connector = dhcp_server_utils.DhcpDbConnector(redis_sock=redis_sock)
|
||||
dhcp_db_connector = utils.DhcpDbConnector(redis_sock=redis_sock)
|
||||
assert dhcp_db_connector.redis_sock == redis_sock
|
||||
|
||||
mock_swsscommon_dbconnector_init.assert_has_calls([
|
||||
@ -48,15 +48,13 @@ def test_construct_sock(mock_swsscommon_dbconnector_init):
|
||||
|
||||
|
||||
def test_get_config_db_table(mock_swsscommon_dbconnector_init, mock_swsscommon_table_init):
|
||||
dhcp_db_connector = dhcp_server_utils.DhcpDbConnector()
|
||||
dhcp_db_connector = utils.DhcpDbConnector()
|
||||
with patch.object(swsscommon.Table, "getKeys", return_value=["key1", "key2"]) as mock_get_keys, \
|
||||
patch.object(dhcp_server_utils, "get_entry", return_value={"list": "1,2", "value": "3,4"}), \
|
||||
patch.object(utils, "get_entry", return_value={"list": "1,2", "value": "3,4"}), \
|
||||
patch.object(swsscommon.Table, "hget", side_effect=mock_hget):
|
||||
ret = dhcp_db_connector.get_config_db_table("VLAN")
|
||||
mock_swsscommon_table_init.assert_called_once_with(dhcp_db_connector.config_db, "VLAN")
|
||||
print(ret)
|
||||
mock_get_keys.assert_called_once_with()
|
||||
print(ret)
|
||||
assert ret == {
|
||||
"key1": {"list": ["1", "2"], "value": "3,4"},
|
||||
"key2": {"list": ["1", "2"], "value": "3,4"}
|
||||
@ -67,7 +65,7 @@ def test_get_config_db_table(mock_swsscommon_dbconnector_init, mock_swsscommon_t
|
||||
def test_merge_intervals(test_type):
|
||||
intervals = convert_ip_address_intervals(interval_test_data[test_type]["intervals"])
|
||||
expected_res = convert_ip_address_intervals(interval_test_data[test_type]["expected_res"])
|
||||
assert dhcp_server_utils.merge_intervals(intervals) == expected_res
|
||||
assert utils.merge_intervals(intervals) == expected_res
|
||||
|
||||
|
||||
def mock_hget(_, field):
|
Loading…
Reference in New Issue
Block a user