From bac7da84089b54c2ca5adbb7596ca8f56c3805a8 Mon Sep 17 00:00:00 2001 From: Hidde Beydals Date: Fri, 2 Jul 2021 17:12:56 +0200 Subject: [PATCH] Make runtime dependency package to use meta API This commit makes the runtime dependency package use the API structs and interface from our own meta package, and aligns the way we make assumptions about objects being being controller-runtime compatible options with that of the other helper packages like `controller` and `conditions`. Signed-off-by: Hidde Beydals --- runtime/dependency/doc.go | 19 +++++ runtime/dependency/sort.go | 63 ++++++-------- runtime/dependency/sort_test.go | 144 +++++++++++++++++++------------- 3 files changed, 130 insertions(+), 96 deletions(-) create mode 100644 runtime/dependency/doc.go diff --git a/runtime/dependency/doc.go b/runtime/dependency/doc.go new file mode 100644 index 00000000..c064e750 --- /dev/null +++ b/runtime/dependency/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2020 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package dependency contains an utility for sorting a set of Kubernetes resource objects that implement the +// Dependent interface. +package dependency diff --git a/runtime/dependency/sort.go b/runtime/dependency/sort.go index e5049ad5..5749fee8 100644 --- a/runtime/dependency/sort.go +++ b/runtime/dependency/sort.go @@ -20,50 +20,33 @@ import ( "fmt" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/runtime/internal/tarjan" ) -// Dependent provides an interface for resources that maintain -// CrossNamespaceDependencyReference list. +// Dependent interface defines methods that a Kubernetes resource object should implement in order to use the dependency +// package for ordering dependencies. type Dependent interface { - // GetDependsOn returns the Dependent's types.NamespacedName, - // and the CrossNamespaceDependencyReference slice it depends on. - GetDependsOn() (types.NamespacedName, []CrossNamespaceDependencyReference) + client.Object + meta.ObjectWithDependencies } -// CrossNamespaceDependencyReference holds the reference to a dependency. -type CrossNamespaceDependencyReference struct { - // Namespace holds the namespace reference of a dependency. - // +optional - Namespace string `json:"namespace,omitempty"` - - // Name holds the name reference of a dependency. - // +required - Name string `json:"name"` -} - -func (r CrossNamespaceDependencyReference) String() string { - if r.Namespace == "" { - return r.Name - } - return fmt.Sprintf("%s%c%s", r.Namespace, types.Separator, r.Name) -} - -// CircularDependencyError contains the circular dependency chains -// that were detected while sorting the Dependent dependencies. +// CircularDependencyError contains the circular dependency chains that were detected while sorting the Dependent +// dependencies. type CircularDependencyError [][]string func (e CircularDependencyError) Error() string { return fmt.Sprintf("circular dependencies: %v", [][]string(e)) } -// Sort sorts the Dependent slice based on their listed -// dependencies using Tarjan's strongly connected components algorithm. -func Sort(d []Dependent) ([]CrossNamespaceDependencyReference, error) { +// Sort sorts the Dependent slice based on their listed dependencies using Tarjan's strongly connected components +// algorithm. +func Sort(d []Dependent) ([]meta.NamespacedObjectReference, error) { g, l := buildGraph(d) sccs := tarjan.SCC(g) - var sorted []CrossNamespaceDependencyReference + var sorted []meta.NamespacedObjectReference var circular CircularDependencyError for i := 0; i < len(sccs); i++ { s := sccs[i] @@ -84,18 +67,22 @@ func Sort(d []Dependent) ([]CrossNamespaceDependencyReference, error) { return sorted, nil } -func buildGraph(d []Dependent) (tarjan.Graph, map[string]CrossNamespaceDependencyReference) { +func buildGraph(d []Dependent) (tarjan.Graph, map[string]meta.NamespacedObjectReference) { g := make(tarjan.Graph) - l := make(map[string]CrossNamespaceDependencyReference) + l := make(map[string]meta.NamespacedObjectReference) for i := 0; i < len(d); i++ { - name, deps := d[i].GetDependsOn() - g[name.String()] = buildEdges(deps, name.Namespace) - l[name.String()] = CrossNamespaceDependencyReference(name) + ref := meta.NamespacedObjectReference{ + Namespace: d[i].GetNamespace(), + Name: d[i].GetName(), + } + deps := d[i].GetDependsOn() + g[namespacedNameObjRef(ref)] = buildEdges(deps, ref.Namespace) + l[namespacedNameObjRef(ref)] = ref } return g, l } -func buildEdges(d []CrossNamespaceDependencyReference, defaultNamespace string) tarjan.Edges { +func buildEdges(d []meta.NamespacedObjectReference, defaultNamespace string) tarjan.Edges { if len(d) == 0 { return nil } @@ -104,7 +91,11 @@ func buildEdges(d []CrossNamespaceDependencyReference, defaultNamespace string) if v.Namespace == "" { v.Namespace = defaultNamespace } - e[v.String()] = struct{}{} + e[namespacedNameObjRef(v)] = struct{}{} } return e } + +func namespacedNameObjRef(ref meta.NamespacedObjectReference) string { + return ref.Namespace + string(types.Separator) + ref.Name +} diff --git a/runtime/dependency/sort_test.go b/runtime/dependency/sort_test.go index 84242649..2c323750 100644 --- a/runtime/dependency/sort_test.go +++ b/runtime/dependency/sort_test.go @@ -20,34 +20,38 @@ import ( "reflect" "testing" - "k8s.io/apimachinery/pkg/types" + "github.com/fluxcd/pkg/apis/meta" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type MockDependent struct { - NamespacedName types.NamespacedName - DependsOn []CrossNamespaceDependencyReference + corev1.Node + DependsOn []meta.NamespacedObjectReference } -func (d MockDependent) GetDependsOn() (types.NamespacedName, []CrossNamespaceDependencyReference) { - return d.NamespacedName, d.DependsOn +func (d MockDependent) GetDependsOn() []meta.NamespacedObjectReference { + return d.DependsOn } func TestDependencySort(t *testing.T) { tests := []struct { name string d []Dependent - want []CrossNamespaceDependencyReference + want []meta.NamespacedObjectReference wantErr bool }{ { "simple", []Dependent{ - MockDependent{ - NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: "frontend", + &MockDependent{ + Node: corev1.Node{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: "frontend", + }, }, - DependsOn: []CrossNamespaceDependencyReference{ + DependsOn: []meta.NamespacedObjectReference{ { Namespace: "linkerd", Name: "linkerd", @@ -58,18 +62,22 @@ func TestDependencySort(t *testing.T) { }, }, }, - MockDependent{ - NamespacedName: types.NamespacedName{ - Namespace: "linkerd", - Name: "linkerd", + &MockDependent{ + Node: corev1.Node{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "linkerd", + Name: "linkerd", + }, }, }, - MockDependent{ - NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: "backend", + &MockDependent{ + Node: corev1.Node{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: "backend", + }, }, - DependsOn: []CrossNamespaceDependencyReference{ + DependsOn: []meta.NamespacedObjectReference{ { Namespace: "linkerd", Name: "linkerd", @@ -77,7 +85,7 @@ func TestDependencySort(t *testing.T) { }, }, }, - []CrossNamespaceDependencyReference{ + []meta.NamespacedObjectReference{ { Namespace: "linkerd", Name: "linkerd", @@ -96,36 +104,42 @@ func TestDependencySort(t *testing.T) { { "circular dependency", []Dependent{ - MockDependent{ - NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: "dependency", + &MockDependent{ + Node: corev1.Node{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: "dependency", + }, }, - DependsOn: []CrossNamespaceDependencyReference{ + DependsOn: []meta.NamespacedObjectReference{ { Namespace: "default", Name: "endless", }, }, }, - MockDependent{ - NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: "endless", + &MockDependent{ + Node: corev1.Node{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: "endless", + }, }, - DependsOn: []CrossNamespaceDependencyReference{ + DependsOn: []meta.NamespacedObjectReference{ { Namespace: "default", Name: "circular", }, }, }, - MockDependent{ - NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: "circular", + &MockDependent{ + Node: corev1.Node{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: "circular", + }, }, - DependsOn: []CrossNamespaceDependencyReference{ + DependsOn: []meta.NamespacedObjectReference{ { Namespace: "default", Name: "dependency", @@ -139,25 +153,29 @@ func TestDependencySort(t *testing.T) { { "missing namespace", []Dependent{ - MockDependent{ - NamespacedName: types.NamespacedName{ - Namespace: "application", - Name: "backend", + &MockDependent{ + Node: corev1.Node{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "application", + Name: "backend", + }, }, }, - MockDependent{ - NamespacedName: types.NamespacedName{ - Namespace: "application", - Name: "frontend", + &MockDependent{ + Node: corev1.Node{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "application", + Name: "frontend", + }, }, - DependsOn: []CrossNamespaceDependencyReference{ + DependsOn: []meta.NamespacedObjectReference{ { Name: "backend", }, }, }, }, - []CrossNamespaceDependencyReference{ + []meta.NamespacedObjectReference{ { Namespace: "application", Name: "backend", @@ -186,34 +204,40 @@ func TestDependencySort(t *testing.T) { func TestDependencySort_DeadEnd(t *testing.T) { d := []Dependent{ - MockDependent{ - NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: "backend", + &MockDependent{ + Node: corev1.Node{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: "backend", + }, }, - DependsOn: []CrossNamespaceDependencyReference{ + DependsOn: []meta.NamespacedObjectReference{ { Namespace: "default", Name: "common", }, }, }, - MockDependent{ - NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: "frontend", + &MockDependent{ + Node: corev1.Node{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: "frontend", + }, }, - DependsOn: []CrossNamespaceDependencyReference{ + DependsOn: []meta.NamespacedObjectReference{ { Namespace: "default", Name: "infra", }, }, }, - MockDependent{ - NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: "common", + &MockDependent{ + Node: corev1.Node{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: "common", + }, }, }, }