Skip to content

Commit 6ade59c

Browse files
author
Shlomi Bitton
committed
Adapt config/show CLI commands to support DHCPv6 relay feature
Support multiple dhcp servers assignment in one command Fix IP validation Adapt and add new UT cases Signed-off-by: Shlomi Bitton <[email protected]>
1 parent c89b62e commit 6ade59c

File tree

4 files changed

+161
-39
lines changed

4 files changed

+161
-39
lines changed

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

+92-4
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,32 @@
1313
import dhcp_relay
1414

1515
config_vlan_add_dhcp_relay_output="""\
16-
Added DHCP relay destination address 192.0.0.100 to Vlan1000
16+
Added DHCP relay destination addresses ['192.0.0.100'] to Vlan1000
17+
Restarting DHCP relay service...
18+
"""
19+
20+
config_vlan_add_dhcpv6_relay_output="""\
21+
Added DHCP relay destination addresses ['fc02:2000::1'] to Vlan1000
22+
Restarting DHCP relay service...
23+
"""
24+
25+
config_vlan_add_multiple_dhcpv6_relay_output="""\
26+
Added DHCP relay destination addresses ['fc02:2000::1', 'fc02:2000::2', 'fc02:2000::3'] to Vlan1000
1727
Restarting DHCP relay service...
1828
"""
1929

2030
config_vlan_del_dhcp_relay_output="""\
21-
Removed DHCP relay destination address 192.0.0.100 from Vlan1000
31+
Removed DHCP relay destination addresses ('192.0.0.100',) from Vlan1000
32+
Restarting DHCP relay service...
33+
"""
34+
35+
config_vlan_del_dhcpv6_relay_output="""\
36+
Removed DHCP relay destination addresses ('fc02:2000::1',) from Vlan1000
37+
Restarting DHCP relay service...
38+
"""
39+
40+
config_vlan_del_multiple_dhcpv6_relay_output="""\
41+
Removed DHCP relay destination addresses ('fc02:2000::1', 'fc02:2000::2', 'fc02:2000::3') from Vlan1000
2242
Restarting DHCP relay service...
2343
"""
2444

@@ -54,19 +74,29 @@ def test_config_vlan_add_dhcp_relay_with_invalid_vlanid(self):
5474
assert "Error: Vlan4096 doesn't exist" in result.output
5575
assert mock_run_command.call_count == 0
5676

57-
def test_config_vlan_add_dhcp_relay_with_invalid_ip(self):
77+
def test_config_vlan_add_dhcp_relay_with_invalid_ip(self, mock_cfgdb):
5878
runner = CliRunner()
79+
db = Db()
80+
db.cfgdb = mock_cfgdb
5981

6082
with mock.patch('utilities_common.cli.run_command') as mock_run_command:
6183
result = runner.invoke(dhcp_relay.vlan_dhcp_relay.commands["add"],
62-
["1000", "192.0.0.1000"])
84+
["1000", "192.0.0.1000"], obj=db)
6385
print(result.exit_code)
6486
print(result.output)
6587
# traceback.print_tb(result.exc_info[2])
6688
assert result.exit_code != 0
6789
assert "Error: 192.0.0.1000 is invalid IP address" in result.output
6890
assert mock_run_command.call_count == 0
6991

92+
result = runner.invoke(dhcp_relay.vlan_dhcp_relay.commands["add"],
93+
["1000", "192.0.0."], obj=db)
94+
print(result.exit_code)
95+
print(result.output)
96+
assert result.exit_code != 0
97+
assert "Error: 192.0.0. is invalid IP address" in result.output
98+
assert mock_run_command.call_count == 0
99+
70100
def test_config_vlan_add_dhcp_relay_with_exist_ip(self, mock_cfgdb):
71101
runner = CliRunner()
72102
db = Db()
@@ -110,6 +140,64 @@ def test_config_vlan_add_del_dhcp_relay_dest(self, mock_cfgdb):
110140
assert mock_run_command.call_count == 3
111141
db.cfgdb.set_entry.assert_called_once_with('VLAN', 'Vlan1000', {'dhcp_servers': ['192.0.0.1']})
112142

