Add a new packet python-statd

Actually a part of statd, but had to make
a separarate package to get it to work.

Statd helper-scripts are now pre-compiled instead of doing it
in runtime.

Fixes #379
This commit is contained in:
Mattias Walström
2024-04-16 12:47:16 +02:00
parent 635acd504f
commit 8c9c432847
20 changed files with 157 additions and 69 deletions
+27
View File
@@ -0,0 +1,27 @@
Copyright (c) 2023 The KernelKit Authors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of copyright holders nor the names of
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+4
View File
@@ -0,0 +1,4 @@
from .cli_pretty import main
if __name__ == "__main__":
main()
+624
View File
@@ -0,0 +1,624 @@
#!/usr/bin/env python3
import json
import argparse
import sys
import re
class Pad:
iface = 16
proto = 11
state = 12
data = 41
class PadMdb:
bridge = 7
vlan = 6
group = 20
ports = 20
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")
@staticmethod
def underline(txt):
return Decore.decorate("4", txt, "24")
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
def remove_yang_prefix(key):
parts = key.split(":", 1)
if len(parts) > 1:
return parts[1]
return key
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_mdb = get_json_data({}, self.data, 'infix-interfaces:bridge', 'multicast-filters')
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 oper(self, detail=False):
"""Remap in brief overview to fit column widths."""
if not detail and self.oper_status == "lower-layer-down":
return "lower-down"
return self.oper_status
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, pipe=''):
row = ""
if len(pipe) > 0:
row = f"{pipe:<{Pad.iface}}"
row += f"{'ethernet':<{Pad.proto}}"
dec = Decore.green if self.oper() == "up" else Decore.red
row += dec(f"{self.oper().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() == "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().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_eth(pipe='')
self.pr_proto_ipv4(pipe='')
self.pr_proto_ipv6(pipe='')
else:
self.pr_proto_eth(pipe=' ')
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():
print(f"{'operational status':<{20}}: {self.oper(detail=True)}")
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("")
for key, val in frame.items():
key = remove_yang_prefix(key)
print(f"eth-{key:<{25}}: {val}")
def pr_mdb(self, bridge):
for group in self.br_mdb.get("multicast-filter", {}):
row = f"{bridge:<{PadMdb.bridge}}"
row += f"{'':<{PadMdb.vlan}}"
row += f"{group['group']:<{PadMdb.group}}"
if (group.get("ports")):
ports = ", ".join(port_dict["port"] for port_dict in group["ports"])
else:
ports = ""
row += f"{ports}"
print(row)
def pr_vlans_mdb(self, bridge):
for vlan in self.br_vlans:
filters=vlan.get("multicast-filters", {})
for group in filters.get("multicast-filter", []):
row = f"{bridge:<{PadMdb.bridge}}"
row += f"{vlan['vid']:<{PadMdb.vlan}}"
row += f"{group['group']:<{PadMdb.group}}"
if (group.get("ports")):
ports = ", ".join(port_dict["port"] for port_dict in group["ports"])
else:
ports = ""
row += f"{ports}"
print(row)
def find_iface(_ifaces, name):
for _iface in [Iface(data) for data in _ifaces]:
if _iface.name == name:
return _iface
return False
def version_sort(iface):
return [int(x) if x.isdigit() else x for x in re.split(r'(\d+)', iface['name'])]
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=version_sort)
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 show_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)
else:
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 show_bridge_mdb(json):
header_printed = False
if not json.get("ietf-interfaces:interfaces"):
print(f"Error, top level \"ietf-interfaces:interface\" missing")
sys.exit(1)
ifaces = sorted(json["ietf-interfaces:interfaces"]["interface"], key=version_sort)
for iface in [Iface(data) for data in ifaces]:
if iface.type != "infix-if-type:bridge":
continue
if not header_printed:
hdr = (f"{'BRIDGE':<{PadMdb.bridge}}"
f"{'VID':<{PadMdb.vlan}}"
f"{'GROUP':<{PadMdb.group}}"
f"{'PORTS':<{PadMdb.ports}}")
print(Decore.invert(hdr))
header_printed = True
iface.pr_mdb(iface.name)
iface.pr_vlans_mdb(iface.name)
def show_routing_table(json, ip):
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 show_software(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 show_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)
parser = argparse.ArgumentParser(description="JSON CLI Pretty Printer")
subparsers = parser.add_subparsers(dest='command', help='Commands')
parser_show_routing_table = subparsers.add_parser('show-routing-table', help='Show the routing table')
parser_show_routing_table.add_argument('-i', '--ip', required=True, help='IPv4 or IPv6 address')
parser_show_interfaces = subparsers.add_parser('show-interfaces', help='Show interfaces')
parser_show_interfaces.add_argument('-n', '--name', help='Interface name')
parser_show_bridge_mdb = subparsers.add_parser('show-bridge-mdb', help='Show bridge MDB')
parser_show_software = subparsers.add_parser('show-software', help='Show software versions')
parser_show_software.add_argument('-n', '--name', help='Slotname')
parser_show_routing_table = subparsers.add_parser('show-hardware', help='Show USB ports')
args = parser.parse_args()
if args.command == "show-interfaces":
show_interfaces(json_data, args.name)
elif args.command == "show-routing-table":
show_routing_table(json_data, args.ip)
elif args.command == "show-software":
show_software(json_data, args.name)
elif args.command == "show-bridge-mdb":
show_bridge_mdb(json_data)
elif args.command == "show-hardware":
show_hardware(json_data)
else:
print(f"Error, unknown command {args.command}")
sys.exit(1)
if __name__ == "__main__":
main()
+4
View File
@@ -0,0 +1,4 @@
from .ospf_status import main
if __name__ == "__main__":
main()
+63
View File
@@ -0,0 +1,63 @@
#!/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
def main():
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))
if __name__ == "__main__":
main()
+23
View File
@@ -0,0 +1,23 @@
[tool.poetry]
name = "infix-yang-tools"
version = "1.0"
description = "Linux to infix-YANG"
license = "MIT"
packages = [
{ include = "yanger/*.py" },
{ include = "cli_pretty/*.py" },
{ include = "ospf_status/*.py" }
]
authors = [
"KernelKit developers"
]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts]
yanger = "yanger:main"
cli-pretty = "cli_pretty:main"
ospf-status = "ospf_status:main"
+4
View File
@@ -0,0 +1,4 @@
from .yanger import main
if __name__ == "__main__":
main()
+896
View File
@@ -0,0 +1,896 @@
#!/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:
data = {}
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 get_brport_multicast(ifname):
data=run_json_cmd(['mctl', 'show', 'igmp', 'json'], "bridge-mdb.json")
multicast={}
if ifname in data.get('fast-leave-ports', []):
multicast["fast-leave"] = True
else:
multicast["fast-leave"] = False
if ifname in data.get('multicast-router-ports', []):
multicast["router"] = "permanent";
else:
multicast["router"] = "auto";
return multicast
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)
multicast = get_brport_multicast(ifname)
insert(iface_out, "infix-interfaces:bridge-port", "multicast", multicast)
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'])
if "OctetsTransmittedOK" in mac_in:
frame['infix-ethernet-interface:out-good-octets'] = str(mac_in['OctetsTransmittedOK'])
if "OctetsReceivedOK" in mac_in:
frame['infix-ethernet-interface:in-good-octets'] = str(mac_in['OctetsReceivedOK'])
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))
def get_querier_data(querier_data):
multicast={}
if not querier_data:
multicast["snooping"] = False
return multicast
multicast["snooping"] = True
if(querier_data.get("query-interval")):
multicast["query-interval"] = querier_data["query-interval"]
return multicast
def get_multicast_filters(filters):
multicast_filters=[]
for f in filters:
multicast_filter={}
multicast_filter["group"] = f["group"]
multicast_filter["ports"] = []
for p in f["ports"]:
port={}
port["port"] = p
multicast_filter["ports"].append(port)
multicast_filters.append(multicast_filter)
return multicast_filters
def add_mdb_to_bridge(brname, iface_out, mc_status):
filters = [entry for entry in mc_status.get("multicast-groups", []) if entry.get('vid') == None and entry.get('bridge') == brname]
querier = next((querier for querier in mc_status.get('multicast-queriers', []) if (querier.get("interface", "") == brname) and (querier.get("vid") is None)), None)
multicast = get_querier_data(querier)
multicast_filters = get_multicast_filters(filters)
insert(iface_out, "infix-interfaces:bridge", "multicast", multicast)
insert(iface_out, "infix-interfaces:bridge", "multicast-filters", "multicast-filter", multicast_filters)
# Helper function to add tagged/untagged interfaces to a vlan dict in a list
def _add_vlan_iface(vlans, multicast_filter, multicast, 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, "multicast-filters": {"multicast-filter": multicast_filter}, "multicast": multicast, key: [val]})
def add_vlans_to_bridge(brname, iface_out, mc_status):
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']:
querier = next((querier for querier in mc_status.get('multicast-queriers', []) if (querier.get("vid") == vlan['vlan'])), None)
filters = [entry for entry in mc_status.get("multicast-groups", []) if (entry.get("vid") == vlan["vlan"])]
if 'flags' in vlan and 'Egress Untagged' in vlan['flags']:
_add_vlan_iface(vlans, get_multicast_filters(filters), get_querier_data(querier), vlan['vlan'], 'untagged', iface['ifname'])
else:
_add_vlan_iface(vlans, get_multicast_filters(filters), get_querier_data(querier), 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":
mc_status = run_json_cmd(['mctl', '-p', 'show', 'igmp', 'json'], "igmp-status.json")
add_vlans_to_bridge(ifname, iface_out, mc_status)
add_mdb_to_bridge(ifname, iface_out, mc_status)
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()