Skip to content

Commit 274d320

Browse files
authored
[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
1 parent c85c12b commit 274d320

38 files changed

+1219
-84
lines changed

dockers/docker-dhcp-relay/Dockerfile.j2

+20
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ ENV IMAGE_VERSION=$image_version
1313
# Update apt's cache of available packages
1414
RUN apt-get update
1515

16+
RUN apt-get install -y libjsoncpp-dev {%- if INCLUDE_DHCP_SERVER == "y" %}\
17+
python3-dev \
18+
build-essential{%- endif %}
19+
20+
{% if INCLUDE_DHCP_SERVER == "y" -%}
21+
RUN pip3 install psutil
22+
{%- endif %}
23+
1624
RUN apt-get install -y libjsoncpp-dev
1725

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

34+
{% if docker_dhcp_relay_whls.strip() %}
35+
# Copy locally-built Python wheel dependencies
36+
{{ copy_files("python-wheels/", docker_dhcp_relay_whls.split(' '), "/python-wheels/") }}
37+
38+
# Install locally-built Python wheel dependencies
39+
{{ install_python_wheels(docker_dhcp_relay_whls.split(' ')) }}
40+
{% endif %}
41+
2642
# Clean up
43+
{% if INCLUDE_DHCP_SERVER == "y" -%}
44+
RUN apt-get remove -y build-essential \
45+
python3-dev
46+
{%- endif %}
2747
RUN apt-get clean -y && \
2848
apt-get autoclean -y && \
2949
apt-get autoremove -y && \

dockers/docker-dhcp-relay/cli-plugin-tests/mock_config.py

+40
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,46 @@
4545
}
4646
}
4747
}
48+
],
49+
[
50+
"ipv4_with_disabled_dhcp_server_with_header",
51+
{
52+
"config_db": {
53+
"VLAN": {
54+
"Vlan1000": {
55+
"dhcp_servers": [
56+
"192.0.0.1",
57+
"192.0.0.2"
58+
]
59+
}
60+
},
61+
"FEATURE": {
62+
"dhcp_server": {
63+
"state": "disabled"
64+
}
65+
}
66+
}
67+
}
68+
],
69+
[
70+
"ipv4_with_enabled_dhcp_server_with_header",
71+
{
72+
"config_db": {
73+
"VLAN": {
74+
"Vlan1000": {
75+
"dhcp_servers": [
76+
"192.0.0.1",
77+
"192.0.0.2"
78+
]
79+
}
80+
},
81+
"FEATURE": {
82+
"dhcp_server": {
83+
"state": "enabled"
84+
}
85+
}
86+
}
87+
}
4888
]
4989
]
5090

dockers/docker-dhcp-relay/cli-plugin-tests/test_config_dhcp_relay.py

+30
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,36 @@ def test_config_add_del_dhcp_relay(self, mock_cfgdb, ip_version):
224224
db.cfgdb.set_entry.assert_called_once_with(config_db_table, "Vlan1000",
225225
expected_dhcp_relay_del_config_db_output[ip_version])
226226

227+
def test_config_add_del_dhcp_relay_with_enable_dhcp_server(self, mock_cfgdb):
228+
runner = CliRunner()
229+
db = Db()
230+
db.cfgdb = mock_cfgdb
231+
ip_version = "ipv4"
232+
test_ip = IP_VER_TEST_PARAM_MAP[ip_version]["ips"][0]
233+
234+
with mock.patch("utilities_common.cli.run_command"), \
235+
mock.patch.object(dhcp_relay, "is_dhcp_server_enabled", return_value=True):
236+
# add new dhcp relay
237+
result = runner.invoke(dhcp_relay.dhcp_relay.commands[ip_version]
238+
.commands[IP_VER_TEST_PARAM_MAP[ip_version]["command"]]
239+
.commands["add"], ["1000", test_ip], obj=db)
240+
print(result.exit_code)
241+
print(result.output)
242+
assert result.exit_code == 0
243+
assert "Cannot change ipv4 dhcp_relay configuration when dhcp_server feature is enabled" in result.output
244+
245+
db.cfgdb.set_entry.reset_mock()
246+
# del dhcp relay
247+
with mock.patch("utilities_common.cli.run_command"), \
248+
mock.patch.object(dhcp_relay, "is_dhcp_server_enabled", return_value=True):
249+
result = runner.invoke(dhcp_relay.dhcp_relay.commands[ip_version]
250+
.commands[IP_VER_TEST_PARAM_MAP[ip_version]["command"]]
251+
.commands["del"], ["1000", test_ip], obj=db)
252+
print(result.exit_code)
253+
print(result.output)
254+
assert result.exit_code == 0
255+
assert "Cannot change ipv4 dhcp_relay configuration when dhcp_server feature is enabled" in result.output
256+
227257
def test_config_add_del_multiple_dhcp_relay(self, mock_cfgdb, ip_version):
228258
runner = CliRunner()
229259
db = Db()

