Skip to content

Commit

Permalink
exec from topology as part of node schedule
Browse files Browse the repository at this point in the history
  • Loading branch information
steiler committed Dec 1, 2023
1 parent 44085d4 commit 3dd92d7
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 37 deletions.
28 changes: 19 additions & 9 deletions clab/clab.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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,
) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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:<NAME>)
Expand Down
3 changes: 0 additions & 3 deletions clab/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
28 changes: 4 additions & 24 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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)

Expand Down
16 changes: 16 additions & 0 deletions mocks/mocknodes/default_node.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions mocks/mocknodes/node.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions nodes/default_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions nodes/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion tests/02-basic-srl/01-two-srls.robot
Original file line number Diff line number Diff line change
Expand Up @@ -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:[email protected].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:[email protected].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
Expand Down

0 comments on commit 3dd92d7

Please sign in to comment.