Skip to content

Commit

Permalink
sonic-utilities: Add support for sFlow (sonic-net#592)
Browse files Browse the repository at this point in the history
* sonic-utilities: Add sFlow CLIs
  • Loading branch information
GarrickHe authored and prsunny committed Nov 5, 2019
1 parent 1b0d542 commit 0e23e1c
Show file tree
Hide file tree
Showing 3 changed files with 359 additions and 2 deletions.
271 changes: 271 additions & 0 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import netaddr
import re
import syslog
import netifaces

import sonic_device_util
import ipaddress
Expand Down Expand Up @@ -398,6 +399,7 @@ def _restart_services():
'pmon',
'lldp',
'hostcfgd',
'sflow',
]
if asic_type == 'mellanox' and 'pmon' in services_to_restart:
services_to_restart.remove('pmon')
Expand Down Expand Up @@ -1802,5 +1804,274 @@ def del_ntp_server(ctx, ntp_ip_address):
except SystemExit as e:
ctx.fail("Restart service ntp-config failed with error {}".format(e))

#
# 'sflow' group ('config sflow ...')
#
@config.group()
@click.pass_context
def sflow(ctx):
"""sFlow-related configuration tasks"""
config_db = ConfigDBConnector()
config_db.connect()
ctx.obj = {'db': config_db}
pass

#
# 'sflow' command ('config sflow enable')
#
@sflow.command()
@click.pass_context
def enable(ctx):
"""Enable sFlow"""
config_db = ctx.obj['db']
sflow_tbl = config_db.get_table('SFLOW')

if not sflow_tbl:
sflow_tbl = {'global': {'admin_state': 'up'}}
else:
sflow_tbl['global']['admin_state'] = 'up'

config_db.mod_entry('SFLOW', 'global', sflow_tbl['global'])

#
# 'sflow' command ('config sflow disable')
#
@sflow.command()
@click.pass_context
def disable(ctx):
"""Disable sFlow"""
config_db = ctx.obj['db']
sflow_tbl = config_db.get_table('SFLOW')

if not sflow_tbl:
sflow_tbl = {'global': {'admin_state': 'down'}}
else:
sflow_tbl['global']['admin_state'] = 'down'

config_db.mod_entry('SFLOW', 'global', sflow_tbl['global'])

#
# 'sflow' command ('config sflow polling-interval ...')
#
@sflow.command('polling-interval')
@click.argument('interval', metavar='<polling_interval>', required=True,
type=int)
@click.pass_context
def polling_int(ctx, interval):
"""Set polling-interval for counter-sampling (0 to disable)"""
if interval not in range(5, 301) and interval != 0:
click.echo("Polling interval must be between 5-300 (0 to disable)")

config_db = ctx.obj['db']
sflow_tbl = config_db.get_table('SFLOW')

if not sflow_tbl:
sflow_tbl = {'global': {'admin_state': 'down'}}

sflow_tbl['global']['polling_interval'] = interval
config_db.mod_entry('SFLOW', 'global', sflow_tbl['global'])

def is_valid_sample_rate(rate):
return rate in range(256, 8388608 + 1)


#
# 'sflow interface' group
#
@sflow.group()
@click.pass_context
def interface(ctx):
"""Configure sFlow settings for an interface"""
pass

#
# 'sflow' command ('config sflow interface enable ...')
#
@interface.command()
@click.argument('ifname', metavar='<interface_name>', required=True, type=str)
@click.pass_context
def enable(ctx, ifname):
if not interface_name_is_valid(ifname) and ifname != 'all':
click.echo("Invalid interface name")
return

config_db = ctx.obj['db']
intf_dict = config_db.get_table('SFLOW_SESSION')

if intf_dict and ifname in intf_dict.keys():
intf_dict[ifname]['admin_state'] = 'up'
config_db.mod_entry('SFLOW_SESSION', ifname, intf_dict[ifname])
else:
config_db.mod_entry('SFLOW_SESSION', ifname, {'admin_state': 'up'})

