...

Source file src/k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision/admission_test.go

Documentation: k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package autoprovision
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"testing"
    23  	"time"
    24  
    25  	corev1 "k8s.io/api/core/v1"
    26  	"k8s.io/apimachinery/pkg/api/errors"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/util/wait"
    30  	"k8s.io/apiserver/pkg/admission"
    31  	genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
    32  	admissiontesting "k8s.io/apiserver/pkg/admission/testing"
    33  	"k8s.io/client-go/informers"
    34  	clientset "k8s.io/client-go/kubernetes"
    35  	"k8s.io/client-go/kubernetes/fake"
    36  	core "k8s.io/client-go/testing"
    37  	api "k8s.io/kubernetes/pkg/apis/core"
    38  )
    39  
    40  // newHandlerForTest returns the admission controller configured for testing.
    41  func newHandlerForTest(c clientset.Interface) (admission.MutationInterface, informers.SharedInformerFactory, error) {
    42  	f := informers.NewSharedInformerFactory(c, 5*time.Minute)
    43  	handler := NewProvision()
    44  	pluginInitializer := genericadmissioninitializer.New(c, nil, f, nil, nil, nil, nil)
    45  	pluginInitializer.Initialize(handler)
    46  	err := admission.ValidateInitialization(handler)
    47  	return handler, f, err
    48  }
    49  
    50  // newMockClientForTest creates a mock client that returns a client configured for the specified list of namespaces.
    51  func newMockClientForTest(namespaces []string) *fake.Clientset {
    52  	mockClient := &fake.Clientset{}
    53  	mockClient.AddReactor("list", "namespaces", func(action core.Action) (bool, runtime.Object, error) {
    54  		namespaceList := &corev1.NamespaceList{
    55  			ListMeta: metav1.ListMeta{
    56  				ResourceVersion: fmt.Sprintf("%d", len(namespaces)),
    57  			},
    58  		}
    59  		for i, ns := range namespaces {
    60  			namespaceList.Items = append(namespaceList.Items, corev1.Namespace{
    61  				ObjectMeta: metav1.ObjectMeta{
    62  					Name:            ns,
    63  					ResourceVersion: fmt.Sprintf("%d", i),
    64  				},
    65  			})
    66  		}
    67  		return true, namespaceList, nil
    68  	})
    69  	return mockClient
    70  }
    71  
    72  // newPod returns a new pod for the specified namespace
    73  func newPod(namespace string) api.Pod {
    74  	return api.Pod{
    75  		ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: namespace},
    76  		Spec: api.PodSpec{
    77  			Volumes:    []api.Volume{{Name: "vol"}},
    78  			Containers: []api.Container{{Name: "ctr", Image: "image"}},
    79  		},
    80  	}
    81  }
    82  
    83  // hasCreateNamespaceAction returns true if it has the create namespace action
    84  func hasCreateNamespaceAction(mockClient *fake.Clientset) bool {
    85  	for _, action := range mockClient.Actions() {
    86  		if action.GetVerb() == "create" && action.GetResource().Resource == "namespaces" {
    87  			return true
    88  		}
    89  	}
    90  	return false
    91  }
    92  
    93  // TestAdmission verifies a namespace is created on create requests for namespace managed resources
    94  func TestAdmission(t *testing.T) {
    95  	namespace := "test"
    96  	mockClient := newMockClientForTest([]string{})
    97  	handler, informerFactory, err := newHandlerForTest(mockClient)
    98  	if err != nil {
    99  		t.Errorf("unexpected error initializing handler: %v", err)
   100  	}
   101  	informerFactory.Start(wait.NeverStop)
   102  
   103  	pod := newPod(namespace)
   104  	err = admissiontesting.WithReinvocationTesting(t, handler).Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil)
   105  	if err != nil {
   106  		t.Errorf("unexpected error returned from admission handler")
   107  	}
   108  	if !hasCreateNamespaceAction(mockClient) {
   109  		t.Errorf("expected create namespace action")
   110  	}
   111  }
   112  
   113  // TestAdmissionNamespaceExists verifies that no client call is made when a namespace already exists
   114  func TestAdmissionNamespaceExists(t *testing.T) {
   115  	namespace := "test"
   116  	mockClient := newMockClientForTest([]string{namespace})
   117  	handler, informerFactory, err := newHandlerForTest(mockClient)
   118  	if err != nil {
   119  		t.Errorf("unexpected error initializing handler: %v", err)
   120  	}
   121  	informerFactory.Start(wait.NeverStop)
   122  
   123  	pod := newPod(namespace)
   124  	err = admissiontesting.WithReinvocationTesting(t, handler).Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil)
   125  	if err != nil {
   126  		t.Errorf("unexpected error returned from admission handler")
   127  	}
   128  	if hasCreateNamespaceAction(mockClient) {
   129  		t.Errorf("unexpected create namespace action")
   130  	}
   131  }
   132  
   133  // TestAdmissionDryRun verifies that no client call is made on a dry run request
   134  func TestAdmissionDryRun(t *testing.T) {
   135  	namespace := "test"
   136  	mockClient := newMockClientForTest([]string{})
   137  	handler, informerFactory, err := newHandlerForTest(mockClient)
   138  	if err != nil {
   139  		t.Errorf("unexpected error initializing handler: %v", err)
   140  	}
   141  	informerFactory.Start(wait.NeverStop)
   142  
   143  	pod := newPod(namespace)
   144  	err = admissiontesting.WithReinvocationTesting(t, handler).Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, true, nil), nil)
   145  	if err != nil {
   146  		t.Errorf("unexpected error returned from admission handler")
   147  	}
   148  	if hasCreateNamespaceAction(mockClient) {
   149  		t.Errorf("unexpected create namespace action")
   150  	}
   151  }
   152  
   153  // TestIgnoreAdmission validates that a request is ignored if its not a create
   154  func TestIgnoreAdmission(t *testing.T) {
   155  	namespace := "test"
   156  	mockClient := newMockClientForTest([]string{})
   157  	handler, informerFactory, err := newHandlerForTest(mockClient)
   158  	if err != nil {
   159  		t.Errorf("unexpected error initializing handler: %v", err)
   160  	}
   161  	informerFactory.Start(wait.NeverStop)
   162  	chainHandler := admissiontesting.WithReinvocationTesting(t, admission.NewChainHandler(handler))
   163  
   164  	pod := newPod(namespace)
   165  	err = admissiontesting.WithReinvocationTesting(t, chainHandler).Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil), nil)
   166  	if err != nil {
   167  		t.Errorf("unexpected error returned from admission handler")
   168  	}
   169  	if hasCreateNamespaceAction(mockClient) {
   170  		t.Errorf("unexpected create namespace action")
   171  	}
   172  }
   173  
   174  func TestAdmissionWithLatentCache(t *testing.T) {
   175  	namespace := "test"
   176  	mockClient := newMockClientForTest([]string{})
   177  	mockClient.AddReactor("create", "namespaces", func(action core.Action) (bool, runtime.Object, error) {
   178  		return true, nil, errors.NewAlreadyExists(api.Resource("namespaces"), namespace)
   179  	})
   180  	handler, informerFactory, err := newHandlerForTest(mockClient)
   181  	if err != nil {
   182  		t.Errorf("unexpected error initializing handler: %v", err)
   183  	}
   184  	informerFactory.Start(wait.NeverStop)
   185  
   186  	pod := newPod(namespace)
   187  	err = admissiontesting.WithReinvocationTesting(t, handler).Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil)
   188  	if err != nil {
   189  		t.Errorf("unexpected error returned from admission handler")
   190  	}
   191  
   192  	if !hasCreateNamespaceAction(mockClient) {
   193  		t.Errorf("expected create namespace action")
   194  	}
   195  }
   196  

View as plain text