Skip to content

Fix Pfc/Qos counter integer range #2

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

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
Empty file added __init__.py
Empty file.
Empty file.
128 changes: 128 additions & 0 deletions src/sonic_ax_impl/lib/vtysh_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import re
import ipaddress
import socket

HOST = '127.0.0.1'
PORT = 2605

def union_bgp_sessions():
bgpsumm_ipv4 = show_bgp_summary('ip')
sessions_ipv4 = parse_bgp_summary(bgpsumm_ipv4)

bgpsumm_ipv6 = show_bgp_summary('ipv6')
sessions_ipv6 = parse_bgp_summary(bgpsumm_ipv6)

# Note: sessions_ipv4 will overwrite sessions_ipv6 if key is the same
sessions = {}
for ses in sessions_ipv6 + sessions_ipv4:
nei = ses['Neighbor']
sessions[nei] = ses
return sessions

def vtysh_run(command):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

cmd = b"zebra\r\n" + command.encode() + b"\r\nexit\r\n"
s.send(cmd)

acc = b""
while True:
data = s.recv(1024)
if not data:
break
acc += data

s.close()
return acc.decode('ascii', 'ignore')

def show_bgp_summary(ipver):
assert(ipver in ['ip', 'ipv6'])
try:
result = vtysh_run('show %s bgp summary' % ipver)

except ConnectionRefusedError as e:
raise RuntimeError('Failed to connect quagga socket') from e
except OSError as e:
raise RuntimeError('Socket error when talking with quagga') from e
return result

def parse_bgp_summary(summ):
ls = summ.splitlines()
bgpinfo = []

## Read until the table header
n = len(ls)
li = 0
while li < n:
l = ls[li]
if l.startswith('Neighbor '): break
if l.startswith('No IPv'): # eg. No IPv6 neighbor is configured
return bgpinfo
li += 1

## Read and store the table header
if li >= n:
raise ValueError('No table header found')
hl = ls[li]
li += 1
ht = re.split('\s+', hl.rstrip())
hn = len(ht)

## Read rows in the table
while li < n:
l = ls[li]
li += 1
if l == '': break

## Handle line wrap
## ref: bgp_show_summary in https://github.com/Azure/sonic-quagga/blob/debian/0.99.24.1/bgpd/bgp_vty.c
if ' ' not in l:
## Read next line
if li >= n:
raise ValueError('Unexpected line wrap')
l += ls[li]
li += 1

## Note: State/PfxRcd field may be 'Idle (Admin)'
lt = re.split('\s+', l.rstrip(), maxsplit = hn - 1)
if len(lt) != hn:
raise ValueError('Unexpected row in the table')
dic = dict(zip(ht, lt))
bgpinfo.append(dic)
return bgpinfo

STATE_CODE = {
"Idle": 1,
"Idle (Admin)": 1,
"Connect": 2,
"Active": 3,
"OpenSent": 4,
"OpenConfirm": 5,
"Established": 6
};

def bgp_peer_tuple(dic):
nei = dic['Neighbor']
ver = dic['V']
sta = dic['State/PfxRcd']

# prefix '*' appears if the entry is a dynamic neighbor
nei = nei[1:] if nei[0] == '*' else nei
ip = ipaddress.ip_address(nei)
if type(ip) is ipaddress.IPv4Address:
oid_head = (1, 4)
else:
oid_head = (2, 16)

oid_ip = tuple(i for i in ip.packed)

if sta.isdigit():
status = 6
elif sta in STATE_CODE:
status = STATE_CODE[sta]
else:
return None, None

return oid_head + oid_ip, status

8 changes: 7 additions & 1 deletion src/sonic_ax_impl/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from sonic_ax_impl.mibs import ieee802_1ab
from . import logger
from .mibs.ietf import rfc1213, rfc2737, rfc2863, rfc4292, rfc4363
from .mibs.vendor import dell
from .mibs.vendor import dell, cisco

