From 0ed0a90a0c7b3f1ee2dca1e3fbb92112449f428a Mon Sep 17 00:00:00 2001 From: Alonza0314 Date: Fri, 17 Apr 2026 06:57:32 +0000 Subject: [PATCH 1/6] feat: depends on field --- integration-test/topo/topo_template.yaml | 2 ++ internal/topo/model.go | 1 + internal/topo/topo.go | 7 +++++ internal/topo/topo_test.go | 39 ++++++++++++++++++++++++ 4 files changed, 49 insertions(+) diff --git a/integration-test/topo/topo_template.yaml b/integration-test/topo/topo_template.yaml index b0060db..eb83ecb 100644 --- a/integration-test/topo/topo_template.yaml +++ b/integration-test/topo/topo_template.yaml @@ -6,6 +6,8 @@ namespaces: - name: br-test-1 bridge: br-test-1-2 ipv4: 10.0.0.1/24 + depends_on: + - test-2 - name: test-2 commands: diff --git a/internal/topo/model.go b/internal/topo/model.go index 829b0a8..1a18b4b 100644 --- a/internal/topo/model.go +++ b/internal/topo/model.go @@ -9,6 +9,7 @@ type Namespace struct { Name string `yaml:"name" validate:"required"` Commands []string `yaml:"commands"` Networks []Net `yaml:"networks"` + DependsOn []string `yaml:"depends_on"` } type Network struct { diff --git a/internal/topo/topo.go b/internal/topo/topo.go index a0c4653..3d096db 100644 --- a/internal/topo/topo.go +++ b/internal/topo/topo.go @@ -42,7 +42,14 @@ func checkTopoNamespace(topo *Topology) error { if err := checkNamespaceNetwork(&ns); err != nil { return err } + } + for _, ns := range topo.Namespaces { + for _, dep := range ns.DependsOn { + if _, exists := nsNames[dep]; !exists { + return fmt.Errorf("namespace %s depends on non-existent namespace: %s", ns.Name, dep) + } + } } return nil diff --git a/internal/topo/topo_test.go b/internal/topo/topo_test.go index d0e6250..f357306 100644 --- a/internal/topo/topo_test.go +++ b/internal/topo/topo_test.go @@ -22,6 +22,7 @@ var testCheckTopoCases = []struct { Ipv4: "10.0.0.1/24", }, }, + DependsOn: []string{"test-2"}, }, { Name: "test-2", @@ -396,6 +397,44 @@ var testCheckTopoCases = []struct { expectedErr: true, expectedErrDetail: "invalid IP address 10.0.0.999/24 for bridge br-test-1-2: invalid IP address: 10.0.0.999/24", }, + { + name: "depends on non-existent namespace", + topo: &Topology{ + Namespaces: []Namespace{ + { + Name: "test-1", + Commands: []string{"echo"}, + Networks: []Net{ + { + Name: "br-test-1", + Bridge: "br-test-1-2", + Ipv4: "10.0.0.1/24", + }, + }, + DependsOn: []string{"test-3"}, + }, + { + Name: "test-2", + Commands: []string{"echo"}, + Networks: []Net{ + { + Name: "br-test-2", + Bridge: "br-test-1-2", + Ipv4: "10.0.0.2/24", + }, + }, + }, + }, + Networks: []Network{ + { + Name: "br-test-1-2", + Subnet: "10.0.0.0/24", + }, + }, + }, + expectedErr: true, + expectedErrDetail: "namespace test-1 depends on non-existent namespace: test-3", + }, } func TestCheckTopo(t *testing.T) { From 5d714751ec9912302bf1eee0be96b7c8332669f6 Mon Sep 17 00:00:00 2001 From: Alonza0314 Date: Fri, 17 Apr 2026 07:43:34 +0000 Subject: [PATCH 2/6] feat: topological sort --- internal/topo/topo.go | 81 ++++++++++++++++++++++ internal/topo/topo_test.go | 137 +++++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+) diff --git a/internal/topo/topo.go b/internal/topo/topo.go index 3d096db..6d6ef54 100644 --- a/internal/topo/topo.go +++ b/internal/topo/topo.go @@ -152,3 +152,84 @@ func checkNamespaceExist(nss []Namespace) error { return nil } + +type graph struct { + aTob map[string][]string + inDegree map[string]int + index map[string]int +} + +func newGraph() *graph { + return &graph{ + aTob: make(map[string][]string), + inDegree: make(map[string]int), + index: make(map[string]int), + } +} + +func (g *graph) make(nss []Namespace) { + for i, ns := range nss { + g.aTob[ns.Name], g.inDegree[ns.Name], g.index[ns.Name] = make([]string, 0), 0, i + } + + for _, ns := range nss { + for _, dep := range ns.DependsOn { + g.aTob[dep] = append(g.aTob[dep], ns.Name) + g.inDegree[ns.Name] += 1 + } + } + +} + +func (g *graph) getNeighbors(ns string) []string { + return g.aTob[ns] +} + +func (g *graph) topologicalSort() []string { + queue := make([]string, 0) + for ns, degree := range g.inDegree { + if degree == 0 { + queue = append(queue, ns) + } + } + + sorted := make([]string, 0) + for len(queue) > 0 { + ns := queue[0] + queue, sorted = queue[1:], append(sorted, ns) + + for _, neighbor := range g.getNeighbors(ns) { + g.inDegree[neighbor] -= 1 + if g.inDegree[neighbor] == 0 { + queue = append(queue, neighbor) + } + } + } + return sorted +} + +func (g *graph) getSortedNamespaces(nssName []string, nss []Namespace) []Namespace { + nsList := make([]Namespace, len(nss)) + for i, ns := range nssName { + nsList[i] = nss[g.index[ns]] + } + return nsList +} + +func SortNamespacesByDependency(nss []Namespace, reverse bool) ([]Namespace, error) { + g := newGraph() + g.make(nss) + sortedNames := g.topologicalSort() + if len(sortedNames) != len(nss) { + return nil, fmt.Errorf("circular dependency detected among namespaces") + } + + sortedNamespaces := g.getSortedNamespaces(sortedNames, nss) + if reverse { + for i, j := 0, len(sortedNamespaces)-1; i < j; i, j = i+1, j-1 { + sortedNamespaces[i], sortedNamespaces[j] = sortedNamespaces[j], sortedNamespaces[i] + } + } + + return sortedNamespaces, nil +} diff --git a/internal/topo/topo_test.go b/internal/topo/topo_test.go index f357306..20a7814 100644 --- a/internal/topo/topo_test.go +++ b/internal/topo/topo_test.go @@ -455,3 +455,140 @@ func TestCheckTopo(t *testing.T) { }) } } + +var testTopologicalSortCases = []struct { + name string + namespaces []Namespace + reverse bool + epectedErr bool + expectedOrder []string + expectedErrDetail string +}{ + { + name: "valid topo", + namespaces: []Namespace{ + { + Name: "test-1", + Commands: []string{"echo"}, + Networks: []Net{ + { + Name: "br-test-1", + Bridge: "br-test-1-2", + Ipv4: "10.0.0.1/24", + }, + }, + DependsOn: []string{"test-2"}, + }, + { + Name: "test-2", + Commands: []string{"echo"}, + Networks: []Net{ + { + Name: "br-test-2", + Bridge: "br-test-1-2", + Ipv4: "10.0.0.2/24", + }, + }, + }, + }, + reverse: false, + epectedErr: false, + expectedOrder: []string{"test-2", "test-1"}, + expectedErrDetail: "", + }, + { + name: "valid topo reverse", + namespaces: []Namespace{ + { + Name: "test-1", + Commands: []string{"echo"}, + Networks: []Net{ + { + Name: "br-test-1", + Bridge: "br-test-1-2", + Ipv4: "10.0.0.1/24", + }, + }, + DependsOn: []string{"test-2"}, + }, + { + Name: "test-2", + Commands: []string{"echo"}, + Networks: []Net{ + { + Name: "br-test-2", + Bridge: "br-test-1-2", + Ipv4: "10.0.0.2/24", + }, + }, + }, + }, + reverse: true, + epectedErr: false, + expectedOrder: []string{"test-1", "test-2"}, + expectedErrDetail: "", + }, + { + name: "circular dependency", + namespaces: []Namespace{ + { + Name: "test-1", + Commands: []string{"echo"}, + Networks: []Net{ + { + Name: "br-test-1", + Bridge: "br-test-1-2", + Ipv4: "10.0.0.1/24", + }, + }, + DependsOn: []string{"test-2"}, + }, + { + Name: "test-2", + Commands: []string{"echo"}, + Networks: []Net{ + { + Name: "br-test-2", + Bridge: "br-test-1-2", + Ipv4: "10.0.0.2/24", + }, + }, + DependsOn: []string{"test-1"}, + }, + }, + reverse: false, + epectedErr: true, + expectedOrder: nil, + expectedErrDetail: "circular dependency detected among namespaces", + }, +} + +func TestTopologicalSort(t *testing.T) { + for _, tc := range testTopologicalSortCases { + t.Run(tc.name, func(t *testing.T) { + sorted, err := SortNamespacesByDependency(tc.namespaces, tc.reverse) + if tc.epectedErr { + if err == nil { + t.Errorf("expected error but got nil") + } else if err.Error() != tc.expectedErrDetail { + t.Errorf("expected error detail '%s' but got '%s'", tc.expectedErrDetail, err.Error()) + } + } else { + if err != nil { + t.Errorf("expected no error but got: %v", err) + } else { + sortedNames := make([]string, len(sorted)) + for i, ns := range sorted { + sortedNames[i] = ns.Name + } + for i := range sortedNames { + if sortedNames[i] != tc.expectedOrder[i] { + t.Errorf("expected order %v but got %v", tc.expectedOrder, sortedNames) + break + } + } + } + } + }) + } +} From 2cad4f1dafdc2c11c39a5c33535bf0a652a05802 Mon Sep 17 00:00:00 2001 From: Alonza0314 Date: Fri, 17 Apr 2026 08:30:09 +0000 Subject: [PATCH 3/6] refactor: cycle detect --- integration-test/topo/test.sh | 8 +++ integration-test/topo/topo_apply_cycle.txt | 1 + .../topo/topo_template_deps_cycle.yaml | 24 +++++++ internal/topo/apply.go | 5 +- internal/topo/delete.go | 5 +- internal/topo/topo.go | 64 +++++++++++-------- internal/topo/topo_test.go | 41 ++++++------ 7 files changed, 96 insertions(+), 52 deletions(-) create mode 100644 integration-test/topo/topo_apply_cycle.txt create mode 100644 integration-test/topo/topo_template_deps_cycle.yaml diff --git a/integration-test/topo/test.sh b/integration-test/topo/test.sh index 8d4f8fd..17068b7 100755 --- a/integration-test/topo/test.sh +++ b/integration-test/topo/test.sh @@ -42,6 +42,13 @@ assert_count() { echo "[+][PASS] $1" } +topo_apply_cycle() { + output=$(../nsctl topo cycle topo_template_deps_cycle.yaml) + expect=$(cat topo_apply_cycle.txt) + + diff "Topo apply cycle" "$output" "$expect" +} + topo_apply() { output=$(../nsctl topo apply topo_template.yaml) expect=$(cat topo_apply.txt) @@ -66,6 +73,7 @@ topo_delete() { } main() { + topo_apply_cycle topo_apply exec_ping_test topo_delete diff --git a/integration-test/topo/topo_apply_cycle.txt b/integration-test/topo/topo_apply_cycle.txt new file mode 100644 index 0000000..4d88028 --- /dev/null +++ b/integration-test/topo/topo_apply_cycle.txt @@ -0,0 +1 @@ +Error: circular dependency detected among namespaces \ No newline at end of file diff --git a/integration-test/topo/topo_template_deps_cycle.yaml b/integration-test/topo/topo_template_deps_cycle.yaml new file mode 100644 index 0000000..1290849 --- /dev/null +++ b/integration-test/topo/topo_template_deps_cycle.yaml @@ -0,0 +1,24 @@ +namespaces: + - name: test-1 + commands: + - ls + networks: + - name: br-test-1 + bridge: br-test-1-2 + ipv4: 10.0.0.1/24 + depends_on: + - test-2 + + - name: test-2 + commands: + - ls + networks: + - name: br-test-2 + bridge: br-test-1-2 + ipv4: 10.0.0.2/24 + depends_on: + - test-1 + +networks: + - name: br-test-1-2 + subnet: 10.0.0.0/24 \ No newline at end of file diff --git a/internal/topo/apply.go b/internal/topo/apply.go index 8906cd4..f025808 100644 --- a/internal/topo/apply.go +++ b/internal/topo/apply.go @@ -8,7 +8,8 @@ import ( ) func Apply(topo *Topology) error { - if err := checkTopo(topo); err != nil { + g, err := checkTopo(topo) + if err != nil { return err } @@ -20,7 +21,7 @@ func Apply(topo *Topology) error { return err } - if err := addNamespaces(topo.Namespaces); err != nil { + if err := addNamespaces(g.getSortedNamespaces(topo.Namespaces, false)); err != nil { return err } diff --git a/internal/topo/delete.go b/internal/topo/delete.go index 8392bb1..7c793f9 100644 --- a/internal/topo/delete.go +++ b/internal/topo/delete.go @@ -9,11 +9,12 @@ import ( ) func Delete(topo *Topology) error { - if err := checkTopo(topo); err != nil { + g, err := checkTopo(topo) + if err != nil { return err } - if err := deleteNamespaces(topo.Namespaces); err != nil { + if err := deleteNamespaces(g.getSortedNamespaces(topo.Namespaces, true)); err != nil { return err } diff --git a/internal/topo/topo.go b/internal/topo/topo.go index 6d6ef54..4dac230 100644 --- a/internal/topo/topo.go +++ b/internal/topo/topo.go @@ -8,51 +8,57 @@ import ( "github.com/pterm/pterm" ) -func checkTopo(topo *Topology) error { +func checkTopo(topo *Topology) (*graph, error) { spinner, err := pterm.DefaultSpinner.Start("Checking topology namespaces...") if err != nil { - return err + return nil, err } - if err := checkTopoNamespace(topo); err != nil { - return err + g, err := checkTopoNamespace(topo) + if err != nil { + return nil, err } spinner.Success("Topology namespaces check passed") spinner, err = pterm.DefaultSpinner.Start("Checking topology networks...") if err != nil { - return err + return nil, err } if err := checkTopoNetwork(topo); err != nil { - return err + return nil, err } spinner.Success("Topology networks check passed") - return nil + return g, nil } -func checkTopoNamespace(topo *Topology) error { +func checkTopoNamespace(topo *Topology) (*graph, error) { nsNames := make(map[string]struct{}) for _, ns := range topo.Namespaces { if _, exists := nsNames[ns.Name]; exists { - return fmt.Errorf("duplicate namespace name: %s", ns.Name) + return nil, fmt.Errorf("duplicate namespace name: %s", ns.Name) } nsNames[ns.Name] = struct{}{} if err := checkNamespaceNetwork(&ns); err != nil { - return err + return nil, err } } for _, ns := range topo.Namespaces { for _, dep := range ns.DependsOn { if _, exists := nsNames[dep]; !exists { - return fmt.Errorf("namespace %s depends on non-existent namespace: %s", ns.Name, dep) + return nil, fmt.Errorf("namespace %s depends on non-existent namespace: %s", ns.Name, dep) } } } - return nil + g, err := existCycle(topo.Namespaces) + if err != nil { + return nil, err + } + + return g, nil } func checkNamespaceNetwork(ns *Namespace) error { @@ -157,6 +163,7 @@ type graph struct { aTob map[string][]string inDegree map[string]int index map[string]int + sorted []string } func newGraph() *graph { @@ -164,6 +171,7 @@ func newGraph() *graph { aTob: make(map[string][]string), inDegree: make(map[string]int), index: make(map[string]int), + sorted: nil, } } @@ -185,7 +193,7 @@ func (g *graph) getNeighbors(ns string) []string { return g.aTob[ns] } -func (g *graph) topologicalSort() []string { +func (g *graph) topologicalSort() { queue := make([]string, 0) for ns, degree := range g.inDegree { if degree == 0 { @@ -205,31 +213,33 @@ func (g *graph) topologicalSort() []string { } } } - return sorted + + g.sorted = sorted } -func (g *graph) getSortedNamespaces(nssName []string, nss []Namespace) []Namespace { +func (g *graph) getSortedNamespaces(nss []Namespace, reverse bool) []Namespace { nsList := make([]Namespace, len(nss)) - for i, ns := range nssName { + for i, ns := range g.sorted { nsList[i] = nss[g.index[ns]] } + + if reverse { + for i, j := 0, len(nsList)-1; i < j; i, j = i+1, j-1 { + nsList[i], nsList[j] = nsList[j], nsList[i] + } + } + return nsList } -func SortNamespacesByDependency(nss []Namespace, reverse bool) ([]Namespace, error) { +func existCycle(nss []Namespace) (*graph, error) { g := newGraph() g.make(nss) - sortedNames := g.topologicalSort() - if len(sortedNames) != len(nss) { - return nil, fmt.Errorf("circular dependency detected among namespaces") - } - sortedNamespaces := g.getSortedNamespaces(sortedNames, nss) - if reverse { - for i, j := 0, len(sortedNamespaces)-1; i < j; i, j = i+1, j-1 { - sortedNamespaces[i], sortedNamespaces[j] = sortedNamespaces[j], sortedNamespaces[i] - } + g.topologicalSort() + if len(g.sorted) != len(nss) { + return nil, fmt.Errorf("circular dependency detected among namespaces") } - return sortedNamespaces, nil + return g, nil } diff --git a/internal/topo/topo_test.go b/internal/topo/topo_test.go index 20a7814..35c001f 100644 --- a/internal/topo/topo_test.go +++ b/internal/topo/topo_test.go @@ -440,7 +440,7 @@ var testCheckTopoCases = []struct { func TestCheckTopo(t *testing.T) { for _, tc := range testCheckTopoCases { t.Run(tc.name, func(t *testing.T) { - err := checkTopo(tc.topo) + _, err := checkTopo(tc.topo) if tc.expectedErr { if err == nil { t.Errorf("expected error but got nil") @@ -566,27 +566,26 @@ var testTopologicalSortCases = []struct { func TestTopologicalSort(t *testing.T) { for _, tc := range testTopologicalSortCases { t.Run(tc.name, func(t *testing.T) { - sorted, err := SortNamespacesByDependency(tc.namespaces, tc.reverse) - if tc.epectedErr { - if err == nil { - t.Errorf("expected error but got nil") - } else if err.Error() != tc.expectedErrDetail { - t.Errorf("expected error detail '%s' but got '%s'", tc.expectedErrDetail, err.Error()) - } - } else { - if err != nil { - t.Errorf("expected no error but got: %v", err) - } else { - sortedNames := make([]string, len(sorted)) - for i, ns := range sorted { - sortedNames[i] = ns.Name - } - for i := range sortedNames { - if sortedNames[i] != tc.expectedOrder[i] { - t.Errorf("expected order %v but got %v", tc.expectedOrder, sortedNames) - break - } + g, err := existCycle(tc.namespaces) + if err != nil { + if tc.epectedErr { + if err.Error() != tc.expectedErrDetail { + t.Errorf("expected error detail '%s' but got '%s'", tc.expectedErrDetail, err.Error()) } + } else { + t.Errorf("expected no error but got: %v", err) + } + return + } + sorted := g.getSortedNamespaces(tc.namespaces, tc.reverse) + sortedNames := make([]string, len(sorted)) + for i, ns := range sorted { + sortedNames[i] = ns.Name + } + for i := range sortedNames { + if sortedNames[i] != tc.expectedOrder[i] { + t.Errorf("expected order %v but got %v", tc.expectedOrder, sortedNames) + break } } }) From 9604f549575a6f3990796e2e0c0ec6dc52d5c1f5 Mon Sep 17 00:00:00 2001 From: Alonza0314 Date: Fri, 17 Apr 2026 08:32:46 +0000 Subject: [PATCH 4/6] fix: cmd error --- integration-test/topo/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-test/topo/test.sh b/integration-test/topo/test.sh index 17068b7..a99530e 100755 --- a/integration-test/topo/test.sh +++ b/integration-test/topo/test.sh @@ -43,7 +43,7 @@ assert_count() { } topo_apply_cycle() { - output=$(../nsctl topo cycle topo_template_deps_cycle.yaml) + output=$(../nsctl topo apply topo_template_deps_cycle.yaml) expect=$(cat topo_apply_cycle.txt) diff "Topo apply cycle" "$output" "$expect" From bebc14b219a4c9ffe218d3f5cdbef11b07778f26 Mon Sep 17 00:00:00 2001 From: Alonza0314 Date: Fri, 17 Apr 2026 08:44:12 +0000 Subject: [PATCH 5/6] refactor: error handle in topo cmd --- cmd/topo.go | 8 ++---- integration-test/topo/test.sh | 2 +- integration-test/topo/topo_apply_cycle.txt | 1 - internal/topo/delete.go | 33 ++++++++++++++++++++-- internal/topo/topo.go | 2 ++ 5 files changed, 36 insertions(+), 10 deletions(-) delete mode 100644 integration-test/topo/topo_apply_cycle.txt diff --git a/cmd/topo.go b/cmd/topo.go index 8657664..16bd464 100644 --- a/cmd/topo.go +++ b/cmd/topo.go @@ -35,15 +35,11 @@ func topoFunc(cmd *cobra.Command, args []string) { switch args[0] { case "apply": - if err := topo.Apply(&topoStruct); err != nil { - errPrint(err) - } else { + if err := topo.Apply(&topoStruct); err == nil { fmt.Printf("Topology applied successfully from file: %s\n", args[1]) } case "delete": - if err := topo.Delete(&topoStruct); err != nil { - errPrint(err) - } else { + if err := topo.Delete(&topoStruct); err == nil { fmt.Printf("Topology deleted successfully from file: %s\n", args[1]) } default: diff --git a/integration-test/topo/test.sh b/integration-test/topo/test.sh index a99530e..7a6ba69 100755 --- a/integration-test/topo/test.sh +++ b/integration-test/topo/test.sh @@ -44,7 +44,7 @@ assert_count() { topo_apply_cycle() { output=$(../nsctl topo apply topo_template_deps_cycle.yaml) - expect=$(cat topo_apply_cycle.txt) + expect="" diff "Topo apply cycle" "$output" "$expect" } diff --git a/integration-test/topo/topo_apply_cycle.txt b/integration-test/topo/topo_apply_cycle.txt deleted file mode 100644 index 4d88028..0000000 --- a/integration-test/topo/topo_apply_cycle.txt +++ /dev/null @@ -1 +0,0 @@ -Error: circular dependency detected among namespaces \ No newline at end of file diff --git a/internal/topo/delete.go b/internal/topo/delete.go index 7c793f9..fce7b67 100644 --- a/internal/topo/delete.go +++ b/internal/topo/delete.go @@ -1,6 +1,8 @@ package topo import ( + "errors" + "github.com/Alonza0314/nsctl/internal/namespace" "github.com/Alonza0314/nsctl/internal/veth" "github.com/pterm/pterm" @@ -14,44 +16,55 @@ func Delete(topo *Topology) error { return err } + errFlag := false if err := deleteNamespaces(g.getSortedNamespaces(topo.Namespaces, true)); err != nil { - return err + errFlag = true } if err := deleteBridges(topo.Networks); err != nil { - return err + errFlag = true } + if errFlag { + return errors.New("Error") + } return nil } func deleteNamespaces(nss []Namespace) error { + errFlag := false + for _, ns := range nss { spinner, err := pterm.DefaultSpinner.Start("Deleting namespace " + ns.Name + "...") if err != nil { spinner.Fail("Failed to start spinner for namespace " + ns.Name + ": " + err.Error()) + errFlag = true continue } found, err := namespace.GetNs(ns.Name) if err != nil { spinner.Fail("Failed to get namespace " + ns.Name + ": " + err.Error()) + errFlag = true continue } if !found { spinner.Fail("Namespace " + ns.Name + " does not exist") + errFlag = true continue } _, originCloseFunc, err := namespace.GetOriginNs() if err != nil { spinner.Fail("Failed to get origin namespace file descriptor: " + err.Error()) + errFlag = true continue } nsFd, nsCloseFunc, err := namespace.GetNsFd(ns.Name) if err != nil { spinner.Fail("Failed to get namespace " + ns.Name + " file descriptor: " + err.Error()) originCloseFunc() + errFlag = true continue } @@ -59,22 +72,26 @@ func deleteNamespaces(nss []Namespace) error { spinner.Fail("Failed to set namespace " + ns.Name + ": " + err.Error()) nsCloseFunc() originCloseFunc() + errFlag = true continue } for _, network := range ns.Networks { if err := veth.UpDown(ns.Name, network.Name, false); err != nil { spinner.Fail("Failed to bring down veth for namespace " + ns.Name + " and network " + network.Name + ": " + err.Error()) + errFlag = true continue } link, err := netlink.LinkByName(network.Name) if err != nil { spinner.Fail("Failed to get link " + network.Name + " in namespace " + ns.Name + ": " + err.Error()) + errFlag = true continue } if err := netlink.LinkDel(link); err != nil { spinner.Fail("Failed to delete link " + network.Name + " in namespace " + ns.Name + ": " + err.Error()) + errFlag = true } } @@ -83,31 +100,43 @@ func deleteNamespaces(nss []Namespace) error { if err := namespace.Delete(ns.Name); err != nil { spinner.Fail("Failed to delete namespace " + ns.Name + ": " + err.Error()) + errFlag = true } spinner.Success("Namespace " + ns.Name + " deleted") } + if errFlag { + return errors.New("Error") + } return nil } func deleteBridges(networks []Network) error { + errFlag := false + for _, network := range networks { spinner, err := pterm.DefaultSpinner.Start("Deleting bridge " + network.Name + "...") if err != nil { spinner.Fail("Failed to start spinner for bridge " + network.Name + ": " + err.Error()) + errFlag = true continue } link, err := netlink.LinkByName(network.Name) if err != nil { spinner.Fail("Failed to get bridge " + network.Name + ": " + err.Error()) + errFlag = true continue } if err := netlink.LinkDel(link); err != nil { spinner.Fail("Failed to delete bridge " + network.Name + ": " + err.Error()) + errFlag = true } spinner.Success("Bridge " + network.Name + " deleted") } + if errFlag { + return errors.New("Error") + } return nil } diff --git a/internal/topo/topo.go b/internal/topo/topo.go index 4dac230..269ff77 100644 --- a/internal/topo/topo.go +++ b/internal/topo/topo.go @@ -15,6 +15,7 @@ func checkTopo(topo *Topology) (*graph, error) { } g, err := checkTopoNamespace(topo) if err != nil { + spinner.Fail("Topology namespaces check failed: " + err.Error()) return nil, err } spinner.Success("Topology namespaces check passed") @@ -24,6 +25,7 @@ func checkTopo(topo *Topology) (*graph, error) { return nil, err } if err := checkTopoNetwork(topo); err != nil { + spinner.Fail("Topology networks check failed: " + err.Error()) return nil, err } spinner.Success("Topology networks check passed") From c2b7215ffb58f0be9f42bb97c99e54de189f852a Mon Sep 17 00:00:00 2001 From: Alonza0314 Date: Tue, 21 Apr 2026 03:32:25 +0000 Subject: [PATCH 6/6] chore: remove command --- internal/topo/model.go | 1 - internal/topo/topo_test.go | 28 ---------------------------- 2 files changed, 29 deletions(-) diff --git a/internal/topo/model.go b/internal/topo/model.go index 1a18b4b..f4a550e 100644 --- a/internal/topo/model.go +++ b/internal/topo/model.go @@ -7,7 +7,6 @@ type Topology struct { type Namespace struct { Name string `yaml:"name" validate:"required"` - Commands []string `yaml:"commands"` Networks []Net `yaml:"networks"` DependsOn []string `yaml:"depends_on"` } diff --git a/internal/topo/topo_test.go b/internal/topo/topo_test.go index 35c001f..6a7a6d2 100644 --- a/internal/topo/topo_test.go +++ b/internal/topo/topo_test.go @@ -14,7 +14,6 @@ var testCheckTopoCases = []struct { Namespaces: []Namespace{ { Name: "test-1", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-1", @@ -26,7 +25,6 @@ var testCheckTopoCases = []struct { }, { Name: "test-2", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-2", @@ -52,7 +50,6 @@ var testCheckTopoCases = []struct { Namespaces: []Namespace{ { Name: "test-1", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-1", @@ -63,7 +60,6 @@ var testCheckTopoCases = []struct { }, { Name: "test-1", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-2", @@ -89,7 +85,6 @@ var testCheckTopoCases = []struct { Namespaces: []Namespace{ { Name: "test-1", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-1", @@ -105,7 +100,6 @@ var testCheckTopoCases = []struct { }, { Name: "test-2", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-2", @@ -131,7 +125,6 @@ var testCheckTopoCases = []struct { Namespaces: []Namespace{ { Name: "test-1", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-1", @@ -147,7 +140,6 @@ var testCheckTopoCases = []struct { }, { Name: "test-2", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-2", @@ -173,7 +165,6 @@ var testCheckTopoCases = []struct { Namespaces: []Namespace{ { Name: "test-1", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-1", @@ -184,7 +175,6 @@ var testCheckTopoCases = []struct { }, { Name: "test-2", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-2", @@ -214,7 +204,6 @@ var testCheckTopoCases = []struct { Namespaces: []Namespace{ { Name: "test-1", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-1", @@ -225,7 +214,6 @@ var testCheckTopoCases = []struct { }, { Name: "test-2", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-2", @@ -251,7 +239,6 @@ var testCheckTopoCases = []struct { Namespaces: []Namespace{ { Name: "test-1", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-1", @@ -262,7 +249,6 @@ var testCheckTopoCases = []struct { }, { Name: "test-2", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-2", @@ -292,7 +278,6 @@ var testCheckTopoCases = []struct { Namespaces: []Namespace{ { Name: "test-1", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-1", @@ -303,7 +288,6 @@ var testCheckTopoCases = []struct { }, { Name: "test-2", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-2", @@ -329,7 +313,6 @@ var testCheckTopoCases = []struct { Namespaces: []Namespace{ { Name: "test-1", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-1", @@ -340,7 +323,6 @@ var testCheckTopoCases = []struct { }, { Name: "test-2", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-2", @@ -366,7 +348,6 @@ var testCheckTopoCases = []struct { Namespaces: []Namespace{ { Name: "test-1", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-1", @@ -377,7 +358,6 @@ var testCheckTopoCases = []struct { }, { Name: "test-2", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-2", @@ -403,7 +383,6 @@ var testCheckTopoCases = []struct { Namespaces: []Namespace{ { Name: "test-1", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-1", @@ -415,7 +394,6 @@ var testCheckTopoCases = []struct { }, { Name: "test-2", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-2", @@ -469,7 +447,6 @@ var testTopologicalSortCases = []struct { namespaces: []Namespace{ { Name: "test-1", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-1", @@ -481,7 +458,6 @@ var testTopologicalSortCases = []struct { }, { Name: "test-2", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-2", @@ -501,7 +477,6 @@ var testTopologicalSortCases = []struct { namespaces: []Namespace{ { Name: "test-1", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-1", @@ -513,7 +488,6 @@ var testTopologicalSortCases = []struct { }, { Name: "test-2", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-2", @@ -533,7 +507,6 @@ var testTopologicalSortCases = []struct { namespaces: []Namespace{ { Name: "test-1", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-1", @@ -545,7 +518,6 @@ var testTopologicalSortCases = []struct { }, { Name: "test-2", - Commands: []string{"echo"}, Networks: []Net{ { Name: "br-test-2",