Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[dhcp_server] Add dhcprelayd for dhcp_server feature #16947

Merged
merged 11 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions dockers/docker-dhcp-relay/Dockerfile.j2
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ ENV IMAGE_VERSION=$image_version
# Update apt's cache of available packages
RUN apt-get update

RUN apt-get install -y libjsoncpp-dev {%- if INCLUDE_DHCP_SERVER == "y" %}\
python3-dev \
build-essential{%- endif %}

{% if INCLUDE_DHCP_SERVER == "y" -%}
RUN pip3 install psutil
{%- endif %}

RUN apt-get install -y libjsoncpp-dev

{% if docker_dhcp_relay_debs.strip() -%}
Expand All @@ -23,7 +31,19 @@ RUN apt-get install -y libjsoncpp-dev
{{ install_debian_packages(docker_dhcp_relay_debs.split(' ')) }}
{%- endif %}

{% if docker_dhcp_relay_whls.strip() %}
# Copy locally-built Python wheel dependencies
{{ copy_files("python-wheels/", docker_dhcp_relay_whls.split(' '), "/python-wheels/") }}

# Install locally-built Python wheel dependencies
{{ install_python_wheels(docker_dhcp_relay_whls.split(' ')) }}
{% endif %}

# Clean up
{% if INCLUDE_DHCP_SERVER == "y" -%}
RUN apt-get remove -y build-essential \
python3-dev
{%- endif %}
RUN apt-get clean -y && \
apt-get autoclean -y && \
apt-get autoremove -y && \
Expand Down
40 changes: 40 additions & 0 deletions dockers/docker-dhcp-relay/cli-plugin-tests/mock_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,46 @@
}
}
}
],
[
"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"
}
}
}
}
]
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,36 @@ def test_config_add_del_dhcp_relay(self, mock_cfgdb, ip_version):
db.cfgdb.set_entry.assert_called_once_with(config_db_table, "Vlan1000",
expected_dhcp_relay_del_config_db_output[ip_version])

def test_config_add_del_dhcp_relay_with_enable_dhcp_server(self, mock_cfgdb):
runner = CliRunner()
db = Db()
db.cfgdb = mock_cfgdb
ip_version = "ipv4"
test_ip = IP_VER_TEST_PARAM_MAP[ip_version]["ips"][0]

with mock.patch("utilities_common.cli.run_command"), \
mock.patch.object(dhcp_relay, "is_dhcp_server_enabled", return_value=True):
# add new dhcp relay
result = runner.invoke(dhcp_relay.dhcp_relay.commands[ip_version]
.commands[IP_VER_TEST_PARAM_MAP[ip_version]["command"]]
.commands["add"], ["1000", test_ip], obj=db)
print(result.exit_code)
print(result.output)
assert result.exit_code == 0
assert "Cannot change ipv4 dhcp_relay configuration when dhcp_server feature is enabled" in result.output

db.cfgdb.set_entry.reset_mock()
# del dhcp relay
with mock.patch("utilities_common.cli.run_command"), \
mock.patch.object(dhcp_relay, "is_dhcp_server_enabled", return_value=True):
result = runner.invoke(dhcp_relay.dhcp_relay.commands[ip_version]
.commands[IP_VER_TEST_PARAM_MAP[ip_version]["command"]]
.commands["del"], ["1000", test_ip], obj=db)
print(result.exit_code)
print(result.output)
assert result.exit_code == 0
assert "Cannot change ipv4 dhcp_relay configuration when dhcp_server feature is enabled" in result.output

