[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
|
# Update apt's cache of available packages
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
|
|
||||||
|
RUN apt-get install -y libjsoncpp-dev {%- if INCLUDE_DHCP_SERVER == "y" %}\
|
||||||
|
python3-dev \
|
||||||
|
build-essential{%- endif %}
|
||||||
|
|
||||||
|
{% if INCLUDE_DHCP_SERVER == "y" -%}
|
||||||
|
RUN pip3 install psutil
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
RUN apt-get install -y libjsoncpp-dev
|
RUN apt-get install -y libjsoncpp-dev
|
||||||
|
|
||||||
{% if docker_dhcp_relay_debs.strip() -%}
|
{% 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(' ')) }}
|
{{ install_debian_packages(docker_dhcp_relay_debs.split(' ')) }}
|
||||||
{%- endif %}
|
{%- 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
|
# Clean up
|
||||||
|
{% if INCLUDE_DHCP_SERVER == "y" -%}
|
||||||
|
RUN apt-get remove -y build-essential \
|
||||||
|
python3-dev
|
||||||
|
{%- endif %}
|
||||||
RUN apt-get clean -y && \
|
RUN apt-get clean -y && \
|
||||||
apt-get autoclean -y && \
|
apt-get autoclean -y && \
|
||||||
apt-get autoremove -y && \
|
apt-get autoremove -y && \
|
||||||
|
@ -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",
|
db.cfgdb.set_entry.assert_called_once_with(config_db_table, "Vlan1000",
|
||||||
expected_dhcp_relay_del_config_db_output[ip_version])
|
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):
|
def test_config_add_del_multiple_dhcp_relay(self, mock_cfgdb, ip_version):
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
db = Db()
|
db = Db()
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import sys
|
import sys
|
||||||
|
import click
|
||||||
import os
|
import os
|
||||||
sys.path.append('../cli/show/plugins/')
|
sys.path.append('../cli/show/plugins/')
|
||||||
import show_dhcp_relay as show
|
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 = """\
|
expected_ipv6_table_without_header = """\
|
||||||
-------- ------------
|
-------- ------------
|
||||||
Vlan1000 fc02:2000::1
|
Vlan1000 fc02:2000::1
|
||||||
@ -86,11 +95,17 @@ def test_plugin_registration():
|
|||||||
assert 'DHCP Helper Address' in dict(vlan.VlanBrief.COLUMNS)
|
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 = (
|
ctx = (
|
||||||
({'Vlan1001': {'dhcp_servers': ['192.0.0.1', '192.168.0.2']}}, {}, {}),
|
({'Vlan1001': {'dhcp_servers': ['192.0.0.1', '192.168.0.2']}}, {}, {}),
|
||||||
(),
|
(MockDb({"FEATURE": feature_table})),
|
||||||
)
|
)
|
||||||
|
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'
|
assert show.get_dhcp_helper_address(ctx, 'Vlan1001') == '192.0.0.1\n192.168.0.2'
|
||||||
|
|
||||||
|
|
||||||
@ -103,7 +118,7 @@ def test_show_dhcp_relay(test_name, test_data, fs):
|
|||||||
config_db = MockConfigDb()
|
config_db = MockConfigDb()
|
||||||
ip_version = "ipv4" if "ipv4" in test_name else "ipv6"
|
ip_version = "ipv4" if "ipv4" in test_name else "ipv6"
|
||||||
table = config_db.get_table(IP_VER_TEST_PARAM_MAP[ip_version]["table"])
|
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"])
|
result = show.get_dhcp_relay_data_with_header(table, IP_VER_TEST_PARAM_MAP[ip_version]["entry"])
|
||||||
expected_output = expected_ipv4_table_with_header
|
expected_output = expected_ipv4_table_with_header
|
||||||
elif test_name == "ipv6_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":
|
elif test_name == "ipv6_without_header":
|
||||||
result = show.get_data(table, "Vlan1000")
|
result = show.get_data(table, "Vlan1000")
|
||||||
expected_output = expected_ipv6_table_without_header
|
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
|
assert result == expected_output
|
||||||
|
|
||||||
|
|
||||||
@ -153,3 +171,31 @@ def test_show_multi_dhcp_relay(test_name, test_data, fs):
|
|||||||
else:
|
else:
|
||||||
expected_output = expected_ipv6_table_multi_with_header
|
expected_output = expected_ipv6_table_multi_with_header
|
||||||
assert result == expected_output
|
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))
|
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")
|
@click.group(cls=clicommon.AbbreviationGroup, name="dhcp_relay")
|
||||||
def dhcp_relay():
|
def dhcp_relay():
|
||||||
"""config DHCP_Relay information"""
|
"""config DHCP_Relay information"""
|
||||||
@ -163,6 +168,9 @@ def dhcp_relay_ipv4_helper():
|
|||||||
@click.argument("dhcp_relay_helpers", nargs=-1, required=True)
|
@click.argument("dhcp_relay_helpers", nargs=-1, required=True)
|
||||||
@clicommon.pass_db
|
@clicommon.pass_db
|
||||||
def add_dhcp_relay_ipv4_helper(db, vid, dhcp_relay_helpers):
|
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)
|
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)
|
@click.argument("dhcp_relay_helpers", nargs=-1, required=True)
|
||||||
@clicommon.pass_db
|
@clicommon.pass_db
|
||||||
def del_dhcp_relay_ipv4_helper(db, vid, dhcp_relay_helpers):
|
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)
|
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))
|
click.echo("{} is already a DHCP relay destination for {}".format(ip_addr, vlan_name))
|
||||||
continue
|
continue
|
||||||
if clicommon.ipaddress_type(ip_addr) == 4:
|
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)
|
dhcp_servers.append(ip_addr)
|
||||||
else:
|
else:
|
||||||
dhcpv6_servers.append(ip_addr)
|
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):
|
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))
|
ctx.fail("{} is not a DHCP relay destination for {}".format(ip_addr, vlan_name))
|
||||||
if clicommon.ipaddress_type(ip_addr) == 4:
|
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)
|
dhcp_servers.remove(ip_addr)
|
||||||
else:
|
else:
|
||||||
dhcpv6_servers.remove(ip_addr)
|
dhcpv6_servers.remove(ip_addr)
|
||||||
|
@ -32,13 +32,15 @@ config_db = ConfigDBConnector()
|
|||||||
|
|
||||||
|
|
||||||
def get_dhcp_helper_address(ctx, vlan):
|
def get_dhcp_helper_address(ctx, vlan):
|
||||||
cfg, _ = ctx
|
cfg, db = ctx
|
||||||
vlan_dhcp_helper_data, _, _ = cfg
|
vlan_dhcp_helper_data, _, _ = cfg
|
||||||
vlan_config = vlan_dhcp_helper_data.get(vlan)
|
vlan_config = vlan_dhcp_helper_data.get(vlan)
|
||||||
if not vlan_config:
|
if not vlan_config:
|
||||||
return ""
|
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))
|
return '\n'.join(natsorted(dhcp_helpers))
|
||||||
|
|
||||||
@ -96,6 +98,11 @@ def dhcp4relay_counters():
|
|||||||
|
|
||||||
|
|
||||||
def ipv4_counters(interface):
|
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 = DHCPv4_Counter()
|
||||||
counter_intf = counter.get_interface()
|
counter_intf = counter.get_interface()
|
||||||
|
|
||||||
@ -193,7 +200,7 @@ def dhcp_relay_helper():
|
|||||||
pass
|
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 = {}
|
vlan_relay = {}
|
||||||
vlans = table_data.keys()
|
vlans = table_data.keys()
|
||||||
for vlan in vlans:
|
for vlan in vlans:
|
||||||
@ -203,6 +210,9 @@ def get_dhcp_relay_data_with_header(table_data, entry_name):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
vlan_relay[vlan] = []
|
vlan_relay[vlan] = []
|
||||||
|
if dhcp_server_enabled:
|
||||||
|
vlan_relay[vlan].append("N/A")
|
||||||
|
else:
|
||||||
for address in dhcp_relay_data:
|
for address in dhcp_relay_data:
|
||||||
vlan_relay[vlan].append(address)
|
vlan_relay[vlan].append(address)
|
||||||
|
|
||||||
@ -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'
|
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):
|
def get_dhcp_relay(table_name, entry_name, with_header):
|
||||||
if config_db is None:
|
if config_db is None:
|
||||||
return
|
return
|
||||||
@ -221,8 +238,13 @@ def get_dhcp_relay(table_name, entry_name, with_header):
|
|||||||
if table_data is None:
|
if table_data is None:
|
||||||
return
|
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:
|
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)
|
print(output)
|
||||||
else:
|
else:
|
||||||
vlans = config_db.get_keys(table_name)
|
vlans = config_db.get_keys(table_name)
|
||||||
|
@ -2,9 +2,13 @@
|
|||||||
programs=
|
programs=
|
||||||
{%- set relay_for_ipv6 = { 'flag': False } %}
|
{%- set relay_for_ipv6 = { 'flag': False } %}
|
||||||
{%- set add_preceding_comma = { 'flag': False } %}
|
{%- set add_preceding_comma = { 'flag': False } %}
|
||||||
|
{% if dhcp_server_ipv4_enabled %}
|
||||||
|
{% set _dummy = add_preceding_comma.update({'flag': True}) %}
|
||||||
|
dhcprelayd
|
||||||
|
{%- endif %}
|
||||||
{% for vlan_name in VLAN_INTERFACE %}
|
{% for vlan_name in VLAN_INTERFACE %}
|
||||||
{# Append DHCPv4 agents #}
|
{# 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 %}
|
{% if add_preceding_comma.flag %},{% endif %}
|
||||||
{% set _dummy = add_preceding_comma.update({'flag': True}) %}
|
{% set _dummy = add_preceding_comma.update({'flag': True}) %}
|
||||||
isc-dhcpv4-relay-{{ vlan_name }}
|
isc-dhcpv4-relay-{{ vlan_name }}
|
||||||
|
@ -39,6 +39,10 @@ stderr_logfile=syslog
|
|||||||
dependent_startup=true
|
dependent_startup=true
|
||||||
dependent_startup_wait_for=rsyslogd:running
|
dependent_startup_wait_for=rsyslogd:running
|
||||||
|
|
||||||
|
{% set dhcp_server_ipv4_enabled = False %}
|
||||||
|
{% if FEATURE and 'dhcp_server' in FEATURE and 'state' in FEATURE['dhcp_server'] and FEATURE['dhcp_server']['state'] == 'enabled' %}
|
||||||
|
{% set dhcp_server_ipv4_enabled = True %}
|
||||||
|
{% endif %}
|
||||||
{# If our configuration has VLANs... #}
|
{# If our configuration has VLANs... #}
|
||||||
{% if VLAN_INTERFACE %}
|
{% if VLAN_INTERFACE %}
|
||||||
{# Count how many VLANs require a DHCP relay agent... #}
|
{# Count how many VLANs require a DHCP relay agent... #}
|
||||||
@ -56,15 +60,28 @@ dependent_startup_wait_for=rsyslogd:running
|
|||||||
{% if ipv4_num_relays.count > 0 or ipv6_num_relays.count > 0 %}
|
{% if ipv4_num_relays.count > 0 or ipv6_num_relays.count > 0 %}
|
||||||
{% include 'dhcp-relay.programs.j2' %}
|
{% include 'dhcp-relay.programs.j2' %}
|
||||||
|
|
||||||
|
|
||||||
{# Create a program entry for each DHCP relay agent instance #}
|
{# Create a program entry for each DHCP relay agent instance #}
|
||||||
{% set relay_for_ipv4 = { 'flag': False } %}
|
{% set relay_for_ipv4 = { 'flag': False } %}
|
||||||
{% set relay_for_ipv6 = { 'flag': False } %}
|
{% set relay_for_ipv6 = { 'flag': False } %}
|
||||||
|
{% if not dhcp_server_ipv4_enabled %}
|
||||||
{% for vlan_name in VLAN_INTERFACE %}
|
{% for vlan_name in VLAN_INTERFACE %}
|
||||||
{% include 'dhcpv4-relay.agents.j2' %}
|
{% include 'dhcpv4-relay.agents.j2' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
{% include 'dhcpv6-relay.agents.j2' %}
|
{% include 'dhcpv6-relay.agents.j2' %}
|
||||||
|
{% if not dhcp_server_ipv4_enabled %}
|
||||||
{% include 'dhcp-relay.monitors.j2' %}
|
{% include 'dhcp-relay.monitors.j2' %}
|
||||||
|
{% else %}
|
||||||
|
[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 %}
|
||||||
{% endif %}
|
{% endif %}
|
@ -3,3 +3,6 @@
|
|||||||
{% for port, config in PORT.items() %}
|
{% for port, config in PORT.items() %}
|
||||||
{{- port }} {% if "alias" in config %}{{ config["alias"] }}{% else %}{{ port }}{% endif %} {{- "\n" -}}
|
{{- port }} {% if "alias" in config %}{{ config["alias"] }}{% else %}{{ port }}{% endif %} {{- "\n" -}}
|
||||||
{% endfor -%}
|
{% endfor -%}
|
||||||
|
{% for pc, config in PORTCHANNEL.items() %}
|
||||||
|
{{- pc }} {{ pc }} {{- "\n" -}}
|
||||||
|
{% endfor -%}
|
||||||
|
@ -60,4 +60,4 @@ autorestart=false
|
|||||||
stdout_logfile=syslog
|
stdout_logfile=syslog
|
||||||
stderr_logfile=syslog
|
stderr_logfile=syslog
|
||||||
dependent_startup=true
|
dependent_startup=true
|
||||||
dependent_startup_wait_for=start:exited
|
dependent_startup_wait_for=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_PYTHON_WHEELS = $(SONIC_UTILITIES_PY3)
|
||||||
$(DOCKER_DHCP_RELAY)_INSTALL_DEBS = $(PYTHON3_SWSSCOMMON)
|
$(DOCKER_DHCP_RELAY)_INSTALL_DEBS = $(PYTHON3_SWSSCOMMON)
|
||||||
|
ifeq ($(INCLUDE_DHCP_SERVER), y)
|
||||||
|
$(DOCKER_DHCP_RELAY)_PYTHON_WHEELS += $(SONIC_DHCP_SERVER_PY3)
|
||||||
|
endif
|
||||||
|
|
||||||
$(DOCKER_DHCP_RELAY)_VERSION = 1.0.0
|
$(DOCKER_DHCP_RELAY)_VERSION = 1.0.0
|
||||||
$(DOCKER_DHCP_RELAY)_PACKAGE_NAME = dhcp-relay
|
$(DOCKER_DHCP_RELAY)_PACKAGE_NAME = dhcp-relay
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"FEATURE": {
|
||||||
|
"dhcp_server": {
|
||||||
|
"state": "enabled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,10 @@
|
|||||||
{
|
{
|
||||||
"VLAN_INTERFACE": {
|
"VLAN_INTERFACE": {
|
||||||
"Vlan1000|fc02:2000::2/24": {}
|
"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]
|
[group:dhcp-relay]
|
||||||
programs=isc-dhcpv4-relay-Vlan1000,dhcp6relay
|
programs=isc-dhcpv4-relay-Vlan1000,dhcp6relay
|
||||||
|
|
||||||
|
|
||||||
[program:isc-dhcpv4-relay-Vlan1000]
|
[program:isc-dhcpv4-relay-Vlan1000]
|
||||||
command=/usr/sbin/dhcrelay -d -m discard -a %%h:%%p %%P --name-alias-map-file /tmp/port-name-alias-map.txt -id Vlan1000 -iu Vlan2000 -iu PortChannel02 -iu PortChannel03 -iu PortChannel04 -iu PortChannel01 192.0.0.1 192.0.0.2
|
command=/usr/sbin/dhcrelay -d -m discard -a %%h:%%p %%P --name-alias-map-file /tmp/port-name-alias-map.txt -id Vlan1000 -iu Vlan2000 -iu PortChannel02 -iu PortChannel03 -iu PortChannel04 -iu PortChannel01 192.0.0.1 192.0.0.2
|
||||||
priority=3
|
priority=3
|
||||||
|
@ -42,7 +42,6 @@ dependent_startup_wait_for=rsyslogd:running
|
|||||||
[group:dhcp-relay]
|
[group:dhcp-relay]
|
||||||
programs=isc-dhcpv4-relay-Vlan1000,isc-dhcpv4-relay-Vlan2000,dhcp6relay
|
programs=isc-dhcpv4-relay-Vlan1000,isc-dhcpv4-relay-Vlan2000,dhcp6relay
|
||||||
|
|
||||||
|
|
||||||
[program:isc-dhcpv4-relay-Vlan1000]
|
[program:isc-dhcpv4-relay-Vlan1000]
|
||||||
command=/usr/sbin/dhcrelay -d -m discard -a %%h:%%p %%P --name-alias-map-file /tmp/port-name-alias-map.txt -id Vlan1000 -iu Vlan2000 -iu PortChannel02 -iu PortChannel03 -iu PortChannel04 -iu PortChannel01 192.0.0.1 192.0.0.2
|
command=/usr/sbin/dhcrelay -d -m discard -a %%h:%%p %%P --name-alias-map-file /tmp/port-name-alias-map.txt -id Vlan1000 -iu Vlan2000 -iu PortChannel02 -iu PortChannel03 -iu PortChannel04 -iu PortChannel01 192.0.0.1 192.0.0.2
|
||||||
priority=3
|
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]
|
[group:dhcp-relay]
|
||||||
programs=isc-dhcpv4-relay-Vlan1000,dhcp6relay
|
programs=isc-dhcpv4-relay-Vlan1000,dhcp6relay
|
||||||
|
|
||||||
|
|
||||||
[program:isc-dhcpv4-relay-Vlan1000]
|
[program:isc-dhcpv4-relay-Vlan1000]
|
||||||
command=/usr/sbin/dhcrelay -d -m discard -a %%h:%%p %%P --name-alias-map-file /tmp/port-name-alias-map.txt -id Vlan1000 -iu Vlan2000 -iu PortChannel01 -iu PortChannel02 -iu PortChannel03 -iu PortChannel04 192.0.0.1 192.0.0.2
|
command=/usr/sbin/dhcrelay -d -m discard -a %%h:%%p %%P --name-alias-map-file /tmp/port-name-alias-map.txt -id Vlan1000 -iu Vlan2000 -iu PortChannel01 -iu PortChannel02 -iu PortChannel03 -iu PortChannel04 192.0.0.1 192.0.0.2
|
||||||
priority=3
|
priority=3
|
||||||
|
@ -42,7 +42,6 @@ dependent_startup_wait_for=rsyslogd:running
|
|||||||
[group:dhcp-relay]
|
[group:dhcp-relay]
|
||||||
programs=isc-dhcpv4-relay-Vlan1000,isc-dhcpv4-relay-Vlan2000,dhcp6relay
|
programs=isc-dhcpv4-relay-Vlan1000,isc-dhcpv4-relay-Vlan2000,dhcp6relay
|
||||||
|
|
||||||
|
|
||||||
[program:isc-dhcpv4-relay-Vlan1000]
|
[program:isc-dhcpv4-relay-Vlan1000]
|
||||||
command=/usr/sbin/dhcrelay -d -m discard -a %%h:%%p %%P --name-alias-map-file /tmp/port-name-alias-map.txt -id Vlan1000 -iu Vlan2000 -iu PortChannel01 -iu PortChannel02 -iu PortChannel03 -iu PortChannel04 192.0.0.1 192.0.0.2
|
command=/usr/sbin/dhcrelay -d -m discard -a %%h:%%p %%P --name-alias-map-file /tmp/port-name-alias-map.txt -id Vlan1000 -iu Vlan2000 -iu PortChannel01 -iu PortChannel02 -iu PortChannel03 -iu PortChannel04 192.0.0.1 192.0.0.2
|
||||||
priority=3
|
priority=3
|
||||||
|
@ -154,22 +154,43 @@ class TestJ2Files(TestCase):
|
|||||||
def test_dhcp_relay(self):
|
def test_dhcp_relay(self):
|
||||||
# Test generation of wait_for_intf.sh
|
# Test generation of wait_for_intf.sh
|
||||||
dhc_sample_data = os.path.join(self.test_dir, "dhcp-relay-sample.json")
|
dhc_sample_data = os.path.join(self.test_dir, "dhcp-relay-sample.json")
|
||||||
|
enable_dhcp_server_sample_data = os.path.join(self.test_dir, "dhcp-relay-enable-dhcp-server-sample.json")
|
||||||
template_path = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-dhcp-relay', 'wait_for_intf.sh.j2')
|
template_path = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-dhcp-relay', 'wait_for_intf.sh.j2')
|
||||||
argument = ['-m', self.t0_minigraph, '-j', dhc_sample_data, '-p', self.t0_port_config, '-t', template_path]
|
argument = ['-m', self.t0_minigraph, '-j', dhc_sample_data, '-p', self.t0_port_config, '-t', template_path]
|
||||||
self.run_script(argument, output_file=self.output_file)
|
self.run_script(argument, output_file=self.output_file)
|
||||||
self.assertTrue(utils.cmp(os.path.join(self.test_dir, 'sample_output', utils.PYvX_DIR, 'wait_for_intf.sh'), self.output_file))
|
self.assertTrue(utils.cmp(os.path.join(self.test_dir, 'sample_output', utils.PYvX_DIR, 'wait_for_intf.sh'), self.output_file))
|
||||||
|
|
||||||
# Test generation of docker-dhcp-relay.supervisord.conf
|
# Test generation of docker-dhcp-relay.supervisord.conf witout dhcp_server feature entry
|
||||||
template_path = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-dhcp-relay', 'docker-dhcp-relay.supervisord.conf.j2')
|
template_path = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-dhcp-relay', 'docker-dhcp-relay.supervisord.conf.j2')
|
||||||
argument = ['-m', self.t0_minigraph, '-p', self.t0_port_config, '-t', template_path]
|
argument = ['-m', self.t0_minigraph, '-p', self.t0_port_config, '-t', template_path]
|
||||||
self.run_script(argument, output_file=self.output_file)
|
self.run_script(argument, output_file=self.output_file)
|
||||||
self.assertTrue(utils.cmp(os.path.join(self.test_dir, 'sample_output', utils.PYvX_DIR, 'docker-dhcp-relay.supervisord.conf'), self.output_file))
|
self.assertTrue(utils.cmp(os.path.join(self.test_dir, 'sample_output', utils.PYvX_DIR, 'docker-dhcp-relay.supervisord.conf'), self.output_file))
|
||||||
|
|
||||||
|
# Test generation of docker-dhcp-relay.supervisord.conf with disabled dhcp_server feature
|
||||||
|
template_path = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-dhcp-relay',
|
||||||
|
'docker-dhcp-relay.supervisord.conf.j2')
|
||||||
|
argument = ['-m', self.t0_minigraph, '-j', dhc_sample_data, '-p', self.t0_port_config, '-t', template_path]
|
||||||
|
self.run_script(argument, output_file=self.output_file)
|
||||||
|
self.assertTrue(utils.cmp(os.path.join(self.test_dir, 'sample_output', utils.PYvX_DIR,
|
||||||
|
'docker-dhcp-relay.supervisord.conf'), self.output_file))
|
||||||
|
|
||||||
|
# Test generation of docker-dhcp-relay.supervisord.conf with enabled dhcp_server feature
|
||||||
|
template_path = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-dhcp-relay',
|
||||||
|
'docker-dhcp-relay.supervisord.conf.j2')
|
||||||
|
argument = ['-m', self.t0_minigraph, '-j', enable_dhcp_server_sample_data, '-p', self.t0_port_config, '-t',
|
||||||
|
template_path]
|
||||||
|
self.run_script(argument, output_file=self.output_file)
|
||||||
|
self.assertTrue(utils.cmp(os.path.join(self.test_dir, 'sample_output', utils.PYvX_DIR,
|
||||||
|
'docker-dhcp-relay-enable-dhcp-server.supervisord.conf'),
|
||||||
|
self.output_file))
|
||||||
|
|
||||||
# Test generation of docker-dhcp-relay.supervisord.conf when a vlan is missing ip/ipv6 helpers
|
# Test generation of docker-dhcp-relay.supervisord.conf when a vlan is missing ip/ipv6 helpers
|
||||||
template_path = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-dhcp-relay', '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]
|
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.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):
|
def test_radv(self):
|
||||||
# Test generation of radvd.conf with multiple ipv6 prefixes
|
# 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)
|
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):
|
def merge_intervals(intervals):
|
||||||
"""
|
"""
|
||||||
Merge ip range 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
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
import syslog
|
import syslog
|
||||||
|
|
||||||
from jinja2 import Environment, FileSystemLoader
|
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"
|
PORT_MAP_PATH = "/tmp/port-name-alias-map.txt"
|
||||||
UNICODE_TYPE = str
|
UNICODE_TYPE = str
|
@ -1,5 +1,3 @@
|
|||||||
import ipaddress
|
|
||||||
import json
|
|
||||||
import signal
|
import signal
|
||||||
import syslog
|
import syslog
|
||||||
import threading
|
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_table = self.db_connector.get_state_db_table(DHCP_SERVER_IPV4_LEASE)
|
||||||
old_lease_key = set(old_lease_table.keys())
|
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.1 If current lease table has this old lease, delete it
|
||||||
# 1.1.2 Else skip
|
# 1.1.2 Else skip
|
||||||
# 1.2 Else, means lease valid, save it.
|
# 1.2 Else, means lease valid, save it.
|
||||||
for key, value in new_lease.items():
|
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:
|
if key in old_lease_key:
|
||||||
self.db_connector.state_db.delete("{}|{}".format(DHCP_SERVER_IPV4_LEASE, key))
|
self.db_connector.state_db.delete("{}|{}".format(DHCP_SERVER_IPV4_LEASE, key))
|
||||||
continue
|
continue
|
@ -2,14 +2,19 @@
|
|||||||
import psutil
|
import psutil
|
||||||
import signal
|
import signal
|
||||||
import time
|
import time
|
||||||
|
import sys
|
||||||
|
import syslog
|
||||||
from .dhcp_cfggen import DhcpServCfgGenerator
|
from .dhcp_cfggen import DhcpServCfgGenerator
|
||||||
from .dhcp_lease import LeaseManager
|
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_CONFIG = "/etc/kea/kea-dhcp4.conf"
|
||||||
KEA_DHCP4_PROC_NAME = "kea-dhcp4"
|
KEA_DHCP4_PROC_NAME = "kea-dhcp4"
|
||||||
KEA_LEASE_FILE_PATH = "/tmp/kea-lease.csv"
|
KEA_LEASE_FILE_PATH = "/tmp/kea-lease.csv"
|
||||||
REDIS_SOCK_PATH = "/var/run/redis/redis.sock"
|
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):
|
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
|
# After refresh kea-config, we need to SIGHUP kea-dhcp4 process to read new config
|
||||||
self._notify_kea_dhcp4_proc()
|
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):
|
def start(self):
|
||||||
self.dump_dhcp4_config()
|
self.dump_dhcp4_config()
|
||||||
|
self._update_dhcp_server_ip()
|
||||||
lease_manager = LeaseManager(self.db_connector, KEA_LEASE_FILE_PATH)
|
lease_manager = LeaseManager(self.db_connector, KEA_LEASE_FILE_PATH)
|
||||||
lease_manager.start()
|
lease_manager.start()
|
||||||
|
|
@ -1,18 +1,12 @@
|
|||||||
from setuptools import setup
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"psutil",
|
"psutil"
|
||||||
"coverage"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
test_deps = [
|
test_deps = [
|
||||||
"pytest"
|
"pytest",
|
||||||
]
|
"freezegun"
|
||||||
|
|
||||||
py_modules = [
|
|
||||||
"dhcp_server_utils",
|
|
||||||
"dhcp_cfggen",
|
|
||||||
"dhcp_lease"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
@ -29,14 +23,16 @@ setup(
|
|||||||
"wheel",
|
"wheel",
|
||||||
],
|
],
|
||||||
packages=[
|
packages=[
|
||||||
"dhcp_server"
|
"dhcp_server.common",
|
||||||
|
"dhcp_server.dhcpservd",
|
||||||
|
"dhcp_server.dhcprelayd"
|
||||||
],
|
],
|
||||||
entry_points={
|
entry_points={
|
||||||
"console_scripts": [
|
"console_scripts": [
|
||||||
"dhcpservd = dhcp_server.dhcpservd:main"
|
"dhcprelayd = dhcp_server.dhcprelayd.dhcprelayd:main",
|
||||||
|
"dhcpservd = dhcp_server.dhcpservd.dhcpservd:main"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
py_modules=py_modules,
|
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
"Operating System :: Linux",
|
"Operating System :: Linux",
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
|
import heapq
|
||||||
import json
|
import json
|
||||||
|
import psutil
|
||||||
|
|
||||||
MOCK_CONFIG_DB_PATH = "tests/test_data/mock_config_db.json"
|
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):
|
class MockConfigDb(object):
|
||||||
@ -10,3 +13,75 @@ class MockConfigDb(object):
|
|||||||
|
|
||||||
def get_config_db_table(self, table_name):
|
def get_config_db_table(self, table_name):
|
||||||
return self.config_db.get(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 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 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")
|
@pytest.fixture(scope="function")
|
||||||
def mock_swsscommon_dbconnector_init():
|
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
|
yield mock_dbconnector_init
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def mock_swsscommon_table_init():
|
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
|
yield mock_table_init
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def mock_get_render_template():
|
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
|
yield mock_template
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_parse_port_map_alias(scope="function"):
|
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"},
|
patch.object(DhcpServCfgGenerator, "port_alias_map", return_value={"Ethernet24": "etp7", "Ethernet28": "etp8"},
|
||||||
new_callable=PropertyMock), \
|
new_callable=PropertyMock), \
|
||||||
patch.object(DhcpServCfgGenerator, "lease_update_script_path", return_value="/etc/kea/lease_update.sh",
|
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):
|
patch.object(DhcpServCfgGenerator, "lease_path", return_value="/tmp/kea-lease.csv", new_callable=PropertyMock):
|
||||||
yield mock_map
|
yield mock_map
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,3 +7,4 @@ address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostn
|
|||||||
192.168.0.2,10:70:fd:b6:13:00,,0,1693997305,1,0,0,7626dced293e,0,,0
|
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.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"
|
"hostname": "sonic-host"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"VLAN": {
|
||||||
|
"Vlan1000": {},
|
||||||
|
"Vlan2000": {}
|
||||||
|
},
|
||||||
"VLAN_INTERFACE": {
|
"VLAN_INTERFACE": {
|
||||||
"Vlan1000|192.168.0.1/21": {
|
"Vlan1000|192.168.0.1/21": {
|
||||||
"NULL": "NULL"
|
"NULL": "NULL"
|
||||||
@ -77,6 +81,17 @@
|
|||||||
"mode": "PORT",
|
"mode": "PORT",
|
||||||
"netmask": "255.255.255.0",
|
"netmask": "255.255.255.0",
|
||||||
"state": "enabled"
|
"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": {
|
"DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS": {
|
||||||
|
@ -3,9 +3,8 @@ import ipaddress
|
|||||||
import json
|
import json
|
||||||
import pytest
|
import pytest
|
||||||
from common_utils import MockConfigDb
|
from common_utils import MockConfigDb
|
||||||
from dhcp_server.dhcp_server_utils import DhcpDbConnector
|
from dhcp_server.common.utils import DhcpDbConnector
|
||||||
from dhcp_server.dhcp_cfggen import DhcpServCfgGenerator
|
from dhcp_server.dhcpservd.dhcp_cfggen import DhcpServCfgGenerator
|
||||||
from unittest.mock import patch, MagicMock
|
|
||||||
|
|
||||||
expected_dhcp_config = {
|
expected_dhcp_config = {
|
||||||
"Dhcp4": {
|
"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.common.utils import DhcpDbConnector
|
||||||
from dhcp_server.dhcp_lease import KeaDhcp4LeaseHandler, LeaseHanlder
|
from dhcp_server.dhcpservd.dhcp_lease import KeaDhcp4LeaseHandler, LeaseHanlder
|
||||||
|
from freezegun import freeze_time
|
||||||
from swsscommon import swsscommon
|
from swsscommon import swsscommon
|
||||||
from unittest.mock import patch, call, MagicMock
|
from unittest.mock import patch, call, MagicMock
|
||||||
|
|
||||||
@ -14,6 +15,11 @@ expected_lease = {
|
|||||||
"lease_end": "1694000915",
|
"lease_end": "1694000915",
|
||||||
"ip": "192.168.0.131"
|
"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": {
|
"Vlan2000|10:70:fd:b6:13:15": {
|
||||||
"lease_start": "1693995705",
|
"lease_start": "1693995705",
|
||||||
"lease_end": "1693999305",
|
"lease_end": "1693999305",
|
||||||
@ -24,6 +30,7 @@ expected_fdb_info = {
|
|||||||
"10:70:fd:b6:13:00": "Vlan1000",
|
"10:70:fd:b6:13:00": "Vlan1000",
|
||||||
"10:70:fd:b6:13:15": "Vlan2000",
|
"10:70:fd:b6:13:15": "Vlan2000",
|
||||||
"10:70:fd:b6:13:17": "Vlan1000",
|
"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")
|
kea_lease_handler = KeaDhcp4LeaseHandler(db_connector, lease_file="tests/test_data/kea-lease.csv")
|
||||||
# Verify whether lease information read is as expected
|
# Verify whether lease information read is as expected
|
||||||
lease = kea_lease_handler._read()
|
lease = kea_lease_handler._read()
|
||||||
print(lease)
|
|
||||||
print(expected_lease)
|
|
||||||
assert lease == expected_lease
|
assert lease == expected_lease
|
||||||
|
|
||||||
|
|
||||||
@ -52,9 +57,10 @@ def test_get_fdb_info(mock_swsscommon_dbconnector_init):
|
|||||||
mock_fdb_table = {
|
mock_fdb_table = {
|
||||||
"Vlan2000:10:70:fd:b6:13:15": {"port": "Ethernet31", "type": "dynamic"},
|
"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: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()
|
db_connector = DhcpDbConnector()
|
||||||
kea_lease_handler = KeaDhcp4LeaseHandler(db_connector, lease_file="tests/test_data/kea-lease.csv")
|
kea_lease_handler = KeaDhcp4LeaseHandler(db_connector, lease_file="tests/test_data/kea-lease.csv")
|
||||||
# Verify whether lease information read is as expected
|
# 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
|
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):
|
def test_update_kea_lease(mock_swsscommon_dbconnector_init, mock_swsscommon_table_init):
|
||||||
tested_lease = expected_lease
|
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"), \
|
with patch.object(swsscommon.Table, "getKeys"), \
|
||||||
patch.object(swsscommon.DBConnector, "hset") as mock_hset, \
|
patch.object(swsscommon.DBConnector, "hset") as mock_hset, \
|
||||||
patch.object(KeaDhcp4LeaseHandler, "_read", MagicMock(return_value=tested_lease)), \
|
patch.object(KeaDhcp4LeaseHandler, "_read", MagicMock(return_value=tested_lease)), \
|
||||||
patch.object(DhcpDbConnector, "get_state_db_table",
|
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.object(swsscommon.DBConnector, "delete") as mock_delete, \
|
||||||
patch("time.sleep", return_value=None) as mock_sleep:
|
patch("time.sleep", return_value=None) as mock_sleep:
|
||||||
db_connector = DhcpDbConnector()
|
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
|
# Verify that old key was deleted
|
||||||
mock_delete.assert_has_calls([
|
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: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")
|
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
|
# 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
|
# lease_start equals to lease_end
|
||||||
mock_hset.assert_has_calls([
|
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:18", "lease_start", "1697607205"),
|
||||||
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:18", "lease_end", "1697610805"),
|
||||||
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", "ip", "193.168.0.132")
|
||||||
])
|
])
|
||||||
kea_lease_handler.update_lease()
|
kea_lease_handler.update_lease()
|
||||||
mock_sleep.assert_called_once_with(2)
|
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 pytest
|
||||||
import psutil
|
import psutil
|
||||||
import signal
|
import signal
|
||||||
from dhcp_server.dhcp_server_utils import DhcpDbConnector
|
import sys
|
||||||
from dhcp_server.dhcp_cfggen import DhcpServCfgGenerator
|
import time
|
||||||
from dhcp_server.dhcpservd import DhcpServd
|
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
|
from unittest.mock import patch, call, MagicMock
|
||||||
|
|
||||||
|
AF_INET = 2
|
||||||
|
AF_INET6 = 10
|
||||||
|
|
||||||
|
|
||||||
def test_dump_dhcp4_config(mock_swsscommon_dbconnector_init):
|
def test_dump_dhcp4_config(mock_swsscommon_dbconnector_init):
|
||||||
with patch("dhcp_server.dhcp_cfggen.DhcpServCfgGenerator.generate", return_value="dummy_config") as mock_generate, \
|
with patch("dhcp_server.dhcpservd.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:
|
patch("dhcp_server.dhcpservd.dhcpservd.DhcpServd._notify_kea_dhcp4_proc", MagicMock()) as mock_notify_kea_dhcp4_proc:
|
||||||
dhcp_db_connector = DhcpDbConnector()
|
dhcp_db_connector = DhcpDbConnector()
|
||||||
dhcp_cfg_generator = DhcpServCfgGenerator(dhcp_db_connector,
|
dhcp_cfg_generator = DhcpServCfgGenerator(dhcp_db_connector,
|
||||||
port_map_path="tests/test_data/port-name-alias-map.txt",
|
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()
|
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):
|
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_db_connector = DhcpDbConnector()
|
||||||
dhcp_cfg_generator = DhcpServCfgGenerator(dhcp_db_connector)
|
dhcp_cfg_generator = DhcpServCfgGenerator(dhcp_db_connector)
|
||||||
dhcpservd = DhcpServd(dhcp_cfg_generator, dhcp_db_connector)
|
dhcpservd = DhcpServd(dhcp_cfg_generator, dhcp_db_connector)
|
||||||
dhcpservd.start()
|
dhcpservd.start()
|
||||||
mock_dump.assert_called_once_with()
|
mock_dump.assert_called_once_with()
|
||||||
|
mock_update_dhcp_server_ip.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
class MockProc(object):
|
class MockIntf(object):
|
||||||
def __init__(self, name):
|
def __init__(self, family, address):
|
||||||
self.proc_name = name
|
self.family = family
|
||||||
|
self.address = address
|
||||||
def name(self):
|
|
||||||
return self.proc_name
|
|
||||||
|
|
||||||
def send_signal(self, sig_num):
|
|
||||||
pass
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import dhcp_server.dhcp_server_utils as dhcp_server_utils
|
import dhcp_server.common.utils as utils
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import pytest
|
import pytest
|
||||||
from swsscommon import swsscommon
|
from swsscommon import swsscommon
|
||||||
@ -29,7 +29,7 @@ interval_test_data = {
|
|||||||
|
|
||||||
|
|
||||||
def test_construct_without_sock(mock_swsscommon_dbconnector_init):
|
def test_construct_without_sock(mock_swsscommon_dbconnector_init):
|
||||||
dhcp_server_utils.DhcpDbConnector()
|
utils.DhcpDbConnector()
|
||||||
mock_swsscommon_dbconnector_init.assert_has_calls([
|
mock_swsscommon_dbconnector_init.assert_has_calls([
|
||||||
call(swsscommon.CONFIG_DB, "127.0.0.1", 6379, 0),
|
call(swsscommon.CONFIG_DB, "127.0.0.1", 6379, 0),
|
||||||
call(swsscommon.STATE_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):
|
def test_construct_sock(mock_swsscommon_dbconnector_init):
|
||||||
redis_sock = "/var/run/redis/redis.sock"
|
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
|
assert dhcp_db_connector.redis_sock == redis_sock
|
||||||
|
|
||||||
mock_swsscommon_dbconnector_init.assert_has_calls([
|
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):
|
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, \
|
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):
|
patch.object(swsscommon.Table, "hget", side_effect=mock_hget):
|
||||||
ret = dhcp_db_connector.get_config_db_table("VLAN")
|
ret = dhcp_db_connector.get_config_db_table("VLAN")
|
||||||
mock_swsscommon_table_init.assert_called_once_with(dhcp_db_connector.config_db, "VLAN")
|
mock_swsscommon_table_init.assert_called_once_with(dhcp_db_connector.config_db, "VLAN")
|
||||||
print(ret)
|
|
||||||
mock_get_keys.assert_called_once_with()
|
mock_get_keys.assert_called_once_with()
|
||||||
print(ret)
|
|
||||||
assert ret == {
|
assert ret == {
|
||||||
"key1": {"list": ["1", "2"], "value": "3,4"},
|
"key1": {"list": ["1", "2"], "value": "3,4"},
|
||||||
"key2": {"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):
|
def test_merge_intervals(test_type):
|
||||||
intervals = convert_ip_address_intervals(interval_test_data[test_type]["intervals"])
|
intervals = convert_ip_address_intervals(interval_test_data[test_type]["intervals"])
|
||||||
expected_res = convert_ip_address_intervals(interval_test_data[test_type]["expected_res"])
|
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):
|
def mock_hget(_, field):
|
Loading…
Reference in New Issue
Block a user