143+
def test_config_vlan_add_del_dhcpv6_relay_dest(self, mock_cfgdb):
144+
runner = CliRunner()
145+
db = Db()
146+
db.cfgdb = mock_cfgdb
147+
148+
# add new relay dest
149+
with mock.patch("utilities_common.cli.run_command") as mock_run_command:
150+
result = runner.invoke(dhcp_relay.vlan_dhcp_relay.commands["add"],
151+
["1000", "fc02:2000::1"], obj=db)
152+
print(result.exit_code)
153+
print(result.output)
154+
assert result.exit_code == 0
155+
assert result.output == config_vlan_add_dhcpv6_relay_output
156+
assert mock_run_command.call_count == 3
157+
db.cfgdb.set_entry.assert_called_once_with('VLAN', 'Vlan1000', {'dhcp_servers': ['192.0.0.1'], 'dhcpv6_servers': ['fc02:2000::1']})
158+
159+
db.cfgdb.set_entry.reset_mock()
160+
161+
# del relay dest
162+
with mock.patch("utilities_common.cli.run_command") as mock_run_command:
163+
result = runner.invoke(dhcp_relay.vlan_dhcp_relay.commands["del"],
164+
["1000", "fc02:2000::1"], obj=db)
165+
print(result.exit_code)
166+
print(result.output)
167+
assert result.exit_code == 0
168+
assert result.output == config_vlan_del_dhcpv6_relay_output
169+
assert mock_run_command.call_count == 3
170+
db.cfgdb.set_entry.assert_called_once_with('VLAN', 'Vlan1000', {'dhcp_servers': ['192.0.0.1']})
171+
172+
def test_config_vlan_add_del_multiple_dhcpv6_relay_dest(self, mock_cfgdb):
173+
runner = CliRunner()
174+
db = Db()
175+
db.cfgdb = mock_cfgdb
176+
177+
# add new relay dest
178+
with mock.patch("utilities_common.cli.run_command") as mock_run_command:
179+
result = runner.invoke(dhcp_relay.vlan_dhcp_relay.commands["add"],
180+
["1000", "fc02:2000::1", "fc02:2000::2", "fc02:2000::3"], obj=db)
181+
print(result.exit_code)
182+
print(result.output)
183+
assert result.exit_code == 0
184+
assert result.output == config_vlan_add_multiple_dhcpv6_relay_output
185+
assert mock_run_command.call_count == 3
186+
db.cfgdb.set_entry.assert_called_once_with('VLAN', 'Vlan1000', {'dhcp_servers': ['192.0.0.1'], 'dhcpv6_servers': ['fc02:2000::1', 'fc02:2000::2', 'fc02:2000::3']})
187+
188+
db.cfgdb.set_entry.reset_mock()
189+
190+
# del relay dest
191+
with mock.patch("utilities_common.cli.run_command") as mock_run_command:
192+
result = runner.invoke(dhcp_relay.vlan_dhcp_relay.commands["del"],
193+
["1000", "fc02:2000::1", "fc02:2000::2", "fc02:2000::3"], obj=db)
194+
print(result.exit_code)
195+
print(result.output)
196+
assert result.exit_code == 0
197+
assert result.output == config_vlan_del_multiple_dhcpv6_relay_output
198+
assert mock_run_command.call_count == 3
199+
db.cfgdb.set_entry.assert_called_once_with('VLAN', 'Vlan1000', {'dhcp_servers': ['192.0.0.1']})
200+
113201
def test_config_vlan_remove_nonexist_dhcp_relay_dest(self, mock_cfgdb):
114202
runner = CliRunner()
115203
db = Db()

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ def test_plugin_registration(self):
2020

2121
def test_dhcp_relay_column_output(self):
2222
ctx = (
23-
({'Vlan100': {'dhcp_servers': ['192.0.0.1', '192.168.0.2']}}, {}, {}),
23+
({'Vlan100': {'dhcp_servers': ['192.0.0.1', '192.168.0.2'], 'dhcpv6_servers': ['fc02:2000::1', 'fc02:2000::2']}}, {}, {}),
2424
(),
2525
)
26-
assert show_dhcp_relay.get_dhcp_helper_address(ctx, 'Vlan100') == '192.0.0.1\n192.168.0.2'
26+
assert show_dhcp_relay.get_dhcp_helper_address(ctx, 'Vlan100') == '192.0.0.1\n192.168.0.2\nfc02:2000::1\nfc02:2000::2'
2727

