mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			561 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			561 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Copyright 2019 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 testing
 | 
						|
 | 
						|
import (
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"reflect"
 | 
						|
	"strconv"
 | 
						|
	"sync"
 | 
						|
 | 
						|
	v1 "k8s.io/api/core/v1"
 | 
						|
	apierrors "k8s.io/apimachinery/pkg/api/errors"
 | 
						|
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						|
	"k8s.io/apimachinery/pkg/runtime"
 | 
						|
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
						|
	"k8s.io/apimachinery/pkg/util/diff"
 | 
						|
	"k8s.io/apimachinery/pkg/watch"
 | 
						|
	"k8s.io/client-go/kubernetes/fake"
 | 
						|
	core "k8s.io/client-go/testing"
 | 
						|
	"k8s.io/klog/v2"
 | 
						|
)
 | 
						|
 | 
						|
// ErrVersionConflict is the error returned when resource version of requested
 | 
						|
// object conflicts with the object in storage.
 | 
						|
var ErrVersionConflict = errors.New("VersionError")
 | 
						|
 | 
						|
// VolumeReactor is a core.Reactor that simulates etcd and API server. It
 | 
						|
// stores:
 | 
						|
// - Latest version of claims volumes saved by the controller.
 | 
						|
// - Queue of all saves (to simulate "volume/claim updated" events). This queue
 | 
						|
//   contains all intermediate state of an object - e.g. a claim.VolumeName
 | 
						|
//   is updated first and claim.Phase second. This queue will then contain both
 | 
						|
//   updates as separate entries.
 | 
						|
// - Number of changes since the last call to VolumeReactor.syncAll().
 | 
						|
// - Optionally, volume and claim fake watchers which should be the same ones
 | 
						|
//   used by the controller. Any time an event function like deleteVolumeEvent
 | 
						|
//   is called to simulate an event, the reactor's stores are updated and the
 | 
						|
//   controller is sent the event via the fake watcher.
 | 
						|
// - Optionally, list of error that should be returned by reactor, simulating
 | 
						|
//   etcd / API server failures. These errors are evaluated in order and every
 | 
						|
//   error is returned only once. I.e. when the reactor finds matching
 | 
						|
//   ReactorError, it return appropriate error and removes the ReactorError from
 | 
						|
//   the list.
 | 
						|
type VolumeReactor struct {
 | 
						|
	volumes              map[string]*v1.PersistentVolume
 | 
						|
	claims               map[string]*v1.PersistentVolumeClaim
 | 
						|
	changedObjects       []interface{}
 | 
						|
	changedSinceLastSync int
 | 
						|
	fakeVolumeWatch      *watch.FakeWatcher
 | 
						|
	fakeClaimWatch       *watch.FakeWatcher
 | 
						|
	lock                 sync.RWMutex
 | 
						|
	errors               []ReactorError
 | 
						|
	watchers             map[schema.GroupVersionResource]map[string][]*watch.RaceFreeFakeWatcher
 | 
						|
}
 | 
						|
 | 
						|
// ReactorError is an error that is returned by test reactor (=simulated
 | 
						|
// etcd+/API server) when an action performed by the reactor matches given verb
 | 
						|
// ("get", "update", "create", "delete" or "*"") on given resource
 | 
						|
// ("persistentvolumes", "persistentvolumeclaims" or "*").
 | 
						|
type ReactorError struct {
 | 
						|
	Verb     string
 | 
						|
	Resource string
 | 
						|
	Error    error
 | 
						|
}
 | 
						|
 | 
						|
// React is a callback called by fake kubeClient from the controller.
 | 
						|
// In other words, every claim/volume change performed by the controller ends
 | 
						|
// here.
 | 
						|
// This callback checks versions of the updated objects and refuse those that
 | 
						|
// are too old (simulating real etcd).
 | 
						|
// All updated objects are stored locally to keep track of object versions and
 | 
						|
// to evaluate test results.
 | 
						|
// All updated objects are also inserted into changedObjects queue and
 | 
						|
// optionally sent back to the controller via its watchers.
 | 
						|
