From 3dd92d7b95e0d5f96d77ceb22acabb206933303f Mon Sep 17 00:00:00 2001 From: steiler Date: Fri, 1 Dec 2023 12:35:50 +0100 Subject: [PATCH] exec from topology as part of node schedule --- clab/clab.go | 28 +++++++++++++++++++--------- clab/config_test.go | 3 --- cmd/deploy.go | 28 ++++------------------------ mocks/mocknodes/default_node.go | 16 ++++++++++++++++ mocks/mocknodes/node.go | 14 ++++++++++++++ nodes/default_node.go | 19 +++++++++++++++++++ nodes/node.go | 3 +++ tests/02-basic-srl/01-two-srls.robot | 2 +- 8 files changed, 76 insertions(+), 37 deletions(-) diff --git a/clab/clab.go b/clab/clab.go index a4c8c3f88a..9f657c112f 100644 --- a/clab/clab.go +++ b/clab/clab.go @@ -18,6 +18,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/srl-labs/containerlab/cert" "github.com/srl-labs/containerlab/clab/dependency_manager" + "github.com/srl-labs/containerlab/clab/exec" errs "github.com/srl-labs/containerlab/errors" "github.com/srl-labs/containerlab/links" "github.com/srl-labs/containerlab/nodes" @@ -303,6 +304,7 @@ func NewContainerLab(opts ...ClabOption) (*CLab, error) { // init a new NodeRegistry c.Reg = nodes.NewNodeRegistry() + c.RegisterNodes() for _, opt := range opts { err := opt(c) @@ -355,7 +357,7 @@ func (c *CLab) GlobalRuntime() runtime.ContainerRuntime { // CreateNodes schedules nodes creation and returns a waitgroup for all nodes. // Nodes interdependencies are created in this function. -func (c *CLab) CreateNodes(ctx context.Context, maxWorkers uint, skipPostDeploy bool) (*sync.WaitGroup, error) { +func (c *CLab) CreateNodes(ctx context.Context, maxWorkers uint, skipPostDeploy bool) (*sync.WaitGroup, *exec.ExecCollection, error) { for nodeName := range c.Nodes { c.dependencyManager.AddNode(nodeName) @@ -364,19 +366,19 @@ func (c *CLab) CreateNodes(ctx context.Context, maxWorkers uint, skipPostDeploy // nodes with static mgmt IP should be scheduled before the dynamic ones err := c.createStaticDynamicDependency() if err != nil { - return nil, err + return nil, nil, err } // create user-defined node dependencies done with `wait-for` node property err = c.createWaitForDependency() if err != nil { - return nil, err + return nil, nil, err } // create a set of dependencies, that makes the ignite nodes start one after the other err = c.createIgniteSerialDependency() if err != nil { - return nil, err + return nil, nil, err } // make network namespace shared containers start in the right order @@ -387,13 +389,13 @@ func (c *CLab) CreateNodes(ctx context.Context, maxWorkers uint, skipPostDeploy // make sure that there are no unresolvable dependencies, which would deadlock. err = c.dependencyManager.CheckAcyclicity() if err != nil { - return nil, err + return nil, nil, err } // start scheduling - NodesWg := c.scheduleNodes(ctx, int(maxWorkers), skipPostDeploy) + NodesWg, execCollection := c.scheduleNodes(ctx, int(maxWorkers), skipPostDeploy) - return NodesWg, nil + return NodesWg, execCollection, nil } // create a set of dependencies, that makes the ignite nodes start one after the other. @@ -489,9 +491,11 @@ func (c *CLab) createWaitForDependency() error { return nil } -func (c *CLab) scheduleNodes(ctx context.Context, maxWorkers int, skipPostDeploy bool) *sync.WaitGroup { +func (c *CLab) scheduleNodes(ctx context.Context, maxWorkers int, skipPostDeploy bool) (*sync.WaitGroup, *exec.ExecCollection) { concurrentChan := make(chan nodes.Node) + execCollection := exec.NewExecCollection() + workerFunc := func(i int, input chan nodes.Node, wg *sync.WaitGroup, dm dependency_manager.DependencyManager, ) { @@ -589,6 +593,12 @@ func (c *CLab) scheduleNodes(ctx context.Context, maxWorkers int, skipPostDeploy dm.SignalDone(node.GetShortName(), types.WaitForConfigure) + // run execs + err = node.RunExecFromConfig(ctx, execCollection) + if err != nil { + log.Errorf("failed to run exec commands for %s: %v", node.GetShortName(), err) + } + // health state processing count, err = dm.GetDependerCount(node.GetShortName(), types.WaitForHealthy) if err != nil { @@ -680,7 +690,7 @@ func (c *CLab) scheduleNodes(ctx context.Context, maxWorkers int, skipPostDeploy // close the channel and thereby terminate the workerFuncs close(concurrentChan) }() - return wg + return wg, execCollection } // WaitForExternalNodeDependencies makes nodes that have a reference to an external container network-namespace (network-mode: container:) diff --git a/clab/config_test.go b/clab/config_test.go index ef658e15f1..2f2b64fd8e 100644 --- a/clab/config_test.go +++ b/clab/config_test.go @@ -75,9 +75,6 @@ func TestLicenseInit(t *testing.T) { t.Fatal(err) } - // fmt.Println(c.Config.Topology.Defaults) - // fmt.Println(c.Config.Topology.Kinds) - // fmt.Println(c.Config.Topology.Nodes) if filepath.Base(c.Nodes["node1"].Config().License) != tc.want { t.Fatalf("wanted '%s' got '%s'", tc.want, c.Nodes["node1"].Config().License) } diff --git a/cmd/deploy.go b/cmd/deploy.go index 54469f7783..f7f67fda3f 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -17,7 +17,6 @@ import ( "github.com/srl-labs/containerlab/cert" "github.com/srl-labs/containerlab/clab" "github.com/srl-labs/containerlab/clab/dependency_manager" - "github.com/srl-labs/containerlab/clab/exec" "github.com/srl-labs/containerlab/links" "github.com/srl-labs/containerlab/runtime" "github.com/srl-labs/containerlab/utils" @@ -217,7 +216,7 @@ func deployFn(_ *cobra.Command, _ []string) error { n.Config().ExtraHosts = extraHosts } - nodesWg, err := c.CreateNodes(ctx, nodeWorkers, skipPostDeploy) + nodesWg, execCollection, err := c.CreateNodes(ctx, nodeWorkers, skipPostDeploy) if err != nil { return err } @@ -226,6 +225,9 @@ func deployFn(_ *cobra.Command, _ []string) error { nodesWg.Wait() } + // write to log + execCollection.Log() + if err := c.GenerateInventories(); err != nil { return err } @@ -258,28 +260,6 @@ func deployFn(_ *cobra.Command, _ []string) error { log.Errorf("failed to create ssh config file: %v", err) } - // execute commands specified for nodes with `exec` node parameter - execCollection := exec.NewExecCollection() - for _, n := range c.Nodes { - for _, e := range n.Config().Exec { - exec, err := exec.NewExecCmdFromString(e) - if err != nil { - log.Warnf("Failed to parse the command string: %s, %v", e, err) - } - - res, err := n.RunExec(ctx, exec) - if err != nil { - // kinds which do not support exec functionality are skipped - continue - } - - execCollection.Add(n.Config().ShortName, res) - } - } - - // write to log - execCollection.Log() - // log new version availability info if ready newVerNotification(vCh) diff --git a/mocks/mocknodes/default_node.go b/mocks/mocknodes/default_node.go index 73fcd16318..7de0376f5f 100644 --- a/mocks/mocknodes/default_node.go +++ b/mocks/mocknodes/default_node.go @@ -12,6 +12,7 @@ import ( context "context" reflect "reflect" + exec "github.com/srl-labs/containerlab/clab/exec" runtime "github.com/srl-labs/containerlab/runtime" gomock "go.uber.org/mock/gomock" ) @@ -110,6 +111,21 @@ func (mr *MockNodeOverwritesMockRecorder) PullImage(ctx any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PullImage", reflect.TypeOf((*MockNodeOverwrites)(nil).PullImage), ctx) } +// RunExec mocks base method. +func (m *MockNodeOverwrites) RunExec(arg0 context.Context, arg1 *exec.ExecCmd) (*exec.ExecResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RunExec", arg0, arg1) + ret0, _ := ret[0].(*exec.ExecResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RunExec indicates an expected call of RunExec. +func (mr *MockNodeOverwritesMockRecorder) RunExec(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RunExec", reflect.TypeOf((*MockNodeOverwrites)(nil).RunExec), arg0, arg1) +} + // VerifyHostRequirements mocks base method. func (m *MockNodeOverwrites) VerifyHostRequirements() error { m.ctrl.T.Helper() diff --git a/mocks/mocknodes/node.go b/mocks/mocknodes/node.go index c096bdfe77..9d04524c9a 100644 --- a/mocks/mocknodes/node.go +++ b/mocks/mocknodes/node.go @@ -385,6 +385,20 @@ func (mr *MockNodeMockRecorder) RunExec(ctx, execCmd any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RunExec", reflect.TypeOf((*MockNode)(nil).RunExec), ctx, execCmd) } +// RunExecFromConfig mocks base method. +func (m *MockNode) RunExecFromConfig(arg0 context.Context, arg1 *exec.ExecCollection) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RunExecFromConfig", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// RunExecFromConfig indicates an expected call of RunExecFromConfig. +func (mr *MockNodeMockRecorder) RunExecFromConfig(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RunExecFromConfig", reflect.TypeOf((*MockNode)(nil).RunExecFromConfig), arg0, arg1) +} + // SaveConfig mocks base method. func (m *MockNode) SaveConfig(arg0 context.Context) error { m.ctrl.T.Helper() diff --git a/nodes/default_node.go b/nodes/default_node.go index f7f5ac9760..5f7cd722a4 100644 --- a/nodes/default_node.go +++ b/nodes/default_node.go @@ -187,6 +187,24 @@ func (d *DefaultNode) GetContainers(ctx context.Context) ([]runtime.GenericConta return cnts, err } +func (d *DefaultNode) RunExecFromConfig(ctx context.Context, ec *exec.ExecCollection) error { + for _, e := range d.Config().Exec { + exec, err := exec.NewExecCmdFromString(e) + if err != nil { + log.Warnf("Failed to parse the command string: %s, %v", e, err) + } + + res, err := d.OverwriteNode.RunExec(ctx, exec) + if err != nil { + // kinds which do not support exec functionality are skipped + continue + } + + ec.Add(d.GetShortName(), res) + } + return nil +} + func (d *DefaultNode) UpdateConfigWithRuntimeInfo(ctx context.Context) error { cnts, err := d.OverwriteNode.GetContainers(ctx) if err != nil { @@ -305,6 +323,7 @@ type NodeOverwrites interface { GetContainers(ctx context.Context) ([]runtime.GenericContainer, error) GetContainerName() string VerifyLicenseFileExists(context.Context) error + RunExec(context.Context, *exec.ExecCmd) (*exec.ExecResult, error) } // LoadStartupConfigFileVr templates a startup-config using the file specified for VM-based nodes in the topo diff --git a/nodes/node.go b/nodes/node.go index 6a24f06f37..cf2d5fc80e 100644 --- a/nodes/node.go +++ b/nodes/node.go @@ -108,7 +108,10 @@ type Node interface { GetState() state.NodeState SetState(state.NodeState) GetSSHConfig() *types.SSHConfig + // WaitForAllLinksCreated will block until all the nodes links are created WaitForAllLinksCreated() + // RunExecFromConfig executes the topologyfile defined exec commands + RunExecFromConfig(context.Context, *exec.ExecCollection) error } type NodeOption func(Node) diff --git a/tests/02-basic-srl/01-two-srls.robot b/tests/02-basic-srl/01-two-srls.robot index d00a90d3f6..f5572e670d 100644 --- a/tests/02-basic-srl/01-two-srls.robot +++ b/tests/02-basic-srl/01-two-srls.robot @@ -133,7 +133,7 @@ Verify TLS works with JSON-RPC and certificate check Verify TLS works with JSON-RPC, certificate check and IP address as SAN ${rc} ${output} = Run And Return Rc And Output - ... curl --cacert ./clab-${lab-name}/.tls/ca/ca.pem 'https://admin:NokiaSrl1!@172.20.20.2/jsonrpc' -d '{"jsonrpc":"2.0","id":0,"method":"get","params":{"commands":[{"path":"/system/information/version","datastore":"state"}]}}' + ... curl --cacert ./clab-${lab-name}/.tls/ca/ca.pem 'https://admin:NokiaSrl1!@172.20.20.200/jsonrpc' -d '{"jsonrpc":"2.0","id":0,"method":"get","params":{"commands":[{"path":"/system/information/version","datastore":"state"}]}}' Log ${output} Should Be Equal As Integers ${rc} 0 Should Not Contain ${output} error