2828

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

+65-32
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,105 @@
11
import click
22
import utilities_common.cli as clicommon
3+
import ipaddress
34

45
@click.group(cls=clicommon.AbbreviationGroup, name='dhcp_relay')
56
def vlan_dhcp_relay():
67
pass
78

89
@vlan_dhcp_relay.command('add')
910
@click.argument('vid', metavar='<vid>', required=True, type=int)
10-
@click.argument('dhcp_relay_destination_ip', metavar='<dhcp_relay_destination_ip>', required=True)
11+
@click.argument('dhcp_relay_destination_ips', nargs=-1, required=True)
1112
@clicommon.pass_db
12-
def add_vlan_dhcp_relay_destination(db, vid, dhcp_relay_destination_ip):
13+
def add_vlan_dhcp_relay_destination(db, vid, dhcp_relay_destination_ips):
1314
""" Add a destination IP address to the VLAN's DHCP relay """
1415

1516
ctx = click.get_current_context()
17+
added_servers = []
1618

17-
if not clicommon.is_ipaddress(dhcp_relay_destination_ip):
18-
ctx.fail('{} is invalid IP address'.format(dhcp_relay_destination_ip))
19-
19+
# Verify vlan is valid
2020
vlan_name = 'Vlan{}'.format(vid)
2121
vlan = db.cfgdb.get_entry('VLAN', vlan_name)
2222
if len(vlan) == 0:
2323
ctx.fail("{} doesn't exist".format(vlan_name))
2424

25-
dhcp_relay_dests = vlan.get('dhcp_servers', [])
26-
if dhcp_relay_destination_ip in dhcp_relay_dests:
27-
click.echo("{} is already a DHCP relay destination for {}".format(dhcp_relay_destination_ip, vlan_name))
28-
return
25+
# Verify all ip addresses are valid and not exist in DB
26+
dhcp_servers = vlan.get('dhcp_servers', [])
27+
dhcpv6_servers = vlan.get('dhcpv6_servers', [])
28+
29+
for ip_addr in dhcp_relay_destination_ips:
30+
try:
31+
ipaddress.ip_address(ip_addr)
32+
if (ip_addr in dhcp_servers) or (ip_addr in dhcpv6_servers):
33+
click.echo("{} is already a DHCP relay destination for {}".format(ip_addr, vlan_name))
34+
continue
35+
if clicommon.ipaddress_type(ip_addr) == 4:
36+
dhcp_servers.append(ip_addr)
37+
else:
38+
dhcpv6_servers.append(ip_addr)
39+
added_servers.append(ip_addr)
40+
except:
41+
ctx.fail('{} is invalid IP address'.format(ip_addr))
42+
43+
# Append new dhcp servers to config DB
44+
if len(dhcp_servers):
45+
vlan['dhcp_servers'] = dhcp_servers
46+
if len(dhcpv6_servers):
47+
vlan['dhcpv6_servers'] = dhcpv6_servers
2948

30-
dhcp_relay_dests.append(dhcp_relay_destination_ip)
31-
vlan['dhcp_servers'] = dhcp_relay_dests
3249
db.cfgdb.set_entry('VLAN', vlan_name, vlan)
33-
click.echo("Added DHCP relay destination address {} to {}".format(dhcp_relay_destination_ip, vlan_name))
34-
try:
35-
click.echo("Restarting DHCP relay service...")
36-
clicommon.run_command("systemctl stop dhcp_relay", display_cmd=False)
37-
clicommon.run_command("systemctl reset-failed dhcp_relay", display_cmd=False)
38-
clicommon.run_command("systemctl start dhcp_relay", display_cmd=False)
39-
except SystemExit as e:
40-
ctx.fail("Restart service dhcp_relay failed with error {}".format(e))
50+
51+
if len(added_servers):
52+
click.echo("Added DHCP relay destination addresses {} to {}".format(added_servers, vlan_name))
53+
try:
54+
click.echo("Restarting DHCP relay service...")
55+
clicommon.run_command("systemctl stop dhcp_relay", display_cmd=False)
56+
clicommon.run_command("systemctl reset-failed dhcp_relay", display_cmd=False)
57+
clicommon.run_command("systemctl start dhcp_relay", display_cmd=False)
58+
except SystemExit as e:
59+
ctx.fail("Restart service dhcp_relay failed with error {}".format(e))
4160