def test_config_add_del_multiple_dhcp_relay(self, mock_cfgdb, ip_version):
runner = CliRunner()
db = Db()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest
import sys
import click
import os
sys.path.append('../cli/show/plugins/')
import show_dhcp_relay as show
Expand Down Expand Up @@ -35,6 +36,14 @@
+-------------+----------------------+
"""

expected_ipv4_table_with_enabled_dhcp_server_with_header = """\
+-------------+----------------------+
| Interface | DHCP Relay Address |
+=============+======================+
| Vlan1000 | N/A |
+-------------+----------------------+
"""

expected_ipv6_table_without_header = """\
-------- ------------
Vlan1000 fc02:2000::1
Expand Down Expand Up @@ -86,12 +95,18 @@ def test_plugin_registration():
assert 'DHCP Helper Address' in dict(vlan.VlanBrief.COLUMNS)


def test_dhcp_relay_column_output():
@pytest.mark.parametrize("feature_table", [{}, {"dhcp_server": {"state": "disabled"}},
{"dhcp_server": {"state": "enabled"}}, {"dhcp_server": {}}])
def test_dhcp_relay_column_output(feature_table):
ctx = (
({'Vlan1001': {'dhcp_servers': ['192.0.0.1', '192.168.0.2']}}, {}, {}),
(),
(MockDb({"FEATURE": feature_table})),
)
assert show.get_dhcp_helper_address(ctx, 'Vlan1001') == '192.0.0.1\n192.168.0.2'
if "dhcp_server" in feature_table and "state" in feature_table["dhcp_server"] and \
feature_table["dhcp_server"]["state"] == "enabled":
assert show.get_dhcp_helper_address(ctx, 'Vlan1001') == 'N/A'
else:
assert show.get_dhcp_helper_address(ctx, 'Vlan1001') == '192.0.0.1\n192.168.0.2'


@parameterized.expand(COMMON_TEST_DATA)
Expand All @@ -103,7 +118,7 @@ def test_show_dhcp_relay(test_name, test_data, fs):
config_db = MockConfigDb()
ip_version = "ipv4" if "ipv4" in test_name else "ipv6"
table = config_db.get_table(IP_VER_TEST_PARAM_MAP[ip_version]["table"])
if test_name == "ipv4_with_header":
if test_name in ["ipv4_with_header", "ipv4_with_disabled_dhcp_server_with_header"]:
result = show.get_dhcp_relay_data_with_header(table, IP_VER_TEST_PARAM_MAP[ip_version]["entry"])
expected_output = expected_ipv4_table_with_header
elif test_name == "ipv6_with_header":
Expand All @@ -112,6 +127,9 @@ def test_show_dhcp_relay(test_name, test_data, fs):
elif test_name == "ipv6_without_header":
result = show.get_data(table, "Vlan1000")
expected_output = expected_ipv6_table_without_header
elif test_name == "ipv4_with_enabled_dhcp_server_with_header":
result = show.get_dhcp_relay_data_with_header(table, IP_VER_TEST_PARAM_MAP[ip_version]["entry"], True)
expected_output = expected_ipv4_table_with_enabled_dhcp_server_with_header
assert result == expected_output


Expand Down Expand Up @@ -153,3 +171,31 @@ def test_show_multi_dhcp_relay(test_name, test_data, fs):
else:
expected_output = expected_ipv6_table_multi_with_header
assert result == expected_output


def test_show_dhcp_relay_ipv4_counter_with_enabled_dhcp_server():
with mock.patch.object(show, "is_dhcp_server_enabled", return_value=True), \
mock.patch.object(swsscommon.ConfigDBConnector, "connect", return_value=None), \
mock.patch.object(swsscommon.ConfigDBConnector, "get_table", return_value=None), \
mock.patch.object(click, "echo", return_value=None) as mock_echo:
show.ipv4_counters("Etherner1")
expected_param = "Unsupport to check dhcp_relay ipv4 counter when dhcp_server feature is enabled"
mock_echo.assert_called_once_with(expected_param)


@pytest.mark.parametrize("enable_dhcp_server", [True, False])
def test_is_dhcp_server_enabled(enable_dhcp_server):
result = show.is_dhcp_server_enabled({"dhcp_server": {"state": "enabled" if enable_dhcp_server else "disabled"}})
assert result == enable_dhcp_server


