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

Add SAG implementation #2881

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
48 changes: 47 additions & 1 deletion config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7008,6 +7008,53 @@ def disable_link_local(ctx):


#
# 'static-anycast-gateway' group ('config static-anycast-gateway ...')
#
@config.group(cls=clicommon.AbbreviationGroup, name='static-anycast-gateway')
def static_anycast_gateway():
"""sag-related configuration tasks"""
pass

#
# 'static-anycast-gateway mac_address' group
#
@static_anycast_gateway.group(cls=clicommon.AbbreviationGroup, name='mac_address')
def mac_address():
"""Add/Delete static-anycast-gateway mac address"""
pass

@mac_address.command('add')
@click.argument('mac_address', metavar='<mac_address>', required=True, type=str)
@clicommon.pass_db
def add_mac(db, mac_address):
"""Add static-anycast-gateway mac address command"""
log.log_info(f"'static-anycast-gateway mac_address add {mac_address}' executing...")

try:
gateway_mac = netaddr.EUI(mac_address)
except Exception as e:
click.get_current_context().fail(f'static-anycast-gateway MAC address {mac_address} format is not valid.')

if (gateway_mac.words[0] & 0b01):
click.get_current_context().fail(f'static-anycast-gateway MAC address {mac_address} is multicast, only allow unicast.')

if not db.cfgdb.get_entry('SAG', 'GLOBAL'):
db.cfgdb.set_entry('SAG', 'GLOBAL', {'gateway_mac': mac_address})
else:
click.get_current_context().fail(f'static-anycast-gateway MAC address {mac_address} is alreday existed. Remove it first')

@mac_address.command('del')
@clicommon.pass_db
def del_mac(db):
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Junchao-Mellanox
The argument is removed.

"""Del static-anycast-gateway mac address command"""
log.log_info(f"'static-anycast-gateway mac_address del {mac_address}' executing...")

sag_entry = db.cfgdb.get_entry('SAG', 'GLOBAL')
if sag_entry:
db.cfgdb.mod_entry('SAG', 'GLOBAL', None)
else:
click.get_current_context().fail(f'static-anycast-gateway MAC address {mac_address} not found.')

# 'rate' group ('config rate ...')
#

Expand Down Expand Up @@ -7037,7 +7084,6 @@ def smoothing_interval(interval, rates_type):
counters_db.set('COUNTERS_DB', 'RATES:TRAP', 'TRAP_SMOOTH_INTERVAL', interval)
counters_db.set('COUNTERS_DB', 'RATES:TRAP', 'TRAP_ALPHA', alpha)


# Load plugins and register them
helper = util_base.UtilHelper()
helper.load_and_register_plugins(plugins, config)
Expand Down
46 changes: 45 additions & 1 deletion config/vlan.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ def del_vlan(db, vid, no_restart_dhcp_relay):
clicommon.run_command(docker_exec_cmd.format("rm -f /etc/supervisor/conf.d/ndppd.conf"), ignore_error=True, return_cmd=True)
clicommon.run_command(docker_exec_cmd.format("supervisorctl update"), return_cmd=True)


def restart_ndppd():
verify_swss_running_cmd = ['docker', 'container', 'inspect', '-f', '{{.State.Status}}', 'swss']
docker_exec_cmd = ['docker', 'exec', '-i', 'swss']
Expand Down Expand Up @@ -282,3 +281,48 @@ def del_vlan_member(db, vid, port):
except JsonPatchConflict:
ctx.fail("{} invalid or does not exist, or {} is not a member of {}".format(vlan, port, vlan))

#
# 'static-anycast-gateway' group ('config vlan static-anycast-gateway ...')
#
@vlan.group(cls=clicommon.AbbreviationGroup, name='static-anycast-gateway')
def static_anycast_gateway():
pass

@static_anycast_gateway.command('enable')
@click.argument('vid', metavar='<vid>', required=True, type=int)
@clicommon.pass_db
def enable_vlan_sag(db, vid):
"""Enable static-anycast-gatweay on VLAN interface"""
ctx = click.get_current_context()

log.log_info(f"'vlan static-anycast-gateway enable {vid}' executing...")

vlan = f'Vlan{vid}'
if not clicommon.is_valid_vlan_interface(db.cfgdb, vlan):
ctx.fail(f"Interface {vlan} does not exist")

if db.cfgdb.get_entry('VLAN_INTERFACE', vlan) == 'true':
ctx.fail(f"static-anycast-gateway is already enabled")