# Background task update frequency ( in seconds )
DEFAULT_UPDATE_FREQUENCY = 5
Expand All @@ -31,6 +31,12 @@ class SonicMIB(
ieee802_1ab.LLDPLocPortTable,
ieee802_1ab.LLDPRemTable,
dell.force10.SSeriesMIB,
cisco.mgmt.CiscoSystemExtMIB,
cisco.bgp4.CiscoBgp4MIB,
cisco.ciscoPfcExtMIB.cpfcIfTable,
cisco.ciscoPfcExtMIB.cpfcIfPriorityTable,
cisco.ciscoSwitchQosMIB.csqIfQosGroupStatsTable,
cisco.ciscoEntityFruControlMIB.cefcFruPowerStatusTable,
):
"""
If SONiC was to create custom MIBEntries, they may be specified here.
Expand Down
66 changes: 66 additions & 0 deletions src/sonic_ax_impl/mibs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from sonic_ax_impl import logger, _if_alias_map

COUNTERS_PORT_NAME_MAP = b'COUNTERS_PORT_NAME_MAP'
COUNTERS_QUEUE_NAME_MAP = b'COUNTERS_QUEUE_NAME_MAP'
LAG_TABLE = b'LAG_TABLE'
LAG_MEMBER_TABLE = b'LAG_MEMBER_TABLE'
APPL_DB = 'APPL_DB'
Expand All @@ -22,6 +23,16 @@ def counter_table(sai_id):
"""
return b'COUNTERS:oid:0x' + sai_id

def queue_table(sai_id):
"""
:param sai_id: given sai_id to cast.
:return: COUNTERS table key.
"""
return b'COUNTERS:' + sai_id

def queue_key(port_index, queue_index):
return str(port_index) + ':' + str(queue_index)


