yanger: Move ietf-{interfaces,routing} implementations to separate modules

Should this have been two commits: yes.

Should we spend time on splitting them after the fact because yours
truly dropped the ball: no.
This commit is contained in:
Tobias Waldekranz
2025-01-10 10:12:22 +01:00
parent a0e4e813dd
commit 2ec226640e
37 changed files with 723 additions and 751 deletions
@@ -0,0 +1,564 @@
from ..common import insert, lookup, LOG
from ..host import HOST
def json_get_yang_type(iface_in):
if iface_in['link_type'] == "loopback":
return "infix-if-type:loopback"
if iface_in['link_type'] in ("gre", "gre6"):
return "infix-if-type:gre"
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 'linkinfo' not in iface_in:
return "infix-if-type:ethernet"
if 'info_kind' not 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'] in ("gretap", "ip6gretap"):
return "infix-if-type:gretap"
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"
if iface_in['linkinfo']['info_kind'] == "dummy":
return "infix-if-type:dummy"
# 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 iface_is_dsa(iface_in):
"""Check if interface is a DSA/intra-switch port"""
if "linkinfo" not in iface_in:
return False
if "info_kind" not in iface_in['linkinfo']:
return False
if iface_in['linkinfo']['info_kind'] != "dsa":
return False
return True
def get_bridge_port_pvid(ifname):
data = HOST.run_json(['bridge', '-j', 'vlan', 'show', 'dev', ifname])
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 = HOST.run_json(['bridge', '-j', 'link', 'show', 'dev', ifname])
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 get_brport_multicast(ifname):
"""Check if multicast snooping is enabled on bridge, default: nope"""
data = HOST.run_json(['mctl', '-p', 'show', 'igmp', 'json'], default={})
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
# We always get all interfaces for two reasons.
# 1) To increase speed on large iron with many ports.
# 2) To simplify testing (single dummy file ip-link-show.json).
def get_ip_link():
"""Fetch interface link information from kernel"""
return HOST.run_json(['ip', '-s', '-d', '-j', 'link', 'show'])
def netns_get_ip_link(netns):
"""Fetch interface link information from within a network namespace"""
return HOST.run_json(['ip', 'netns', 'exec', netns, 'ip', '-s', '-d', '-j', 'link', 'show'])
def get_ip_addr():
"""Fetch interface address information from kernel"""
return HOST.run_json(['ip', '-j', 'addr', 'show'])
def netns_get_ip_addr(netns):
"""Fetch interface address information from within a network namespace"""
return HOST.run_json(['ip', 'netns', 'exec', netns, 'ip', '-j', 'addr', 'show'])
def get_netns_list():
"""Fetch a list of network namespaces"""
return HOST.run_json(['ip', '-j', 'netns', 'list'], [])
def netns_find_ifname(ifname):
"""Find which network namespace owns ifname (if any)"""
for netns in get_netns_list():
for iface in netns_get_ip_link(netns['name']):
if 'ifalias' in iface and iface['ifalias'] == ifname:
return netns['name']
return None
def netns_ifindex_to_ifname(ifindex):
"""Look through all network namespaces for an interface index and return its name"""
for netns in get_netns_list():
for iface in netns_get_ip_link(netns['name']):
if iface['ifindex'] == ifindex:
if 'ifalias' in iface:
return iface['ifalias']
if 'ifname' in iface:
return iface['ifname']
return None
return None
def add_bridge_port_common(ifname, iface_in, iface_out):
li = iface_in.get("linkinfo", {})
if not (li.get("info_slave_kind") == "bridge" or \
li.get("info_kind") == "bridge"):
return
pvid = get_bridge_port_pvid(ifname)
if pvid is not None:
insert(iface_out, "infix-interfaces:bridge-port", "pvid", pvid)
def add_bridge_port_lower(ifname, iface_in, iface_out):
li = iface_in.get("linkinfo", {})
if not li.get("info_slave_kind") == "bridge":
return
insert(iface_out, "infix-interfaces:bridge-port", "bridge", iface_in['master'])
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)
def add_gre(iface_in, iface_out):
if 'link_type' in iface_in:
val = json_get_yang_type(iface_in)
if val != "infix-if-type:gre" and val != "infix-if-type:gretap":
return;
gre={}
info_data=iface_in.get("linkinfo", {}).get("info_data", {})
gre["local"] = info_data.get("local")
gre["remote"] = info_data.get("remote")
insert(iface_out, "infix-interfaces:gre", gre)
def add_ip_link(ifname, iface_in, iface_out):
if 'ifname' in iface_in:
iface_out['name'] = ifname
if 'ifindex' in iface_in:
iface_out['if-index'] = iface_in['ifindex']
if 'ifalias' in iface_in:
iface_out['description'] = iface_in['ifalias']
if 'address' in iface_in and not "POINTOPOINT" in iface_in["flags"]:
iface_out['phys-address'] = iface_in['address']
add_bridge_port_common(ifname, iface_in, iface_out)
add_bridge_port_lower(ifname, iface_in, iface_out)
if not iface_is_dsa(iface_in):
if iface_in.get('link'):
insert(iface_out, "infix-interfaces:vlan", "lower-layer-if", iface_in['link'])
elif 'link_index' in iface_in:
# 'link_index' is the only reference we have if the link iface is in a namespace
lower = netns_ifindex_to_ifname(iface_in['link_index'])
if lower:
insert(iface_out, "infix-interfaces:vlan", "lower-layer-if", lower)
if 'flags' in iface_in:
iface_out['admin-status'] = "up" if "UP" in iface_in['flags'] else "down"
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
add_gre(iface_in, iface_out)
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_in, iface_out):
if 'mtu' in iface_in and ifname != "lo":
insert(iface_out, "ietf-ip:ipv4", "mtu", iface_in['mtu'])
val = HOST.read(f"/proc/sys/net/ipv6/conf/{ifname}/mtu")
if val is not None:
insert(iface_out, "ietf-ip:ipv6", "mtu", int(val.strip()))
if 'addr_info' in iface_in:
inet = []
inet6 = []
for addr in iface_in['addr_info']:
new = {}
if 'family' not in addr:
LOG.error("'family' missing from 'addr_info'")
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:
LOG.error("invalid 'family' in 'addr_info'")
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 (need new JSON format!)"""
cmd = ['ethtool', '--json', '-S', ifname, '--all-groups']
try:
data = HOST.run_json(cmd)
if len(data) != 1:
LOG.warning("%s: no counters available, skipping.", ifname)
return
except subprocess.CalledProcessError:
# Allow comand to fail, not all NICs support --json yet
return
iface_in = data[0]
# TODO: room for improvement, 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 = HOST.run_multiline(['ethtool', ifname])
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)
def add_container_ifaces(yang_ifaces):
"""Add all podman interfaces with limited data"""
interfaces={}
try:
containers = HOST.run_json(['podman', 'ps', '-a', '--format=json'], default=[])
except Exception as e:
logging.error(f"Error, unable to run podman: {e}")
return
for container in containers:
name = container.get('Names', ['Unknown'])[0]
networks = container.get('Networks', [])
for network in networks:
if not network in interfaces:
interfaces[network] = []
if name not in interfaces[network]:
interfaces[network].append(name)
for ifname, containers in interfaces.items():
iface_out = {}
iface_out['name'] = ifname
iface_out['type'] = "infix-if-type:other" # Fallback
insert(iface_out, "infix-interfaces:container-network", "containers", containers)
netns = netns_find_ifname(ifname)
if netns is not None:
ip_link_data = netns_get_ip_link(netns)
ip_link_data = next((d for d in ip_link_data if d.get('ifalias') == ifname), None)
add_ip_link(ifname, ip_link_data, iface_out)
yang_ifaces.append(iface_out)
# 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 HOST.run_json(['bridge', '-j', 'link']):
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 HOST.run_json(['bridge', '-j', 'vlan']):
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 get_iface_data(ifname, ip_link_data, ip_addr_data):
iface_out = {}
add_ip_link(ifname, ip_link_data, iface_out)
add_ip_addr(ifname, ip_addr_data, 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":
# Fail silent, multicast snooping may not be enabled on bridge
mc_status = HOST.run_json(['mctl', '-p', 'show', 'igmp', 'json'], default={})
add_vlans_to_bridge(ifname, iface_out, mc_status)
add_mdb_to_bridge(ifname, iface_out, mc_status)
return iface_out
def _add_interface(ifname, ip_link_data, ip_addr_data, yang_ifaces):
# We expect both ip addr and link data to exist.
if not ip_link_data or not ip_addr_data:
return
# Skip internal interfaces.
if 'group' in ip_link_data and ip_link_data['group'] == "internal":
return
yang_ifaces.append(get_iface_data(ifname, ip_link_data, ip_addr_data))
def operational(ifname=None):
out = {
"ietf-interfaces:interfaces": {
"interface": []
}
}
out_ifaces = out["ietf-interfaces:interfaces"]["interface"]
ip_link_data = get_ip_link()
ip_addr_data = get_ip_addr()
if ifname:
ip_link_data = next((d for d in ip_link_data if d.get('ifname') == ifname), None)
ip_addr_data = next((d for d in ip_addr_data if d.get('ifname') == ifname), None)
_add_interface(ifname, ip_link_data, ip_addr_data, out_ifaces)
else:
for link in ip_link_data:
addr = next((d for d in ip_addr_data if d.get('ifname') == link["ifname"]), None)
_add_interface(link["ifname"], link, addr, out_ifaces)
add_container_ifaces(out_ifaces)
return out
+149
View File
@@ -0,0 +1,149 @@
from datetime import timedelta
from re import match
from .common import insert
from .host import HOST
def uptime2datetime(uptime):
"""
Convert uptime to YANG format (YYYY-MM-DDTHH:MM:SS+00:00)
Handles the following input formats (frrtime):
HH:MM:SS
XdXXhXXm
XXwXdXXh
"""
h = m = s = 0
# Format HH:MM:SS
if match(r'^\d{2}:\d{2}:\d{2}$', uptime):
h, m, s = map(int, uptime.split(':'))
# Format XdXXhXXm (days, hours, minutes)
elif match(r'^\d+d\d{2}h\d{2}m$', uptime):
days = int(uptime.split('d')[0])
h = int(uptime.split('d')[1].split('h')[0])
m = int(uptime.split('h')[1].split('m')[0])
h += days * 24
# Format XwXdXXh (weeks, days, hours)
elif match(r'^\d{2}w\d{1}d\d{2}h$', uptime):
weeks = int(uptime.split('w')[0])
days = int(uptime.split('w')[1].split('d')[0])
h = int(uptime.split('d')[1].split('h')[0])
h += weeks * 7 * 24
h += days * 24
uptime_delta = timedelta(hours=h, minutes=m, seconds=s)
current_time = HOST.now()
last_updated = current_time - uptime_delta
date_timestd = last_updated.strftime('%Y-%m-%dT%H:%M:%S%z')
return date_timestd[:-2] + ':' + date_timestd[-2:]
def add_protocol(routes, proto):
"""Populate routes from vtysh JSON output"""
frrproto = "ip" if proto == "ipv4" else proto
data = HOST.run_json(['vtysh', '-c', f"show {frrproto} route json"], {})
# Mapping of FRR protocol names to IETF routing-protocol
pmap = {
'kernel': 'infix-routing:kernel',
'connected': 'direct',
'static': 'static',
'ospf': 'ietf-ospf:ospfv2',
'ospf6': 'ietf-ospf:ospfv3',
}
out = {}
out["route"] = []
if proto == "ipv4":
default = "0.0.0.0/0"
host_prefix_length = "32"
else:
default = "::/0"
host_prefix_length = "128"
for prefix, entries in data.items():
for route in entries:
new = {}
dst = route.get('prefix', default)
if '/' not in dst:
dst = f"{dst}/{route.get('prefixLen', host_prefix_length)}"
new[f'ietf-{proto}-unicast-routing:destination-prefix'] = dst
frr = route.get('protocol', 'infix-routing:kernel')
new['source-protocol'] = pmap.get(frr, 'infix-routing:kernel')
new['route-preference'] = route.get('distance', 0)
# Metric only available in the model for OSPF routes
if 'ospf' in frr:
new['ietf-ospf:metric'] = route.get('metric', 0)
# See https://datatracker.ietf.org/doc/html/rfc7951#section-6.9
# for details on how presence leaves are encoded in JSON: [null]
if route.get('selected', False):
new['active'] = [None]
new['last-updated'] = uptime2datetime(route.get('uptime', 0))
installed = route.get('installed', False)
next_hops = []
for hop in route.get('nexthops', []):
next_hop = {}
if hop.get('ip'):
next_hop[f'ietf-{proto}-unicast-routing:address'] = hop['ip']
elif hop.get('interfaceName'):
next_hop['outgoing-interface'] = hop['interfaceName']
# See zebra/zebra_vty.c:re_status_outpupt_char()
if installed and hop.get('fib', False):
next_hop['infix-routing:installed'] = [None]
next_hops.append(next_hop)
if next_hops:
new['next-hop'] = {'next-hop-list': {'next-hop': next_hops}}
else:
next_hop = {}
protocol = route.get('protocol', 'unicast')
if protocol == "blackhole":
next_hop['special-next-hop'] = "blackhole"
elif protocol == "unreachable":
next_hop['special-next-hop'] = "unreachable"
else:
if route.get('interfaceName'):
next_hop['outgoing-interface'] = route['interfaceName']
if route.get('nexthop'):
next_hop[f'ietf-{proto}-unicast-routing:next-hop-address'] = route['nexthop']
new['next-hop'] = next_hop
out['route'].append(new)
insert(routes, 'routes', out)
def operational():
out = {
"ietf-routing:routing": {
"ribs": {
"rib": [{
"name": "ipv4",
"address-family": "ipv4"
}, {
"name": "ipv6",
"address-family": "ipv6"
}]
}
}
}
ipv4routes = out['ietf-routing:routing']['ribs']['rib'][0]
ipv6routes = out['ietf-routing:routing']['ribs']['rib'][1]
add_protocol(ipv4routes, "ipv4")
add_protocol(ipv6routes, "ipv6")
return out
+10 -751
View File
@@ -6,7 +6,6 @@ import json
import sys # (built-in module)
import os
import argparse
from re import match
from datetime import datetime, timedelta, timezone
from . import common
@@ -20,29 +19,6 @@ def datetime_now():
return datetime(2023, 1, 1, 12, 0, 0, tzinfo=timezone.utc)
return datetime.now(timezone.utc)
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:
logger.error(f"failed reading from {procfile}")
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"""
@@ -72,7 +48,7 @@ def run_cmd(cmd, testfile, default=None, check=True):
output = result.stdout
return output.splitlines()
except subprocess.CalledProcessError as err:
logger.error(f"{err}")
common.LOG.error(f"{err}")
if default is not None:
return default
raise
@@ -90,713 +66,19 @@ def run_json_cmd(cmd, testfile, default=None, check=True):
output = result.stdout
data = json.loads(output)
except subprocess.CalledProcessError as err:
logger.error(f"{err}")
common.LOG.error(f"{err}")
if default is not None:
return default
raise
except json.JSONDecodeError as err:
if check is True:
logger.error("failed parsing JSON output of command: "
common.LOG.error("failed parsing JSON output of command: "
f"{' '.join(cmd)}, error: {err}")
if default is not None:
return default
raise
return data
def json_get_yang_type(iface_in):
if iface_in['link_type'] == "loopback":
return "infix-if-type:loopback"
if iface_in['link_type'] in ("gre", "gre6"):
return "infix-if-type:gre"
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 'linkinfo' not in iface_in:
return "infix-if-type:ethernet"
if 'info_kind' not 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'] in ("gretap", "ip6gretap"):
return "infix-if-type:gretap"
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"
if iface_in['linkinfo']['info_kind'] == "dummy":
return "infix-if-type:dummy"
# 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 iface_is_dsa(iface_in):
"""Check if interface is a DSA/intra-switch port"""
if "linkinfo" not in iface_in:
return False
if "info_kind" not in iface_in['linkinfo']:
return False
if iface_in['linkinfo']['info_kind'] != "dsa":
return False
return True
def uptime2datetime(uptime):
"""
Convert uptime to YANG format (YYYY-MM-DDTHH:MM:SS+00:00)
Handles the following input formats (frrtime):
HH:MM:SS
XdXXhXXm
XXwXdXXh
"""
h = m = s = 0
# Format HH:MM:SS
if match(r'^\d{2}:\d{2}:\d{2}$', uptime):
h, m, s = map(int, uptime.split(':'))
# Format XdXXhXXm (days, hours, minutes)
elif match(r'^\d+d\d{2}h\d{2}m$', uptime):
days = int(uptime.split('d')[0])
h = int(uptime.split('d')[1].split('h')[0])
m = int(uptime.split('h')[1].split('m')[0])
h += days * 24
# Format XwXdXXh (weeks, days, hours)
elif match(r'^\d{2}w\d{1}d\d{2}h$', uptime):
weeks = int(uptime.split('w')[0])
days = int(uptime.split('w')[1].split('d')[0])
h = int(uptime.split('d')[1].split('h')[0])
h += weeks * 7 * 24
h += days * 24
uptime_delta = timedelta(hours=h, minutes=m, seconds=s)
current_time = datetime_now()
last_updated = current_time - uptime_delta
date_timestd = last_updated.strftime('%Y-%m-%dT%H:%M:%S%z')
return date_timestd[:-2] + ':' + date_timestd[-2:]
def get_routes(routes, proto, data):
"""Populate routes from vtysh JSON output"""
# Mapping of FRR protocol names to IETF routing-protocol
pmap = {
'kernel': 'infix-routing:kernel',
'connected': 'direct',
'static': 'static',
'ospf': 'ietf-ospf:ospfv2',
'ospf6': 'ietf-ospf:ospfv3',
}
out = {}
out["route"] = []
if proto == "ipv4":
default = "0.0.0.0/0"
host_prefix_length = "32"
else:
default = "::/0"
host_prefix_length = "128"
for prefix, entries in data.items():
for route in entries:
new = {}
dst = route.get('prefix', default)
if '/' not in dst:
dst = f"{dst}/{route.get('prefixLen', host_prefix_length)}"
new[f'ietf-{proto}-unicast-routing:destination-prefix'] = dst
frr = route.get('protocol', 'infix-routing:kernel')
new['source-protocol'] = pmap.get(frr, 'infix-routing:kernel')
new['route-preference'] = route.get('distance', 0)
# Metric only available in the model for OSPF routes
if 'ospf' in frr:
new['ietf-ospf:metric'] = route.get('metric', 0)
# See https://datatracker.ietf.org/doc/html/rfc7951#section-6.9
# for details on how presence leaves are encoded in JSON: [null]
if route.get('selected', False):
new['active'] = [None]
new['last-updated'] = uptime2datetime(route.get('uptime', 0))
installed = route.get('installed', False)
next_hops = []
for hop in route.get('nexthops', []):
next_hop = {}
if hop.get('ip'):
next_hop[f'ietf-{proto}-unicast-routing:address'] = hop['ip']
elif hop.get('interfaceName'):
next_hop['outgoing-interface'] = hop['interfaceName']
# See zebra/zebra_vty.c:re_status_outpupt_char()
if installed and hop.get('fib', False):
next_hop['infix-routing:installed'] = [None]
next_hops.append(next_hop)
if next_hops:
new['next-hop'] = {'next-hop-list': {'next-hop': next_hops}}
else:
next_hop = {}
protocol = route.get('protocol', 'unicast')
if protocol == "blackhole":
next_hop['special-next-hop'] = "blackhole"
elif protocol == "unreachable":
next_hop['special-next-hop'] = "unreachable"
else:
if route.get('interfaceName'):
next_hop['outgoing-interface'] = route['interfaceName']
if route.get('nexthop'):
next_hop[f'ietf-{proto}-unicast-routing:next-hop-address'] = route['nexthop']
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(['sudo', 'vtysh', '-c', "show ip route json"],
"vtysh-ip4-route.json", check=False, default={})
get_routes(routes, "ipv4", data)
def add_ipv6_route(routes):
"""Fetch IPv6 routes from kernel and populate tree"""
data = run_json_cmd(['sudo', 'vtysh', '-c', "show ipv6 route json"],
"vtysh-ip6-route.json", check=False, default={})
get_routes(routes, "ipv6", data)
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 get_brport_multicast(ifname):
"""Check if multicast snooping is enabled on bridge, default: nope"""
data = run_json_cmd(['mctl', 'show', 'igmp', 'json'], "igmp-status.json",
default={}, check=False)
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
# We always get all interfaces for two reasons.
# 1) To increase speed on large iron with many ports.
# 2) To simplify testing (single dummy file ip-link-show.json).
def get_ip_link():
"""Fetch interface link information from kernel"""
return run_json_cmd(['ip', '-s', '-d', '-j', 'link', 'show'],
"ip-link-show.json")
def netns_get_ip_link(netns):
"""Fetch interface link information from within a network namespace"""
return run_json_cmd(['ip', 'netns', 'exec', netns, 'ip', '-s', '-d', '-j', 'link', 'show'],
f"netns-{netns}-ip-link-show.json")
def get_ip_addr():
"""Fetch interface address information from kernel"""
return run_json_cmd(['ip', '-j', 'addr', 'show'],
"ip-addr-show.json")
def netns_get_ip_addr(netns):
"""Fetch interface address information from within a network namespace"""
return run_json_cmd(['ip', 'netns', 'exec', netns, 'ip', '-j', 'addr', 'show'],
f"netns-{netns}-ip-addr-show.json")
def get_netns_list():
"""Fetch a list of network namespaces"""
return run_json_cmd(['ip', '-j', 'netns', 'list'],
"netns-list.json")
def netns_find_ifname(ifname):
"""Find which network namespace owns ifname (if any)"""
for netns in get_netns_list():
for iface in netns_get_ip_link(netns['name']):
if 'ifalias' in iface and iface['ifalias'] == ifname:
return netns['name']
return None
def netns_ifindex_to_ifname(ifindex):
"""Look through all network namespaces for an interface index and return its name"""
for netns in get_netns_list():
for iface in netns_get_ip_link(netns['name']):
if iface['ifindex'] == ifindex:
if 'ifalias' in iface:
return iface['ifalias']
if 'ifname' in iface:
return iface['ifname']
return None
return None
def add_bridge_port_common(ifname, iface_in, iface_out):
li = iface_in.get("linkinfo", {})
if not (li.get("info_slave_kind") == "bridge" or \
li.get("info_kind") == "bridge"):
return
pvid = get_bridge_port_pvid(ifname)
if pvid is not None:
insert(iface_out, "infix-interfaces:bridge-port", "pvid", pvid)
def add_bridge_port_lower(ifname, iface_in, iface_out):
li = iface_in.get("linkinfo", {})
if not li.get("info_slave_kind") == "bridge":
return
insert(iface_out, "infix-interfaces:bridge-port", "bridge", iface_in['master'])
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)
def add_gre(iface_in, iface_out):
if 'link_type' in iface_in:
val = json_get_yang_type(iface_in)
if val != "infix-if-type:gre" and val != "infix-if-type:gretap":
return;
gre={}
info_data=iface_in.get("linkinfo", {}).get("info_data", {})
gre["local"] = info_data.get("local")
gre["remote"] = info_data.get("remote")
insert(iface_out, "infix-interfaces:gre", gre)
def add_ip_link(ifname, iface_in, iface_out):
if 'ifname' in iface_in:
iface_out['name'] = ifname
if 'ifindex' in iface_in:
iface_out['if-index'] = iface_in['ifindex']
if 'ifalias' in iface_in:
iface_out['description'] = iface_in['ifalias']
if 'address' in iface_in and not "POINTOPOINT" in iface_in["flags"]:
iface_out['phys-address'] = iface_in['address']
add_bridge_port_common(ifname, iface_in, iface_out)
add_bridge_port_lower(ifname, iface_in, iface_out)
if not iface_is_dsa(iface_in):
if iface_in.get('link'):
insert(iface_out, "infix-interfaces:vlan", "lower-layer-if", iface_in['link'])
elif 'link_index' in iface_in:
# 'link_index' is the only reference we have if the link iface is in a namespace
lower = netns_ifindex_to_ifname(iface_in['link_index'])
if lower:
insert(iface_out, "infix-interfaces:vlan", "lower-layer-if", lower)
if 'flags' in iface_in:
iface_out['admin-status'] = "up" if "UP" in iface_in['flags'] else "down"
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
add_gre(iface_in, iface_out)
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_in, iface_out):
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 'family' not in addr:
logger.error("'family' missing from 'addr_info'")
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:
logger.error("invalid 'family' in 'addr_info'")
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 (need new JSON format!)"""
cmd = ['ethtool', '--json', '-S', ifname, '--all-groups']
try:
data = run_json_cmd(cmd, f"ethtool-groups-{ifname}.json")
if len(data) != 1:
logger.warning("%s: no counters available, skipping.", ifname)
return
except subprocess.CalledProcessError:
# Allow comand to fail, not all NICs support --json yet
return
iface_in = data[0]
# TODO: room for improvement, 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)
def add_container_ifaces(yang_ifaces):
"""Add all podman interfaces with limited data"""
interfaces={}
try:
containers = run_json_cmd(['podman', 'ps', '--format', 'json'], "podman-ps.json", default=[])
except Exception as e:
logging.error(f"Error, unable to run podman: {e}")
return
for container in containers:
name = container.get('Names', ['Unknown'])[0]
networks = container.get('Networks', [])
for network in networks:
if not network in interfaces:
interfaces[network] = []
if name not in interfaces[network]:
interfaces[network].append(name)
for ifname, containers in interfaces.items():
iface_out = {}
iface_out['name'] = ifname
iface_out['type'] = "infix-if-type:other" # Fallback
insert(iface_out, "infix-interfaces:container-network", "containers", containers)
netns = netns_find_ifname(ifname)
if netns is not None:
ip_link_data = netns_get_ip_link(netns)
ip_link_data = next((d for d in ip_link_data if d.get('ifalias') == ifname), None)
add_ip_link(ifname, ip_link_data, iface_out)
yang_ifaces.append(iface_out)
# 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 get_iface_data(ifname, ip_link_data, ip_addr_data):
iface_out = {}
add_ip_link(ifname, ip_link_data, iface_out)
add_ip_addr(ifname, ip_addr_data, 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":
# Fail silent, multicast snooping may not be enabled on bridge
mc_status = run_json_cmd(['mctl', '-p', 'show', 'igmp', 'json'],
"igmp-status.json", default={},
check=False)
add_vlans_to_bridge(ifname, iface_out, mc_status)
add_mdb_to_bridge(ifname, iface_out, mc_status)
return iface_out
def _add_interface(ifname, ip_link_data, ip_addr_data, yang_ifaces):
# We expect both ip addr and link data to exist.
if not ip_link_data or not ip_addr_data:
return
# Skip internal interfaces.
if 'group' in ip_link_data and ip_link_data['group'] == "internal":
return
yang_ifaces.append(get_iface_data(ifname, ip_link_data, ip_addr_data))
def add_interface(ifname, yang_ifaces):
ip_link_data = get_ip_link()
ip_addr_data = get_ip_addr()
if ifname:
ip_link_data = next((d for d in ip_link_data if d.get('ifname') == ifname), None)
ip_addr_data = next((d for d in ip_addr_data if d.get('ifname') == ifname), None)
_add_interface(ifname, ip_link_data, ip_addr_data, yang_ifaces)
else:
for link in ip_link_data:
addr = next((d for d in ip_addr_data if d.get('ifname') == link["ifname"]), None)
_add_interface(link["ifname"], link, addr, yang_ifaces)
add_container_ifaces(yang_ifaces)
def main():
global TESTPATH
@@ -810,7 +92,7 @@ def main():
# Set up syslog output for critical errors to aid debugging
logger = logging.getLogger('yanger')
common.LOG = logging.getLogger('yanger')
if os.path.exists('/dev/log'):
log = logging.handlers.SysLogHandler(address='/dev/log')
else:
@@ -819,9 +101,8 @@ def main():
fmt = logging.Formatter('%(name)s[%(process)d]: %(message)s')
log.setFormatter(fmt)
logger.setLevel(logging.INFO)
logger.addHandler(log)
common.LOG = logger
common.LOG.setLevel(logging.INFO)
common.LOG.addHandler(log)
if args.test:
TESTPATH = args.test
@@ -831,33 +112,11 @@ def main():
host.HOST = host.Localhost()
if args.model == 'ietf-interfaces':
yang_data = {
"ietf-interfaces:interfaces": {
"interface": []
}
}
add_interface(args.param, yang_data['ietf-interfaces:interfaces']['interface'])
from . import ietf_interfaces
yang_data = ietf_interfaces.operational(args.param)
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)
from . import ietf_routing
yang_data = ietf_routing.operational()
elif args.model == 'ietf-ospf':
from . import ietf_ospf
yang_data = ietf_ospf.operational()