dockers/docker-dhcp-relay/cli-plugin-tests/test_show_dhcp_relay.py

+50-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pytest
22
import sys
3+
import click
34
import os
45
sys.path.append('../cli/show/plugins/')
56
import show_dhcp_relay as show
@@ -35,6 +36,14 @@
3536
+-------------+----------------------+
3637
"""
3738

39+
expected_ipv4_table_with_enabled_dhcp_server_with_header = """\
40+
+-------------+----------------------+
41+
| Interface | DHCP Relay Address |
42+
+=============+======================+
43+
| Vlan1000 | N/A |
44+
+-------------+----------------------+
45+
"""
46+
3847
expected_ipv6_table_without_header = """\
3948
-------- ------------
4049
Vlan1000 fc02:2000::1
@@ -86,12 +95,18 @@ def test_plugin_registration():
8695
assert 'DHCP Helper Address' in dict(vlan.VlanBrief.COLUMNS)
8796

8897

89-
def test_dhcp_relay_column_output():
98+
@pytest.mark.parametrize("feature_table", [{}, {"dhcp_server": {"state": "disabled"}},
99+
{"dhcp_server": {"state": "enabled"}}, {"dhcp_server": {}}])
100+
def test_dhcp_relay_column_output(feature_table):
90101
ctx = (
91102
({'Vlan1001': {'dhcp_servers': ['192.0.0.1', '192.168.0.2']}}, {}, {}),
92-
(),
103+
(MockDb({"FEATURE": feature_table})),
93104
)
94-
assert show.get_dhcp_helper_address(ctx, 'Vlan1001') == '192.0.0.1\n192.168.0.2'
105+
if "dhcp_server" in feature_table and "state" in feature_table["dhcp_server"] and \
106+
feature_table["dhcp_server"]["state"] == "enabled":
107+
assert show.get_dhcp_helper_address(ctx, 'Vlan1001') == 'N/A'
108+
else:
109+
assert show.get_dhcp_helper_address(ctx, 'Vlan1001') == '192.0.0.1\n192.168.0.2'
95110

96111

97112
@parameterized.expand(COMMON_TEST_DATA)
@@ -103,7 +118,7 @@ def test_show_dhcp_relay(test_name, test_data, fs):
103118
config_db = MockConfigDb()
104119
ip_version = "ipv4" if "ipv4" in test_name else "ipv6"
105120
table = config_db.get_table(IP_VER_TEST_PARAM_MAP[ip_version]["table"])
106-
if test_name == "ipv4_with_header":
121+
if test_name in ["ipv4_with_header", "ipv4_with_disabled_dhcp_server_with_header"]:
107122
result = show.get_dhcp_relay_data_with_header(table, IP_VER_TEST_PARAM_MAP[ip_version]["entry"])
108123
expected_output = expected_ipv4_table_with_header
109124
elif test_name == "ipv6_with_header":
@@ -112,6 +127,9 @@ def test_show_dhcp_relay(test_name, test_data, fs):
112127
elif test_name == "ipv6_without_header":
113128
result = show.get_data(table, "Vlan1000")
114129
expected_output = expected_ipv6_table_without_header
130+
elif test_name == "ipv4_with_enabled_dhcp_server_with_header":
131+
result = show.get_dhcp_relay_data_with_header(table, IP_VER_TEST_PARAM_MAP[ip_version]["entry"], True)
132+
expected_output = expected_ipv4_table_with_enabled_dhcp_server_with_header
115133
assert result == expected_output
116134