func (r *VolumeReactor) React(action core.Action) (handled bool, ret runtime.Object, err error) {
 | 
						|
	r.lock.Lock()
 | 
						|
	defer r.lock.Unlock()
 | 
						|
 | 
						|
	klog.V(4).Infof("reactor got operation %q on %q", action.GetVerb(), action.GetResource())
 | 
						|
 | 
						|
	// Inject error when requested
 | 
						|
	err = r.injectReactError(action)
 | 
						|
	if err != nil {
 | 
						|
		return true, nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Test did not request to inject an error, continue simulating API server.
 | 
						|
	switch {
 | 
						|
	case action.Matches("create", "persistentvolumes"):
 | 
						|
		obj := action.(core.UpdateAction).GetObject()
 | 
						|
		volume := obj.(*v1.PersistentVolume)
 | 
						|
 | 
						|
		// check the volume does not exist
 | 
						|
		_, found := r.volumes[volume.Name]
 | 
						|
		if found {
 | 
						|
			return true, nil, fmt.Errorf("cannot create volume %s: volume already exists", volume.Name)
 | 
						|
		}
 | 
						|
 | 
						|
		// mimic apiserver defaulting
 | 
						|
		if volume.Spec.VolumeMode == nil {
 | 
						|
			volume.Spec.VolumeMode = new(v1.PersistentVolumeMode)
 | 
						|
			*volume.Spec.VolumeMode = v1.PersistentVolumeFilesystem
 | 
						|
		}
 | 
						|
 | 
						|
		// Store the updated object to appropriate places.
 | 
						|
		r.volumes[volume.Name] = volume
 | 
						|
		for _, w := range r.getWatches(action.GetResource(), action.GetNamespace()) {
 | 
						|
			w.Add(volume)
 | 
						|
		}
 | 
						|
		r.changedObjects = append(r.changedObjects, volume)
 | 
						|
		r.changedSinceLastSync++
 | 
						|
		klog.V(4).Infof("created volume %s", volume.Name)
 | 
						|
		return true, volume, nil
 | 
						|
 | 
						|
	case action.Matches("create", "persistentvolumeclaims"):
 | 
						|
		obj := action.(core.UpdateAction).GetObject()
 | 
						|
		claim := obj.(*v1.PersistentVolumeClaim)
 | 
						|
 | 
						|
		// check the claim does not exist
 | 
						|
		_, found := r.claims[claim.Name]
 | 
						|
		if found {
 | 
						|
			return true, nil, fmt.Errorf("cannot create claim %s: claim already exists", claim.Name)
 | 
						|
		}
 | 
						|
 | 
						|
		// Store the updated object to appropriate places.
 | 
						|
		r.claims[claim.Name] = claim
 | 
						|
		for _, w := range r.getWatches(action.GetResource(), action.GetNamespace()) {
 | 
						|
			w.Add(claim)
 | 
						|
		}
 | 
						|
		r.changedObjects = append(r.changedObjects, claim)
 | 
						|
		r.changedSinceLastSync++
 | 
						|
		klog.V(4).Infof("created claim %s", claim.Name)
 | 
						|
		return true, claim, nil
 | 
						|
 | 
						|
	case action.Matches("update", "persistentvolumes"):
 | 
						|
		obj := action.(core.UpdateAction).GetObject()
 | 
						|
		volume := obj.(*v1.PersistentVolume)
 | 
						|
 | 
						|
		// Check and bump object version
 | 
						|
		storedVolume, found := r.volumes[volume.Name]
 | 
						|
		if found {
 | 
						|
			storedVer, _ := strconv.Atoi(storedVolume.ResourceVersion)
 | 
						|
			requestedVer, _ := strconv.Atoi(volume.ResourceVersion)
 | 
						|
			if storedVer != requestedVer {
 | 
						|
				return true, obj, ErrVersionConflict
 | 
						|
			}
 | 
						|
			if reflect.DeepEqual(storedVolume, volume) {
 | 
						|
				klog.V(4).Infof("nothing updated volume %s", volume.Name)
 | 
						|
				return true, volume, nil
 | 
						|
			}
 | 
						|
			// Don't modify the existing object
 | 
						|
			volume = volume.DeepCopy()
 | 
						|
			volume.ResourceVersion = strconv.Itoa(storedVer + 1)
 | 
						|
		} else {
 | 
						|
			return true, nil, fmt.Errorf("cannot update volume %s: volume not found", volume.Name)
 | 
						|
		}
 | 
						|
 | 
						|
		// Store the updated object to appropriate places.
 | 
						|
		for _, w := range r.getWatches(action.GetResource(), action.GetNamespace()) {
 | 
						|
			w.Modify(volume)
 | 
						|
		}
 | 
						|
		r.volumes[volume.Name] = volume
 | 
						|
		r.changedObjects = append(r.changedObjects, volume)
 | 
						|
		r.changedSinceLastSync++
 | 
						|
		klog.V(4).Infof("saved updated volume %s", volume.Name)
 | 
						|
		return true, volume, nil
 | 
						|
 | 
						|
	case action.Matches("update", "persistentvolumeclaims"):
 | 
						|
		obj := action.(core.UpdateAction).GetObject()
 | 
						|
		claim := obj.(*v1.PersistentVolumeClaim)
 | 
						|
 | 
						|
		// Check and bump object version
 | 
						|
		storedClaim, found := r.claims[claim.Name]
 | 
						|
		if found {
 | 
						|
			storedVer, _ := strconv.Atoi(storedClaim.ResourceVersion)
 | 
						|
			requestedVer, _ := strconv.Atoi(claim.ResourceVersion)
 | 
						|
			if storedVer != requestedVer {
 | 
						|
				return true, obj, ErrVersionConflict
 | 
						|
			}
 | 
						|
			if reflect.DeepEqual(storedClaim, claim) {
 | 
						|
				klog.V(4).Infof("nothing updated claim %s", claim.Name)
 | 
						|
				return true, claim, nil
 | 
						|
			}
 | 
						|
			// Don't modify the existing object
 | 
						|
			claim = claim.DeepCopy()
 | 
						|
			claim.ResourceVersion = strconv.Itoa(storedVer + 1)
 | 
						|
		} else {
 | 
						|
			return true, nil, fmt.Errorf("cannot update claim %s: claim not found", claim.Name)
 | 
						|
		}
 | 
						|
 | 
						|
		// Store the updated object to appropriate places.
 | 
						|
		for _, w := range r.getWatches(action.GetResource(), action.GetNamespace()) {
 | 
						|
			w.Modify(claim)
 | 
						|
		}
 | 
						|
		r.claims[claim.Name] = claim
 | 
						|
		r.changedObjects = append(r.changedObjects, claim)
 | 
						|
		r.changedSinceLastSync++
 | 
						|
		klog.V(4).Infof("saved updated claim %s", claim.Name)
 | 
						|
		return true, claim, nil
 | 
						|
 | 
						|
	case action.Matches("get", "persistentvolumes"):
 | 
						|
		name := action.(core.GetAction).GetName()
 | 
						|
		volume, found := r.volumes[name]
 | 
						|
		if found {
 | 
						|
			klog.V(4).Infof("GetVolume: found %s", volume.Name)
 | 
						|
			return true, volume.DeepCopy(), nil
 | 
						|
		}
 | 
						|
		klog.V(4).Infof("GetVolume: volume %s not found", name)
 | 
						|
		return true, nil, apierrors.NewNotFound(action.GetResource().GroupResource(), name)
 | 
						|
 | 
						|
	case action.Matches("get", "persistentvolumeclaims"):
 | 
						|
		name := action.(core.GetAction).GetName()
 | 
						|
		claim, found := r.claims[name]
 | 
						|
		if found {
 | 
						|
			klog.V(4).Infof("GetClaim: found %s", claim.Name)
 | 
						|
			return true, claim.DeepCopy(), nil
 | 
						|
		}
 | 
						|
		klog.V(4).Infof("GetClaim: claim %s not found", name)
 | 
						|
		return true, nil, apierrors.NewNotFound(action.GetResource().GroupResource(), name)
 | 
						|
 | 
						|
	case action.Matches("delete", "persistentvolumes"):
 | 
						|
		name := action.(core.DeleteAction).GetName()
 | 
						|
		klog.V(4).Infof("deleted volume %s", name)
 | 
						|
		obj, found := r.volumes[name]
 | 
						|
		if found {
 | 
						|
			delete(r.volumes, name)
 | 
						|
			for _, w := range r.getWatches(action.GetResource(), action.GetNamespace()) {
 | 
						|
				w.Delete(obj)
 | 
						|
			}
 | 
						|
			r.changedSinceLastSync++
 | 
						|
			return true, nil, nil
 | 
						|
		}
 | 
						|
		return true, nil, fmt.Errorf("cannot delete volume %s: not found", name)
 | 
						|
 | 
						|
	case action.Matches("delete", "persistentvolumeclaims"):
 | 
						|
		name := action.(core.DeleteAction).GetName()
 | 
						|
		klog.V(4).Infof("deleted claim %s", name)
 | 
						|
		obj, found := r.claims[name]
 | 
						|
		if found {
 | 
						|
			delete(r.claims, name)
 | 
						|
			for _, w := range r.getWatches(action.GetResource(), action.GetNamespace()) {
 | 
						|
				w.Delete(obj)
 | 
						|
			}
 | 
						|
			r.changedSinceLastSync++
 | 
						|
			return true, nil, nil
 | 
						|
		}
 | 
						|
		return true, nil, fmt.Errorf("cannot delete claim %s: not found", name)
 | 
						|
	}
 | 
						|
 | 
						|
	return false, nil, nil
 | 
						|
}
 | 
						|
 | 
						|
// Watch watches objects from the VolumeReactor. Watch returns a channel which
 | 
						|
// will push added / modified / deleted object.
 | 
						|
func (r *VolumeReactor) Watch(gvr schema.GroupVersionResource, ns string) (watch.Interface, error) {
 | 
						|
	r.lock.Lock()
 | 
						|
	defer r.lock.Unlock()
 | 
						|
 | 
						|
	fakewatcher := watch.NewRaceFreeFake()
 | 
						|
 | 
						|
	if _, exists := r.watchers[gvr]; !exists {
 | 
						|
		r.watchers[gvr] = make(map[string][]*watch.RaceFreeFakeWatcher)
 | 
						|
	}
 | 
						|
	r.watchers[gvr][ns] = append(r.watchers[gvr][ns], fakewatcher)
 | 
						|
	return fakewatcher, nil
 | 
						|
}
 | 
						|
 | 
						|
func (r *VolumeReactor) getWatches(gvr schema.GroupVersionResource, ns string) []*watch.RaceFreeFakeWatcher {
 | 
						|
	watches := []*watch.RaceFreeFakeWatcher{}
 | 
						|
	if r.watchers[gvr] != nil {
 | 
						|
		if w := r.watchers[gvr][ns]; w != nil {
 | 
						|
			watches = append(watches, w...)
 | 
						|
		}
 | 
						|
		if ns != metav1.NamespaceAll {
 | 
						|
			if w := r.watchers[gvr][metav1.NamespaceAll]; w != nil {
 | 
						|
				watches = append(watches, w...)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return watches
 | 
						|
}
 | 
						|
 | 
						|
// injectReactError returns an error when the test requested given action to
 | 
						|
// fail. nil is returned otherwise.
 | 
						|
func (r *VolumeReactor) injectReactError(action core.Action) error {
 | 
						|
	if len(r.errors) == 0 {
 | 
						|
		// No more errors to inject, everything should succeed.
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	for i, expected := range r.errors {
 | 
						|
		klog.V(4).Infof("trying to match %q %q with %q %q", expected.Verb, expected.Resource, action.GetVerb(), action.GetResource())
 | 
						|
		if action.Matches(expected.Verb, expected.Resource) {
 | 
						|
			// That's the action we're waiting for, remove it from injectedErrors
 | 
						|
			r.errors = append(r.errors[:i], r.errors[i+1:]...)
 | 
						|
			klog.V(4).Infof("reactor found matching error at index %d: %q %q, returning %v", i, expected.Verb, expected.Resource, expected.Error)
 | 
						|
			return expected.Error
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// CheckVolumes compares all expectedVolumes with set of volumes at the end of
 | 
						|
// the test and reports differences.
 | 
						|
func (r *VolumeReactor) CheckVolumes(expectedVolumes []*v1.PersistentVolume) error {
 | 
						|
	r.lock.Lock()
 | 
						|
	defer r.lock.Unlock()
 | 
						|
 | 
						|
	expectedMap := make(map[string]*v1.PersistentVolume)
 | 
						|
	gotMap := make(map[string]*v1.PersistentVolume)
 | 
						|
	// Clear any ResourceVersion from both sets
 | 
						|
	for _, v := range expectedVolumes {
 | 
						|
		// Don't modify the existing object
 | 
						|
		v := v.DeepCopy()
 | 
						|
		v.ResourceVersion = ""
 | 
						|
		if v.Spec.ClaimRef != nil {
 | 
						|
			v.Spec.ClaimRef.ResourceVersion = ""
 | 
						|
		}
 | 
						|
		expectedMap[v.Name] = v
 | 
						|
	}
 | 
						|
	for _, v := range r.volumes {
 | 
						|
		// We must clone the volume because of golang race check - it was
 | 
						|
		// written by the controller without any locks on it.
 | 
						|
		v := v.DeepCopy()
 | 
						|
		v.ResourceVersion = ""
 | 
						|
		if v.Spec.ClaimRef != nil {
 | 
						|
			v.Spec.ClaimRef.ResourceVersion = ""
 | 
						|
		}
 | 
						|
		gotMap[v.Name] = v
 | 
						|
	}
 | 
						|
	if !reflect.DeepEqual(expectedMap, gotMap) {
 | 
						|
		// Print ugly but useful diff of expected and received objects for
 | 
						|
		// easier debugging.
 | 
						|
		return fmt.Errorf("Volume check failed [A-expected, B-got]: %s", diff.ObjectDiff(expectedMap, gotMap))
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// CheckClaims compares all expectedClaims with set of claims at the end of the
 | 
						|
// test and reports differences.
 | 
						|
func (r *VolumeReactor) CheckClaims(expectedClaims []*v1.PersistentVolumeClaim) error {
 | 
						|
	r.lock.Lock()
 | 
						|
	defer r.lock.Unlock()
 | 
						|
 | 
						|
	expectedMap := make(map[string]*v1.PersistentVolumeClaim)
 | 
						|
	gotMap := make(map[string]*v1.PersistentVolumeClaim)
 | 
						|
	for _, c := range expectedClaims {
 | 
						|
		// Don't modify the existing object
 | 
						|
		c = c.DeepCopy()
 | 
						|
		c.ResourceVersion = ""
 | 
						|
		expectedMap[c.Name] = c
 | 
						|
	}
 | 
						|
	for _, c := range r.claims {
 | 
						|
		// We must clone the claim because of golang race check - it was
 | 
						|
		// written by the controller without any locks on it.
 | 
						|
		c = c.DeepCopy()
 | 
						|
		c.ResourceVersion = ""
 | 
						|
		gotMap[c.Name] = c
 | 
						|
	}
 | 
						|
	if !reflect.DeepEqual(expectedMap, gotMap) {
 | 
						|
		// Print ugly but useful diff of expected and received objects for
 | 
						|
		// easier debugging.
 | 
						|
		return fmt.Errorf("Claim check failed [A-expected, B-got result]: %s", diff.ObjectDiff(expectedMap, gotMap))
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// PopChange returns one recorded updated object, either *v1.PersistentVolume
 | 
						|
// or *v1.PersistentVolumeClaim. Returns nil when there are no changes.
 | 
						|
func (r *VolumeReactor) PopChange() interface{} {
 | 
						|
	r.lock.Lock()
 | 
						|
	defer r.lock.Unlock()
 | 
						|
 | 
						|
	if len(r.changedObjects) == 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	// For debugging purposes, print the queue
 | 
						|
	for _, obj := range r.changedObjects {
 | 
						|
		switch obj.(type) {
 | 
						|
		case *v1.PersistentVolume:
 | 
						|
			vol, _ := obj.(*v1.PersistentVolume)
 | 
						|
			klog.V(4).Infof("reactor queue: %s", vol.Name)
 | 
						|
		case *v1.PersistentVolumeClaim:
 | 
						|
			claim, _ := obj.(*v1.PersistentVolumeClaim)
 | 
						|
			klog.V(4).Infof("reactor queue: %s", claim.Name)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Pop the first item from the queue and return it
 | 
						|
	obj := r.changedObjects[0]
 | 
						|
	r.changedObjects = r.changedObjects[1:]
 | 
						|
	return obj
 | 
						|
}
 | 
						|
 | 
						|
// SyncAll simulates the controller periodic sync of volumes and claim. It
 | 
						|
// simply adds all these objects to the internal queue of updates. This method
 | 
						|
// should be used when the test manually calls syncClaim/syncVolume. Test that
 | 
						|
// use real controller loop (ctrl.Run()) will get periodic sync automatically.
 | 
						|
func (r *VolumeReactor) SyncAll() {
 | 
						|
	r.lock.Lock()
 | 
						|
	defer r.lock.Unlock()
 | 
						|
 | 
						|
	for _, c := range r.claims {
 | 
						|
		r.changedObjects = append(r.changedObjects, c)
 | 
						|
	}
 | 
						|
	for _, v := range r.volumes {
 | 
						|
		r.changedObjects = append(r.changedObjects, v)
 | 
						|
	}
 | 
						|
	r.changedSinceLastSync = 0
 | 
						|
}
 | 
						|
 | 
						|
// GetChangeCount returns changes since last sync.
 | 
						|
func (r *VolumeReactor) GetChangeCount() int {
 | 
						|
	r.lock.Lock()
 | 
						|
	defer r.lock.Unlock()
 | 
						|
	return r.changedSinceLastSync
 | 
						|
}
 | 
						|
 | 
						|
// DeleteVolumeEvent simulates that a volume has been deleted in etcd and
 | 
						|
// the controller receives 'volume deleted' event.
 | 
						|
func (r *VolumeReactor) DeleteVolumeEvent(volume *v1.PersistentVolume) {
 | 
						|
	r.lock.Lock()
 | 
						|
	defer r.lock.Unlock()
 | 
						|
 | 
						|
	// Remove the volume from list of resulting volumes.
 | 
						|
	delete(r.volumes, volume.Name)
 | 
						|
 | 
						|
	// Generate deletion event. Cloned volume is needed to prevent races (and we
 | 
						|
	// would get a clone from etcd too).
 | 
						|
	if r.fakeVolumeWatch != nil {
 | 
						|
		r.fakeVolumeWatch.Delete(volume.DeepCopy())
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// DeleteClaimEvent simulates that a claim has been deleted in etcd and the
 | 
						|
// controller receives 'claim deleted' event.
 | 
						|
func (r *VolumeReactor) DeleteClaimEvent(claim *v1.PersistentVolumeClaim) {
 | 
						|
	r.lock.Lock()
 | 
						|
	defer r.lock.Unlock()
 | 
						|
 | 
						|
	// Remove the claim from list of resulting claims.
 | 
						|
	delete(r.claims, claim.Name)
 | 
						|
 | 
						|
	// Generate deletion event. Cloned volume is needed to prevent races (and we
 | 
						|
	// would get a clone from etcd too).
 | 
						|
	if r.fakeClaimWatch != nil {
 | 
						|
		r.fakeClaimWatch.Delete(claim.DeepCopy())
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// AddClaimEvent simulates that a claim has been deleted in etcd and the
 | 
						|
// controller receives 'claim added' event.
 | 
						|
func (r *VolumeReactor) AddClaimEvent(claim *v1.PersistentVolumeClaim) {
 | 
						|
	r.lock.Lock()
 | 
						|
	defer r.lock.Unlock()
 | 
						|
 | 
						|
	r.claims[claim.Name] = claim
 | 
						|
	// Generate event. No cloning is needed, this claim is not stored in the
 | 
						|
	// controller cache yet.
 | 
						|
	if r.fakeClaimWatch != nil {
 | 
						|
		r.fakeClaimWatch.Add(claim)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// AddClaims adds PVCs into VolumeReactor.
 | 
						|
func (r *VolumeReactor) AddClaims(claims []*v1.PersistentVolumeClaim) {
 | 
						|
	r.lock.Lock()
 | 
						|
	defer r.lock.Unlock()
 | 
						|
	for _, claim := range claims {
 | 
						|
		r.claims[claim.Name] = claim
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// AddVolumes adds PVs into VolumeReactor.
 | 
						|
func (r *VolumeReactor) AddVolumes(volumes []*v1.PersistentVolume) {
 | 
						|
	r.lock.Lock()
 | 
						|
	defer r.lock.Unlock()
 | 
						|
	for _, volume := range volumes {
 | 
						|
		r.volumes[volume.Name] = volume
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// AddClaim adds a PVC into VolumeReactor.
 | 
						|
func (r *VolumeReactor) AddClaim(claim *v1.PersistentVolumeClaim) {
 | 
						|
	r.lock.Lock()
 | 
						|
	defer r.lock.Unlock()
 | 
						|
	r.claims[claim.Name] = claim
 | 
						|
}
 | 
						|
 | 
						|
// AddVolume adds a PV into VolumeReactor.
 | 
						|
func (r *VolumeReactor) AddVolume(volume *v1.PersistentVolume) {
 | 
						|
	r.lock.Lock()
 | 
						|
	defer r.lock.Unlock()
 | 
						|
	r.volumes[volume.Name] = volume
 | 
						|
}
 | 
						|
 | 
						|
// DeleteVolume deletes a PV by name.
 | 
						|
func (r *VolumeReactor) DeleteVolume(name string) {
 | 
						|
	r.lock.Lock()
 | 
						|
	defer r.lock.Unlock()
 | 
						|
	delete(r.volumes, name)
 | 
						|
}
 | 
						|
 | 
						|
// AddClaimBoundToVolume adds a PVC and binds it to corresponding PV.
 | 
						|
func (r *VolumeReactor) AddClaimBoundToVolume(claim *v1.PersistentVolumeClaim) {
 | 
						|
	r.lock.Lock()
 | 
						|
	defer r.lock.Unlock()
 | 
						|
	r.claims[claim.Name] = claim
 | 
						|
	if volume, ok := r.volumes[claim.Spec.VolumeName]; ok {
 | 
						|
		volume.Status.Phase = v1.VolumeBound
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// MarkVolumeAvailable marks a PV available by name.
 | 
						|
func (r *VolumeReactor) MarkVolumeAvailable(name string) {
 | 
						|
	r.lock.Lock()
 | 
						|
	defer r.lock.Unlock()
 | 
						|
	if volume, ok := r.volumes[name]; ok {
 | 
						|
		volume.Spec.ClaimRef = nil
 | 
						|
		volume.Status.Phase = v1.VolumeAvailable
 | 
						|
		volume.Annotations = nil
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// NewVolumeReactor creates a volume reactor.
 | 
						|
func NewVolumeReactor(client *fake.Clientset, fakeVolumeWatch, fakeClaimWatch *watch.FakeWatcher, errors []ReactorError) *VolumeReactor {
 | 
						|
	reactor := &VolumeReactor{
 | 
						|
		volumes:         make(map[string]*v1.PersistentVolume),
 | 
						|
		claims:          make(map[string]*v1.PersistentVolumeClaim),
 | 
						|
		fakeVolumeWatch: fakeVolumeWatch,
 | 
						|
		fakeClaimWatch:  fakeClaimWatch,
 | 
						|
		errors:          errors,
 | 
						|
		watchers:        make(map[schema.GroupVersionResource]map[string][]*watch.RaceFreeFakeWatcher),
 | 
						|
	}
 | 
						|
	client.AddReactor("create", "persistentvolumes", reactor.React)
 | 
						|
	client.AddReactor("create", "persistentvolumeclaims", reactor.React)
 | 
						|
	client.AddReactor("update", "persistentvolumes", reactor.React)
 | 
						|
	client.AddReactor("update", "persistentvolumeclaims", reactor.React)
 | 
						|
	client.AddReactor("get", "persistentvolumes", reactor.React)
 | 
						|
	client.AddReactor("get", "persistentvolumeclaims", reactor.React)
 | 
						|
	client.AddReactor("delete", "persistentvolumes", reactor.React)
 | 
						|
	client.AddReactor("delete", "persistentvolumeclaims", reactor.React)
 | 
						|
	return reactor
 | 
						|
}
 |