kubernetes/plugin/pkg/admission/podnodeselector/admission_test.go
Kubernetes Submit Queue 3b9eb1a875 Merge pull request #43876 from caesarxuchao/blockOwnerDeletion-admission
Automatic merge from submit-queue (batch tested with PRs 44440, 44038, 44302, 44316, 43876)

Extend the gc admission plugin to check ownerReference.blockOwnerDeletion

#Extend the gc admission plugin to prevent user who doesn't have delete permission of the *owner* from changing blockOwnerDeletion field of existing ownerReferences, or adding ownerReference with blockOwnerDeletion=true.

The plugin need a RESTMapper to translate ownerRef.Kind to Resource. It should be using a dynamic one. However, as discussed in https://github.com/kubernetes/kubernetes/pull/42615, such a RESTMapper will be built after watchable discovery API is implemented, so in this PR the plugin is using the `api.Registry.RESTMapper()`, which is also [used](https://github.com/kubernetes/kubernetes/blob/master/cmd/kube-controller-manager/app/core.go#L165-L166) by the garbage collector currently.

```release-note
Extending the gc admission plugin so that a user who doesn't have delete permission of the *owner* cannot modify blockOwnerDeletion field of existing ownerReferences, or add new ownerReference with blockOwnerDeletion=true
```

cc @lavalamp
2017-04-13 23:18:06 -07:00

199 lines
7.4 KiB
Go

/*
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"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apiserver/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"
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
kubeadmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
)
// TestPodAdmission verifies various scenarios involving pod/namespace/global node label selectors
func TestPodAdmission(t *testing.T) {
namespace := &api.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "testNamespace",
Namespace: "",
},
}
mockClient := &fake.Clientset{}
handler, informerFactory, err := newHandlerForTest(mockClient)
if err != nil {
t.Errorf("unexpected error initializing handler: %v", err)
}
stopCh := make(chan struct{})
defer close(stopCh)
informerFactory.Start(stopCh)
pod := &api.Pod{
ObjectMeta: metav1.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",
},
{
defaultNodeSelector: "env=dev",
ignoreTestNamespaceNodeSelector: true,
whitelist: "env=prd",
podNodeSelector: map[string]string{},
admit: false,
testName: "Default node selector 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}
informerFactory.Core().InternalVersion().Namespaces().Informer().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)
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(nil)
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil)
pluginInitializer.Initialize(handler)
err := admission.Validate(handler)
return handler, f, err
}