#
# 'sflow' command ('config sflow interface disable ...')
#
@interface.command()
@click.argument('ifname', metavar='<interface_name>', required=True, type=str)
@click.pass_context
def disable(ctx, ifname):
if not interface_name_is_valid(ifname) and ifname != 'all':
click.echo("Invalid interface name")
return

config_db = ctx.obj['db']
intf_dict = config_db.get_table('SFLOW_SESSION')

if intf_dict and ifname in intf_dict.keys():
intf_dict[ifname]['admin_state'] = 'down'
config_db.mod_entry('SFLOW_SESSION', ifname, intf_dict[ifname])
else:
config_db.mod_entry('SFLOW_SESSION', ifname,
{'admin_state': 'down'})

#
# 'sflow' command ('config sflow interface sample-rate ...')
#
@interface.command('sample-rate')
@click.argument('ifname', metavar='<interface_name>', required=True, type=str)
@click.argument('rate', metavar='<sample_rate>', required=True, type=int)
@click.pass_context
def sample_rate(ctx, ifname, rate):
if not interface_name_is_valid(ifname) and ifname != 'all':
click.echo('Invalid interface name')
return
if not is_valid_sample_rate(rate):
click.echo('Error: Sample rate must be between 256 and 8388608')
return

config_db = ctx.obj['db']
sess_dict = config_db.get_table('SFLOW_SESSION')

if sess_dict and ifname in sess_dict.keys():
sess_dict[ifname]['sample_rate'] = rate
config_db.mod_entry('SFLOW_SESSION', ifname, sess_dict[ifname])
else:
config_db.mod_entry('SFLOW_SESSION', ifname, {'sample_rate': rate})


#
# 'sflow collector' group
#
@sflow.group()
@click.pass_context
def collector(ctx):
"""Add/Delete a sFlow collector"""
pass

def is_valid_collector_info(name, ip, port):
if len(name) > 16:
click.echo("Collector name must not exceed 16 characters")
return False

if port not in range(0, 65535 + 1):
click.echo("Collector port number must be between 0 and 65535")
return False

if not is_ipaddress(ip):
click.echo("Invalid IP address")
return False

return True

#
# 'sflow' command ('config sflow collector add ...')
#
@collector.command()
@click.option('--port', required=False, type=int, default=6343,
help='Collector port number')
@click.argument('name', metavar='<collector_name>', required=True)
@click.argument('ipaddr', metavar='<IPv4/v6_address>', required=True)
@click.pass_context
def add(ctx, name, ipaddr, port):
"""Add a sFlow collector"""
ipaddr = ipaddr.lower()

if not is_valid_collector_info(name, ipaddr, port):
return

config_db = ctx.obj['db']
collector_tbl = config_db.get_table('SFLOW_COLLECTOR')

if (collector_tbl and name not in collector_tbl.keys() and len(collector_tbl) == 2):
click.echo("Only 2 collectors can be configured, please delete one")
return

config_db.mod_entry('SFLOW_COLLECTOR', name,
{"collector_ip": ipaddr, "collector_port": port})
return

#
# 'sflow' command ('config sflow collector del ...')
#
@collector.command('del')
@click.argument('name', metavar='<collector_name>', required=True)
@click.pass_context
def del_collector(ctx, name):
"""Delete a sFlow collector"""
config_db = ctx.obj['db']
collector_tbl = config_db.get_table('SFLOW_COLLECTOR')

if name not in collector_tbl.keys():
click.echo("Collector: {} not configured".format(name))
return

config_db.mod_entry('SFLOW_COLLECTOR', name, None)

#
# 'sflow agent-id' group
#
@sflow.group('agent-id')
@click.pass_context
def agent_id(ctx):
"""Add/Delete a sFlow agent"""
pass

#
# 'sflow' command ('config sflow agent-id add ...')
#
@agent_id.command()
@click.argument('ifname', metavar='<interface_name>', required=True)
@click.pass_context
def add(ctx, ifname):
"""Add sFlow agent information"""
if ifname not in netifaces.interfaces():
click.echo("Invalid interface name")
return

config_db = ctx.obj['db']
sflow_tbl = config_db.get_table('SFLOW')

if not sflow_tbl:
sflow_tbl = {'global': {'admin_state': 'down'}}

