Skip to content

Commit

Permalink
Merge pull request #783 from rical/cli-add-show-iface-cont-sup
Browse files Browse the repository at this point in the history
  • Loading branch information
troglobit authored Oct 31, 2024
2 parents fb3c026 + b9492c7 commit 142eda8
Show file tree
Hide file tree
Showing 14 changed files with 763 additions and 14 deletions.
10 changes: 9 additions & 1 deletion src/confd/yang/infix-if-container.yang
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ submodule infix-if-container {
Ensures a container interface can never be a bridge port, or
LAG member, at the same time.";

revision 2024-10-29 {
description "Add read only container list to container-network";
reference "internal";
}
revision 2024-01-15 {
description "Initial revision.";
reference "internal";
Expand Down Expand Up @@ -64,7 +68,11 @@ submodule infix-if-container {
base container-network;
}
}

leaf-list containers {
type string;
config false;
description "List of containers using this interface";
}
list subnet {
description "Static IP ranges to hand out addresses to containers from.
Expand Down
2 changes: 1 addition & 1 deletion src/klish-plugin-infix/xml/infix.xml
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@
<ACTION sym="script">
if [ -n "$KLISH_PARAM_name" ]; then
sysrepocfg -f json -X -d operational -x \
"/ietf-interfaces:interfaces/interface[name='$KLISH_PARAM_name']" | \
"/ietf-interfaces:interfaces/interface[name=\"$KLISH_PARAM_name\"]" | \
/usr/libexec/statd/cli-pretty "show-interfaces" -n "$KLISH_PARAM_name"
else
sysrepocfg -f json -X -d operational -m ietf-interfaces | \
Expand Down
24 changes: 24 additions & 0 deletions src/statd/python/cli_pretty/cli_pretty.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ def yellow(txt):
def underline(txt):
return Decore.decorate("4", txt, "24")

@staticmethod
def gray_bg(txt):
return Decore.decorate("100", txt)

def datetime_now():
if UNIT_TEST:
return datetime(2023, 1, 1, 12, 0, 0, tzinfo=timezone.utc)
Expand Down Expand Up @@ -273,6 +277,7 @@ def __init__(self, data):
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')
self.containers = get_json_data('', self.data, 'infix-interfaces:container-network', 'containers')

if data.get('statistics'):
self.in_octets = data.get('statistics').get('in-octets', '')
Expand Down Expand Up @@ -302,6 +307,10 @@ def __init__(self, data):
def is_vlan(self):
return self.type == "infix-if-type:vlan"

def is_in_container(self):
# Return negative if cointainer isn't set or is an empty list
return getattr(self, 'containers', None)

def is_bridge(self):
return self.type == "infix-if-type:bridge"

Expand Down Expand Up @@ -436,7 +445,18 @@ def pr_vlan(self, _ifaces):
parent.pr_name(pipe='└ ')
parent.pr_proto_eth()

def pr_container(self):
row = f"{self.name:<{Pad.iface}}"
row += f"{'container':<{Pad.proto}}"
row += f"{'':<{Pad.state}}"
row += f"{', ' . join(self.containers):<{Pad.data}}"

print(Decore.gray_bg(row))

def pr_iface(self):
if self.is_in_container():
print(Decore.gray_bg(f"{'owned by container':<{20}}: {', ' . join(self.containers)}"))

print(f"{'name':<{20}}: {self.name}")
print(f"{'index':<{20}}: {self.index}")
if self.mtu:
Expand Down Expand Up @@ -564,6 +584,10 @@ def pr_interface_list(json):
if iface.name == "lo":
continue

if iface.is_in_container():
iface.pr_container()
continue

if iface.is_bridge():
iface.pr_bridge(ifaces)
continue
Expand Down
93 changes: 81 additions & 12 deletions src/statd/python/yanger/yanger.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,18 +629,52 @@ def get_brport_multicast(ifname):
def get_ip_link():
"""Fetch interface link information from kernel"""
return run_json_cmd(['ip', '-s', '-d', '-j', 'link', 'show'],
f"ip-link-show.json")
"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'],
f"ip-addr-show.json")
"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_ip_link(ifname, iface_in, iface_out):
if 'ifname' in iface_in:
iface_out['name'] = iface_in['ifname']
iface_out['name'] = ifname

if 'ifindex' in iface_in:
iface_out['if-index'] = iface_in['ifindex']
Expand All @@ -664,17 +698,17 @@ def add_ip_link(ifname, iface_in, iface_out):

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 not iface_is_dsa(iface_in):
if 'link' in iface_in:
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:
admin_xlate = {
"UP": "up",
"DOWN": "down"
}

admin_status = admin_xlate.get("UP" if "UP" in iface_in['flags'] else "DOWN", "testing")
iface_out['admin-status'] = admin_status
iface_out['admin-status'] = "up" if "UP" in iface_in['flags'] else "down"

if 'operstate' in iface_in:
xlate = {
Expand Down Expand Up @@ -891,6 +925,39 @@ def add_mdb_to_bridge(brname, iface_out, mc_status):
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:
Expand Down Expand Up @@ -968,6 +1035,8 @@ def add_interface(ifname, yang_ifaces):
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
global logger
Expand Down
6 changes: 6 additions & 0 deletions test/case/cli/cli-output/show-interfaces.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,11 @@ br0 bridge  vlan:40u,50t
br1 bridge  
│ ethernet UP 02:00:00:00:00:02
└ e2 bridge FORWARDING vlan:30u pvid:30
e2 container system 
e3 ethernet UP 02:00:00:00:00:03
e4 ethernet DOWN 02:00:00:00:00:04
veth0b container system 
veth0j ethernet UP b2:82:e3:ce:d5:9e
veth peer:veth0k
ipv4 192.168.1.1/24 (static)
veth0k container system2 
32 changes: 32 additions & 0 deletions test/case/cli/system-output/ip-addr-show.json
Original file line number Diff line number Diff line change
Expand Up @@ -625,5 +625,37 @@
"collisions": 0
}
}
},
{
"ifindex": 9,
"link_index": 8,
"ifname": "veth0j",
"flags": [
"BROADCAST",
"MULTICAST",
"UP",
"LOWER_UP"
],
"mtu": 1500,
"qdisc": "noqueue",
"operstate": "UP",
"group": "default",
"txqlen": 1000,
"link_type": "ether",
"address": "b2:82:e3:ce:d5:9e",
"broadcast": "ff:ff:ff:ff:ff:ff",
"link_netnsid": 1,
"addr_info": [
{
"family": "inet",
"local": "192.168.1.1",
"prefixlen": 24,
"scope": "global",
"protocol": "static",
"label": "veth0j",
"valid_life_time": 4294967295,
"preferred_life_time": 4294967295
}
]
}
]
56 changes: 56 additions & 0 deletions test/case/cli/system-output/ip-link-show.json
Original file line number Diff line number Diff line change
Expand Up @@ -618,5 +618,61 @@
"collisions": 0
}
}
},
{
"ifindex": 9,
"link_index": 8,
"ifname": "veth0j",
"flags": [
"BROADCAST",
"MULTICAST",
"UP",
"LOWER_UP"
],
"mtu": 1500,
"qdisc": "noqueue",
"operstate": "UP",
"linkmode": "DEFAULT",
"group": "default",
"txqlen": 1000,
"link_type": "ether",
"address": "b2:82:e3:ce:d5:9e",
"broadcast": "ff:ff:ff:ff:ff:ff",
"link_netnsid": 1,
"promiscuity": 0,
"allmulti": 0,
"min_mtu": 68,
"max_mtu": 65535,
"linkinfo": {
"info_kind": "veth"
},
"inet6_addr_gen_mode": "none",
"num_tx_queues": 1,
"num_rx_queues": 1,
"gso_max_size": 65536,
"gso_max_segs": 65535,
"tso_max_size": 524280,
"tso_max_segs": 65535,
"gro_max_size": 65536,
"gso_ipv4_max_size": 65536,
"gro_ipv4_max_size": 65536,
"stats64": {
"rx": {
"bytes": 1006,
"packets": 13,
"errors": 0,
"dropped": 0,
"over_errors": 0,
"multicast": 0
},
"tx": {
"bytes": 18668,
"packets": 50,
"errors": 0,
"dropped": 0,
"carrier_errors": 0,
"collisions": 0
}
}
}
]
Loading

0 comments on commit 142eda8

Please sign in to comment.