diff --git a/config/vlan.py b/config/vlan.py index 543708b5d6..343331e395 100644 --- a/config/vlan.py +++ b/config/vlan.py @@ -6,6 +6,10 @@ from time import sleep from .utils import log +DHCP_RELAY_TABLE = "DHCP_RELAY" +DHCPV6_SERVERS = "dhcpv6_servers" + + # # 'vlan' group ('config vlan ...') # @@ -19,6 +23,11 @@ def set_dhcp_relay_table(table, config_db, vlan_name, value): config_db.set_entry(table, vlan_name, value) +def is_dhcp_relay_running(): + out, _ = clicommon.run_command("systemctl show dhcp_relay.service --property ActiveState --value", return_cmd=True) + return out.strip() == "active" + + @vlan.command('add') @click.argument('vid', metavar='', required=True, type=int) @clicommon.pass_db @@ -39,16 +48,25 @@ def add_vlan(db, vid): # set dhcpv4_relay table set_dhcp_relay_table('VLAN', db.cfgdb, vlan, {'vlanid': str(vid)}) - # set dhcpv6_relay table - set_dhcp_relay_table('DHCP_RELAY', db.cfgdb, vlan, None) - # We need to restart dhcp_relay service after dhcpv6_relay config change - dhcp_relay_util.handle_restart_dhcp_relay_service() + +def is_dhcpv6_relay_config_exist(db, vlan_name): + keys = db.cfgdb.get_keys(DHCP_RELAY_TABLE) + if len(keys) == 0 or vlan_name not in keys: + return False + + table = db.cfgdb.get_entry(DHCP_RELAY_TABLE, vlan_name) + dhcpv6_servers = table.get(DHCPV6_SERVERS, []) + if len(dhcpv6_servers) > 0: + return True @vlan.command('del') @click.argument('vid', metavar='', required=True, type=int) +@click.option('--no_restart_dhcp_relay', is_flag=True, type=click.BOOL, required=False, default=False, + help="If no_restart_dhcp_relay is True, do not restart dhcp_relay while del vlan and \ + require dhcpv6 relay of this is empty") @clicommon.pass_db -def del_vlan(db, vid): +def del_vlan(db, vid, no_restart_dhcp_relay): """Delete VLAN""" log.log_info("'vlan del {}' executing...".format(vid)) @@ -61,6 +79,9 @@ def del_vlan(db, vid): vlan = 'Vlan{}'.format(vid) if clicommon.check_if_vlanid_exist(db.cfgdb, vlan) == False: ctx.fail("{} does not exist".format(vlan)) + if no_restart_dhcp_relay: + if is_dhcpv6_relay_config_exist(db, vlan): + ctx.fail("Can't delete {} because related DHCPv6 Relay config is exist".format(vlan)) intf_table = db.cfgdb.get_table('VLAN_INTERFACE') for intf_key in intf_table: @@ -76,10 +97,12 @@ def del_vlan(db, vid): # set dhcpv4_relay table set_dhcp_relay_table('VLAN', db.cfgdb, vlan, None) - # set dhcpv6_relay table - set_dhcp_relay_table('DHCP_RELAY', db.cfgdb, vlan, None) - # We need to restart dhcp_relay service after dhcpv6_relay config change - dhcp_relay_util.handle_restart_dhcp_relay_service() + if not no_restart_dhcp_relay and is_dhcpv6_relay_config_exist(db, vlan): + # set dhcpv6_relay table + set_dhcp_relay_table('DHCP_RELAY', db.cfgdb, vlan, None) + # We need to restart dhcp_relay service after dhcpv6_relay config change + if is_dhcp_relay_running(): + dhcp_relay_util.handle_restart_dhcp_relay_service() def restart_ndppd(): diff --git a/tests/conftest.py b/tests/conftest.py index ad862f1059..d65f8fcee5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -224,9 +224,13 @@ def setup_ip_route_commands(): @pytest.fixture(scope='function') def mock_restart_dhcp_relay_service(): print("We are mocking restart dhcp_relay") - origin_func = config.vlan.dhcp_relay_util.handle_restart_dhcp_relay_service - config.vlan.dhcp_relay_util.handle_restart_dhcp_relay_service = mock.MagicMock(return_value=0) + origin_funcs = [] + origin_funcs.append(config.vlan.dhcp_relay_util.restart_dhcp_relay_service) + origin_funcs.append(config.vlan.is_dhcp_relay_running) + config.vlan.dhcp_relay_util.restart_dhcp_relay_service = mock.MagicMock(return_value=0) + config.vlan.is_dhcp_relay_running = mock.MagicMock(return_value=True) yield - config.vlan.dhcp_relay_util.handle_restart_dhcp_relay_service = origin_func + config.vlan.dhcp_relay_util.restart_dhcp_relay_service = origin_funcs[0] + config.vlan.is_dhcp_relay_running = origin_funcs[1] diff --git a/tests/vlan_test.py b/tests/vlan_test.py index b83f0a8ab5..df3f3edacf 100644 --- a/tests/vlan_test.py +++ b/tests/vlan_test.py @@ -847,7 +847,7 @@ def test_config_vlan_add_member_of_portchannel(self): assert "Error: Ethernet32 is part of portchannel!" in result.output @pytest.mark.parametrize("ip_version", ["ipv4", "ipv6"]) - def test_config_add_del_vlan_dhcp_relay(self, ip_version, mock_restart_dhcp_relay_service): + def test_config_add_del_vlan_dhcp_relay_with_empty_entry(self, ip_version, mock_restart_dhcp_relay_service): runner = CliRunner() db = Db() @@ -861,11 +861,103 @@ def test_config_add_del_vlan_dhcp_relay(self, ip_version, mock_restart_dhcp_rela assert db.cfgdb.get_entry(IP_VERSION_PARAMS_MAP[ip_version]["table"], "Vlan1001") == exp_output # del vlan 1001 - result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001"], obj=db) + with mock.patch("utilities_common.dhcp_relay_util.handle_restart_dhcp_relay_service") as mock_handle_restart: + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001"], obj=db) + print(result.exit_code) + print(result.output) + + assert result.exit_code == 0 + assert "Vlan1001" not in db.cfgdb.get_keys(IP_VERSION_PARAMS_MAP[ip_version]["table"]) + assert "Restart service dhcp_relay failed with error" not in result.output + + @pytest.mark.parametrize("ip_version", ["ipv4", "ipv6"]) + def test_config_add_del_vlan_dhcp_relay_with_non_empty_entry(self, ip_version, mock_restart_dhcp_relay_service): + runner = CliRunner() + db = Db() + + # add vlan 1001 + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["1001"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + exp_output = {"vlanid": "1001"} if ip_version == "ipv4" else {} + assert db.cfgdb.get_entry(IP_VERSION_PARAMS_MAP[ip_version]["table"], "Vlan1001") == exp_output + db.cfgdb.set_entry("DHCP_RELAY", "Vlan1001", {"dhcpv6_servers": ["fc02:2000::5"]}) + + # del vlan 1001 + with mock.patch("utilities_common.dhcp_relay_util.handle_restart_dhcp_relay_service") as mock_handle_restart: + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001"], obj=db) + print(result.exit_code) + print(result.output) + + assert result.exit_code == 0 + assert "Vlan1001" not in db.cfgdb.get_keys(IP_VERSION_PARAMS_MAP[ip_version]["table"]) + mock_handle_restart.assert_called_once() + assert "Restart service dhcp_relay failed with error" not in result.output + + @pytest.mark.parametrize("ip_version", ["ipv4", "ipv6"]) + def test_config_add_del_vlan_with_dhcp_relay_not_running(self, ip_version): + runner = CliRunner() + db = Db() + + # add vlan 1001 + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["1001"], obj=db) print(result.exit_code) print(result.output) + assert result.exit_code == 0 + + exp_output = {"vlanid": "1001"} if ip_version == "ipv4" else {} + assert db.cfgdb.get_entry(IP_VERSION_PARAMS_MAP[ip_version]["table"], "Vlan1001") == exp_output + + # del vlan 1001 + with mock.patch("utilities_common.dhcp_relay_util.handle_restart_dhcp_relay_service") \ + as mock_restart_dhcp_relay_service: + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001"], obj=db) + print(result.exit_code) + print(result.output) - assert "Vlan1001" not in db.cfgdb.get_keys(IP_VERSION_PARAMS_MAP[ip_version]["table"]) + assert result.exit_code == 0 + assert "Vlan1001" not in db.cfgdb.get_keys(IP_VERSION_PARAMS_MAP[ip_version]["table"]) + assert mock_restart_dhcp_relay_service.call_count == 0 + assert "Restarting DHCP relay service..." not in result.output + assert "Restart service dhcp_relay failed with error" not in result.output + + def test_config_add_del_vlan_with_not_restart_dhcp_relay_ipv6(self): + runner = CliRunner() + db = Db() + + # add vlan 1001 + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["1001"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + db.cfgdb.set_entry("DHCP_RELAY", "Vlan1001", {"dhcpv6_servers": ["fc02:2000::5"]}) + + # del vlan 1001 + with mock.patch("utilities_common.dhcp_relay_util.handle_restart_dhcp_relay_service") \ + as mock_restart_dhcp_relay_service: + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001", "--no_restart_dhcp_relay"], + obj=db) + print(result.exit_code) + print(result.output) + + assert result.exit_code != 0 + assert mock_restart_dhcp_relay_service.call_count == 0 + assert "Can't delete Vlan1001 because related DHCPv6 Relay config is exist" in result.output + + db.cfgdb.set_entry("DHCP_RELAY", "Vlan1001", None) + # del vlan 1001 + with mock.patch("utilities_common.dhcp_relay_util.handle_restart_dhcp_relay_service") \ + as mock_restart_dhcp_relay_service: + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001", "--no_restart_dhcp_relay"], + obj=db) + print(result.exit_code) + print(result.output) + + assert result.exit_code == 0 + assert mock_restart_dhcp_relay_service.call_count == 0 @pytest.mark.parametrize("ip_version", ["ipv6"]) def test_config_add_exist_vlan_dhcp_relay(self, ip_version):