diff --git a/config/main.py b/config/main.py index f336fb5818..ae49bc0abf 100644 --- a/config/main.py +++ b/config/main.py @@ -57,6 +57,7 @@ from . import mclag from . import syslog from . import dns +from . import twamp_light # mock masic APIs for unit test try: @@ -1190,6 +1191,7 @@ def config(ctx): config.add_command(nat.nat) config.add_command(vlan.vlan) config.add_command(vxlan.vxlan) +config.add_command(twamp_light.twamp_light) #add mclag commands config.add_command(mclag.mclag) diff --git a/config/twamp_light.py b/config/twamp_light.py new file mode 100644 index 0000000000..e3c060b029 --- /dev/null +++ b/config/twamp_light.py @@ -0,0 +1,365 @@ +import click +import utilities_common.cli as clicommon +from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector +import ipaddress + +from .utils import log + +TWAMP_MODE_FULL = "FULL" +TWAMP_MODE_LIGHT = "LIGHT" +TWAMP_ROLE_SENDER = "SENDER" +TWAMP_ROLE_REFLECTOR = "REFLECTOR" +CFG_TWAMP_SESSION_TABLE_NAME = "TWAMP_SESSION" +STATE_TWAMP_SESSION_TABLE_NAME = "TWAMP_SESSION_TABLE" +TWAMP_SESSION_DEFAULT_SENDER_UDP_PORT = 862 +TWAMP_SESSION_DEFAULT_REFLECTOR_UDP_PORT = 863 + + +def check_if_twamp_session_exist(config_db, name): + """ Check if TWAMP-Light session exits in the config db """ + if len(config_db.get_entry(CFG_TWAMP_SESSION_TABLE_NAME, name)) != 0: + return True + return False + +def check_twamp_udp_port(udp_port): + """ Check if TWAMP-Light session udp_port """ + udp_port = int(udp_port) + if udp_port == TWAMP_SESSION_DEFAULT_SENDER_UDP_PORT: + return True + if udp_port == TWAMP_SESSION_DEFAULT_REFLECTOR_UDP_PORT: + return True + if udp_port in range(1025, 65535+1): + return True + return False + +def check_if_twamp_session_active(config_db, name): + """ Check if TWAMP-Light session status in the state db """ + entry = config_db.get_entry(CFG_TWAMP_SESSION_TABLE_NAME, name, 'role') + if entry is None: + return False + if entry['role'] != TWAMP_ROLE_SENDER: + return False + + state_db = SonicV2Connector(host='127.0.0.1') + state_db.connect(state_db.STATE_DB, False) + entry = state_db.get_entry(STATE_TWAMP_SESSION_TABLE_NAME, name) + if entry is None: + return False + if entry['status'] == 'active': + return True + return False + +############### TWAMP CLI param callback check ################## +def validate_twamp_session_exist_cb(ctx, param, name): + """ Helper function to validate session name """ + config_db = ConfigDBConnector() + config_db.connect() + if check_if_twamp_session_exist(config_db, name) is True: + raise click.UsageError('Invalid value for "<{}>": {}. TWAMP-Light session already exist'.format(param.name, name)) + return name + +def validate_twamp_session_cb(ctx, param, name): + """ Helper function to validate session name """ + config_db = ConfigDBConnector() + config_db.connect() + if name == 'all': + session_keys = config_db.get_table(CFG_TWAMP_SESSION_TABLE_NAME).keys() + if len(session_keys) == 0: + raise click.UsageError('Invalid value for "<{}>": {}. TWAMP-Light session not exist'.format(param.name, name)) + return session_keys + else: + session_keys = name.split(",") + for key in session_keys: + if check_if_twamp_session_exist(config_db, key) is False: + raise click.UsageError('Invalid value for "<{}>": {}. TWAMP-Light session not exist'.format(param.name, key)) + return session_keys + +def validate_twamp_ip_port_cb(ctx, param, ip_port): + """ Helper function to validate ip address and udp port """ + if ip_port.count(':') == 1: + ip_addr, udp_port = ip_port.split(':') + if check_twamp_udp_port(udp_port) is False: + raise click.UsageError('Invalid value for "<{}>": {}. Valid udp port range in 862|863|1025-65535'.format(param.name, udp_port)) + else: + ip_addr = ip_port + if TWAMP_ROLE_SENDER in param.name.upper(): + udp_port = TWAMP_SESSION_DEFAULT_SENDER_UDP_PORT + else: + udp_port = TWAMP_SESSION_DEFAULT_REFLECTOR_UDP_PORT + + try: + ip = ipaddress.ip_interface(ip_addr) + except ValueError as e: + raise click.UsageError('Invalid value for "<{}>": {}. Valid format in IPv4_address or IPv4_address:udp_port'.format(param.name, ip_addr)) + + return ip_addr, udp_port + +############### TWAMP-Light Configuration ################## +# +# 'twamp-light' group ('config twamp-light ...') +# +@click.group(cls=clicommon.AbbreviationGroup, name='twamp-light') +def twamp_light(): + """ TWAMP-Light related configuration tasks """ + pass + +# +# 'twamp-light session-sender' group ('config twamp-light session-sender ...') +# +@twamp_light.group(cls=clicommon.AbbreviationGroup, name='session-sender') +def twamp_light_sender(): + """ TWAMP-Light session-sender related configutation tasks """ + pass + +# +# 'twamp-light session-sender packet-count' group ('config twamp-light session-sender packet-count ...') +# +@twamp_light_sender.group('packet-count') +def twamp_light_sender_packet_count(): + """ TWAMP-Light session-sender packet-count mode """ + pass + +# +# 'twamp-light session-sender packet-count add' command ('config twamp-light session-sender packet-count add ...') +# +@twamp_light_sender_packet_count.command('add') +@click.argument('session_name', metavar='', required=True, callback=validate_twamp_session_exist_cb) +@click.argument('sender_ip_port', metavar='', required=True, callback=validate_twamp_ip_port_cb) +@click.argument('reflector_ip_port', metavar='', required=True, callback=validate_twamp_ip_port_cb) +@click.argument('packet_count', metavar='', required=True, type=click.IntRange(min=100, max=30000), default=100) +@click.argument('tx_interval', metavar='', required=True, type=click.Choice(['10', '100', '1000']), default='100') +@click.argument('timeout', metavar='', required=True, type=click.IntRange(min=1, max=10), default=5) +@click.argument('statistics_interval', metavar='', required=False, type=click.IntRange(min=2000, max=3600000)) +@click.option('--vrf', required=False, help='VRF Name') +@click.option('--dscp', required=False, type=click.IntRange(min=0, max=63), help='DSCP Value') +@click.option('--ttl', required=False, type=click.IntRange(min=1, max=255), help='TTL Value') +@click.option('--timestamp-format', required=False, type=click.Choice(['ntp', 'ptp']), help='Timestamp Format') +@clicommon.pass_db +def twamp_light_sender_packet_count_add(db, session_name, sender_ip_port, reflector_ip_port, + packet_count, tx_interval, timeout, statistics_interval, + vrf, dscp, ttl, timestamp_format): + """ Add TWAMP-Light session-sender packet-count session """ + + ctx = click.get_current_context() + + log.log_info("'twamp-light session-sender add packet-count {} {} {} {} {} {} {}' executing..." + .format(session_name, sender_ip_port, reflector_ip_port, + packet_count, tx_interval, timeout, statistics_interval)) + + sender_ip, sender_udp_port = sender_ip_port + reflector_ip, reflector_udp_port = reflector_ip_port + + if statistics_interval == None: + statistics_interval = int(packet_count) * int(tx_interval) + int(timeout)*1000 + else: + if int(statistics_interval) < int(tx_interval) + int(timeout)*1000: + ctx.fail("Statistics interval must be bigger than tx_interval + timeout*1000") + + fvs = { + 'mode' : TWAMP_MODE_LIGHT, + 'role' : TWAMP_ROLE_SENDER, + 'src_ip': sender_ip, + 'dst_ip': reflector_ip, + 'src_udp_port': sender_udp_port, + 'dst_udp_port': reflector_udp_port, + 'packet_count': packet_count, + 'tx_interval': tx_interval, + 'timeout': timeout, + 'statistics_interval': statistics_interval + } + + if vrf is not None: + fvs['vrf_name'] = vrf + if dscp is not None: + fvs['dscp'] = dscp + if ttl is not None: + fvs['ttl'] = ttl + if timestamp_format is not None: + fvs['timestamp_format'] = timestamp_format + + db.cfgdb.set_entry(CFG_TWAMP_SESSION_TABLE_NAME, session_name, fvs) + +# +# 'twamp-light session-sender continuous' group ('config twamp-light session-sender continuous ...') +# +@twamp_light_sender.group('continuous') +def twamp_light_sender_continuous(): + """ TWAMP-Light session-sender continuous mode """ + pass + +# +# 'twamp-light session-sender continuous add' command ('config twamp-light session-sender continuous add ...') +# +@twamp_light_sender_continuous.command('add') +@click.argument('session_name', metavar='', required=True, callback=validate_twamp_session_exist_cb) +@click.argument('sender_ip_port', metavar='', required=True, callback=validate_twamp_ip_port_cb) +@click.argument('reflector_ip_port', metavar='', required=True, callback=validate_twamp_ip_port_cb) +@click.argument('monitor_time', metavar='', required=True, type=click.INT, default=0) +@click.argument('tx_interval', metavar='', required=True, type=click.Choice(['10', '100', '1000']), default='100') +@click.argument('timeout', metavar='', required=True, type=click.IntRange(min=1, max=10), default=5) +@click.argument('statistics_interval', metavar='', required=False, type=click.IntRange(min=2000, max=3600000)) +@click.option('--vrf', required=False, help='VRF Name') +@click.option('--dscp', required=False, type=click.IntRange(min=0, max=63), help='DSCP Value') +@click.option('--ttl', required=False, type=click.IntRange(min=1, max=255), help='TTL Value') +@click.option('--timestamp-format', required=False, type=click.Choice(['ntp', 'ptp']), help='Timestamp Format') +@clicommon.pass_db +def twamp_light_sender_continuous_add(db, session_name, sender_ip_port, reflector_ip_port, + monitor_time, tx_interval, timeout, statistics_interval, + vrf, dscp, ttl, timestamp_format): + """ Add TWAMP-Light Session-Sender continuous session """ + + ctx = click.get_current_context() + + log.log_info("'twamp-light session-sender add continuous {} {} {} {} {} {} {}' executing..." + .format(session_name, sender_ip_port, reflector_ip_port, + monitor_time, tx_interval, timeout, statistics_interval)) + + sender_ip, sender_udp_port = sender_ip_port + reflector_ip, reflector_udp_port = reflector_ip_port + + if statistics_interval == None: + if int(monitor_time) == 0: + ctx.fail("Statistics interval must be configured while monitor_time is 0(forever)") + else: + statistics_interval = int(monitor_time)*1000 + + if int(statistics_interval) <= int(timeout)*1000: + ctx.fail("Statistics interval must be bigger than timeout*1000") + + fvs = { + 'mode' : TWAMP_MODE_LIGHT, + 'role' : TWAMP_ROLE_SENDER, + 'src_ip': sender_ip, + 'dst_ip': reflector_ip, + 'src_udp_port': sender_udp_port, + 'dst_udp_port': reflector_udp_port, + 'monitor_time': monitor_time, + 'tx_interval': tx_interval, + 'timeout': timeout, + 'statistics_interval': statistics_interval + } + + if vrf is not None: + fvs['vrf_name'] = vrf + if dscp is not None: + fvs['dscp'] = dscp + if ttl is not None: + fvs['ttl'] = ttl + if timestamp_format is not None: + fvs['timestamp_format'] = timestamp_format + + db.cfgdb.set_entry(CFG_TWAMP_SESSION_TABLE_NAME, session_name, fvs) + +# +# 'twamp-light session-sender start' command ('config twamp-light session-sender start ...') +# +@twamp_light_sender.command('start') +@click.argument('session_name', metavar='', required=True, callback=validate_twamp_session_cb) +@clicommon.pass_db +def twamp_light_sender_start(db, session_name): + """ Start TWAMP-Light session-sender session """ + + ctx = click.get_current_context() + + log.log_info("'twamp-light session-sender start {}' executing...".format(session_name)) + + session_keys = session_name + + for key in session_keys: + fvs = {} + fvs = db.cfgdb.get_entry(CFG_TWAMP_SESSION_TABLE_NAME, key) + fvs['admin_state'] = 'enabled' + + db.cfgdb.set_entry(CFG_TWAMP_SESSION_TABLE_NAME, key, fvs) + +# +# 'twamp-light session-sender stop' command ('config twamp-light session-sender stop ...') +# +@twamp_light_sender.command('stop') +@click.argument('session_name', metavar='', required=True, callback=validate_twamp_session_cb) +@clicommon.pass_db +def twamp_light_sender_stop(db, session_name): + """ Stop TWAMP-Light session-sender session """ + + ctx = click.get_current_context() + + log.log_info("'twamp-light session-sender stop {}' executing...".format(session_name)) + + session_keys = session_name + + for key in session_keys: + fvs = {} + fvs = db.cfgdb.get_entry(CFG_TWAMP_SESSION_TABLE_NAME, key) + fvs['admin_state'] = 'disabled' + + db.cfgdb.set_entry(CFG_TWAMP_SESSION_TABLE_NAME, key, fvs) + +# +# 'twamp-light session-reflector' group ('config twamp-light session-reflector ...') +# +@twamp_light.group(cls=clicommon.AbbreviationGroup, name='session-reflector') +def twamp_light_reflector(): + """ TWAMP-Light session-reflector related configutation tasks """ + pass + +# +# 'twamp-light session-reflector add' command ('config twamp-light session-reflector add ...') +# +@twamp_light_reflector.command('add') +@click.argument('session_name', metavar='', required=True, callback=validate_twamp_session_exist_cb) +@click.argument('sender_ip_port', metavar='', required=True, callback=validate_twamp_ip_port_cb) +@click.argument('reflector_ip_port', metavar='', required=True, callback=validate_twamp_ip_port_cb) +@click.option('--vrf', required=False, help='VRF Name') +@click.option('--dscp', required=False, type=click.IntRange(min=0, max=63), help='DSCP Value') +@click.option('--ttl', required=False, type=click.IntRange(min=1, max=255), help='TTL Value') +@click.option('--timestamp-format', required=False, type=click.Choice(['ntp', 'ptp']), help="Timestamp Format") +@clicommon.pass_db +def twamp_light_reflector_add(db, session_name, sender_ip_port, reflector_ip_port, + vrf, dscp, ttl, timestamp_format): + """ Add TWAMP-Light session-reflector session """ + + ctx = click.get_current_context() + + log.log_info("'twamp-light add reflector {} {} {}' executing..." + .format(session_name, sender_ip_port, reflector_ip_port)) + + sender_ip, sender_udp_port = sender_ip_port + reflector_ip, reflector_udp_port = reflector_ip_port + + fvs = { + 'mode' : TWAMP_MODE_LIGHT, + 'role' : TWAMP_ROLE_REFLECTOR, + 'src_ip': sender_ip, + 'dst_ip': reflector_ip, + 'src_udp_port': sender_udp_port, + 'dst_udp_port': reflector_udp_port + } + + if vrf is not None: + fvs['vrf_name'] = vrf + if dscp is not None: + fvs['dscp'] = dscp + if ttl is not None: + fvs['ttl'] = ttl + if timestamp_format is not None: + fvs['timestamp_format'] = timestamp_format + + db.cfgdb.set_entry(CFG_TWAMP_SESSION_TABLE_NAME, session_name, fvs) + +# +# 'twamp-light remove' command ('config twamp-light remove ...') +# +@twamp_light.command('remove') +@click.argument('session_name', metavar='', required=True, callback=validate_twamp_session_cb) +@clicommon.pass_db +def twamp_light_remove(db, session_name): + """ Remove TWAMP-Light session """ + + ctx = click.get_current_context() + + log.log_info("'twamp-light remove {}' executing...".format(session_name)) + + session_keys = session_name + + for key in session_keys: + db.cfgdb.set_entry(CFG_TWAMP_SESSION_TABLE_NAME, key, None) diff --git a/crm/main.py b/crm/main.py index 998ff23fc4..4c1ece552c 100644 --- a/crm/main.py +++ b/crm/main.py @@ -22,13 +22,13 @@ class Crm: "ipv4_route", "ipv6_route", "ipv4_nexthop", "ipv6_nexthop", "ipv4_neighbor", "ipv6_neighbor", "nexthop_group_member", "nexthop_group", "acl_table", "acl_group", "acl_entry", "acl_counter", "fdb_entry", "ipmc_entry", "snat_entry", "dnat_entry", "mpls_inseg", - "mpls_nexthop","srv6_nexthop", "srv6_my_sid_entry" + "mpls_nexthop","srv6_nexthop", "srv6_my_sid_entry", "twamp_entry" ) resources = ( "ipv4_route", "ipv6_route", "ipv4_nexthop", "ipv6_nexthop", "ipv4_neighbor", "ipv6_neighbor", "nexthop_group_member", "nexthop_group", "fdb_entry", "ipmc_entry", "snat_entry", "dnat_entry", - "mpls_inseg", "mpls_nexthop","srv6_nexthop", "srv6_my_sid_entry" + "mpls_inseg", "mpls_nexthop","srv6_nexthop", "srv6_my_sid_entry", "twamp_entry" ) acl_resources = ( @@ -795,6 +795,15 @@ def srv6_my_sid_entry(ctx): elif ctx.obj["crm"].cli_mode == 'resources': ctx.obj["crm"].show_resources('srv6_my_sid_entry') +@resources.command() +@click.pass_context +def twamp_entry(ctx): + """Show CRM information for TWAMP entry""" + if ctx.obj["crm"].cli_mode == 'thresholds': + ctx.obj["crm"].show_thresholds('twamp_entry') + elif ctx.obj["crm"].cli_mode == 'resources': + ctx.obj["crm"].show_resources('twamp_entry') + thresholds.add_command(acl) thresholds.add_command(all) thresholds.add_command(fdb) @@ -807,6 +816,7 @@ def srv6_my_sid_entry(ctx): thresholds.add_command(dnat) thresholds.add_command(srv6_nexthop) thresholds.add_command(srv6_my_sid_entry) +thresholds.add_command(twamp_entry) if device_info.get_platform_info().get('switch_type') == "dpu": resources.add_command(show_dash) diff --git a/scripts/twamp-light b/scripts/twamp-light new file mode 100755 index 0000000000..89c6b8449d --- /dev/null +++ b/scripts/twamp-light @@ -0,0 +1,378 @@ +#!/usr/bin/env python3 + +""" + Script to show TWAMP-Light session + Example of the output: + +""" + +import argparse +import sys +import time + +from tabulate import tabulate +from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector + + +class TwampLight(object): + + TWAMP_MODE_FULL = "FULL" + TWAMP_MODE_LIGHT = "LIGHT" + TWAMP_ROLE_SENDER = "SENDER" + TWAMP_ROLE_REFLECTOR = "REFLECTOR" + TWAMP_STATISTICS_TWOWAY_DELAY = "twoway-delay" + TWAMP_STATISTICS_TWOWAY_LOSS = "twoway-loss" + CFG_TWAMP_SESSION_TABLE_NAME = "TWAMP_SESSION" + STATE_TWAMP_SESSION_TABLE_NAME = "TWAMP_SESSION_TABLE" + COUNTERS_TWAMP_SESSION_NAME_MAP_TABLE_NAME = "COUNTERS_TWAMP_SESSION_NAME_MAP" + COUNTER_TABLE_PREFIX = 'COUNTERS:' + COUNTER_TABLE_INDEX = ':INDEX:' + COUNTER_TABLE_INDEX0 = 0 + + def __init__(self): + super(TwampLight,self).__init__() + self.config_db = ConfigDBConnector() + self.state_db = SonicV2Connector(host="127.0.0.1") + self.counters_db = SonicV2Connector(host="127.0.0.1") + + self.session_db_info = {} + self.name_map_db_info = {} + self.stats_db_info = {} + + def fetch_session_info(self): + self.config_db.connect() + self.state_db.connect(self.state_db.STATE_DB) + + self.read_session_info() + self.read_session_state_info() + + def fetch_statistics_info(self, session_name, brief, lastest_number): + self.config_db.connect() + self.state_db.connect(self.state_db.STATE_DB) + self.counters_db.connect(self.counters_db.COUNTERS_DB) + + self.read_statistics_info(session_name, brief, lastest_number) + + def read_session_info(self): + """ + Read CFG_TWAMP_SESSION_TABLE_NAME table from configuration database + :return: + """ + self.session_db_info = self.config_db.get_table(self.CFG_TWAMP_SESSION_TABLE_NAME) + + def get_session_db_info(self): + return self.session_db_info + + def read_session_state_info(self): + """ + Read STATE_TWAMP_SESSION_TABLE_NAME table from configuration database + :return: + """ + for key in self.get_session_db_info(): + state_db_info = self.state_db.get_all(self.state_db.STATE_DB, "{}|{}".format(self.STATE_TWAMP_SESSION_TABLE_NAME, key)) + self.session_db_info[key]["status"] = state_db_info.get("status", "inactive") if state_db_info else "error" + + def read_statistics_info(self, session_name, brief, lastest_number): + """ + Read COUNTERS table from counters database + :return: + """ + self.name_map_db_info = self.counters_db.get_all(self.counters_db.COUNTERS_DB, self.COUNTERS_TWAMP_SESSION_NAME_MAP_TABLE_NAME) + for name,oid in self.name_map_db_info.items(): + if session_name and name != session_name: + continue + self.stats_db_info[name] = {} + if brief: + counters_keys = self.counters_db.keys(self.counters_db.COUNTERS_DB, self.COUNTER_TABLE_PREFIX + oid) + else: + counters_keys = self.counters_db.keys(self.counters_db.COUNTERS_DB, self.COUNTER_TABLE_PREFIX + oid + '*') + if counters_keys: + tmp_db_info = {} + for key in counters_keys: + counters_db_info = self.counters_db.get_all(self.counters_db.COUNTERS_DB, key) + if self.COUNTER_TABLE_INDEX in key: + index = int(key.split(self.COUNTER_TABLE_INDEX)[1]) + else: + index = self.COUNTER_TABLE_INDEX0 + tmp_db_info[index] = {} + tmp_db_info[index]["tx_packets"] = counters_db_info.get("SAI_TWAMP_SESSION_STAT_TX_PACKETS", "") + tmp_db_info[index]["tx_bytes"] = counters_db_info.get("SAI_TWAMP_SESSION_STAT_TX_BYTE", "") + tmp_db_info[index]["rx_packets"] = counters_db_info.get("SAI_TWAMP_SESSION_STAT_RX_PACKETS", "") + tmp_db_info[index]["rx_bytes"] = counters_db_info.get("SAI_TWAMP_SESSION_STAT_RX_BYTE", "") + tmp_db_info[index]["drop_packets"] = counters_db_info.get("SAI_TWAMP_SESSION_STAT_DROP_PACKETS", "") + tmp_db_info[index]["max_latency"] = counters_db_info.get("SAI_TWAMP_SESSION_STAT_MAX_LATENCY", "") + tmp_db_info[index]["min_latency"] = counters_db_info.get("SAI_TWAMP_SESSION_STAT_MIN_LATENCY", "") + tmp_db_info[index]["avg_latency"] = counters_db_info.get("SAI_TWAMP_SESSION_STAT_AVG_LATENCY", "") + tmp_db_info[index]["max_jitter"] = counters_db_info.get("SAI_TWAMP_SESSION_STAT_MAX_JITTER", "") + tmp_db_info[index]["min_jitter"] = counters_db_info.get("SAI_TWAMP_SESSION_STAT_MIN_JITTER", "") + tmp_db_info[index]["avg_jitter"] = counters_db_info.get("SAI_TWAMP_SESSION_STAT_AVG_JITTER", "") + tmp_db_info_sort = sorted(tmp_db_info.keys())[lastest_number:] + self.stats_db_info[name][self.COUNTER_TABLE_INDEX0] = tmp_db_info[self.COUNTER_TABLE_INDEX0] + for index in tmp_db_info_sort: + self.stats_db_info[name][index] = tmp_db_info[index] + + def get_stats_db_info(self): + return self.stats_db_info + + def display_sender_session(self, session_name): + """ + Show twamp session configuration. + :param session_name: Optional. Twamp-Light session name. Filter sessions by specified name. + :return: + """ + output_summary = '' + output_summary += "Time unit: Monitor Time/Timeout in second; Tx Interval/Stats Interval in millisecond\n" + sender_header = ("Name", "Status", "Sender IP:PORT", "Reflector IP:PORT", + "Packet Count", "Monitor Time", "Tx Interval", "Stats Interval", "Timeout") + + sender_data = [] + for key, val in self.get_session_db_info().items(): + if session_name and key != session_name: + continue + if val.get("role", "") != self.TWAMP_ROLE_SENDER: + continue + + sender_data.append([key, val.get("status", ""), + "{}:{}".format(val.get("src_ip", ""), val.get("src_udp_port", "")), "{}:{}".format(val.get("dst_ip", ""), val.get("dst_udp_port", "")), + val.get("packet_count", "-"), val.get("monitor_time", "-"), val.get("tx_interval", "-"), val.get("statistics_interval", ""), val.get("timeout", "")]) + + output_summary += "TWAMP-Light Sender Sessions\n" + output_summary += tabulate(sender_data, headers=sender_header, tablefmt="simple", missingval="") + "\n" + print(output_summary) + + def display_reflector_session(self, session_name): + """ + Show twamp session configuration. + :param session_name: Optional. Twamp-Light session name. Filter sessions by specified name. + :return: + """ + output_summary = '' + reflector_header = ("Name", "Status", "Sender IP:PORT", "Reflector IP:PORT") + + reflector_data = [] + for key, val in self.get_session_db_info().items(): + if session_name and key != session_name: + continue + if val.get("role", "") != self.TWAMP_ROLE_REFLECTOR: + continue + + reflector_data.append([key, val.get("status", ""), + "{}:{}".format(val.get("src_ip", ""), val.get("src_udp_port", "")), "{}:{}".format(val.get("dst_ip", ""), val.get("dst_udp_port", ""))]) + + output_summary += "TWAMP-Light Reflector Sessions\n" + output_summary += tabulate(reflector_data, headers=reflector_header, tablefmt="simple", missingval="") + "\n" + print(output_summary) + + def display_statistics_twoway_delay_index(self, session_name): + """ + Show twamp session statistics. + :param session_name: Optional. Twamp-Light session name. Filter sessions by specified name. + :return: + """ + statistics_header = ("Name", "Index", "Delay(AVG)", "Jitter(AVG)") + print("Latest two-way delay statistics(nsec):") + statistics_data = [] + session_data = [] + for key, val in self.get_stats_db_info().items(): + if session_name and key != session_name: + continue + insert_key = True + for index, stats_info in val.items(): + if index == self.COUNTER_TABLE_INDEX0: + continue + avg_latency = stats_info.get("avg_latency", "") + if insert_key: + session_data.append([key, index, avg_latency, stats_info.get("avg_jitter", "")]) + insert_key = False + else: + session_data.append(["", index, avg_latency, stats_info.get("avg_jitter", "")]) + statistics_data += session_data + session_data = [] + print(tabulate(statistics_data, headers=statistics_header, tablefmt="simple", missingval="")) + print("") + + def display_statistics_twoway_delay_total(self, session_name): + """ + Show twamp session statistics. + :param session_name: Optional. Twamp-Light session name. Filter sessions by specified name. + :return: + """ + statistics_header = ("Name", "Delay(AVG)", "Delay(MIN)", "Delay(MAX)", "Jitter(AVG)", "Jitter(MIN)", "Jitter(MAX)") + print("Total two-way delay statistics(nsec):") + statistics_data = [] + for name, val in self.get_stats_db_info().items(): + if session_name and name != session_name: + continue + for index, stats_info in val.items(): + if index != self.COUNTER_TABLE_INDEX0: + continue + avg_latency = stats_info.get("avg_latency", "") + min_latency = stats_info.get("min_latency", "") + max_latency = stats_info.get("max_latency", "") + statistics_data.append([name, + avg_latency, min_latency, max_latency, + stats_info.get("avg_jitter", ""), stats_info.get("min_jitter", ""), stats_info.get("max_jitter", "")]) + + print(tabulate(statistics_data, headers=statistics_header, tablefmt="simple", missingval="")) + print("") + + def display_statistics_twoway_delay(self, session_name, brief): + """ + Show twamp session statistics. + :param session_name: Optional. Twamp-Light session name. Filter sessions by specified name. + :return: + """ + if not brief: + self.display_statistics_twoway_delay_index(session_name) + self.display_statistics_twoway_delay_total(session_name) + + def display_statistics_twoway_loss_index(self, session_name): + """ + Show twamp session statistics. + :param session_name: Optional. Twamp-Light session name. Filter sessions by specified name. + :return: + """ + statistics_header = ("Name", "Index", "Loss Count", "Loss Ratio") + print("Latest two-way loss statistics:") + statistics_data = [] + session_data = [] + for key, val in self.get_stats_db_info().items(): + if session_name and key != session_name: + continue + insert_key = True + for index, stats_info in val.items(): + if index == self.COUNTER_TABLE_INDEX0: + continue + tx_packets = int(stats_info.get("tx_packets", "0")) + rx_packets = int(stats_info.get("rx_packets", "0")) + drop_packets = tx_packets - rx_packets + drop_ratio = drop_packets * 100 / tx_packets if tx_packets else 0 + if insert_key: + session_data.append([key, index, drop_packets, drop_ratio]) + insert_key = False + else: + session_data.append(["", index, drop_packets, drop_ratio]) + statistics_data += session_data + session_data = [] + print(tabulate(statistics_data, headers=statistics_header, tablefmt="simple", missingval="")) + print("") + + def display_statistics_twoway_loss_total(self, session_name): + """ + Show twamp session statistics. + :param session_name: Optional. Twamp-Light session name. Filter sessions by specified name. + :return: + """ + statistics_header = ("Name", "Loss Count(AVG)", "Loss Count(MIN)", "Loss Count(MAX)", "Loss Ratio(AVG)", "Loss Ratio(MIN)", "Loss Ratio(MAX)") + print("Total two-way loss statistics:") + statistics_data = [] + for name, val in self.get_stats_db_info().items(): + if session_name and name != session_name: + continue + if not val: + continue + avg_drop_packets = 0 + min_drop_packets = 0 + max_drop_packets = 0 + avg_drop_ratio = 0 + min_drop_ratio = 0 + max_drop_ratio = 0 + total_tx_packets = 0 + total_drop_packets = 0 + total_index = 0 + for index, stats_info in val.items(): + if index == self.COUNTER_TABLE_INDEX0: + continue + total_index += 1 + tx_packets = int(stats_info.get("tx_packets", "0")) + rx_packets = int(stats_info.get("rx_packets", "0")) + drop_packets = tx_packets - rx_packets + total_tx_packets += tx_packets + total_drop_packets += drop_packets + if 1 == total_index: + min_drop_packets = drop_packets + else: + min_drop_packets = drop_packets if drop_packets < min_drop_packets else min_drop_packets + max_drop_packets = drop_packets if drop_packets > max_drop_packets else max_drop_packets + if tx_packets: + drop_ratio = drop_packets * 100 / tx_packets + if 1 == total_index: + min_drop_ratio = drop_ratio + else: + min_drop_ratio = drop_ratio if drop_ratio < min_drop_ratio else min_drop_ratio + max_drop_ratio = drop_ratio if drop_ratio > max_drop_ratio else max_drop_ratio + else: + min_drop_ratio = 0 + max_drop_ratio = 0 + avg_drop_packets = total_drop_packets / total_index if total_index else 0 + avg_drop_ratio = total_drop_packets * 100 / total_tx_packets if total_tx_packets else 0 + statistics_data.append([name, + avg_drop_packets, min_drop_packets, max_drop_packets, + avg_drop_ratio, min_drop_ratio, max_drop_ratio]) + + print(tabulate(statistics_data, headers=statistics_header, tablefmt="simple", missingval="")) + print("") + + def display_statistics_twoway_loss(self, session_name, brief): + """ + Show twamp session statistics. + :param session_name: Optional. Twamp-Light session name. Filter sessions by specified name. + :return: + """ + if not brief: + self.display_statistics_twoway_loss_index(session_name) + self.display_statistics_twoway_loss_total(session_name) + + def display_session(self, role, session_name): + if role is None or role == self.TWAMP_ROLE_SENDER: + self.display_sender_session(session_name) + if role is None or role == self.TWAMP_ROLE_REFLECTOR: + self.display_reflector_session(session_name) + + def display_statistics(self, statistics_type, session_name, brief): + if statistics_type == self.TWAMP_STATISTICS_TWOWAY_DELAY: + self.display_statistics_twoway_delay(session_name, brief) + elif statistics_type == self.TWAMP_STATISTICS_TWOWAY_LOSS: + self.display_statistics_twoway_loss(session_name, brief) + +def main(): + parser = argparse.ArgumentParser(description='Display the TWAMP-Light information', + formatter_class=argparse.RawTextHelpFormatter, + epilog=""" + Examples: + twamp-light + twamp-light -n session1 + twamp-light -n session2 -t twoway-delay + twamp-light -n session3 -t twoway-loss -b + twamp-light -n session4 -t twoway-loss -l 10 + """) + + parser.add_argument('-r', '--role', type=str, help='TWAMP-Light role', default=None) + parser.add_argument('-n', '--sessionname', type=str, help='TWAMP-Light session name', default=None) + parser.add_argument('-t', '--statisticstype', type=str, help='TWAMP-Light statistics type choose from twoway-delay, twoway-loss', default=None) + parser.add_argument('-b', '--brief', action='store_true', help='TWAMP-Light brief info') + parser.add_argument('-l', '--lastestnumber', type=int, help='TWAMP-Light lastest number set of statistics info', default=None) + args = parser.parse_args() + + role = args.role + session_name = args.sessionname + statistics_type = args.statisticstype + brief = args.brief + lastest_number = (0 - args.lastestnumber) if args.lastestnumber else 0 + + try: + twamplight = TwampLight() + + if statistics_type: + twamplight.fetch_statistics_info(session_name, brief, lastest_number) + twamplight.display_statistics(statistics_type, session_name, brief) + else: + twamplight.fetch_session_info() + twamplight.fetch_statistics_info(session_name, True, 0) + twamplight.display_session(role, session_name) + except Exception as e: + print(str(e)) + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/setup.py b/setup.py index 6888d3405d..06c2be9090 100644 --- a/setup.py +++ b/setup.py @@ -187,7 +187,8 @@ 'scripts/verify_image_sign.sh', 'scripts/verify_image_sign_common.sh', 'scripts/check_db_integrity.py', - 'scripts/sysreadyshow' + 'scripts/sysreadyshow', + 'scripts/twamp-light' ], entry_points={ 'console_scripts': [ diff --git a/show/main.py b/show/main.py index 725556e6e8..232a363f51 100755 --- a/show/main.py +++ b/show/main.py @@ -66,6 +66,7 @@ from . import plugins from . import syslog from . import dns +from . import twamp_light # Global Variables PLATFORM_JSON = 'platform.json' @@ -299,6 +300,7 @@ def cli(ctx): cli.add_command(system_health.system_health) cli.add_command(warm_restart.warm_restart) cli.add_command(dns.dns) +cli.add_command(twamp_light.twamp_light) # syslog module cli.add_command(syslog.syslog) diff --git a/show/twamp_light.py b/show/twamp_light.py new file mode 100644 index 0000000000..08347f193a --- /dev/null +++ b/show/twamp_light.py @@ -0,0 +1,53 @@ +import click +import utilities_common.cli as clicommon + +# +# 'twamp-light' group ("show twamp-light") +# +@click.group(cls=clicommon.AliasedGroup) +def twamp_light(): + """ Show TWAMP-Light related information """ + pass + +# +# 'twamp-light session' subcommand ("show twamp-light session") +# +@twamp_light.command('session') +@click.argument('session_name', metavar='', required=False) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +@clicommon.pass_db +def twamp_light_session(db, session_name, verbose): + """ Show TWAMP-Light session """ + cmd = "twamp-light" + + if session_name is not None: + cmd += " -n {}".format(session_name) + + clicommon.run_command(cmd, display_cmd=verbose) + +# +# 'twamp-light statistics' subcommand ("show twamp-light statistics") +# +@twamp_light.command('statistics') +@click.argument('statistics_type', metavar='', required=True, type=click.Choice(['twoway-delay', 'twoway-loss'])) +@click.argument('session_name', metavar='', required=False) +@click.option('--brief', '-b', is_flag=True, help="Enable brief output") +@click.option('--lastest_number', '-l', is_flag=False, type=click.INT, help="Enable lastest number set of statistics output") +@click.option('--verbose', is_flag=True, help="Enable verbose output") +@clicommon.pass_db +def twamp_light_statistics(db, statistics_type, session_name, brief, lastest_number, verbose): + """Show TWAMP-Light statistics""" + cmd = "twamp-light" + + if statistics_type is not None: + cmd += " -t {}".format(statistics_type) + + if session_name is not None: + cmd += " -n {}".format(session_name) + + if brief is not False: + cmd += " -b" + elif lastest_number is not None: + cmd += " -l {}".format(lastest_number) + + clicommon.run_command(cmd, display_cmd=verbose)