117135

@@ -153,3 +171,31 @@ def test_show_multi_dhcp_relay(test_name, test_data, fs):
153171
else:
154172
expected_output = expected_ipv6_table_multi_with_header
155173
assert result == expected_output
174+
175+
176+
def test_show_dhcp_relay_ipv4_counter_with_enabled_dhcp_server():
177+
with mock.patch.object(show, "is_dhcp_server_enabled", return_value=True), \
178+
mock.patch.object(swsscommon.ConfigDBConnector, "connect", return_value=None), \
179+
mock.patch.object(swsscommon.ConfigDBConnector, "get_table", return_value=None), \
180+
mock.patch.object(click, "echo", return_value=None) as mock_echo:
181+
show.ipv4_counters("Etherner1")
182+
expected_param = "Unsupport to check dhcp_relay ipv4 counter when dhcp_server feature is enabled"
183+
mock_echo.assert_called_once_with(expected_param)
184+
185+
186+
@pytest.mark.parametrize("enable_dhcp_server", [True, False])
187+
def test_is_dhcp_server_enabled(enable_dhcp_server):
188+
result = show.is_dhcp_server_enabled({"dhcp_server": {"state": "enabled" if enable_dhcp_server else "disabled"}})
189+
assert result == enable_dhcp_server
190+
191+
192+
class MockDb(object):
193+
class MockCfgDb(object):
194+
def __init__(self, mock_cfgdb):
195+
self.mock_cfgdb = mock_cfgdb
196+
197+
def get_table(self, table_name):
198+
return self.mock_cfgdb.get(table_name, {})
199+
200+
def __init__(self, mock_cfgdb):
201+
self.cfgdb = self.MockCfgDb(mock_cfgdb)

dockers/docker-dhcp-relay/cli/config/plugins/dhcp_relay.py

+17
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ def del_dhcp_relay(vid, dhcp_relay_ips, db, ip_version):
116116
ctx.fail("Restart service dhcp_relay failed with error {}".format(e))
117117

118118

119+
def is_dhcp_server_enabled(db):
120+
dhcp_server_feature_entry = db.cfgdb.get_entry("FEATURE", "dhcp_server")
121+
return "state" in dhcp_server_feature_entry and dhcp_server_feature_entry["state"] == "enabled"
122+
123+
119124
@click.group(cls=clicommon.AbbreviationGroup, name="dhcp_relay")
120125
def dhcp_relay():
121126
"""config DHCP_Relay information"""
@@ -163,6 +168,9 @@ def dhcp_relay_ipv4_helper():
163168
@click.argument("dhcp_relay_helpers", nargs=-1, required=True)
164169
@clicommon.pass_db
165170
def add_dhcp_relay_ipv4_helper(db, vid, dhcp_relay_helpers):
171+
if is_dhcp_server_enabled(db):
172+
click.echo("Cannot change ipv4 dhcp_relay configuration when dhcp_server feature is enabled")
173+
return
166174
add_dhcp_relay(vid, dhcp_relay_helpers, db, IPV4)
167175

168176

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

176187

@@ -207,6 +218,9 @@ def add_vlan_dhcp_relay_destination(db, vid, dhcp_relay_destination_ips):
207218
click.echo("{} is already a DHCP relay destination for {}".format(ip_addr, vlan_name))
208219
continue
209220
if clicommon.ipaddress_type(ip_addr) == 4:
221+
if is_dhcp_server_enabled(db):
222+
click.echo("Cannot change dhcp_relay configuration when dhcp_server feature is enabled")
223+
return
210224
dhcp_servers.append(ip_addr)
211225
else:
212226
dhcpv6_servers.append(ip_addr)
@@ -253,6 +267,9 @@ def del_vlan_dhcp_relay_destination(db, vid, dhcp_relay_destination_ips):
253267
if (ip_addr not in dhcp_servers) and (ip_addr not in dhcpv6_servers):
254268
ctx.fail("{} is not a DHCP relay destination for {}".format(ip_addr, vlan_name))
255269
if clicommon.ipaddress_type(ip_addr) == 4:
270+
if is_dhcp_server_enabled(db):
271+
click.echo("Cannot change dhcp_relay configuration when dhcp_server feature is enabled")
272+
return
256273
dhcp_servers.remove(ip_addr)
257274
else:
258275
dhcpv6_servers.remove(ip_addr)

