Merge pull request #873 from kernelkit/lag

Add support for static & lacp link aggragetes
This commit is contained in:
Tobias Waldekranz
2025-02-13 09:23:44 +01:00
committed by GitHub
86 changed files with 2601 additions and 360 deletions
+3 -1
View File
@@ -55,7 +55,7 @@ jobs:
sudo apt-get -y update
sudo apt-get -y install pkg-config libjansson-dev libev-dev \
libcrypt-dev libglib2.0-dev libpcre2-dev \
libuev-dev libite-dev
libuev-dev
- name: Build dependencies
run: |
@@ -65,6 +65,8 @@ jobs:
git clone https://github.com/sysrepo/sysrepo.git
mkdir sysrepo/build
(cd sysrepo/build && cmake .. && make all && sudo make install)
git clone https://github.com/troglobit/libite.git
(cd libite && ./autogen.sh && ./configure && make && sudo make install)
make dep
- name: Check applications
+8
View File
@@ -0,0 +1,8 @@
#!/bin/sh
# Initialize speed/duplex of virtio interfaces
# For virtual test systems (lacp tests)
ifaces=$(ip -d -json link show | jq -r '.[] | select(.parentbus == "virtio") | .ifname')
for iface in $ifaces; do
ethtool -s "$iface" speed 1000 duplex full
done
+3
View File
@@ -11,8 +11,10 @@ All notable changes to the project are documented in this file.
- Upgrade Linux kernel to 6.12.13 (LTS)
- YANG type for SSH private/public keys has changed, from
ietf-crypto-types to infix-crypto-types
- Add support for link aggregation (lag), static (balance-xor) and LACP
### Fixes
- N/A
[v25.01.0][] - 2025-01-31
@@ -1483,6 +1485,7 @@ Supported YANG models in addition to those used by sysrepo and netopeer:
[buildroot]: https://buildroot.org/
[UNRELEASED]: https://github.com/kernelkit/infix/compare/v25.01.0...HEAD
[v25.02.0]: https://github.com/kernelkit/infix/compare/v25.01.0...v25.02.0
[v25.01.0]: https://github.com/kernelkit/infix/compare/v24.11.0...v25.01.0
[v24.11.1]: https://github.com/kernelkit/infix/compare/v24.11.0...v24.11.1
[v24.11.0]: https://github.com/kernelkit/infix/compare/v24.10.0...v24.11.0
+9 -5
View File
@@ -1,13 +1,16 @@
<img align="right" src="logo.png" alt="Infix - Linux <3 NETCONF" width=480 border=10>
Welcome to Infix, your friendly Network Operating System! On these
pages you can find both user and developer documentation.
> Topics on configuring the system include CLI examples, every setting
> is also possible to perform using NETCONF. In fact, the Infix test
> system solely relies on NETCONF for configuring network topologies.
Most topics on configuring the system include CLI examples, but every
setting, as well as status read-back from the operational datastore, is
also possible to perform using NETCONF or RESTCONF. In fact, the Infix
regression test system solely relies on NETCONF and RESTCONF.
The CLI documentation is also available from inside the CLI itself using
the `help` command.
> [!TIP]
> The CLI documentation is also available from inside the CLI itself
> using the `help` command in admin-exec mode.
- **CLI Topics**
- [Introduction to the CLI](cli/introduction.md)
@@ -18,6 +21,7 @@ the `help` command.
- [Introduction](introduction.md)
- [System Configuration](system.md)
- [Network Configuration](networking.md)
- [DHCP Server](dhcp.md)
- [Syslog Support](syslog.md)
- **Infix In-Depth**
- [Boot Procedure](boot.md)
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 358 KiB

After

Width:  |  Height:  |  Size: 368 KiB

+1 -1
View File
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 281 KiB

After

Width:  |  Height:  |  Size: 280 KiB

