From f40b241740d8dea57cd4ed179828607857e9e64a Mon Sep 17 00:00:00 2001 From: Dennis Fanshaw <64549250+hyposcaler-bot@users.noreply.github.com> Date: Thu, 24 Oct 2024 15:18:13 +0000 Subject: [PATCH 1/4] adds support for __clabNodeName__ magic var --- clab/config.go | 10 +++--- clab/config_test.go | 43 +++++++++++++++++++++++++ clab/test_data/topo13.yml | 8 +++++ clab/test_data/topo14.yml | 6 ++++ clab/test_data/topo15.yml | 7 +++++ docs/manual/nodes.md | 66 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 clab/test_data/topo13.yml create mode 100644 clab/test_data/topo14.yml create mode 100644 clab/test_data/topo15.yml diff --git a/clab/config.go b/clab/config.go index 3fd2f482c..2a95558c6 100644 --- a/clab/config.go +++ b/clab/config.go @@ -33,8 +33,9 @@ const ( DefaultVethLinkMTU = 9500 // clab specific topology variables. - clabDirVar = "__clabDir__" - nodeDirVar = "__clabNodeDir__" + clabDirVar = "__clabDir__" + nodeDirVar = "__clabNodeDir__" + nodeNameVar = "__clabNodeName__" ) // Config defines lab configuration as it is provided in the YAML file. @@ -261,8 +262,9 @@ func (c *CLab) createNodeCfg(nodeName string, nodeDef *types.NodeDefinition, idx // It handles remote files, local files and embedded configs. // Returns an absolute path to the startup-config file. func (c *CLab) processStartupConfig(nodeCfg *types.NodeConfig) error { - // process startup-config - p := c.Config.Topology.GetNodeStartupConfig(nodeCfg.ShortName) + // replace __clabNodeName__ magic var in startup-config path with node short name + r := strings.NewReplacer(nodeNameVar, nodeCfg.ShortName) + p := r.Replace(c.Config.Topology.GetNodeStartupConfig(nodeCfg.ShortName)) // embedded config is a config that is defined as a multi-line string in the topology file // it contains at least one newline diff --git a/clab/config_test.go b/clab/config_test.go index 48d6c3bf9..b6e4ea2f1 100644 --- a/clab/config_test.go +++ b/clab/config_test.go @@ -731,3 +731,46 @@ func TestSuppressConfigInit(t *testing.T) { }) } } + +func TestStartupConfigInit(t *testing.T) { + tests := map[string]struct { + got string + node string + want string + }{ + "kinds_startup": { + got: "test_data/topo13.yml", + node: "node1", + want: "/clab/clab/test_data/configs/fabric/node1.cfg", + }, + "node_startup": { + got: "test_data/topo14.yml", + node: "node1", + want: "/clab/clab/test_data/configs/fabric/node1.cfg", + }, + "default_startup": { + got: "test_data/topo15.yml", + node: "node1", + want: "/clab/clab/test_data/configs/fabric/node1.cfg", + }, + } + + teardownTestCase := setupTestCase(t) + defer teardownTestCase(t) + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + opts := []ClabOption{ + WithTopoPath(tc.got, ""), + } + c, err := NewContainerLab(opts...) + if err != nil { + t.Error(err) + } + + if c.Nodes[tc.node].Config().StartupConfig != tc.want { + t.Errorf("want startup-config %q got startup-config %q", tc.want, c.Nodes[tc.node].Config().StartupConfig) + } + }) + } +} diff --git a/clab/test_data/topo13.yml b/clab/test_data/topo13.yml new file mode 100644 index 000000000..28495e670 --- /dev/null +++ b/clab/test_data/topo13.yml @@ -0,0 +1,8 @@ +name: topo13 +topology: + kinds: + nokia_srlinux: + startup-config: ./configs/fabric/__clabNodeName__.cfg + nodes: + node1: + kind: nokia_srlinux diff --git a/clab/test_data/topo14.yml b/clab/test_data/topo14.yml new file mode 100644 index 000000000..58325c023 --- /dev/null +++ b/clab/test_data/topo14.yml @@ -0,0 +1,6 @@ +name: topo14 +topology: + nodes: + node1: + kind: nokia_srlinux + startup-config: ./configs/fabric/__clabNodeName__.cfg \ No newline at end of file diff --git a/clab/test_data/topo15.yml b/clab/test_data/topo15.yml new file mode 100644 index 000000000..2a2d3ef16 --- /dev/null +++ b/clab/test_data/topo15.yml @@ -0,0 +1,7 @@ +name: topo15 +topology: + defaults: + startup-config: ./configs/fabric/__clabNodeName__.cfg + nodes: + node1: + kind: nokia_srlinux diff --git a/docs/manual/nodes.md b/docs/manual/nodes.md index 0b43015b2..63128b559 100644 --- a/docs/manual/nodes.md +++ b/docs/manual/nodes.md @@ -174,6 +174,72 @@ The remote file will be downloaded to the containerlab's temp directory at `$TMP * If a lab is redeployed with the lab name and startup-config paths unchanged, the local file will be overwritten. * For https locations the certificates won't be verified to allow fetching artifacts from servers with self-signed certificates. +???info "Startup-config variables" + By default, the startup-config references are either provided as an absolute or a relative (to the current working dir) path to the file to be used. + + Consider a two-node lab mylab.clab.yml with seed configs that the user may wish to use with thier lab. A user could create a directory for such files similar to this: + + ``` + . + ├── cfgs + │   ├── node1.partial.cfg + │   └── node2.partial.cfg + └── mylab.clab.yml + + 2 directories, 3 files + ``` + + Then to leverage these configs, the node could be configured with startup-config references like this: + + ```yaml + name: mylab + topology: + nodes: + node1: + startup-config: cfgs/node1.partial.cfg + node2: + startup-config: cfgs/node2.partial.cfg + ``` + + while this configuration is correct, it might be considered verbose as the number of nodes grows. To remove this verbosity, the users can use a special variable `__clabNodeName__` in their startup-config paths. This variable will expand to the node-name for the parent node that the startup-config reference falls under. + + ```yaml + name: mylab + topology: + nodes: + node1: + startup-config: cfg/__clabNodeName__.partial.cfg + node2: + startup-config: cfgs/__clabNodeName__.partial.cfg + ``` + + The `__clabNodeName__` variable can also be used in the kind and default sections of the config. Using the same directory structure from the example above, the following shows how to use the magic varable for a kind. + + ```yaml + name: mylab + topology: + defaults: + kind: nokia_srlinux + kinds: + nokia_srlinux: + startup-config: cfgs/__clabNodeName__.partial.cfg + nodes: + node1: + node2: + ``` + + The following example shows how one would do it using defaults. + + ```yaml + name: mylab + topology: + defaults: + kind: nokia_srlinux + startup-config: cfgs/__clabNodeName__.partial.cfg + nodes: + node1: + node2: + ``` ### enforce-startup-config By default, containerlab will use the config file that is available in the lab directory for a given node even if the `startup config` parameter points to another file. To make a node to boot with the config set with `startup-config` parameter no matter what, set the `enforce-startup-config` to `true`. From d093757b0aaed86f691e94fc4b74590617a4e049 Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Fri, 3 Jan 2025 12:58:44 +0100 Subject: [PATCH 2/4] fix test --- clab/config_test.go | 11 ++++++----- clab/test_data/topo14.yml | 2 +- clab/test_data/topo15.yml | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/clab/config_test.go b/clab/config_test.go index b6e4ea2f1..a7f2bdb39 100644 --- a/clab/config_test.go +++ b/clab/config_test.go @@ -741,17 +741,17 @@ func TestStartupConfigInit(t *testing.T) { "kinds_startup": { got: "test_data/topo13.yml", node: "node1", - want: "/clab/clab/test_data/configs/fabric/node1.cfg", + want: "/test_data/configs/fabric/node1.cfg", }, "node_startup": { got: "test_data/topo14.yml", node: "node1", - want: "/clab/clab/test_data/configs/fabric/node1.cfg", + want: "/test_data/configs/fabric/node1.cfg", }, "default_startup": { got: "test_data/topo15.yml", node: "node1", - want: "/clab/clab/test_data/configs/fabric/node1.cfg", + want: "/test_data/configs/fabric/node1.cfg", }, } @@ -768,8 +768,9 @@ func TestStartupConfigInit(t *testing.T) { t.Error(err) } - if c.Nodes[tc.node].Config().StartupConfig != tc.want { - t.Errorf("want startup-config %q got startup-config %q", tc.want, c.Nodes[tc.node].Config().StartupConfig) + cwd, _ := os.Getwd() + if c.Nodes[tc.node].Config().StartupConfig != filepath.Join(cwd, tc.want) { + t.Errorf("want startup-config %q got startup-config %q", filepath.Join(cwd, tc.want), c.Nodes[tc.node].Config().StartupConfig) } }) } diff --git a/clab/test_data/topo14.yml b/clab/test_data/topo14.yml index 58325c023..93fa6721c 100644 --- a/clab/test_data/topo14.yml +++ b/clab/test_data/topo14.yml @@ -3,4 +3,4 @@ topology: nodes: node1: kind: nokia_srlinux - startup-config: ./configs/fabric/__clabNodeName__.cfg \ No newline at end of file + startup-config: ./configs/fabric/__clabNodeName__.cfg diff --git a/clab/test_data/topo15.yml b/clab/test_data/topo15.yml index 2a2d3ef16..06c5b71c7 100644 --- a/clab/test_data/topo15.yml +++ b/clab/test_data/topo15.yml @@ -1,7 +1,7 @@ name: topo15 topology: defaults: - startup-config: ./configs/fabric/__clabNodeName__.cfg + startup-config: ./configs/fabric/__clabNodeName__.cfg nodes: node1: kind: nokia_srlinux From 189d5dc2e72e2fc492933ae597957669a349a257 Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Fri, 3 Jan 2025 13:31:14 +0100 Subject: [PATCH 3/4] regen mocks --- mocks/dependency_manager.go | 1 + mocks/mocknodes/default_node.go | 31 +++++++++++++++++++++++++++++++ mocks/mocknodes/node.go | 7 ++++--- mocks/mockruntime/runtime.go | 2 ++ 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/mocks/dependency_manager.go b/mocks/dependency_manager.go index c5e6bd13b..a814a792a 100644 --- a/mocks/dependency_manager.go +++ b/mocks/dependency_manager.go @@ -21,6 +21,7 @@ import ( type MockDependencyManager struct { ctrl *gomock.Controller recorder *MockDependencyManagerMockRecorder + isgomock struct{} } // MockDependencyManagerMockRecorder is the mock recorder for MockDependencyManager. diff --git a/mocks/mocknodes/default_node.go b/mocks/mocknodes/default_node.go index 94a698a3f..6acc4530d 100644 --- a/mocks/mocknodes/default_node.go +++ b/mocks/mocknodes/default_node.go @@ -22,6 +22,7 @@ import ( type MockNodeOverwrites struct { ctrl *gomock.Controller recorder *MockNodeOverwritesMockRecorder + isgomock struct{} } // MockNodeOverwritesMockRecorder is the mock recorder for MockNodeOverwrites. @@ -41,6 +42,21 @@ func (m *MockNodeOverwrites) EXPECT() *MockNodeOverwritesMockRecorder { return m.recorder } +// CalculateInterfaceIndex mocks base method. +func (m *MockNodeOverwrites) CalculateInterfaceIndex(ifName string) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CalculateInterfaceIndex", ifName) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CalculateInterfaceIndex indicates an expected call of CalculateInterfaceIndex. +func (mr *MockNodeOverwritesMockRecorder) CalculateInterfaceIndex(ifName any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CalculateInterfaceIndex", reflect.TypeOf((*MockNodeOverwrites)(nil).CalculateInterfaceIndex), ifName) +} + // CheckInterfaceName mocks base method. func (m *MockNodeOverwrites) CheckInterfaceName() error { m.ctrl.T.Helper() @@ -98,6 +114,21 @@ func (mr *MockNodeOverwritesMockRecorder) GetImages(ctx any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImages", reflect.TypeOf((*MockNodeOverwrites)(nil).GetImages), ctx) } +// GetMappedInterfaceName mocks base method. +func (m *MockNodeOverwrites) GetMappedInterfaceName(ifName string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMappedInterfaceName", ifName) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMappedInterfaceName indicates an expected call of GetMappedInterfaceName. +func (mr *MockNodeOverwritesMockRecorder) GetMappedInterfaceName(ifName any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMappedInterfaceName", reflect.TypeOf((*MockNodeOverwrites)(nil).GetMappedInterfaceName), ifName) +} + // PullImage mocks base method. func (m *MockNodeOverwrites) PullImage(ctx context.Context) error { m.ctrl.T.Helper() diff --git a/mocks/mocknodes/node.go b/mocks/mocknodes/node.go index 1f76fdc2d..aa5c4289c 100644 --- a/mocks/mocknodes/node.go +++ b/mocks/mocknodes/node.go @@ -28,6 +28,7 @@ import ( type MockNode struct { ctrl *gomock.Controller recorder *MockNodeMockRecorder + isgomock struct{} } // MockNodeMockRecorder is the mock recorder for MockNode. @@ -78,16 +79,16 @@ func (mr *MockNodeMockRecorder) AddLinkToContainer(ctx, link, f any) *gomock.Cal // CalculateInterfaceIndex mocks base method. func (m *MockNode) CalculateInterfaceIndex(ifName string) (int, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CalculateInterfaceIndex") + ret := m.ctrl.Call(m, "CalculateInterfaceIndex", ifName) ret0, _ := ret[0].(int) ret1, _ := ret[1].(error) return ret0, ret1 } // CalculateInterfaceIndex indicates an expected call of CalculateInterfaceIndex. -func (mr *MockNodeMockRecorder) CalculateInterfaceIndex() *gomock.Call { +func (mr *MockNodeMockRecorder) CalculateInterfaceIndex(ifName any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CalculateInterfaceIndex", reflect.TypeOf((*MockNode)(nil).CalculateInterfaceIndex)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CalculateInterfaceIndex", reflect.TypeOf((*MockNode)(nil).CalculateInterfaceIndex), ifName) } // CheckDeploymentConditions mocks base method. diff --git a/mocks/mockruntime/runtime.go b/mocks/mockruntime/runtime.go index f45b84746..67f2ba1df 100644 --- a/mocks/mockruntime/runtime.go +++ b/mocks/mockruntime/runtime.go @@ -24,6 +24,7 @@ import ( type MockContainerRuntime struct { ctrl *gomock.Controller recorder *MockContainerRuntimeMockRecorder + isgomock struct{} } // MockContainerRuntimeMockRecorder is the mock recorder for MockContainerRuntime. @@ -374,6 +375,7 @@ func (mr *MockContainerRuntimeMockRecorder) WithMgmtNet(arg0 any) *gomock.Call { type MockNode struct { ctrl *gomock.Controller recorder *MockNodeMockRecorder + isgomock struct{} } // MockNodeMockRecorder is the mock recorder for MockNode. From eb74eee02988650ab95d5acf2f88856187a46848 Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Fri, 3 Jan 2025 13:31:28 +0100 Subject: [PATCH 4/4] move doc section closer to the path-based startup configs --- docs/manual/nodes.md | 77 ++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/docs/manual/nodes.md b/docs/manual/nodes.md index 63128b559..caba9a91a 100644 --- a/docs/manual/nodes.md +++ b/docs/manual/nodes.md @@ -138,46 +138,10 @@ topology: Check the particular kind documentation to see if the startup-config is supported and how it is applied. -#### embedded startup-config - -It is possible to embed the startup configuraion in the topology file itself. This is done by providing the startup-config as a multiline string. - -```yaml -topology: - nodes: - srl: - startup-config: | - system information location "I am embedded config" -``` - -!!!note - If a config file exists in the lab directory for a given node, then it will take preference over the startup config passed with this setting. If it is desired to discard the previously saved config and use the startup config instead, use the `enforce-startup-config` setting or deploy a lab with the [`reconfigure`](../cmd/deploy.md#reconfigure) flag. - -#### remote startup-config - -It is possible to specify a remote `http(s)` location for a startup-config file. Simply provide a URL that can be accessed from the containerlab host. - -```yaml -topology: - kinds: - nokia_srlinux: - type: ixrd3 - image: ghcr.io/nokia/srlinux - startup-config: https://raw.githubusercontent.com/srl-labs/containerlab/main/tests/02-basic-srl/srl2-startup.cli -``` - -The remote file will be downloaded to the containerlab's temp directory at `$TMP/.clab/` path and provided to the node as a locally available startup-config file. The filename will have a generated name that follows the pattern `--`, where `` is the last element of the URL path. - -!!!note - - * Upon deletion of a lab, the downloaded startup-config files will not be removed. A manual cleanup should be performed if required. - * If a lab is redeployed with the lab name and startup-config paths unchanged, the local file will be overwritten. - * For https locations the certificates won't be verified to allow fetching artifacts from servers with self-signed certificates. - -???info "Startup-config variables" +???info "Startup-config path variable" By default, the startup-config references are either provided as an absolute or a relative (to the current working dir) path to the file to be used. - Consider a two-node lab mylab.clab.yml with seed configs that the user may wish to use with thier lab. A user could create a directory for such files similar to this: + Consider a two-node lab `mylab.clab.yml` with seed configs that the user may wish to use in their lab. A user could create a directory for such files similar to this: ``` . @@ -240,6 +204,43 @@ The remote file will be downloaded to the containerlab's temp directory at `$TMP node1: node2: ``` + +#### embedded startup-config + +It is possible to embed the startup configuraion in the topology file itself. This is done by providing the startup-config as a multiline string. + +```yaml +topology: + nodes: + srl: + startup-config: | + system information location "I am embedded config" +``` + +!!!note + If a config file exists in the lab directory for a given node, then it will take preference over the startup config passed with this setting. If it is desired to discard the previously saved config and use the startup config instead, use the `enforce-startup-config` setting or deploy a lab with the [`reconfigure`](../cmd/deploy.md#reconfigure) flag. + +#### remote startup-config + +It is possible to specify a remote `http(s)` location for a startup-config file. Simply provide a URL that can be accessed from the containerlab host. + +```yaml +topology: + kinds: + nokia_srlinux: + type: ixrd3 + image: ghcr.io/nokia/srlinux + startup-config: https://raw.githubusercontent.com/srl-labs/containerlab/main/tests/02-basic-srl/srl2-startup.cli +``` + +The remote file will be downloaded to the containerlab's temp directory at `$TMP/.clab/` path and provided to the node as a locally available startup-config file. The filename will have a generated name that follows the pattern `--`, where `` is the last element of the URL path. + +!!!note + + * Upon deletion of a lab, the downloaded startup-config files will not be removed. A manual cleanup should be performed if required. + * If a lab is redeployed with the lab name and startup-config paths unchanged, the local file will be overwritten. + * For https locations the certificates won't be verified to allow fetching artifacts from servers with self-signed certificates. + ### enforce-startup-config By default, containerlab will use the config file that is available in the lab directory for a given node even if the `startup config` parameter points to another file. To make a node to boot with the config set with `startup-config` parameter no matter what, set the `enforce-startup-config` to `true`.