Merge pull request #873 from kernelkit/lag
Add support for static & lacp link aggragetes
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
Before Width: | Height: | Size: 358 KiB After Width: | Height: | Size: 368 KiB |
|
Before Width: | Height: | Size: 281 KiB After Width: | Height: | Size: 280 KiB |
@@ -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.
|
||||
|
||||
@@ -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 - - - - -
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
/*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,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"
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
infix-if-lag.yang
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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[]
|
||||
|
||||
|
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[]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
lag_basic.adoc
|
||||
|
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
|
||||
|
||||
|
||||
<<<
|
||||
|
||||
@@ -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--dut1 -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmt1--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--dut1 -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:mon--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--dut2 -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>host:mgmt2--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--dut2 -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>dut1:link1--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--dut2 -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>dut1:link2--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 |
@@ -0,0 +1 @@
|
||||
lag_failure.adoc
|
||||
|
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
|
||||
|
||||
|
||||
<<<
|
||||
|
||||
@@ -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--dut1 -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmt1--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--dut1 -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:mon--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--dut2 -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>host:mgmt2--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--dut2 -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>host:lb1b--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--dut2 -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>host:lb2b--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--host -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>dut1:link1--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--host -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>dut1:link2--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 |
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
@@ -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"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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--dut1 -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:d1a--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--dut1 -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:d1b--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--dut1 -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>host:d1c--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--dut2 -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>host:d2a--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--dut2 -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>host:d2b--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--dut2 -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>host:d2c--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--dut2 -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>dut1:e4--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--dut2 -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>dut1:e5--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--dut2 -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>dut1:e6--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 |
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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--dut1 -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:d1a--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--dut1 -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:d1b--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--dut1 -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>host:d1c--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--dut1 -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>host:d1d--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--dut2 -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>host:d2a--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--dut2 -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>host:d2b--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--dut2 -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>host:d2c--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--dut2 -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>host:d2d--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--dut3 -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>host:d3a--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--dut3 -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>host:d3b--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--dut3 -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>host:d3c--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--dut3 -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>host:d3d--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--dut4 -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>host:d4a--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--dut4 -->
|
||||
<g id="edge14" class="edge">
|
||||
<title>host:d4b--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--dut4 -->
|
||||
<g id="edge15" class="edge">
|
||||
<title>host:d4c--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--dut4 -->
|
||||
<g id="edge16" class="edge">
|
||||
<title>host:d4d--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--dut2 -->
|
||||
<g id="edge19" class="edge">
|
||||
<title>dut1:e8--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--dut3 -->
|
||||
<g id="edge22" class="edge">
|
||||
<title>dut1:e6--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--dut3 -->
|
||||
<g id="edge17" class="edge">
|
||||
<title>dut2:e7--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--dut3 -->
|
||||
<g id="edge18" class="edge">
|
||||
<title>dut2:e8--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--dut4 -->
|
||||
<g id="edge23" class="edge">
|
||||
<title>dut2:e6--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--dut4 -->
|
||||
<g id="edge20" class="edge">
|
||||
<title>dut3:e8--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--dut1 -->
|
||||
<g id="edge21" class="edge">
|
||||
<title>dut4:e8--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 |
@@ -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
|
||||
|
||||