dockers/docker-dhcp-relay/cli/show/plugins/show_dhcp_relay.py

+28-6
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,15 @@
3232

3333

3434
def get_dhcp_helper_address(ctx, vlan):
35-
cfg, _ = ctx
35+
cfg, db = ctx
3636
vlan_dhcp_helper_data, _, _ = cfg
3737
vlan_config = vlan_dhcp_helper_data.get(vlan)
3838
if not vlan_config:
3939
return ""
4040

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

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

@@ -96,6 +98,11 @@ def dhcp4relay_counters():
9698

9799

98100
def ipv4_counters(interface):
101+
config_db.connect()
102+
feature_tbl = config_db.get_table("FEATURE")
103+
if is_dhcp_server_enabled(feature_tbl):
104+
click.echo("Unsupport to check dhcp_relay ipv4 counter when dhcp_server feature is enabled")
105+
return
99106
counter = DHCPv4_Counter()
100107
counter_intf = counter.get_interface()
101108

@@ -193,7 +200,7 @@ def dhcp_relay_helper():
193200
pass
194201

195202

196-
def get_dhcp_relay_data_with_header(table_data, entry_name):
203+
def get_dhcp_relay_data_with_header(table_data, entry_name, dhcp_server_enabled=False):
197204
vlan_relay = {}
198205
vlans = table_data.keys()
199206
for vlan in vlans:
@@ -203,15 +210,25 @@ def get_dhcp_relay_data_with_header(table_data, entry_name):
203210
continue
204211

205212
vlan_relay[vlan] = []
206-
for address in dhcp_relay_data:
207-
vlan_relay[vlan].append(address)
213+
if dhcp_server_enabled:
214+
vlan_relay[vlan].append("N/A")
215+
else:
216+
for address in dhcp_relay_data:
217+
vlan_relay[vlan].append(address)
208218

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

214224

225+
def is_dhcp_server_enabled(feature_tbl):
226+
if feature_tbl is not None and "dhcp_server" in feature_tbl and "state" in feature_tbl["dhcp_server"] and \
227+
feature_tbl["dhcp_server"]["state"] == "enabled":
228+
return True
229+
return False
230+
231+
215232
def get_dhcp_relay(table_name, entry_name, with_header):
216233
if config_db is None:
217234
return
@@ -221,8 +238,13 @@ def get_dhcp_relay(table_name, entry_name, with_header):
221238
if table_data is None:
222239
return
223240

241+
dhcp_server_enabled = False
242+
if table_name == VLAN:
243+
feature_tbl = config_db.get_table("FEATURE")
244+
dhcp_server_enabled = is_dhcp_server_enabled(feature_tbl)
245+
224246
if with_header:
225-
output = get_dhcp_relay_data_with_header(table_data, entry_name)
247+
output = get_dhcp_relay_data_with_header(table_data, entry_name, dhcp_server_enabled)
226248
print(output)
227249
else:
228250
vlans = config_db.get_keys(table_name)

dockers/docker-dhcp-relay/dhcp-relay.programs.j2

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22
programs=
33
{%- set relay_for_ipv6 = { 'flag': False } %}
44
{%- set add_preceding_comma = { 'flag': False } %}
5+
{% if dhcp_server_ipv4_enabled %}
6+
{% set _dummy = add_preceding_comma.update({'flag': True}) %}
7+
dhcprelayd
8+
{%- endif %}
59
{% for vlan_name in VLAN_INTERFACE %}
610
{# Append DHCPv4 agents #}
7-
{% if VLAN and vlan_name in VLAN and 'dhcp_servers' in VLAN[vlan_name] and VLAN[vlan_name]['dhcp_servers']|length > 0 %}
11+
{% 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 %}
812
{% if add_preceding_comma.flag %},{% endif %}
913
{% set _dummy = add_preceding_comma.update({'flag': True}) %}
1014
isc-dhcpv4-relay-{{ vlan_name }}

0 commit comments

Comments
 (0)