...

Source file src/k8s.io/kubernetes/pkg/controller/volume/pvprotection/pv_protection_controller_test.go

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

     1  /*
     2  Copyright 2018 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 pvprotection
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"reflect"
    23  	"testing"
    24  	"time"
    25  
    26  	v1 "k8s.io/api/core/v1"
    27  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    28  	"k8s.io/apimachinery/pkg/api/meta"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	"k8s.io/apimachinery/pkg/runtime/schema"
    32  	"k8s.io/apimachinery/pkg/util/dump"
    33  	"k8s.io/client-go/informers"
    34  	"k8s.io/client-go/kubernetes/fake"
    35  	clienttesting "k8s.io/client-go/testing"
    36  	"k8s.io/klog/v2/ktesting"
    37  	"k8s.io/kubernetes/pkg/controller"
    38  	volumeutil "k8s.io/kubernetes/pkg/volume/util"
    39  )
    40  
    41  const defaultPVName = "default-pv"
    42  
    43  type reaction struct {
    44  	verb      string
    45  	resource  string
    46  	reactorfn clienttesting.ReactionFunc
    47  }
    48  
    49  func pv() *v1.PersistentVolume {
    50  	return &v1.PersistentVolume{
    51  		ObjectMeta: metav1.ObjectMeta{
    52  			Name: defaultPVName,
    53  		},
    54  	}
    55  }
    56  
    57  func boundPV() *v1.PersistentVolume {
    58  	return &v1.PersistentVolume{
    59  		ObjectMeta: metav1.ObjectMeta{
    60  			Name: defaultPVName,
    61  		},
    62  		Status: v1.PersistentVolumeStatus{
    63  			Phase: v1.VolumeBound,
    64  		},
    65  	}
    66  }
    67  
    68  func withProtectionFinalizer(pv *v1.PersistentVolume) *v1.PersistentVolume {
    69  	pv.Finalizers = append(pv.Finalizers, volumeutil.PVProtectionFinalizer)
    70  	return pv
    71  }
    72  
    73  func generateUpdateErrorFunc(t *testing.T, failures int) clienttesting.ReactionFunc {
    74  	i := 0
    75  	return func(action clienttesting.Action) (bool, runtime.Object, error) {
    76  		i++
    77  		if i <= failures {
    78  			// Update fails
    79  			update, ok := action.(clienttesting.UpdateAction)
    80  
    81  			if !ok {
    82  				t.Fatalf("Reactor got non-update action: %+v", action)
    83  			}
    84  			acc, _ := meta.Accessor(update.GetObject())
    85  			return true, nil, apierrors.NewForbidden(update.GetResource().GroupResource(), acc.GetName(), errors.New("Mock error"))
    86  		}
    87  		// Update succeeds
    88  		return false, nil, nil
    89  	}
    90  }
    91  
    92  func deleted(pv *v1.PersistentVolume) *v1.PersistentVolume {
    93  	pv.DeletionTimestamp = &metav1.Time{}
    94  	return pv
    95  }
    96  
    97  func TestPVProtectionController(t *testing.T) {
    98  	pvVer := schema.GroupVersionResource{
    99  		Group:    v1.GroupName,
   100  		Version:  "v1",
   101  		Resource: "persistentvolumes",
   102  	}
   103  	tests := []struct {
   104  		name string
   105  		// Object to insert into fake kubeclient before the test starts.
   106  		initialObjects []runtime.Object
   107  		// Optional client reactors.
   108  		reactors []reaction
   109  		// PV event to simulate. This PV will be automatically added to
   110  		// initialObjects.
   111  		updatedPV *v1.PersistentVolume
   112  		// List of expected kubeclient actions that should happen during the
   113  		// test.
   114  		expectedActions []clienttesting.Action
   115  	}{
   116  		// PV events
   117  		//
   118  		{
   119  			name:      "PV without finalizer -> finalizer is added",
   120  			updatedPV: pv(),
   121  			expectedActions: []clienttesting.Action{
   122  				clienttesting.NewUpdateAction(pvVer, "", withProtectionFinalizer(pv())),
   123  			},
   124  		},
   125  		{
   126  			name:            "PVC with finalizer -> no action",
   127  			updatedPV:       withProtectionFinalizer(pv()),
   128  			expectedActions: []clienttesting.Action{},
   129  		},
   130  		{
   131  			name:      "saving PVC finalizer fails -> controller retries",
   132  			updatedPV: pv(),
   133  			reactors: []reaction{
   134  				{
   135  					verb:      "update",
   136  					resource:  "persistentvolumes",
   137  					reactorfn: generateUpdateErrorFunc(t, 2 /* update fails twice*/),
   138  				},
   139  			},
   140  			expectedActions: []clienttesting.Action{
   141  				// This fails
   142  				clienttesting.NewUpdateAction(pvVer, "", withProtectionFinalizer(pv())),
   143  				// This fails too
   144  				clienttesting.NewUpdateAction(pvVer, "", withProtectionFinalizer(pv())),
   145  				// This succeeds
   146  				clienttesting.NewUpdateAction(pvVer, "", withProtectionFinalizer(pv())),
   147  			},
   148  		},
   149  		{
   150  			name:      "deleted PV with finalizer -> finalizer is removed",
   151  			updatedPV: deleted(withProtectionFinalizer(pv())),
   152  			expectedActions: []clienttesting.Action{
   153  				clienttesting.NewUpdateAction(pvVer, "", deleted(pv())),
   154  			},
   155  		},
   156  		{
   157  			name:      "finalizer removal fails -> controller retries",
   158  			updatedPV: deleted(withProtectionFinalizer(pv())),
   159  			reactors: []reaction{
   160  				{
   161  					verb:      "update",
   162  					resource:  "persistentvolumes",
   163  					reactorfn: generateUpdateErrorFunc(t, 2 /* update fails twice*/),
   164  				},
   165  			},
   166  			expectedActions: []clienttesting.Action{
   167  				// Fails
   168  				clienttesting.NewUpdateAction(pvVer, "", deleted(pv())),
   169  				// Fails too
   170  				clienttesting.NewUpdateAction(pvVer, "", deleted(pv())),
   171  				// Succeeds
   172  				clienttesting.NewUpdateAction(pvVer, "", deleted(pv())),
   173  			},
   174  		},
   175  		{
   176  			name:            "deleted PVC with finalizer + PV is bound -> finalizer is not removed",
   177  			updatedPV:       deleted(withProtectionFinalizer(boundPV())),
   178  			expectedActions: []clienttesting.Action{},
   179  		},
   180  	}
   181  
   182  	for _, test := range tests {
   183  		// Create client with initial data
   184  		objs := test.initialObjects
   185  		if test.updatedPV != nil {
   186  			objs = append(objs, test.updatedPV)
   187  		}
   188  
   189  		client := fake.NewSimpleClientset(objs...)
   190  
   191  		// Create informers
   192  		informers := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc())
   193  		pvInformer := informers.Core().V1().PersistentVolumes()
   194  
   195  		// Populate the informers with initial objects so the controller can
   196  		// Get() it.
   197  		for _, obj := range objs {
   198  			switch obj.(type) {
   199  			case *v1.PersistentVolume:
   200  				pvInformer.Informer().GetStore().Add(obj)
   201  			default:
   202  				t.Fatalf("Unknown initialObject type: %+v", obj)
   203  			}
   204  		}
   205  
   206  		// Add reactor to inject test errors.
   207  		for _, reactor := range test.reactors {
   208  			client.Fake.PrependReactor(reactor.verb, reactor.resource, reactor.reactorfn)
   209  		}
   210  
   211  		// Create the controller
   212  		logger, _ := ktesting.NewTestContext(t)
   213  		ctrl := NewPVProtectionController(logger, pvInformer, client)
   214  
   215  		// Start the test by simulating an event
   216  		if test.updatedPV != nil {
   217  			ctrl.pvAddedUpdated(logger, test.updatedPV)
   218  		}
   219  
   220  		// Process the controller queue until we get expected results
   221  		timeout := time.Now().Add(10 * time.Second)
   222  		lastReportedActionCount := 0
   223  		for {
   224  			if time.Now().After(timeout) {
   225  				t.Errorf("Test %q: timed out", test.name)
   226  				break
   227  			}
   228  			if ctrl.queue.Len() > 0 {
   229  				logger.V(5).Info("Non-empty events queue, processing one", "test", test.name, "queueLength", ctrl.queue.Len())
   230  				ctrl.processNextWorkItem(context.TODO())
   231  			}
   232  			if ctrl.queue.Len() > 0 {
   233  				// There is still some work in the queue, process it now
   234  				continue
   235  			}
   236  			currentActionCount := len(client.Actions())
   237  			if currentActionCount < len(test.expectedActions) {
   238  				// Do not log evey wait, only when the action count changes.
   239  				if lastReportedActionCount < currentActionCount {
   240  					logger.V(5).Info("Waiting for the remaining actions", "test", test.name, "currentActionCount", currentActionCount, "expectedActionCount", len(test.expectedActions))
   241  					lastReportedActionCount = currentActionCount
   242  				}
   243  				// The test expected more to happen, wait for the actions.
   244  				// Most probably it's exponential backoff
   245  				time.Sleep(10 * time.Millisecond)
   246  				continue
   247  			}
   248  			break
   249  		}
   250  		actions := client.Actions()
   251  
   252  		if !reflect.DeepEqual(actions, test.expectedActions) {
   253  			t.Errorf("Test %q: action not expected\nExpected:\n%s\ngot:\n%s", test.name, dump.Pretty(test.expectedActions), dump.Pretty(actions))
   254  		}
   255  
   256  	}
   257  
   258  }
   259  

View as plain text