class MockDb(object):
class MockCfgDb(object):
def __init__(self, mock_cfgdb):
self.mock_cfgdb = mock_cfgdb

def get_table(self, table_name):
return self.mock_cfgdb.get(table_name, {})

def __init__(self, mock_cfgdb):
self.cfgdb = self.MockCfgDb(mock_cfgdb)
17 changes: 17 additions & 0 deletions dockers/docker-dhcp-relay/cli/config/plugins/dhcp_relay.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ def del_dhcp_relay(vid, dhcp_relay_ips, db, ip_version):
ctx.fail("Restart service dhcp_relay failed with error {}".format(e))


def is_dhcp_server_enabled(db):
dhcp_server_feature_entry = db.cfgdb.get_entry("FEATURE", "dhcp_server")
return "state" in dhcp_server_feature_entry and dhcp_server_feature_entry["state"] == "enabled"


@click.group(cls=clicommon.AbbreviationGroup, name="dhcp_relay")
def dhcp_relay():
"""config DHCP_Relay information"""
Expand Down Expand Up @@ -163,6 +168,9 @@ def dhcp_relay_ipv4_helper():
@click.argument("dhcp_relay_helpers", nargs=-1, required=True)
@clicommon.pass_db
def add_dhcp_relay_ipv4_helper(db, vid, dhcp_relay_helpers):
if is_dhcp_server_enabled(db):
yaqiangz marked this conversation as resolved.
Show resolved Hide resolved
click.echo("Cannot change ipv4 dhcp_relay configuration when dhcp_server feature is enabled")
return
add_dhcp_relay(vid, dhcp_relay_helpers, db, IPV4)


Expand All @@ -171,6 +179,9 @@ def add_dhcp_relay_ipv4_helper(db, vid, dhcp_relay_helpers):
@click.argument("dhcp_relay_helpers", nargs=-1, required=True)
@clicommon.pass_db
def del_dhcp_relay_ipv4_helper(db, vid, dhcp_relay_helpers):
if is_dhcp_server_enabled(db):
click.echo("Cannot change ipv4 dhcp_relay configuration when dhcp_server feature is enabled")
return
del_dhcp_relay(vid, dhcp_relay_helpers, db, IPV4)


Expand Down Expand Up @@ -207,6 +218,9 @@ def add_vlan_dhcp_relay_destination(db, vid, dhcp_relay_destination_ips):
click.echo("{} is already a DHCP relay destination for {}".format(ip_addr, vlan_name))
continue
if clicommon.ipaddress_type(ip_addr) == 4:
if is_dhcp_server_enabled(db):
click.echo("Cannot change dhcp_relay configuration when dhcp_server feature is enabled")
return
dhcp_servers.append(ip_addr)
else:
dhcpv6_servers.append(ip_addr)
Expand Down Expand Up @@ -253,6 +267,9 @@ def del_vlan_dhcp_relay_destination(db, vid, dhcp_relay_destination_ips):
if (ip_addr not in dhcp_servers) and (ip_addr not in dhcpv6_servers):
ctx.fail("{} is not a DHCP relay destination for {}".format(ip_addr, vlan_name))
if clicommon.ipaddress_type(ip_addr) == 4:
if is_dhcp_server_enabled(db):
click.echo("Cannot change dhcp_relay configuration when dhcp_server feature is enabled")
return
dhcp_servers.remove(ip_addr)
else:
dhcpv6_servers.remove(ip_addr)
Expand Down
34 changes: 28 additions & 6 deletions dockers/docker-dhcp-relay/cli/show/plugins/show_dhcp_relay.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@


def get_dhcp_helper_address(ctx, vlan):
cfg, _ = ctx
cfg, db = ctx
vlan_dhcp_helper_data, _, _ = cfg
vlan_config = vlan_dhcp_helper_data.get(vlan)
if not vlan_config:
return ""