4261
@vlan_dhcp_relay.command('del')
4362
@click.argument('vid', metavar='<vid>', required=True, type=int)
44-
@click.argument('dhcp_relay_destination_ip', metavar='<dhcp_relay_destination_ip>', required=True)
63+
@click.argument('dhcp_relay_destination_ips', nargs=-1, required=True)
4564
@clicommon.pass_db
46-
def del_vlan_dhcp_relay_destination(db, vid, dhcp_relay_destination_ip):
65+
def del_vlan_dhcp_relay_destination(db, vid, dhcp_relay_destination_ips):
4766
""" Remove a destination IP address from the VLAN's DHCP relay """
4867

4968
ctx = click.get_current_context()
5069

51-
if not clicommon.is_ipaddress(dhcp_relay_destination_ip):
52-
ctx.fail('{} is invalid IP address'.format(dhcp_relay_destination_ip))
53-
70+
# Verify vlan is valid
5471
vlan_name = 'Vlan{}'.format(vid)
5572
vlan = db.cfgdb.get_entry('VLAN', vlan_name)
5673
if len(vlan) == 0:
5774
ctx.fail("{} doesn't exist".format(vlan_name))
5875

59-
dhcp_relay_dests = vlan.get('dhcp_servers', [])
60-
if not dhcp_relay_destination_ip in dhcp_relay_dests:
61-
ctx.fail("{} is not a DHCP relay destination for {}".format(dhcp_relay_destination_ip, vlan_name))
76+
# Remove dhcp servers if they exist in the DB
77+
dhcp_servers = vlan.get('dhcp_servers', [])
78+
dhcpv6_servers = vlan.get('dhcpv6_servers', [])
79+
80+
for ip_addr in dhcp_relay_destination_ips:
81+
if (ip_addr not in dhcp_servers) and (ip_addr not in dhcpv6_servers):
82+
ctx.fail("{} is not a DHCP relay destination for {}".format(ip_addr, vlan_name))
83+
if clicommon.ipaddress_type(ip_addr) == 4:
84+
dhcp_servers.remove(ip_addr)
85+
else:
86+
dhcpv6_servers.remove(ip_addr)
87+
88+
# Update dhcp servers to config DB
89+
if len(dhcp_servers):
90+
vlan['dhcp_servers'] = dhcp_servers
91+
else:
92+
if 'dhcp_servers' in vlan.keys():
93+
del vlan['dhcp_servers']
6294

63-
dhcp_relay_dests.remove(dhcp_relay_destination_ip)
64-
if len(dhcp_relay_dests) == 0:
65-
del vlan['dhcp_servers']
95+
if len(dhcpv6_servers):
96+
vlan['dhcpv6_servers'] = dhcpv6_servers
6697
else:
67-
vlan['dhcp_servers'] = dhcp_relay_dests
98+
if 'dhcpv6_servers' in vlan.keys():
99+
del vlan['dhcpv6_servers']
100+
68101
db.cfgdb.set_entry('VLAN', vlan_name, vlan)
69-
click.echo("Removed DHCP relay destination address {} from {}".format(dhcp_relay_destination_ip, vlan_name))
102+
click.echo("Removed DHCP relay destination addresses {} from {}".format(dhcp_relay_destination_ips, vlan_name))
70103
try:
71104
click.echo("Restarting DHCP relay service...")
72105
clicommon.run_command("systemctl stop dhcp_relay", display_cmd=False)

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ def get_dhcp_helper_address(ctx, vlan):
99
return ""
1010

1111
dhcp_helpers = vlan_config.get('dhcp_servers', [])
12+
dhcpv6_helpers = vlan_config.get('dhcpv6_servers', [])
1213

13-
return '\n'.join(natsorted(dhcp_helpers))
14+
return '\n'.join(natsorted(dhcp_helpers) + natsorted(dhcpv6_helpers))
1415

1516

1617
vlan.VlanBrief.register_column('DHCP Helper Address', get_dhcp_helper_address)

0 commit comments

Comments
 (0)