From fad478f608a62b0f572ab0679ebcbe165c9d8ebf Mon Sep 17 00:00:00 2001
From: Angus Lees <gus@inodes.org>
Date: Thu, 18 May 2017 16:48:29 +1000
Subject: [PATCH] Attempt to topologically sort resources before updating

The goal is to make a best-effort attempt at reducing the number of
"crash-restart" loops required to bring up a group of interdependent
resources.

The current implementation is very simple and just sorts into 3 tiers:
- Namespace, ThirdPartyResource, StorageClass
- everything else
- Pods or similar (Pod/Job/Deployment/DaemonSet/StatefulSet)
---
 cmd/update.go      |  4 ++++
 utils/sort.go      | 59 ++++++++++++++++++++++++++++++++++++++++++++++
 utils/sort_test.go | 34 ++++++++++++++++++++++++++
 3 files changed, 97 insertions(+)
 create mode 100644 utils/sort.go
 create mode 100644 utils/sort_test.go

diff --git a/cmd/update.go b/cmd/update.go
index 8ffc485d..655afe59 100644
--- a/cmd/update.go
+++ b/cmd/update.go
@@ -10,6 +10,8 @@ import (
 	"k8s.io/client-go/pkg/api/errors"
 	"k8s.io/client-go/pkg/runtime"
 	"k8s.io/client-go/pkg/util/diff"
+
+	"github.com/ksonnet/kubecfg/utils"
 )
 
 const (
@@ -47,6 +49,8 @@ var updateCmd = &cobra.Command{
 			return err
 		}
 
+		utils.SortDepFirst(objs)
+
 		for _, obj := range objs {
 			desc := fmt.Sprintf("%s/%s", obj.GetKind(), fqName(obj))
 			glog.Info("Updating ", desc)
diff --git a/utils/sort.go b/utils/sort.go
new file mode 100644
index 00000000..554f4c24
--- /dev/null
+++ b/utils/sort.go
@@ -0,0 +1,59 @@
+package utils
+
+import (
+	"sort"
+
+	"k8s.io/client-go/pkg/api/unversioned"
+	"k8s.io/client-go/pkg/runtime"
+)
+
+var (
+	gkNamespace    = unversioned.GroupKind{Group: "", Kind: "Namespace"}
+	gkTpr          = unversioned.GroupKind{Group: "extensions", Kind: "ThirdPartyResource"}
+	gkStorageClass = unversioned.GroupKind{Group: "storage.k8s.io", Kind: "StorageClass"}
+
+	gkPod         = unversioned.GroupKind{Group: "", Kind: "Pod"}
+	gkJob         = unversioned.GroupKind{Group: "batch", Kind: "Job"}
+	gkDeployment  = unversioned.GroupKind{Group: "extensions", Kind: "Deployment"}
+	gkDaemonSet   = unversioned.GroupKind{Group: "extensions", Kind: "DaemonSet"}
+	gkStatefulSet = unversioned.GroupKind{Group: "apps", Kind: "StatefulSet"}
+)
+
+// These kinds all start pods.
+// TODO: expand this list.
+func isPodOrSimilar(gk unversioned.GroupKind) bool {
+	return gk == gkPod ||
+		gk == gkJob ||
+		gk == gkDeployment ||
+		gk == gkDaemonSet ||
+		gk == gkStatefulSet
+}
+
+// Arbitrary numbers used to do a simple topological sort of resources.
+// TODO: expand this list.
+func depTier(o *runtime.Unstructured) int {
+	gk := o.GroupVersionKind().GroupKind()
+	if gk == gkNamespace || gk == gkTpr || gk == gkStorageClass {
+		return 10
+	} else if isPodOrSimilar(gk) {
+		return 100
+	} else {
+		return 50
+	}
+}
+
+type dependentObjects []*runtime.Unstructured
+
+func (l dependentObjects) Len() int      { return len(l) }
+func (l dependentObjects) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
+func (l dependentObjects) Less(i, j int) bool {
+	return depTier(l[i]) < depTier(l[j])
+}
+
+// SortDepFirst *best-effort* sorts the objects so that known
+// dependencies appear earlier in the list.  The idea is to prevent
+// *some* of the "crash-restart" loops when creating inter-dependent
+// resources.
+func SortDepFirst(objs []*runtime.Unstructured) {
+	sort.Sort(dependentObjects(objs))
+}
diff --git a/utils/sort_test.go b/utils/sort_test.go
new file mode 100644
index 00000000..c579badc
--- /dev/null
+++ b/utils/sort_test.go
@@ -0,0 +1,34 @@
+package utils
+
+import (
+	"testing"
+
+	"k8s.io/client-go/pkg/runtime"
+)
+
+func newObj(apiVersion, kind string) *runtime.Unstructured {
+	return &runtime.Unstructured{
+		Object: map[string]interface{}{
+			"apiVersion": apiVersion,
+			"kind":       kind,
+		},
+	}
+}
+
+func TestSort(t *testing.T) {
+	objs := []*runtime.Unstructured{
+		newObj("extensions/v1beta1", "Deployment"),
+		newObj("v1", "ConfigMap"),
+		newObj("v1", "Namespace"),
+		newObj("v1", "Service"),
+	}
+
+	SortDepFirst(objs)
+
+	if objs[0].GetKind() != "Namespace" {
+		t.Error("Namespace should be sorted first")
+	}
+	if objs[3].GetKind() != "Deployment" {
+		t.Error("Deployment should be sorted after other objects")
+	}
+}
-- 
GitLab