dhcp_helpers = vlan_config.get('dhcp_servers', [])
feature_data = db.cfgdb.get_table("FEATURE")
dhcp_server_enabled = is_dhcp_server_enabled(feature_data)
dhcp_helpers = ["N/A"] if dhcp_server_enabled else vlan_config.get('dhcp_servers', [])

return '\n'.join(natsorted(dhcp_helpers))

Expand Down Expand Up @@ -96,6 +98,11 @@ def dhcp4relay_counters():


def ipv4_counters(interface):
config_db.connect()
feature_tbl = config_db.get_table("FEATURE")
if is_dhcp_server_enabled(feature_tbl):
click.echo("Unsupport to check dhcp_relay ipv4 counter when dhcp_server feature is enabled")
return
counter = DHCPv4_Counter()
counter_intf = counter.get_interface()

Expand Down Expand Up @@ -193,7 +200,7 @@ def dhcp_relay_helper():
pass


def get_dhcp_relay_data_with_header(table_data, entry_name):
def get_dhcp_relay_data_with_header(table_data, entry_name, dhcp_server_enabled=False):
vlan_relay = {}
vlans = table_data.keys()
for vlan in vlans:
Expand All @@ -203,15 +210,25 @@ def get_dhcp_relay_data_with_header(table_data, entry_name):
continue

vlan_relay[vlan] = []
for address in dhcp_relay_data:
vlan_relay[vlan].append(address)
if dhcp_server_enabled:
vlan_relay[vlan].append("N/A")
else:
for address in dhcp_relay_data:
vlan_relay[vlan].append(address)

dhcp_relay_vlan_keys = vlan_relay.keys()
relay_address_list = ["\n".join(vlan_relay[key]) for key in dhcp_relay_vlan_keys]
data = {"Interface": dhcp_relay_vlan_keys, "DHCP Relay Address": relay_address_list}
return tabulate(data, tablefmt='grid', stralign='right', headers='keys') + '\n'


def is_dhcp_server_enabled(feature_tbl):
if feature_tbl is not None and "dhcp_server" in feature_tbl and "state" in feature_tbl["dhcp_server"] and \
feature_tbl["dhcp_server"]["state"] == "enabled":
return True
return False


def get_dhcp_relay(table_name, entry_name, with_header):
if config_db is None:
return
Expand All @@ -221,8 +238,13 @@ def get_dhcp_relay(table_name, entry_name, with_header):
if table_data is None:
return

dhcp_server_enabled = False
if table_name == VLAN:
feature_tbl = config_db.get_table("FEATURE")
dhcp_server_enabled = is_dhcp_server_enabled(feature_tbl)

if with_header:
output = get_dhcp_relay_data_with_header(table_data, entry_name)
output = get_dhcp_relay_data_with_header(table_data, entry_name, dhcp_server_enabled)
print(output)
else:
vlans = config_db.get_keys(table_name)
Expand Down
6 changes: 5 additions & 1 deletion dockers/docker-dhcp-relay/dhcp-relay.programs.j2
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
programs=
{%- set relay_for_ipv6 = { 'flag': False } %}
{%- set add_preceding_comma = { 'flag': False } %}
{% if dhcp_server_ipv4_enabled %}
{% set _dummy = add_preceding_comma.update({'flag': True}) %}
dhcprelayd
{%- endif %}
{% for vlan_name in VLAN_INTERFACE %}
{# Append DHCPv4 agents #}
{% if VLAN and vlan_name in VLAN and 'dhcp_servers' in VLAN[vlan_name] and VLAN[vlan_name]['dhcp_servers']|length > 0 %}
{% if not dhcp_server_ipv4_enabled and VLAN and vlan_name in VLAN and 'dhcp_servers' in VLAN[vlan_name] and VLAN[vlan_name]['dhcp_servers']|length > 0 %}
{% if add_preceding_comma.flag %},{% endif %}
{% set _dummy = add_preceding_comma.update({'flag': True}) %}
isc-dhcpv4-relay-{{ vlan_name }}
Expand Down
Loading