Skip to content
28 changes: 28 additions & 0 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4685,6 +4685,34 @@ def unbind(ctx, interface_name):
remove_router_interface_ip_address(config_db, interface_name, ipaddress)
config_db.set_entry(table_name, interface_name, None)

#
# 'config interface loopback-action <interface-name> <action>'
#

@interface.command()
@click.argument('interface_name', metavar='<interface_name>', required=True)
@click.argument('action', metavar='<action>', required=True)
@click.pass_context
def loopback_action(ctx, interface_name, action):
"""Set IP interface loopback action"""
config_db = ctx.obj['config_db']

if clicommon.get_interface_naming_mode() == "alias":
interface_name = interface_alias_to_name(config_db, interface_name)
if interface_name is None:
ctx.fail('Interface {} is invalid'.format(interface_name))

if not clicommon.is_interface_in_config_db(config_db, interface_name):
ctx.fail('Interface {} is not an IP interface'.format(interface_name))

allowed_actions = ['drop', 'forward']
if action not in allowed_actions:
ctx.fail('Invalid action')

table_name = get_interface_table_name(interface_name)
if not table_name:
ctx.fail('Interface {} is invalid'.format(interface_name))
config_db.set_entry(table_name, interface_name, {"loopback_action": action})

#
# 'ipv6' subgroup ('config interface ipv6 ...')
Expand Down
48 changes: 41 additions & 7 deletions show/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,15 +805,49 @@ def ip():
# Addresses from all scopes are included. Interfaces with no addresses are
# excluded.
#
@ip.command()

@ip.group(invoke_without_command=True)
@multi_asic_util.multi_asic_click_options
def interfaces(namespace, display):
cmd = "sudo ipintutil -a ipv4"
if namespace is not None:
cmd += " -n {}".format(namespace)
@click.pass_context
def interfaces(ctx, namespace, display):
if ctx.invoked_subcommand is None:
cmd = "sudo ipintutil -a ipv4"
if namespace is not None:
cmd += " -n {}".format(namespace)

cmd += " -d {}".format(display)
clicommon.run_command(cmd)
cmd += " -d {}".format(display)
clicommon.run_command(cmd)

#
# 'show ip interfaces loopback-action' command
#

@interfaces.command()
def loopback_action():
"""show ip interfaces loopback-action"""
config_db = ConfigDBConnector()
config_db.connect()
header = ['Interface', 'Action']
body = []

if_tbl = config_db.get_table('INTERFACE')
vlan_if_tbl = config_db.get_table('VLAN_INTERFACE')
po_if_tbl = config_db.get_table('PORTCHANNEL_INTERFACE')
sub_if_tbl = config_db.get_table('VLAN_SUB_INTERFACE')

all_tables = {}
for tbl in [if_tbl, vlan_if_tbl, po_if_tbl, sub_if_tbl]:
all_tables.update(tbl)

if all_tables:
ifs_action = []
ifs = list(all_tables.keys())
for iface in ifs:
if 'loopback_action' in all_tables[iface]:
action = all_tables[iface]['loopback_action']
ifs_action.append([iface, action])
body = natsorted(ifs_action)
click.echo(tabulate(body, header))

#
# 'route' subcommand ("show ip route")
Expand Down
121 changes: 121 additions & 0 deletions tests/loopback_action_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import os
from click.testing import CliRunner
import config.main as config
import show.main as show
from utilities_common.db import Db

show_ip_interfaces_loopback_action_output="""\
Interface Action
--------------- --------
Eth32.10 drop
Ethernet0 forward
PortChannel0001 drop
Vlan3000 forward
"""

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

def test_config_loopback_action_on_physical_interface(self):
runner = CliRunner()
db = Db()
obj = {'config_db':db.cfgdb}
action = 'drop'
iface = 'Ethernet0'

result = runner.invoke(config.config.commands['interface'].commands['loopback-action'], [iface, action], obj=obj)

table = db.cfgdb.get_table('INTERFACE')
assert(table[iface]['loopback_action'] == action)

print(result.exit_code, result.output)
assert result.exit_code == 0

def test_config_loopback_action_on_port_channel_interface(self):
runner = CliRunner()
db = Db()
obj = {'config_db':db.cfgdb}
action = 'forward'
iface = 'PortChannel0002'

result = runner.invoke(config.config.commands['interface'].commands['loopback-action'], [iface, action], obj=obj)

table = db.cfgdb.get_table('PORTCHANNEL_INTERFACE')
assert(table[iface]['loopback_action'] == action)

