...

Source file src/k8s.io/kubernetes/pkg/controller/volume/ephemeral/controller_test.go

Documentation: k8s.io/kubernetes/pkg/controller/volume/ephemeral

     1  /*
     2  Copyright 2020 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 ephemeral
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"sort"
    23  	"testing"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  	v1 "k8s.io/api/core/v1"
    27  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"k8s.io/apimachinery/pkg/types"
    31  	"k8s.io/client-go/informers"
    32  	"k8s.io/client-go/kubernetes/fake"
    33  	k8stesting "k8s.io/client-go/testing"
    34  	"k8s.io/client-go/tools/cache"
    35  	"k8s.io/component-base/metrics/testutil"
    36  	"k8s.io/klog/v2"
    37  	"k8s.io/kubernetes/pkg/controller"
    38  	ephemeralvolumemetrics "k8s.io/kubernetes/pkg/controller/volume/ephemeral/metrics"
    39  )
    40  
    41  var (
    42  	testPodName         = "test-pod"
    43  	testNamespace       = "my-namespace"
    44  	testPodUID          = types.UID("uidpod1")
    45  	otherNamespace      = "not-my-namespace"
    46  	ephemeralVolumeName = "ephemeral-volume"
    47  
    48  	testPod               = makePod(testPodName, testNamespace, testPodUID)
    49  	testPodWithEphemeral  = makePod(testPodName, testNamespace, testPodUID, *makeEphemeralVolume(ephemeralVolumeName))
    50  	testPodEphemeralClaim = makePVC(testPodName+"-"+ephemeralVolumeName, testNamespace, makeOwnerReference(testPodWithEphemeral, true))
    51  	conflictingClaim      = makePVC(testPodName+"-"+ephemeralVolumeName, testNamespace, nil)
    52  	otherNamespaceClaim   = makePVC(testPodName+"-"+ephemeralVolumeName, otherNamespace, nil)
    53  )
    54  
    55  func init() {
    56  	klog.InitFlags(nil)
    57  }
    58  
    59  func TestSyncHandler(t *testing.T) {
    60  	tests := []struct {
    61  		name            string
    62  		podKey          string
    63  		pvcs            []*v1.PersistentVolumeClaim
    64  		pods            []*v1.Pod
    65  		expectedPVCs    []v1.PersistentVolumeClaim
    66  		expectedError   bool
    67  		expectedMetrics expectedMetrics
    68  	}{
    69  		{
    70  			name:            "create",
    71  			pods:            []*v1.Pod{testPodWithEphemeral},
    72  			podKey:          podKey(testPodWithEphemeral),
    73  			expectedPVCs:    []v1.PersistentVolumeClaim{*testPodEphemeralClaim},
    74  			expectedMetrics: expectedMetrics{1, 0},
    75  		},
    76  		{
    77  			name:   "no-such-pod",
    78  			podKey: podKey(testPodWithEphemeral),
    79  		},
    80  		{
    81  			name: "pod-deleted",
    82  			pods: func() []*v1.Pod {
    83  				deleted := metav1.Now()
    84  				pods := []*v1.Pod{testPodWithEphemeral.DeepCopy()}
    85  				pods[0].DeletionTimestamp = &deleted
    86  				return pods
    87  			}(),
    88  			podKey: podKey(testPodWithEphemeral),
    89  		},
    90  		{
    91  			name:   "no-volumes",
    92  			pods:   []*v1.Pod{testPod},
    93  			podKey: podKey(testPod),
    94  		},
    95  		{
    96  			name:            "create-with-other-PVC",
    97  			pods:            []*v1.Pod{testPodWithEphemeral},
    98  			podKey:          podKey(testPodWithEphemeral),
    99  			pvcs:            []*v1.PersistentVolumeClaim{otherNamespaceClaim},
   100  			expectedPVCs:    []v1.PersistentVolumeClaim{*otherNamespaceClaim, *testPodEphemeralClaim},
   101  			expectedMetrics: expectedMetrics{1, 0},
   102  		},
   103  		{
   104  			name:          "wrong-PVC-owner",
   105  			pods:          []*v1.Pod{testPodWithEphemeral},
   106  			podKey:        podKey(testPodWithEphemeral),
   107  			pvcs:          []*v1.PersistentVolumeClaim{conflictingClaim},
   108  			expectedPVCs:  []v1.PersistentVolumeClaim{*conflictingClaim},
   109  			expectedError: true,
   110  		},
   111  		{
   112  			name:            "create-conflict",
   113  			pods:            []*v1.Pod{testPodWithEphemeral},
   114  			podKey:          podKey(testPodWithEphemeral),
   115  			expectedMetrics: expectedMetrics{1, 1},
   116  			expectedError:   true,
   117  		},
   118  	}
   119  
   120  	for _, tc := range tests {
   121  		// Run sequentially because of global logging and global metrics.
   122  		t.Run(tc.name, func(t *testing.T) {
   123  			// There is no good way to shut down the informers. They spawn
   124  			// various goroutines and some of them (in particular shared informer)
   125  			// become very unhappy ("close on closed channel") when using a context
   126  			// that gets cancelled. Therefore we just keep everything running.
   127  			ctx := context.Background()
   128  
   129  			var objects []runtime.Object
   130  			for _, pod := range tc.pods {
   131  				objects = append(objects, pod)
   132  			}
   133  			for _, pvc := range tc.pvcs {
   134  				objects = append(objects, pvc)
   135  			}
   136  
   137  			fakeKubeClient := createTestClient(objects...)
   138  			if tc.expectedMetrics.numFailures > 0 {
   139  				fakeKubeClient.PrependReactor("create", "persistentvolumeclaims", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
   140  					return true, nil, apierrors.NewConflict(action.GetResource().GroupResource(), "fake name", errors.New("fake conflict"))
   141  				})
   142  			}
   143  			setupMetrics()
   144  			informerFactory := informers.NewSharedInformerFactory(fakeKubeClient, controller.NoResyncPeriodFunc())
   145  			podInformer := informerFactory.Core().V1().Pods()
   146  			pvcInformer := informerFactory.Core().V1().PersistentVolumeClaims()
   147  
   148  			c, err := NewController(ctx, fakeKubeClient, podInformer, pvcInformer)
   149  			if err != nil {
   150  				t.Fatalf("error creating ephemeral controller : %v", err)
   151  			}
   152  			ec, _ := c.(*ephemeralController)
   153  
   154  			// Ensure informers are up-to-date.
   155  			go informerFactory.Start(ctx.Done())
   156  			informerFactory.WaitForCacheSync(ctx.Done())
   157  			cache.WaitForCacheSync(ctx.Done(), podInformer.Informer().HasSynced, pvcInformer.Informer().HasSynced)
   158  
   159  			err = ec.syncHandler(context.TODO(), tc.podKey)
   160  			if err != nil && !tc.expectedError {
   161  				t.Fatalf("unexpected error while running handler: %v", err)
   162  			}
   163  			if err == nil && tc.expectedError {
   164  				t.Fatalf("unexpected success")
   165  			}
   166  
   167  			pvcs, err := fakeKubeClient.CoreV1().PersistentVolumeClaims("").List(ctx, metav1.ListOptions{})
   168  			if err != nil {
   169  				t.Fatalf("unexpected error while listing PVCs: %v", err)
   170  			}
   171  			assert.Equal(t, sortPVCs(tc.expectedPVCs), sortPVCs(pvcs.Items))
   172  			expectMetrics(t, tc.expectedMetrics)
   173  		})
   174  	}
   175  }
   176  
   177  func makePVC(name, namespace string, owner *metav1.OwnerReference) *v1.PersistentVolumeClaim {
   178  	pvc := &v1.PersistentVolumeClaim{
   179  		ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
   180  		Spec:       v1.PersistentVolumeClaimSpec{},
   181  	}
   182  	if owner != nil {
   183  		pvc.OwnerReferences = []metav1.OwnerReference{*owner}
   184  	}
   185  
   186  	return pvc
   187  }
   188  
   189  func makeEphemeralVolume(name string) *v1.Volume {
   190  	return &v1.Volume{
   191  		Name: name,
   192  		VolumeSource: v1.VolumeSource{
   193  			Ephemeral: &v1.EphemeralVolumeSource{
   194  				VolumeClaimTemplate: &v1.PersistentVolumeClaimTemplate{},
   195  			},
   196  		},
   197  	}
   198  }
   199  
   200  func makePod(name, namespace string, uid types.UID, volumes ...v1.Volume) *v1.Pod {
   201  	pvc := &v1.Pod{
   202  		ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace, UID: uid},
   203  		Spec: v1.PodSpec{
   204  			Volumes: volumes,
   205  		},
   206  	}
   207  
   208  	return pvc
   209  }
   210  
   211  func podKey(pod *v1.Pod) string {
   212  	key, _ := cache.DeletionHandlingMetaNamespaceKeyFunc(testPodWithEphemeral)
   213  	return key
   214  }
   215  
   216  func makeOwnerReference(pod *v1.Pod, isController bool) *metav1.OwnerReference {
   217  	isTrue := true
   218  	return &metav1.OwnerReference{
   219  		APIVersion:         "v1",
   220  		Kind:               "Pod",
   221  		Name:               pod.Name,
   222  		UID:                pod.UID,
   223  		Controller:         &isController,
   224  		BlockOwnerDeletion: &isTrue,
   225  	}
   226  }
   227  
   228  func sortPVCs(pvcs []v1.PersistentVolumeClaim) []v1.PersistentVolumeClaim {
   229  	sort.Slice(pvcs, func(i, j int) bool {
   230  		return pvcs[i].Namespace < pvcs[j].Namespace ||
   231  			pvcs[i].Name < pvcs[j].Name
   232  	})
   233  	return pvcs
   234  }
   235  
   236  func createTestClient(objects ...runtime.Object) *fake.Clientset {
   237  	fakeClient := fake.NewSimpleClientset(objects...)
   238  	return fakeClient
   239  }
   240  
   241  // Metrics helpers
   242  
   243  type expectedMetrics struct {
   244  	numCreated  int
   245  	numFailures int
   246  }
   247  
   248  func expectMetrics(t *testing.T, em expectedMetrics) {
   249  	t.Helper()
   250  
   251  	actualCreated, err := testutil.GetCounterMetricValue(ephemeralvolumemetrics.EphemeralVolumeCreateAttempts)
   252  	handleErr(t, err, "ephemeralVolumeCreate")
   253  	if actualCreated != float64(em.numCreated) {
   254  		t.Errorf("Expected PVCs to be created %d, got %v", em.numCreated, actualCreated)
   255  	}
   256  	actualConflicts, err := testutil.GetCounterMetricValue(ephemeralvolumemetrics.EphemeralVolumeCreateFailures)
   257  	handleErr(t, err, "ephemeralVolumeCreate/Conflict")
   258  	if actualConflicts != float64(em.numFailures) {
   259  		t.Errorf("Expected PVCs to have conflicts %d, got %v", em.numFailures, actualConflicts)
   260  	}
   261  }
   262  
   263  func handleErr(t *testing.T, err error, metricName string) {
   264  	if err != nil {
   265  		t.Errorf("Failed to get %s value, err: %v", metricName, err)
   266  	}
   267  }
   268  
   269  func setupMetrics() {
   270  	ephemeralvolumemetrics.RegisterMetrics()
   271  	ephemeralvolumemetrics.EphemeralVolumeCreateAttempts.Reset()
   272  	ephemeralvolumemetrics.EphemeralVolumeCreateFailures.Reset()
   273  }
   274  

View as plain text