From 5bb2cb82490c970260ec75fe4f5395574552b305 Mon Sep 17 00:00:00 2001 From: Avesh Agarwal Date: Fri, 30 Sep 2016 16:58:38 -0400 Subject: [PATCH] Added new unit tests. --- pkg/labels/labels_test.go | 171 ++++++++++++++++ .../podnodeselector/admission_test.go | 188 ++++++++++++++++++ 2 files changed, 359 insertions(+) create mode 100644 plugin/pkg/admission/podnodeselector/admission_test.go diff --git a/pkg/labels/labels_test.go b/pkg/labels/labels_test.go index a9f19309880..2d4d761bc2b 100644 --- a/pkg/labels/labels_test.go +++ b/pkg/labels/labels_test.go @@ -58,3 +58,174 @@ func TestLabelGet(t *testing.T) { t.Errorf("Set.Get is broken") } } + +func TestLabelConflict(t *testing.T) { + tests := []struct { + labels1 map[string]string + labels2 map[string]string + conflict bool + }{ + { + labels1: map[string]string{}, + labels2: map[string]string{}, + conflict: false, + }, + { + labels1: map[string]string{"env": "test"}, + labels2: map[string]string{"infra": "true"}, + conflict: false, + }, + { + labels1: map[string]string{"env": "test"}, + labels2: map[string]string{"infra": "true", "env": "test"}, + conflict: false, + }, + { + labels1: map[string]string{"env": "test"}, + labels2: map[string]string{"env": "dev"}, + conflict: true, + }, + { + labels1: map[string]string{"env": "test", "infra": "false"}, + labels2: map[string]string{"infra": "true", "color": "blue"}, + conflict: true, + }, + } + for _, test := range tests { + conflict := Conflicts(Set(test.labels1), Set(test.labels2)) + if conflict != test.conflict { + t.Errorf("expected: %v but got: %v", test.conflict, conflict) + } + } +} + +func TestLabelMerge(t *testing.T) { + tests := []struct { + labels1 map[string]string + labels2 map[string]string + mergedLabels map[string]string + }{ + { + labels1: map[string]string{}, + labels2: map[string]string{}, + mergedLabels: map[string]string{}, + }, + { + labels1: map[string]string{"infra": "true"}, + labels2: map[string]string{}, + mergedLabels: map[string]string{"infra": "true"}, + }, + { + labels1: map[string]string{"infra": "true"}, + labels2: map[string]string{"env": "test", "color": "blue"}, + mergedLabels: map[string]string{"infra": "true", "env": "test", "color": "blue"}, + }, + } + for _, test := range tests { + mergedLabels := Merge(Set(test.labels1), Set(test.labels2)) + if !Equals(mergedLabels, test.mergedLabels) { + t.Errorf("expected: %v but got: %v", test.mergedLabels, mergedLabels) + } + } +} + +func TestLabelSelectorParse(t *testing.T) { + tests := []struct { + selector string + labels map[string]string + valid bool + }{ + { + selector: "", + labels: map[string]string{}, + valid: true, + }, + { + selector: "x=a", + labels: map[string]string{"x": "a"}, + valid: true, + }, + { + selector: "x=a,y=b,z=c", + labels: map[string]string{"x": "a", "y": "b", "z": "c"}, + valid: true, + }, + { + selector: " x = a , y = b , z = c ", + labels: map[string]string{"x": "a", "y": "b", "z": "c"}, + valid: true, + }, + { + selector: "color=green,env=test,service=front", + labels: map[string]string{"color": "green", "env": "test", "service": "front"}, + valid: true, + }, + { + selector: "color=green, env=test, service=front", + labels: map[string]string{"color": "green", "env": "test", "service": "front"}, + valid: true, + }, + { + selector: ",", + labels: map[string]string{}, + valid: false, + }, + { + selector: "x", + labels: map[string]string{}, + valid: false, + }, + { + selector: "x,y", + labels: map[string]string{}, + valid: false, + }, + { + selector: "x=$y", + labels: map[string]string{}, + valid: false, + }, + { + selector: "x!=y", + labels: map[string]string{}, + valid: false, + }, + { + selector: "x==y", + labels: map[string]string{}, + valid: false, + }, + { + selector: "x=a||y=b", + labels: map[string]string{}, + valid: false, + }, + { + selector: "x in (y)", + labels: map[string]string{}, + valid: false, + }, + { + selector: "x notin (y)", + labels: map[string]string{}, + valid: false, + }, + { + selector: "x y", + labels: map[string]string{}, + valid: false, + }, + } + for _, test := range tests { + labels, err := ConvertSelectorToLabelsMap(test.selector) + if test.valid && err != nil { + t.Errorf("selector: %s, expected no error but got: %s", test.selector, err) + } else if !test.valid && err == nil { + t.Errorf("selector: %s, expected an error", test.selector) + } + + if !Equals(Set(labels), test.labels) { + t.Errorf("expected: %s but got: %s", test.labels, labels) + } + } +} diff --git a/plugin/pkg/admission/podnodeselector/admission_test.go b/plugin/pkg/admission/podnodeselector/admission_test.go new file mode 100644 index 00000000000..dee7fd39533 --- /dev/null +++ b/plugin/pkg/admission/podnodeselector/admission_test.go @@ -0,0 +1,188 @@ +/* +Copyright 2016 The Kubernetes 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 podnodeselector + +import ( + "testing" + "time" + + "k8s.io/kubernetes/pkg/admission" + "k8s.io/kubernetes/pkg/api" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/pkg/controller/informers" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/util/wait" +) + +// TestPodAdmission verifies various scenarios involving pod/namespace/global node label selectors +func TestPodAdmission(t *testing.T) { + namespace := &api.Namespace{ + ObjectMeta: api.ObjectMeta{ + Name: "testNamespace", + Namespace: "", + }, + } + + mockClient := &fake.Clientset{} + handler, informerFactory, err := newHandlerForTest(mockClient) + if err != nil { + t.Errorf("unexpected error initializing handler: %v", err) + } + informerFactory.Start(wait.NeverStop) + + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "testPod", Namespace: "testNamespace"}, + } + + tests := []struct { + defaultNodeSelector string + namespaceNodeSelector string + whitelist string + podNodeSelector map[string]string + mergedNodeSelector labels.Set + ignoreTestNamespaceNodeSelector bool + admit bool + testName string + }{ + { + defaultNodeSelector: "", + podNodeSelector: map[string]string{}, + mergedNodeSelector: labels.Set{}, + ignoreTestNamespaceNodeSelector: true, + admit: true, + testName: "No node selectors", + }, + { + defaultNodeSelector: "infra = false", + podNodeSelector: map[string]string{}, + mergedNodeSelector: labels.Set{"infra": "false"}, + ignoreTestNamespaceNodeSelector: true, + admit: true, + testName: "Default node selector and no conflicts", + }, + { + defaultNodeSelector: "", + namespaceNodeSelector: " infra = false ", + podNodeSelector: map[string]string{}, + mergedNodeSelector: labels.Set{"infra": "false"}, + admit: true, + testName: "TestNamespace node selector with whitespaces and no conflicts", + }, + { + defaultNodeSelector: "infra = false", + namespaceNodeSelector: "infra=true", + podNodeSelector: map[string]string{}, + mergedNodeSelector: labels.Set{"infra": "true"}, + admit: true, + testName: "Default and namespace node selector, no conflicts", + }, + { + defaultNodeSelector: "infra = false", + namespaceNodeSelector: "", + podNodeSelector: map[string]string{}, + mergedNodeSelector: labels.Set{}, + admit: true, + testName: "Empty namespace node selector and no conflicts", + }, + { + defaultNodeSelector: "infra = false", + namespaceNodeSelector: "infra=true", + podNodeSelector: map[string]string{"env": "test"}, + mergedNodeSelector: labels.Set{"infra": "true", "env": "test"}, + admit: true, + testName: "TestNamespace and pod node selector, no conflicts", + }, + { + defaultNodeSelector: "env = test", + namespaceNodeSelector: "infra=true", + podNodeSelector: map[string]string{"infra": "false"}, + admit: false, + testName: "Conflicting pod and namespace node selector, one label", + }, + { + defaultNodeSelector: "env=dev", + namespaceNodeSelector: "infra=false, env = test", + podNodeSelector: map[string]string{"env": "dev", "color": "blue"}, + admit: false, + testName: "Conflicting pod and namespace node selector, multiple labels", + }, + { + defaultNodeSelector: "env=dev", + namespaceNodeSelector: "infra=false, env = dev", + whitelist: "env=dev, infra=false, color=blue", + podNodeSelector: map[string]string{"env": "dev", "color": "blue"}, + mergedNodeSelector: labels.Set{"infra": "false", "env": "dev", "color": "blue"}, + admit: true, + testName: "Merged pod node selectors satisfy the whitelist", + }, + { + defaultNodeSelector: "env=dev", + namespaceNodeSelector: "infra=false, env = dev", + whitelist: "env=dev, infra=true, color=blue", + podNodeSelector: map[string]string{"env": "dev", "color": "blue"}, + admit: false, + testName: "Merged pod node selectors conflict with the whitelist", + }, + } + for _, test := range tests { + if !test.ignoreTestNamespaceNodeSelector { + namespace.ObjectMeta.Annotations = map[string]string{"scheduler.alpha.kubernetes.io/node-selector": test.namespaceNodeSelector} + handler.namespaceInformer.GetStore().Update(namespace) + } + handler.clusterNodeSelectors = make(map[string]string) + handler.clusterNodeSelectors["clusterDefaultNodeSelector"] = test.defaultNodeSelector + handler.clusterNodeSelectors[namespace.Name] = test.whitelist + pod.Spec = api.PodSpec{NodeSelector: test.podNodeSelector} + + err := handler.Admit(admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) + if test.admit && err != nil { + t.Errorf("Test: %s, expected no error but got: %s", test.testName, err) + } else if !test.admit && err == nil { + t.Errorf("Test: %s, expected an error", test.testName) + } + + if test.admit && !labels.Equals(test.mergedNodeSelector, labels.Set(pod.Spec.NodeSelector)) { + t.Errorf("Test: %s, expected: %s but got: %s", test.testName, test.mergedNodeSelector, pod.Spec.NodeSelector) + } + } +} + +func TestHandles(t *testing.T) { + for op, shouldHandle := range map[admission.Operation]bool{ + admission.Create: true, + admission.Update: false, + admission.Connect: false, + admission.Delete: false, + } { + nodeEnvionment := NewPodNodeSelector(nil, nil) + if e, a := shouldHandle, nodeEnvionment.Handles(op); e != a { + t.Errorf("%v: shouldHandle=%t, handles=%t", op, e, a) + } + } +} + +// newHandlerForTest returns the admission controller configured for testing. +func newHandlerForTest(c clientset.Interface) (*podNodeSelector, informers.SharedInformerFactory, error) { + f := informers.NewSharedInformerFactory(c, 5*time.Minute) + handler := NewPodNodeSelector(c, nil) + plugins := []admission.Interface{handler} + pluginInitializer := admission.NewPluginInitializer(f, nil) + pluginInitializer.Initialize(plugins) + err := admission.Validate(plugins) + return handler, f, err +}