print(result.exit_code, result.output)
assert result.exit_code == 0

def test_config_loopback_action_on_vlan_interface(self):
runner = CliRunner()
db = Db()
obj = {'config_db':db.cfgdb}
action = 'drop'
iface = 'Vlan1000'

result = runner.invoke(config.config.commands['interface'].commands['loopback-action'], [iface, action], obj=obj)

table = db.cfgdb.get_table('VLAN_INTERFACE')
assert(table[iface]['loopback_action'] == action)

print(result.exit_code, result.output)
assert result.exit_code == 0

def test_config_loopback_action_on_subinterface(self):
runner = CliRunner()
db = Db()
obj = {'config_db':db.cfgdb}
action = 'forward'
iface = 'Ethernet0.10'

result = runner.invoke(config.config.commands['interface'].commands['loopback-action'], [iface, action], obj=obj)

table = db.cfgdb.get_table('VLAN_SUB_INTERFACE')
assert(table[iface]['loopback_action'] == action)

print(result.exit_code, result.output)
assert result.exit_code == 0

def test_show_ip_interfaces_loopback_action(self):
runner = CliRunner()
result = runner.invoke(show.cli.commands["ip"].commands["interfaces"].commands["loopback-action"], [])

print(result.exit_code, result.output)
assert result.exit_code == 0
assert result.output == show_ip_interfaces_loopback_action_output

def test_config_loopback_action_on_non_ip_interface(self):
runner = CliRunner()
db = Db()
obj = {'config_db':db.cfgdb}
action = 'forward'
iface = 'Ethernet0.11'
ERROR_MSG = "Error: Interface {} is not an IP interface".format(iface)

result = runner.invoke(config.config.commands['interface'].commands['loopback-action'], [iface, action], obj=obj)

print(result.exit_code, result.output)
assert result.exit_code != 0
assert ERROR_MSG in result.output

def test_config_loopback_action_invalid_action(self):
runner = CliRunner()
db = Db()
obj = {'config_db':db.cfgdb}
action = 'xforwardx'
iface = 'Ethernet0'
ERROR_MSG = "Error: Invalid action"

result = runner.invoke(config.config.commands['interface'].commands['loopback-action'], [iface, action], obj=obj)

print(result.exit_code, result.output)
assert result.exit_code != 0
assert ERROR_MSG in result.output

@classmethod
def teardown_class(cls):
print("\nTEARDOWN")
os.environ['UTILITIES_UNIT_TESTING'] = "0"
10 changes: 8 additions & 2 deletions tests/mock_tables/config_db.json
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@
},
"VLAN_SUB_INTERFACE|Eth32.10": {
"admin_status": "up",
"loopback_action": "drop",
"vlan": "100"
},
"ACL_RULE|NULL_ROUTE_V4|DEFAULT_RULE": {
Expand Down Expand Up @@ -552,6 +553,9 @@
"VLAN_INTERFACE|Vlan2000": {
"proxy_arp": "enabled"
},
"VLAN_INTERFACE|Vlan3000": {
"loopback_action": "forward"
},
"VLAN_INTERFACE|Vlan1000|192.168.0.1/21": {
"NULL": "NULL"
},
Expand Down Expand Up @@ -636,7 +640,8 @@
"NULL": "NULL"
},
"PORTCHANNEL_INTERFACE|PortChannel0001": {
"ipv6_use_link_local_only": "disable"
"ipv6_use_link_local_only": "disable",
"loopback_action": "drop"
},
"PORTCHANNEL_INTERFACE|PortChannel0002": {
"NULL": "NULL"
Expand Down Expand Up @@ -672,7 +677,8 @@
"NULL": "NULL"
},
"INTERFACE|Ethernet0": {
"ipv6_use_link_local_only": "disable"
"ipv6_use_link_local_only": "disable",
"loopback_action": "forward"
},
"INTERFACE|Ethernet0|14.14.0.1/24": {
"NULL": "NULL"
Expand Down
1 change: 1 addition & 0 deletions utilities_common/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,7 @@ def is_interface_in_config_db(config_db, interface_name):
if (not interface_name in config_db.get_keys('VLAN_INTERFACE') and
not interface_name in config_db.get_keys('INTERFACE') and
not interface_name in config_db.get_keys('PORTCHANNEL_INTERFACE') and
not interface_name in config_db.get_keys('VLAN_SUB_INTERFACE') and
not interface_name == 'null'):
return False

Expand Down