Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
3debcf8
feat: add SR-OS component config generation to SR-SIM
kaelemc Sep 23, 2025
db7e1b9
feat: add power config generation for SR-SIM
kaelemc Sep 27, 2025
4ffad0d
make default srsim configurated root context based
kaelemc Sep 27, 2025
2ab700a
srsim: Add SR-1se to power config
kaelemc Sep 27, 2025
326795c
tests: add initial sr-14s config gen test
kaelemc Sep 30, 2025
eeba89f
tests: add 08 srsim tests to gh workflow def
kaelemc Sep 30, 2025
e62085e
tests: remove fatfingered 's'
kaelemc Sep 30, 2025
7520029
tests: fix license path
kaelemc Sep 30, 2025
c6bf79f
test: use correct robot syntax...
kaelemc Sep 30, 2025
456d018
tests: sr-14s power modules 2->20
kaelemc Sep 30, 2025
2dd6481
tests: add SR-2s w/ MDA state verification
kaelemc Sep 30, 2025
3b2b87a
tests: change to 30s wait to ensure MDAs come up
kaelemc Sep 30, 2025
64041ec
remove redundant distributed node check
kaelemc Sep 30, 2025
413cbf9
docs: update srsim docs for component config generation
kaelemc Sep 30, 2025
5080174
remove redundant conditional check
kaelemc Sep 30, 2025
4dfa9c0
tests: bump 08 srsim sleep to 45s
kaelemc Sep 30, 2025
ffc0e7e
tests: 08 srsim, use wait until instead of sleep
kaelemc Sep 30, 2025
977225d
don't return err for generateComponentConfig()
kaelemc Sep 30, 2025
28f2e1a
feat: add SR-OS component config generation to SR-SIM
kaelemc Sep 23, 2025
220f7d4
feat: add power config generation for SR-SIM
kaelemc Sep 27, 2025
10d4753
make default srsim configurated root context based
kaelemc Sep 27, 2025
b3ded6d
srsim: Add SR-1se to power config
kaelemc Sep 27, 2025
4967505
tests: add initial sr-14s config gen test
kaelemc Sep 30, 2025
b75218a
tests: add 08 srsim tests to gh workflow def
kaelemc Sep 30, 2025
0e66ec5
tests: remove fatfingered 's'
kaelemc Sep 30, 2025
7ee5115
tests: fix license path
kaelemc Sep 30, 2025
1c94469
test: use correct robot syntax...
kaelemc Sep 30, 2025
c4c4920
tests: sr-14s power modules 2->20
kaelemc Sep 30, 2025
b043ea2
tests: add SR-2s w/ MDA state verification
kaelemc Sep 30, 2025
4b3c79a
tests: change to 30s wait to ensure MDAs come up
kaelemc Sep 30, 2025
358707b
remove redundant distributed node check
kaelemc Sep 30, 2025
48b2d00
docs: update srsim docs for component config generation
kaelemc Sep 30, 2025
b38537e
remove redundant conditional check
kaelemc Sep 30, 2025
9679d17
tests: bump 08 srsim sleep to 45s
kaelemc Sep 30, 2025
58d7b0b
tests: 08 srsim, use wait until instead of sleep
kaelemc Sep 30, 2025
341cadb
don't return err for generateComponentConfig()
kaelemc Sep 30, 2025
c8791a0
fix: add component sorting to SR-SIM
kaelemc Sep 24, 2025
3175877
reverse sorting order so we attach to non CPM slot
kaelemc Sep 30, 2025
94b2009
test: update srsim unit test for new sorting
kaelemc Sep 30, 2025
d730ad3
adjust srsim hostname & ssh entry for base node name.
kaelemc Sep 30, 2025
379848f
linter fixes
kaelemc Sep 30, 2025
bc37c14
Move distributed srsim mgmt ip fetching into dedicated func
kaelemc Sep 30, 2025
e8d904d
tests: remove component suffixes
kaelemc Sep 30, 2025
c4d0e78
revert sshconfig.go
kaelemc Sep 30, 2025
4899297
don't rename the base node for component based distributed nodes
kaelemc Sep 30, 2025
a2f4dd1
fix typo, set ipv6 base node val to ipv6 not ipv4
kaelemc Oct 1, 2025
7578e37
Add the base node name to the TLS cert SAN
kaelemc Oct 1, 2025
f79dfc3
tests: make 07 test less randomly failure prone
kaelemc Oct 1, 2025
6129d97
tests: srsim 07, fix dupe name
kaelemc Oct 1, 2025
3d3c2fe
add env var to disable config generation
kaelemc Oct 1, 2025
06c5d4c
update test for config disable env var
kaelemc Oct 1, 2025
22fbb1b
change disable config gen env var name to align with clab scoping.
kaelemc Oct 1, 2025
a3f9bef
docs: change type values to lowercase for schema alignment
kaelemc Oct 1, 2025
5412c6a
docs: improve config generation section
kaelemc Oct 1, 2025
13f47b8
in the inspect output show the mgmt IP even if it doesn't belong to C…
kaelemc Oct 1, 2025
9ef3a44
format
hellt Oct 1, 2025
095ab8c
remove obv comment
hellt Oct 1, 2025
064ab03
doc brushup
hellt Oct 1, 2025
7be9738
factor out power config
hellt Oct 1, 2025
edcd4cd
move power gen func to power
hellt Oct 1, 2025
e7a046b
tests: change 07-srsim to expect 10 host entries to account for base …
kaelemc Oct 1, 2025
4e2eebd
don't generate the component config if startup cfg is defined
kaelemc Oct 1, 2025
644702e
add debug log message to indicate startup config is being skipped
kaelemc Oct 1, 2025
9da0fb6
Merge remote-tracking branch 'origin/sros-component-cfg-gen' into srs…
kaelemc Oct 1, 2025
4fceeb7
fix merge mistake
kaelemc Oct 1, 2025
bcb7fbf
fix error in mgmt pLen assingment (v4/v6 mismatch)
kaelemc Oct 2, 2025
4b18b30
tets: add test to confirm sorting & mgmt ip val for clab ins
kaelemc Oct 2, 2025
495e5ac
tests: remove -a cpm suffix from target hostnames
kaelemc Oct 2, 2025
a1bb303
tests: fix variable syntax for 08-srsim
kaelemc Oct 2, 2025
d26de22
tests: move component sort rf test to it's own robot test
kaelemc Oct 2, 2025
2ec0ff4
tests: 09-srsim, fix inspect topo file path
kaelemc Oct 2, 2025
13ceaf5
tests: 09-test, fix hardcoded lab name in jq expr
kaelemc Oct 2, 2025
e915830
tests: bump 07-srsim link check to 4 mins timeout
kaelemc Oct 2, 2025
ee56d75
docs: add info about component sorting for grouped node def
kaelemc Oct 2, 2025
02fc0bd
Merge remote-tracking branch 'origin/main' into srsim-comp-sorting
kaelemc Oct 3, 2025
e87cf05
tests: remove `sudo` before clab bin var
kaelemc Oct 3, 2025
4862155
docs: grammar fix for component sorting point
kaelemc Oct 3, 2025
d9a54ff
fix sorting order so that cpm B comes before A
kaelemc Oct 3, 2025
c695cc7
update comment to reflect sorting order
kaelemc Oct 3, 2025
a2fd3ff
make format
hellt Oct 3, 2025
65c741d
var rename
hellt Oct 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/srsim-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
- "05*.robot"
- "07*.robot"
- "08*.robot"
- "09*.robot"
steps:
- name: Checkout
uses: actions/checkout@v5
Expand Down
23 changes: 12 additions & 11 deletions docs/manual/kinds/sros.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,14 +382,14 @@ topology:

