diff --git a/core/clab.go b/core/clab.go index aa416eb980..b0e3ad074b 100644 --- a/core/clab.go +++ b/core/clab.go @@ -182,8 +182,6 @@ func (c *CLab) filterClabNodes(nodeFilter []string) error { return nil } - c.nodeFilter = nodeFilter - // ensure that the node filter is a subset of the nodes in the topology for _, n := range nodeFilter { if _, ok := c.Config.Topology.Nodes[n]; !ok { @@ -192,6 +190,13 @@ func (c *CLab) filterClabNodes(nodeFilter []string) error { } } + // Auto-expand the filter to include nodes that share the network namespace + // of a filtered node. Without this, destroying a namespace-owning node (e.g. a CPM) + // would orphan its dependents (e.g. line cards) that use network-mode: container:. + nodeFilter = c.expandFilterWithNSDependents(nodeFilter) + + c.nodeFilter = nodeFilter + log.Infof("Applying node filter: %q", nodeFilter) // filter nodes @@ -205,6 +210,48 @@ func (c *CLab) filterClabNodes(nodeFilter []string) error { return nil } +// expandFilterWithNSDependents expands the node filter to include nodes that +// depend on a filtered node's network namespace via network-mode: container:. +// This prevents orphaning containers when their namespace provider is destroyed. +func (c *CLab) expandFilterWithNSDependents(nodeFilter []string) []string { + // Build a map of namespace provider -> dependent nodes from the full topology. + nsDependents := map[string][]string{} + + for name := range c.Config.Topology.Nodes { + netMode := c.Config.Topology.GetNodeNetworkMode(name) + + parts := strings.SplitN(netMode, ":", 2) //nolint: mnd + if parts[0] != "container" || len(parts) < 2 { + continue + } + + provider := parts[1] + nsDependents[provider] = append(nsDependents[provider], name) + } + + // For each filtered node, pull in its namespace dependents (transitively). + expanded := make([]string, len(nodeFilter)) + copy(expanded, nodeFilter) + + queue := make([]string, len(nodeFilter)) + copy(queue, nodeFilter) + + for len(queue) > 0 { + node := queue[0] + queue = queue[1:] + + for _, dep := range nsDependents[node] { + if !slices.Contains(expanded, dep) { + log.Infof("Auto-including node %q in filter (shares network namespace of %q)", dep, node) + expanded = append(expanded, dep) + queue = append(queue, dep) + } + } + } + + return expanded +} + // initMgmtNetwork sets management network config. func (c *CLab) initMgmtNetwork() error { log.Debugf("method initMgmtNetwork was called mgmt params %+v", c.Config.Mgmt) diff --git a/core/config.go b/core/config.go index fa6bf3394d..a67ce2a694 100644 --- a/core/config.go +++ b/core/config.go @@ -588,12 +588,15 @@ func (c *CLab) verifyContainersUniqueness(ctx context.Context) error { // check that none of the existing containers has a label that matches // the lab name of a currently deploying lab // this ensures lab uniqueness - for idx := range containers { - if containers[idx].Labels[clabconstants.Containerlab] == c.Config.Name { - return fmt.Errorf( - "the '%s' lab has already been deployed. Destroy the lab before deploying a "+ - "lab with the same name", c.Config.Name, - ) + // Skip this check when node-filter is used for a subset deployment + if len(c.nodeFilter) == 0 { + for idx := range containers { + if containers[idx].Labels[clabconstants.Containerlab] == c.Config.Name { + return fmt.Errorf( + "the '%s' lab has already been deployed. Destroy the lab before deploying a "+ + "lab with the same name", c.Config.Name, + ) + } } } diff --git a/core/deploy.go b/core/deploy.go index 8b941fd138..53feb3ba8c 100644 --- a/core/deploy.go +++ b/core/deploy.go @@ -35,10 +35,23 @@ func (c *CLab) Deploy( //nolint: funlen if options.reconfigure { _ = c.destroy(ctx, uint(len(c.Nodes)), true) - log.Info("Removing directory", "path", c.TopoPaths.TopologyLabDir()) - if err := os.RemoveAll(c.TopoPaths.TopologyLabDir()); err != nil { - return nil, err + if len(c.nodeFilter) > 0 { + // When node-filter is used, only remove the filtered nodes' directories + for _, node := range c.Nodes { + nodeDir := node.Config().LabDir + log.Info("Removing node directory", "path", nodeDir) + + if err := os.RemoveAll(nodeDir); err != nil { + return nil, err + } + } + } else { + log.Info("Removing directory", "path", c.TopoPaths.TopologyLabDir()) + + if err := os.RemoveAll(c.TopoPaths.TopologyLabDir()); err != nil { + return nil, err + } } } diff --git a/core/destroy.go b/core/destroy.go index c9da945a63..896dd5ccdc 100644 --- a/core/destroy.go +++ b/core/destroy.go @@ -70,6 +70,17 @@ func (c *CLab) Destroy(ctx context.Context, options ...DestroyOption) (err error } }() + // If no containers found but we have a topology file provided via CLI, + // use that topology file directly. This handles cases where: + // - Containers were already removed + // - Containers don't have containerlab labels (e.g. node, kind) + // - Using --node-filter for nodes that never got deployed + if len(topos) == 0 && c.TopoPaths.TopologyFilenameAbsPath() != "" { + log.Debug("No containers with topology labels found, using topology file from CLI", + "path", c.TopoPaths.TopologyFilenameAbsPath()) + topos[c.TopoPaths.TopologyFilenameAbsPath()] = c.TopoPaths.TopologyLabDir() + } + if len(topos) == 0 { return nil } @@ -173,6 +184,11 @@ func (c *CLab) makeCopyForDestroy( } func (c *CLab) destroyLabDirs(topos map[string]string, all bool) error { + // When node-filter is active, never remove the entire lab directory + if len(c.nodeFilter) > 0 { + return nil + } + if len(topos) == 0 { log.Info("no containerlab containers found") @@ -234,6 +250,20 @@ func (c *CLab) destroy(ctx context.Context, maxWorkers uint, keepMgmtNet bool) e c.deleteNodes(ctx, maxWorkers) + for _, node := range c.Nodes { + err = node.DeleteNetnsSymlink() + if err != nil { + return fmt.Errorf("error while deleting netns symlinks: %w", err) + } + } + + // When node-filter is used, skip lab-wide cleanup operations + // because other nodes from the same lab are still running and + // depend on these shared resources. + if len(c.nodeFilter) > 0 { + return nil + } + c.deleteToolContainers(ctx) log.Info("Removing host entries", "path", "/etc/hosts") @@ -250,14 +280,6 @@ func (c *CLab) destroy(ctx context.Context, maxWorkers uint, keepMgmtNet bool) e log.Errorf("failed to remove ssh config file: %v", err) } - // delete container network namespaces symlinks - for _, node := range c.Nodes { - err = node.DeleteNetnsSymlink() - if err != nil { - return fmt.Errorf("error while deleting netns symlinks: %w", err) - } - } - // delete lab management network if c.Config.Mgmt.Network != "bridge" && !keepMgmtNet { log.Debugf("Calling DeleteNet method. *CLab.Config.Mgmt value is: %+v", c.Config.Mgmt) @@ -388,7 +410,7 @@ func cliPromptToDestroyAll(topos map[string]string) error { idx := 1 for topo, labDir := range topos { - sb.WriteString(fmt.Sprintf(" %d. Topology: %s\n Lab Dir: %s\n", idx, topo, labDir)) + fmt.Fprintf(&sb, " %d. Topology: %s\n Lab Dir: %s\n", idx, topo, labDir) idx++ } diff --git a/nodes/sros/sros.go b/nodes/sros/sros.go index 0c00bdbe59..55aec8b281 100644 --- a/nodes/sros/sros.go +++ b/nodes/sros/sros.go @@ -146,17 +146,17 @@ var ( `^(?:e(?P\d+)-(?:x(?P\d+)-)?(?P\d+)(?:-c(?P\d+))?-(?P\d+)|eth(?P\d+))$`, ) InterfaceHelp = `The format of the interface name need to be one of: - Regular SR OS interface names, that is: - 1/2/3 -> card 1, mda 2, port 3 - 1/2/c3/4 -> card 1, mda 2, connector 3, port 4 - 1/x2/3/4 -> card 1, xiom 2, mda 3, port 4 - 1/x2/3/c4/5 -> card 1, xiom 2, mda 3, connector 4, port 5 - The mapped Linux interface names, that is: - e1-2-3 -> card 1, mda 2, port 3 - e1-2-c3-4 -> card 1, mda 2, connector 3, port 4 - e1-x2-3-4 -> card 1, xiom 2, mda 3, port 4 - e1-x2-3-c4-5 -> card 1, xiom 2, mda 3, connector 4, port 5 - eth[0-9], for management interfaces of CPM-A/CPM-B or for fabric interfaces` + Regular SR OS interface names, that is: + 1/2/3 -> card 1, mda 2, port 3 + 1/2/c3/4 -> card 1, mda 2, connector 3, port 4 + 1/x2/3/4 -> card 1, xiom 2, mda 3, port 4 + 1/x2/3/c4/5 -> card 1, xiom 2, mda 3, connector 4, port 5 + The mapped Linux interface names, that is: + e1-2-3 -> card 1, mda 2, port 3 + e1-2-c3-4 -> card 1, mda 2, connector 3, port 4 + e1-x2-3-4 -> card 1, xiom 2, mda 3, port 4 + e1-x2-3-c4-5 -> card 1, xiom 2, mda 3, connector 4, port 5 + eth[0-9], for management interfaces of CPM-A/CPM-B or for fabric interfaces` // Auxiliary regexps for IXR/SAR detection. sarRegexp = regexp.MustCompile(`(?i)\bsar-`) sarHmRegexp = regexp.MustCompile(`(?i)\b(sar-hm|sar-hmc)\b`) @@ -547,6 +547,7 @@ func (n *sros) DeleteNetnsSymlink() error { // sortComponents ensure components are in order of // LCs first, then CPMs (cpm b comes first if present). +// LCs own the network namespace so CPMs can be cycled independently. func (n *sros) sortComponents() { slices.SortFunc(n.Cfg.Components, func(a, b *clabtypes.Component) int { s1 := strings.ToUpper(strings.TrimSpace(a.Slot)) @@ -1326,13 +1327,14 @@ func (n *sros) GetContainers(ctx context.Context) ([]clabruntime.GenericContaine return n.DefaultNode.GetContainers(ctx) } + // For distributed base nodes, find all component containers (CPM + line cards) cpmSlot, err := n.cpmSlot() if err != nil { return nil, err } containerName := n.calcComponentName(n.GetContainerName(), cpmSlot) - cnts, err := n.Runtime.ListContainers(ctx, []*clabtypes.GenericFilter{ + counts, err := n.Runtime.ListContainers(ctx, []*clabtypes.GenericFilter{ { FilterType: "name", Match: containerName, @@ -1343,7 +1345,7 @@ func (n *sros) GetContainers(ctx context.Context) ([]clabruntime.GenericContaine } // check that we retrieved some container information // otherwise throw ErrContainersNotFound error - if len(cnts) == 0 { + if len(counts) == 0 { return nil, fmt.Errorf("node: %s. %w", n.GetContainerName(), clabnodes.ErrContainersNotFound) } @@ -1354,17 +1356,17 @@ func (n *sros) GetContainers(ctx context.Context) ([]clabruntime.GenericContaine ips, err := n.distNodeMgmtIPs() if err == nil { if ips.IPv4 != "" { - cnts[0].NetworkSettings.IPv4addr = ips.IPv4 - cnts[0].NetworkSettings.IPv4pLen = ips.IPv4pLen + counts[0].NetworkSettings.IPv4addr = ips.IPv4 + counts[0].NetworkSettings.IPv4pLen = ips.IPv4pLen } if ips.IPv6 != "" { - cnts[0].NetworkSettings.IPv6addr = ips.IPv6 - cnts[0].NetworkSettings.IPv6pLen = ips.IPv6pLen + counts[0].NetworkSettings.IPv6addr = ips.IPv6 + counts[0].NetworkSettings.IPv6pLen = ips.IPv6pLen } } } - return cnts, err + return counts, err } // populateHosts adds container hostnames for other nodes of a lab to SR Linux /etc/hosts file @@ -1643,8 +1645,8 @@ func buildTLSProfileXML() string { // TLS bootstrap via NETCONF to enable secure gRPC. func (n *sros) tlsCertBootstrap(ctx context.Context, addr string) error { // Always import PKI key and cert: - // import "cf3:\node.key" in PEM format as "cf3:\system-pki\node.key" (encrypted DER) - // import "cf3:\node.crt" in PEM format as "cf3:\system-pki\node.crt" (encrypted DER) + // import "cf3:\node.key" in PEM format as "cf3:\system-pki\node.key" (encrypted DER) + // import "cf3:\node.crt" in PEM format as "cf3:\system-pki\node.crt" (encrypted DER) operations := []clabnetconf.Operation{ func(d *netconf.Driver) (*response.NetconfResponse, error) { return d.RPC(opoptions.WithFilter(buildPKIImportXML( @@ -1657,7 +1659,7 @@ func (n *sros) tlsCertBootstrap(ctx context.Context, addr string) error { } // Activate cert-profile in MD is via NETCONF, in Classic mode is via SSH - // enable enables cert-profile "clab-grpc-certs" administratively + // enable enables cert-profile "clab-grpc-certs" administratively cmd := []string{} if n.isConfigClassic() { cmd = append( diff --git a/tests/01-smoke/12-node-filter-srl.clab.yml b/tests/01-smoke/12-node-filter-srl.clab.yml new file mode 100644 index 0000000000..b1abdfd76d --- /dev/null +++ b/tests/01-smoke/12-node-filter-srl.clab.yml @@ -0,0 +1,12 @@ +name: nf-srl +topology: + nodes: + srl1: + kind: nokia_srlinux + image: ghcr.io/nokia/srlinux + srl2: + kind: nokia_srlinux + image: ghcr.io/nokia/srlinux + + links: + - endpoints: ["srl1:e1-1", "srl2:e1-1"] diff --git a/tests/01-smoke/12-node-filter-srl.robot b/tests/01-smoke/12-node-filter-srl.robot new file mode 100644 index 0000000000..52ec22c362 --- /dev/null +++ b/tests/01-smoke/12-node-filter-srl.robot @@ -0,0 +1,126 @@ +*** Settings *** +Library Process +Library OperatingSystem +Resource ../common.robot + +Suite Teardown Cleanup + + +*** Variables *** +${lab-file-name} 12-node-filter-srl.clab.yml +${lab-name} nf-srl +${runtime} docker +${runtime-cli-exec-cmd} sudo docker exec + + +*** Test Cases *** +Deploy full lab + [Documentation] Deploy the full srl lab as a baseline + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} deploy -t ${CURDIR}/${lab-file-name} + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + Should Contain ${output.stdout} srl1 + Should Contain ${output.stdout} srl2 + +Destroy with node-filter srl1 + [Documentation] Destroy only srl1. Expect srl1 removed, srl2 still running, lab dir preserved. + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} destroy -t ${CURDIR}/${lab-file-name} --node-filter srl1 + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + + # srl1 container should be gone + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect clab-${lab-name}-srl1 2>&1 + Should Not Be Equal As Integers ${rc} 0 + + # srl2 container should still be running + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.State.Running}}' clab-${lab-name}-srl2 2>&1 + Should Be Equal As Integers ${rc} 0 + Should Contain ${out} true + + # Lab directory should be preserved + ${rc} ${out} = Run And Return Rc And Output + ... test -d ${CURDIR}/clab-${lab-name} + Should Be Equal As Integers ${rc} 0 + + # srl1's link endpoint (e1-1) should be cleared on srl2 + ${rc} ${out} = Run And Return Rc And Output + ... ${runtime-cli-exec-cmd} clab-${lab-name}-srl2 ip link show e1-1 2>&1 + Should Not Be Equal As Integers ${rc} 0 + +Deploy with node-filter srl1 + [Documentation] Redeploy srl1 into the existing lab. Expect srl1 created, srl2 untouched. + ... Links to nodes outside the filter are not reconnected. + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} deploy -t ${CURDIR}/${lab-file-name} --node-filter srl1 --reconfigure + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + Should Contain ${output.stdout} srl1 + + # Both containers should be running + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.State.Running}}' clab-${lab-name}-srl1 2>&1 + Should Be Equal As Integers ${rc} 0 + Should Contain ${out} true + + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.State.Running}}' clab-${lab-name}-srl2 2>&1 + Should Be Equal As Integers ${rc} 0 + Should Contain ${out} true + +Reconfigure with node-filter srl1 + [Documentation] Reconfigure srl1 only. srl1 destroyed and redeployed, srl2 untouched. + # Capture srl2 container ID before reconfigure to verify it wasn't recreated + ${rc} ${srl2_id_before} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.Id}}' clab-${lab-name}-srl2 2>&1 + Should Be Equal As Integers ${rc} 0 + + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} deploy -t ${CURDIR}/${lab-file-name} --node-filter srl1 --reconfigure + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + + # srl1 should be running + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.State.Running}}' clab-${lab-name}-srl1 2>&1 + Should Be Equal As Integers ${rc} 0 + Should Contain ${out} true + + # srl2 should be the same container (not recreated) + ${rc} ${srl2_id_after} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.Id}}' clab-${lab-name}-srl2 2>&1 + Should Be Equal As Integers ${rc} 0 + Should Be Equal ${srl2_id_before} ${srl2_id_after} + + # srl1 node directory should exist (recreated) + ${rc} ${out} = Run And Return Rc And Output + ... test -d ${CURDIR}/clab-${lab-name}/srl1 + Should Be Equal As Integers ${rc} 0 + +Invalid node filter returns error + [Documentation] Using a non-existent node in the filter should return an error. + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} destroy -t ${CURDIR}/${lab-file-name} --node-filter nonexistent + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Not Be Equal As Integers ${output.rc} 0 + Should Contain ${output.stderr} not present in the topology + + +*** Keywords *** +Cleanup + Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} destroy -t ${CURDIR}/${lab-file-name} --cleanup + ... shell=True diff --git a/tests/13-srsim/10-srsim-comp-node-filter.clab.yml b/tests/13-srsim/10-srsim-comp-node-filter.clab.yml new file mode 100644 index 0000000000..1e67af76c5 --- /dev/null +++ b/tests/13-srsim/10-srsim-comp-node-filter.clab.yml @@ -0,0 +1,31 @@ +name: sr10 +mgmt: + network: sr10_mgmt + ipv4-subnet: 10.78.150.0/24 +topology: + kinds: + nokia_srsim: + license: /opt/nokia/sros/license-sros25.txt + image: registry.srlinux.dev/pub/nokia_srsim:25.10.R2 + nodes: + srsim10: + kind: nokia_srsim + type: SR-7 + mgmt-ipv4: 10.78.150.2 + components: + - slot: A + - slot: 1 + env: + NOKIA_SROS_CARD: iom5-e + NOKIA_SROS_MDA_1: me6-100gb-qsfp28 + NOKIA_SROS_SFM: m-sfm6-7/12 + srsim11: + kind: nokia_srsim + type: SR-7 + mgmt-ipv4: 10.78.150.3 + components: + - slot: A + - slot: 1 + + links: + - endpoints: ["srsim10:1/1/c1/1", "srsim11:1/1/c1/1"] diff --git a/tests/13-srsim/10-srsim-comp-node-filter.robot b/tests/13-srsim/10-srsim-comp-node-filter.robot new file mode 100644 index 0000000000..0c392b2bc0 --- /dev/null +++ b/tests/13-srsim/10-srsim-comp-node-filter.robot @@ -0,0 +1,149 @@ +*** Settings *** +Library Process +Library OperatingSystem +Resource ../common.robot + +Suite Teardown Cleanup + + +*** Variables *** +${lab-file-name} 10-srsim-comp-node-filter.clab.yml +${lab-name} sr10 +${runtime} docker + + +*** Test Cases *** +Deploy full components lab + [Documentation] Deploy the distributed components lab with srsim10 and srsim11 (2 components each). + ... Nodes are deployed sequentially (one at a time) because each nokia_srsim node + ... requires 4 GB of memory and resource-constrained environments may not have + ... enough headroom to start both simultaneously in a single clab deploy call. + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} deploy -t ${CURDIR}/${lab-file-name} --node-filter srsim11 + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} deploy -t ${CURDIR}/${lab-file-name} --node-filter srsim10 + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + + # All 4 component containers should be running (2 per node) + FOR ${node} IN srsim10 srsim11 + FOR ${suffix} IN a 1 + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.State.Running}}' clab-${lab-name}-${node}-${suffix} 2>&1 + Should Be Equal As Integers ${rc} 0 msg=${node}-${suffix} not running + Should Contain ${out} true + END + END + +Destroy with node-filter srsim10 + [Documentation] Destroy srsim10. Both srsim10 containers removed. srsim11 untouched. + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} destroy -t ${CURDIR}/${lab-file-name} --node-filter srsim10 + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + + # Both srsim10 component containers should be gone + FOR ${suffix} IN a 1 + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect clab-${lab-name}-srsim10-${suffix} 2>&1 + Should Not Be Equal As Integers ${rc} 0 msg=srsim10-${suffix} still exists + END + + # Both srsim11 component containers should still be running + FOR ${suffix} IN a 1 + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.State.Running}}' clab-${lab-name}-srsim11-${suffix} 2>&1 + Should Be Equal As Integers ${rc} 0 msg=srsim11-${suffix} not running + Should Contain ${out} true + END + + # Lab directory should be preserved + ${rc} ${out} = Run And Return Rc And Output + ... test -d ${CURDIR}/clab-${lab-name} + Should Be Equal As Integers ${rc} 0 + + # Inspect should still work with partial lab (srsim11 only). + # Use --name instead of --topo: when --topo is given, inspect queries every node + # defined in the topology and errors if any container is missing. Using --name performs + # a label-based lookup that returns only the containers that are actually running. + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} inspect --name ${lab-name} + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + Should Contain ${output.stdout} srsim11 + Should Not Contain ${output.stdout} srsim10 + +Deploy with node-filter srsim10 + [Documentation] Redeploy srsim10 into the existing lab. Both containers created. + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} deploy -t ${CURDIR}/${lab-file-name} --node-filter srsim10 + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + + # Both srsim10 containers should be running again + FOR ${suffix} IN a 1 + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.State.Running}}' clab-${lab-name}-srsim10-${suffix} 2>&1 + Should Be Equal As Integers ${rc} 0 msg=srsim10-${suffix} not running + Should Contain ${out} true + END + + # srsim10-1 (LC) owns the network namespace and should have the management IP + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAMConfig.IPv4Address}}{{end}}' clab-${lab-name}-srsim10-1 2>&1 + Should Be Equal As Integers ${rc} 0 + Should Contain ${out} 10.78.150.2 + + # CPM (srsim10-a) should share the LC's network namespace + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.HostConfig.NetworkMode}}' clab-${lab-name}-srsim10-a 2>&1 + Should Be Equal As Integers ${rc} 0 + Should Start With ${out} container: + +Reconfigure with node-filter srsim11 + [Documentation] Reconfigure srsim11 only. srsim10 untouched, srsim11 redeployed fresh. + # Capture srsim10-a container ID before to verify it wasn't recreated + ${rc} ${id_before} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.Id}}' clab-${lab-name}-srsim10-a 2>&1 + Should Be Equal As Integers ${rc} 0 + + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} deploy -t ${CURDIR}/${lab-file-name} --reconfigure --node-filter srsim11 + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + + # srsim10-a should be the same container + ${rc} ${id_after} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.Id}}' clab-${lab-name}-srsim10-a 2>&1 + Should Be Equal As Integers ${rc} 0 + Should Be Equal ${id_before} ${id_after} + + # Both srsim11 containers should be running (freshly redeployed) + FOR ${suffix} IN a 1 + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.State.Running}}' clab-${lab-name}-srsim11-${suffix} 2>&1 + Should Be Equal As Integers ${rc} 0 msg=srsim11-${suffix} not running + Should Contain ${out} true + END + + +*** Keywords *** +Cleanup + Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} destroy -t ${CURDIR}/${lab-file-name} --cleanup + ... shell=True diff --git a/tests/13-srsim/11-srsim-dist-node-filter.clab.yml b/tests/13-srsim/11-srsim-dist-node-filter.clab.yml new file mode 100644 index 0000000000..a6f9aabebb --- /dev/null +++ b/tests/13-srsim/11-srsim-dist-node-filter.clab.yml @@ -0,0 +1,33 @@ +name: sr11 +mgmt: + network: sr11_mgmt + ipv4-subnet: 10.78.151.0/24 +topology: + kinds: + nokia_srsim: + license: /opt/nokia/sros/license-sros25.txt + image: registry.srlinux.dev/pub/nokia_srsim:25.10.R2 + nodes: + srsim10-a: + kind: nokia_srsim + type: SR-7 + mgmt-ipv4: 10.78.151.2 + env: + NOKIA_SROS_SLOT: A + NOKIA_SROS_SYSTEM_BASE_MAC: 1c:58:07:00:03:01 + srsim10-b: + kind: nokia_srsim + type: SR-7 + network-mode: container:srsim10-a + env: + NOKIA_SROS_SLOT: B + NOKIA_SROS_SYSTEM_BASE_MAC: 1c:58:07:00:03:01 + srsim10-1: + kind: nokia_srsim + type: SR-7 + network-mode: container:srsim10-a + env: + NOKIA_SROS_SLOT: 1 + NOKIA_SROS_CARD: iom5-e + NOKIA_SROS_SFM: m-sfm6-7/12 + NOKIA_SROS_MDA_1: me6-100gb-qsfp28 diff --git a/tests/13-srsim/11-srsim-dist-node-filter.robot b/tests/13-srsim/11-srsim-dist-node-filter.robot new file mode 100644 index 0000000000..17b8a1c29f --- /dev/null +++ b/tests/13-srsim/11-srsim-dist-node-filter.robot @@ -0,0 +1,184 @@ +*** Settings *** +Library Process +Library OperatingSystem +Resource ../common.robot + +Suite Teardown Cleanup + + +*** Variables *** +${lab-file-name} 11-srsim-dist-node-filter.clab.yml +${lab-name} sr11 +${runtime} docker + + +*** Test Cases *** +Deploy full distributed lab + [Documentation] Deploy the manual distributed lab with 3 containers (CPM-A, CPM-B, IOM-1). + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} deploy -t ${CURDIR}/${lab-file-name} + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + + # All 3 containers should be running + FOR ${suffix} IN a b 1 + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.State.Running}}' clab-${lab-name}-srsim10-${suffix} 2>&1 + Should Be Equal As Integers ${rc} 0 msg=srsim10-${suffix} not running + Should Contain ${out} true + END + +Destroy CPM-A auto-expands to include dependents + [Documentation] + ... Destroy with --node-filter srsim10-a. The filter should auto-expand to include + ... srsim10-b and srsim10-1 because they share srsim10-a's network namespace. + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} destroy -t ${CURDIR}/${lab-file-name} --node-filter srsim10-a + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + + # Verify auto-expansion happened + Should Contain ${output.stderr} Auto-including node + + # All 3 containers should be gone + FOR ${suffix} IN a b 1 + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect clab-${lab-name}-srsim10-${suffix} 2>&1 + Should Not Be Equal As Integers ${rc} 0 msg=srsim10-${suffix} still exists + END + +Redeploy and destroy line card only + [Documentation] Deploy the full lab, then destroy only srsim10-1. CPMs should remain. + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} deploy -t ${CURDIR}/${lab-file-name} --reconfigure + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + + # Destroy only the line card - should NOT auto-expand + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} destroy -t ${CURDIR}/${lab-file-name} --node-filter srsim10-1 + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + Should Not Contain ${output.stderr} Auto-including node + + # Line card should be gone + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect clab-${lab-name}-srsim10-1 2>&1 + Should Not Be Equal As Integers ${rc} 0 msg=srsim10-1 still exists + + # Both CPMs should still be running + FOR ${suffix} IN a b + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.State.Running}}' clab-${lab-name}-srsim10-${suffix} 2>&1 + Should Be Equal As Integers ${rc} 0 msg=srsim10-${suffix} not running + Should Contain ${out} true + END + + # Lab dir preserved + ${rc} ${out} = Run And Return Rc And Output + ... test -d ${CURDIR}/clab-${lab-name} + Should Be Equal As Integers ${rc} 0 + +Redeploy line card with node-filter + [Documentation] Redeploy only srsim10-1. CPMs should not be recreated. + ${rc} ${cpm_id_before} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.Id}}' clab-${lab-name}-srsim10-a 2>&1 + Should Be Equal As Integers ${rc} 0 + + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} deploy -t ${CURDIR}/${lab-file-name} --node-filter srsim10-1 + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + + # Line card should be running again + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.State.Running}}' clab-${lab-name}-srsim10-1 2>&1 + Should Be Equal As Integers ${rc} 0 + Should Contain ${out} true + + # srsim10-1 should share network namespace of srsim10-a (Docker stores container ID in NetworkMode) + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.HostConfig.NetworkMode}}' clab-${lab-name}-srsim10-1 2>&1 + Should Be Equal As Integers ${rc} 0 + Should Start With ${out} container: + Should Contain ${out} ${cpm_id_before} + + # CPM-A should be the same container (not recreated) + ${rc} ${cpm_id_after} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.Id}}' clab-${lab-name}-srsim10-a 2>&1 + Should Be Equal As Integers ${rc} 0 + Should Be Equal ${cpm_id_before} ${cpm_id_after} + +Destroy CPM-B does not auto-expand + [Documentation] + ... Destroy with --node-filter srsim10-b. No other node depends on srsim10-b's namespace, + ... so the filter should NOT expand. Only srsim10-b is destroyed. + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} destroy -t ${CURDIR}/${lab-file-name} --node-filter srsim10-b + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + + # Auto-expansion should NOT have happened + Should Not Contain ${output.stderr} Auto-including node + + # srsim10-b should be gone + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect clab-${lab-name}-srsim10-b 2>&1 + Should Not Be Equal As Integers ${rc} 0 msg=srsim10-b still exists + + # srsim10-a and srsim10-1 should still be running + FOR ${suffix} IN a 1 + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.State.Running}}' clab-${lab-name}-srsim10-${suffix} 2>&1 + Should Be Equal As Integers ${rc} 0 msg=srsim10-${suffix} not running + Should Contain ${out} true + END + +Reconfigure with node-filter srsim10-a + [Documentation] Reconfigure srsim10-a. Auto-expands to all 3 containers. + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} deploy -t ${CURDIR}/${lab-file-name} --reconfigure --node-filter srsim10-a + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + + # Verify auto-expansion happened + Should Contain ${output.stderr} Auto-including node + + # All 3 containers should be running (freshly redeployed) + FOR ${suffix} IN a b 1 + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.State.Running}}' clab-${lab-name}-srsim10-${suffix} 2>&1 + Should Be Equal As Integers ${rc} 0 msg=srsim10-${suffix} not running + Should Contain ${out} true + END + +Invalid node filter returns error + [Documentation] Using a non-existent node in the filter should return an error. + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} destroy -t ${CURDIR}/${lab-file-name} --node-filter nonexistent + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Not Be Equal As Integers ${output.rc} 0 + Should Contain ${output.stderr} not present in the topology + + +*** Keywords *** +Cleanup + Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} destroy -t ${CURDIR}/${lab-file-name} --cleanup + ... shell=True diff --git a/tests/13-srsim/12-srsim-dist-node-filter.robot b/tests/13-srsim/12-srsim-dist-node-filter.robot new file mode 100644 index 0000000000..df1423bc7c --- /dev/null +++ b/tests/13-srsim/12-srsim-dist-node-filter.robot @@ -0,0 +1,189 @@ +*** Settings *** +Library Process +Library OperatingSystem +Resource ../common.robot + +Suite Teardown Cleanup + + +*** Variables *** +${lab-file} ${EXECDIR}/lab-examples/sr-sim/test-cpm-destroy.clab.yaml +${lab-name} cpm-destroy-test +${runtime} docker + + +*** Test Cases *** +Deploy full distributed lab + [Documentation] Deploy the manual distributed lab with 3 containers. + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} deploy -t ${lab-file} + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + + # All 3 containers should be running + FOR ${suffix} IN a b 1 + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.State.Running}}' clab-${lab-name}-srsim10-${suffix} 2>&1 + Should Be Equal As Integers ${rc} 0 msg=srsim10-${suffix} not running + Should Contain ${out} true + END + +Destroy CPM-A auto-expands to include dependents + [Documentation] + ... Destroy with --node-filter srsim10-a. The filter should auto-expand to include + ... srsim10-b and srsim10-1 because they share srsim10-a's network namespace. + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} destroy -t ${lab-file} --node-filter srsim10-a + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + + # Verify auto-expansion happened + Should Contain ${output.stderr} Auto-including node + + # All 3 containers should be gone + FOR ${suffix} IN a b 1 + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect clab-${lab-name}-srsim10-${suffix} 2>&1 + Should Not Be Equal As Integers ${rc} 0 msg=srsim10-${suffix} still exists + END + +Redeploy and destroy line card only + [Documentation] Deploy the full lab, then destroy only srsim10-1. CPMs should remain. + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} deploy -t ${lab-file} --reconfigure + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + + # Destroy only the line card + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} destroy -t ${lab-file} --node-filter srsim10-1 + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + + # Line card should be gone + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect clab-${lab-name}-srsim10-1 2>&1 + Should Not Be Equal As Integers ${rc} 0 msg=srsim10-1 still exists + + # Both CPMs should still be running + FOR ${suffix} IN a b + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.State.Running}}' clab-${lab-name}-srsim10-${suffix} 2>&1 + Should Be Equal As Integers ${rc} 0 msg=srsim10-${suffix} not running + Should Contain ${out} true + END + + # Lab dir preserved + ${rc} ${out} = Run And Return Rc And Output + ... test -d ${EXECDIR}/lab-examples/sr-sim/clab-${lab-name} + Should Be Equal As Integers ${rc} 0 + +Redeploy line card with node-filter + [Documentation] Redeploy only srsim10-1. CPMs should not be recreated. + ${rc} ${cpm_id_before} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.Id}}' clab-${lab-name}-srsim10-a 2>&1 + Should Be Equal As Integers ${rc} 0 + + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} deploy -t ${lab-file} --node-filter srsim10-1 + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + + # Line card should be running again + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.State.Running}}' clab-${lab-name}-srsim10-1 2>&1 + Should Be Equal As Integers ${rc} 0 + Should Contain ${out} true + + # srsim10-1 should share network namespace of srsim10-a (Docker stores container ID in NetworkMode) + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.HostConfig.NetworkMode}}' clab-${lab-name}-srsim10-1 2>&1 + Should Be Equal As Integers ${rc} 0 + Should Start With ${out} container: + Should Contain ${out} ${cpm_id_before} + + # CPM-A should be the same container (not recreated) + ${rc} ${cpm_id_after} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.Id}}' clab-${lab-name}-srsim10-a 2>&1 + Should Be Equal As Integers ${rc} 0 + Should Be Equal ${cpm_id_before} ${cpm_id_after} + +Destroy CPM-B does not auto-expand + [Documentation] + ... Destroy with --node-filter srsim10-b. No other node depends on srsim10-b's namespace, + ... so the filter should NOT expand. Only srsim10-b is destroyed. + # First make sure all 3 are running + FOR ${suffix} IN a b 1 + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.State.Running}}' clab-${lab-name}-srsim10-${suffix} 2>&1 + Should Be Equal As Integers ${rc} 0 msg=srsim10-${suffix} not running + Should Contain ${out} true + END + + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} destroy -t ${lab-file} --node-filter srsim10-b + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + + # Auto-expansion should NOT have happened + Should Not Contain ${output.stderr} Auto-including node + + # srsim10-b should be gone + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect clab-${lab-name}-srsim10-b 2>&1 + Should Not Be Equal As Integers ${rc} 0 msg=srsim10-b still exists + + # srsim10-a and srsim10-1 should still be running + FOR ${suffix} IN a 1 + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.State.Running}}' clab-${lab-name}-srsim10-${suffix} 2>&1 + Should Be Equal As Integers ${rc} 0 msg=srsim10-${suffix} not running + Should Contain ${out} true + END + + # Redeploy srsim10-b for the next test + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} deploy -t ${lab-file} --node-filter srsim10-b + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + +Reconfigure with node-filter srsim10-a + [Documentation] Reconfigure srsim10-a. Auto-expands to all 3 containers. + ${output} = Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} deploy -t ${lab-file} --reconfigure --node-filter srsim10-a + ... shell=True + Log ${output.stdout} + Log ${output.stderr} + Should Be Equal As Integers ${output.rc} 0 + + # Verify auto-expansion happened + Should Contain ${output.stderr} Auto-including node + + # All 3 containers should be running (freshly redeployed) + FOR ${suffix} IN a b 1 + ${rc} ${out} = Run And Return Rc And Output + ... sudo docker inspect -f '{{.State.Running}}' clab-${lab-name}-srsim10-${suffix} 2>&1 + Should Be Equal As Integers ${rc} 0 msg=srsim10-${suffix} not running + Should Contain ${out} true + END + + +*** Keywords *** +Cleanup + Process.Run Process + ... ${CLAB_BIN} --runtime ${runtime} destroy -t ${lab-file} --cleanup + ... shell=True