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

[vlan] Refresh dhcpv6_relay config while adding/deleting a vlan #2660

Merged
merged 3 commits into from
Feb 10, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
39 changes: 25 additions & 14 deletions config/vlan.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import click
import utilities_common.cli as clicommon
import utilities_common.dhcp_relay_util as dhcp_relay_util

from jsonpatch import JsonPatchConflict
from time import sleep
Expand All @@ -16,6 +17,11 @@ def vlan():
"""VLAN-related configuration tasks"""
pass


def set_dhcp_relay_table(table, config_db, vlan_name, value):
config_db.set_entry(table, vlan_name, value)


@vlan.command('add')
@click.argument('vid', metavar='<vid>', required=True, type=int)
@clicommon.pass_db
Expand All @@ -24,22 +30,23 @@ def add_vlan(db, vid):

ctx = click.get_current_context()
vlan = 'Vlan{}'.format(vid)

config_db = ValidatedConfigDBConnector(db.cfgdb)
if ADHOC_VALIDATION:
if not clicommon.is_vlanid_in_range(vid):
ctx.fail("Invalid VLAN ID {} (1-4094)".format(vid))

if vid == 1:
ctx.fail("{} is default VLAN".format(vlan)) # TODO: MISSING CONSTRAINT IN YANG MODEL

if clicommon.check_if_vlanid_exist(db.cfgdb, vlan): # TODO: MISSING CONSTRAINT IN YANG MODEL
ctx.fail("{} already exists".format(vlan))

try:
config_db.set_entry('VLAN', vlan, {'vlanid': str(vid)})
except ValueError:
ctx.fail("Invalid VLAN ID {} (1-4094)".format(vid))
# set dhcpv4_relay table
set_dhcp_relay_table('VLAN', config_db, vlan, {'vlanid': str(vid)})

# set dhcpv6_relay table
set_dhcp_relay_table('DHCP_RELAY', config_db, vlan, {'vlanid': str(vid)})


@vlan.command('del')
@click.argument('vid', metavar='<vid>', required=True, type=int)
Expand Down Expand Up @@ -67,19 +74,23 @@ def del_vlan(db, vid):
ctx.fail("{} can not be removed. First remove IP addresses assigned to this VLAN".format(vlan))

keys = [ (k, v) for k, v in db.cfgdb.get_table('VLAN_MEMBER') if k == 'Vlan{}'.format(vid) ]

if keys: # TODO: MISSING CONSTRAINT IN YANG MODEL
ctx.fail("VLAN ID {} can not be removed. First remove all members assigned to this VLAN.".format(vid))

vxlan_table = db.cfgdb.get_table('VXLAN_TUNNEL_MAP')
for vxmap_key, vxmap_data in vxlan_table.items():
if vxmap_data['vlan'] == 'Vlan{}'.format(vid):
ctx.fail("vlan: {} can not be removed. First remove vxlan mapping '{}' assigned to VLAN".format(vid, '|'.join(vxmap_key)) )

try:
config_db.set_entry('VLAN', 'Vlan{}'.format(vid), None)
except JsonPatchConflict:
ctx.fail("{} does not exist".format(vlan))

# set dhcpv4_relay table
set_dhcp_relay_table('VLAN', config_db, vlan, None)

# set dhcpv6_relay table
set_dhcp_relay_table('DHCP_RELAY', config_db, vlan, None)
# We need to restart dhcp_relay service after dhcpv6_relay config change
dhcp_relay_util.handle_restart_dhcp_relay_service()


def restart_ndppd():
verify_swss_running_cmd = "docker container inspect -f '{{.State.Status}}' swss"
Expand Down
57 changes: 52 additions & 5 deletions tests/vlan_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import traceback
import pytest
from unittest import mock

from click.testing import CliRunner
Expand All @@ -10,6 +11,18 @@
from importlib import reload
import utilities_common.bgp_util as bgp_util

IP_VERSION_PARAMS_MAP = {
"ipv4": {
"table": "VLAN"
},
"ipv6": {
"table": "DHCP_RELAY"
}
}
DHCP_RELAY_TABLE_ENTRY = {
"vlanid": "1001"
}

