Skip to content

Commit 5f5832c

Browse files
committed
[dhcp_relay] Fix dhcp_relay restart error while add/del vlan (sonic-net#2688)
Why I did In device that doesn't have dhcp_relay service, restart dhcp_relay after add/del vlan would encounter failed How I did it Add support to check whether device is support dhcp_relay service. How to verify it 1. Unit test 2. Build and install in device Signed-off-by: Yaqiang Zhu <[email protected]>
1 parent 03ef272 commit 5f5832c

3 files changed

Lines changed: 132 additions & 15 deletions

File tree

config/vlan.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
from time import sleep
77
from .utils import log
8+
DHCP_RELAY_TABLE = "DHCP_RELAY"
9+
DHCPV6_SERVERS = "dhcpv6_servers"
810

911
#
1012
# 'vlan' group ('config vlan ...')
@@ -19,6 +21,11 @@ def set_dhcp_relay_table(table, config_db, vlan_name, value):
1921
config_db.set_entry(table, vlan_name, value)
2022

2123

24+
def is_dhcp_relay_running():
25+
out, _ = clicommon.run_command("systemctl show dhcp_relay.service --property ActiveState --value", return_cmd=True)
26+
return out.strip() == "active"
27+
28+
2229
@vlan.command('add')
2330
@click.argument('vid', metavar='<vid>', required=True, type=int)
2431
@clicommon.pass_db
@@ -39,16 +46,25 @@ def add_vlan(db, vid):
3946
# set dhcpv4_relay table
4047
set_dhcp_relay_table('VLAN', db.cfgdb, vlan, {'vlanid': str(vid)})
4148

42-
# set dhcpv6_relay table
43-
set_dhcp_relay_table('DHCP_RELAY', db.cfgdb, vlan, None)
44-
# We need to restart dhcp_relay service after dhcpv6_relay config change
45-
dhcp_relay_util.handle_restart_dhcp_relay_service()
49+
50+
def is_dhcpv6_relay_config_exist(db, vlan_name):
51+
keys = db.cfgdb.get_keys(DHCP_RELAY_TABLE)
52+
if len(keys) == 0 or vlan_name not in keys:
53+
return False
54+
55+
table = db.cfgdb.get_entry(DHCP_RELAY_TABLE, vlan_name)
56+
dhcpv6_servers = table.get(DHCPV6_SERVERS, [])
57+
if len(dhcpv6_servers) > 0:
58+
return True
4659

4760

4861
@vlan.command('del')
4962
@click.argument('vid', metavar='<vid>', required=True, type=int)
63+
@click.option('--no_restart_dhcp_relay', is_flag=True, type=click.BOOL, required=False, default=False,
64+
help="If no_restart_dhcp_relay is True, do not restart dhcp_relay while del vlan and \
65+
require dhcpv6 relay of this is empty")
5066
@clicommon.pass_db
51-
def del_vlan(db, vid):
67+
def del_vlan(db, vid, no_restart_dhcp_relay):
5268
"""Delete VLAN"""
5369

5470
log.log_info("'vlan del {}' executing...".format(vid))
@@ -61,6 +77,9 @@ def del_vlan(db, vid):
6177
vlan = 'Vlan{}'.format(vid)
6278
if clicommon.check_if_vlanid_exist(db.cfgdb, vlan) == False:
6379
ctx.fail("{} does not exist".format(vlan))
80+
if no_restart_dhcp_relay:
81+
if is_dhcpv6_relay_config_exist(db, vlan):
82+
ctx.fail("Can't delete {} because related DHCPv6 Relay config is exist".format(vlan))
6483

6584
intf_table = db.cfgdb.get_table('VLAN_INTERFACE')
6685
for intf_key in intf_table:
@@ -76,10 +95,12 @@ def del_vlan(db, vid):
7695
# set dhcpv4_relay table
7796
set_dhcp_relay_table('VLAN', db.cfgdb, vlan, None)
7897

79-
# set dhcpv6_relay table
80-
set_dhcp_relay_table('DHCP_RELAY', db.cfgdb, vlan, None)
81-
# We need to restart dhcp_relay service after dhcpv6_relay config change
82-
dhcp_relay_util.handle_restart_dhcp_relay_service()
98+
if not no_restart_dhcp_relay and is_dhcpv6_relay_config_exist(db, vlan):
99+
# set dhcpv6_relay table
100+
set_dhcp_relay_table('DHCP_RELAY', db.cfgdb, vlan, None)
101+
# We need to restart dhcp_relay service after dhcpv6_relay config change
102+
if is_dhcp_relay_running():
103+
dhcp_relay_util.handle_restart_dhcp_relay_service()
83104

84105

85106
def restart_ndppd():

tests/conftest.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,13 @@ def setup_ip_route_commands():
224224
@pytest.fixture(scope='function')
225225
def mock_restart_dhcp_relay_service():
226226
print("We are mocking restart dhcp_relay")
227-
origin_func = config.vlan.dhcp_relay_util.handle_restart_dhcp_relay_service
228-
config.vlan.dhcp_relay_util.handle_restart_dhcp_relay_service = mock.MagicMock(return_value=0)
227+
origin_funcs = []
228+
origin_funcs.append(config.vlan.dhcp_relay_util.restart_dhcp_relay_service)
229+
origin_funcs.append(config.vlan.is_dhcp_relay_running)
230+
config.vlan.dhcp_relay_util.restart_dhcp_relay_service = mock.MagicMock(return_value=0)
231+
config.vlan.is_dhcp_relay_running = mock.MagicMock(return_value=True)
229232

230233
yield
231234

232-
config.vlan.dhcp_relay_util.handle_restart_dhcp_relay_service = origin_func
235+
config.vlan.dhcp_relay_util.restart_dhcp_relay_service = origin_funcs[0]
236+
config.vlan.is_dhcp_relay_running = origin_funcs[1]

tests/vlan_test.py

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -847,7 +847,7 @@ def test_config_vlan_add_member_of_portchannel(self):
847847
assert "Error: Ethernet32 is part of portchannel!" in result.output
848848

849849
@pytest.mark.parametrize("ip_version", ["ipv4", "ipv6"])
850-
def test_config_add_del_vlan_dhcp_relay(self, ip_version, mock_restart_dhcp_relay_service):
850+
def test_config_add_del_vlan_dhcp_relay_with_empty_entry(self, ip_version, mock_restart_dhcp_relay_service):
851851
runner = CliRunner()
852852
db = Db()
853853

@@ -861,11 +861,103 @@ def test_config_add_del_vlan_dhcp_relay(self, ip_version, mock_restart_dhcp_rela
861861
assert db.cfgdb.get_entry(IP_VERSION_PARAMS_MAP[ip_version]["table"], "Vlan1001") == exp_output
862862

863863
# del vlan 1001
864-
result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001"], obj=db)
864+
with mock.patch("utilities_common.dhcp_relay_util.handle_restart_dhcp_relay_service") as mock_handle_restart:
865+
result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001"], obj=db)
866+
print(result.exit_code)
867+
print(result.output)
868+
869+
assert result.exit_code == 0
870+
assert "Vlan1001" not in db.cfgdb.get_keys(IP_VERSION_PARAMS_MAP[ip_version]["table"])
871+
assert "Restart service dhcp_relay failed with error" not in result.output
872+
873+
@pytest.mark.parametrize("ip_version", ["ipv4", "ipv6"])
874+
def test_config_add_del_vlan_dhcp_relay_with_non_empty_entry(self, ip_version, mock_restart_dhcp_relay_service):
875+
runner = CliRunner()
876+
db = Db()
877+
878+
# add vlan 1001
879+
result = runner.invoke(config.config.commands["vlan"].commands["add"], ["1001"], obj=db)
880+
print(result.exit_code)
881+
print(result.output)
882+
assert result.exit_code == 0
883+
884+
exp_output = {"vlanid": "1001"} if ip_version == "ipv4" else {}
885+
assert db.cfgdb.get_entry(IP_VERSION_PARAMS_MAP[ip_version]["table"], "Vlan1001") == exp_output
886+
db.cfgdb.set_entry("DHCP_RELAY", "Vlan1001", {"dhcpv6_servers": ["fc02:2000::5"]})
887+
888+
# del vlan 1001
889+
with mock.patch("utilities_common.dhcp_relay_util.handle_restart_dhcp_relay_service") as mock_handle_restart:
890+
result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001"], obj=db)
891+
print(result.exit_code)
892+
print(result.output)
893+
894+
assert result.exit_code == 0
895+
assert "Vlan1001" not in db.cfgdb.get_keys(IP_VERSION_PARAMS_MAP[ip_version]["table"])
896+
mock_handle_restart.assert_called_once()
897+
assert "Restart service dhcp_relay failed with error" not in result.output
898+
899+
@pytest.mark.parametrize("ip_version", ["ipv4", "ipv6"])
900+
def test_config_add_del_vlan_with_dhcp_relay_not_running(self, ip_version):
901+
runner = CliRunner()
902+
db = Db()
903+
904+
# add vlan 1001
905+
result = runner.invoke(config.config.commands["vlan"].commands["add"], ["1001"], obj=db)
865906
print(result.exit_code)
866907
print(result.output)
908+
assert result.exit_code == 0
909+
910+
exp_output = {"vlanid": "1001"} if ip_version == "ipv4" else {}
911+
assert db.cfgdb.get_entry(IP_VERSION_PARAMS_MAP[ip_version]["table"], "Vlan1001") == exp_output
912+
913+
# del vlan 1001
914+
with mock.patch("utilities_common.dhcp_relay_util.handle_restart_dhcp_relay_service") \
915+
as mock_restart_dhcp_relay_service:
916+
result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001"], obj=db)
917+
print(result.exit_code)
918+
print(result.output)
867919

868-
assert "Vlan1001" not in db.cfgdb.get_keys(IP_VERSION_PARAMS_MAP[ip_version]["table"])
920+
assert result.exit_code == 0
921+
assert "Vlan1001" not in db.cfgdb.get_keys(IP_VERSION_PARAMS_MAP[ip_version]["table"])
922+
assert mock_restart_dhcp_relay_service.call_count == 0
923+
assert "Restarting DHCP relay service..." not in result.output
924+
assert "Restart service dhcp_relay failed with error" not in result.output
925+
926+
def test_config_add_del_vlan_with_not_restart_dhcp_relay_ipv6(self):
927+
runner = CliRunner()
928+
db = Db()
929+
930+
# add vlan 1001
931+
result = runner.invoke(config.config.commands["vlan"].commands["add"], ["1001"], obj=db)
932+
print(result.exit_code)
933+
print(result.output)
934+
assert result.exit_code == 0
935+
936+
db.cfgdb.set_entry("DHCP_RELAY", "Vlan1001", {"dhcpv6_servers": ["fc02:2000::5"]})
937+
938+
# del vlan 1001
939+
with mock.patch("utilities_common.dhcp_relay_util.handle_restart_dhcp_relay_service") \
940+
as mock_restart_dhcp_relay_service:
941+
result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001", "--no_restart_dhcp_relay"],
942+
obj=db)
943+
print(result.exit_code)
944+
print(result.output)
945+
946+
assert result.exit_code != 0
947+
assert mock_restart_dhcp_relay_service.call_count == 0
948+
assert "Can't delete Vlan1001 because related DHCPv6 Relay config is exist" in result.output
949+
950+
db.cfgdb.set_entry("DHCP_RELAY", "Vlan1001", None)
951+
# del vlan 1001
952+
with mock.patch("utilities_common.dhcp_relay_util.handle_restart_dhcp_relay_service") \
953+
as mock_restart_dhcp_relay_service:
954+
result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001", "--no_restart_dhcp_relay"],
955+
obj=db)
956+
print(result.exit_code)
957+
print(result.output)
958+
959+
assert result.exit_code == 0
960+
assert mock_restart_dhcp_relay_service.call_count == 0
869961

870962
@pytest.mark.parametrize("ip_version", ["ipv6"])
871963
def test_config_add_exist_vlan_dhcp_relay(self, ip_version):

0 commit comments

Comments
 (0)