db.cfgdb.mod_entry('VLAN_INTERFACE', vlan, {"static_anycast_gateway": "true"})
click.echo('static-anycast-gateway setting saved to ConfigDB')


@static_anycast_gateway.command('disable')
@click.argument('vid', metavar='<vid>', required=True, type=int)
@clicommon.pass_db
def disable_vlan_sag(db, vid):
"""Disable static-anycast-gatweay on VLAN interface"""
ctx = click.get_current_context()

log.log_info(f"'vlan static-anycast-gateway disable {vid}' executing...")

vlan = f'Vlan{vid}'
if not clicommon.is_valid_vlan_interface(db.cfgdb, vlan):
ctx.fail(f"Interface {vlan} does not exist")

if db.cfgdb.get_entry('VLAN_INTERFACE', vlan) == 'false':
ctx.fail(f"static-anycast-gateway is already disabled")

db.cfgdb.mod_entry('VLAN_INTERFACE', vlan, {"static_anycast_gateway": "false"})
click.echo('static-anycast-gateway setting saved to ConfigDB')
24 changes: 24 additions & 0 deletions show/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2057,6 +2057,30 @@ def ztp(status, verbose):
cmd += ["--verbose"]
run_command(cmd, display_cmd=verbose)

#
# 'static anycast gateway' command ("show static-anycast-gateway")
#
@cli.command('static-anycast-gateway')
@clicommon.pass_db
def sag(db):
"""Show static anycast gateway information"""
header = ['MacAddress', 'Interfaces']
body = []

sag_entry = db.cfgdb.get_entry('SAG', 'GLOBAL')
if sag_entry:
sag_mac = sag_entry.get('gateway_mac')

intf_dict = db.cfgdb.get_table('VLAN_INTERFACE')
for key, value in intf_dict.items():
if value.get('static_anycast_gateway') == 'true':
if not body:
body.append([sag_mac, key])
else:
body.append(['', key])

click.echo("Static Anycast Gateway Information")
click.echo(tabulate(body, header, tablefmt='simple'))

#
# 'bfd' group ("show bfd ...")
Expand Down
20 changes: 15 additions & 5 deletions show/vlan.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@ def get_proxy_arp(ctx, vlan):

return proxy_arp

def get_static_anycast_gateway(ctx, vlan):
cfg, _ = ctx
_, vlan_ip_data, _ = cfg

if vlan in vlan_ip_data:
if vlan_ip_data[vlan].get("static_anycast_gateway") == "true":
return "enabled"

return "disabled"