show_vlan_brief_output="""\
+-----------+-----------------+-----------------+----------------+-------------+
| VLAN ID | IP Address | Ports | Port Tagging | Proxy ARP |
Expand Down Expand Up @@ -143,6 +156,20 @@
| 4000 | | PortChannel1001 | tagged | disabled |
+-----------+-----------------+-----------------+----------------+-------------+
"""


mock_funcs = [None]
@pytest.fixture(scope='function')
def mock_func():
print("We are mocking")
mock_funcs[0] = 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)

yield

config.vlan.dhcp_relay_util.handle_restart_dhcp_relay_service = mock_funcs[0]


class TestVlan(object):
_old_run_bgp_command = None
@classmethod
Expand Down Expand Up @@ -343,7 +370,7 @@ def test_config_vlan_with_vxlanmap_del_vlan(self):
assert result.exit_code != 0
assert "Error: vlan: 1027 can not be removed. First remove vxlan mapping" in result.output

def test_config_vlan_del_vlan(self):
def test_config_vlan_del_vlan(self, mock_func):
runner = CliRunner()
db = Db()
obj = {'config_db':db.cfgdb}
Expand Down Expand Up @@ -401,7 +428,7 @@ def test_config_vlan_del_nonexist_vlan_member(self):
assert result.exit_code != 0
assert "Error: Ethernet0 is not a member of Vlan1000" in result.output

def test_config_add_del_vlan_and_vlan_member(self):
def test_config_add_del_vlan_and_vlan_member(self, mock_func):
runner = CliRunner()
db = Db()

Expand Down Expand Up @@ -444,7 +471,7 @@ def test_config_add_del_vlan_and_vlan_member(self):
assert result.exit_code == 0
assert result.output == show_vlan_brief_output

def test_config_add_del_vlan_and_vlan_member_in_alias_mode(self):
def test_config_add_del_vlan_and_vlan_member_in_alias_mode(self, mock_func):
runner = CliRunner()
db = Db()

Expand Down Expand Up @@ -521,7 +548,7 @@ def test_config_vlan_proxy_arp_with_nonexist_vlan_intf(self):
assert result.exit_code != 0
assert "Interface Vlan1001 does not exist" in result.output

def test_config_vlan_proxy_arp_enable(self):
def test_config_vlan_proxy_arp_enable(self, mock_func):
runner = CliRunner()
db = Db()

Expand All @@ -533,7 +560,7 @@ def test_config_vlan_proxy_arp_enable(self):
assert result.exit_code == 0
assert db.cfgdb.get_entry("VLAN_INTERFACE", "Vlan1000") == {"proxy_arp": "enabled"}

def test_config_vlan_proxy_arp_disable(self):
def test_config_vlan_proxy_arp_disable(self, mock_func):
runner = CliRunner()
db = Db()

Expand Down Expand Up @@ -584,6 +611,26 @@ def test_config_vlan_add_member_of_portchannel(self):
assert result.exit_code != 0
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):
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

assert db.cfgdb.get_entry(IP_VERSION_PARAMS_MAP[ip_version]["table"], "Vlan1001") == DHCP_RELAY_TABLE_ENTRY

# del vlan 1001
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"])

@classmethod
def teardown_class(cls):
os.environ['UTILITIES_UNIT_TESTING'] = "0"
Expand Down
20 changes: 20 additions & 0 deletions utilities_common/dhcp_relay_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import click
import utilities_common.cli as clicommon


def restart_dhcp_relay_service():
"""
Restart dhcp_relay service
"""
click.echo("Restarting DHCP relay service...")
clicommon.run_command("systemctl stop dhcp_relay", display_cmd=False)
clicommon.run_command("systemctl reset-failed dhcp_relay", display_cmd=False)
clicommon.run_command("systemctl start dhcp_relay", display_cmd=False)


def handle_restart_dhcp_relay_service():
try:
restart_dhcp_relay_service()
except SystemExit as e:
ctx = click.get_current_context()
ctx.fail("Restart service dhcp_relay failed with error {}".format(e))