From 78499757b008aaa2bf28a334950ce74808db461b Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 16 Dec 2024 00:16:54 +0100 Subject: [PATCH] confd,statd: add support for link aggregates Signed-off-by: Joachim Wiberg --- src/confd/src/Makefile.am | 1 + src/confd/src/ietf-interfaces.c | 25 +- src/confd/src/ietf-interfaces.h | 28 +- src/confd/src/ietf-ip.c | 4 +- src/confd/src/infix-if-lag.c | 189 +++++++++++ src/confd/yang/infix-if-lag.yang | 304 ++++++++++++------ src/statd/python/cli_pretty/cli_pretty.py | 119 ++++++- .../python/yanger/ietf_interfaces/lag.py | 79 +++++ .../python/yanger/ietf_interfaces/link.py | 15 +- 9 files changed, 644 insertions(+), 120 deletions(-) create mode 100644 src/confd/src/infix-if-lag.c create mode 100644 src/statd/python/yanger/ietf_interfaces/lag.py diff --git a/src/confd/src/Makefile.am b/src/confd/src/Makefile.am index aca9bde5..15f08bef 100644 --- a/src/confd/src/Makefile.am +++ b/src/confd/src/Makefile.am @@ -30,6 +30,7 @@ confd_plugin_la_SOURCES = \ ieee802-ethernet-interface.c \ ietf-ip.c \ infix-if-bridge.c \ + infix-if-lag.c \ infix-if-bridge-mcd.c \ infix-if-bridge-port.c \ infix-if-veth.c \ diff --git a/src/confd/src/ietf-interfaces.c b/src/confd/src/ietf-interfaces.c index 8f4f8d73..6219e874 100644 --- a/src/confd/src/ietf-interfaces.c +++ b/src/confd/src/ietf-interfaces.c @@ -73,6 +73,10 @@ static int ifchange_cand_infer_type(sr_session_ctx_t *session, const char *path) inferred.data.string_val = "infix-if-type:ethernet"; else if (!fnmatch("br+([0-9])", ifname, FNM_EXTMATCH)) inferred.data.string_val = "infix-if-type:bridge"; + else if (!fnmatch("bond+([0-9])", ifname, FNM_EXTMATCH)) + inferred.data.string_val = "infix-if-type:lag"; + else if (!fnmatch("lag+([0-9])", ifname, FNM_EXTMATCH)) + inferred.data.string_val = "infix-if-type:lag"; else if (!fnmatch("docker+([0-9])", ifname, FNM_EXTMATCH)) inferred.data.string_val = "infix-if-type:bridge"; else if (!fnmatch("dummy+([0-9])", ifname, FNM_EXTMATCH)) @@ -299,6 +303,7 @@ static int netdag_gen_sysctl_setting(struct dagger *net, const char *ifname, FIL return 0; } + static int netdag_gen_sysctl(struct dagger *net, struct lyd_node *cif, struct lyd_node *dif) @@ -355,6 +360,8 @@ static int netdag_gen_afspec_add(sr_session_ctx_t *session, struct dagger *net, case IFT_GRE: case IFT_GRETAP: return gre_gen(NULL, cif, ip); + case IFT_LAG: + return lag_gen(dif, cif, ip, 1); case IFT_VETH: return veth_gen(NULL, cif, ip); case IFT_VLAN: @@ -368,7 +375,6 @@ static int netdag_gen_afspec_add(sr_session_ctx_t *session, struct dagger *net, case IFT_UNKNOWN: sr_session_set_error_message(net->session, "%s: unsupported interface type \"%s\"", ifname, lydx_get_cattr(cif, "type")); - return -ENOSYS; } __builtin_unreachable(); @@ -383,6 +389,8 @@ static int netdag_gen_afspec_set(sr_session_ctx_t *session, struct dagger *net, switch (iftype_from_iface(cif)) { case IFT_BRIDGE: return bridge_gen(dif, cif, ip, 0); + case IFT_LAG: + return lag_gen(dif, cif, ip, 0); case IFT_VLAN: return vlan_gen(dif, cif, ip); @@ -420,6 +428,9 @@ static bool netdag_must_del(struct lyd_node *dif, struct lyd_node *cif) case IFT_GRE: case IFT_GRETAP: return lydx_get_descendant(lyd_child(dif), "gre", NULL); + case IFT_LAG: + return lydx_get_child(dif, "custom-phys-address") || + lydx_get_descendant(lyd_child(dif), "lag", "mode", NULL); case IFT_VLAN: return lydx_get_descendant(lyd_child(dif), "vlan", NULL); case IFT_VETH: @@ -505,6 +516,7 @@ static int netdag_gen_iface_del(struct dagger *net, struct lyd_node *dif, case IFT_DUMMY: case IFT_GRE: case IFT_GRETAP: + case IFT_LAG: case IFT_VLAN: case IFT_VXLAN: case IFT_UNKNOWN: @@ -593,6 +605,10 @@ static sr_error_t netdag_gen_iface(sr_session_ctx_t *session, struct dagger *net if (err) goto err_close_ip; + err = lag_port_gen(dif, cif); + if (err) + goto err_close_ip; + /* Set type specific attributes */ if (!fixed && op != LYDX_OP_CREATE) { err = netdag_gen_afspec_set(session, net, dif, cif, ip); @@ -613,8 +629,7 @@ static sr_error_t netdag_gen_iface(sr_session_ctx_t *session, struct dagger *net fprintf(ip, "link set alias \"%s\" dev %s\n", attr ?: "", ifname); /* Bring interface back up, if enabled */ - attr = lydx_get_cattr(cif, "enabled"); - if (!attr || !strcmp(attr, "true")) + if (lydx_is_enabled(cif, "enabled")) fprintf(ip, "link set dev %s up state up\n", ifname); err = err ? : netdag_gen_sysctl(net, cif, dif); @@ -641,8 +656,8 @@ static int netdag_init_iface(struct lyd_node *cif) switch (iftype_from_iface(cif)) { case IFT_BRIDGE: return bridge_add_deps(cif); - /* case IFT_LAG: */ - /* return lag_add_deps(cif); */ + case IFT_LAG: + return lag_add_deps(cif); case IFT_VLAN: return vlan_add_deps(cif); case IFT_VETH: diff --git a/src/confd/src/ietf-interfaces.h b/src/confd/src/ietf-interfaces.h index a3cdf5a2..22a0d86d 100644 --- a/src/confd/src/ietf-interfaces.h +++ b/src/confd/src/ietf-interfaces.h @@ -28,6 +28,7 @@ _map(IFT_ETHISH, "infix-if-type:etherlike") \ _map(IFT_GRE, "infix-if-type:gre") \ _map(IFT_GRETAP, "infix-if-type:gretap") \ + _map(IFT_LAG, "infix-if-type:lag") \ _map(IFT_LO, "infix-if-type:loopback") \ _map(IFT_VETH, "infix-if-type:veth") \ _map(IFT_VLAN, "infix-if-type:vlan") \ @@ -70,14 +71,26 @@ static inline const char *bridge_tagtype2str(const char *type) return NULL; } -static inline bool is_bridge_port(struct lyd_node *cif) +static inline struct lyd_node *get_master(struct lyd_node *cif) { - struct lyd_node *node = lydx_get_descendant(lyd_child(cif), "bridge-port", NULL); + struct lyd_node *node; - if (!node || !lydx_get_child(node, "bridge")) - return false; + node = lydx_get_descendant(lyd_child(cif), "bridge-port", NULL); + if (node) + return lydx_get_child(node, "bridge"); - return true; + node = lydx_get_descendant(lyd_child(cif), "lag-port", NULL); + if (node) + return lydx_get_child(node, "lag"); + + return NULL; +} + +static inline bool is_member_port(struct lyd_node *cif) +{ + if (get_master(cif)) + return true; + return false; } @@ -108,6 +121,11 @@ int bridge_port_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip); /* infix-if-gre.c */ int gre_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip); +/* infix-if-lag.c */ +int lag_port_gen(struct lyd_node *dif, struct lyd_node *cif); +int lag_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip, int add); +int lag_add_deps(struct lyd_node *cif); + /* infix-if-veth.c */ bool veth_is_primary(struct lyd_node *cif); int ifchange_cand_infer_veth(sr_session_ctx_t *session, const char *path); diff --git a/src/confd/src/ietf-ip.c b/src/confd/src/ietf-ip.c index cf6fdc93..a428967b 100644 --- a/src/confd/src/ietf-ip.c +++ b/src/confd/src/ietf-ip.c @@ -22,7 +22,7 @@ int netdag_gen_ipv6_autoconf(struct dagger *net, struct lyd_node *cif, struct lyd_node *node; FILE *fp; - if (!ipconf || !lydx_is_enabled(ipconf, "enabled") || is_bridge_port(cif)) { + if (!ipconf || !lydx_is_enabled(ipconf, "enabled") || is_member_port(cif)) { fputs(" addrgenmode none", ip); return 0; } @@ -86,7 +86,7 @@ int netdag_gen_ipv4_autoconf(struct dagger *net, struct lyd_node *cif, snprintf(defaults, sizeof(defaults), "/etc/default/zeroconf-%s", ifname); /* no ipv4 at all, ipv4 selectively disabled, or interface is a bridge port */ - if (!ipconf || !lydx_is_enabled(ipconf, "enabled") || is_bridge_port(cif)) + if (!ipconf || !lydx_is_enabled(ipconf, "enabled") || is_member_port(cif)) goto disable; /* diff --git a/src/confd/src/infix-if-lag.c b/src/confd/src/infix-if-lag.c new file mode 100644 index 00000000..54667a0a --- /dev/null +++ b/src/confd/src/infix-if-lag.c @@ -0,0 +1,189 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ietf-interfaces.h" + +/* + * The lag-port noode is only in the dif at initial creation, or removal. + * When a lag is recreated, e.g., when changing mode, the lag-port is only + * in the cif. + * + * To simplify confd dependency handling we always call this function, so + * a lag member always has its lag master reset. This is cruicial in the + * case described above when the the lag is removed to change mode. + */ +int lag_port_gen(struct lyd_node *dif, struct lyd_node *cif) +{ + const char *ifname = lydx_get_cattr(cif, "name"); + struct lyd_node *node; + const char *lagname; + int err = 0; + FILE *fp; + + node = lydx_get_descendant(lyd_child(dif), "lag-port", NULL); + if (node) { + struct lydx_diff lagdiff = { 0 }; + + if (!lydx_get_diff(lydx_get_child(node, "lag"), &lagdiff)) + goto fail; + + if (lagdiff.old) { + fp = dagger_fopen_net_exit(&confd.netdag, lagdiff.old, + NETDAG_EXIT_LOWERS, "delete-ports.ip"); + if (!fp) { + err = -EIO; + goto fail; + } + + fprintf(fp, "link set %s nomaster\n", ifname); + fclose(fp); + + return 0; + } + + lagname = lagdiff.new; + } else { + node = lydx_get_descendant(lyd_child(cif), "lag-port", NULL); + lagname = lydx_get_cattr(node, "lag"); + if (!node || !lagname) + goto fail; /* done, nothing to do */ + } + + fp = dagger_fopen_net_init(&confd.netdag, lagname, NETDAG_INIT_LOWERS, + "add-ports.ip"); + if (!fp) + return -EIO; + + fprintf(fp, "link set %s down master %s\n", ifname, lagname); + if (lydx_is_enabled(cif, "enabled")) + fprintf(fp, "link set dev %s up state up\n", ifname); + fclose(fp); + + err = dagger_add_dep(&confd.netdag, lagname, ifname); + if (err) + ERROR("%s: unable to add dep to %s", ifname, lagname); +fail: + return err; +} + +int lag_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip, int add) +{ + const char *lagname = lydx_get_cattr(cif, "name"); + const char *op = add ? "add" : "set"; + struct lyd_node *lag, *mon; + const char *mode; + int err = 0; + + lag = lydx_get_descendant(lyd_child(cif), "lag", NULL); + if (!lag) + return -EINVAL; + + /* Must take lag down if changing mode */ + if (!add) + fprintf(ip, "link set %s down\n", lagname); + + fprintf(ip, "link %s dev %s", op, lagname); + + if (add) + link_gen_address(cif, ip); + + fprintf(ip, " type bond min_links 1"); + + mode = lydx_get_cattr(lag, "mode"); + if (!strcmp(mode, "lacp")) { + struct lyd_node *lacp = lydx_get_child(lag, "lacp"); + + if (add) + fprintf(ip, " mode 802.3ad ad_select bandwidth"); + + mode = lydx_get_cattr(lacp, "mode"); + if (!strcmp(mode, "active")) + mode = "on"; + else + mode = "off"; + + fprintf(ip, " lacp_rate %s lacp_active %s", + lydx_get_cattr(lacp, "rate"), mode); + } else { + /* XXX: mode hard-coded for now for mv88e6xxxx operation */ + if (add) + fprintf(ip, " mode balance-xor"); + } + + /* + * Required in lacp mode, we rely on it also in static mode. + * A previous attempt supported arp-monitor, but it does not + * work with mv88e6xxx link aggregates, unfortunately so was + * dropped for the final version. + */ + fprintf(ip, " miimon 100 use_carrier 1"); + + mon = lydx_get_descendant(lyd_child(lag), "link-monitor", NULL); + if (mon) { + const struct lyd_node *debounce; + + debounce = lydx_get_descendant(lyd_child(mon), "debounce", NULL); + if (debounce) { + const char *msec; + + msec = lydx_get_cattr(mon, "up"); + if (msec) + fprintf(ip, " updelay %s", msec); + msec = lydx_get_cattr(mon, "down"); + if (msec) + fprintf(ip, " downdelay %s", msec); + } + } + + fputs("\n", ip); + + /* + * When netdag_must_del() is triggered, this is when we reattach + * unmodified ports when recreating the lag. + */ + if (add) { + struct lyd_node *node, *cifs; + + cifs = lydx_get_descendant(lyd_parent(cif), "interfaces", "interface", NULL); + LYX_LIST_FOR_EACH(cifs, node, "interface") + err += lag_port_gen(NULL, node); + } + + return err; +} + +int lag_add_deps(struct lyd_node *cif) +{ + const char *lagname = lydx_get_cattr(cif, "name"); + struct ly_set *ports; + const char *portname; + int err = 0; + uint32_t i; + + ports = lydx_find_xpathf(cif, "../interface[lag-port/lag='%s']", lagname); + if (!ports) + return ERR_IFACE(cif, -ENOENT, "Unable to fetch lag ports"); + + + for (i = 0; i < ports->count; i++) { + portname = lydx_get_cattr(ports->dnodes[i], "name"); + + err = dagger_add_dep(&confd.netdag, lagname, portname); + if (err) { + ERR_IFACE(cif, err, "Unable to depend on \"%s\"", portname); + break; + } + } + + ly_set_free(ports, NULL); + return err; +} diff --git a/src/confd/yang/infix-if-lag.yang b/src/confd/yang/infix-if-lag.yang index 937c625d..9f67cc62 100644 --- a/src/confd/yang/infix-if-lag.yang +++ b/src/confd/yang/infix-if-lag.yang @@ -10,6 +10,12 @@ submodule infix-if-lag { import iana-if-type { prefix ianaift; } + import ietf-inet-types { + prefix inet; + } + import ietf-yang-types { + prefix yang; + } organization "KernelKit"; contact "kernelkit@googlegroups.com"; @@ -24,35 +30,93 @@ submodule infix-if-lag { * Typedefs */ - typedef lag-type { - description "Mode values for link aggregates."; + typedef lag-mode { + description "Mode values for link aggregates."; - /* Temporarily limited to 802.1AX and Balanced XOR */ - type enumeration { - enum static { - description "Static mode (Balanced XOR)."; - value 2; - } - enum lacp { - description "IEEE 802.3ad LACP mode."; - value 4; - } + type enumeration { + enum static { + description "Static mode (Balanced XOR)."; } + enum lacp { + description "IEEE 802.3ad LACP mode."; + } + } + } + + typedef lag-type { + description "Static mode type of distribution (driver modes)."; + + type enumeration { + enum balance-rr; + enum active-backup; + enum balance-xor; + enum broadcast; + enum balance-tlb; + enum balance-alb; + } + } + + typedef hash-policy { + description "Egress hash policy, note: offloading limitations!"; + + type enumeration { + enum layer2; + enum layer3-4; + enum layer2-3; + enum encap2-3; + enum encap3-4; + enum vlan-srcmac; + } + } + + typedef member-state { + description "Lag port membership state, taking active part or backup."; + + type enumeration { + enum backup; + enum active; + } } typedef lacp-mode { - description "LACP mode values."; + description "LACP mode values."; - type enumeration { - enum passive { - description "LACP active mode"; - value 0; - } - enum active { - description "LACP passive mode."; - value 1; - } + type enumeration { + enum passive { + description "LACP active mode"; } + enum active { + description "LACP passive mode."; + } + } + } + + typedef lacp-rate { + description "LACP rate values."; + + type enumeration { + enum slow { + description "Send LACPDUs every 30 seconds (90-second timeout)."; + } + enum fast { + description "Send LACPDUs every 1 second (3-second timeout)."; + } + } + } + + typedef lacp-state { + description "LACP port state flags."; + + type enumeration { + enum active; + enum short_timeout; + enum aggregating; + enum in_sync; + enum collecting; + enum distributing; + enum defaulted; + enum expired; + } } /* @@ -62,78 +126,8 @@ submodule infix-if-lag { grouping hash { leaf hash { description "Transmit hash policy."; + type hash-policy; config false; // For now, staically set to layer2 only - type string; - } - } - - grouping lacp-settings { - description "LACP mode settings."; - - container lacp { - description "Settings specific for LACP mode."; - - uses hash; - - leaf mode { - description "Operational mode of LACP, default: active. - - - Active: initiates negotiation by sending LACPDUs. - - Passive: waits for the peer to initiate. - - At least one end of the link must be in active mode. When both ends - are active, there is slightly more traffic, but the default ensures - fail-safe operation. - - Passive mode is typically used for troubleshooting, in dynamic - setups (e.g., MLAG), or to minimize the risk of unintended - aggregation. - - For most production scenarios, active mode is preferred to ensure - faster and more predictable link aggregation."; - type lacp-mode; - default "active"; - } - - leaf rate { - description "Rate of LACP keep-alives, default: slow. - - Determines the frequency of LACPDU transmission and the associated - timeout for link failure detection. - - - slow: Sends LACPDUs every 30 seconds. The associated timeout is 90 - seconds, meaning a link is considered failed after 3 consecutive - missed LACPDUs. - - - fast: Sends LACPDUs every 1 second. The associated timeout is 3 - seconds, meaning a link is considered failed after 3 consecutive - missed LACPDUs. - - The selected rate affects the responsiveness of link failure - detection and the amount of control traffic."; - type enumeration { - enum slow { - description "Send LACPDUs every 30 seconds (90-second timeout)."; - } - enum fast { - description "Send LACPDUs every 1 second (3-second timeout)."; - } - } - default "slow"; - } - } - } - - grouping static-settings { - container static { - config false; // For now, we need to read out mode and other status - - uses hash; - - leaf mode { - description "Active mode for static aggregates."; - type string; - } } } @@ -153,12 +147,100 @@ submodule infix-if-lag { leaf mode { description "Link aggregation mode."; - type lag-type; + type lag-mode; mandatory true; } - uses lacp-settings; - uses static-settings; + container lacp { + description "Settings specific for LACP mode."; + + uses hash; + + leaf mode { + description "Operational mode of LACP, default: active. + + - Active: initiates negotiation by sending LACPDUs. + - Passive: waits for the peer to initiate. + + At least one end of the link must be in active mode. When both ends + are active, there is slightly more traffic, but the default ensures + fail-safe operation. + + Passive mode is typically used for troubleshooting, in dynamic + setups (e.g., MLAG), or to minimize the risk of unintended + aggregation. + + For most production scenarios, active mode is preferred to ensure + faster and more predictable link aggregation."; + type lacp-mode; + default active; + } + + leaf rate { + description "Rate of LACP keep-alives, default: slow. + + Determines the frequency of LACPDU transmission and the associated + timeout for link failure detection. + + - slow: Sends LACPDUs every 30 seconds. The associated timeout is 90 + seconds, meaning a link is considered failed after 3 consecutive + missed LACPDUs. + + - fast: Sends LACPDUs every 1 second. The associated timeout is 3 + seconds, meaning a link is considered failed after 3 consecutive + missed LACPDUs. + + The selected rate affects the responsiveness of link failure + detection and the amount of control traffic."; + type lacp-rate; + default slow; + } + + leaf system-priority { + description "Sytem priority used by the node on this LAG interface. + + Lower value is higher priority for determining which node + is the controlling system."; + type uint16 { + range "1 .. 65535"; + } + } + + leaf aggregator-id { + description "Aggregator ID."; + config false; + type uint16; + } + + leaf actor-key { + description "Actor key."; + config false; + type uint16; + } + + leaf partner-key { + description "Partner key."; + config false; + type uint16; + } + + leaf partner-mac { + description "Partner MAC address."; + config false; + type yang:phys-address; + } + } + + container static { + config false; // For now, we need to read out mode and other status + + leaf mode { + description "Active mode for static aggregates."; + type lag-type; + } + + uses hash; + } container link-monitor { description "Link monitor properties."; @@ -183,6 +265,16 @@ submodule infix-if-lag { } } } + + must "not(./mode = 'lacp' and ./monitor/arp-monitor/interval > 0)" { + error-message "ARP monitor cannot be enabled in LACP mode."; + description "The Linux bond driver disables miimon when arpmon != 0."; + } + + must "not(./mode = 'lacp' and ./monitor/link-monitor/interval = 0)" { + error-message "Link monitor must be enabled in LACP mode."; + description "The Linux bond driver requires miimon in 802.3ad mode."; + } } } @@ -206,24 +298,36 @@ submodule infix-if-lag { error-message "Must refer to a valid LAG interface."; } } + leaf state { - description "Port state, active or backup member."; + description "Link state, active or backup member."; + type member-state; + config false; + } + + leaf link-failures { + description "Link failure counter, cannot be reset."; + type uint32; config false; - type string; } container lacp { description "LACP port state, ours and partner."; config false; + leaf aggregator-id { + description "Aggregator ID."; + type uint16; + } + leaf-list actor-state { description "LACP state flags."; - type string; + type lacp-state; } leaf-list partner-state { description "LACP state flags for link partner."; - type string; + type lacp-state; } } } diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index 4d278b78..fa32d528 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -451,6 +451,34 @@ class Iface: 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', 'cist', 'state') + + self.lag_mode = get_json_data('', self.data, 'infix-interfaces:lag', 'mode') + if self.lag_mode: + self.lag_type = get_json_data('', self.data, 'infix-interfaces:lag', 'static', 'mode') + if self.lag_mode == "lacp": + self.lag_hash = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'hash') + self.lacp_id = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'aggregator-id') + self.lacp_actor_key = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'actor-key') + self.lacp_partner_key = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'partner-key') + self.lacp_partner_mac = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'partner-mac') + self.lacp_sys_prio = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'system-priority') + else: + self.lag_hash = get_json_data('', self.data, 'infix-interfaces:lag', 'static', 'hash') + self.link_updelay = get_json_data('', self.data, 'infix-interfaces:lag', 'link-monitor', 'debounce', 'up') + self.link_downdelay = get_json_data('', self.data, 'infix-interfaces:lag', 'link-monitor', 'debounce', 'down') + + self.lacp_mode = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'mode') + rate = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'rate') + self.lacp_rate = "fast (1s)" if rate == "fast" else "slow (30 sec)" + + self.lag = get_json_data('', self.data, 'infix-interfaces:lag-port', 'lag') + if self.lag: + self.lag_state = get_json_data('', self.data, 'infix-interfaces:lag-port', 'state') + self.lacp_id = get_json_data('', self.data, 'infix-interfaces:lag-port', 'lacp', 'aggregator-id') + self.lacp_state = get_json_data('', self.data, 'infix-interfaces:lag-port', 'lacp', 'actor-state') + self.lacp_pstate = get_json_data('', self.data, 'infix-interfaces:lag-port', 'lacp', 'partner-state') + self.link_failures = get_json_data('', self.data, 'infix-interfaces:lag-port', 'link-failures') + self.containers = get_json_data('', self.data, 'infix-interfaces:container-network', 'containers') @@ -491,6 +519,9 @@ class Iface: def is_bridge(self): return self.type == "infix-if-type:bridge" + def is_lag(self): + return self.type == "infix-if-type:lag" + def is_veth(self): return self.data['type'] == "infix-if-type:veth" @@ -635,6 +666,59 @@ class Iface: self.pr_proto_ipv4() self.pr_proto_ipv6() + def pr_proto_lag(self, member=True): + data_str = "" + + row = f"{'lag':<{Pad.proto}}" + if member: + state = self.lag_state.upper() + if self.oper() == "up": + row += Decore.green(f"{state:<{Pad.state}}") + else: + row += Decore.yellow(f"{state:<{Pad.state}}") + if self.lacp_state: + lacp = ', '.join(self.lacp_state) + data_str += lacp + else: + dec = Decore.green if self.oper() == "up" else Decore.yellow + row += dec(f"{self.oper().upper():<{Pad.state}}") + data_str += f"{self.lag_mode}" + if self.lag_mode == "lacp": + data_str += f": {self.lacp_mode}" + data_str += f", rate: {self.lacp_rate}" + data_str += f", hash: {self.lag_hash}" + else: + data_str += f": {self.lag_type}" + data_str += f", hash: {self.lag_hash}" + + if data_str: + row += f"{data_str:<{Pad.data}}" + + print(row) + + def pr_lag(self, _ifaces): + self.pr_name(pipe="") + self.pr_proto_lag(member=False) + + lowers = [] + for _iface in [Iface(data) for data in _ifaces]: + if _iface.lag and _iface.lag == 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_lag() + def pr_veth(self, _ifaces): self.pr_name(pipe="") self.pr_proto_veth() @@ -716,6 +800,32 @@ class Iface: if self.phys_address: print(f"{'physical address':<{20}}: {self.phys_address}") + if self.lag_mode: + print(f"{'lag mode':<{20}}: {self.lag_mode}") + if self.lag_mode == "lacp": + print(f"{'lag hash':<{20}}: {self.lag_hash}") + print(f"{'lacp mode':<{20}}: {self.lacp_mode}") + print(f"{'lacp rate':<{20}}: {self.lacp_rate}") + print(f"{'lacp aggregate id':<{20}}: {self.lacp_id}") + print(f"{'lacp system priority':<{20}}: {self.lacp_sys_prio}") + print(f"{'lacp actor key':<{20}}: {self.lacp_actor_key}") + print(f"{'lacp partner key':<{20}}: {self.lacp_partner_key}") + print(f"{'lacp partner mac':<{20}}: {self.lacp_partner_mac}") + else: + print(f"{'lag type':<{20}}: {self.lag_type}") + print(f"{'lag hash':<{20}}: {self.lag_hash}") + print(f"{'link debounce up':<{20}}: {self.link_updelay} msec") + print(f"{'link debounce down':<{20}}: {self.link_downdelay} msec") + + if self.lag: + print(f"{'lag member':<{20}}: {self.lag}") + print(f"{'lag member state':<{20}}: {self.lag_state}") + if self.lacp_state: + print(f"{'lacp aggregate id':<{20}}: {self.lacp_id}") + print(f"{'lacp actor state':<{20}}: {', '.join(self.lacp_state)}") + print(f"{'lacp partner state':<{20}}: {', '.join(self.lacp_pstate)}") + print(f"{'link failure count':<{20}}: {self.link_failures}") + if self.ipv4_addr: first = True for addr in self.ipv4_addr: @@ -872,6 +982,10 @@ def pr_interface_list(json): iface.pr_bridge(ifaces) continue + if iface.is_lag(): + iface.pr_lag(ifaces) + continue + if iface.is_veth(): iface.pr_veth(ifaces) continue @@ -892,11 +1006,14 @@ def pr_interface_list(json): iface.pr_vlan(ifaces) continue - # These interfaces are printed by there parent, such as bridge + # These interfaces are printed by their parent, such as bridge if iface.lower_if: continue if iface.bridge: continue + if iface.lag: + continue + print_interface(iface) diff --git a/src/statd/python/yanger/ietf_interfaces/lag.py b/src/statd/python/yanger/ietf_interfaces/lag.py new file mode 100644 index 00000000..ec4c351c --- /dev/null +++ b/src/statd/python/yanger/ietf_interfaces/lag.py @@ -0,0 +1,79 @@ +"""The lag/bond oper-status always follows the carrier""" + + +def lower(iplink): + """Return a dictionary of the status of a lag member""" + port = { + "lag": iplink['master'], + } + + info = iplink['linkinfo']['info_slave_data'] + if info: + # active or backup link + port['state'] = info['state'].lower() + port['link-failures'] = info['link_failure_count'] + + # On etherlike interfaces and tap interfaces oper-status lies + # if info['mii_status'] == "DOWN": + # iface['oper-status'] = "down" + + # Initialize lacp dict only if we encounter LACP-related fields + if 'ad_aggregator_id' in info: + port['lacp'] = {} + port['lacp']['aggregator-id'] = info['ad_aggregator_id'] + port['lacp']['actor-state'] = info['ad_actor_oper_port_state_str'] + port['lacp']['partner-state'] = info['ad_partner_oper_port_state_str'] + else: + port['state'] = 'backup' + port['link-failures'] = 0 + + return port + + +def lag(iplink): + """Return a dictionary of the status of the lag""" + mode = { + "balance-xor": "static", + "802.3ad": "lacp", + } + hash_policy = { + "layer2": "layer2", + "layer3+4": "layer3-4", + "layer2+3": "layer2-3", + "encap2+3": "encap2-3", + "encap3+4": "encap3-4", + "vlan+srcmac": "vlan-srcmac", + } + bond = {} + + info = iplink["linkinfo"]["info_data"] + if info: + bond["mode"] = mode.get(info['mode'], "static") + if bond["mode"] == "lacp": + bond["lacp"] = { + "mode": 'active' if info['ad_lacp_active'] == "on" else 'passive', + "rate": info['ad_lacp_rate'], + "hash": hash_policy.get(info['xmit_hash_policy'], "layer2"), + } + + if 'ad_info' in info: + bond["lacp"]["aggregator-id"] = info['ad_info']['aggregator'] + bond["lacp"]["actor-key"] = info['ad_info']['actor_key'] + bond["lacp"]["partner-key"] = info['ad_info']['partner_key'] + bond["lacp"]["partner-mac"] = info['ad_info']['partner_mac'] + if 'ad_actor_sys_prio' in info: + bond["lacp"]["system-priority"] = info['ad_actor_sys_prio'] + else: + bond["static"] = { + "mode": info['mode'], + "hash": info['xmit_hash_policy'] + } + + bond["link-monitor"] = { + "debounce": { + "up": info['updelay'], + "down": info['downdelay'] + } + } + + return bond diff --git a/src/statd/python/yanger/ietf_interfaces/link.py b/src/statd/python/yanger/ietf_interfaces/link.py index 05a9a5a2..b53dd6a7 100644 --- a/src/statd/python/yanger/ietf_interfaces/link.py +++ b/src/statd/python/yanger/ietf_interfaces/link.py @@ -3,6 +3,7 @@ from . import common from . import bridge from . import ethernet from . import ip +from . import lag from . import tun from . import veth from . import vlan @@ -117,16 +118,16 @@ def interface(iplink, ipaddr): interface["infix-interfaces:bridge"] = br if brport := bridge.lower(iplink): interface["infix-interfaces:bridge-port"] = brport - # case "infix-if-type:lag": - # if l := lag.lag(iplink): - # interface["infix-interfaces:lag"] = l + case "infix-if-type:lag": + if lg := lag.lag(iplink): + interface["infix-interfaces:lag"] = lg case "infix-if-type:ethernet": if eth := ethernet.ethernet(iplink): interface["ieee802-ethernet-interface:ethernet"] = eth case "infix-if-type:vxlan": if vxlan := tun.vxlan(iplink): interface["infix-interfaces:vxlan"] = vxlan - case "infix-if-type:gre"|"infix-if-type:gretap": + case "infix-if-type:gre" | "infix-if-type:gretap": if gre := tun.gre(iplink): interface["infix-interfaces:gre"] = gre case "infix-if-type:veth": @@ -140,9 +141,9 @@ def interface(iplink, ipaddr): case "infix-interfaces:bridge-port": if brport := bridge.lower(iplink): interface["infix-interfaces:bridge-port"] = brport - # case "infix-interfaces:lag-port": - # if lagport := lag.lower(iplink): - # interface["infix-interfaces:lag-port"] = lagport + case "infix-interfaces:lag-port": + if lagport := lag.lower(iplink): + interface["infix-interfaces:lag-port"] = lagport return interface