class VlanBrief:
""" This class is used as a namespace to
Expand All @@ -103,7 +112,8 @@ class VlanBrief:
("IP Address", get_vlan_ip_address),
("Ports", get_vlan_ports),
("Port Tagging", get_vlan_ports_tagging),
("Proxy ARP", get_proxy_arp)
("Proxy ARP", get_proxy_arp),
("Static Anycast Gateway", get_static_anycast_gateway)
]

@classmethod
Expand Down Expand Up @@ -147,19 +157,19 @@ def config(db):
member_data = db.cfgdb.get_table('VLAN_MEMBER')
interface_naming_mode = clicommon.get_interface_naming_mode()
iface_alias_converter = clicommon.InterfaceAliasConverter(db)

def get_iface_name_for_display(member):
name_for_display = member
if interface_naming_mode == "alias" and member:
name_for_display = iface_alias_converter.name_to_alias(member)
return name_for_display

def get_tagging_mode(vlan, member):
if not member:
return ''
tagging_mode = db.cfgdb.get_entry('VLAN_MEMBER', (vlan, member)).get('tagging_mode')
return '?' if tagging_mode is None else tagging_mode

def tablelize(keys, data):
table = []

Expand All @@ -168,7 +178,7 @@ def tablelize(keys, data):
# vlan with no members
if not members:
members = [(k, '')]

for vlan, member in natsorted(members):
r = [vlan, data[vlan]['vlanid'], get_iface_name_for_display(member), get_tagging_mode(vlan, member)]
table.append(r)
Expand Down
6 changes: 5 additions & 1 deletion tests/mock_tables/config_db.json
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,8 @@
"vlanid": "4000"
},
"VLAN_INTERFACE|Vlan1000": {
"NULL": "NULL"
"NULL": "NULL",
"static_anycast_gateway": "true"
},
"VLAN_INTERFACE|Vlan2000": {
"proxy_arp": "enabled"
Expand Down Expand Up @@ -2664,6 +2665,9 @@
"QUEUE|Ethernet96|6": {
"scheduler": "[SCHEDULER|scheduler.0]"
},
"SAG|GLOBAL": {
"gateway_mac": "00:11:22:33:44:55"
},
"MIRROR_SESSION|test_session_db1": {
"dst_port": "Ethernet44",
"src_port": "Ethernet40,Ethernet48",
Expand Down
89 changes: 89 additions & 0 deletions tests/sag_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import pytest
import os
import logging
from click.testing import CliRunner

import config.main as config
import show.main as show
from utilities_common.db import Db
from importlib import reload

show_sag_output="""\
Static Anycast Gateway Information
MacAddress Interfaces
----------------- ------------
00:11:22:33:44:55 Vlan1000
"""

class TestSag(object):
@classmethod
def setup_class(cls):
os.environ['UTILITIES_UNIT_TESTING'] = "1"
print("SETUP")

def test_config_add_sag_with_existed_mac(self):
runner = CliRunner()
db = Db()

result = runner.invoke(config.config.commands["static-anycast-gateway"].commands["mac_address"].commands["add"],
["00:22:33:44:55:66"], obj=db)
assert result.exit_code != 0, f"sag invalid mac with code {type(result.exit_code)}:{result.exit_code} Output:{result.output}"
assert {"gateway_mac": "00:11:22:33:44:55"} == db.cfgdb.get_entry("SAG", "GLOBAL")

def test_config_del_add_invalid_sag_mac_address(self):
runner = CliRunner()
db = Db()

result = runner.invoke(config.config.commands["static-anycast-gateway"].commands["mac_address"].commands["del"],
obj=db)
assert result.exit_code == 0, f"sag invalid mac with code {type(result.exit_code)}:{result.exit_code} Output:{result.output}"
assert not db.cfgdb.get_entry("SAG", "GLOBAL")

result = runner.invoke(config.config.commands["static-anycast-gateway"].commands["mac_address"].commands["add"],
["01:22:33:44:55:66"], obj=db)
assert result.exit_code != 0, f"sag invalid mac with code {type(result.exit_code)}:{result.exit_code} Output:{result.output}"
assert {"gateway_mac": "01:11:22:33:44:55"} != db.cfgdb.get_entry("SAG", "GLOBAL")

def test_config_del_add_sag_mac_address(self):
runner = CliRunner()
db = Db()

result = runner.invoke(config.config.commands["static-anycast-gateway"].commands["mac_address"].commands["del"],
obj=db)
assert result.exit_code == 0, f"sag invalid mac with code {type(result.exit_code)}:{result.exit_code} Output:{result.output}"
assert not db.cfgdb.get_entry("SAG", "GLOBAL")

result = runner.invoke(config.config.commands["static-anycast-gateway"].commands["mac_address"].commands["add"],
["00:22:33:44:55:66"], obj=db)
assert result.exit_code == 0, f"sag invalid mac with code {type(result.exit_code)}:{result.exit_code} Output:{result.output}"
assert {"gateway_mac": "00:22:33:44:55:66"} == db.cfgdb.get_entry("SAG", "GLOBAL")

def test_config_enable_sag_on_vlan_interface(self):
runner = CliRunner()
db = Db()

result = runner.invoke(config.config.commands["vlan"].commands["static-anycast-gateway"].commands["enable"],
["2000"], obj=db)
assert result.exit_code == 0, f"sag invalid vlan with code {type(result.exit_code)}:{result.exit_code} Output:{result.output}"
assert {"static_anycast_gateway": "true"}.items() <= db.cfgdb.get_entry("VLAN_INTERFACE", "Vlan2000").items()

def test_config_disable_sag_on_vlan_interface(self):
runner = CliRunner()
db = Db()

result = runner.invoke(config.config.commands["vlan"].commands["static-anycast-gateway"].commands["disable"],
["1000"], obj=db)
assert result.exit_code == 0, f"sag invalid vlan with code {type(result.exit_code)}:{result.exit_code} Output:{result.output}"
assert {"static_anycast_gateway": "false"}.items() <= db.cfgdb.get_entry("VLAN_INTERFACE", "Vlan1000").items()

def test_show_sag_mac(self):
runner = CliRunner()
result = runner.invoke(show.cli.commands["static-anycast-gateway"], [])
assert result.exit_code == 0, f"invalid show sag with code {type(result.exit_code)}:{result.exit_code} Output:{result.output}"
assert result.output == show_sag_output

@classmethod
def teardown_class(cls):
os.environ['UTILITIES_UNIT_TESTING'] = "0"
print("TEARDOWN")

Loading
Loading