def lldp_entry_table(if_name):
"""
Expand Down Expand Up @@ -158,3 +169,58 @@ def member_name_str(val, lag_name):
oid_lag_name_map[idx] = if_name

return lag_name_if_name_map, if_name_lag_name_map, oid_lag_name_map

def init_sync_d_queue_tables(db_conn):
"""
Initializes queue maps for SyncD-connected MIB(s).
:return: tuple(port_queues_map, queue_stat_map)
"""

# Make sure we're connected to COUNTERS_DB
db_conn.connect(COUNTERS_DB)

# { Port index : Queue index (SONiC) -> sai_id }
# ex: { "1:2" : "1000000000023" }
queue_name_map = db_conn.get_all(COUNTERS_DB, COUNTERS_QUEUE_NAME_MAP, blocking=True)
logger.debug("Queue name map:\n" + pprint.pformat(queue_name_map, indent=2))

# Parse the queue_name_map and create the following maps:
# port_queues_map -> {"if_index : queue_index" : sai_oid}
# queue_stat_map -> {queue stat table name : {counter name : value}}
# port_queue_list_map -> {if_index: [sorted queue list]}
port_queues_map = {}
queue_stat_map = {}
port_queue_list_map = {}

for queue_name, sai_id in queue_name_map.items():
port_name, queue_index = queue_name.decode().split(':')
queue_index = ''.join(i for i in queue_index if i.isdigit())
port_index = get_index_from_str(port_name)
key = queue_key(port_index, queue_index)
port_queues_map[key] = sai_id

queue_stat_name = queue_table(sai_id)
queue_stat = db_conn.get_all(COUNTERS_DB, queue_stat_name, blocking=False)
if queue_stat is not None:
queue_stat_map[queue_stat_name] = queue_stat

if not port_queue_list_map.get(int(port_index)):
port_queue_list_map[int(port_index)] = [int(queue_index)]
else:
port_queue_list_map[int(port_index)].append(int(queue_index))

# SyncD consistency checks.
if not port_queues_map:
# In the event no queue exists that follows the SONiC pattern, no OIDs are able to be registered.
# A RuntimeError here will prevent the 'main' module from loading. (This is desirable.)
logger.error("No queues found in the Counter DB. SyncD database is incoherent.")
raise RuntimeError('The port_queues_map is not defined')
elif not queue_stat_map:
logger.error("No queue stat counters found in the Counter DB. SyncD database is incoherent.")
raise RuntimeError('The queue_stat_map is not defined')

for queues in port_queue_list_map.values():
queues.sort()

return port_queues_map, queue_stat_map, port_queue_list_map

4 changes: 4 additions & 0 deletions src/sonic_ax_impl/mibs/vendor/cisco/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import mgmt, bgp4
from . import ciscoPfcExtMIB
from . import ciscoSwitchQosMIB
from . import ciscoEntityFruControlMIB
44 changes: 44 additions & 0 deletions src/sonic_ax_impl/mibs/vendor/cisco/bgp4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from bisect import bisect_right
from sonic_ax_impl import mibs
from sonic_ax_impl.lib import vtysh_helper
from ax_interface import MIBMeta, ValueType, MIBUpdater, SubtreeMIBEntry
from ax_interface.mib import MIBEntry

class BgpSessionUpdater(MIBUpdater):
def __init__(self):
super().__init__()
self.update_data()

def update_data(self):
self.session_status_map = {}
self.session_status_list = []

try:
sessions = vtysh_helper.union_bgp_sessions()
except RuntimeError as e:
mibs.logger.error("Failed to union bgp sessions: {}.".format(e))
return

for nei, ses in sessions.items():
oid, status = vtysh_helper.bgp_peer_tuple(ses)
if oid is None: continue
self.session_status_list.append(oid)
self.session_status_map[oid] = status

self.session_status_list.sort()

def sessionstatus(self, sub_id):
return self.session_status_map.get(sub_id, None)

def get_next(self, sub_id):
right = bisect_right(self.session_status_list, sub_id)
if right >= len(self.session_status_list):
return None

return self.session_status_list[right]


class CiscoBgp4MIB(metaclass=MIBMeta, prefix='.1.3.6.1.4.1.9.9.187'):
bgpsession_updater = BgpSessionUpdater()

cbgpPeer2State = SubtreeMIBEntry('1.2.5.1.3', bgpsession_updater, ValueType.INTEGER, bgpsession_updater.sessionstatus)
102 changes: 102 additions & 0 deletions src/sonic_ax_impl/mibs/vendor/cisco/ciscoEntityFruControlMIB.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import imp
import re
import sys

from sonic_ax_impl import mibs
from ax_interface import MIBMeta, ValueType, MIBUpdater, MIBEntry, SubtreeMIBEntry
from ax_interface.encodings import ObjectIdentifier

PSU_PLUGIN_MODULE_NAME = 'psuutil'
PSU_PLUGIN_MODULE_PATH = "/usr/share/sonic/platform/plugins/{}.py".format(PSU_PLUGIN_MODULE_NAME)
PSU_PLUGIN_CLASS_NAME = 'PsuUtil'

class PowerStatusHandler:
"""
Class to handle the SNMP request
"""
def __init__(self):
"""
init the handler
"""
self.psuutil = None

try:
module = imp.load_source(PSU_PLUGIN_MODULE_NAME, PSU_PLUGIN_MODULE_PATH)
except ImportError as e:
mibs.logger.error("Failed to load PSU module '%s': %s" % (PSU_PLUGIN_MODULE_NAME, str(e)), True)
return
except FileNotFoundError as e:
mibs.logger.error("Failed to get platform specific PSU module '%s': %s" % (PSU_PLUGIN_MODULE_NAME, str(e)), True)
return

try:
platform_psuutil_class = getattr(module, PSU_PLUGIN_CLASS_NAME)
self.psuutil = platform_psuutil_class()
except AttributeError as e:
mibs.logger.error("Failed to instantiate '%s' class: %s" % (PLATFORM_SPECIFIC_CLASS_NAME, str(e)), True)

def _getPsuIndex(self, sub_id):
"""
Get the PSU index from sub_id
:return: the index of supported PSU
"""
if not self.psuutil or not sub_id or len(sub_id) > 1:
return None

psu_index = int(sub_id[0])

if psu_index < 1 or psu_index > self.psuutil.get_num_psus():
return None

return psu_index

def get_next(self, sub_id):
"""
:param sub_id: The 1-based snmp sub-identifier query.
:return: the next sub id.
"""
if not self.psuutil:
return None

if not sub_id:
return (1,)

psu_index = self._getPsuIndex(sub_id)

if psu_index and psu_index + 1 <= self.psuutil.get_num_psus():
return (psu_index + 1,)

return None

def getPsuStatus(self, sub_id):
"""
:param sub_id: The 1-based sub-identifier query.
:return: the status of requested PSU according to cefcModuleOperStatus ModuleOperType
2 - PSU has correct functionalling - ok
7 - PSU has a problem with functionalling - failed
:ref: https://www.cisco.com/c/en/us/td/docs/switches/wan/mgx/mgx_8850/software/mgx_r2-0-10/pxm/reference/guide/pxm/cscoent.html
"""
psu_index = self._getPsuIndex(sub_id)

if not psu_index:
return None

psu_status = self.psuutil.get_psu_status(psu_index)

if psu_status:
return 2

return 7


class cefcFruPowerStatusTable(metaclass=MIBMeta, prefix='.1.3.6.1.4.1.9.9.117.1.1.2'):
"""
'cefcFruPowerStatusTable' http://oidref.com/1.3.6.1.4.1.9.9.117.1.1.2
"""

power_status_handler = PowerStatusHandler()

# cefcFruPowerStatusTable = '1.3.6.1.4.1.9.9.117.1.1.2'
# csqIfQosGroupStatsEntry = '1.3.6.1.4.1.9.9.117.1.1.2.1'

psu_status = SubtreeMIBEntry('1.2', power_status_handler, ValueType.INTEGER, power_status_handler.getPsuStatus)
Loading