mirror of
https://github.com/kernelkit/infix.git
synced 2026-08-06 15:43:02 +02:00
statd: refactor to autotools, same as confd
Fix issue #299 Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
@@ -274,9 +274,9 @@
|
||||
sysrepocfg -d operational -X -f json -x /ietf-system:system-state/infix-system:software | \
|
||||
jq -r '.[][].slot |= sort_by(.name)' > "$tmp"
|
||||
if [ -n "$KLISH_PARAM_name" ]; then
|
||||
cat "$tmp" | /libexec/infix/cli-pretty "infix-system" -n "$KLISH_PARAM_name"
|
||||
cat "$tmp" | /usr/libexec/statd/cli-pretty "infix-system" -n "$KLISH_PARAM_name"
|
||||
else
|
||||
cat "$tmp" | /libexec/infix/cli-pretty "infix-system"
|
||||
cat "$tmp" | /usr/libexec/statd/cli-pretty "infix-system"
|
||||
fi
|
||||
rm "$tmp"
|
||||
</ACTION>
|
||||
@@ -323,14 +323,14 @@
|
||||
fi
|
||||
fi
|
||||
sysrepocfg -f json -X -d operational -x "/ietf-routing:routing/ribs" | \
|
||||
/libexec/infix/cli-pretty "ietf-routing" -n "$KLISH_PARAM_name"
|
||||
/usr/libexec/statd/cli-pretty "ietf-routing" -n "$KLISH_PARAM_name"
|
||||
</ACTION>
|
||||
</COMMAND>
|
||||
|
||||
<COMMAND name="hardware" help="Show hardware information">
|
||||
<ACTION sym="script">
|
||||
sysrepocfg -f json -X -d operational -x "/ietf-hardware:hardware" | \
|
||||
/libexec/infix/cli-pretty "ietf-hardware"
|
||||
/usr/libexec/statd/cli-pretty "ietf-hardware"
|
||||
</ACTION>
|
||||
</COMMAND>
|
||||
|
||||
@@ -346,10 +346,10 @@
|
||||
if [ -n "$KLISH_PARAM_name" ]; then
|
||||
sysrepocfg -f json -X -d operational -x \
|
||||
"/ietf-interfaces:interfaces/interface[name='$KLISH_PARAM_name']" | \
|
||||
/libexec/infix/cli-pretty "ietf-interfaces" -n "$KLISH_PARAM_name"
|
||||
/usr/libexec/statd/cli-pretty "ietf-interfaces" -n "$KLISH_PARAM_name"
|
||||
else
|
||||
sysrepocfg -f json -X -d operational -m ietf-interfaces | \
|
||||
/libexec/infix/cli-pretty "ietf-interfaces"
|
||||
/usr/libexec/statd/cli-pretty "ietf-interfaces"
|
||||
fi
|
||||
</ACTION>
|
||||
</COMMAND>
|
||||
|
||||
@@ -1,3 +1,28 @@
|
||||
*~
|
||||
*.o
|
||||
statd
|
||||
|
||||
/aclocal.m4
|
||||
/autom4te.cache/
|
||||
/aux
|
||||
/clixon.xml
|
||||
/compile
|
||||
/config.h
|
||||
/config.h.in
|
||||
/config.guess
|
||||
/config.log
|
||||
/config.status
|
||||
/config.sub
|
||||
/configure
|
||||
/depcomp
|
||||
/install-sh
|
||||
/libtool
|
||||
/ltmain.sh
|
||||
/m4
|
||||
/missing
|
||||
|
||||
GPATH
|
||||
GRTAGS
|
||||
GTAGS
|
||||
Makefile
|
||||
Makefile.in
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
CFLAGS += -Wall -Wextra -Werror
|
||||
CPPFLAGS += -D_GNU_SOURCE
|
||||
LDLIBS += -lsysrepo -lev -lyang -ljansson -lsrx -lite
|
||||
|
||||
TARGET = statd
|
||||
HEADERS = shared.h
|
||||
SRC = statd.c shared.c
|
||||
OBJ = $(SRC:.c=.o)
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
.c.o:
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJ): $(SRC) $(HEADERS)
|
||||
|
||||
$(TARGET): $(OBJ)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
|
||||
|
||||
clean:
|
||||
-rm -f $(TARGET) $(OBJ)
|
||||
|
||||
distclean: clean
|
||||
-rm *~
|
||||
|
||||
install: $(TARGET)
|
||||
install -D $(TARGET) $(DESTDIR)/sbin/
|
||||
@@ -0,0 +1,13 @@
|
||||
DISTCLEANFILES = *~ *.d
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
|
||||
pkglibexec_SCRIPTS = cli-pretty yanger ospf-status
|
||||
|
||||
sbin_PROGRAMS = statd
|
||||
statd_SOURCES = statd.c shared.c shared.h
|
||||
statd_CPPFLAGS = -D_DEFAULT_SOURCE -D_GNU_SOURCE
|
||||
statd_CFLAGS = -W -Wall -Wextra
|
||||
statd_CFLAGS += $(jansson_CFLAGS) $(libite_CFLAGS) $(libyang_CFLAGS)
|
||||
statd_CFLAGS += $(sysrepo_CFLAGS) $(libsrx_CFLAGS)
|
||||
statd_LDADD = $(jansson_LIBS) $(libite_LIBS) $(libyang_LIBS)
|
||||
statd_LDADD += $(sysrepo_LIBS) $(libsrx_LIBS)
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
autoreconf -W portability -vifm
|
||||
Executable
+530
@@ -0,0 +1,530 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="JSON CLI Pretty Printer")
|
||||
parser.add_argument("module", help="IETF Module")
|
||||
parser.add_argument("-n", "--name", help="Focus on specific name")
|
||||
args = parser.parse_args()
|
||||
|
||||
class Pad:
|
||||
iface = 16
|
||||
proto = 11
|
||||
state = 12
|
||||
data = 41
|
||||
|
||||
class PadRoute:
|
||||
prefix = 30
|
||||
protocol = 10
|
||||
next_hop = 30
|
||||
pref = 8
|
||||
|
||||
class PadSoftware:
|
||||
name = 10
|
||||
date = 25
|
||||
hash = 64
|
||||
state = 10
|
||||
version = 23
|
||||
|
||||
|
||||
class PadUsbPort:
|
||||
title = 30
|
||||
name = 20
|
||||
state = 10
|
||||
|
||||
class Decore():
|
||||
@staticmethod
|
||||
def decorate(sgr, txt, restore="0"):
|
||||
return f"\033[{sgr}m{txt}\033[{restore}m"
|
||||
|
||||
@staticmethod
|
||||
def invert(txt):
|
||||
return Decore.decorate("7", txt)
|
||||
|
||||
@staticmethod
|
||||
def red(txt):
|
||||
return Decore.decorate("31", txt, "39")
|
||||
|
||||
@staticmethod
|
||||
def green(txt):
|
||||
return Decore.decorate("32", txt, "39")
|
||||
|
||||
@staticmethod
|
||||
def yellow(txt):
|
||||
return Decore.decorate("33", txt, "39")
|
||||
|
||||
def get_json_data(default, indata, *args):
|
||||
data = indata
|
||||
for arg in args:
|
||||
if arg in data:
|
||||
data = data.get(arg)
|
||||
else:
|
||||
return default
|
||||
|
||||
return data
|
||||
|
||||
class Route:
|
||||
def __init__(self,data,ip):
|
||||
self.data = data
|
||||
self.prefix = data.get(f'ietf-{ip}-unicast-routing:destination-prefix', '')
|
||||
self.protocol = data.get('source-protocol','')[14:]
|
||||
self.pref = data.get('route-preference','')
|
||||
self.next_hop = []
|
||||
next_hop_list=get_json_data(None, self.data, 'next-hop', 'next-hop-list')
|
||||
if next_hop_list:
|
||||
for nh in next_hop_list["next-hop"]:
|
||||
if(nh.get(f"ietf-{ip}-unicast-routing:address")):
|
||||
self.next_hop.append(nh[f"ietf-{ip}-unicast-routing:address"])
|
||||
elif(nh.get("outgoing-interface")):
|
||||
self.next_hop.append(nh["outgoing-interface"])
|
||||
else:
|
||||
self.next_hop.append("unspecified")
|
||||
else:
|
||||
interface = get_json_data(None, self.data, 'next-hop', 'outgoing-interface')
|
||||
address = get_json_data(None, self.data, 'next-hop', f'ietf-{ip}-unicast-routing:next-hop-address')
|
||||
special = get_json_data(None, self.data, 'next-hop', 'special-next-hop')
|
||||
|
||||
if address:
|
||||
self.next_hop.append(address)
|
||||
elif interface:
|
||||
self.next_hop.append(interface)
|
||||
elif special:
|
||||
self.next_hop.append(special)
|
||||
else:
|
||||
self.next_hop.append("unspecified")
|
||||
def print(self):
|
||||
row = f"{self.prefix:<{PadRoute.prefix}}"
|
||||
row += f"{self.next_hop[0]:<{PadRoute.next_hop}}"
|
||||
row += f"{self.pref:>{PadRoute.pref}} "
|
||||
row += f"{self.protocol:<{PadRoute.protocol}}"
|
||||
print(row)
|
||||
for nh in self.next_hop[1:]:
|
||||
row = f"{'':<{PadRoute.prefix}}"
|
||||
row += f"{nh:<{PadRoute.next_hop}}"
|
||||
print(row)
|
||||
|
||||
class Software:
|
||||
"""Software bundle class """
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
self.name = data.get('bootname', '')
|
||||
self.size = data.get('size', '')
|
||||
self.type = data.get('class', '')
|
||||
self.hash = data.get('sha256', '')
|
||||
self.state = data.get('state', '')
|
||||
self.version = get_json_data('', self.data, 'bundle', 'version')
|
||||
self.date = get_json_data('', self.data, 'installed', 'datetime')
|
||||
|
||||
def is_rootfs(self):
|
||||
"""True if bundle type is 'rootfs'"""
|
||||
return self.type == "rootfs"
|
||||
|
||||
def print(self):
|
||||
"""Brief information about one bundle"""
|
||||
row = f"{self.name:<{PadSoftware.name}}"
|
||||
row += f"{self.state:<{PadSoftware.state}}"
|
||||
row += f"{self.version:<{PadSoftware.version}}"
|
||||
row += f"{self.date:<{PadSoftware.date}}"
|
||||
print(row)
|
||||
|
||||
def detail(self):
|
||||
"""Detailed information about one bundle"""
|
||||
print(f"Name : {self.name}")
|
||||
print(f"State : {self.state}")
|
||||
print(f"Version : {self.version}")
|
||||
print(f"Size : {self.size}")
|
||||
print(f"SHA-256 : {self.hash}")
|
||||
print(f"Installed : {self.date}")
|
||||
|
||||
class USBport:
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
self.name = data.get('name', '')
|
||||
self.state = get_json_data('', self.data, 'state', 'admin-state')
|
||||
|
||||
def print(self):
|
||||
#print(self.name)
|
||||
row = f"{self.name:<{PadUsbPort.name}}"
|
||||
row += f"{self.state:<{PadUsbPort.state}}"
|
||||
print(row)
|
||||
|
||||
class Iface:
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
self.name = data.get('name', '')
|
||||
self.type = data.get('type', '')
|
||||
self.index = data.get('if-index', '')
|
||||
self.oper_status = data.get('oper-status', '')
|
||||
self.autoneg = get_json_data('unknown', self.data, 'ieee802-ethernet-interface:ethernet',
|
||||
'auto-negotiation', 'enable')
|
||||
self.duplex = get_json_data('', self.data,'ieee802-ethernet-interface:ethernet','duplex')
|
||||
self.speed = get_json_data('', self.data, 'ieee802-ethernet-interface:ethernet', 'speed')
|
||||
self.phys_address = data.get('phys-address', '')
|
||||
|
||||
self.br_vlans = get_json_data({}, self.data, 'infix-interfaces:bridge', 'vlans', "vlan")
|
||||
self.bridge = get_json_data('', self.data, 'infix-interfaces:bridge-port', 'bridge')
|
||||
self.pvid = get_json_data('', self.data, 'infix-interfaces:bridge-port', 'pvid')
|
||||
self.stp_state = get_json_data('', self.data, 'infix-interfaces:bridge-port', 'stp-state')
|
||||
|
||||
if data.get('statistics'):
|
||||
self.in_octets = data.get('statistics').get('in-octets', '')
|
||||
self.out_octets = data.get('statistics').get('out-octets', '')
|
||||
else:
|
||||
self.in_octets = ''
|
||||
self.out_octets = ''
|
||||
|
||||
if self.data.get('ietf-ip:ipv4'):
|
||||
self.mtu = self.data.get('ietf-ip:ipv4').get('mtu', '')
|
||||
self.ipv4_addr = self.data.get('ietf-ip:ipv4').get('address', '')
|
||||
else:
|
||||
self.mtu = ''
|
||||
self.ipv4_addr = []
|
||||
|
||||
if self.data.get('ietf-ip:ipv6'):
|
||||
self.ipv6_addr = self.data.get('ietf-ip:ipv6').get('address', '')
|
||||
else:
|
||||
self.ipv6_addr = []
|
||||
|
||||
|
||||
if self.data.get('infix-interfaces:vlan'):
|
||||
self.lower_if = self.data.get('infix-interfaces:vlan', None).get('lower-layer-if',None)
|
||||
else:
|
||||
self.lower_if = ''
|
||||
|
||||
def is_vlan(self):
|
||||
return self.type == "infix-if-type:vlan"
|
||||
|
||||
def is_bridge(self):
|
||||
return self.type == "infix-if-type:bridge"
|
||||
|
||||
def is_veth(self):
|
||||
return self.data['type'] == "infix-if-type:veth"
|
||||
|
||||
def pr_name(self, pipe=""):
|
||||
print(f"{pipe}{self.name:<{Pad.iface - len(pipe)}}", end="")
|
||||
|
||||
|
||||
def pr_proto_ipv4(self, pipe=''):
|
||||
for addr in self.ipv4_addr:
|
||||
origin = f"({addr['origin']})" if addr.get('origin') else ""
|
||||
|
||||
row = f"{pipe:<{Pad.iface}}"
|
||||
row += f"{'ipv4':<{Pad.proto}}"
|
||||
row += f"{'':<{Pad.state}}{addr['ip']}/{addr['prefix-length']} {origin}"
|
||||
print(row)
|
||||
|
||||
def pr_proto_ipv6(self, pipe=''):
|
||||
for addr in self.ipv6_addr:
|
||||
origin = f"({addr['origin']})" if addr.get('origin') else ""
|
||||
|
||||
row = f"{pipe:<{Pad.iface}}"
|
||||
row += f"{'ipv6':<{Pad.proto}}"
|
||||
row += f"{'':<{Pad.state}}{addr['ip']}/{addr['prefix-length']} {origin}"
|
||||
print(row)
|
||||
|
||||
def pr_proto_eth(self):
|
||||
row = f"{'ethernet':<{Pad.proto}}"
|
||||
dec = Decore.green if self.oper_status == "up" else Decore.red
|
||||
row += dec(f"{self.oper_status.upper():<{Pad.state}}")
|
||||
row += f"{self.phys_address:<{Pad.data}}"
|
||||
print(row)
|
||||
|
||||
def pr_proto_br(self, br_vlans):
|
||||
data_str = ""
|
||||
|
||||
row = f"{'bridge':<{Pad.proto}}"
|
||||
|
||||
if self.oper_status == "up":
|
||||
dec = Decore.green if self.stp_state == "forwarding" else Decore.yellow
|
||||
row += dec(f"{self.stp_state.upper():<{Pad.state}}")
|
||||
else:
|
||||
row += Decore.red(f"{self.oper_status.upper():<{Pad.state}}")
|
||||
|
||||
for vlan in br_vlans:
|
||||
if 'tagged' in vlan:
|
||||
for tagged in vlan['tagged']:
|
||||
if tagged == self.name:
|
||||
if data_str:
|
||||
data_str += f",{vlan['vid']}t"
|
||||
else:
|
||||
data_str += f"vlan:{vlan['vid']}t"
|
||||
if 'untagged' in vlan:
|
||||
for untagged in vlan['untagged']:
|
||||
if untagged == self.name:
|
||||
if data_str:
|
||||
data_str += f",{vlan['vid']}u"
|
||||
else:
|
||||
data_str += f"vlan:{vlan['vid']}u"
|
||||
if self.pvid:
|
||||
data_str += f" pvid:{self.pvid}"
|
||||
|
||||
if data_str:
|
||||
row += f"{data_str:<{Pad.data}}"
|
||||
|
||||
print(row)
|
||||
|
||||
def pr_bridge(self, _ifaces):
|
||||
self.pr_name(pipe="")
|
||||
self.pr_proto_br(self.br_vlans)
|
||||
|
||||
lowers = []
|
||||
for _iface in [Iface(data) for data in _ifaces]:
|
||||
if _iface.bridge and _iface.bridge == self.name:
|
||||
lowers.append(_iface)
|
||||
|
||||
if lowers:
|
||||
self.pr_proto_ipv4(pipe='│')
|
||||
self.pr_proto_ipv6(pipe='│')
|
||||
else:
|
||||
self.pr_proto_ipv4()
|
||||
self.pr_proto_ipv6()
|
||||
|
||||
for i, lower in enumerate(lowers):
|
||||
pipe = '└ ' if (i == len(lowers) -1) else '├ '
|
||||
lower.pr_name(pipe)
|
||||
lower.pr_proto_br(self.br_vlans)
|
||||
|
||||
def pr_veth(self, _ifaces):
|
||||
self.pr_name(pipe="")
|
||||
self.pr_proto_eth()
|
||||
|
||||
if self.lower_if:
|
||||
row = f"{'':<{Pad.iface}}"
|
||||
row += f"{'veth':<{Pad.proto}}"
|
||||
row += f"{'':<{Pad.state}}"
|
||||
row += f"peer:{self.lower_if}"
|
||||
print(row)
|
||||
|
||||
self.pr_proto_ipv4()
|
||||
self.pr_proto_ipv6()
|
||||
|
||||
def pr_vlan(self, _ifaces):
|
||||
self.pr_name(pipe="")
|
||||
self.pr_proto_eth()
|
||||
|
||||
if self.lower_if:
|
||||
self.pr_proto_ipv4(pipe='│')
|
||||
self.pr_proto_ipv6(pipe='│')
|
||||
else:
|
||||
self.pr_proto_ipv4()
|
||||
self.pr_proto_ipv6()
|
||||
return
|
||||
|
||||
parent = find_iface(_ifaces, self.lower_if)
|
||||
if not parent:
|
||||
print(f"Error, didn't find parent interface for vlan {self.name}")
|
||||
sys.exit(1)
|
||||
parent.pr_name(pipe='└ ')
|
||||
parent.pr_proto_eth()
|
||||
|
||||
def pr_iface(self):
|
||||
print(f"{'name':<{20}}: {self.name}")
|
||||
print(f"{'index':<{20}}: {self.index}")
|
||||
if self.mtu:
|
||||
print(f"{'mtu':<{20}}: {self.mtu}")
|
||||
if self.oper_status:
|
||||
print(f"{'operational status':<{20}}: {self.oper_status}")
|
||||
|
||||
if self.lower_if:
|
||||
print(f"{'lower-layer-if':<{20}}: {self.lower_if}")
|
||||
|
||||
if self.autoneg != 'unknown':
|
||||
val = "on" if self.autoneg else "off"
|
||||
print(f"{'auto-negotiation':<{20}}: {val}")
|
||||
|
||||
if self.duplex:
|
||||
print(f"{'duplex':<{20}}: {self.duplex}")
|
||||
|
||||
if self.speed:
|
||||
mbs = float(self.speed) * 1000
|
||||
print(f"{'speed':<{20}}: {int(mbs)}")
|
||||
|
||||
if self.phys_address:
|
||||
print(f"{'physical address':<{20}}: {self.phys_address}")
|
||||
|
||||
if self.ipv4_addr:
|
||||
first = True
|
||||
for addr in self.ipv4_addr:
|
||||
origin = f"({addr['origin']})" if addr.get('origin') else ""
|
||||
key = 'ipv4 addresses' if first else ''
|
||||
colon = ':' if first else ' '
|
||||
row = f"{key:<{20}}{colon} "
|
||||
row += f"{addr['ip']}/{addr['prefix-length']} {origin}"
|
||||
print(row)
|
||||
first = False
|
||||
else:
|
||||
print(f"{'ipv4 addresses':<{20}}:")
|
||||
|
||||
if self.ipv6_addr:
|
||||
first = True
|
||||
for addr in self.ipv6_addr:
|
||||
origin = f"({addr['origin']})" if addr.get('origin') else ""
|
||||
key = 'ipv6 addresses' if first else ''
|
||||
colon = ':' if first else ' '
|
||||
row = f"{key:<{20}}{colon} "
|
||||
row += f"{addr['ip']}/{addr['prefix-length']} {origin}"
|
||||
print(row)
|
||||
first = False
|
||||
else:
|
||||
print(f"{'ipv6 addresses':<{20}}:")
|
||||
|
||||
if self.in_octets and self.out_octets:
|
||||
print(f"{'in-octets':<{20}}: {self.in_octets}")
|
||||
print(f"{'out-octets':<{20}}: {self.out_octets}")
|
||||
|
||||
frame = get_json_data([], self.data,'ieee802-ethernet-interface:ethernet',
|
||||
'statistics', 'frame')
|
||||
if frame:
|
||||
print(f"")
|
||||
for key, val in frame.items():
|
||||
print(f"eth-{key:<{25}}: {val}")
|
||||
|
||||
|
||||
def find_iface(_ifaces, name):
|
||||
for _iface in [Iface(data) for data in _ifaces]:
|
||||
if _iface.name == name:
|
||||
return _iface
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def pr_interface_list(json):
|
||||
hdr = (f"{'INTERFACE':<{Pad.iface}}"
|
||||
f"{'PROTOCOL':<{Pad.proto}}"
|
||||
f"{'STATE':<{Pad.state}}"
|
||||
f"{'DATA':<{Pad.data}}")
|
||||
|
||||
print(Decore.invert(hdr))
|
||||
|
||||
ifaces = sorted(json["ietf-interfaces:interfaces"]["interface"], key=lambda x: x['name'])
|
||||
|
||||
for iface in [Iface(data) for data in ifaces]:
|
||||
if iface.is_bridge():
|
||||
iface.pr_bridge(ifaces)
|
||||
continue
|
||||
|
||||
if iface.is_veth():
|
||||
iface.pr_veth(ifaces)
|
||||
continue
|
||||
|
||||
if iface.is_vlan():
|
||||
iface.pr_vlan(ifaces)
|
||||
continue
|
||||
|
||||
# These interfaces are printed by there parent, such as bridge
|
||||
if iface.lower_if:
|
||||
continue
|
||||
if iface.bridge:
|
||||
continue
|
||||
|
||||
iface.pr_name()
|
||||
iface.pr_proto_eth()
|
||||
iface.pr_proto_ipv4()
|
||||
iface.pr_proto_ipv6()
|
||||
|
||||
def ietf_interfaces(json, name):
|
||||
if name:
|
||||
if not json.get("ietf-interfaces:interfaces"):
|
||||
print(f"No interface data found for \"{name}\"")
|
||||
sys.exit(1)
|
||||
iface = find_iface(json["ietf-interfaces:interfaces"]["interface"], name)
|
||||
if not iface:
|
||||
print(f"Interface \"{name}\" not found")
|
||||
sys.exit(1)
|
||||
iface.pr_iface()
|
||||
else:
|
||||
if not json.get("ietf-interfaces:interfaces"):
|
||||
print(f"Error, top level \"ietf-interfaces:interfaces\" missing")
|
||||
sys.exit(1)
|
||||
pr_interface_list(json)
|
||||
|
||||
def ietf_routing(json, ip="ipv4"):
|
||||
if not json.get("ietf-routing:routing"):
|
||||
print(f"Error, top level \"ietf-routing:routing\" missing")
|
||||
sys.exit(1)
|
||||
hdr = (f"{'PREFIX':<{PadRoute.prefix}}"
|
||||
f"{'NEXT-HOP':<{PadRoute.next_hop}}"
|
||||
f"{'PREF':>{PadRoute.pref}} "
|
||||
f"{'PROTOCOL':<{PadRoute.protocol}}")
|
||||
|
||||
print(Decore.invert(hdr))
|
||||
for rib in get_json_data({}, json, 'ietf-routing:routing','ribs', 'rib'):
|
||||
if rib["name"] != ip:
|
||||
continue;
|
||||
routes = get_json_data(None, rib, "routes", "route")
|
||||
if routes:
|
||||
for r in routes:
|
||||
route = Route(r, ip)
|
||||
route.print()
|
||||
|
||||
def find_slot(_slots, name):
|
||||
for _slot in [Software(data) for data in _slots]:
|
||||
if _slot.name == name:
|
||||
return _slot
|
||||
|
||||
return False
|
||||
|
||||
def infix_system(json, name):
|
||||
if not json.get("ietf-system:system-state", "infix-system:software"):
|
||||
print("Error, cannot find infix-system:software")
|
||||
sys.exit(1)
|
||||
|
||||
slots = get_json_data({}, json, 'ietf-system:system-state', 'infix-system:software', 'slot')
|
||||
if name:
|
||||
slot = find_slot(slots, name)
|
||||
if slot:
|
||||
slot.detail()
|
||||
else:
|
||||
hdr = (f"{'NAME':<{PadSoftware.name}}"
|
||||
f"{'STATE':<{PadSoftware.state}}"
|
||||
f"{'VERSION':<{PadSoftware.version}}"
|
||||
f"{'DATE':<{PadSoftware.date}}")
|
||||
print(Decore.invert(hdr))
|
||||
for _s in slots:
|
||||
slot = Software(_s)
|
||||
if slot.is_rootfs():
|
||||
slot.print()
|
||||
|
||||
def ietf_hardware(json):
|
||||
if not json.get("ietf-hardware:hardware"):
|
||||
print(f"Error, top level \"ietf-hardware:component\" missing")
|
||||
sys.exit(1)
|
||||
|
||||
hdr = (f"{'USB PORTS':<{PadUsbPort.title}}")
|
||||
print(Decore.invert(hdr))
|
||||
hdr = (f"{'NAME':<{PadUsbPort.name}}"
|
||||
f"{'STATE':<{PadUsbPort.state}}")
|
||||
print(Decore.invert(hdr))
|
||||
|
||||
components = get_json_data({}, json, "ietf-hardware:hardware", "component")
|
||||
|
||||
for component in components:
|
||||
if component.get("class") == "infix-hardware:usb":
|
||||
port = USBport(component)
|
||||
port.print()
|
||||
|
||||
def main():
|
||||
try:
|
||||
json_data = json.load(sys.stdin)
|
||||
except json.JSONDecodeError:
|
||||
print("Error, invalid JSON input")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print("Error, unexpected error parsing JSON")
|
||||
sys.exit(1)
|
||||
|
||||
if args.module == "ietf-interfaces":
|
||||
sys.exit(ietf_interfaces(json_data, args.name))
|
||||
if args.module == "ietf-routing":
|
||||
sys.exit(ietf_routing(json_data, args.name))
|
||||
if args.module == "infix-system":
|
||||
sys.exit(infix_system(json_data, args.name))
|
||||
if args.module == "ietf-hardware":
|
||||
sys.exit(ietf_hardware(json_data))
|
||||
else:
|
||||
print(f"Error, unknown module {args.module}")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,76 @@
|
||||
AC_PREREQ(2.61)
|
||||
AC_INIT([statd], [1.0.0], [https://github.com/kernelkit/infix/issues])
|
||||
AM_INIT_AUTOMAKE(1.11 foreign subdir-objects)
|
||||
AM_SILENT_RULES(yes)
|
||||
|
||||
AC_CONFIG_FILES([
|
||||
Makefile
|
||||
])
|
||||
|
||||
AC_PROG_CC
|
||||
AC_PROG_INSTALL
|
||||
|
||||
# Check for pkg-config first, warn if it's not installed
|
||||
PKG_PROG_PKG_CONFIG
|
||||
|
||||
PKG_CHECK_MODULES([jansson], [jansson >= 2.0.0])
|
||||
PKG_CHECK_MODULES([libite], [libite >= 2.5.0])
|
||||
PKG_CHECK_MODULES([libyang], [libyang >= 2.1.80])
|
||||
PKG_CHECK_MODULES([sysrepo], [sysrepo >= 2.2.36])
|
||||
PKG_CHECK_MODULES([libsrx], [libsrx >= 1.0.0])
|
||||
|
||||
AC_CHECK_HEADER([ev.h],
|
||||
[AC_CHECK_LIB([ev], [ev_loop_new],
|
||||
[],
|
||||
[AC_MSG_ERROR("libev not found")] )],
|
||||
[AC_MSG_ERROR("ev.h not found")]
|
||||
)
|
||||
|
||||
test "x$prefix" = xNONE && prefix=$ac_default_prefix
|
||||
test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
|
||||
|
||||
DATAROOTDIR=`eval echo $datarootdir`
|
||||
DATAROOTDIR=`eval echo $DATAROOTDIR`
|
||||
AC_SUBST(DATAROOTDIR)
|
||||
|
||||
LIBDIR=`eval echo $libdir`
|
||||
LIBDIR=`eval echo $LIBDIR`
|
||||
AC_SUBST(LIBDIR)
|
||||
|
||||
LOCALSTATEDIR=`eval echo $localstatedir`
|
||||
LOCALSTATEDIR=`eval echo $LOCALSTATEDIR`
|
||||
AC_SUBST(LOCALSTATEDIR)
|
||||
|
||||
RUNSTATEDIR=`eval echo $runstatedir`
|
||||
RUNSTATEDIR=`eval echo $RUNSTATEDIR`
|
||||
AC_SUBST(RUNSTATEDIR)
|
||||
|
||||
SYSCONFDIR=`eval echo $sysconfdir`
|
||||
SYSCONFDIR=`eval echo $SYSCONFDIR`
|
||||
AC_SUBST(SYSCONFDIR)
|
||||
|
||||
AC_OUTPUT
|
||||
|
||||
cat <<EOF
|
||||
|
||||
------------------ Summary ------------------
|
||||
$PACKAGE_NAME version $PACKAGE_VERSION
|
||||
Prefix................: $prefix
|
||||
Exec prefix...........: $eprefix
|
||||
Sysconfdir............: `eval echo $sysconfdir`
|
||||
Localstatedir.........: `eval echo $localstatedir`
|
||||
Default fstab.........: `eval echo $fstab`
|
||||
System environment....: ${sysconfig_path:-${sysconfig}}
|
||||
C Compiler............: $CC $CFLAGS $CPPFLAGS $LDFLAGS $LIBS
|
||||
Linker................: $LD $LLDP_LDFLAGS $LLDP_BIN_LDFLAGS $LDFLAGS $LIBS
|
||||
|
||||
------------- Compiler version --------------
|
||||
$($CC --version || true)
|
||||
-------------- Linker version ---------------
|
||||
$($LD --version || true)
|
||||
---------------------------------------------
|
||||
|
||||
Check the above options and compile with:
|
||||
${MAKE-make}
|
||||
|
||||
EOF
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/python3
|
||||
# This script is used to transform the output from the show ip ospf commands and order
|
||||
# them to match the ietf-ospf YANG model. For example, interfaces is ordered under
|
||||
# area but FRR has areas in interfaces.
|
||||
#
|
||||
# This makes the parsing for the operational parts of YANG model more easy
|
||||
#
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
|
||||
iface_out=subprocess.check_output("vtysh -c 'show ip ospf interface json'", shell=True)
|
||||
ospf_out=subprocess.check_output("vtysh -c 'show ip ospf json'", shell=True)
|
||||
neighbor_out=subprocess.check_output("vtysh -c 'show ip ospf neighbor detail json'", shell=True)
|
||||
interfaces=json.loads(iface_out)
|
||||
ospf=json.loads(ospf_out)
|
||||
neighbors=json.loads(neighbor_out)
|
||||
|
||||
for ifname,iface in interfaces["interfaces"].items():
|
||||
iface["name"] = ifname
|
||||
iface["neighbors"] = []
|
||||
for area_id in ospf["areas"]:
|
||||
area_type=""
|
||||
|
||||
stub=False
|
||||
if("NSSA" in iface["area"]):
|
||||
iface_area_id = iface["area"][:-7]
|
||||
area_type = "nssa-area"
|
||||
elif("Stub" in iface["area"]):
|
||||
iface_area_id = iface["area"][:-7]
|
||||
iface["areaId"] = iface["area"][:-7]
|
||||
area_type = "stub-area"
|
||||
else:
|
||||
iface_area_id = iface["area"]
|
||||
area_type = "normal-area"
|
||||
|
||||
if(iface_area_id != area_id):
|
||||
continue
|
||||
|
||||
ospf["areas"][area_id]["area-type"] = area_type
|
||||
iface["area"] = iface_area_id
|
||||
|
||||
for nbrAddress,nbrDatas in neighbors["neighbors"].items():
|
||||
for nbrData in nbrDatas:
|
||||
nbrIfname=nbrData["ifaceName"].split(":")[0]
|
||||
if(("NSSA" in nbrData.get("areaId", {})) or ("Stub" in nbrData.get("areaId", {}))):
|
||||
nbrData["areaId"] = nbrData["areaId"][:-7]
|
||||
|
||||
if ((nbrIfname != ifname) and (area_id != nbrData.get("areaId"))):
|
||||
#print(f'Continute {ifname} {nbrData.get("areaId")}')
|
||||
continue
|
||||
nbrData["neighborIp"] = nbrAddress
|
||||
iface["neighbors"].append(nbrData)
|
||||
|
||||
if(not ospf["areas"][area_id].get("interfaces", None)):
|
||||
ospf["areas"][area_id]["interfaces"] = []
|
||||
ospf["areas"][area_id]["interfaces"].append(iface)
|
||||
|
||||
print(json.dumps(ospf))
|
||||
|
||||
+1
-1
@@ -134,7 +134,7 @@ static int ly_add_yanger_data(const struct ly_ctx *ctx, struct lyd_node **parent
|
||||
const char *model, const char *arg)
|
||||
{
|
||||
char *yanger_args[5] = {
|
||||
"/libexec/infix/yanger",
|
||||
"/usr/libexec/statd/yanger",
|
||||
(char *)model,
|
||||
NULL,
|
||||
NULL,
|
||||
|
||||
Executable
+840
@@ -0,0 +1,840 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import subprocess
|
||||
import json
|
||||
import sys # (built-in module)
|
||||
import os
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
|
||||
TESTPATH = ""
|
||||
|
||||
def json_get_yang_type(iface_in):
|
||||
if iface_in['link_type'] == "loopback":
|
||||
return "infix-if-type:loopback"
|
||||
|
||||
if iface_in['link_type'] != "ether":
|
||||
return "infix-if-type:other"
|
||||
|
||||
if 'parentbus' in iface_in and iface_in['parentbus'] == "virtio":
|
||||
return "infix-if-type:etherlike"
|
||||
|
||||
if not 'linkinfo' in iface_in:
|
||||
return "infix-if-type:ethernet"
|
||||
|
||||
if not 'info_kind' in iface_in['linkinfo']:
|
||||
return "infix-if-type:ethernet"
|
||||
|
||||
if iface_in['linkinfo']['info_kind'] == "veth":
|
||||
return "infix-if-type:veth"
|
||||
|
||||
if iface_in['linkinfo']['info_kind'] == "vlan":
|
||||
return "infix-if-type:vlan"
|
||||
|
||||
if iface_in['linkinfo']['info_kind'] == "bridge":
|
||||
return "infix-if-type:bridge"
|
||||
|
||||
if iface_in['linkinfo']['info_kind'] == "dsa":
|
||||
return "infix-if-type:ethernet"
|
||||
|
||||
# Fallback
|
||||
return "infix-if-type:ethernet"
|
||||
|
||||
def json_get_yang_origin(addr):
|
||||
"""Translate kernel IP address origin to YANG"""
|
||||
xlate = {
|
||||
"kernel_ll": "link-layer",
|
||||
"kernel_ra": "link-layer",
|
||||
"static": "static",
|
||||
"dhcp": "dhcp",
|
||||
"random": "random",
|
||||
}
|
||||
proto = addr['protocol']
|
||||
|
||||
if proto in ("kernel_ll", "kernel_ra"):
|
||||
if "stable-privacy" in addr:
|
||||
return "random"
|
||||
|
||||
return xlate.get(proto, "other")
|
||||
|
||||
def getitem(data, key):
|
||||
"""Get sub-object from an object"""
|
||||
while key:
|
||||
data = data[key[0]]
|
||||
key = key[1:]
|
||||
|
||||
return data
|
||||
|
||||
def get_proc_value(procfile):
|
||||
"""Return contents of /proc file, or None"""
|
||||
try:
|
||||
with open(procfile, 'r') as file:
|
||||
data = file.read().strip()
|
||||
return data
|
||||
except FileNotFoundError:
|
||||
# This is considered OK
|
||||
return None
|
||||
except IOError:
|
||||
print(f"Error: reading from {procfile}", file=sys.stderr)
|
||||
|
||||
def lookup(obj, *keys):
|
||||
"""This function returns a value from a nested json object"""
|
||||
curr = obj
|
||||
for key in keys:
|
||||
if isinstance(curr, dict) and key in curr:
|
||||
curr = curr[key]
|
||||
else:
|
||||
return None
|
||||
return curr
|
||||
|
||||
def insert(obj, *path_and_value):
|
||||
""""This function inserts a value into a nested json object"""
|
||||
if len(path_and_value) < 2:
|
||||
raise ValueError("Error: insert() takes at least two args")
|
||||
|
||||
*path, value = path_and_value
|
||||
|
||||
curr = obj
|
||||
for key in path[:-1]:
|
||||
if key not in curr or not isinstance(curr[key], dict):
|
||||
curr[key] = {}
|
||||
curr = curr[key]
|
||||
|
||||
curr[path[-1]] = value
|
||||
|
||||
def run_cmd(cmd, testfile):
|
||||
"""Run a command (array of args) and return an array of lines"""
|
||||
|
||||
if TESTPATH and testfile:
|
||||
cmd = ['cat', os.path.join(TESTPATH, testfile)]
|
||||
|
||||
try:
|
||||
output = subprocess.check_output(cmd, stderr=subprocess.DEVNULL, text=True)
|
||||
return output.splitlines()
|
||||
except subprocess.CalledProcessError:
|
||||
print("Error: command returned error", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def run_json_cmd(cmd, testfile):
|
||||
"""Run a command (array of args) that returns JSON text output and return JSON"""
|
||||
|
||||
if TESTPATH and testfile:
|
||||
cmd = ['cat', os.path.join(TESTPATH, testfile)]
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE, text=True)
|
||||
output = result.stdout
|
||||
data = json.loads(output)
|
||||
except subprocess.CalledProcessError as err:
|
||||
print("Error: unable to get data:", file=sys.stderr)
|
||||
print(f"{err.stderr}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except json.JSONDecodeError as err:
|
||||
print(f"Error: parsing JSON output: {err.msg}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
return data
|
||||
|
||||
def iface_is_dsa(iface_in):
|
||||
"""Check if interface is a DSA/intra-switch port"""
|
||||
if not "linkinfo" in iface_in:
|
||||
return False
|
||||
if not "info_kind" in iface_in['linkinfo']:
|
||||
return False
|
||||
if iface_in['linkinfo']['info_kind'] != "dsa":
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_vpd_vendor_extensions(data):
|
||||
vendor_extensions=[]
|
||||
for ext in data:
|
||||
vendor_extension = {}
|
||||
vendor_extension["iana-enterprise-number"] = ext[0]
|
||||
vendor_extension["extension-data"] = ext[1]
|
||||
vendor_extensions.append(vendor_extension)
|
||||
return vendor_extensions
|
||||
|
||||
def get_vpd_data(vpd):
|
||||
component={}
|
||||
component["name"]=vpd.get("board")
|
||||
component["infix-hardware:vpd-data"] = {}
|
||||
|
||||
if vpd.get("data"):
|
||||
component["class"] = "infix-hardware:vpd"
|
||||
if vpd["data"].get("manufacture-date"):
|
||||
component["mfg-date"]=datetime.strptime(vpd["data"]["manufacture-date"],"%m/%d/%Y %H:%M:%S").strftime("%Y-%m-%-dT%H:%M:%SZ")
|
||||
if vpd["data"].get("manufacter"):
|
||||
component["mfg-name"]=vpd["data"]["manufacturer"]
|
||||
if vpd["data"].get("product-name"):
|
||||
component["model-name"]=vpd["data"]["product-name"]
|
||||
if vpd["data"].get("serial-number"):
|
||||
component["serial-num"]=vpd["data"]["serial-number"]
|
||||
|
||||
# Set VPD-data entrys
|
||||
for k,v in vpd["data"].items():
|
||||
if vpd["data"].get(k):
|
||||
if k != "vendor-extension":
|
||||
component["infix-hardware:vpd-data"][k] = v
|
||||
else:
|
||||
vendor_extensions=get_vpd_vendor_extensions(v)
|
||||
component["infix-hardware:vpd-data"]["infix-hardware:vendor-extension"] = vendor_extensions
|
||||
return component
|
||||
|
||||
def get_usb_ports(usb_ports):
|
||||
ports=[]
|
||||
names=[]
|
||||
for usb_port in usb_ports:
|
||||
port={}
|
||||
if usb_port.get("path"):
|
||||
if usb_port["name"] in names:
|
||||
continue
|
||||
|
||||
path = usb_port["path"]
|
||||
if os.path.basename(path) == "authorized_default":
|
||||
if os.path.exists(path):
|
||||
with open(path, "r") as f:
|
||||
names.append(usb_port["name"])
|
||||
data=int(f.readline().strip())
|
||||
enabled="unlocked" if data == 1 else "locked"
|
||||
port["state"] = {}
|
||||
port["state"]["admin-state"] = enabled
|
||||
port["name"] = usb_port["name"]
|
||||
port["class"] = "infix-hardware:usb"
|
||||
port["state"]["oper-state"] = "enabled"
|
||||
ports.append(port)
|
||||
|
||||
return ports
|
||||
|
||||
|
||||
def add_hardware(hw_out):
|
||||
data = run_json_cmd(['cat', "/run/system.json"], "system.json")
|
||||
components=[]
|
||||
for _,vpd in data.get("vpd").items():
|
||||
component=get_vpd_data(vpd)
|
||||
components.append(component)
|
||||
if data.get("usb-ports", None):
|
||||
components.extend(get_usb_ports(data["usb-ports"]))
|
||||
insert(hw_out, "component", components)
|
||||
|
||||
def get_routes(routes, proto, data):
|
||||
"""Populate routes"""
|
||||
out={}
|
||||
out["route"] = []
|
||||
|
||||
if proto == "ipv4":
|
||||
default = "0.0.0.0/0"
|
||||
host_prefix_length="32"
|
||||
else:
|
||||
default = "::/0"
|
||||
host_prefix_length="128"
|
||||
|
||||
for entry in data:
|
||||
new = {}
|
||||
if entry['dst'] == "default":
|
||||
entry['dst'] = default
|
||||
if entry['dst'].find('/') == -1:
|
||||
entry['dst'] = entry['dst'] + "/" + host_prefix_length
|
||||
new[f'ietf-{proto}-unicast-routing:destination-prefix'] = entry['dst']
|
||||
new['source-protocol'] = "infix-routing:" + entry['protocol']
|
||||
if entry.get("metric"):
|
||||
new['route-preference'] = entry['metric']
|
||||
else:
|
||||
new['route-preference'] = 0
|
||||
|
||||
if entry.get('nexthops'):
|
||||
next_hops = []
|
||||
for hop in entry.get('nexthops'):
|
||||
next_hop = {}
|
||||
if hop.get("dev"):
|
||||
next_hop['outgoing-interface'] = hop['dev']
|
||||
if hop.get("gateway"):
|
||||
next_hop[f'ietf-{proto}-unicast-routing:address'] = hop['gateway']
|
||||
next_hops.append(next_hop)
|
||||
|
||||
insert(new, 'next-hop', 'next-hop-list', 'next-hop', next_hops)
|
||||
else:
|
||||
next_hop = {}
|
||||
if entry['type'] == "blackhole":
|
||||
next_hop['special-next-hop'] = "blackhole"
|
||||
if entry['type'] == "unreachable":
|
||||
next_hop['special-next-hop'] = "unreachable"
|
||||
if entry['type'] == "unicast":
|
||||
if entry.get("dev"):
|
||||
next_hop['outgoing-interface'] = entry['dev']
|
||||
if entry.get("gateway"):
|
||||
next_hop[f'ietf-{proto}-unicast-routing:next-hop-address'] = entry['gateway']
|
||||
|
||||
new['next-hop'] = next_hop
|
||||
|
||||
out['route'].append(new)
|
||||
|
||||
insert(routes, 'routes', out)
|
||||
|
||||
def add_ipv4_route(routes):
|
||||
"""Fetch IPv4 routes from kernel and populate tree"""
|
||||
data = run_json_cmd(['ip', '-4', '-s', '-d', '-j', 'route'], "ip-4-route.json")
|
||||
get_routes(routes, "ipv4", data)
|
||||
|
||||
def add_ipv6_route(routes):
|
||||
"""Fetch IPv6 routes from kernel and populate tree"""
|
||||
data = run_json_cmd(['ip', '-6', '-s', '-d', '-j', 'route'], "ip-6-route.json")
|
||||
get_routes(routes, "ipv6", data)
|
||||
|
||||
def frr_to_ietf_neighbor_state(state):
|
||||
"""Fetch OSPF neighbor state from Frr"""
|
||||
state = state.split("/")[0]
|
||||
if state == "TwoWay":
|
||||
return "2-way"
|
||||
return state.lower()
|
||||
|
||||
def add_ospf_routes(ospf):
|
||||
"""Fetch OSPF routes from Frr"""
|
||||
cmd = ['vtysh', '-c', "show ip ospf rout json"]
|
||||
data = run_json_cmd(cmd, "")
|
||||
routes=[]
|
||||
|
||||
for prefix,info in data.items():
|
||||
if prefix.find("/") == -1: # Ignore router IDs
|
||||
continue
|
||||
|
||||
route={}
|
||||
route["prefix"] = prefix
|
||||
|
||||
nexthops=[]
|
||||
routetype=info["routeType"].split(" ")
|
||||
|
||||
if len(routetype) > 1:
|
||||
if routetype[1] == "E1":
|
||||
route["route-type"] = "external-1"
|
||||
elif routetype[1] == "E2":
|
||||
route["route-type"] = "external-2"
|
||||
elif routetype[1] == "IA":
|
||||
route["route-type"] = "inter-area"
|
||||
elif routetype[0] == "N":
|
||||
route["route-type"] = "intra-area"
|
||||
|
||||
for hop in info["nexthops"]:
|
||||
nexthop={}
|
||||
if hop["ip"] != " ":
|
||||
nexthop["next-hop"] = hop["ip"]
|
||||
else:
|
||||
nexthop["outgoing-interface"] = hop["directlyAttachedTo"]
|
||||
nexthops.append(nexthop)
|
||||
|
||||
route["next-hops"] = {}
|
||||
route["next-hops"]["next-hop"] = nexthops
|
||||
routes.append(route)
|
||||
|
||||
insert(ospf, "ietf-ospf:local-rib", "ietf-ospf:route", routes)
|
||||
|
||||
def add_ospf(ospf):
|
||||
"""Populate OSPF status"""
|
||||
|
||||
cmd = ['/usr/libexec/statd/ospf-status']
|
||||
data = run_json_cmd(cmd, "")
|
||||
|
||||
ospf["ietf-ospf:router-id"] = data["routerId"]
|
||||
ospf["ietf-ospf:address-family"] = "ipv4"
|
||||
areas=[]
|
||||
|
||||
for area_id,values in data["areas"].items():
|
||||
area={}
|
||||
area["ietf-ospf:area-id"] = area_id
|
||||
area["ietf-ospf:interfaces"] = {}
|
||||
if values.get("area-type"):
|
||||
area["ietf-ospf:area-type"] = values["area-type"]
|
||||
interfaces=[]
|
||||
for iface in values.get("interfaces", {}):
|
||||
interface={}
|
||||
interface["ietf-ospf:neighbors"] = {}
|
||||
interface["name"] = iface["name"]
|
||||
|
||||
if iface.get("drId"):
|
||||
interface["dr-router-id"] = iface["drId"]
|
||||
if iface.get("drAddress"):
|
||||
interface["dr-ip-addr"] = iface["drAddress"]
|
||||
if iface.get("bdrId"):
|
||||
interface["bdr-router-id"] = iface["bdrId"]
|
||||
if iface.get("bdrAddress"):
|
||||
interface["bdr-ip-addr"] = iface["bdrAddress"]
|
||||
|
||||
if iface.get("timerPassiveIface"):
|
||||
interface["passive"] = True
|
||||
else:
|
||||
interface["passive"] = False
|
||||
|
||||
interface["enabled"] = iface["ospfEnabled"]
|
||||
if iface["networkType"] == "POINTOPOINT":
|
||||
interface["interface-type"] = "point-to-point"
|
||||
if iface["networkType"] == "BROADCAST":
|
||||
interface["interface-type"] = "broadcast"
|
||||
|
||||
if iface.get("state"):
|
||||
# Wev've never seen "DependUpon", and has no entry in
|
||||
# the YANG model, but is listed before down in Frr
|
||||
xlate = {
|
||||
"DependUpon": "down",
|
||||
"Down": "down",
|
||||
"Waiting": "waiting",
|
||||
"Loopback": "loopback",
|
||||
"Point-To-Point": "point-to-point",
|
||||
"DROther": "dr-other",
|
||||
"Backup": "bdr",
|
||||
"DR": "dr"
|
||||
}
|
||||
val = xlate.get(iface["state"], "unknown")
|
||||
interface["state"] = val
|
||||
|
||||
neighbors = []
|
||||
for neigh in iface["neighbors"]:
|
||||
neighbor={}
|
||||
neighbor["neighbor-router-id"] = neigh["neighborIp"]
|
||||
neighbor["address"] = neigh["ifaceAddress"]
|
||||
neighbor["dr-router-id"] = neigh["routerDesignatedId"]
|
||||
neighbor["bdr-router-id"] = neigh["routerDesignatedBackupId"]
|
||||
neighbor["dead-timer"] = neigh["routerDeadIntervalTimerDueMsec"]
|
||||
neighbor["state"]=frr_to_ietf_neighbor_state(neigh["nbrState"])
|
||||
neighbors.append(neighbor)
|
||||
|
||||
interface["ietf-ospf:neighbors"] = {}
|
||||
interface["ietf-ospf:neighbors"]["ietf-ospf:neighbor"] = neighbors
|
||||
interfaces.append(interface)
|
||||
|
||||
area["ietf-ospf:interfaces"]["ietf-ospf:interface"] = interfaces
|
||||
areas.append(area)
|
||||
|
||||
insert(ospf, "ietf-ospf:areas", "area", areas)
|
||||
add_ospf_routes(ospf)
|
||||
|
||||
def get_bridge_port_pvid(ifname):
|
||||
data = run_json_cmd(['bridge', '-j', 'vlan', 'show', 'dev', ifname],
|
||||
f"bridge-vlan-show-dev-{ifname}.json")
|
||||
if len(data) != 1:
|
||||
return None
|
||||
|
||||
iface = data[0]
|
||||
|
||||
for vlan in iface['vlans']:
|
||||
if 'flags' in vlan and 'PVID' in vlan['flags']:
|
||||
return vlan['vlan']
|
||||
|
||||
return None
|
||||
|
||||
def get_bridge_port_stp_state(ifname):
|
||||
data = run_json_cmd(['bridge', '-j', 'link', 'show', 'dev', ifname],
|
||||
f"bridge-link-show-dev-{ifname}.json")
|
||||
if len(data) != 1:
|
||||
return None
|
||||
|
||||
iface = data[0]
|
||||
|
||||
states = [ 'disabled', 'listening', 'learning', 'forwarding', 'blocking' ]
|
||||
if 'state' in iface and iface['state'] in states:
|
||||
return iface['state']
|
||||
|
||||
return None
|
||||
|
||||
def container_inspect(name, key):
|
||||
"""Call podman inspect {name}, return object at {path} or None"""
|
||||
cmd = ['podman', 'inspect', name]
|
||||
raw = run_json_cmd(cmd, "")
|
||||
|
||||
return getitem(raw[0], key)
|
||||
|
||||
def add_container(containers):
|
||||
"""In container-state we list *all* containers, not just ones manged in configuration."""
|
||||
cmd = ['podman', 'ps', '-a', '--format=json']
|
||||
|
||||
raw = run_json_cmd(cmd, "")
|
||||
for entry in raw:
|
||||
running = entry["State"] == "running"
|
||||
|
||||
container = {
|
||||
"name": entry["Names"][0],
|
||||
"id": entry["Id"],
|
||||
"image": entry["Image"],
|
||||
"image-id": entry["ImageID"],
|
||||
"running": running,
|
||||
"status": entry["Status"]
|
||||
}
|
||||
|
||||
# Bonus information, may not be available
|
||||
if entry["Command"]:
|
||||
container["command"] = " ".join(entry["Command"])
|
||||
|
||||
# The 'podman ps' command lists ports even in host mode, but
|
||||
# that's not applicable, so skip networks and port forwardings
|
||||
networks = container_inspect(container["name"], ("NetworkSettings", "Networks"))
|
||||
if "host" in networks:
|
||||
container["network"] = { "host": True }
|
||||
else:
|
||||
container["network"] = {
|
||||
"interface": [],
|
||||
"publish": []
|
||||
}
|
||||
|
||||
if entry["Networks"]:
|
||||
for net in entry["Networks"]:
|
||||
container["network"]["interface"].append({ "name": net })
|
||||
|
||||
if running and entry["Ports"]:
|
||||
for port in entry["Ports"]:
|
||||
addr = ""
|
||||
if port["host_ip"]:
|
||||
addr = f"{port['host_ip']}:"
|
||||
|
||||
pub = f"{addr}{port['host_port']}->{port['container_port']}/{port['protocol']}"
|
||||
container["network"]["publish"].append(pub)
|
||||
|
||||
containers.append(container)
|
||||
|
||||
def add_ip_link(ifname, iface_out):
|
||||
"""Fetch interface link information from kernel"""
|
||||
data = run_json_cmd(['ip', '-s', '-d', '-j', 'link', 'show', 'dev', ifname],
|
||||
f"ip-link-show-dev-{ifname}.json")
|
||||
if len(data) != 1:
|
||||
print("Error: expected ip link output to be array with length 1", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
iface_in = data[0]
|
||||
|
||||
if 'ifname' in iface_in:
|
||||
iface_out['name'] = iface_in['ifname']
|
||||
|
||||
if 'ifindex' in iface_in:
|
||||
iface_out['if-index'] = iface_in['ifindex']
|
||||
|
||||
if 'address' in iface_in:
|
||||
iface_out['phys-address'] = iface_in['address']
|
||||
|
||||
if 'master' in iface_in:
|
||||
insert(iface_out, "infix-interfaces:bridge-port", "bridge", iface_in['master'])
|
||||
|
||||
pvid = get_bridge_port_pvid(ifname)
|
||||
if pvid is not None:
|
||||
insert(iface_out, "infix-interfaces:bridge-port", "pvid", pvid)
|
||||
|
||||
stp_state = get_bridge_port_stp_state(ifname)
|
||||
if stp_state is not None:
|
||||
insert(iface_out, "infix-interfaces:bridge-port", "stp-state", stp_state)
|
||||
|
||||
if 'link' in iface_in and not iface_is_dsa(iface_in):
|
||||
insert(iface_out, "infix-interfaces:vlan", "lower-layer-if", iface_in['link'])
|
||||
|
||||
if 'operstate' in iface_in:
|
||||
xlate = {
|
||||
"DOWN": "down",
|
||||
"UP": "up",
|
||||
"DORMANT": "dormant",
|
||||
"TESTING": "testing",
|
||||
"LOWERLAYERDOWN": "lower-layer-down",
|
||||
"NOTPRESENT": "not-present"
|
||||
}
|
||||
val = xlate.get(iface_in['operstate'], "unknown")
|
||||
iface_out['oper-status'] = val
|
||||
|
||||
if 'link_type' in iface_in:
|
||||
val = json_get_yang_type(iface_in)
|
||||
iface_out['type'] = val
|
||||
|
||||
val = lookup(iface_in, "stats64", "rx", "bytes")
|
||||
if val is not None:
|
||||
insert(iface_out, "statistics", "out-octets", str(val))
|
||||
|
||||
val = lookup(iface_in, "stats64", "tx", "bytes")
|
||||
if val is not None:
|
||||
insert(iface_out, "statistics", "in-octets", str(val))
|
||||
|
||||
def add_ip_addr(ifname, iface_out):
|
||||
"""Fetch interface address information from kernel"""
|
||||
|
||||
data = run_json_cmd(['ip', '-j', 'addr', 'show', 'dev', ifname],
|
||||
f"ip-addr-show-dev-{ifname}.json")
|
||||
if len(data) != 1:
|
||||
print("Error: expected ip addr output to be array with length 1", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
iface_in = data[0]
|
||||
|
||||
if 'mtu' in iface_in and ifname != "lo":
|
||||
insert(iface_out, "ietf-ip:ipv4", "mtu", iface_in['mtu'])
|
||||
|
||||
# We avoid importing os to check if the file exists (for performance)
|
||||
val = get_proc_value(f"/proc/sys/net/ipv6/conf/{ifname}/mtu")
|
||||
if val is not None:
|
||||
insert(iface_out, "ietf-ip:ipv6", "mtu", int(val))
|
||||
|
||||
if 'addr_info' in iface_in:
|
||||
inet = []
|
||||
inet6 = []
|
||||
|
||||
for addr in iface_in['addr_info']:
|
||||
new = {}
|
||||
|
||||
if not 'family' in addr:
|
||||
print("Error: 'family' missing from 'addr_info'", file=sys.stderr)
|
||||
continue
|
||||
|
||||
if 'local' in addr:
|
||||
new['ip'] = addr['local']
|
||||
if 'prefixlen' in addr:
|
||||
new['prefix-length'] = addr['prefixlen']
|
||||
if 'protocol' in addr:
|
||||
new['origin'] = json_get_yang_origin(addr)
|
||||
|
||||
if addr['family'] == "inet":
|
||||
inet.append(new)
|
||||
elif addr['family'] == "inet6":
|
||||
inet6.append(new)
|
||||
else:
|
||||
print("Error: invalid 'family' in 'addr_info'", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
insert(iface_out, "ietf-ip:ipv4", "address", inet)
|
||||
insert(iface_out, "ietf-ip:ipv6", "address", inet6)
|
||||
|
||||
def add_ethtool_groups(ifname, iface_out):
|
||||
"""Fetch interface counters from kernel"""
|
||||
|
||||
data = run_json_cmd(['ethtool', '--json', '-S', ifname, '--all-groups'],
|
||||
f"ethtool-groups-{ifname}.json")
|
||||
if len(data) != 1:
|
||||
print("Error: expected ethtool groups output to be array with length 1", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
iface_in = data[0]
|
||||
|
||||
# TODO: room for improvement here, the "frame" creation could be more dynamic.
|
||||
if "eth-mac" in iface_in or "rmon" in iface_in:
|
||||
insert(iface_out, "ieee802-ethernet-interface:ethernet", "statistics", "frame", {})
|
||||
frame = iface_out['ieee802-ethernet-interface:ethernet']['statistics']['frame']
|
||||
|
||||
if "eth-mac" in iface_in:
|
||||
mac_in = iface_in['eth-mac']
|
||||
|
||||
if "FramesTransmittedOK" in mac_in:
|
||||
frame['out-frames'] = str(mac_in['FramesTransmittedOK'])
|
||||
if "MulticastFramesXmittedOK" in mac_in:
|
||||
frame['out-multicast-frames'] = str(mac_in['MulticastFramesXmittedOK'])
|
||||
if "BroadcastFramesXmittedOK" in mac_in:
|
||||
frame['out-broadcast-frames'] = str(mac_in['BroadcastFramesXmittedOK'])
|
||||
if "FramesReceivedOK" in mac_in:
|
||||
frame['in-frames'] = str(mac_in['FramesReceivedOK'])
|
||||
if "MulticastFramesReceivedOK" in mac_in:
|
||||
frame['in-multicast-frames'] = str(mac_in['MulticastFramesReceivedOK'])
|
||||
if "BroadcastFramesReceivedOK" in mac_in:
|
||||
frame['in-broadcast-frames'] = str(mac_in['BroadcastFramesReceivedOK'])
|
||||
if "FrameCheckSequenceErrors" in mac_in:
|
||||
frame['in-error-fcs-frames'] = str(mac_in['FrameCheckSequenceErrors'])
|
||||
if "FramesLostDueToIntMACRcvError" in mac_in:
|
||||
frame['in-error-mac-internal-frames'] = str(mac_in['FramesLostDueToIntMACRcvError'])
|
||||
|
||||
tot = 0
|
||||
found = False
|
||||
if "FramesReceivedOK" in mac_in:
|
||||
tot += mac_in['FramesReceivedOK']
|
||||
found = True
|
||||
if "FrameCheckSequenceErrors" in mac_in:
|
||||
tot += mac_in['FrameCheckSequenceErrors']
|
||||
found = True
|
||||
if "FramesLostDueToIntMACRcvError" in mac_in:
|
||||
tot += mac_in['FramesLostDueToIntMACRcvError']
|
||||
found = True
|
||||
if "AlignmentErrors" in mac_in:
|
||||
tot += mac_in['AlignmentErrors']
|
||||
found = True
|
||||
if "etherStatsOversizePkts" in mac_in:
|
||||
tot += mac_in['etherStatsOversizePkts']
|
||||
found = True
|
||||
if "etherStatsJabbers" in mac_in:
|
||||
tot += mac_in['etherStatsJabbers']
|
||||
found = True
|
||||
if found:
|
||||
frame['in-total-frames'] = str(tot)
|
||||
|
||||
if "rmon" in iface_in:
|
||||
rmon_in = iface_in['rmon']
|
||||
|
||||
if "undersize_pkts" in rmon_in:
|
||||
frame['in-error-undersize-frames'] = str(rmon_in['undersize_pkts'])
|
||||
|
||||
tot = 0
|
||||
found = False
|
||||
if "etherStatsJabbers" in rmon_in:
|
||||
tot += rmon_in['etherStatsJabbers']
|
||||
found = True
|
||||
if "etherStatsOversizePkts" in rmon_in:
|
||||
tot += rmon_in['etherStatsOversizePkts']
|
||||
found = True
|
||||
if found:
|
||||
frame['in-error-oversize-frames'] = str(tot)
|
||||
|
||||
def add_ethtool_std(ifname, iface_out):
|
||||
"""Fetch interface speed/duplex/autoneg from kernel"""
|
||||
keys = ['Speed', 'Duplex', 'Auto-negotiation']
|
||||
result = {}
|
||||
|
||||
lines = run_cmd(['ethtool', ifname], f"ethtool-{ifname}.txt")
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
key = line.split(':', 1)[0].strip()
|
||||
if key in keys:
|
||||
key, value = line.split(':', 1)
|
||||
result[key.strip()] = value.strip()
|
||||
|
||||
if "Auto-negotiation" in result:
|
||||
if result['Auto-negotiation'] == "on":
|
||||
insert(iface_out, "ieee802-ethernet-interface:ethernet", "auto-negotiation", "enable", True)
|
||||
else:
|
||||
insert(iface_out, "ieee802-ethernet-interface:ethernet", "auto-negotiation", "enable", False)
|
||||
|
||||
if "Duplex" in result:
|
||||
if result['Duplex'] == "Half":
|
||||
insert(iface_out, "ieee802-ethernet-interface:ethernet", "duplex", "half")
|
||||
elif result['Duplex'] == "Full":
|
||||
insert(iface_out, "ieee802-ethernet-interface:ethernet", "duplex", "full")
|
||||
else:
|
||||
insert(iface_out, "ieee802-ethernet-interface:ethernet", "duplex", "unknown")
|
||||
|
||||
if "Speed" in result and result['Speed'] != "Unknown!":
|
||||
# Avoid importing re (performance)
|
||||
num = ''.join(filter(str.isdigit, result['Speed']))
|
||||
if num:
|
||||
num = round((int(num) / 1000), 3)
|
||||
insert(iface_out, "ieee802-ethernet-interface:ethernet", "speed", str(num))
|
||||
|
||||
# Helper function to add tagged/untagged interfaces to a vlan dict in a list
|
||||
def _add_vlan_iface(vlans, vid, key, val):
|
||||
for d in vlans:
|
||||
if d['vid'] == vid:
|
||||
if key in d:
|
||||
d[key].append(val)
|
||||
else:
|
||||
d[key] = [val]
|
||||
return
|
||||
vlans.append({'vid': vid, key: [val]})
|
||||
|
||||
def add_vlans_to_bridge(brname, iface_out):
|
||||
slaves = [] # Contains all interfaces that has this bridge as 'master'
|
||||
for iface in run_json_cmd(['bridge', '-j', 'link'], "bridge-link.json"):
|
||||
if "master" in iface and iface['master'] == brname:
|
||||
slaves.append(iface['ifname'])
|
||||
|
||||
vlans = [] # Contains all vlans and slaves belonging to this bridge
|
||||
for iface in run_json_cmd(['bridge', '-j', 'vlan'], "bridge-vlan.json"):
|
||||
if iface['ifname'] not in slaves and iface['ifname'] != brname:
|
||||
continue
|
||||
|
||||
for vlan in iface['vlans']:
|
||||
if 'flags' in vlan and 'Egress Untagged' in vlan['flags']:
|
||||
_add_vlan_iface(vlans, vlan['vlan'], 'untagged', iface['ifname'])
|
||||
else:
|
||||
_add_vlan_iface(vlans, vlan['vlan'], 'tagged', iface['ifname'])
|
||||
|
||||
insert(iface_out, "infix-interfaces:bridge", "vlans", "vlan", vlans)
|
||||
|
||||
def main():
|
||||
global TESTPATH
|
||||
|
||||
parser = argparse.ArgumentParser(description="YANG data creator")
|
||||
parser.add_argument("model", help="YANG Model")
|
||||
parser.add_argument("-p", "--param", default=None, help="Model dependant parameter")
|
||||
parser.add_argument("-t", "--test", default=None, help="Test data base path")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.test:
|
||||
TESTPATH = args.test
|
||||
else:
|
||||
TESTPATH = ""
|
||||
|
||||
if args.model == 'ietf-interfaces':
|
||||
# For now, we handle each interface separately, as this is how it's
|
||||
# currently implemented in sysrepo. I.e sysrepo will subscribe to
|
||||
# each individual interface and query it for YANG data.
|
||||
|
||||
if not args.param:
|
||||
print("usage: yanger ietf-interfaces -p INTERFACE", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
yang_data = {
|
||||
"ietf-interfaces:interfaces": {
|
||||
"interface": [{}]
|
||||
}
|
||||
}
|
||||
|
||||
ifname = args.param
|
||||
iface_out = yang_data['ietf-interfaces:interfaces']['interface'][0]
|
||||
|
||||
add_ip_link(ifname, iface_out, )
|
||||
add_ip_addr(ifname, iface_out)
|
||||
|
||||
if 'type' in iface_out and iface_out['type'] == "infix-if-type:ethernet":
|
||||
add_ethtool_groups(ifname, iface_out)
|
||||
add_ethtool_std(ifname, iface_out)
|
||||
|
||||
if 'type' in iface_out and iface_out['type'] == "infix-if-type:bridge":
|
||||
add_vlans_to_bridge(ifname, iface_out)
|
||||
|
||||
elif args.model == 'ietf-routing':
|
||||
yang_data = {
|
||||
"ietf-routing:routing": {
|
||||
"ribs": {
|
||||
"rib": [{
|
||||
"name": "ipv4",
|
||||
"address-family": "ipv4"
|
||||
},
|
||||
{
|
||||
"name": "ipv6",
|
||||
"address-family": "ipv6"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ipv4routes = yang_data['ietf-routing:routing']['ribs']['rib'][0]
|
||||
ipv6routes = yang_data['ietf-routing:routing']['ribs']['rib'][1]
|
||||
add_ipv4_route(ipv4routes)
|
||||
add_ipv6_route(ipv6routes)
|
||||
|
||||
elif args.model == 'ietf-ospf':
|
||||
yang_data = {
|
||||
"ietf-routing:routing": {
|
||||
"control-plane-protocols": {
|
||||
"control-plane-protocol": [
|
||||
{
|
||||
"type": "ietf-ospf:ospfv2",
|
||||
"name": "default",
|
||||
"ietf-ospf:ospf": {
|
||||
"ietf-ospf:areas":
|
||||
{
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
add_ospf(yang_data['ietf-routing:routing']['control-plane-protocols']['control-plane-protocol'][0]["ietf-ospf:ospf"])
|
||||
|
||||
elif args.model == 'ietf-hardware':
|
||||
yang_data = {
|
||||
"ietf-hardware:hardware": {
|
||||
}
|
||||
}
|
||||
add_hardware(yang_data["ietf-hardware:hardware"])
|
||||
|
||||
elif args.model == 'infix-containers':
|
||||
yang_data = {
|
||||
"infix-containers:containers": {
|
||||
"container": []
|
||||
}
|
||||
}
|
||||
add_container(yang_data['infix-containers:containers']['container'])
|
||||
|
||||
else:
|
||||
print(f"Unsupported model {args.model}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(json.dumps(yang_data, indent=2))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user