+261 -2
View File
@@ -48,7 +48,7 @@ other traffic would be bridged as usual.
| bridge | infix-if-bridge | SW implementation of an IEEE 802.1Q bridge |
| ip | ietf-ip, infix-ip | IP address to the subordinate interface |
| vlan | infix-if-vlan | Capture all traffic belonging to a specific 802.1Q VID |
| lag[^1] | infix-if-lag | Bond multiple interfaces into one, creating a link aggregate |
| lag | infix-if-lag | Link aggregation, static and IEEE 802.3ad (LACP) |
| lo | ietf-interfaces | Software loopback interface |
| eth | ieee802-ethernet-interface | Physical Ethernet device/port. |
| | infix-ethernet-interface | |
@@ -430,6 +430,265 @@ admin@example:/config/interface/br0/bridge/> set ieee-group-forward lldp
admin@example:/config/interface/br0/bridge/>
```
### Link Aggregation
A link aggregate, or *lag*, allows multiple physical interfaces to be
combined into a single logical interface, providing increased bandwidth
(in some cases) and redundancy (primarily). Two modes of qualifying lag
member ports are available:
1. **static**: Active members selected based on link status (carrier)
2. **lacp:** IEEE 802.3ad Link Aggregation Control Protocol
In LACP mode, LACPDUs are exchanged by the link partners to qualify each
lag member, while in static mode only carrier is used. This additional
exchange in LACP ensures traffic can be forwarded in both directions.
Traffic distribution, for both modes, across the active lag member ports
is determined by the hash policy[^1]. It uses an XOR of the source,
destination MAC addresses and the EtherType field. This, IEEE
802.3ad-compliant, algorithm will place all traffic to a particular
network peer on the same link. Meaning there is no increased bandwidth
for communication between two specific devices.
> [!TIP]
> Similar to other interface types, naming your interface `lagN`, where
> `N` is a number, allows the CLI to automatically infer the interface
> type as LAG.
#### Basic Configuration
Creating a link aggregate interface and adding member ports:
```
admin@example:/> configure
admin@example:/config/> edit interface lag0
admin@example:/config/interface/lag0/> set lag mode static
admin@example:/config/interface/lag0/> end
admin@example:/config/> set interface eth7 lag-port lag lag0
admin@example:/config/> set interface eth8 lag-port lag lag0
admin@example:/config/> leave
```
A static lag responds only to link (carrier) changes of member ports.
E.g., in this example egressing traffic is continuously distributed over
the two links until link down on one link is detected, triggering all
traffic to be steered to the sole remaining link.
#### LACP Configuration
LACP mode provides dynamic negotiation of the link aggregate. Key
settings include:
```
admin@example:/> configure
admin@example:/config/> edit interface lag0
admin@example:/config/interface/lag0/> set lag mode lacp
admin@example:/config/interface/lag0/> set lag lacp mode passive
admin@example:/config/interface/lag0/> set lag lacp rate fast
admin@example:/config/interface/lag0/> set lag lacp system-priority 100
```
LACP mode supports two operational modes:
- **active:** Initiates negotiation by sending LACPDUs (default)
- **passive:** Waits for peer to initiate negotiation
> [!NOTE]
> At least one end of the link must be in active mode for negotiation to occur.
The LACP rate setting controls protocol timing:
- **slow:** LACPDUs sent every 30 seconds, with 90 second timeout (default)
- **fast:** LACPDUs sent every second, with 3 second timeout
#### Link Flapping
To protect against link flapping, debounce timers can be configured to
delay link qualification. Usually only the `up` delay is needed:
```
admin@example:/config/interface/lag0/lag/link-monitor/> edit debounce
admin@example:/config/interface/lag0/lag/link-monitor/debounce/> set up 500
admin@example:/config/interface/lag0/lag/link-monitor/debounce/> set down 200
```
#### Operational Status, Overview
Like other interfaces, link aggregates are also available in the general
interfaces overview in the CLI admin-exec context. Here is the above
static mode aggregate:
```
admin@example:/> show interfaces
INTERFACE PROTOCOL STATE DATA
lo ethernet UP 00:00:00:00:00:00
ipv4 127.0.0.1/8 (static)
ipv6 ::1/128 (static)
.
.
.
lag0 lag UP static: balance-xor, hash: layer2
│ ethernet UP 00:a0:85:00:02:00
├ eth7 lag ACTIVE
└ eth8 lag ACTIVE
```
Same aggregate, but in LACP mode:
```
admin@example:/> show interfaces
INTERFACE PROTOCOL STATE DATA
lo ethernet UP 00:00:00:00:00:00
ipv4 127.0.0.1/8 (static)
ipv6 ::1/128 (static)
.
.
.
lag0 lag UP lacp: active, rate: fast (1s), hash: layer2
│ ethernet UP 00:a0:85:00:02:00
├ eth7 lag ACTIVE active, short_timeout, aggregating, in_sync, collecting, distributing
└ eth8 lag ACTIVE active, short_timeout, aggregating, in_sync, collecting, distributing
```
#### Operational Status, Detail
In addition to basic status shown in the interface overview, detailed
LAG status can be inspected:
```
admin@example:/> show interfaces name lag0
name : lag0
index : 25
mtu : 1500
operational status : up
physical address : 00:a0:85:00:02:00
lag mode : static
lag type : balance-xor
lag hash : layer2
link debounce up : 0 msec
link debounce down : 0 msec
ipv4 addresses :
ipv6 addresses :
in-octets : 0
out-octets : 2142
```
Same aggregate, but in LACP mode:
```
admin@example:/> show interfaces name lag0
name : lag0
index : 24
mtu : 1500
operational status : up
physical address : 00:a0:85:00:02:00
lag mode : lacp
lag hash : layer2
lacp mode : active
lacp rate : fast (1s)
lacp aggregate id : 1
lacp system priority: 65535
lacp actor key : 9
lacp partner key : 9
lacp partner mac : 00:a0:85:00:03:00
link debounce up : 0 msec
link debounce down : 0 msec
ipv4 addresses :
ipv6 addresses :
in-octets : 100892
out-octets : 111776
```
Member ports provide additional status information:
- Link failure counter: number of detected link failures
- LACP state flags: various states of LACP negotiation:
- `active`: port is actively sending LACPDUs
- `short_timeout`: using fast rate (1s) vs. slow rate (30s)
- `aggregating`: port is allowed to aggregate in this LAG
- `in_sync`: port is synchronized with partner
- `collecting`: port is allowed to receive traffic
- `distributing`: port is allowed to send traffic
- `defaulted`: using default partner info (partner not responding)
- `expired`: partner info has expired (no LACPDUs received)
- Aggregator ID: unique identifier for this LAG group
- Actor state: LACP state flags for this port (local)
- Partner state: LACP state flags from the remote port
Example member port status:
```
admin@example:/> show interfaces name eth7
name : eth7
index : 8
mtu : 1500
operational status : up
physical address : 00:a0:85:00:02:00
lag member : lag0
lag member state : active
lacp aggregate id : 1
lacp actor state : active, short_timeout, aggregating, in_sync, collecting, distributing
lacp partner state : active, short_timeout, aggregating, in_sync, collecting, distributing
link failure count : 0
ipv4 addresses :
ipv6 addresses :
in-octets : 473244
out-octets : 499037
```
#### Example: Switch Uplink with LACP
LACP mode provides the most robust operation, automatically negotiating
the link aggregate and detecting configuration mismatches.
A common use case is connecting a switch to an upstream device:
```
admin@example:/> configure
admin@example:/config/> edit interface lag0
admin@example:/config/interface/lag0/> set lag mode lacp
```
Enable fast LACP for quicker fail-over:
```
admin@example:/config/interface/lag0/> set lag lacp rate fast
```
Add uplink ports
```
admin@example:/config/interface/lag0/> end
admin@example:/config/> set interface eth7 lag-port lag lag0
admin@example:/config/> set interface eth8 lag-port lag lag0
```
Enable protection against "link flapping".
```
admin@example:/config/interface/lag0/> edit lag link-monitor
admin@example:/config/interface/lag0/lag/link-monitor/> edit debounce
admin@example:/config/interface/lag0/lag/link-monitor/debounce/> set up 500
admin@example:/config/interface/lag0/lag/link-monitor/debounce/> set down 200
admin@example:/config/interface/lag0/lag/link-monitor/debounce/> top
```
Add to bridge for switching
```
admin@example:/config/interface/lag0/lag/link-monitor/debounce/> end
admin@example:/config/> set interface lag0 bridge-port bridge br0
admin@example:/config/> leave
```
### VLAN Interfaces
Creating a VLAN can be done in many ways. This section assumes VLAN
@@ -1228,7 +1487,7 @@ currently supported, namely `ipv4` and `ipv6`.
[4]: https://www.rfc-editor.org/rfc/rfc3442
[0]: https://frrouting.org/
[^1]: Please note, link aggregates are not yet supported.
[^1]: `(source MAC XOR destination MAC XOR EtherType) MODULO num_links`
[^2]: Link-local IPv6 addresses are implicitly enabled when enabling
IPv6. IPv6 can be enabled/disabled per interface in the
[ietf-ip][2] YANG model.
+8 -1
View File
@@ -1,3 +1,9 @@
################################################################################
#
# confd-test-mode
#
################################################################################
CONFD_TEST_MODE_VERSION = 1.0
CONFD_TEST_MODE_SITE_METHOD = local
CONFD_TEST_MODE_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/test-mode
@@ -16,7 +22,8 @@ COMMON_SYSREPO_ENV = \
define CONFD_TEST_MODE_INSTALL_YANG_MODULES
$(COMMON_SYSREPO_ENV) \
SEARCH_PATH="$(TARGET_DIR)/usr/share/yang/modules/test-mode/" $(BR2_EXTERNAL_INFIX_PATH)/utils/sysrepo-load-modules.sh $(@D)/yang/test-mode.inc
SEARCH_PATH="$(TARGET_DIR)/usr/share/yang/modules/test-mode/" \
$(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/test-mode.inc
endef
define CONFD_TEST_MODE_PERMISSIONS
/etc/sysrepo/data/ r 660 root wheel - - - - -
+2 -4
View File
@@ -41,13 +41,13 @@ COMMON_SYSREPO_ENV = \
define CONFD_INSTALL_YANG_MODULES
$(COMMON_SYSREPO_ENV) \
$(BR2_EXTERNAL_INFIX_PATH)/utils/sysrepo-load-modules.sh $(@D)/yang/confd.inc
$(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/confd.inc
endef
ifeq ($(BR2_PACKAGE_PODMAN),y)
define CONFD_INSTALL_YANG_MODULES_CONTAINERS
$(COMMON_SYSREPO_ENV) \
$(BR2_EXTERNAL_INFIX_PATH)/utils/sysrepo-load-modules.sh $(@D)/yang/containers.inc
$(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/containers.inc
endef
endif
@@ -63,9 +63,7 @@ endef
CONFD_PRE_INSTALL_TARGET_HOOKS += CONFD_CLEANUP
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_EXTRA
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES
ifeq ($(BR2_PACKAGE_PODMAN),y)
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_CONTAINERS
endif
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_CLEANUP
$(eval $(autotools-package))
@@ -1,18 +1,15 @@
From 773a0e32210336d682148e2e4c87add5e69105ec Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Tue, 12 Mar 2024 10:27:24 +0100
Subject: [PATCH 01/25] [FIX] net: dsa: mv88e6xxx: Fix timeout on waiting for
Subject: [PATCH 01/26] [FIX] net: dsa: mv88e6xxx: Fix timeout on waiting for
PPU on 6393X
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
In a multi-chip setup, delays of up to 750ms are observed before the
device (6393X) signals completion of PPU initialization (Global 1,
register 0, bit 15). Therefore, increase the timeout threshold to 1s.
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
drivers/net/dsa/mv88e6xxx/chip.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
@@ -1,11 +1,8 @@
From 765cc95de163acf8022b093ce227184e04b49d7b Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Wed, 27 Mar 2024 15:52:43 +0100
Subject: [PATCH 02/25] net: dsa: mv88e6xxx: Improve indirect register access
Subject: [PATCH 02/26] net: dsa: mv88e6xxx: Improve indirect register access
perf on 6393
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
When operating in multi-chip mode, the 6393 family maps a subset of
@@ -17,7 +14,7 @@ Therefore, add a new set of SMI operations which remaps accesses to
such registers to the corresponding directly addressable register. All
other accesses use the regular indirect interface.
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
drivers/net/dsa/mv88e6xxx/chip.c | 7 +++
drivers/net/dsa/mv88e6xxx/global1.h | 3 ++
@@ -1,11 +1,8 @@
From eb1ba5920254e04daff5c9a09e90b8882bf34490 Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Mon, 22 Apr 2024 23:18:01 +0200
Subject: [PATCH 03/25] net: dsa: mv88e6xxx: Honor ports being managed via
Subject: [PATCH 03/26] net: dsa: mv88e6xxx: Honor ports being managed via
in-band-status
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
Keep all link parameters in their unforced states when the port is
@@ -17,7 +14,7 @@ This state is the default set up by mv88e6xxx_port_setup_mac(), so all
we have to do is to make the phylink MAC callbacks no-ops in cases
when in-band-status is being used.
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
drivers/net/dsa/mv88e6xxx/chip.c | 6 ++++++
1 file changed, 6 insertions(+)
@@ -1,11 +1,8 @@
From 8b3590e253746d5704650889c106cf5454e26cd3 Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Wed, 24 Apr 2024 22:41:04 +0200
Subject: [PATCH 04/25] net: dsa: mv88e6xxx: Limit rsvd2cpu policy to user
Subject: [PATCH 04/26] net: dsa: mv88e6xxx: Limit rsvd2cpu policy to user
ports on 6393X
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
For packets with a DA in the IEEE reserved L2 group range, originating
@@ -33,7 +30,7 @@ switch would try to trap it back to the CPU. Given that the CPU is
trusted, instead assume that it indeed meant for the packet to be
forwarded like any other.
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
drivers/net/dsa/mv88e6xxx/port.c | 31 +++++++++++++++++++++++++------
1 file changed, 25 insertions(+), 6 deletions(-)
@@ -1,16 +1,13 @@
From 3c13e341f9e18cdd819584086bed0d207a3f042a Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Thu, 16 Nov 2023 19:44:32 +0100
Subject: [PATCH 05/25] net: dsa: mv88e6xxx: Add LED infrastructure
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Subject: [PATCH 05/26] net: dsa: mv88e6xxx: Add LED infrastructure
Organization: Wires
Parse DT for LEDs and register them for devices that support it,
though no actual implementations exist yet.
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
drivers/net/dsa/mv88e6xxx/Makefile | 1 +
drivers/net/dsa/mv88e6xxx/chip.c | 5 +-
@@ -1,10 +1,7 @@
From eb8d96aab3ee81c40252f8c5e1628b3709e23cab Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Thu, 16 Nov 2023 21:59:35 +0100
Subject: [PATCH 06/25] net: dsa: mv88e6xxx: Add LED support for 6393X
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Subject: [PATCH 06/26] net: dsa: mv88e6xxx: Add LED support for 6393X
Organization: Wires
Trigger support:
@@ -12,7 +9,7 @@ Trigger support:
- "timer"
- "netdev"
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
drivers/net/dsa/mv88e6xxx/chip.c | 1 +
drivers/net/dsa/mv88e6xxx/leds.c | 229 +++++++++++++++++++++++++++++++
@@ -1,11 +1,8 @@
From 06a7be8d8ca0241b89504595b0838ecf25b6c768 Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Tue, 28 May 2024 10:38:42 +0200
Subject: [PATCH 07/25] net: dsa: tag_dsa: Use tag priority as initial
Subject: [PATCH 07/26] net: dsa: tag_dsa: Use tag priority as initial
skb->priority
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
Use the 3-bit priority field from the DSA tag as the initial packet
@@ -23,7 +20,7 @@ can do with an "ingress-qos-map" on VLAN interfaces. Until that is
implemented, support the setup that is likely to be the most common; a
1:1 mapping from FPri to skb->priority.
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
net/dsa/tag_dsa.c | 7 +++++++
1 file changed, 7 insertions(+)
@@ -1,11 +1,8 @@
From 0312ac7f2a3d956ff9aec3f84b3b64302e59d36c Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Tue, 16 Jan 2024 16:00:55 +0100
Subject: [PATCH 08/25] net: dsa: Support MDB memberships whose L2 addresses
Subject: [PATCH 08/26] net: dsa: Support MDB memberships whose L2 addresses
overlap
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
Multiple IP multicast groups (32 for v4, 2^80 for v6) map to the same
@@ -33,7 +30,7 @@ needed to do this is already in place, since it is also needed on CPU
and DSA ports. Thus, "implement" this by simply removing the guards
which previously skipped reference countung on user ports.
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
net/dsa/switch.c | 16 ----------------
1 file changed, 16 deletions(-)
@@ -1,13 +1,10 @@
From 45716feb5762311a384f38d8b6449fc652a73764 Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Thu, 21 Mar 2024 19:12:15 +0100
Subject: [PATCH 09/25] net: dsa: Support EtherType based priority overrides
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Subject: [PATCH 09/26] net: dsa: Support EtherType based priority overrides
Organization: Wires
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
include/net/dsa.h | 4 ++++
net/dsa/user.c | 56 +++++++++++++++++++++++++++++++++++++++++++++--
@@ -1,14 +1,11 @@
From d2802357f81b4995c92fdd6f2ab8ea923b69cef1 Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Fri, 22 Mar 2024 16:15:43 +0100
Subject: [PATCH 10/25] net: dsa: mv88e6xxx: Support EtherType based priority
Subject: [PATCH 10/26] net: dsa: mv88e6xxx: Support EtherType based priority
overrides
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
drivers/net/dsa/mv88e6xxx/chip.c | 64 +++++++++++++++++++++++++++++
drivers/net/dsa/mv88e6xxx/chip.h | 21 ++++++++++
@@ -1,10 +1,7 @@
From 985f9b471326f65d2c29978b511181200c1b32dd Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Tue, 28 May 2024 11:04:22 +0200
Subject: [PATCH 11/25] net: dsa: mv88e6xxx: Add mqprio qdisc support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Subject: [PATCH 11/26] net: dsa: mv88e6xxx: Add mqprio qdisc support
Organization: Wires
Add support for attaching mqprio qdisc's to mv88e6xxx ports and use
@@ -28,7 +25,7 @@ Since FPri is always a 3-bit field, even on older chips with only 4
physical queues, always report 8 queues and let the chip's policy
handle the mapping down to the "real" number.
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
drivers/net/dsa/mv88e6xxx/chip.c | 70 ++++++++++++++++++++++++++++++++
net/dsa/tag_dsa.c | 4 +-
@@ -1,11 +1,8 @@
From da95446b056b1efc27353d2549298580978a079f Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Wed, 29 May 2024 13:20:41 +0200
Subject: [PATCH 12/25] net: dsa: mv88e6xxx: Use VLAN prio over IP when both
Subject: [PATCH 12/26] net: dsa: mv88e6xxx: Use VLAN prio over IP when both
are available
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
Switch the priority sourcing precdence to prefer VLAN PCP over IP
@@ -26,7 +23,7 @@ main reasons for choosing the new default:
core over trusted VLAN trunks, the packet should keep its original
priority, independent of what inner protocol fields may indicate.
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
drivers/net/dsa/mv88e6xxx/chip.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
@@ -1,11 +1,8 @@
From e4a71c66724ba89a37dee71db5ae9e555676547e Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Tue, 26 Nov 2024 19:45:59 +0100
Subject: [PATCH 13/25] [FIX] net: dsa: mv88e6xxx: Trap locally terminated
Subject: [PATCH 13/26] [FIX] net: dsa: mv88e6xxx: Trap locally terminated
VLANs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
Before this change, in a setup like the following, packets assigned to
@@ -28,7 +25,7 @@ marked as policy entries. As the VTU policy of user ports is already
set to TRAP (to ensure proper standalone port operation), this will
cause all packets assigned to these VLANs to properly terminated.
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
drivers/net/dsa/mv88e6xxx/chip.c | 33 ++++++++++++++++++--------------
include/net/switchdev.h | 4 ++++
@@ -1,11 +1,8 @@
From 9ade37c2fa9f5a6faebeaa8894d9703624eae5ef Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Tue, 19 Sep 2023 18:38:10 +0200
Subject: [PATCH 14/25] net: phy: marvell10g: Support firmware loading on
Subject: [PATCH 14/26] net: phy: marvell10g: Support firmware loading on
88X3310
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
When probing, if a device is waiting for firmware to be loaded into
@@ -15,7 +12,7 @@ We have no choice but to bail out of the probe if firmware is not
available, as the device does not have any built-in image on which to
fall back.
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
drivers/net/phy/marvell10g.c | 161 +++++++++++++++++++++++++++++++++++
1 file changed, 161 insertions(+)
@@ -1,18 +1,15 @@
From 9571b0e79717fbcef4540b9a20a4fb2fe4c57f26 Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Tue, 21 Nov 2023 20:15:24 +0100
Subject: [PATCH 15/25] net: phy: marvell10g: Fix power-up when strapped to
Subject: [PATCH 15/26] net: phy: marvell10g: Fix power-up when strapped to
start powered down
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
On devices which are hardware strapped to start powered down (PDSTATE
== 1), make sure that we clear the power-down bit on all units
affected by this setting.
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
drivers/net/phy/marvell10g.c | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
@@ -1,10 +1,7 @@
From 05a6f49a221e797fdbd21501902bd969b9c64640 Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Wed, 15 Nov 2023 20:58:42 +0100
Subject: [PATCH 16/25] net: phy: marvell10g: Add LED support for 88X3310
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Subject: [PATCH 16/26] net: phy: marvell10g: Add LED support for 88X3310
Organization: Wires
Pickup the LEDs from the state in which the hardware reset or
@@ -21,7 +18,7 @@ Trigger support:
- "netdev": Offload link or duplex information to the solid behavior;
tx and/or rx activity to blink behavior.
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
drivers/net/phy/marvell10g.c | 422 +++++++++++++++++++++++++++++++++++
1 file changed, 422 insertions(+)
@@ -1,11 +1,8 @@
From 202a7fccb4d46e38ed6f82788a44cf7320377549 Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Tue, 12 Dec 2023 09:51:05 +0100
Subject: [PATCH 17/25] net: phy: marvell10g: Support LEDs tied to a single
Subject: [PATCH 17/26] net: phy: marvell10g: Support LEDs tied to a single
media side
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
In a combo-port setup, i.e. where both the copper and fiber interface
@@ -18,7 +15,7 @@ the offloading of the "netdev" trigger, such that LEDs attached to the
RJ45 jack only lights up when a copper link is established, and vice
versa for the SFP cage.
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
drivers/net/phy/marvell10g.c | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
@@ -1,10 +1,7 @@
From 31f2aa0e957643cc51be65750c4f65be8c507a18 Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Wed, 27 Mar 2024 10:10:19 +0100
Subject: [PATCH 18/25] net: phy: Do not resume PHY when attaching
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Subject: [PATCH 18/26] net: phy: Do not resume PHY when attaching
Organization: Wires
The PHY should not start negotiating with its link-partner until
@@ -19,7 +16,7 @@ probing (e.g. DSA) would end up with a physical link being
established, even though the corresponding interface was still
administratively down.
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
drivers/net/phy/phy_device.c | 1 -
1 file changed, 1 deletion(-)
@@ -1,11 +1,8 @@
From 83c259d997fa40f0b9b9a3250746531df9c730c3 Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
Date: Mon, 4 Mar 2024 16:47:28 +0100
Subject: [PATCH 19/25] net: bridge: avoid classifying unknown multicast as
Subject: [PATCH 19/26] net: bridge: avoid classifying unknown multicast as
mrouters_only
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
Unknown multicast, MAC/IPv4/IPv6, should always be flooded according to
@@ -19,7 +16,7 @@ Because a multicast router should always receive both known and unknown
multicast.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
include/uapi/linux/if_bridge.h | 1 +
net/bridge/br.c | 5 +++++
@@ -1,11 +1,8 @@
From f2d2f489eb2073e79d3908a4f9abe797ce5c5a17 Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
Date: Tue, 5 Mar 2024 06:44:41 +0100
Subject: [PATCH 20/25] net: bridge: Ignore router ports when forwarding L2
Subject: [PATCH 20/26] net: bridge: Ignore router ports when forwarding L2
multicast
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
Multicast router ports are either statically configured or learned from
@@ -15,7 +12,6 @@ of unknown multicast or using permanent MDB entries.
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
net/bridge/br_private.h | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
@@ -1,11 +1,8 @@
From 4a88856e21984b9292361ad77d7dfc13fad239a5 Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
Date: Thu, 4 Apr 2024 16:36:30 +0200
Subject: [PATCH 21/25] net: bridge: drop delay for applying strict multicast
Subject: [PATCH 21/26] net: bridge: drop delay for applying strict multicast
filtering
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
This *local* patch drops the initial delay before applying strict multicast
@@ -22,7 +19,7 @@ A proper fix for upstreaming could be to add a knob to disable the delay.
[2]: https://lore.kernel.org/netdev/20240127175033.9640-1-linus.luessing@c0d3.blue/
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
net/bridge/br_multicast.c | 42 +++++++--------------------------------
net/bridge/br_private.h | 4 +---
@@ -1,11 +1,8 @@
From 12fe155cedae381d143a0b61b5375d1edb7c46e6 Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Thu, 16 May 2024 14:51:54 +0200
Subject: [PATCH 22/25] net: bridge: Differentiate MDB additions from
Subject: [PATCH 22/26] net: bridge: Differentiate MDB additions from
modifications
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
Before this change, the reception of an IGMPv3 report (and analogously
@@ -25,7 +22,7 @@ generated.
Therefore, discriminate new groups from changes to existing groups by
introducing a RTM_SETMDB events to be used in the latter scenario.
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
include/uapi/linux/rtnetlink.h | 2 ++
net/bridge/br_mdb.c | 4 ++--
@@ -1,11 +1,8 @@
From d8d81ca2a348095d87110d3d6a65134f1b7ce212 Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Fri, 24 Nov 2023 23:29:55 +0100
Subject: [PATCH 23/25] nvmem: layouts: onie-tlv: Let device probe even when
Subject: [PATCH 23/26] nvmem: layouts: onie-tlv: Let device probe even when
TLV is invalid
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
Before this change, probing an NVMEM device, expected to contain a
@@ -17,7 +14,7 @@ be successfully probed.
Therefore, settle for reporting data corruption issues in the log, and
simply refrain from registering any cells in those cases.
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
drivers/nvmem/layouts/onie-tlv.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
@@ -1,10 +1,7 @@
From 49592053a901870104c3ea6f6d4a4c18711c955d Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
Date: Mon, 29 Apr 2024 15:14:51 +0200
Subject: [PATCH 24/25] usb: core: adjust log level for unauthorized devices
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Subject: [PATCH 24/26] usb: core: adjust log level for unauthorized devices
Organization: Wires
The fact that a USB device currently is not authorized is not an error,
@@ -12,7 +9,7 @@ so let's adjust the log level so these messages slip below radar for the
commonly used 'quiet' log level.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
drivers/usb/core/driver.c | 4 ++--
drivers/usb/core/generic.c | 2 +-
@@ -1,11 +1,8 @@
From a86053426ab5a0e9155e0b4c7c20495964fa4b59 Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
Date: Thu, 16 Jan 2025 12:35:12 +0100
Subject: [PATCH 25/25] net: dsa: mv88e6xxx: collapse disabled state into
Subject: [PATCH 25/26] net: dsa: mv88e6xxx: collapse disabled state into
blocking
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
This patch changes the behavior of switchcore ports wrt. the port state.
@@ -21,7 +18,7 @@ each member port, preventing LACPDUs from passing through to qualify the
link and become active.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
drivers/net/dsa/mv88e6xxx/port.c | 2 --
1 file changed, 2 deletions(-)
@@ -0,0 +1,147 @@
From a98487405e1a7b8b69569a3e5f7765f395c36f55 Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Wed, 12 Feb 2025 22:03:14 +0100
Subject: [PATCH 26/26] net: dsa: mv88e6xxx: Only activate LAG offloading when
bridged
Organization: Wires
The current port isolation scheme for mv88e6xxx is detailed here:
https://lore.kernel.org/netdev/20220203101657.990241-1-tobias@waldekranz.com/
As it turns out, this is not compatible with LAGs. Consider the
following setup:
.-----.
| CPU |
'--+--'
|
.--0--. .-----.
| sw1 9---0 sw2 |
'-----' '-4-5-'
A LAG is created from sw2p{4,5}, using ID 0, but it is not attached
to any bridge - so port isolation is active. Let's walk through a
packet's journey to the CPU:
1. Packet ingresses sw2p4, the MapDA bit is not set, so it is flooded
according to the PVT, which only contains sw2p0
2. Packet egresses sw2p0 with FORWARD vid:0 dev:2 port:0(lag)
3. Packet ingresses sw1p9, the VTU policy bit is set for VLAN 0, thus
the packet is classified as MGMT and trapped to the CPU
4. Packet egresses sw1p0 with TO_CPU vid:0 dev:2 port:0
5. Packet ingresses CPU, since no user port is mapped to sw2p0 the
packet is dropped
The problem is that in step 2, the original source port information is
lost (replaced with "lag 0"), and then sw1 rewrites the ingressing
FORWARD to a TO_CPU, which does not have a LAG bit. As a result, after
the translation between steps 3 and 4, it now looks as if the packet
originally ingressed on sw2p0.
Therefore, defer enabling the LAG offload until it joins a bridge. In
the example above, it means that the original source port (sw2p4)
information will be in the FORWARD sent in step 2, which allows the
existing port isolation to work as intended.
Before joining a bridge, the offload does not offer any performance
benefit anyway. All ingressing packet will always have to go to the
CPU; egress traffic always relies on software hashing.
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
drivers/net/dsa/mv88e6xxx/chip.c | 42 +++++++++++++++-----------------
1 file changed, 20 insertions(+), 22 deletions(-)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 785cd3d0e2b1..37e79d52c175 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -3065,6 +3065,7 @@ static int mv88e6xxx_port_bridge_join(struct dsa_switch *ds, int port,
struct netlink_ext_ack *extack)
{
struct mv88e6xxx_chip *chip = ds->priv;
+ unsigned int lagid;
int err;
mv88e6xxx_reg_lock(chip);
@@ -3073,6 +3074,13 @@ static int mv88e6xxx_port_bridge_join(struct dsa_switch *ds, int port,
if (err)
goto unlock;
+ if ((lagid = dsa_port_lag_id_get(dsa_to_port(ds, port)))) {
+ /* DSA LAG IDs are one-based */
+ err = mv88e6xxx_port_set_trunk(chip, port, true, lagid - 1);
+ if (err)
+ goto unlock;
+ }
+
err = mv88e6xxx_port_set_map_da(chip, port, true);
if (err)
goto unlock;
@@ -3117,6 +3125,14 @@ static void mv88e6xxx_port_bridge_leave(struct dsa_switch *ds, int port,
"port %d failed to restore map-DA: %pe\n",
port, ERR_PTR(err));
+ if (dsa_port_lag_id_get(dsa_to_port(ds, port))) {
+ err = mv88e6xxx_port_set_trunk(chip, port, false, 0);
+ if (err)
+ dev_err(ds->dev,
+ "port %d failed to disable trunking: %pe\n",
+ port, ERR_PTR(err));
+ }
+
err = mv88e6xxx_port_commit_pvid(chip, port);
if (err)
dev_err(ds->dev,
@@ -6992,30 +7008,13 @@ static int mv88e6xxx_port_lag_join(struct dsa_switch *ds, int port,
struct netlink_ext_ack *extack)
{
struct mv88e6xxx_chip *chip = ds->priv;
- int err, id;
+ int err;
if (!mv88e6xxx_lag_can_offload(ds, lag, info, extack))
return -EOPNOTSUPP;
- /* DSA LAG IDs are one-based */
- id = lag.id - 1;
-
mv88e6xxx_reg_lock(chip);
-
- err = mv88e6xxx_port_set_trunk(chip, port, true, id);
- if (err)
- goto err_unlock;
-
err = mv88e6xxx_lag_sync_masks_map(ds, lag);
- if (err)
- goto err_clear_trunk;
-
- mv88e6xxx_reg_unlock(chip);
- return 0;
-
-err_clear_trunk:
- mv88e6xxx_port_set_trunk(chip, port, false, 0);
-err_unlock:
mv88e6xxx_reg_unlock(chip);
return err;
}
@@ -7024,13 +7023,12 @@ static int mv88e6xxx_port_lag_leave(struct dsa_switch *ds, int port,
struct dsa_lag lag)
{
struct mv88e6xxx_chip *chip = ds->priv;
- int err_sync, err_trunk;
+ int err;
mv88e6xxx_reg_lock(chip);
- err_sync = mv88e6xxx_lag_sync_masks_map(ds, lag);
- err_trunk = mv88e6xxx_port_set_trunk(chip, port, false, 0);
+ err = mv88e6xxx_lag_sync_masks_map(ds, lag);
mv88e6xxx_reg_unlock(chip);
- return err_sync ? : err_trunk;
+ return err;
}
static int mv88e6xxx_crosschip_lag_change(struct dsa_switch *ds, int sw_index,
--
2.43.0
+1 -1
View File
@@ -157,7 +157,7 @@ do_prune()
local keep=${1:-10}
cd "$basedir"
rm -f old=$(ls -rv *.tar.gz | tail "+$((keep + 1))")
rm -f old=$(ls -rv *.tar.gz 2>/dev/null | tail "+$((keep + 1))")
}
usage()
+1
View File
@@ -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 \
+25 -3
View File
@@ -87,10 +87,32 @@ FILE *dagger_fopen_net_exit(struct dagger *d, const char *node, enum netdag_exit
return dagger_fopen_current(d, "exit", node, order, script);
}
int dagger_add_dep(struct dagger *d, const char *depender, const char *dependee)
int dagger_add_dep(const struct dagger *d, const char *depender, const char *dependee)
{
return systemf("ln -s ../%s %s/%d/dag/%s", dependee,
d->path, d->next, depender);
char link[strlen(d->path) + strlen(depender) + strlen(dependee) + 16];
char target[strlen(dependee) + 16];
char path[strlen(dependee) + 16];
ssize_t len;
/*
* Some callbacks may run twice, double check symlink, if it
* exists already and points to the same target, we're OK.
*/
snprintf(target, sizeof(target), "../%s", dependee);
snprintf(link, sizeof(link), "%s/%d/dag/%s/%s", d->path, d->next, depender, dependee);
len = readlink(link, path, sizeof(path));
if (len > 0) {
path[len] = 0;
if (strcmp(target, path)) {
ERROR("Dagger dependency already exists %s -> %s", target, path);
return errno = EEXIST;
}
return 0; /* same, ignore */
}
return symlink(target, link);
}
int dagger_add_node(struct dagger *d, const char *node)
+1 -1
View File
@@ -21,7 +21,7 @@ FILE *dagger_fopen_next(struct dagger *d, const char *action, const char *node,
FILE *dagger_fopen_current(struct dagger *d, const char *action, const char *node,
unsigned char prio, const char *script);
int dagger_add_dep(struct dagger *d, const char *depender, const char *dependee);
int dagger_add_dep(const struct dagger *d, const char *depender, const char *dependee);
int dagger_add_node(struct dagger *d, const char *node);
int dagger_abandon(struct dagger *d);
int dagger_evolve(struct dagger *d);
+20 -5
View File
@@ -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:
+23 -5
View File
@@ -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);
+2 -2
View File
@@ -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;
/*
+189
View File
@@ -0,0 +1,189 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#include <fnmatch.h>
#include <stdbool.h>
#include <jansson.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <srx/common.h>
#include <srx/lyx.h>
#include <srx/srx_val.h>
#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;
}
+2 -1
View File
@@ -1,5 +1,6 @@
# -*- sh -*-
# REMEMBER TO UPDATE infix-interfaces ALSO IN containers.inc
#FORCE_UPDATE="infix-interfaces"
MODULES=(
"ietf-system@2014-08-06.yang -e authentication -e local-users -e ntp -e ntp-udp-port -e timezone-name"
"iana-timezones@2013-11-19.yang"
@@ -39,7 +40,7 @@ MODULES=(
"ieee802-ethernet-interface@2019-06-21.yang"
"infix-ethernet-interface@2024-02-27.yang"
"infix-factory-default@2023-06-28.yang"
"infix-interfaces@2025-01-08.yang -e vlan-filtering"
"infix-interfaces@2025-01-09.yang -e vlan-filtering"
"infix-crypto-types@2025-02-04.yang"
"infix-keystore@2025-02-04.yang"
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- sh -*-
# REMEMBER TO UPDATE infix-interfaces ALSO IN confd.inc
MODULES=(
"infix-interfaces@2025-01-08.yang -e vlan-filtering -e containers"
"infix-interfaces@2025-01-09.yang -e vlan-filtering -e containers"
"infix-containers@2024-11-15.yang"
)
-3
View File
@@ -15,9 +15,6 @@ submodule infix-if-bridge {
import ietf-interfaces {
prefix if;
}
import ietf-ip {
prefix ip;
}
import ieee802-dot1q-types {
prefix dot1q-types;
}
+336
View File
@@ -0,0 +1,336 @@
submodule infix-if-lag {
yang-version 1.1;
belongs-to infix-interfaces {
prefix infix-if;
}
import ietf-interfaces {
prefix if;
}
import iana-if-type {
prefix ianaift;
}
import ietf-inet-types {
prefix inet;
}
import ietf-yang-types {
prefix yang;
}
organization "KernelKit";
contact "kernelkit@googlegroups.com";
description "Linux link aggregates (lag) for ietf-interfaces.";
revision 2025-01-09 {
description "Initial revision.";
reference "internal";
}
/*
* Typedefs
*/
typedef lag-mode {
description "Mode values for link aggregates.";
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.";
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;
}
}
/*
* Shared settings
*/
grouping hash {
leaf hash {
description "Transmit hash policy.";
type hash-policy;
config false; // For now, staically set to layer2 only
}
}
/*
* Data Nodes
*/
augment "/if:interfaces/if:interface" {
when "derived-from-or-self(if:type,'ianaift:ieee8023adLag')" {
description "Only shown for if:type bridge";
}
description "Augment generic interfaces with link aggregates.";
container lag {
description "Link aggregates are for load balancing and fault tolerance.";
leaf mode {
description "Link aggregation mode.";
type lag-mode;
mandatory true;
}
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.";
container debounce {
description "Link flapping protection.";
leaf up {
description "Wait before enabling link after link up.";
type uint32 {
range "0 .. 30000";
}
units "milliseconds";
}
leaf down {
description "Wait before disabling link after link down.";
type uint32 {
range "0 .. 30000";
}
units "milliseconds";
}
}
}
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.";
}
}
}
augment "/if:interfaces/if:interface/infix-if:port" {
when "derived-from-or-self(if:type,'ianaift:ethernetCsmacd') or "+
"derived-from-or-self(if:type,'ianaift:ilan')" {
description "Applies when a LAG interface exists.";
}
description "Augments the interface model with the link aggregate member.";
case lag-port {
description "Extension of the IETF Interfaces model (RFC7223).";
container lag-port {
leaf lag {
description "LAG interface to which this interface is a member of.";
type if:interface-ref;
mandatory true;
must "deref(.)/../lag" {
error-message "Must refer to a valid LAG interface.";
}
}
leaf state {
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;
}
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 lacp-state;
}
leaf-list partner-state {
description "LACP state flags for link partner.";
type lacp-state;
}
}
}
}
}
}
+1
View File
@@ -0,0 +1 @@
infix-if-lag.yang
+7
View File
@@ -18,6 +18,7 @@ module infix-interfaces {
include infix-if-base;
include infix-if-bridge;
include infix-if-lag;
include infix-if-container;
include infix-if-veth;
include infix-if-vlan;
@@ -28,6 +29,11 @@ module infix-interfaces {
contact "kernelkit@googlegroups.com";
description "Linux bridge and lag extensions for ietf-interfaces.";
revision 2025-01-09 {
description "Add support for link aggregation, static and LACP.";
reference "internal";
}
revision 2025-01-08 {
description "Add Spanning Tree Protocol (STP) support to bridges.";
reference "internal";
@@ -37,6 +43,7 @@ module infix-interfaces {
description "Allow IP addresses directly on VLAN filtering bridges.";
reference "internal";
}
revision 2024-11-15 {
description "Two changes:
- Limit name 1-15 chars, Linux limitation
+2 -2
View File
@@ -401,11 +401,11 @@
</SWITCH>
<ACTION sym="script" in="tty" out="tty" interrupt="true">
if [ -n "$KLISH_PARAM_name" ]; then
sysrepocfg -f json -X -d operational -x \
doas sysrepocfg -f json -X -d operational -x \
"/ietf-interfaces:interfaces/interface[name=\"$KLISH_PARAM_name\"]" | \
/usr/libexec/statd/cli-pretty "show-interfaces" -n "$KLISH_PARAM_name" |pager
else
sysrepocfg -f json -X -d operational -m ietf-interfaces | \
doas sysrepocfg -f json -X -d operational -m ietf-interfaces | \
/usr/libexec/statd/cli-pretty "show-interfaces" |pager
fi
</ACTION>
+118 -1
View File
@@ -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)
@@ -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
@@ -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
+7 -2
View File
@@ -1,18 +1,23 @@
from .common import LOG
from .host import HOST
# Catch errors (check=True), at this point we've run 'podman ps' (below)
def podman_inspect(name):
"""Call podman inspect {name}, return object at {path} or None."""
cmd = ['podman', 'inspect', name]
try:
return HOST.run_json(cmd, default=[])
except Exception as e:
LOG(f"Error running podman inspect: {e}")
LOG.error(f"failed podman inspect: {e}")
return []
# Ignore any errors here, may be called on a build without containers
def podman_ps():
"""We list *all* containers, not just those in the configuraion."""
return HOST.run_json("podman ps -a --format=json".split(), default=[])
cmd = ['podman', 'ps', '-a', '--format=json']
return HOST.run_json(cmd, default=[])
def network(ps, inspect):
+4
View File
@@ -33,6 +33,10 @@ include::bridge_vlan_separation/Readme.adoc[]
include::dual_bridge/Readme.adoc[]
include::lag_basic/Readme.adoc[]
include::lag_failure/Readme.adoc[]
include::igmp_basic/Readme.adoc[]
include::igmp_vlan/Readme.adoc[]
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 549 KiB

@@ -1,15 +1,20 @@
=== Bridge VLAN
==== Description
Basic test of VLAN functionality in a bridge, tagged/untagged traffic and a VLAN interface in the bridge.
....
¦ ¦
¦ vlan10 IP:10.0.0.2 ¦ br0 IP:10.0.0.3
¦ / ¦ /
¦ br0 <-- VLAN filtering ¦ link.10
¦ u/ \t ¦ /
PC ------data link -----------------|-- link
¦ dut1 ¦ dut2
....
Verify VLAN filtering bridge, with a VLAN trunk to a neighboring device,
which in turn untags one VLAN outisde a non-VLAN filtering bridge.
.Logical network setup
ifdef::topdoc[]
image::../../test/case/ietf_interfaces/bridge_vlan/bridge-vlan.svg[]
endif::topdoc[]
ifndef::topdoc[]
ifdef::testgroup[]
image::bridge_vlan/bridge-vlan.svg[]
endif::testgroup[]
ifndef::testgroup[]
image::bridge-vlan.svg[]
endif::testgroup[]
endif::topdoc[]
==== Topology
ifdef::topdoc[]
+18 -41
View File
@@ -1,32 +1,22 @@
#!/usr/bin/env python3
"""
Bridge VLAN
"""Bridge VLAN
Basic test of VLAN functionality in a bridge, tagged/untagged traffic and a VLAN interface in the bridge.
....
¦ ¦
¦ vlan10 IP:10.0.0.2 ¦ br0 IP:10.0.0.3
¦ / ¦ /
¦ br0 <-- VLAN filtering ¦ link.10
¦ u/ \\t ¦ /
PC ------data link -----------------|-- link
¦ dut1 ¦ dut2
....
Verify VLAN filtering bridge, with a VLAN trunk to a neighboring device,
which in turn untags one VLAN outisde a non-VLAN filtering bridge.
.Logical network setup
image::bridge-vlan.svg[]
"""
import infamy
with infamy.Test() as test:
with test.step("Set up topology and attach to target DUT"):
env = infamy.Env()
env = infamy.Env()
dut1 = env.attach("dut1", "mgmt")
dut2 = env.attach("dut2", "mgmt")
_, dut1_e0 = env.ltop.xlate("dut1", "data")
_, dut1_e1 = env.ltop.xlate("dut1", "link")
_, dut2_e0 = env.ltop.xlate("dut2", "link")
with test.step("Configure DUTs"):
dut1.put_config_dict("ietf-interfaces", {
"interfaces": {
@@ -34,24 +24,20 @@ with infamy.Test() as test:
{
"name": "br0",
"type": "infix-if-type:bridge",
"enabled": True,
"bridge": {
"vlans": {
"pvid": 4094,
"vlan": [
{
"vid": 10,
"untagged": [ dut1_e0 ],
"tagged": [ "br0", dut1_e1 ]
"untagged": [dut1["data"]],
"tagged": [dut1["link"], "br0"]
}
]
}
}
},
{
}, {
"name": "vlan10",
"type": "infix-if-type:vlan",
"enabled": True,
"vlan": {
"lower-layer-if": "br0",
"id": 10,
@@ -64,18 +50,14 @@ with infamy.Test() as test:
}
]
}
},
{
"name": dut1_e0,
"enabled": True,
}, {
"name": dut1["data"],
"infix-interfaces:bridge-port": {
"pvid": 10,
"bridge": "br0"
}
},
{
"name": dut1_e1,
"enabled": True,
}, {
"name": dut1["link"],
"infix-interfaces:bridge-port": {
"pvid": 10,
"bridge": "br0"
@@ -91,7 +73,6 @@ with infamy.Test() as test:
{
"name": "br0",
"type": "infix-if-type:bridge",
"enabled": True,
"ipv4": {
"address": [
{
@@ -100,17 +81,13 @@ with infamy.Test() as test:
}
]
}
},
{
"name": dut2_e0,
"enabled": True
},
{
}, {
"name": dut2["link"],
}, {
"name": "e0.10",
"type": "infix-if-type:vlan",
"enabled": True,
"vlan": {
"lower-layer-if": dut2_e0,
"lower-layer-if": dut2["link"],
"id": 10,
},
"infix-interfaces:bridge-port": {
@@ -35,6 +35,12 @@
- name: ipv4_autoconf
case: ipv4_autoconf/test.py
- name: lag_basic
case: lag_basic/test.py
- name: lag_failure
case: lag_failure/test.py
- name: bridge_fwd_sgl_dut
case: bridge_fwd_sgl_dut/test.py
+1
View File
@@ -0,0 +1 @@
lag_basic.adoc
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 465 KiB

@@ -0,0 +1,43 @@
=== Ling Aggregation Basic
==== Description
Verify communication over a link aggregate in static and LACP operating
modes during basic failure scenarios.
.Internal network setup, PC verifies connectivity with dut2 via dut1
ifdef::topdoc[]
image::../../test/case/ietf_interfaces/lag_basic/lag-basic.svg[Internal networks]
endif::topdoc[]
ifndef::topdoc[]
ifdef::testgroup[]
image::lag_basic/lag-basic.svg[Internal networks]
endif::testgroup[]
ifndef::testgroup[]
image::lag-basic.svg[Internal networks]
endif::testgroup[]
endif::topdoc[]
The host verifies connectivity with dut2 via dut1 over the aggregate for
each test step using the `mon` interface.
==== Topology
ifdef::topdoc[]
image::{topdoc}../../test/case/ietf_interfaces/lag_basic/topology.svg[Ling Aggregation Basic topology]
endif::topdoc[]
ifndef::topdoc[]
ifdef::testgroup[]
image::lag_basic/topology.svg[Ling Aggregation Basic topology]
endif::testgroup[]
ifndef::testgroup[]
image::topology.svg[Ling Aggregation Basic topology]
endif::testgroup[]
endif::topdoc[]
==== Test sequence
. Set up topology and attach to target DUTs
. Set up LACP link aggregate, lag0, on dut1 and dut2
. Verify failure modes for lacp mode
. Set up static link aggregate, lag0, on dut1 and dut2
. Verify failure modes for static mode
<<<
+165
View File
@@ -0,0 +1,165 @@
#!/usr/bin/env python3
r"""Ling Aggregation Basic
Verify communication over a link aggregate in static and LACP operating
modes during basic failure scenarios.
.Internal network setup, PC verifies connectivity with dut2 via dut1
image::lag-basic.svg[Internal networks]
The host verifies connectivity with dut2 via dut1 over the aggregate for
each test step using the `mon` interface.
"""
from time import sleep, time
from datetime import datetime
import infamy
import infamy.lag
from infamy.util import parallel, until
class DumbLinkBreaker:
"""Encapsulates basic, dumb link-breaking ops over SSH."""
def __init__(self, sys, dut, netns):
self.env = sys
self.dut = dut
self.net = netns
self.tgt = {}
for i, (name, _) in dut.items():
self.tgt[i] = env.attach(name, "mgmt", "ssh")
def set_link(self, link, updown):
"""Set link up or down, verify before returning."""
def set_and_verify(i):
name, dut = self.dut[i]
cmd = self.tgt[i].runsh(f"sudo ip link set {dut[link]} {updown}")
if cmd.returncode:
for out in [cmd.stdout, cmd.stderr]:
if out:
print(f"{name}: {out.rstrip()}")
raise RuntimeError(f"{name}: failed setting {link} {updown}")
for _ in range(10):
sleep(0.1)
check = self.tgt[i].runsh(f"ip link show {dut[link]}")
if f"state {updown.upper()}" in check.stdout:
break
else:
raise RuntimeError(f"{name}: {dut[link]} did not go {updown}")
parallel(*[lambda i=i: set_and_verify(i) for i in self.dut])
def fail_check(self, peer):
"""Verify connectivity with peer during link failure."""
sequence = [
[("link1", "up"), ("link2", "up")],
[("link1", "down"), ("link2", "up")],
[("link1", "up"), ("link2", "down")],
[("link1", "up"), ("link2", "up")]
]
total_start = time()
for state in sequence:
state_start = time()
print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} {state}")
for link, updown in state:
self.set_link(link, updown)
self.net.must_reach(peer, timeout=10)
print(f"Completed in {time() - state_start:.2f}s")
print(f"Total time: {time() - total_start:.2f}s")
def lag_init(mode):
"""Set up mode specific attributes for the LAG"""
if mode == "lacp":
lag = [{
"name": "lag0",
"lag": {"lacp": {"rate": "fast"}}
}]
else:
lag = []
return lag
def net_init(host, addr):
"""Set up DUT network, dut1 bridges host port with lag0"""
if host:
net = [{
"name": "br0",
"type": "infix-if-type:bridge",
}, {
"name": host,
"bridge-port": {"bridge": "br0"}
}, {
"name": "lag0",
"bridge-port": {"bridge": "br0"}
}]
else:
net = [{
"name": "lag0",
"ipv4": {
"address": [{"ip": addr, "prefix-length": 24}]
}
}]
return net
def dut_init(dut, mode, addr):
"""Set up link aggregate on dut"""
net = net_init(dut["mon"], addr)
lag = lag_init(mode)
dut.put_config_dict("ietf-interfaces", {
"interfaces": {
"interface": [{
"name": "lag0",
"type": "infix-if-type:lag",
"lag": {
"mode": mode,
"link-monitor": {"interval": 100}
}
}, {
"name": dut["link1"],
"lag-port": {"lag": "lag0"}
}, {
"name": dut["link2"],
"lag-port": {"lag": "lag0"}
}] + net + lag
}
})
with infamy.Test() as test:
with test.step("Set up topology and attach to target DUTs"):
env = infamy.Env(edge_mappings=infamy.lag.edge_mappings)
dut1 = env.attach("dut1", "mgmt")
dut2 = env.attach("dut2", "mgmt")
_, mon = env.ltop.xlate("host", "mon")
with infamy.IsolatedMacVlan(mon) as ns:
dm = {
'1': ("dut1", dut1),
'2': ("dut2", dut2)
}
lb = DumbLinkBreaker(env, dm, ns)
ns.addip("192.168.2.1")
with test.step("Set up LACP link aggregate, lag0, on dut1 and dut2"):
parallel(lambda: dut_init(dut1, "lacp", None),
lambda: dut_init(dut2, "lacp", "192.168.2.42"))
with test.step("Verify failure modes for lacp mode"):
lb.fail_check("192.168.2.42")
with test.step("Set up static link aggregate, lag0, on dut1 and dut2"):
parallel(lambda: dut_init(dut1, "static", None),
lambda: dut_init(dut2, "static", "192.168.2.42"))
with test.step("Verify failure modes for static mode"):
lb.fail_check("192.168.2.42")
test.succeed()
@@ -0,0 +1,33 @@
graph "lag" {
layout="neato";
overlap="false";
esep="+23";
node [shape=record, fontsize=12, fontname="DejaVu Sans Mono, Book"];
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
host [
label="host | { <mgmt1> mgmt1 | <mon> mon | \n\n\n\n | <mgmt2> mgmt2 }",
pos="0,15!",
requires="controller",
];
dut1 [
label="{ <mgmt> mgmt | <mon> mon } | { dut1\r | { <link1> link1 | <link2> link2 } }",
pos="2,15.25!",
requires="infix",
];
dut2 [
label="<mgmt> mgmt | { { <link1> link1 | <link2> link2 } | dut2\r }",
pos="2,14.75!",
requires="infix",
];
host:mgmt1 -- dut1:mgmt [requires="mgmt", color=lightgray]
host:mon -- dut1:mon // Monitor connection to dut2 via dut1
host:mgmt2 -- dut2:mgmt [requires="mgmt", color=lightgrey]
dut1:link1 -- dut2:link1 [lag=true, color=black, fontcolor=black, penwidth=3]
dut1:link2 -- dut2:link2 [lag=true, color=black, fontcolor=black, penwidth=3]
}
@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: lag Pages: 1 -->
<svg width="538pt" height="152pt"
viewBox="0.00 0.00 538.04 151.51" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 147.51)">
<title>lag</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-147.51 534.04,-147.51 534.04,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="black" points="0,-8.26 0,-135.26 101,-135.26 101,-8.26 0,-8.26"/>
<text text-anchor="middle" x="23.5" y="-68.66" font-family="DejaVu Sans Mono, Book" font-size="12.00">host</text>
<polyline fill="none" stroke="black" points="47,-8.26 47,-135.26 "/>
<text text-anchor="middle" x="74" y="-121.66" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt1</text>
<polyline fill="none" stroke="black" points="47,-114.26 101,-114.26 "/>
<text text-anchor="middle" x="74" y="-100.66" font-family="DejaVu Sans Mono, Book" font-size="12.00">mon</text>
<polyline fill="none" stroke="black" points="47,-93.26 101,-93.26 "/>
<polyline fill="none" stroke="black" points="47,-29.26 101,-29.26 "/>
<text text-anchor="middle" x="74" y="-15.66" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt2</text>
</g>
<!-- dut1 -->
<g id="node2" class="node">
<title>dut1</title>
<polygon fill="none" stroke="black" points="375.04,-101.01 375.04,-143.01 530.04,-143.01 530.04,-101.01 375.04,-101.01"/>
<text text-anchor="middle" x="398.54" y="-129.41" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt</text>
<polyline fill="none" stroke="black" points="375.04,-122.01 422.04,-122.01 "/>
<text text-anchor="middle" x="398.54" y="-108.41" font-family="DejaVu Sans Mono, Book" font-size="12.00">mon</text>
<polyline fill="none" stroke="black" points="422.04,-101.01 422.04,-143.01 "/>
<text text-anchor="end" x="522.04" y="-129.41" font-family="DejaVu Sans Mono, Book" font-size="12.00">dut1</text>
<polyline fill="none" stroke="black" points="422.04,-122.01 530.04,-122.01 "/>
<text text-anchor="middle" x="449.04" y="-108.41" font-family="DejaVu Sans Mono, Book" font-size="12.00">link1</text>
<polyline fill="none" stroke="black" points="476.04,-101.01 476.04,-122.01 "/>
<text text-anchor="middle" x="503.04" y="-108.41" font-family="DejaVu Sans Mono, Book" font-size="12.00">link2</text>
</g>
<!-- host&#45;&#45;dut1 -->
<g id="edge1" class="edge">
<title>host:mgmt1&#45;&#45;dut1:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M101.5,-124.76C101.5,-124.76 374.54,-133.01 374.54,-133.01"/>
</g>
<!-- host&#45;&#45;dut1 -->
<g id="edge2" class="edge">
<title>host:mon&#45;&#45;dut1:mon</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M101.5,-103.76C101.5,-103.76 374.54,-111.01 374.54,-111.01"/>
</g>
<!-- dut2 -->
<g id="node3" class="node">
<title>dut2</title>
<polygon fill="none" stroke="black" points="375.04,-0.5 375.04,-42.5 530.04,-42.5 530.04,-0.5 375.04,-0.5"/>
<text text-anchor="middle" x="398.54" y="-18.4" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt</text>
<polyline fill="none" stroke="black" points="422.04,-0.5 422.04,-42.5 "/>
<text text-anchor="middle" x="449.04" y="-28.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">link1</text>
<polyline fill="none" stroke="black" points="476.04,-21.5 476.04,-42.5 "/>
<text text-anchor="middle" x="503.04" y="-28.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">link2</text>
<polyline fill="none" stroke="black" points="422.04,-21.5 530.04,-21.5 "/>
<text text-anchor="end" x="522.04" y="-7.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">dut2</text>
</g>
<!-- host&#45;&#45;dut2 -->
<g id="edge3" class="edge">
<title>host:mgmt2&#45;&#45;dut2:mgmt</title>
<path fill="none" stroke="lightgrey" stroke-width="2" d="M101.5,-18.76C101.5,-18.76 374.54,-21.5 374.54,-21.5"/>
</g>
<!-- dut1&#45;&#45;dut2 -->
<g id="edge4" class="edge">
<title>dut1:link1&#45;&#45;dut2:link1</title>
<path fill="none" stroke="black" stroke-width="3" d="M448.54,-101.01C448.54,-101.01 448.54,-42.5 448.54,-42.5"/>
</g>
<!-- dut1&#45;&#45;dut2 -->
<g id="edge5" class="edge">
<title>dut1:link2&#45;&#45;dut2:link2</title>
<path fill="none" stroke="black" stroke-width="3" d="M503.54,-101.01C503.54,-101.01 503.54,-42.5 503.54,-42.5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

+1
View File
@@ -0,0 +1 @@
lag_failure.adoc
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 473 KiB

@@ -0,0 +1,42 @@
=== LACP Aggregate w/ Degraded Link
==== Description
Verify communication over an LACP link aggregate when individual member
links stop forwarding traffic, without carrier loss.
.Logical network setup, link breakers (lb1 & lb2) here managed by host PC
ifdef::topdoc[]
image::../../test/case/ietf_interfaces/lag_failure/lag-failure.svg[]
endif::topdoc[]
ifndef::topdoc[]
ifdef::testgroup[]
image::lag_failure/lag-failure.svg[]
endif::testgroup[]
ifndef::testgroup[]
image::lag-failure.svg[]
endif::testgroup[]
endif::topdoc[]
The host verifies connectivity with dut2 via dut1 over the aggregate for
each failure mode step using the `mon` interface.
==== Topology
ifdef::topdoc[]
image::{topdoc}../../test/case/ietf_interfaces/lag_failure/topology.svg[LACP Aggregate w/ Degraded Link topology]
endif::topdoc[]
ifndef::topdoc[]
ifdef::testgroup[]
image::lag_failure/topology.svg[LACP Aggregate w/ Degraded Link topology]
endif::testgroup[]
ifndef::testgroup[]
image::topology.svg[LACP Aggregate w/ Degraded Link topology]
endif::testgroup[]
endif::topdoc[]
==== Test sequence
. Set up topology and attach to target DUTs
. Set up link aggregate, lag0, between dut1 and dut2
. Initial connectivity check ...
. Verify failure modes
<<<
+144
View File
@@ -0,0 +1,144 @@
#!/usr/bin/env python3
r"""LACP Aggregate w/ Degraded Link
Verify communication over an LACP link aggregate when individual member
links stop forwarding traffic, without carrier loss.
.Logical network setup, link breakers (lb1 & lb2) here managed by host PC
image::lag-failure.svg[]
The host verifies connectivity with dut2 via dut1 over the aggregate for
each failure mode step using the `mon` interface.
"""
from time import time
import infamy
import infamy.lag
from infamy.netns import TPMR
from infamy.util import parallel
IPH = "192.168.2.1"
IP1 = "192.168.2.41"
IP2 = "192.168.2.42"
class LinkBreaker:
"""Encapsulates TPMR based link-breakers."""
def __init__(self, sys, netns):
self.net = netns
self.lb1 = TPMR(sys.ltop.xlate("host", "lb1a")[1],
sys.ltop.xlate("host", "lb1b")[1]).start()
self.lb2 = TPMR(sys.ltop.xlate("host", "lb2a")[1],
sys.ltop.xlate("host", "lb2b")[1]).start()
def forward(self, lb1, lb2):
"""Set link breakers in forwarding or blocking state."""
getattr(self.lb1, lb1)()
getattr(self.lb2, lb2)()
def fail_check(self, peer):
"""Verify connectivity with a given peer during failure."""
sequence = [
("forward", "forward"),
("forward", "block"),
("block", "forward"),
("forward", "forward")
]
total_start = time()
print(f"{'LB1':<8} | {'LB2':<8} | {'Status':<8}")
print("---------|----------|---------")
for lb1, lb2 in sequence:
state_start = time()
try:
print(f"{lb1:<8} | {lb2:<8} | {'...':<8}", end="\r# ")
self.forward(lb1, lb2)
self.net.must_reach(peer, timeout=30)
print(f"{lb1:<8} | {lb2:<8} | {'OK':<8} in "
f"{time() - state_start:.2f}s")
except Exception as e:
print(f"{lb1:<8} | {lb2:<8} | {'FAIL':<8} after "
f"{time() - state_start:.2f}s")
print(f"\nError encountered: {e}")
print(f"Link breakers were in state: LB1='{lb1}', LB2='{lb2}'")
raise
print(f"Total time: {time() - total_start:.2f}s")
def net_init(host, addr):
"""Set up DUT network, dut1 bridges host port with lag0"""
if host:
net = [{
"name": "br0",
"type": "infix-if-type:bridge",
"ipv4": {
"address": [{"ip": addr, "prefix-length": 24}]
}
}, {
"name": host,
"bridge-port": {"bridge": "br0"}
}, {
"name": "lag0",
"bridge-port": {"bridge": "br0"}
}]
else:
net = [{
"name": "lag0",
"ipv4": {
"address": [{"ip": addr, "prefix-length": 24}]
}
}]
return net
def dut_init(dut, addr, peer):
"""Configure each DUT specific according to LAG mode and peer"""
net = net_init(dut["mon"], addr)
dut.put_config_dict("ietf-interfaces", {
"interfaces": {
"interface": [{
"name": "lag0",
"type": "infix-if-type:lag",
"lag": {
"mode": "lacp",
"lacp": {"rate": "fast"},
"link-monitor": {"interval": 100}
}
}, {
"name": dut["link1"],
"lag-port": {"lag": "lag0"}
}, {
"name": dut["link2"],
"lag-port": {"lag": "lag0"}
}] + net
}
})
with infamy.Test() as test:
with test.step("Set up topology and attach to target DUTs"):
env = infamy.Env(edge_mappings=infamy.lag.edge_mappings)
dut1 = env.attach("dut1")
dut2 = env.attach("dut2")
_, mon = env.ltop.xlate("host", "mon")
with infamy.IsolatedMacVlan(mon) as ns:
lb = LinkBreaker(env, ns)
ns.addip(IPH)
print(f"Setting up lag0 in LACP mode between {dut1} and {dut2}")
with test.step("Set up link aggregate, lag0, between dut1 and dut2"):
parallel(lambda: dut_init(dut1, IP1, IP2),
lambda: dut_init(dut2, IP2, IP1))
with test.step("Initial connectivity check ..."):
ns.must_reach(IP2, timeout=30)
with test.step("Verify failure modes"):
lb.fail_check(IP2)
test.succeed()
@@ -0,0 +1,36 @@
graph "lag" {
layout="neato";
overlap="false";
esep="+23";
node [shape=record, fontsize=12, fontname="DejaVu Sans Mono, Book"];
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
host [
label="{{ <mgmt1> mgmt1 | <mon> mon | <lb1a> lb1a | <lb2a> lb2a | <lb2b> lb2b | <lb1b> lb1b | <mgmt2> mgmt2 } | host}",
pos="9,0!",
requires="controller",
];
dut1 [
label="{ dut1\l | { <mgmt> mgmt | <mon> mon | <link1> link1 | <link2> link2 } }",
pos="0,6!",
requires="infix",
];
dut2 [
label="{ dut2\r | { <link2> link2 | <link1> link1 | <mgmt> mgmt } }",
pos="18,6!",
requires="infix",
];
host:mgmt1 -- dut1:mgmt [requires="mgmt", color=lightgray]
host:mon -- dut1:mon // Monitor connection to dut2 via dut1
host:mgmt2 -- dut2:mgmt [requires="mgmt", color=lightgrey]
dut1:link1 -- host:lb1a [requires="ieee-mc", lag=true, color=black, fontcolor=black]
host:lb1b -- dut2:link1 [requires="ieee-mc", lag=true, color=black, fontcolor=black]
dut1:link2 -- host:lb2a [requires="ieee-mc", lag=true, color=black, fontcolor=black]
host:lb2b -- dut2:link2 [requires="ieee-mc", lag=true, color=black, fontcolor=black]
}
@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: lag Pages: 1 -->
<svg width="484pt" height="152pt"
viewBox="0.00 0.00 484.03 151.51" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 147.51)">
<title>lag</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-147.51 480.03,-147.51 480.03,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="black" points="80.27,-0.5 80.27,-42.5 415.27,-42.5 415.27,-0.5 80.27,-0.5"/>
<text text-anchor="middle" x="107.27" y="-28.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt1</text>
<polyline fill="none" stroke="black" points="134.27,-21.5 134.27,-42.5 "/>
<text text-anchor="middle" x="153.77" y="-28.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">mon</text>
<polyline fill="none" stroke="black" points="173.27,-21.5 173.27,-42.5 "/>
<text text-anchor="middle" x="196.77" y="-28.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">lb1a</text>
<polyline fill="none" stroke="black" points="220.27,-21.5 220.27,-42.5 "/>
<text text-anchor="middle" x="243.77" y="-28.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">lb2a</text>
<polyline fill="none" stroke="black" points="267.27,-21.5 267.27,-42.5 "/>
<text text-anchor="middle" x="290.77" y="-28.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">lb2b</text>
<polyline fill="none" stroke="black" points="314.27,-21.5 314.27,-42.5 "/>
<text text-anchor="middle" x="337.77" y="-28.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">lb1b</text>
<polyline fill="none" stroke="black" points="361.27,-21.5 361.27,-42.5 "/>
<text text-anchor="middle" x="388.27" y="-28.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt2</text>
<polyline fill="none" stroke="black" points="80.27,-21.5 415.27,-21.5 "/>
<text text-anchor="middle" x="247.77" y="-7.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">host</text>
</g>
<!-- dut1 -->
<g id="node2" class="node">
<title>dut1</title>
<polygon fill="none" stroke="black" points="0,-101.01 0,-143.01 194,-143.01 194,-101.01 0,-101.01"/>
<text text-anchor="start" x="8" y="-129.41" font-family="DejaVu Sans Mono, Book" font-size="12.00">dut1</text>
<polyline fill="none" stroke="black" points="0,-122.01 194,-122.01 "/>
<text text-anchor="middle" x="23.5" y="-108.41" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt</text>
<polyline fill="none" stroke="black" points="47,-101.01 47,-122.01 "/>
<text text-anchor="middle" x="66.5" y="-108.41" font-family="DejaVu Sans Mono, Book" font-size="12.00">mon</text>
<polyline fill="none" stroke="black" points="86,-101.01 86,-122.01 "/>
<text text-anchor="middle" x="113" y="-108.41" font-family="DejaVu Sans Mono, Book" font-size="12.00">link1</text>
<polyline fill="none" stroke="black" points="140,-101.01 140,-122.01 "/>
<text text-anchor="middle" x="167" y="-108.41" font-family="DejaVu Sans Mono, Book" font-size="12.00">link2</text>
</g>
<!-- host&#45;&#45;dut1 -->
<g id="edge1" class="edge">
<title>host:mgmt1&#45;&#45;dut1:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M106.77,-42.5C106.77,-42.5 23,-101.01 23,-101.01"/>
</g>
<!-- host&#45;&#45;dut1 -->
<g id="edge2" class="edge">
<title>host:mon&#45;&#45;dut1:mon</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M153.77,-42.5C153.77,-42.5 66,-101.01 66,-101.01"/>
</g>
<!-- dut2 -->
<g id="node3" class="node">
<title>dut2</title>
<polygon fill="none" stroke="black" points="321.03,-101.01 321.03,-143.01 476.03,-143.01 476.03,-101.01 321.03,-101.01"/>
<text text-anchor="end" x="468.03" y="-129.41" font-family="DejaVu Sans Mono, Book" font-size="12.00">dut2</text>
<polyline fill="none" stroke="black" points="321.03,-122.01 476.03,-122.01 "/>
<text text-anchor="middle" x="348.03" y="-108.41" font-family="DejaVu Sans Mono, Book" font-size="12.00">link2</text>
<polyline fill="none" stroke="black" points="375.03,-101.01 375.03,-122.01 "/>
<text text-anchor="middle" x="402.03" y="-108.41" font-family="DejaVu Sans Mono, Book" font-size="12.00">link1</text>
<polyline fill="none" stroke="black" points="429.03,-101.01 429.03,-122.01 "/>
<text text-anchor="middle" x="452.53" y="-108.41" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt</text>
</g>
<!-- host&#45;&#45;dut2 -->
<g id="edge3" class="edge">
<title>host:mgmt2&#45;&#45;dut2:mgmt</title>
<path fill="none" stroke="lightgrey" stroke-width="2" d="M388.77,-42.5C388.77,-42.5 452.53,-101.01 452.53,-101.01"/>
</g>
<!-- host&#45;&#45;dut2 -->
<g id="edge5" class="edge">
<title>host:lb1b&#45;&#45;dut2:link1</title>
<path fill="none" stroke="black" stroke-width="2" d="M337.77,-42.5C337.77,-42.5 402.53,-101.01 402.53,-101.01"/>
</g>
<!-- host&#45;&#45;dut2 -->
<g id="edge7" class="edge">
<title>host:lb2b&#45;&#45;dut2:link2</title>
<path fill="none" stroke="black" stroke-width="2" d="M290.77,-42.5C290.77,-42.5 320.53,-111.01 320.53,-111.01"/>
</g>
<!-- dut1&#45;&#45;host -->
<g id="edge4" class="edge">
<title>dut1:link1&#45;&#45;host:lb1a</title>
<path fill="none" stroke="black" stroke-width="2" d="M113,-101.01C113,-101.01 196.77,-42.5 196.77,-42.5"/>
</g>
<!-- dut1&#45;&#45;host -->
<g id="edge6" class="edge">
<title>dut1:link2&#45;&#45;host:lb2a</title>
<path fill="none" stroke="black" stroke-width="2" d="M194,-111.01C194,-111.01 243.77,-42.5 243.77,-42.5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

+1
View File
@@ -3,6 +3,7 @@ import os
from .container import Container
from .env import Env
from .env import ArgumentParser
from .env import test_argument
from .furl import Furl
from .netns import IsolatedMacVlan,IsolatedMacVlans
from .sniffer import Sniffer
+22 -3
View File
@@ -16,6 +16,7 @@ class NullEnv:
ENV = NullEnv()
class ArgumentParser():
def DefaultTransport():
"""Pick pseudo-random transport
@@ -41,7 +42,6 @@ class ArgumentParser():
self.args.add_argument("ptop", nargs=1, metavar="topology")
self.args.add_argument("-l", "--logical-topology", dest="ltop", default=top)
def add_argument(self, *args, **kwargs):
kwargs["required"] = True
self.args.add_argument(*args, **kwargs)
@@ -49,8 +49,25 @@ class ArgumentParser():
def parse_args(self, argv):
return self.args.parse_args(argv)
def test_argument(option, **kwargs):
"""See lag_failure/test.py for an example @infamy.test_argumet()"""
def decorator(cls):
super_init = cls.__init__
def new_init(self, *args, **kw):
super_init(self, *args, **kw)
self.add_argument(option, **kwargs)
cls.__init__ = new_init
return cls
return decorator
class Env(object):
def __init__(self, ltop=None, args=None, argv=sys.argv[1::], environ=os.environ):
def __init__(self, ltop=None, args=None, argv=sys.argv[1::], environ=os.environ,
nodes_compatible=topology.compatible,
edge_mappings=topology.edge_mappings):
if "INFAMY_ARGS" in environ:
argv = shlex.split(environ["INFAMY_ARGS"]) + argv
@@ -74,7 +91,9 @@ class Env(object):
ldot = pydot.graph_from_dot_file(top_path)[0]
self.ltop = topology.Topology(ldot)
if not self.ltop.map_to(self.ptop):
if not self.ltop.map_to(self.ptop,
nodes_compatible=nodes_compatible,
edge_mappings=edge_mappings):
raise tap.TestSkip()
print(repr(self.ltop))
+26
View File
@@ -0,0 +1,26 @@
from . import topology
def edge_mappings(les, pes):
"""Specialized topology edge mapper for LAG tests
In addition to the standard provides/requires validation, ensure
that for all logical ports marked with a "lag" attribute, the
corresponding physical ports are all of the same link type
(e.g. "link-10gbase-r").
"""
def links_compatible(candidate):
seen = None
for (le, pe) in candidate:
if le.get("lag"):
link = set(filter(lambda f: f.startswith("link-"), pe["provides"]))
if seen is None:
seen = link
elif link != seen:
return False
return True
for candidate in topology.edge_mappings(les, pes):
if links_compatible(candidate):
yield candidate
+7 -4
View File
@@ -155,8 +155,7 @@ class Device(Transport):
for schema in schemas:
if os.path.exists(yangdir + "/" + schema['filename']) is False:
self.get_schema(schema, yangdir)
sys.stdout.write("Downloading YANG model "
f"{schema['identifier']} ...\r\033[K")
print("YANG models downloaded.")
def _ly_init(self, yangdir):
@@ -309,20 +308,24 @@ class Device(Transport):
break
def put_config_dicts(self, models):
"""PUT full configuration of all models to running-config"""
config = ""
infer_put_dict(self.name, models)
for model in models.keys():
mod = self.ly.get_module(model)
lyd = mod.parse_data_dict(models[model], no_state=True, validate=False)
config+=lyd.print_mem("xml", with_siblings=True, pretty=False)+"\n"
config += lyd.print_mem("xml", with_siblings=True, pretty=False) + "\n"
# print(f"Send new XML config: {config}")
return self.put_config(config)
def put_config_dict(self, modname, edit):
"""Convert Python dictionary to XMl and send as configuration"""
mod = self.ly.get_module(modname)
lyd = mod.parse_data_dict(edit, no_state=True, validate=False)
return self.put_config(lyd.print_mem("xml", with_siblings=True, pretty=False))
config = lyd.print_mem("xml", with_siblings=True, pretty=False)
# print(f"Send new XML config: {config}")
return self.put_config(config)
def call(self, call):
"""Call RPC, XML version"""
+7 -4
View File
@@ -265,6 +265,7 @@ class Device(Transport):
return {container: v}
def put_config_dicts(self, models):
"""PUT full configuration of all models to running-config"""
infer_put_dict(self.name, models)
running = self.get_running()
@@ -273,8 +274,9 @@ class Device(Transport):
lyd = mod.parse_data_dict(models[model], no_state=True, validate=False)
running.merge(lyd)
return self.put_datastore("running", json.loads(running.print_mem("json", with_siblings=True, pretty=False)))
cfg = running.print_mem("json", with_siblings=True, pretty=True)
# print(f"PUT new running-config: {cfg}")
return self.put_datastore("running", json.loads(cfg))
def put_config_dict(self, modname, edit):
"""Add @edit to running config and put the whole configuration"""
@@ -298,8 +300,9 @@ class Device(Transport):
change = mod.parse_data_dict(edit, no_state=True, validate=False)
running.merge_module(change)
data = json.loads(running.print_mem("json", with_siblings=True,
pretty=False))
cfg = running.print_mem("json", with_siblings=True, pretty=False)
# print(f"PUT new running-config: {cfg}")
data = json.loads(cfg)
return self.put_datastore("running", data)
+3 -3
View File
@@ -43,19 +43,19 @@ graph "ring-4-duts" {
host:mgmt3 -- R3:mgmt [kind=mgmt, color="lightgray"]
host:mgmt4 -- R4:mgmt [kind=mgmt, color="lightgray"]
# host-Dut links
// host-Dut links
host:data1 -- R1:data [color="darkgreen"]
host:data2 -- R2:data [color="darkgreen"]
host:data3 -- R3:data [color="darkgreen"]
host:data4 -- R4:data [color="darkgreen"]
# Ring
// Ring
R1:ring1 -- R2:ring2 [color="blue",headlabel=".2", label="10.0.12.1/30", taillabel=".1", labeldistance=1, fontcolor="blue"]
R2:ring1 -- R3:ring2 [color="blue",headlabel=".2", label="10.0.23.0/30", taillabel=".1", labeldistance=1, fontcolor="blue"]
R3:ring1 -- R4:ring2 [color="blue",headlabel=".2", label="192.168.4.0/24", taillabel=".1", labeldistance=1 fontcolor="blue"]
R4:ring1 -- R1:ring2 [color="blue",headlabel=".2", label="10.0.41.0/30", taillabel=".1", labeldistance=1, fontcolor="blue"]
# Cross
// Cross
R1:cross -- R3:cross [color="black", label="10.0.13.0/30\n\n"]
R2:cross -- R4:cross [color="brown", label="\n\n10.0.24.0/30", fontcolor="brown"]
}
+19 -18
View File
@@ -14,25 +14,17 @@ def _qstrip(text):
return text
def compatible(physical, logical):
provides_set = set(physical.get("provides", "").split())
requires_set = set(logical.get("requires", "").split())
return logical["requires"].issubset(physical["provides"])
return requires_set.issubset(provides_set)
def map_edges(les, pes):
def edge_mappings(les, pes):
les = les.values()
pes = pes.values()
for perm in permutations(pes, len(les)):
candidate = tuple(zip(les, perm))
if all(map(lambda pair: compatible(pair[1], pair[0]), candidate)):
return candidate
yield candidate
def match_node(pn, ln):
return compatible(pn, ln)
def match_edge(pes, les):
return map_edges(les, pes) is not None
class Topology:
def __init__(self, dotg):
@@ -46,6 +38,9 @@ class Topology:
repr(n.get_attributes())
attrs = { _qstrip(k): _qstrip(v) for k, v in n.get_attributes().items() if k != "label" }
for attr in ("requires", "provides"):
attrs[attr] = set(attrs.get(attr, "").split())
self.g.add_node(name, **attrs)
for e in self.dotg.get_edges():
@@ -55,6 +50,10 @@ class Topology:
attrs = {_qstrip(k): _qstrip(v) for k, v in e.get_attributes().items()}
attrs[sn] = sp
attrs[dn] = dp
for attr in ("requires", "provides"):
attrs[attr] = set(attrs.get(attr, "").split())
self.g.add_edge(sn, dn, **attrs)
def __repr__(self):
@@ -73,13 +72,15 @@ class Topology:
return out
def map_to(self, phy):
def map_to(self, phy,
nodes_compatible=compatible, edge_mappings=edge_mappings):
mapper = isomorphism.MultiGraphMatcher(phy.g, self.g,
edge_match=match_edge,
node_match=match_node)
edge_match=lambda pes, les: any(edge_mappings(les, pes)),
node_match=nodes_compatible)
if not mapper.subgraph_is_monomorphic():
return False
# breakpoint()
self.phy = phy
self.mapping = {}
@@ -93,7 +94,7 @@ class Topology:
les = self.g.get_edge_data(lsrc, ldst)
pes = self.phy.g.get_edge_data(psrc, pdst)
for le, pe in map_edges(les, pes):
for le, pe in next(edge_mappings(les, pes)):
self.mapping[lsrc][le[lsrc]] = pe[psrc]
self.mapping[ldst][le[ldst]] = pe[pdst]
@@ -139,15 +140,15 @@ class Topology:
return None
def get_mgmt_link(self, src, dst):
return self.get_link(src, dst, lambda e: compatible(e, {"requires": "mgmt"}))
return self.get_link(src, dst, lambda e: compatible(e, {"requires": {"mgmt"}}))
def get_ctrl(self):
ns = self.get_nodes(lambda _, attrs: compatible(attrs, {"requires": "controller"}))
ns = self.get_nodes(lambda _, attrs: compatible(attrs, {"requires": {"controller"}}))
assert len(ns) == 1
return ns[0]
def get_infixen(self):
return self.get_nodes(lambda _, attrs: compatible(attrs, {"requires": "infix"}))
return self.get_nodes(lambda _, attrs: compatible(attrs, {"requires": {"infix"}}))
def get_attr(self, name, default=None):
+3 -1
View File
@@ -56,7 +56,9 @@ class Transport(ABC):
pass
def __getitem__(self, key):
return self.mapping[key]
if key in self.mapping:
return self.mapping[key]
return None
def get_iface(self, name):
"""Fetch target dict for iface and extract param from JSON"""
+30 -19
View File
@@ -11,6 +11,7 @@ from pathlib import Path
import graphviz
def replace_image_tag(text, test_dir):
"""
Convert images added in the description and replace with the required ifdefs to work
@@ -106,33 +107,43 @@ def replace_in_steps(steps, variables):
class TestCase:
"""All test specifcation resources for a test case"""
def __init__(self, directory, rootdir=None):
self.test_dir=Path(directory)
self.test_dir = Path(directory)
if rootdir:
rootdir=Path(f"{rootdir}")
self.test_dir=self.test_dir.relative_to(rootdir)
self.topology_dot=f"{directory}/topology.dot"
self.topology_image=f"{directory}/topology"
self.test_case=f"{directory}/test.py"
self.specification=f"{directory}/Readme.adoc"
rootdir = Path(f"{rootdir}")
self.test_dir = self.test_dir.relative_to(rootdir)
self.topology_dot = f"{directory}/topology.dot"
self.topology_image = f"{directory}/topology"
self.test_case = f"{directory}/test.py"
self.specification = f"{directory}/Readme.adoc"
def generate_topology(self):
"""Generate SVG file from the topology.dot file"""
with open(self.topology_dot, 'r') as dot_file:
dot_graph = dot_file.read()
graph = graphviz.Source(dot_graph)
graph.render(self.topology_image, format='svg', cleanup=True)
pattern = r'<!--\s*Generated by graphviz.*?-->'
content=""
with open(f"{self.topology_image}.svg", "r") as f:
content = f.read()
mod_content = re.sub(pattern, '',content, flags=re.DOTALL)
with open(f"{self.topology_image}.svg", 'w') as f:
f.write(mod_content)
with open(self.topology_dot, 'r', encoding='utf-8') as dot_file:
svg_file = f"{self.topology_image}.svg"
try:
dot_graph = dot_file.read()
graph = graphviz.Source(dot_graph)
graph.render(self.topology_image, format='svg', cleanup=True,
quiet=True)
content = ""
with open(svg_file, 'r', encoding='utf-8') as f:
content = f.read()
pattern = r'<!--\s*Generated by graphviz.*?-->'
mod_content = re.sub(pattern, '', content, flags=re.DOTALL)
with open(svg_file, 'w', encoding='utf-8') as f:
f.write(mod_content)
except graphviz.backend.execute.CalledProcessError as e:
msg = e.stderr.decode()
print(f"Failed rendering SVG from {self.topology_dot}: {msg}")
except UnboundLocalError:
print(f"Failed cleaning {svg_file}, empty or missing.")
def generate_specification(self, name, case_path, spec_path, variables):
"""Generate a ASCIIDOC specification for the test case"""
with open(case_path, 'r') as file:
with open(case_path, 'r', encoding='utf-8') as file:
script_content = file.read()
parsed_script = ast.parse(script_content)
+17 -14
View File
@@ -2,7 +2,8 @@ graph "dual" {
layout="neato";
overlap="false";
splines="true";
esep="+20";
esep="+30";
sep="+30";
node [shape=record, fontname="monospace"];
edge [color="cornflowerblue", penwidth="2"];
@@ -14,14 +15,16 @@ graph "dual" {
host [
label="host | { <d1a> d1a | <d1b> d1b | <d1c> d1c | <d2a> d2a | <d2b> d2b | <d2c> d2c }",
color="grey",fontcolor="grey",pos="0,15!",
kind="controller",
color="grey",
fontcolor="grey",
pos="0,15!",
provides="controller",
];
dut1 [
label="{ <e1> e1 | <e2> e2 | <e3> e3 } | dut1 | { <e4> e4 | <e5> e5 | <e6> e6 }",
pos="10,18!",
kind="infix",
provides="infix",
qn_console=9001,
qn_mem="384M",
qn_usb="dut1.usb"
@@ -29,21 +32,21 @@ graph "dual" {
dut2 [
label="{ <e1> e1 | <e2> e2 | <e3> e3 } | dut2 | { <e4> e4 | <e5> e5 | <e6> e6 }",
pos="10,12!",
kind="infix",
provides="infix",
qn_console=9002,
qn_mem="384M",
qn_usb="dut2.usb"
];
host:d1a -- dut1:e1 [kind=mgmt]
host:d1b -- dut1:e2
host:d1c -- dut1:e3
host:d1a -- dut1:e1 [provides=mgmt, color="lightgray"]
host:d1b -- dut1:e2 [provides="ieee-mc"]
host:d1c -- dut1:e3 [provides="ieee-mc"]
host:d2a -- dut2:e1 [kind=mgmt]
host:d2b -- dut2:e2
host:d2c -- dut2:e3
host:d2a -- dut2:e1 [provides=mgmt, color="lightgray"]
host:d2b -- dut2:e2 [provides="ieee-mc"]
host:d2c -- dut2:e3 [provides="ieee-mc"]
dut1:e4 -- dut2:e6
dut1:e5 -- dut2:e5
dut1:e6 -- dut2:e4
dut1:e4 -- dut2:e6 [color="black"]
dut1:e5 -- dut2:e5 [color="red"]
dut1:e6 -- dut2:e4 [color="black"]
}
+112
View File
@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.43.0 (0)
-->
<!-- Title: dual Pages: 1 -->
<svg width="365pt" height="208pt"
viewBox="0.00 0.00 365.08 208.01" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 204.01)">
<title>dual</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-204.01 361.08,-204.01 361.08,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="grey" points="0,-31.01 0,-169.01 91,-169.01 91,-31.01 0,-31.01"/>
<text text-anchor="middle" x="25" y="-96.31" font-family="monospace" font-size="14.00" fill="grey">host</text>
<polyline fill="none" stroke="grey" points="50,-31.01 50,-169.01 "/>
<text text-anchor="middle" x="70.5" y="-153.81" font-family="monospace" font-size="14.00" fill="grey">d1a</text>
<polyline fill="none" stroke="grey" points="50,-146.01 91,-146.01 "/>
<text text-anchor="middle" x="70.5" y="-130.81" font-family="monospace" font-size="14.00" fill="grey">d1b</text>
<polyline fill="none" stroke="grey" points="50,-123.01 91,-123.01 "/>
<text text-anchor="middle" x="70.5" y="-107.81" font-family="monospace" font-size="14.00" fill="grey">d1c</text>
<polyline fill="none" stroke="grey" points="50,-100.01 91,-100.01 "/>
<text text-anchor="middle" x="70.5" y="-84.81" font-family="monospace" font-size="14.00" fill="grey">d2a</text>
<polyline fill="none" stroke="grey" points="50,-77.01 91,-77.01 "/>
<text text-anchor="middle" x="70.5" y="-61.81" font-family="monospace" font-size="14.00" fill="grey">d2b</text>
<polyline fill="none" stroke="grey" points="50,-54.01 91,-54.01 "/>
<text text-anchor="middle" x="70.5" y="-38.81" font-family="monospace" font-size="14.00" fill="grey">d2c</text>
</g>
<!-- dut1 -->
<g id="node2" class="node">
<title>dut1</title>
<polygon fill="none" stroke="black" points="204.19,-130.51 204.19,-199.51 320.19,-199.51 320.19,-130.51 204.19,-130.51"/>
<text text-anchor="middle" x="220.69" y="-184.31" font-family="monospace" font-size="14.00">e1</text>
<polyline fill="none" stroke="black" points="204.19,-176.51 237.19,-176.51 "/>
<text text-anchor="middle" x="220.69" y="-161.31" font-family="monospace" font-size="14.00">e2</text>
<polyline fill="none" stroke="black" points="204.19,-153.51 237.19,-153.51 "/>
<text text-anchor="middle" x="220.69" y="-138.31" font-family="monospace" font-size="14.00">e3</text>
<polyline fill="none" stroke="black" points="237.19,-130.51 237.19,-199.51 "/>
<text text-anchor="middle" x="262.19" y="-161.31" font-family="monospace" font-size="14.00">dut1</text>
<polyline fill="none" stroke="black" points="287.19,-130.51 287.19,-199.51 "/>
<text text-anchor="middle" x="303.69" y="-184.31" font-family="monospace" font-size="14.00">e4</text>
<polyline fill="none" stroke="black" points="287.19,-176.51 320.19,-176.51 "/>
<text text-anchor="middle" x="303.69" y="-161.31" font-family="monospace" font-size="14.00">e5</text>
<polyline fill="none" stroke="black" points="287.19,-153.51 320.19,-153.51 "/>
<text text-anchor="middle" x="303.69" y="-138.31" font-family="monospace" font-size="14.00">e6</text>
</g>
<!-- host&#45;&#45;dut1 -->
<g id="edge1" class="edge">
<title>host:d1a&#45;&#45;dut1:e1</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-158.01C91.5,-158.01 204.19,-188.01 204.19,-188.01"/>
</g>
<!-- host&#45;&#45;dut1 -->
<g id="edge2" class="edge">
<title>host:d1b&#45;&#45;dut1:e2</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M91.5,-135.01C91.5,-135.01 204.19,-165.01 204.19,-165.01"/>
</g>
<!-- host&#45;&#45;dut1 -->
<g id="edge3" class="edge">
<title>host:d1c&#45;&#45;dut1:e3</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M91.5,-112.01C91.5,-112.01 204.19,-142.01 204.19,-142.01"/>
</g>
<!-- dut2 -->
<g id="node3" class="node">
<title>dut2</title>
<polygon fill="none" stroke="black" points="204.19,-0.5 204.19,-69.5 320.19,-69.5 320.19,-0.5 204.19,-0.5"/>
<text text-anchor="middle" x="220.69" y="-54.3" font-family="monospace" font-size="14.00">e1</text>
<polyline fill="none" stroke="black" points="204.19,-46.5 237.19,-46.5 "/>
<text text-anchor="middle" x="220.69" y="-31.3" font-family="monospace" font-size="14.00">e2</text>
<polyline fill="none" stroke="black" points="204.19,-23.5 237.19,-23.5 "/>
<text text-anchor="middle" x="220.69" y="-8.3" font-family="monospace" font-size="14.00">e3</text>
<polyline fill="none" stroke="black" points="237.19,-0.5 237.19,-69.5 "/>
<text text-anchor="middle" x="262.19" y="-31.3" font-family="monospace" font-size="14.00">dut2</text>
<polyline fill="none" stroke="black" points="287.19,-0.5 287.19,-69.5 "/>
<text text-anchor="middle" x="303.69" y="-54.3" font-family="monospace" font-size="14.00">e4</text>
<polyline fill="none" stroke="black" points="287.19,-46.5 320.19,-46.5 "/>
<text text-anchor="middle" x="303.69" y="-31.3" font-family="monospace" font-size="14.00">e5</text>
<polyline fill="none" stroke="black" points="287.19,-23.5 320.19,-23.5 "/>
<text text-anchor="middle" x="303.69" y="-8.3" font-family="monospace" font-size="14.00">e6</text>
</g>
<!-- host&#45;&#45;dut2 -->
<g id="edge4" class="edge">
<title>host:d2a&#45;&#45;dut2:e1</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-88.01C91.5,-88.01 204.19,-58 204.19,-58"/>
</g>
<!-- host&#45;&#45;dut2 -->
<g id="edge5" class="edge">
<title>host:d2b&#45;&#45;dut2:e2</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M91.5,-65.01C91.5,-65.01 204.19,-35 204.19,-35"/>
</g>
<!-- host&#45;&#45;dut2 -->
<g id="edge6" class="edge">
<title>host:d2c&#45;&#45;dut2:e3</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M91.5,-42.01C91.5,-42.01 204.19,-12 204.19,-12"/>
</g>
<!-- dut1&#45;&#45;dut2 -->
<g id="edge7" class="edge">
<title>dut1:e4&#45;&#45;dut2:e6</title>
<path fill="none" stroke="black" stroke-width="2" d="M320.27,-187.95C320.27,-187.95 357,-140.44 350.19,-99.5 343.45,-58.98 320.27,-12.06 320.27,-12.06"/>
</g>
<!-- dut1&#45;&#45;dut2 -->
<g id="edge8" class="edge">
<title>dut1:e5&#45;&#45;dut2:e5</title>
<path fill="none" stroke="red" stroke-width="2" d="M320.29,-165.01C320.29,-165.01 343.38,-131.37 350.19,-100.51 357.08,-69.26 320.29,-35 320.29,-35"/>
</g>
<!-- dut1&#45;&#45;dut2 -->
<g id="edge9" class="edge">
<title>dut1:e6&#45;&#45;dut2:e4</title>
<path fill="none" stroke="black" stroke-width="2" d="M304.19,-130.01C304.19,-130.01 304.19,-70 304.19,-70"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.5 KiB

+28 -23
View File
@@ -2,7 +2,8 @@ graph "quad" {
layout="neato";
overlap="false";
splines="true";
esep="+20";
esep="+30";
sep="+30";
node [shape=record, fontname="monospace"];
edge [color="cornflowerblue", penwidth="2"];
@@ -13,30 +14,31 @@ graph "quad" {
qn_append="quiet";
host [
label="host | { <d1a> d1a | <d1b> d1b | <d1c> d1c | <d1c> d1d | <d2a> d2a | <d2b> d2b | <d2c> d2c | <d2d> d2d | <d2a> d3a | <d3b> d3b | <d3c> d3c | <d3c> d3d | <d4a> d4a | <d4b> d4b | <d4c> d4c | <d4c> d4d }",
color="grey",fontcolor="grey",pos="0,15!",
label="host | { <d1a> d1a | <d1b> d1b | <d1c> d1c | <d1d> d1d | <d2a> d2a | <d2b> d2b | <d2c> d2c | <d2d> d2d | <d3a> d3a | <d3b> d3b | <d3c> d3c | <d3d> d3d | <d4a> d4a | <d4b> d4b | <d4c> d4c | <d4d> d4d }",
color="grey", fontcolor="grey",
pos="-15,15!",
provides="controller",
];
dut1 [
label="{ <e1> e1 | <e2> e2 | <e3> e3 } | dut1 | { <e4> e4 | <e5> e5 | <e6> e6 | <e7> e7 | <e8> e8}",
pos="10,18!",
label="{ <e1> e1 | <e2> e2 | <e3> e3 | <e4> e4 } | dut1 | { <e5> e5 | <e6> e6 | <e7> e7 | <e8> e8}",
pos="10,30!",
provides="infix",
qn_console=9001,
qn_mem="384M",
qn_usb="dut1.usb"
];
dut2 [
label="{ <e1> e1 | <e2> e2 | <e3> e3 } | dut2 | { <e4> e4 | <e5> e5 | <e6> e6 | <e7> e7 | <e8> e8}",
pos="10,12!",
label="{ <e1> e1 | <e2> e2 | <e3> e3 | <e4> e4 } | dut2 | { <e5> e5 | <e6> e6 | <e7> e7 | <e8> e8}",
pos="0,20!",
provides="infix",
qn_console=9002,
qn_mem="384M",
qn_usb="dut2.usb"
];
dut3 [
label="{ <e1> e1 | <e2> e2 | <e3> e3 } | dut2 | { <e4> e4 | <e5> e5 | <e6> e6 | <e7> e7 | <e8> e8}",
pos="10,12!",
label="{ <e1> e1 | <e2> e2 | <e3> e3 | <e4> e4 } | dut3 | { <e5> e5 | <e6> e6 | <e7> e7 | <e8> e8}",
pos="0,10!",
provides="infix",
qn_console=9003,
qn_mem="384M",
@@ -44,41 +46,44 @@ graph "quad" {
];
dut4 [
label="{ <e1> e1 | <e2> e2 | <e3> e3 } | dut2 | { <e4> e4 | <e5> e5 | <e6> e6 | <e7> e7 | <e8> e8}",
pos="10,12!",
label="{ <e1> e1 | <e2> e2 | <e3> e3 | <e4> e4 } | dut4 | { <e5> e5 | <e6> e6 | <e7> e7 | <e8> e8}",
pos="10,0!",
provides="infix",
qn_console=9004,
qn_mem="384M",
qn_usb="dut4.usb"
];
host:d1a -- dut1:e1 [provides="mgmt"]
host:d1a -- dut1:e1 [provides="mgmt", color="lightgray"]
host:d1b -- dut1:e2 [provides="ieee-mc"]
host:d1c -- dut1:e3 [provides="ieee-mc"]
host:d1d -- dut1:e4 [provides="ieee-mc"]
host:d2a -- dut2:e1 [provides="mgmt"]
host:d2a -- dut2:e1 [provides="mgmt", color="lightgray"]
host:d2b -- dut2:e2 [provides="ieee-mc"]
host:d2c -- dut2:e3 [provides="ieee-mc"]
host:d2d -- dut2:e4 [provides="ieee-mc"]
host:d3a -- dut3:e1 [provides="mgmt"]
host:d3a -- dut3:e1 [provides="mgmt", color="lightgray"]
host:d3b -- dut3:e2 [provides="ieee-mc"]
host:d3c -- dut3:e3 [provides="ieee-mc"]
host:d3d -- dut3:e4 [provides="ieee-mc"]
host:d4a -- dut4:e1 [provides="mgmt"]
host:d4a -- dut4:e1 [provides="mgmt", color="lightgray"]
host:d4b -- dut4:e2 [provides="ieee-mc"]
host:d4c -- dut4:e3 [provides="ieee-mc"]
host:d4d -- dut4:e4 [provides="ieee-mc"]
# Ring
dut1:e5 -- dut2:e6
dut2:e5 -- dut3:e6
dut3:e5 -- dut4:e6
dut4:e5 -- dut1:e6
// Lag
dut2:e7 -- dut3:e6 [color="black", penwidth="3"]
dut2:e8 -- dut3:e5 [color="black", penwidth="3"]
# Cross-links
dut3:e7 -- dut1:e7
dut2:e7 -- dut4:e7
// Ring
dut1:e8 -- dut2:e5 [color="black"]
dut3:e8 -- dut4:e5 [color="black"]
dut4:e8 -- dut1:e5 [color="black"]
// Cross-links
dut1:e6 -- dut3:e7 [color="red"]
dut2:e6 -- dut4:e7 [color="red"]
}
+254
View File
@@ -0,0 +1,254 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.43.0 (0)
-->
<!-- Title: quad Pages: 1 -->
<svg width="542pt" height="560pt"
viewBox="0.00 0.00 541.97 560.05" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 556.05)">
<title>quad</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-556.05 537.97,-556.05 537.97,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="grey" points="0,-92.02 0,-460.02 91,-460.02 91,-92.02 0,-92.02"/>
<text text-anchor="middle" x="25" y="-272.32" font-family="monospace" font-size="14.00" fill="grey">host</text>
<polyline fill="none" stroke="grey" points="50,-92.02 50,-460.02 "/>
<text text-anchor="middle" x="70.5" y="-444.82" font-family="monospace" font-size="14.00" fill="grey">d1a</text>
<polyline fill="none" stroke="grey" points="50,-437.02 91,-437.02 "/>
<text text-anchor="middle" x="70.5" y="-421.82" font-family="monospace" font-size="14.00" fill="grey">d1b</text>
<polyline fill="none" stroke="grey" points="50,-414.02 91,-414.02 "/>
<text text-anchor="middle" x="70.5" y="-398.82" font-family="monospace" font-size="14.00" fill="grey">d1c</text>
<polyline fill="none" stroke="grey" points="50,-391.02 91,-391.02 "/>
<text text-anchor="middle" x="70.5" y="-375.82" font-family="monospace" font-size="14.00" fill="grey">d1d</text>
<polyline fill="none" stroke="grey" points="50,-368.02 91,-368.02 "/>
<text text-anchor="middle" x="70.5" y="-352.82" font-family="monospace" font-size="14.00" fill="grey">d2a</text>
<polyline fill="none" stroke="grey" points="50,-345.02 91,-345.02 "/>
<text text-anchor="middle" x="70.5" y="-329.82" font-family="monospace" font-size="14.00" fill="grey">d2b</text>
<polyline fill="none" stroke="grey" points="50,-322.02 91,-322.02 "/>
<text text-anchor="middle" x="70.5" y="-306.82" font-family="monospace" font-size="14.00" fill="grey">d2c</text>
<polyline fill="none" stroke="grey" points="50,-299.02 91,-299.02 "/>
<text text-anchor="middle" x="70.5" y="-283.82" font-family="monospace" font-size="14.00" fill="grey">d2d</text>
<polyline fill="none" stroke="grey" points="50,-276.02 91,-276.02 "/>
<text text-anchor="middle" x="70.5" y="-260.82" font-family="monospace" font-size="14.00" fill="grey">d3a</text>
<polyline fill="none" stroke="grey" points="50,-253.02 91,-253.02 "/>
<text text-anchor="middle" x="70.5" y="-237.82" font-family="monospace" font-size="14.00" fill="grey">d3b</text>
<polyline fill="none" stroke="grey" points="50,-230.02 91,-230.02 "/>
<text text-anchor="middle" x="70.5" y="-214.82" font-family="monospace" font-size="14.00" fill="grey">d3c</text>
<polyline fill="none" stroke="grey" points="50,-207.02 91,-207.02 "/>
<text text-anchor="middle" x="70.5" y="-191.82" font-family="monospace" font-size="14.00" fill="grey">d3d</text>
<polyline fill="none" stroke="grey" points="50,-184.02 91,-184.02 "/>
<text text-anchor="middle" x="70.5" y="-168.82" font-family="monospace" font-size="14.00" fill="grey">d4a</text>
<polyline fill="none" stroke="grey" points="50,-161.02 91,-161.02 "/>
<text text-anchor="middle" x="70.5" y="-145.82" font-family="monospace" font-size="14.00" fill="grey">d4b</text>
<polyline fill="none" stroke="grey" points="50,-138.02 91,-138.02 "/>
<text text-anchor="middle" x="70.5" y="-122.82" font-family="monospace" font-size="14.00" fill="grey">d4c</text>
<polyline fill="none" stroke="grey" points="50,-115.02 91,-115.02 "/>
<text text-anchor="middle" x="70.5" y="-99.82" font-family="monospace" font-size="14.00" fill="grey">d4d</text>
</g>
<!-- dut1 -->
<g id="node2" class="node">
<title>dut1</title>
<polygon fill="none" stroke="black" points="370.04,-459.55 370.04,-551.55 486.04,-551.55 486.04,-459.55 370.04,-459.55"/>
<text text-anchor="middle" x="386.54" y="-536.35" font-family="monospace" font-size="14.00">e1</text>
<polyline fill="none" stroke="black" points="370.04,-528.55 403.04,-528.55 "/>
<text text-anchor="middle" x="386.54" y="-513.35" font-family="monospace" font-size="14.00">e2</text>
<polyline fill="none" stroke="black" points="370.04,-505.55 403.04,-505.55 "/>
<text text-anchor="middle" x="386.54" y="-490.35" font-family="monospace" font-size="14.00">e3</text>
<polyline fill="none" stroke="black" points="370.04,-482.55 403.04,-482.55 "/>
<text text-anchor="middle" x="386.54" y="-467.35" font-family="monospace" font-size="14.00">e4</text>
<polyline fill="none" stroke="black" points="403.04,-459.55 403.04,-551.55 "/>
<text text-anchor="middle" x="428.04" y="-501.85" font-family="monospace" font-size="14.00">dut1</text>
<polyline fill="none" stroke="black" points="453.04,-459.55 453.04,-551.55 "/>
<text text-anchor="middle" x="469.54" y="-536.35" font-family="monospace" font-size="14.00">e5</text>
<polyline fill="none" stroke="black" points="453.04,-528.55 486.04,-528.55 "/>
<text text-anchor="middle" x="469.54" y="-513.35" font-family="monospace" font-size="14.00">e6</text>
<polyline fill="none" stroke="black" points="453.04,-505.55 486.04,-505.55 "/>
<text text-anchor="middle" x="469.54" y="-490.35" font-family="monospace" font-size="14.00">e7</text>
<polyline fill="none" stroke="black" points="453.04,-482.55 486.04,-482.55 "/>
<text text-anchor="middle" x="469.54" y="-467.35" font-family="monospace" font-size="14.00">e8</text>
</g>
<!-- host&#45;&#45;dut1 -->
<g id="edge1" class="edge">
<title>host:d1a&#45;&#45;dut1:e1</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-449.02C91.5,-449.02 370.04,-540.55 370.04,-540.55"/>
</g>
<!-- host&#45;&#45;dut1 -->
<g id="edge2" class="edge">
<title>host:d1b&#45;&#45;dut1:e2</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M91.5,-426.02C91.5,-426.02 370.04,-517.55 370.04,-517.55"/>
</g>
<!-- host&#45;&#45;dut1 -->
<g id="edge3" class="edge">
<title>host:d1c&#45;&#45;dut1:e3</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M91.5,-403.02C91.5,-403.02 370.04,-493.55 370.04,-493.55"/>
</g>
<!-- host&#45;&#45;dut1 -->
<g id="edge4" class="edge">
<title>host:d1d&#45;&#45;dut1:e4</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M91.5,-380.02C91.5,-380.02 142.26,-412.3 187.02,-428.53 265.48,-456.97 370.04,-470.55 370.04,-470.55"/>
</g>
<!-- dut2 -->
<g id="node3" class="node">
<title>dut2</title>
<polygon fill="none" stroke="black" points="217.02,-306.53 217.02,-398.53 333.02,-398.53 333.02,-306.53 217.02,-306.53"/>
<text text-anchor="middle" x="233.52" y="-383.33" font-family="monospace" font-size="14.00">e1</text>
<polyline fill="none" stroke="black" points="217.02,-375.53 250.02,-375.53 "/>
<text text-anchor="middle" x="233.52" y="-360.33" font-family="monospace" font-size="14.00">e2</text>
<polyline fill="none" stroke="black" points="217.02,-352.53 250.02,-352.53 "/>
<text text-anchor="middle" x="233.52" y="-337.33" font-family="monospace" font-size="14.00">e3</text>
<polyline fill="none" stroke="black" points="217.02,-329.53 250.02,-329.53 "/>
<text text-anchor="middle" x="233.52" y="-314.33" font-family="monospace" font-size="14.00">e4</text>
<polyline fill="none" stroke="black" points="250.02,-306.53 250.02,-398.53 "/>
<text text-anchor="middle" x="275.02" y="-348.83" font-family="monospace" font-size="14.00">dut2</text>
<polyline fill="none" stroke="black" points="300.02,-306.53 300.02,-398.53 "/>
<text text-anchor="middle" x="316.52" y="-383.33" font-family="monospace" font-size="14.00">e5</text>
<polyline fill="none" stroke="black" points="300.02,-375.53 333.02,-375.53 "/>
<text text-anchor="middle" x="316.52" y="-360.33" font-family="monospace" font-size="14.00">e6</text>
<polyline fill="none" stroke="black" points="300.02,-352.53 333.02,-352.53 "/>
<text text-anchor="middle" x="316.52" y="-337.33" font-family="monospace" font-size="14.00">e7</text>
<polyline fill="none" stroke="black" points="300.02,-329.53 333.02,-329.53 "/>
<text text-anchor="middle" x="316.52" y="-314.33" font-family="monospace" font-size="14.00">e8</text>
</g>
<!-- host&#45;&#45;dut2 -->
<g id="edge5" class="edge">
<title>host:d2a&#45;&#45;dut2:e1</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-357.02C91.5,-357.02 217.02,-387.53 217.02,-387.53"/>
</g>
<!-- host&#45;&#45;dut2 -->
<g id="edge6" class="edge">
<title>host:d2b&#45;&#45;dut2:e2</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M91.5,-334.02C91.5,-334.02 217.02,-364.53 217.02,-364.53"/>
</g>
<!-- host&#45;&#45;dut2 -->
<g id="edge7" class="edge">
<title>host:d2c&#45;&#45;dut2:e3</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M91.5,-311.02C91.5,-311.02 217.02,-340.53 217.02,-340.53"/>
</g>
<!-- host&#45;&#45;dut2 -->
<g id="edge8" class="edge">
<title>host:d2d&#45;&#45;dut2:e4</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M91.5,-288.02C91.5,-288.02 217.02,-317.53 217.02,-317.53"/>
</g>
<!-- dut3 -->
<g id="node4" class="node">
<title>dut3</title>
<polygon fill="none" stroke="black" points="217.02,-153.52 217.02,-245.52 333.02,-245.52 333.02,-153.52 217.02,-153.52"/>
<text text-anchor="middle" x="233.52" y="-230.32" font-family="monospace" font-size="14.00">e1</text>
<polyline fill="none" stroke="black" points="217.02,-222.52 250.02,-222.52 "/>
<text text-anchor="middle" x="233.52" y="-207.32" font-family="monospace" font-size="14.00">e2</text>
<polyline fill="none" stroke="black" points="217.02,-199.52 250.02,-199.52 "/>
<text text-anchor="middle" x="233.52" y="-184.32" font-family="monospace" font-size="14.00">e3</text>
<polyline fill="none" stroke="black" points="217.02,-176.52 250.02,-176.52 "/>
<text text-anchor="middle" x="233.52" y="-161.32" font-family="monospace" font-size="14.00">e4</text>
<polyline fill="none" stroke="black" points="250.02,-153.52 250.02,-245.52 "/>
<text text-anchor="middle" x="275.02" y="-195.82" font-family="monospace" font-size="14.00">dut3</text>
<polyline fill="none" stroke="black" points="300.02,-153.52 300.02,-245.52 "/>
<text text-anchor="middle" x="316.52" y="-230.32" font-family="monospace" font-size="14.00">e5</text>
<polyline fill="none" stroke="black" points="300.02,-222.52 333.02,-222.52 "/>
<text text-anchor="middle" x="316.52" y="-207.32" font-family="monospace" font-size="14.00">e6</text>
<polyline fill="none" stroke="black" points="300.02,-199.52 333.02,-199.52 "/>
<text text-anchor="middle" x="316.52" y="-184.32" font-family="monospace" font-size="14.00">e7</text>
<polyline fill="none" stroke="black" points="300.02,-176.52 333.02,-176.52 "/>
<text text-anchor="middle" x="316.52" y="-161.32" font-family="monospace" font-size="14.00">e8</text>
</g>
<!-- host&#45;&#45;dut3 -->
<g id="edge9" class="edge">
<title>host:d3a&#45;&#45;dut3:e1</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-264.02C91.5,-264.02 217.02,-234.52 217.02,-234.52"/>
</g>
<!-- host&#45;&#45;dut3 -->
<g id="edge10" class="edge">
<title>host:d3b&#45;&#45;dut3:e2</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M91.5,-241.02C91.5,-241.02 217.02,-211.52 217.02,-211.52"/>
</g>
<!-- host&#45;&#45;dut3 -->
<g id="edge11" class="edge">
<title>host:d3c&#45;&#45;dut3:e3</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M91.5,-218.02C91.5,-218.02 217.02,-187.52 217.02,-187.52"/>
</g>
<!-- host&#45;&#45;dut3 -->
<g id="edge12" class="edge">
<title>host:d3d&#45;&#45;dut3:e4</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M91.5,-195.02C91.5,-195.02 217.02,-164.52 217.02,-164.52"/>
</g>
<!-- dut4 -->
<g id="node5" class="node">
<title>dut4</title>
<polygon fill="none" stroke="black" points="370.04,-0.5 370.04,-92.5 486.04,-92.5 486.04,-0.5 370.04,-0.5"/>
<text text-anchor="middle" x="386.54" y="-77.3" font-family="monospace" font-size="14.00">e1</text>
<polyline fill="none" stroke="black" points="370.04,-69.5 403.04,-69.5 "/>
<text text-anchor="middle" x="386.54" y="-54.3" font-family="monospace" font-size="14.00">e2</text>
<polyline fill="none" stroke="black" points="370.04,-46.5 403.04,-46.5 "/>
<text text-anchor="middle" x="386.54" y="-31.3" font-family="monospace" font-size="14.00">e3</text>
<polyline fill="none" stroke="black" points="370.04,-23.5 403.04,-23.5 "/>
<text text-anchor="middle" x="386.54" y="-8.3" font-family="monospace" font-size="14.00">e4</text>
<polyline fill="none" stroke="black" points="403.04,-0.5 403.04,-92.5 "/>
<text text-anchor="middle" x="428.04" y="-42.8" font-family="monospace" font-size="14.00">dut4</text>
<polyline fill="none" stroke="black" points="453.04,-0.5 453.04,-92.5 "/>
<text text-anchor="middle" x="469.54" y="-77.3" font-family="monospace" font-size="14.00">e5</text>
<polyline fill="none" stroke="black" points="453.04,-69.5 486.04,-69.5 "/>
<text text-anchor="middle" x="469.54" y="-54.3" font-family="monospace" font-size="14.00">e6</text>
<polyline fill="none" stroke="black" points="453.04,-46.5 486.04,-46.5 "/>
<text text-anchor="middle" x="469.54" y="-31.3" font-family="monospace" font-size="14.00">e7</text>
<polyline fill="none" stroke="black" points="453.04,-23.5 486.04,-23.5 "/>
<text text-anchor="middle" x="469.54" y="-8.3" font-family="monospace" font-size="14.00">e8</text>
</g>
<!-- host&#45;&#45;dut4 -->
<g id="edge13" class="edge">
<title>host:d4a&#45;&#45;dut4:e1</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-172.02C91.5,-172.02 142.26,-139.74 187.02,-123.52 265.48,-95.07 370.04,-81.5 370.04,-81.5"/>
</g>
<!-- host&#45;&#45;dut4 -->
<g id="edge14" class="edge">
<title>host:d4b&#45;&#45;dut4:e2</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M91.5,-149.02C91.5,-149.02 370.04,-58.5 370.04,-58.5"/>
</g>
<!-- host&#45;&#45;dut4 -->
<g id="edge15" class="edge">
<title>host:d4c&#45;&#45;dut4:e3</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M91.5,-126.02C91.5,-126.02 370.04,-34.5 370.04,-34.5"/>
</g>
<!-- host&#45;&#45;dut4 -->
<g id="edge16" class="edge">
<title>host:d4d&#45;&#45;dut4:e4</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M91.5,-103.02C91.5,-103.02 370.04,-11.5 370.04,-11.5"/>
</g>
<!-- dut1&#45;&#45;dut2 -->
<g id="edge19" class="edge">
<title>dut1:e8&#45;&#45;dut2:e5</title>
<path fill="none" stroke="black" stroke-width="2" d="M470.04,-459.55C470.04,-459.55 333.02,-387.53 333.02,-387.53"/>
</g>
<!-- dut1&#45;&#45;dut3 -->
<g id="edge22" class="edge">
<title>dut1:e6&#45;&#45;dut3:e7</title>
<path fill="none" stroke="red" stroke-width="2" d="M486.13,-517.51C486.13,-517.51 522.61,-470.31 516.04,-429.55 494.57,-296.4 333.02,-187.52 333.02,-187.52"/>
</g>
<!-- dut2&#45;&#45;dut3 -->
<g id="edge17" class="edge">
<title>dut2:e7&#45;&#45;dut3:e6</title>
<path fill="none" stroke="black" stroke-width="3" d="M333.12,-340.57C333.12,-340.57 369.92,-306.58 363.02,-275.52 356.22,-244.85 333.12,-211.48 333.12,-211.48"/>
</g>
<!-- dut2&#45;&#45;dut3 -->
<g id="edge18" class="edge">
<title>dut2:e8&#45;&#45;dut3:e5</title>
<path fill="none" stroke="black" stroke-width="3" d="M317.02,-306.53C317.02,-306.53 317.02,-245.52 317.02,-245.52"/>
</g>
<!-- dut2&#45;&#45;dut4 -->
<g id="edge23" class="edge">
<title>dut2:e6&#45;&#45;dut4:e7</title>
<path fill="none" stroke="red" stroke-width="2" d="M333.02,-364.53C333.02,-364.53 494.57,-255.64 516.04,-122.5 522.61,-81.73 486.13,-34.54 486.13,-34.54"/>
</g>
<!-- dut3&#45;&#45;dut4 -->
<g id="edge20" class="edge">
<title>dut3:e8&#45;&#45;dut4:e5</title>
<path fill="none" stroke="black" stroke-width="2" d="M333.02,-164.52C333.02,-164.52 470.04,-92.5 470.04,-92.5"/>
</g>
<!-- dut4&#45;&#45;dut1 -->
<g id="edge21" class="edge">
<title>dut4:e8&#45;&#45;dut1:e5</title>
<path fill="none" stroke="black" stroke-width="2" d="M486.1,-11.58C486.1,-11.58 509.33,-71.88 516.04,-122.5 533.97,-257.78 533.97,-294.26 516.04,-429.55 509.33,-480.17 486.1,-540.47 486.1,-540.47"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

+68 -46
View File
@@ -7,16 +7,17 @@
# their respective enabled features in a MODULES array.
# Example:
# MODULES=("module@revision -e feature1 -e feature2")
# shellcheck disable=SC1090
source "$1"
# optional env variable override
if [ -n "$SYSREPOCTL_EXECUTABLE" ]; then
SYSREPOCTL="$SYSREPOCTL_EXECUTABLE"
# avoid problems with sudo PATH
elif [ `id -u` -eq 0 ] && [ -n "$USER" ] && [ `command -v su` ]; then
SYSREPOCTL=`command sysrepoctl -l $USER`
elif [ "$(id -u)" -eq 0 ] && [ -n "$USER" ] && [ -n "$(command -v su)" ]; then
SYSREPOCTL=$(command sysrepoctl -l "$USER")
else
SYSREPOCTL=`command sysrepoctl`
SYSREPOCTL=$(command sysrepoctl)
fi
MODDIR=${SEARCH_PATH}
@@ -26,22 +27,54 @@ PERMS="660"
CMD_INSTALL=
# functions
INSTALL_MODULE_CMD() {
install()
{
if [ -z "${CMD_INSTALL}" ]; then
CMD_INSTALL="'$SYSREPOCTL' -s $MODDIR -v2"
fi
CMD_INSTALL="$CMD_INSTALL -i $MODDIR/$1 -p '$PERMS'"
if [ ! -z "${OWNER}" ]; then
if [ -n "${OWNER}" ]; then
CMD_INSTALL="$CMD_INSTALL -o '$OWNER'"
fi
if [ ! -z "${GROUP}" ]; then
if [ -n "${GROUP}" ]; then
CMD_INSTALL="$CMD_INSTALL -g '$GROUP'"
fi
}
UPDATE_MODULE() {
CMD="'$SYSREPOCTL' -U $MODDIR/$1 -s '$MODDIR' -v2"
update()
{
local module="$1"
local cmd="'$SYSREPOCTL' -U $MODDIR/$module -s '$MODDIR' -v2"
local output rc
output=$(eval "$cmd" 2>&1)
rc=$?
if [ $rc -ne 0 ]; then
if echo "$output" | grep -q "Module .* already installed"; then
echo "*** Warning: Module $module is already installed. Skipping update."
return 0
fi
echo "*** Error: failed updating module $module: $output" >&2
return $rc
fi
echo "*** Successfully updated module $module."
return 0
}
chperm()
{
CMD="'$SYSREPOCTL' -c $1 -p '$PERMS' -v2"
if [ -n "${OWNER}" ]; then
CMD="$CMD -o '$OWNER'"
fi
if [ -n "${GROUP}" ]; then
CMD="$CMD -g '$GROUP'"
fi
eval "$CMD"
local rc=$?
if [ $rc -ne 0 ]; then
@@ -49,79 +82,68 @@ UPDATE_MODULE() {
fi
}
CHANGE_PERMS() {
CMD="'$SYSREPOCTL' -c $1 -p '$PERMS' -v2"
if [ ! -z "${OWNER}" ]; then
CMD="$CMD -o '$OWNER'"
fi
if [ ! -z "${GROUP}" ]; then
CMD="$CMD -g '$GROUP'"
fi
eval $CMD
enable()
{
$SYSREPOCTL -c "$1" -e "$2" -v2
local rc=$?
if [ $rc -ne 0 ]; then
exit $rc
fi
}
ENABLE_FEATURE() {
"$SYSREPOCTL" -c $1 -e $2 -v2
local rc=$?
if [ $rc -ne 0 ]; then
exit $rc
fi
}
# get current modules
SCTL_MODULES=`$SYSREPOCTL -l`
for i in "${MODULES[@]}"; do
name=`echo "$i" | sed 's/\([^@]*\).*/\1/'`
# Skip first 5 lines of header and last 3 lines of footer
SCTL_MODULES=$($SYSREPOCTL -l |tail -n +5 |head -n -3)
SCTL_MODULE=`echo "$SCTL_MODULES" | grep "^$name \+|[^|]*| I"`
for module in "${MODULES[@]}"; do
name=$(echo "$module" | awk -F'[@.]' '{print $1}')
date=$(echo "$module" | awk -F'[@.]' '{print $2}')
SCTL_MODULE=$(echo "$SCTL_MODULES" | grep "^$name \+|[^|]*| I")
if [ -z "$SCTL_MODULE" ]; then
# prepare command to install module with all its features
echo "*** Installing YANG model $name ..."
INSTALL_MODULE_CMD "$i"
install "$module"
continue
fi
sctl_revision=`echo "$SCTL_MODULE" | sed 's/[^|]*| \([^ ]*\).*/\1/'`
revision=`echo "$i" | sed 's/[^@]*@\([^\.]*\).*/\1/'`
if [ "$sctl_revision" \< "$revision" ]; then
rev=$(echo "$SCTL_MODULE" | awk '{print $3}')
if [ "$rev" != "$date" ] || echo "$FORCE_UPDATE" | grep -qw "$name"; then
# update module without any features
file=`echo "$i" | cut -d' ' -f 1`
file=$(echo "$module" | cut -d' ' -f 1)
echo "*** Updating YANG model $name ($file) ..."
UPDATE_MODULE "$file"
update "$file"
fi
sctl_owner=`echo "$SCTL_MODULE" | sed 's/\([^|]*|\)\{3\} \([^:]*\).*/\2/'`
sctl_group=`echo "$SCTL_MODULE" | sed 's/\([^|]*|\)\{3\}[^:]*:\([^ ]*\).*/\2/'`
sctl_perms=`echo "$SCTL_MODULE" | sed 's/\([^|]*|\)\{4\} \([^ ]*\).*/\2/'`
#sctl_owner=`echo "$SCTL_MODULE" | sed 's/\([^|]*|\)\{3\} \([^:]*\).*/\2/'`
#sctl_group=`echo "$SCTL_MODULE" | sed 's/\([^|]*|\)\{3\}[^:]*:\([^ ]*\).*/\2/'`
sctl_perms=$(echo "$SCTL_MODULE" | sed 's/\([^|]*|\)\{4\} \([^ ]*\).*/\2/')
if [ "$sctl_perms" != "$PERMS" ]; then
# change permissions/owner
echo "*** Changing YANG model $name permissions ..."
CHANGE_PERMS "$name"
chperm "$name"
fi
# parse sysrepoctl features and add extra space at the end for easier matching
sctl_features="`echo "$SCTL_MODULE" | sed 's/\([^|]*|\)\{6\}\(.*\)/\2/'` "
# parse features we want to enable
features=`echo "$i" | sed 's/[^ ]* \(.*\)/\1/'`
features=`echo "$module" | sed 's/[^ ]* \(.*\)/\1/'`
while [ "${features:0:3}" = "-e " ]; do
# skip "-e "
features=${features:3}
# parse feature
feature=`echo "$features" | sed 's/\([^[:space:]]*\).*/\1/'`
feature=$(echo "$features" | sed 's/\([^[:space:]]*\).*/\1/')
# enable feature if not already
sctl_feature=`echo "$sctl_features" | grep " ${feature} "`
sctl_feature=$(echo "$sctl_features" | grep " ${feature} ")
if [ -z "$sctl_feature" ]; then
# enable feature
ENABLE_FEATURE $name $feature
enable "$name" "$feature"
fi
# next iteration, skip this feature
features=`echo "$features" | sed 's/[^[:space:]]* \(.*\)/\1/'`
features=$(echo "$features" | sed 's/[^[:space:]]* \(.*\)/\1/')
done
done