if 'agent_id' in sflow_tbl['global'].keys():
click.echo("Agent already configured. Please delete it first.")
return

sflow_tbl['global']['agent_id'] = ifname
config_db.mod_entry('SFLOW', 'global', sflow_tbl['global'])

#
# 'sflow' command ('config sflow agent-id del')
#
@agent_id.command('del')
@click.pass_context
def delete(ctx):
"""Delete sFlow agent information"""
config_db = ctx.obj['db']
sflow_tbl = config_db.get_table('SFLOW')

if not sflow_tbl:
sflow_tbl = {'global': {'admin_state': 'down'}}

if 'agent_id' not in sflow_tbl['global'].keys():
click.echo("sFlow agent not configured.")
return

sflow_tbl['global'].pop('agent_id')
config_db.set_entry('SFLOW', 'global', sflow_tbl['global'])


if __name__ == '__main__':
config()
86 changes: 86 additions & 0 deletions show/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2023,6 +2023,92 @@ def policer(policer_name, verbose):
run_command(cmd, display_cmd=verbose)


#
# 'sflow command ("show sflow ...")
#
@cli.group(invoke_without_command=True)
@click.pass_context
def sflow(ctx):
"""Show sFlow related information"""
config_db = ConfigDBConnector()
config_db.connect()
ctx.obj = {'db': config_db}
if ctx.invoked_subcommand is None:
show_sflow_global(config_db)

#
# 'sflow command ("show sflow interface ...")
#
@sflow.command('interface')
@click.pass_context
def sflow_interface(ctx):
"""Show sFlow interface information"""
show_sflow_interface(ctx.obj['db'])

def sflow_appDB_connect():
db = SonicV2Connector(host='127.0.0.1')
db.connect(db.APPL_DB, False)
return db

def show_sflow_interface(config_db):
sess_db = sflow_appDB_connect()
if not sess_db:
click.echo("sflow AppDB error")
return

port_tbl = config_db.get_table('PORT')
if not port_tbl:
click.echo("No ports configured")
return

idx_to_port_map = {int(port_tbl[name]['index']): name for name in
port_tbl.keys()}
click.echo("\nsFlow interface configurations")
header = ['Interface', 'Admin State', 'Sampling Rate']
body = []
for idx in sorted(idx_to_port_map.keys()):
pname = idx_to_port_map[idx]
intf_key = 'SFLOW_SESSION_TABLE:' + pname
sess_info = sess_db.get_all(sess_db.APPL_DB, intf_key)
if sess_info is None:
continue
body_info = [pname]
body_info.append(sess_info['admin_state'])
body_info.append(sess_info['sample_rate'])
body.append(body_info)
click.echo(tabulate(body, header, tablefmt='grid'))

def show_sflow_global(config_db):

sflow_info = config_db.get_table('SFLOW')
global_admin_state = 'down'
if sflow_info:
global_admin_state = sflow_info['global']['admin_state']

click.echo("\nsFlow Global Information:")
click.echo(" sFlow Admin State:".ljust(30) + "{}".format(global_admin_state))


click.echo(" sFlow Polling Interval:".ljust(30), nl=False)
if (sflow_info and 'polling_interval' in sflow_info['global'].keys()):
click.echo("{}".format(sflow_info['global']['polling_interval']))
else:
click.echo("default")

click.echo(" sFlow AgentID:".ljust(30), nl=False)
if (sflow_info and 'agent_id' in sflow_info['global'].keys()):
click.echo("{}".format(sflow_info['global']['agent_id']))
else:
click.echo("default")

sflow_info = config_db.get_table('SFLOW_COLLECTOR')
click.echo("\n {} Collectors configured:".format(len(sflow_info)))
for collector_name in sorted(sflow_info.keys()):
click.echo(" Name: {}".format(collector_name).ljust(30) +
"IP addr: {}".format(sflow_info[collector_name]['collector_ip']).ljust(20) +
"UDP port: {}".format(sflow_info[collector_name]['collector_port']))


#
# 'acl' group ###
#
Expand Down
Loading

0 comments on commit 0e23e1c

Please sign in to comment.