When a distributed SR-SIM node is defined using `components`, we need to take into account the following:

1. Individual containers will be attached to the namespace of the 1st element of the `components` list: CPM-A in the above examples.
1. The component order gets sorted[^5] upon deployment of the lab. Individual containers will be attached to the namespace of the 1st element in the sorted `components` list: CPM-1 in the above examples.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

likely we need to explain the sorting order, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also it seems the -a|b suffix is no more for the containers? This needs to be explained in the docs maybe?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

@kaelemc kaelemc Oct 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re: suffix, ok yes I will adjust that

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hellt Actually it looks like there is not really any mention of the CPM suffix in the docs to begin with?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, looks like it =)

2. When changing a MDA or card type from its default value, the configuration for card, SFM and MDA must be also defined.
3. Links can be added referring to the node name. The same [interface naming](#interface-naming) convention holds for all SR-SIM nodes.
4. Environment variable based configuration on per-component, or node-level will override the configuration set in `type`, `xiom`, `sfm` and `mda` fields.

##### Configuration for components

When using the `components` structure in the node definition for a distributed node, containerlab will also generate the SR OS configuration for the installed components in the chassis, as well as relevant power supply configuration[^5] to ensure the installed components come up without requiring a user to manually provide the configuration.
When using the `components` structure in the node definition for a distributed node, containerlab will also generate the SR OS configuration for the installed components in the chassis, as well as relevant power supply configuration[^6] to ensure the installed components come up without requiring a user to manually provide the configuration.

/// details | Disabling generated SR OS configuration for `components`
type: tip
Expand Down Expand Up @@ -595,7 +595,7 @@ topology:

## Node configuration

Nokia SR OS nodes come up with a default configuration where only the management interfaces such as NETCONF, SNMP, and gNMI are provisioned[^6].
Nokia SR OS nodes come up with a default configuration where only the management interfaces such as NETCONF, SNMP, and gNMI are provisioned[^7].

### User-defined config

Expand Down Expand Up @@ -712,13 +712,13 @@ Some common BOF options can also be controlled using environmental variables as

### SSH keys

Containerlab supports SSH key injection into the Nokia SR OS nodes prior to deployment. First containerlab retrieves all public keys from `~/.ssh`[^7] directory and `~/.ssh/authorized_keys` file, then it retrieves public keys from the ssh agent if one is running.
Containerlab supports SSH key injection into the Nokia SR OS nodes prior to deployment. First containerlab retrieves all public keys from `~/.ssh`[^8] directory and `~/.ssh/authorized_keys` file, then it retrieves public keys from the ssh agent if one is running.

Next, it will filter out public keys that are not of RSA/ECDSA type. The remaining valid public keys will be configured for the admin user of the Nokia SR OS node using key IDs from 32 downwards[^8] at startup. This will enable key-based authentication when you connect to the node.
Next, it will filter out public keys that are not of RSA/ECDSA type. The remaining valid public keys will be configured for the admin user of the Nokia SR OS node using key IDs from 32 downwards[^9] at startup. This will enable key-based authentication when you connect to the node.

## Packet Capture

Currently, a packet capture on the veth interfaces of the `-{{ kind_display_name }}-` will only display traffic at the ingress direction[^9]. In order to capture traffic bidirectionally, a user needs to create a [mirror service](https://documentation.nokia.com/sr/25-7/7750-sr/books/oam-diagnostics/mirror-services.html) in the SR OS configuration. A simple example topology using [bridges in container namespace](bridge.md#bridges-in-container-namespace) and mirror configuration is provided below for convenience.
Currently, a packet capture on the veth interfaces of the `-{{ kind_display_name }}-` will only display traffic at the ingress direction[^10]. In order to capture traffic bidirectionally, a user needs to create a [mirror service](https://documentation.nokia.com/sr/25-7/7750-sr/books/oam-diagnostics/mirror-services.html) in the SR OS configuration. A simple example topology using [bridges in container namespace](bridge.md#bridges-in-container-namespace) and mirror configuration is provided below for convenience.

/// tab | Topology with mirror service

Expand Down Expand Up @@ -875,8 +875,9 @@ The following labs feature Nokia SR OS (SR-SIM) node:
[^2]: There are some caveats to this, for instance, if the container referred by the `network-mode` directive is stopped for any reason, all the other depending containers will stop working properly.
[^3]: If needed, switches can be created using the clab kind `bridge` or using `iproute2` commands. MTU needs to be set to 9000 at least.
[^4]: The word SHOULD is interpreted as [RFC2129](https://datatracker.ietf.org/doc/html/rfc2119) and [RFC8174](https://datatracker.ietf.org/doc/html/rfc8174). Links will come up as long as they are attached to the same Linux namespace.
[^5]: Power configuration is only applied for sr-1s, 1se, 2s, 2se, 7s and 14s nodes. See [sros.go](https://github.com/srl-labs/containerlab/pull/2827/files#diff-ae71218e629cf2763a2702c67297cb2ade467276acff8f39973caf1a09731d94R142-R175) for more info.
[^6]: This is a change from the [Vrnetlab](../vrnetlab.md) based vSIM where line cards and MDAs were pre-provisioned for some cases.
[^7]: `~` is the home directory of the user that runs containerlab.
[^8]: If a user wishes to provide a custom startup-config with public keys defined, then they should use key IDs from 1 onwards. This will minimize chances of key ID collision causing containerlab to overwrite user-defined keys.
[^9]: See Github issue [#2741](https://github.com/srl-labs/containerlab/issues/2741)
[^5]: The sort order has numeric defined slots come first, in order of lowest value to highest, and then alphabetically named slots (CPM) come last. See the [sorting test](https://github.com/srl-labs/containerlab/pull/2834/files#diff-ae18606243948313f0fc2df17b8a4eefd16cfcbccfe15219a1ca649712494c6eR16-R24) for more info.
[^6]: Power configuration is only applied for sr-1s, 1se, 2s, 2se, 7s and 14s nodes. See [sros.go](https://github.com/srl-labs/containerlab/pull/2827/files#diff-ae71218e629cf2763a2702c67297cb2ade467276acff8f39973caf1a09731d94R142-R175) for more info.
[^7]: This is a change from the [Vrnetlab](../vrnetlab.md) based vSIM where line cards and MDAs were pre-provisioned for some cases.
[^8]: `~` is the home directory of the user that runs containerlab.
[^9]: If a user wishes to provide a custom startup-config with public keys defined, then they should use key IDs from 1 onwards. This will minimize chances of key ID collision causing containerlab to overwrite user-defined keys.
[^10]: See Github issue [#2741](https://github.com/srl-labs/containerlab/issues/2741)
197 changes: 183 additions & 14 deletions nodes/sros/sros.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ import (
"path"
"path/filepath"
"regexp"
"slices"
"strconv"
"strings"
"text/template"
"time"
"unicode"

"github.com/beevik/etree"
"github.com/brunoga/deep"
Expand Down Expand Up @@ -180,6 +182,11 @@ type sros struct {
// rootComponents stores the OG components from root node for dist setups
// ..allows children of the distributed root node to access root components (ie. for cfg gen)
rootComponents []*clabtypes.Component
// store the longname with cpm suffix
cpmContainerName string
// for component nodes, store base nodes
baseShortName string
baseLongName string

preDeployParams *clabnodes.PreDeployParams
}
Expand Down Expand Up @@ -316,6 +323,7 @@ func (n *sros) PreDeploy(_ context.Context, params *clabnodes.PreDeployParams) e
if n.isStandaloneNode() || (n.isDistributedCardNode() && n.isCPM("")) {
// generate the certificate
if *n.Cfg.Certificate.Issue {
n.Cfg.Certificate.SANs = append(n.Cfg.Certificate.SANs, n.baseShortName, n.baseLongName)
certificate, err := n.LoadOrGenerateCertificate(params.Cert, params.TopologyName)
if err != nil {
return err
Expand Down Expand Up @@ -467,11 +475,40 @@ func (n *sros) DeleteNetnsSymlink() error {
return n.DefaultNode.DeleteNetnsSymlink()
}

// sortComponents ensure components are in order of
// LCs first, then CPMs (cpm b comes first if present).
func (n *sros) sortComponents() {
slices.SortFunc(n.Cfg.Components, func(a, b *clabtypes.Component) int {
s1 := strings.ToUpper(strings.TrimSpace(a.Slot))
s2 := strings.ToUpper(strings.TrimSpace(b.Slot))

p1 := n.getSortOrder(s1)
p2 := n.getSortOrder(s2)

return p1 - p2
})
}

func (n *sros) getSortOrder(slot string) int {
r := rune(slot[0])
if unicode.IsLetter(r) {
// for letters, B before A, letters after numbers
// B=66 -> 34
// A=65 -> 35
return 100 - int(r)
} else {
num, _ := strconv.Atoi(slot)
return num // 1, 2, 3... smaller than CPM slots, so will come first
}
}

func (n *sros) setupComponentNodes() error {
if !n.isDistributedBaseNode() {
return nil
}

n.sortComponents()

// Registry, because it is not a package Var
nr := clabnodes.NewNodeRegistry()
Register(nr)
Expand Down Expand Up @@ -567,6 +604,9 @@ func (n *sros) setupComponentNodes() error {
// store root components for cpms, for config gen
if srosNode, ok := componentNode.(*sros); ok {
srosNode.rootComponents = n.Cfg.Components
// store base node name
srosNode.baseShortName = n.Cfg.ShortName
srosNode.baseLongName = n.Cfg.LongName
}

// store the node in the componentNodes
Expand All @@ -591,25 +631,18 @@ func (n *sros) deployFabric(ctx context.Context, deployParams *clabnodes.DeployP
if err != nil {
return err
}

// adjust general node to be represented as the cpm node
n.Cfg.ShortName = n.calcComponentName(n.Cfg.ShortName, cpmSlot)
n.Cfg.LongName = n.calcComponentName(n.Cfg.LongName, cpmSlot)
n.Cfg.Fqdn = n.calcComponentFqdn(cpmSlot)
// store the CPM container name
n.cpmContainerName = n.calcComponentName(n.Cfg.LongName, cpmSlot)
n.renameDone = true

cpmNode, err := n.cpmNode()
if err != nil {
return err
}

// adjust also the mgmt IP addresses of the general node
contList, err := cpmNode.GetContainers(ctx)
ips, err := n.distNodeMgmtIPs()
if err != nil {
return err
}
n.Cfg.MgmtIPv4Address = contList[0].GetContainerIPv4()
n.Cfg.MgmtIPv6Address = contList[0].GetContainerIPv6()

n.Cfg.MgmtIPv4Address = ips.IPv4
n.Cfg.MgmtIPv6Address = ips.IPv6

return nil
}
Expand Down Expand Up @@ -1145,14 +1178,35 @@ func (n *sros) GetContainers(ctx context.Context) ([]clabruntime.GenericContaine
clabnodes.ErrContainersNotFound)
}

// Forge the IP address to be the actual IP of mgmt
// because the CPM A might not own the netns & mgmt IP
if len(n.Cfg.Components) > 0 {
ips, err := n.distNodeMgmtIPs()
if err == nil {
if ips.IPv4 != "" {
cnts[0].NetworkSettings.IPv4addr = ips.IPv4
cnts[0].NetworkSettings.IPv4pLen = ips.IPv4pLen
}
if ips.IPv6 != "" {
cnts[0].NetworkSettings.IPv6addr = ips.IPv6
cnts[0].NetworkSettings.IPv6pLen = ips.IPv6pLen
}
}
}

return cnts, err
}

// populateHosts adds container hostnames for other nodes of a lab to SR Linux /etc/hosts file
// to mitigate the fact that srlinux uses non default netns for management and thus
// can't leverage docker DNS service.
func (n *sros) populateHosts(ctx context.Context, nodes map[string]clabnodes.Node) error {
hosts, err := n.Runtime.GetHostsPath(ctx, n.Cfg.LongName)
containerName := n.Cfg.LongName
if n.isDistributedBaseNode() && n.cpmContainerName != "" {
containerName = n.cpmContainerName
}

hosts, err := n.Runtime.GetHostsPath(ctx, containerName)
if err != nil {
log.Warn("Unable to locate SR OS node /etc/hosts file", "node", n.Cfg.ShortName, "err", err)
return err
Expand Down Expand Up @@ -1471,6 +1525,98 @@ func CheckPortWithRetry(
return false, lastErr
}

// MgmtIP represents the management IPv4/v6 addresses of a node.
type MgmtIP struct {
IPv4 string
IPv4pLen int
IPv6 string
IPv6pLen int
}

// distNodeMgmtIPs returns both ipv4 and ipv6 management IP address of a
// distributed node defined via components.
// It returns an error if neither is set in the node config.
func (n *sros) distNodeMgmtIPs() (MgmtIP, error) {
ips := MgmtIP{}

var components []*clabtypes.Component

if n.isDistributedBaseNode() {
components = n.Cfg.Components
} else {
components = n.rootComponents
}

// for distributed nodes, get the IP from the
// 0th component container
if len(components) > 0 {
c := components[0]
slot := c.Slot

componentName := n.Cfg.LongName + "-" + slot

containers, err := n.Runtime.ListContainers(
context.Background(),
[]*clabtypes.GenericFilter{
{
FilterType: "name",
Match: componentName,
},
},
)
if err != nil {
return MgmtIP{}, fmt.Errorf("unable to get container %q: %v", componentName, err)
}

for _, container := range containers {
if container.NetworkSettings.IPv4addr != "" {
ips.IPv4 = container.NetworkSettings.IPv4addr
ips.IPv4pLen = container.NetworkSettings.IPv4pLen

}
if container.NetworkSettings.IPv6addr != "" {
ips.IPv6 = container.NetworkSettings.IPv6addr
ips.IPv6pLen = container.NetworkSettings.IPv6pLen
}
}
}

return ips, nil
}

// custom override for hosts file entry to write basename when using component nodes.
func (n *sros) GetHostsEntries(ctx context.Context) (clabtypes.HostEntries, error) {
result, err := n.DefaultNode.GetHostsEntries(ctx)
if err != nil {
return nil, err
}

if n.isDistributedBaseNode() {
ips, err := n.distNodeMgmtIPs()
if err != nil {
return clabtypes.HostEntries{}, err
}

if ips.IPv4 != "" { // v4
result = append(result, clabtypes.NewHostEntry(
ips.IPv4,
n.Cfg.LongName,
clabtypes.IpVersionV4,
).SetDescription(fmt.Sprintf("Kind: %s", n.Cfg.Kind)))
}

if ips.IPv6 != "" {
result = append(result, clabtypes.NewHostEntry(
ips.IPv6,
n.Cfg.LongName,
clabtypes.IpVersionV6,
).SetDescription(fmt.Sprintf("Kind: %s", n.Cfg.Kind)))
}
}

return result, nil
}

// MgmtIPAddr returns ipv4 or ipv6 management IP address of the node.
// It returns an error if neither is set in the node config.
func (n *sros) MgmtIPAddr() (string, error) {
Expand All @@ -1480,6 +1626,21 @@ func (n *sros) MgmtIPAddr() (string, error) {
case n.Cfg.MgmtIPv4Address != "":
return n.Cfg.MgmtIPv4Address, nil
}

if !n.isStandaloneNode() {
ips, err := n.distNodeMgmtIPs()
if err != nil {
return "", err
}

switch {
case ips.IPv6 != "":
return ips.IPv6, nil
case ips.IPv4 != "":
return ips.IPv4, nil
}
}

return n.Cfg.LongName, fmt.Errorf(
"no management IP address (IPv4 or IPv6) configured for node %q",
n.Cfg.LongName,
Expand Down Expand Up @@ -1585,3 +1746,11 @@ func (n *sros) generateComponentConfig() string {

return config.String()
}

// override to fetch CPM to avoid renaming the base node to CPM A.
func (n *sros) GetContainerName() string {
if n.isDistributedBaseNode() && n.cpmContainerName != "" {
return n.cpmContainerName
}
return n.DefaultNode.GetContainerName()
}
Loading