Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement --keep-mgmt-net for destroy subcmd #462

Merged
merged 14 commits into from
Jul 8, 2021
Merged
6 changes: 6 additions & 0 deletions clab/clab.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ func WithRuntime(name string, d bool, dur time.Duration, gracefulShutdown bool)
}
}

func WithKeepMgmtNet() ClabOption {
return func(c *CLab) {
c.Runtime.WithKeepMgmtNet()
hellt marked this conversation as resolved.
Show resolved Hide resolved
}
}

func WithTopoFile(file string) ClabOption {
return func(c *CLab) {
if file == "" {
Expand Down
107 changes: 54 additions & 53 deletions cmd/destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ import (
"github.com/srl-labs/containerlab/types"
)

var cleanup bool
var graceful bool
var (
cleanup bool
graceful bool
keepmgmtnet bool
steiler marked this conversation as resolved.
Show resolved Hide resolved
)

// destroyCmd represents the destroy command
var destroyCmd = &cobra.Command{
Expand All @@ -35,69 +38,66 @@ var destroyCmd = &cobra.Command{
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

opts := []clab.ClabOption{
clab.WithDebug(debug),
clab.WithTimeout(timeout),
clab.WithRuntime(rt, debug, timeout, graceful),
}

if keepmgmtnet {
opts = append(opts, clab.WithKeepMgmtNet())
}

topos := map[string]struct{}{}

switch {
case !all:
// stop if not topo file provided and not all labs are requested
// to be deleted
if err = topoSet(); err != nil {
return err
{
// stop if not topo file provided and not all labs are requested
// to be deleted
if err = topoSet(); err != nil {
return err
}
topos[topo] = struct{}{}
}
opts := []clab.ClabOption{
clab.WithDebug(debug),
clab.WithTimeout(timeout),
case all:
{
c := clab.NewContainerLab(opts...)
// list all containerlab containers
containers, err := c.Runtime.ListContainers(ctx, []*types.GenericFilter{{FilterType: "label", Field: "containerlab", Operator: "exists"}})
if err != nil {
return fmt.Errorf("could not list containers: %v", err)
}
if len(containers) == 0 {
return fmt.Errorf("no containerlab labs were found")
}
// get unique topo files from all labs
for _, cont := range containers {
topos[cont.Labels["clab-topo-file"]] = struct{}{}
}

}
}

for topo := range topos {
opts := append(opts,
clab.WithTopoFile(topo),
clab.WithRuntime(rt, debug, timeout, graceful),
clab.WithGracefulShutdown(graceful),
}
)
c := clab.NewContainerLab(opts...)
// change to the dir where topo file is located
// to resolve relative paths of license/configs in ParseTopology
if err = os.Chdir(filepath.Dir(topo)); err != nil {
return err
}

// Parse topology information
if err = c.ParseTopology(); err != nil {
return err
}
labs = append(labs, c)
case all:
opts := []clab.ClabOption{
clab.WithDebug(debug),
clab.WithTimeout(timeout),
clab.WithRuntime(rt, debug, timeout, graceful),
}
c := clab.NewContainerLab(opts...)
// list all containerlab containers
containers, err := c.Runtime.ListContainers(ctx, []*types.GenericFilter{{FilterType: "label", Field: "containerlab", Operator: "exists"}})
if err != nil {
return fmt.Errorf("could not list containers: %v", err)
}
if len(containers) == 0 {
return fmt.Errorf("no containerlab labs were found")
}
// get unique topo files from all labs
topos := map[string]struct{}{}
for _, cont := range containers {
topos[cont.Labels["clab-topo-file"]] = struct{}{}
}
for topo := range topos {
opts := []clab.ClabOption{
clab.WithDebug(debug),
clab.WithTimeout(timeout),
clab.WithTopoFile(topo),
clab.WithRuntime(rt, debug, timeout, graceful),
clab.WithGracefulShutdown(graceful),
}
c = clab.NewContainerLab(opts...)
// change to the dir where topo file is located
// to resolve relative paths of license/configs in ParseTopology
if err = os.Chdir(filepath.Dir(topo)); err != nil {
return err
}

// Parse topology information
if err = c.ParseTopology(); err != nil {
return err
}
labs = append(labs, c)
}
}

var errs []error
for _, clab := range labs {
err = destroyLab(ctx, clab)
Expand All @@ -119,6 +119,7 @@ func init() {
destroyCmd.Flags().BoolVarP(&graceful, "graceful", "", false, "attempt to stop containers before removing")
destroyCmd.Flags().BoolVarP(&all, "all", "a", false, "destroy all containerlab labs")
destroyCmd.Flags().UintVarP(&maxWorkers, "max-workers", "", 0, "limit the maximum number of workers deleteing nodes")
destroyCmd.Flags().BoolVarP(&keepmgmtnet, "keep-mgmt-net", "", false, "do not remove the mgmt network")
steiler marked this conversation as resolved.
Show resolved Hide resolved
}

func deleteEntriesFromHostsFile(containers []types.GenericContainer, bridgeName string) error {
Expand Down Expand Up @@ -209,7 +210,7 @@ func destroyLab(ctx context.Context, c *clab.CLab) (err error) {
}

// delete lab management network
log.Infof("Deleting network '%s'...", c.Config.Mgmt.Network)
hellt marked this conversation as resolved.
Show resolved Hide resolved
log.Infof("Cleanup networking")
if err = c.Runtime.DeleteNet(ctx); err != nil {
// do not log error message if deletion error simply says that such network doesn't exist
if err.Error() != fmt.Sprintf("Error: No such network: %s", c.Config.Mgmt.Network) {
Expand Down
1 change: 0 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ var rootCmd = &cobra.Command{
if debug {
log.SetLevel(log.DebugLevel)
}

},
}

Expand Down
22 changes: 18 additions & 4 deletions runtime/containerd/containerd.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ const (
func init() {
runtime.Register(dockerRuntimeName, func() runtime.ContainerRuntime {
return &ContainerdRuntime{
Mgmt: new(types.MgmtNet),
}
Mgmt: new(types.MgmtNet)}
})
}

func (c *ContainerdRuntime) Init(opts ...runtime.RuntimeOption) error {
var err error
log.Debug("Runtime: containerd")
c.keepMgmtNet = false
steiler marked this conversation as resolved.
Show resolved Hide resolved
c.client, err = containerd.New("/run/containerd/containerd.sock")
if err != nil {
return err
Expand All @@ -71,6 +71,7 @@ type ContainerdRuntime struct {
Mgmt *types.MgmtNet
debug bool
gracefulShutdown bool
keepMgmtNet bool
}

func (c *ContainerdRuntime) WithConfig(cfg *runtime.RuntimeConfig) {
Expand All @@ -93,14 +94,27 @@ func (c *ContainerdRuntime) WithMgmtNet(n *types.MgmtNet) {
c.Mgmt = n
}

func (c *ContainerdRuntime) WithKeepMgmtNet() {
c.keepMgmtNet = true
}

func (c *ContainerdRuntime) CreateNet(ctx context.Context) error {
log.Debug("CreateNet() - Not needed with containerd")
return nil
}
func (c *ContainerdRuntime) DeleteNet(context.Context) error {
log.Debug("DeleteNet() - Not yet required with containerd")
return nil
bridgename := c.Mgmt.Bridge
brInUse, err := utils.CheckBrInUse(bridgename)
if c.keepMgmtNet || brInUse {
log.Infof("Skipping deletion of bridge '%s'", bridgename)
return nil
}
if err != nil {
return err
}
return utils.DeleteNetworkInterface(bridgename)
}

func (c *ContainerdRuntime) ContainerInspect(context.Context, string) (*types.GenericContainer, error) {
return nil, errors.New("not implemented")
}
Expand Down
19 changes: 13 additions & 6 deletions runtime/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,13 @@ type DockerRuntime struct {
Mgmt *types.MgmtNet
debug bool
gracefulShutdown bool
keepMgmtNet bool
}

func (c *DockerRuntime) Init(opts ...runtime.RuntimeOption) error {
var err error
log.Debug("Runtime: Docker")
c.keepMgmtNet = false
c.Client, err = dockerC.NewClientWithOpts(dockerC.FromEnv, dockerC.WithAPIVersionNegotiation())
if err != nil {
return err
Expand All @@ -63,6 +65,10 @@ func (c *DockerRuntime) Init(opts ...runtime.RuntimeOption) error {
return nil
}

func (c *DockerRuntime) WithKeepMgmtNet() {
c.keepMgmtNet = true
}

func (c *DockerRuntime) WithConfig(cfg *runtime.RuntimeConfig) {
c.timeout = cfg.Timeout
c.debug = cfg.Debug
Expand Down Expand Up @@ -183,28 +189,29 @@ func (c *DockerRuntime) CreateNet(ctx context.Context) (err error) {

// DeleteNet deletes a docker bridge
func (c *DockerRuntime) DeleteNet(ctx context.Context) (err error) {
if c.Mgmt.Network == "bridge" {
log.Debug("Skipping potential deletion of docker default bridge 'bridge'.")
network := c.Mgmt.Network
if network == "bridge" || c.keepMgmtNet {
log.Infof("Skipping potential deletion of docker default bridge '%s'", network)
steiler marked this conversation as resolved.
Show resolved Hide resolved
return nil
}
nctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()

nres, err := c.Client.NetworkInspect(ctx, c.Mgmt.Network, dockerTypes.NetworkInspectOptions{})
nres, err := c.Client.NetworkInspect(ctx, network, dockerTypes.NetworkInspectOptions{})
if err != nil {
return err
}
numEndpoints := len(nres.Containers)
if numEndpoints > 0 {
if c.debug {
log.Debugf("network '%s' has %d active endpoints, deletion skipped", c.Mgmt.Network, numEndpoints)
log.Debugf("network '%s' has %d active endpoints, deletion skipped", network, numEndpoints)
for _, endp := range nres.Containers {
log.Debugf("'%s' is connected to %s", endp.Name, c.Mgmt.Network)
log.Debugf("'%s' is connected to %s", endp.Name, network)
}
}
return nil
}
err = c.Client.NetworkRemove(nctx, c.Mgmt.Network)
err = c.Client.NetworkRemove(nctx, network)
if err != nil {
return err
}
Expand Down
9 changes: 9 additions & 0 deletions runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ type ContainerRuntime interface {
WithConfig(*RuntimeConfig)
// Set the network management details (generated by the config.go)
WithMgmtNet(*types.MgmtNet)
// Instructs the runtime not to delete the mgmt network on destroy
WithKeepMgmtNet()
// Create container (bridge) network
CreateNet(context.Context) error
// Delete container (bridge) network
Expand Down Expand Up @@ -57,6 +59,7 @@ type RuntimeConfig struct {
Timeout time.Duration
GracefulShutdown bool
Debug bool
KeepMgmtNet bool
}

var ContainerRuntimes = map[string]Initializer{}
Expand All @@ -76,3 +79,9 @@ func WithMgmtNet(mgmt *types.MgmtNet) RuntimeOption {
r.WithMgmtNet(mgmt)
}
}

func WithKeepMgmtNet() RuntimeOption {
return func(r ContainerRuntime) {
r.WithKeepMgmtNet()
}
}
28 changes: 28 additions & 0 deletions utils/netlink.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,31 @@ func DefaultNetMTU() (string, error) {
}
return fmt.Sprint(b.MTU), nil
}

func CheckBrInUse(brname string) (bool, error) {
InUse := false
l, err := netlink.LinkList()
if err != nil {
return InUse, err
}
mgmtbr, err := netlink.LinkByName(brname)
if err != nil {
return InUse, err
}
mgmtbridx := mgmtbr.Attrs().Index
for _, link := range l {
if link.Attrs().MasterIndex == mgmtbridx {
InUse = true
break
}
}
return InUse, nil
}

func DeleteNetworkInterface(name string) error {
hellt marked this conversation as resolved.
Show resolved Hide resolved
l, err := netlink.LinkByName(name)
if err != nil {
return err
}
return netlink.LinkDel(l)
}