From 7b6205e900ff64fdb40095ce7362ea542b05dd1e Mon Sep 17 00:00:00 2001 From: Malte Poll Date: Tue, 28 Jun 2022 11:53:10 +0200 Subject: [PATCH] [node operator] node image util functions Signed-off-by: Malte Poll --- operators/constellation-node-operator/go.sum | 10 + .../internal/node/node.go | 43 ++++- .../internal/node/node_test.go | 171 ++++++++++++++++++ 3 files changed, 219 insertions(+), 5 deletions(-) create mode 100644 operators/constellation-node-operator/internal/node/node_test.go diff --git a/operators/constellation-node-operator/go.sum b/operators/constellation-node-operator/go.sum index 4389a280c..2921e510b 100644 --- a/operators/constellation-node-operator/go.sum +++ b/operators/constellation-node-operator/go.sum @@ -170,6 +170,7 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk= @@ -346,7 +347,10 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -401,6 +405,7 @@ github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGV github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -746,6 +751,7 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -998,6 +1004,8 @@ k8s.io/component-base v0.24.0 h1:h5jieHZQoHrY/lHG+HyrSbJeyfuitheBvqvKwKHVC0g= k8s.io/component-base v0.24.0/go.mod h1:Dgazgon0i7KYUsS8krG8muGiMVtUZxG037l1MKyXgrA= k8s.io/component-helpers v0.24.0/go.mod h1:Q2SlLm4h6g6lPTC9GMMfzdywfLSvJT2f1hOnnjaWD8c= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= @@ -1007,11 +1015,13 @@ k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc= k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 h1:Gii5eqf+GmIEwGNKQYQClCayuJCe2/4fZUvF7VG99sU= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= k8s.io/kubectl v0.24.0/go.mod h1:pdXkmCyHiRTqjYfyUJiXtbVNURhv0/Q1TyRhy2d5ic0= k8s.io/metrics v0.24.0/go.mod h1:jrLlFGdKl3X+szubOXPG0Lf2aVxuV3QJcbsgVRAM6fI= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/operators/constellation-node-operator/internal/node/node.go b/operators/constellation-node-operator/internal/node/node.go index ac3925ddd..138dd74f6 100644 --- a/operators/constellation-node-operator/internal/node/node.go +++ b/operators/constellation-node-operator/internal/node/node.go @@ -1,15 +1,48 @@ package node -import corev1 "k8s.io/api/core/v1" +import ( + "regexp" + updatev1alpha1 "github.com/edgelesssys/constellation/operators/constellation-node-operator/api/v1alpha1" + corev1 "k8s.io/api/core/v1" +) + +var reservedHostRegex = regexp.MustCompile(`^(.+\.|)(kubernetes|k8s)\.io(/.*)?$`) + +// Ready checks if a kubernetes node has the `NodeReady` condition set to true. func Ready(node *corev1.Node) bool { for _, cond := range node.Status.Conditions { if cond.Type == corev1.NodeReady { - if cond.Status == corev1.ConditionTrue { - return true - } - return false + return cond.Status == corev1.ConditionTrue } } return false } + +// FindPending searches for a pending node that matches a node. +// The pending node has to have the goal to join the cluster and be reported as ready be the CSP. +// if the node is not found, nil is returned. +func FindPending(pendingNodes []updatev1alpha1.PendingNode, node *corev1.Node) *updatev1alpha1.PendingNode { + if node == nil { + return nil + } + for _, pendingNode := range pendingNodes { + if pendingNode.Spec.Goal == updatev1alpha1.NodeGoalJoin && pendingNode.Spec.NodeName == node.Name && pendingNode.Status.CSPNodeState == updatev1alpha1.NodeStateReady { + return &pendingNode + } + } + return nil +} + +// FilterLabels removes reserved node labels from a map of labels. +// reference: https://kubernetes.io/docs/reference/labels-annotations-taints/ . +func FilterLabels(labels map[string]string) map[string]string { + result := make(map[string]string) + for key, val := range labels { + if reservedHostRegex.MatchString(key) { + continue + } + result[key] = val + } + return result +} diff --git a/operators/constellation-node-operator/internal/node/node_test.go b/operators/constellation-node-operator/internal/node/node_test.go new file mode 100644 index 000000000..b3619f887 --- /dev/null +++ b/operators/constellation-node-operator/internal/node/node_test.go @@ -0,0 +1,171 @@ +package node + +import ( + "testing" + + updatev1alpha1 "github.com/edgelesssys/constellation/operators/constellation-node-operator/api/v1alpha1" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestReady(t *testing.T) { + testCases := map[string]struct { + node corev1.Node + wantReady bool + }{ + "node without status conditions": {}, + "node with NodeReady set to false": { + node: corev1.Node{ + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + {Type: corev1.NodeReady, Status: corev1.ConditionFalse}, + }, + }, + }, + }, + "node with NodeReady set to unknown": { + node: corev1.Node{ + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + {Type: corev1.NodeReady, Status: corev1.ConditionUnknown}, + }, + }, + }, + }, + "node with NodeReady set to true": { + node: corev1.Node{ + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + {Type: corev1.NodeReady, Status: corev1.ConditionTrue}, + }, + }, + }, + wantReady: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + assert.Equal(tc.wantReady, Ready(&tc.node)) + }) + } +} + +func TestFindPending(t *testing.T) { + testCases := map[string]struct { + pendingNodes []updatev1alpha1.PendingNode + node *corev1.Node + wantPending *updatev1alpha1.PendingNode + }{ + "everything nil": {}, + "node nil": { + pendingNodes: pendingNodes, + }, + "node is not in pending nodes list": { + pendingNodes: pendingNodes, + node: &corev1.Node{ + ObjectMeta: v1.ObjectMeta{ + Name: "doesnotexist", + }, + }, + }, + "pending node is leaving": { + pendingNodes: pendingNodes, + node: &corev1.Node{ + ObjectMeta: v1.ObjectMeta{ + Name: "leavingnode", + }, + }, + }, + "pending node is not ready": { + pendingNodes: pendingNodes, + node: &corev1.Node{ + ObjectMeta: v1.ObjectMeta{ + Name: "unreadynode", + }, + }, + }, + "pending node is found": { + pendingNodes: pendingNodes, + node: &corev1.Node{ + ObjectMeta: v1.ObjectMeta{ + Name: "joiningnode", + }, + }, + wantPending: &pendingNodes[0], + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + pending := FindPending(tc.pendingNodes, tc.node) + if tc.wantPending == nil { + assert.Nil(pending) + return + } + assert.Equal(*tc.wantPending, *pending) + }) + } +} + +func TestFilterLabels(t *testing.T) { + labels := map[string]string{ + "key": "value", + "app.kubernetes.io/component": "component", + "app.kubernetes.io/created-by": "created-by", + "app.kubernetes.io/instance": "instance", + "app.kubernetes.io/managed-by": "managed-by", + "app.kubernetes.io/name": "name", + "app.kubernetes.io/part-of": "part-of", + "app.kubernetes.io/version": "version", + "kubernetes.io/arch": "arch", + "kubernetes.io/os": "os", + "beta.kubernetes.io/arch": "arch", + "beta.kubernetes.io/os": "os", + "kubernetes.io/hostname": "hostname", + "kubernetes.io/change-cause": "change-cause", + "kubernetes.io/description": "description", + "node.kubernetes.io/instance-type": "instance-type", + "failure-domain.beta.kubernetes.io/region": "region", + "failure-domain.beta.kubernetes.io/zone": "zone", + "topology.kubernetes.io/region": "region", + "topology.kubernetes.io/zone": "zone", + "node.kubernetes.io/windows-build": "windows-build", + "node-role.kubernetes.io/control-plane": "control-plane", + } + wantFiltered := map[string]string{ + "key": "value", + } + assert := assert.New(t) + assert.Equal(wantFiltered, FilterLabels(labels)) +} + +var pendingNodes = []updatev1alpha1.PendingNode{ + { + Spec: updatev1alpha1.PendingNodeSpec{ + NodeName: "joiningnode", + Goal: updatev1alpha1.NodeGoalJoin, + }, + Status: updatev1alpha1.PendingNodeStatus{ + CSPNodeState: updatev1alpha1.NodeStateReady, + }, + }, + { + Spec: updatev1alpha1.PendingNodeSpec{ + NodeName: "unreadynode", + Goal: updatev1alpha1.NodeGoalJoin, + }, + Status: updatev1alpha1.PendingNodeStatus{ + CSPNodeState: updatev1alpha1.NodeStateCreating, + }, + }, + { + Spec: updatev1alpha1.PendingNodeSpec{ + NodeName: "leavingnode", + Goal: updatev1alpha1.NodeGoalLeave, + }, + }, +}