mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-11-03 23:40:03 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			756 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			756 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Copyright 2017 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 persistentvolume
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"reflect"
 | 
						|
	"testing"
 | 
						|
 | 
						|
	"github.com/golang/glog"
 | 
						|
 | 
						|
	"k8s.io/api/core/v1"
 | 
						|
	storagev1 "k8s.io/api/storage/v1"
 | 
						|
	"k8s.io/apimachinery/pkg/api/resource"
 | 
						|
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						|
	"k8s.io/apimachinery/pkg/types"
 | 
						|
	"k8s.io/apimachinery/pkg/util/diff"
 | 
						|
	utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
						|
	"k8s.io/client-go/informers"
 | 
						|
	clientset "k8s.io/client-go/kubernetes"
 | 
						|
	"k8s.io/client-go/kubernetes/fake"
 | 
						|
	"k8s.io/client-go/tools/cache"
 | 
						|
	"k8s.io/kubernetes/pkg/api/testapi"
 | 
						|
	"k8s.io/kubernetes/pkg/controller"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	unboundPVC          = makeTestPVC("unbound-pvc", "1G", pvcUnbound, "", &waitClass)
 | 
						|
	unboundPVC2         = makeTestPVC("unbound-pvc2", "5G", pvcUnbound, "", &waitClass)
 | 
						|
	preboundPVC         = makeTestPVC("prebound-pvc", "1G", pvcPrebound, "pv-node1a", &waitClass)
 | 
						|
	boundPVC            = makeTestPVC("bound-pvc", "1G", pvcBound, "pv-bound", &waitClass)
 | 
						|
	boundPVC2           = makeTestPVC("bound-pvc2", "1G", pvcBound, "pv-bound2", &waitClass)
 | 
						|
	badPVC              = makeBadPVC()
 | 
						|
	immediateUnboundPVC = makeTestPVC("immediate-unbound-pvc", "1G", pvcUnbound, "", &immediateClass)
 | 
						|
	immediateBoundPVC   = makeTestPVC("immediate-bound-pvc", "1G", pvcBound, "pv-bound-immediate", &immediateClass)
 | 
						|
 | 
						|
	pvNoNode                   = makeTestPV("pv-no-node", "", "1G", "1", nil, waitClass)
 | 
						|
	pvNode1a                   = makeTestPV("pv-node1a", "node1", "5G", "1", nil, waitClass)
 | 
						|
	pvNode1b                   = makeTestPV("pv-node1b", "node1", "10G", "1", nil, waitClass)
 | 
						|
	pvNode2                    = makeTestPV("pv-node2", "node2", "1G", "1", nil, waitClass)
 | 
						|
	pvPrebound                 = makeTestPV("pv-prebound", "node1", "1G", "1", unboundPVC, waitClass)
 | 
						|
	pvBound                    = makeTestPV("pv-bound", "node1", "1G", "1", boundPVC, waitClass)
 | 
						|
	pvNode1aBound              = makeTestPV("pv-node1a", "node1", "1G", "1", unboundPVC, waitClass)
 | 
						|
	pvNode1bBound              = makeTestPV("pv-node1b", "node1", "5G", "1", unboundPVC2, waitClass)
 | 
						|
	pvNode1bBoundHigherVersion = makeTestPV("pv-node1b", "node1", "5G", "2", unboundPVC2, waitClass)
 | 
						|
	pvBoundImmediate           = makeTestPV("pv-bound-immediate", "node1", "1G", "1", immediateBoundPVC, immediateClass)
 | 
						|
	pvBoundImmediateNode2      = makeTestPV("pv-bound-immediate", "node2", "1G", "1", immediateBoundPVC, immediateClass)
 | 
						|
 | 
						|
	binding1a      = makeBinding(unboundPVC, pvNode1a)
 | 
						|
	binding1b      = makeBinding(unboundPVC2, pvNode1b)
 | 
						|
	bindingNoNode  = makeBinding(unboundPVC, pvNoNode)
 | 
						|
	bindingBad     = makeBinding(badPVC, pvNode1b)
 | 
						|
	binding1aBound = makeBinding(unboundPVC, pvNode1aBound)
 | 
						|
	binding1bBound = makeBinding(unboundPVC2, pvNode1bBound)
 | 
						|
 | 
						|
	waitClass      = "waitClass"
 | 
						|
	immediateClass = "immediateClass"
 | 
						|
)
 | 
						|
 | 
						|
type testEnv struct {
 | 
						|
	client           clientset.Interface
 | 
						|
	reactor          *volumeReactor
 | 
						|
	binder           SchedulerVolumeBinder
 | 
						|
	internalBinder   *volumeBinder
 | 
						|
	internalPVCache  *pvAssumeCache
 | 
						|
	internalPVCCache cache.Indexer
 | 
						|
}
 | 
						|
 | 
						|
func newTestBinder(t *testing.T) *testEnv {
 | 
						|
	client := &fake.Clientset{}
 | 
						|
	reactor := newVolumeReactor(client, nil, nil, nil, nil)
 | 
						|
	informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc())
 | 
						|
 | 
						|
	pvcInformer := informerFactory.Core().V1().PersistentVolumeClaims()
 | 
						|
	nodeInformer := informerFactory.Core().V1().Nodes()
 | 
						|
	classInformer := informerFactory.Storage().V1().StorageClasses()
 | 
						|
 | 
						|
	binder := NewVolumeBinder(
 | 
						|
		client,
 | 
						|
		pvcInformer,
 | 
						|
		informerFactory.Core().V1().PersistentVolumes(),
 | 
						|
		nodeInformer,
 | 
						|
		classInformer)
 | 
						|
 | 
						|
	// Add a node
 | 
						|
	err := nodeInformer.Informer().GetIndexer().Add(&v1.Node{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Name:   "node1",
 | 
						|
			Labels: map[string]string{"key1": "node1"},
 | 
						|
		},
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Failed to add node to internal cache: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Add storageclasses
 | 
						|
	waitMode := storagev1.VolumeBindingWaitForFirstConsumer
 | 
						|
	immediateMode := storagev1.VolumeBindingImmediate
 | 
						|
	classes := []*storagev1.StorageClass{
 | 
						|
		{
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name: waitClass,
 | 
						|
			},
 | 
						|
			VolumeBindingMode: &waitMode,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name: immediateClass,
 | 
						|
			},
 | 
						|
			VolumeBindingMode: &immediateMode,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for _, class := range classes {
 | 
						|
		if err = classInformer.Informer().GetIndexer().Add(class); err != nil {
 | 
						|
			t.Fatalf("Failed to add storage class to internal cache: %v", err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Get internal types
 | 
						|
	internalBinder, ok := binder.(*volumeBinder)
 | 
						|
	if !ok {
 | 
						|
		t.Fatalf("Failed to convert to internal binder")
 | 
						|
	}
 | 
						|
 | 
						|
	pvCache := internalBinder.pvCache
 | 
						|
	internalPVCache, ok := pvCache.(*pvAssumeCache)
 | 
						|
	if !ok {
 | 
						|
		t.Fatalf("Failed to convert to internal PV cache")
 | 
						|
	}
 | 
						|
 | 
						|
	return &testEnv{
 | 
						|
		client:           client,
 | 
						|
		reactor:          reactor,
 | 
						|
		binder:           binder,
 | 
						|
		internalBinder:   internalBinder,
 | 
						|
		internalPVCache:  internalPVCache,
 | 
						|
		internalPVCCache: pvcInformer.Informer().GetIndexer(),
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (env *testEnv) initClaims(t *testing.T, pvcs []*v1.PersistentVolumeClaim) {
 | 
						|
	for _, pvc := range pvcs {
 | 
						|
		err := env.internalPVCCache.Add(pvc)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatalf("Failed to add PVC %q to internal cache: %v", pvc.Name, err)
 | 
						|
		}
 | 
						|
		env.reactor.claims[pvc.Name] = pvc
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (env *testEnv) initVolumes(cachedPVs []*v1.PersistentVolume, apiPVs []*v1.PersistentVolume) {
 | 
						|
	internalPVCache := env.internalPVCache
 | 
						|
	for _, pv := range cachedPVs {
 | 
						|
		internalPVCache.add(pv)
 | 
						|
		if apiPVs == nil {
 | 
						|
			env.reactor.volumes[pv.Name] = pv
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for _, pv := range apiPVs {
 | 
						|
		env.reactor.volumes[pv.Name] = pv
 | 
						|
	}
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
func (env *testEnv) assumeVolumes(t *testing.T, name, node string, pod *v1.Pod, bindings []*bindingInfo) {
 | 
						|
	pvCache := env.internalBinder.pvCache
 | 
						|
	for _, binding := range bindings {
 | 
						|
		if err := pvCache.Assume(binding.pv); err != nil {
 | 
						|
			t.Fatalf("Failed to setup test %q: error: %v", name, err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	env.internalBinder.podBindingCache.UpdateBindings(pod, node, bindings)
 | 
						|
}
 | 
						|
 | 
						|
func (env *testEnv) initPodCache(pod *v1.Pod, node string, bindings []*bindingInfo) {
 | 
						|
	cache := env.internalBinder.podBindingCache
 | 
						|
	cache.UpdateBindings(pod, node, bindings)
 | 
						|
}
 | 
						|
 | 
						|
func (env *testEnv) validatePodCache(t *testing.T, name, node string, pod *v1.Pod, expectedBindings []*bindingInfo) {
 | 
						|
	cache := env.internalBinder.podBindingCache
 | 
						|
	bindings := cache.GetBindings(pod, node)
 | 
						|
 | 
						|
	if !reflect.DeepEqual(expectedBindings, bindings) {
 | 
						|
		t.Errorf("Test %q failed: Expected bindings %+v, got %+v", name, expectedBindings, bindings)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (env *testEnv) validateAssume(t *testing.T, name string, pod *v1.Pod, bindings []*bindingInfo) {
 | 
						|
	// TODO: Check binding cache
 | 
						|
 | 
						|
	// Check pv cache
 | 
						|
	pvCache := env.internalBinder.pvCache
 | 
						|
	for _, b := range bindings {
 | 
						|
		pv, err := pvCache.GetPV(b.pv.Name)
 | 
						|
		if err != nil {
 | 
						|
			t.Errorf("Test %q failed: GetPV %q returned error: %v", name, b.pv.Name, err)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if pv.Spec.ClaimRef == nil {
 | 
						|
			t.Errorf("Test %q failed: PV %q ClaimRef is nil", name, b.pv.Name)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if pv.Spec.ClaimRef.Name != b.pvc.Name {
 | 
						|
			t.Errorf("Test %q failed: expected PV.ClaimRef.Name %q, got %q", name, b.pvc.Name, pv.Spec.ClaimRef.Name)
 | 
						|
		}
 | 
						|
		if pv.Spec.ClaimRef.Namespace != b.pvc.Namespace {
 | 
						|
			t.Errorf("Test %q failed: expected PV.ClaimRef.Namespace %q, got %q", name, b.pvc.Namespace, pv.Spec.ClaimRef.Namespace)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (env *testEnv) validateFailedAssume(t *testing.T, name string, pod *v1.Pod, bindings []*bindingInfo) {
 | 
						|
	// All PVs have been unmodified in cache
 | 
						|
	pvCache := env.internalBinder.pvCache
 | 
						|
	for _, b := range bindings {
 | 
						|
		pv, _ := pvCache.GetPV(b.pv.Name)
 | 
						|
		// PV could be nil if it's missing from cache
 | 
						|
		if pv != nil && pv != b.pv {
 | 
						|
			t.Errorf("Test %q failed: PV %q was modified in cache", name, b.pv.Name)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (env *testEnv) validateBind(
 | 
						|
	t *testing.T,
 | 
						|
	name string,
 | 
						|
	pod *v1.Pod,
 | 
						|
	expectedPVs []*v1.PersistentVolume,
 | 
						|
	expectedAPIPVs []*v1.PersistentVolume) {
 | 
						|
 | 
						|
	// Check pv cache
 | 
						|
	pvCache := env.internalBinder.pvCache
 | 
						|
	for _, pv := range expectedPVs {
 | 
						|
		cachedPV, err := pvCache.GetPV(pv.Name)
 | 
						|
		if err != nil {
 | 
						|
			t.Errorf("Test %q failed: GetPV %q returned error: %v", name, pv.Name, err)
 | 
						|
		}
 | 
						|
		if !reflect.DeepEqual(cachedPV, pv) {
 | 
						|
			t.Errorf("Test %q failed: cached PV check failed [A-expected, B-got]:\n%s", name, diff.ObjectDiff(pv, cachedPV))
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Check reactor for API updates
 | 
						|
	if err := env.reactor.checkVolumes(expectedAPIPVs); err != nil {
 | 
						|
		t.Errorf("Test %q failed: API reactor validation failed: %v", name, err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
const (
 | 
						|
	pvcUnbound = iota
 | 
						|
	pvcPrebound
 | 
						|
	pvcBound
 | 
						|
)
 | 
						|
 | 
						|
func makeTestPVC(name, size string, pvcBoundState int, pvName string, className *string) *v1.PersistentVolumeClaim {
 | 
						|
	pvc := &v1.PersistentVolumeClaim{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Name:            name,
 | 
						|
			Namespace:       "testns",
 | 
						|
			UID:             types.UID("pvc-uid"),
 | 
						|
			ResourceVersion: "1",
 | 
						|
			SelfLink:        testapi.Default.SelfLink("pvc", name),
 | 
						|
		},
 | 
						|
		Spec: v1.PersistentVolumeClaimSpec{
 | 
						|
			Resources: v1.ResourceRequirements{
 | 
						|
				Requests: v1.ResourceList{
 | 
						|
					v1.ResourceName(v1.ResourceStorage): resource.MustParse(size),
 | 
						|
				},
 | 
						|
			},
 | 
						|
			StorageClassName: className,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	switch pvcBoundState {
 | 
						|
	case pvcBound:
 | 
						|
		metav1.SetMetaDataAnnotation(&pvc.ObjectMeta, annBindCompleted, "yes")
 | 
						|
		fallthrough
 | 
						|
	case pvcPrebound:
 | 
						|
		pvc.Spec.VolumeName = pvName
 | 
						|
	}
 | 
						|
	return pvc
 | 
						|
}
 | 
						|
 | 
						|
func makeBadPVC() *v1.PersistentVolumeClaim {
 | 
						|
	return &v1.PersistentVolumeClaim{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Name:            "bad-pvc",
 | 
						|
			Namespace:       "testns",
 | 
						|
			UID:             types.UID("pvc-uid"),
 | 
						|
			ResourceVersion: "1",
 | 
						|
			// Don't include SefLink, so that GetReference will fail
 | 
						|
		},
 | 
						|
		Spec: v1.PersistentVolumeClaimSpec{
 | 
						|
			Resources: v1.ResourceRequirements{
 | 
						|
				Requests: v1.ResourceList{
 | 
						|
					v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"),
 | 
						|
				},
 | 
						|
			},
 | 
						|
			StorageClassName: &waitClass,
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func makeTestPV(name, node, capacity, version string, boundToPVC *v1.PersistentVolumeClaim, className string) *v1.PersistentVolume {
 | 
						|
	pv := &v1.PersistentVolume{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Name:            name,
 | 
						|
			ResourceVersion: version,
 | 
						|
		},
 | 
						|
		Spec: v1.PersistentVolumeSpec{
 | 
						|
			Capacity: v1.ResourceList{
 | 
						|
				v1.ResourceName(v1.ResourceStorage): resource.MustParse(capacity),
 | 
						|
			},
 | 
						|
			StorageClassName: className,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	if node != "" {
 | 
						|
		pv.Annotations = getAnnotationWithNodeAffinity("key1", node)
 | 
						|
	}
 | 
						|
 | 
						|
	if boundToPVC != nil {
 | 
						|
		pv.Spec.ClaimRef = &v1.ObjectReference{
 | 
						|
			Name:      boundToPVC.Name,
 | 
						|
			Namespace: boundToPVC.Namespace,
 | 
						|
			UID:       boundToPVC.UID,
 | 
						|
		}
 | 
						|
		metav1.SetMetaDataAnnotation(&pv.ObjectMeta, annBoundByController, "yes")
 | 
						|
	}
 | 
						|
 | 
						|
	return pv
 | 
						|
}
 | 
						|
 | 
						|
func makePod(pvcs []*v1.PersistentVolumeClaim) *v1.Pod {
 | 
						|
	pod := &v1.Pod{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Name:      "test-pod",
 | 
						|
			Namespace: "testns",
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	volumes := []v1.Volume{}
 | 
						|
	for i, pvc := range pvcs {
 | 
						|
		pvcVol := v1.Volume{
 | 
						|
			Name: fmt.Sprintf("vol%v", i),
 | 
						|
			VolumeSource: v1.VolumeSource{
 | 
						|
				PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
 | 
						|
					ClaimName: pvc.Name,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		}
 | 
						|
		volumes = append(volumes, pvcVol)
 | 
						|
	}
 | 
						|
	pod.Spec.Volumes = volumes
 | 
						|
	pod.Spec.NodeName = "node1"
 | 
						|
	return pod
 | 
						|
}
 | 
						|
 | 
						|
func makePodWithoutPVC() *v1.Pod {
 | 
						|
	pod := &v1.Pod{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Name:      "test-pod",
 | 
						|
			Namespace: "testns",
 | 
						|
		},
 | 
						|
		Spec: v1.PodSpec{
 | 
						|
			Volumes: []v1.Volume{
 | 
						|
				{
 | 
						|
					VolumeSource: v1.VolumeSource{
 | 
						|
						EmptyDir: &v1.EmptyDirVolumeSource{},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	return pod
 | 
						|
}
 | 
						|
 | 
						|
func makeBinding(pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume) *bindingInfo {
 | 
						|
	return &bindingInfo{pvc: pvc, pv: pv}
 | 
						|
}
 | 
						|
 | 
						|
func makeStringPtr(str string) *string {
 | 
						|
	s := fmt.Sprintf("%v", str)
 | 
						|
	return &s
 | 
						|
}
 | 
						|
 | 
						|
func TestFindPodVolumes(t *testing.T) {
 | 
						|
	scenarios := map[string]struct {
 | 
						|
		// Inputs
 | 
						|
		pvs     []*v1.PersistentVolume
 | 
						|
		podPVCs []*v1.PersistentVolumeClaim
 | 
						|
		// Defaults to node1
 | 
						|
		node string
 | 
						|
		// If nil, use pod PVCs
 | 
						|
		cachePVCs []*v1.PersistentVolumeClaim
 | 
						|
		// If nil, makePod with podPVCs
 | 
						|
		pod *v1.Pod
 | 
						|
 | 
						|
		// Expected podBindingCache fields
 | 
						|
		expectedBindings []*bindingInfo
 | 
						|
 | 
						|
		// Expected return values
 | 
						|
		expectedUnbound bool
 | 
						|
		expectedBound   bool
 | 
						|
		shouldFail      bool
 | 
						|
	}{
 | 
						|
		"no-volumes": {
 | 
						|
			pod:             makePod(nil),
 | 
						|
			expectedUnbound: true,
 | 
						|
			expectedBound:   true,
 | 
						|
		},
 | 
						|
		"no-pvcs": {
 | 
						|
			pod:             makePodWithoutPVC(),
 | 
						|
			expectedUnbound: true,
 | 
						|
			expectedBound:   true,
 | 
						|
		},
 | 
						|
		"pvc-not-found": {
 | 
						|
			cachePVCs:       []*v1.PersistentVolumeClaim{},
 | 
						|
			podPVCs:         []*v1.PersistentVolumeClaim{boundPVC},
 | 
						|
			expectedUnbound: false,
 | 
						|
			expectedBound:   false,
 | 
						|
			shouldFail:      true,
 | 
						|
		},
 | 
						|
		"bound-pvc": {
 | 
						|
			podPVCs:         []*v1.PersistentVolumeClaim{boundPVC},
 | 
						|
			pvs:             []*v1.PersistentVolume{pvBound},
 | 
						|
			expectedUnbound: true,
 | 
						|
			expectedBound:   true,
 | 
						|
		},
 | 
						|
		"bound-pvc,pv-not-exists": {
 | 
						|
			podPVCs:         []*v1.PersistentVolumeClaim{boundPVC},
 | 
						|
			expectedUnbound: false,
 | 
						|
			expectedBound:   false,
 | 
						|
			shouldFail:      true,
 | 
						|
		},
 | 
						|
		"prebound-pvc": {
 | 
						|
			podPVCs:         []*v1.PersistentVolumeClaim{preboundPVC},
 | 
						|
			pvs:             []*v1.PersistentVolume{pvNode1aBound},
 | 
						|
			expectedUnbound: true,
 | 
						|
			expectedBound:   true,
 | 
						|
		},
 | 
						|
		"unbound-pvc,node-not-exists": {
 | 
						|
			podPVCs:         []*v1.PersistentVolumeClaim{unboundPVC},
 | 
						|
			node:            "node12",
 | 
						|
			expectedUnbound: false,
 | 
						|
			expectedBound:   false,
 | 
						|
			shouldFail:      true,
 | 
						|
		},
 | 
						|
		"unbound-pvc,pv-same-node": {
 | 
						|
			podPVCs:          []*v1.PersistentVolumeClaim{unboundPVC},
 | 
						|
			pvs:              []*v1.PersistentVolume{pvNode2, pvNode1a, pvNode1b},
 | 
						|
			expectedBindings: []*bindingInfo{binding1a},
 | 
						|
			expectedUnbound:  true,
 | 
						|
			expectedBound:    true,
 | 
						|
		},
 | 
						|
		"unbound-pvc,pv-different-node": {
 | 
						|
			podPVCs:         []*v1.PersistentVolumeClaim{unboundPVC},
 | 
						|
			pvs:             []*v1.PersistentVolume{pvNode2},
 | 
						|
			expectedUnbound: false,
 | 
						|
			expectedBound:   true,
 | 
						|
		},
 | 
						|
		"two-unbound-pvcs": {
 | 
						|
			podPVCs:          []*v1.PersistentVolumeClaim{unboundPVC, unboundPVC2},
 | 
						|
			pvs:              []*v1.PersistentVolume{pvNode1a, pvNode1b},
 | 
						|
			expectedBindings: []*bindingInfo{binding1a, binding1b},
 | 
						|
			expectedUnbound:  true,
 | 
						|
			expectedBound:    true,
 | 
						|
		},
 | 
						|
		"two-unbound-pvcs,order-by-size": {
 | 
						|
			podPVCs:          []*v1.PersistentVolumeClaim{unboundPVC2, unboundPVC},
 | 
						|
			pvs:              []*v1.PersistentVolume{pvNode1a, pvNode1b},
 | 
						|
			expectedBindings: []*bindingInfo{binding1a, binding1b},
 | 
						|
			expectedUnbound:  true,
 | 
						|
			expectedBound:    true,
 | 
						|
		},
 | 
						|
		"two-unbound-pvcs,partial-match": {
 | 
						|
			podPVCs:         []*v1.PersistentVolumeClaim{unboundPVC, unboundPVC2},
 | 
						|
			pvs:             []*v1.PersistentVolume{pvNode1a},
 | 
						|
			expectedUnbound: false,
 | 
						|
			expectedBound:   true,
 | 
						|
		},
 | 
						|
		"one-bound,one-unbound": {
 | 
						|
			podPVCs:          []*v1.PersistentVolumeClaim{unboundPVC, boundPVC},
 | 
						|
			pvs:              []*v1.PersistentVolume{pvBound, pvNode1a},
 | 
						|
			expectedBindings: []*bindingInfo{binding1a},
 | 
						|
			expectedUnbound:  true,
 | 
						|
			expectedBound:    true,
 | 
						|
		},
 | 
						|
		"one-bound,one-unbound,no-match": {
 | 
						|
			podPVCs:         []*v1.PersistentVolumeClaim{unboundPVC, boundPVC},
 | 
						|
			pvs:             []*v1.PersistentVolume{pvBound, pvNode2},
 | 
						|
			expectedUnbound: false,
 | 
						|
			expectedBound:   true,
 | 
						|
		},
 | 
						|
		"one-prebound,one-unbound": {
 | 
						|
			podPVCs:          []*v1.PersistentVolumeClaim{unboundPVC, preboundPVC},
 | 
						|
			pvs:              []*v1.PersistentVolume{pvNode1a, pvNode1b},
 | 
						|
			expectedBindings: []*bindingInfo{binding1a},
 | 
						|
			expectedUnbound:  true,
 | 
						|
			expectedBound:    true,
 | 
						|
		},
 | 
						|
		"immediate-bound-pvc": {
 | 
						|
			podPVCs:         []*v1.PersistentVolumeClaim{immediateBoundPVC},
 | 
						|
			pvs:             []*v1.PersistentVolume{pvBoundImmediate},
 | 
						|
			expectedUnbound: true,
 | 
						|
			expectedBound:   true,
 | 
						|
		},
 | 
						|
		"immediate-bound-pvc-wrong-node": {
 | 
						|
			podPVCs:         []*v1.PersistentVolumeClaim{immediateBoundPVC},
 | 
						|
			pvs:             []*v1.PersistentVolume{pvBoundImmediateNode2},
 | 
						|
			expectedUnbound: true,
 | 
						|
			expectedBound:   false,
 | 
						|
		},
 | 
						|
		"immediate-unbound-pvc": {
 | 
						|
			podPVCs:         []*v1.PersistentVolumeClaim{immediateUnboundPVC},
 | 
						|
			expectedUnbound: false,
 | 
						|
			expectedBound:   false,
 | 
						|
			shouldFail:      true,
 | 
						|
		},
 | 
						|
		"immediate-unbound-pvc,delayed-mode-bound": {
 | 
						|
			podPVCs:         []*v1.PersistentVolumeClaim{immediateUnboundPVC, boundPVC},
 | 
						|
			pvs:             []*v1.PersistentVolume{pvBound},
 | 
						|
			expectedUnbound: false,
 | 
						|
			expectedBound:   false,
 | 
						|
			shouldFail:      true,
 | 
						|
		},
 | 
						|
		"immediate-unbound-pvc,delayed-mode-unbound": {
 | 
						|
			podPVCs:         []*v1.PersistentVolumeClaim{immediateUnboundPVC, unboundPVC},
 | 
						|
			expectedUnbound: false,
 | 
						|
			expectedBound:   false,
 | 
						|
			shouldFail:      true,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	// Set feature gate
 | 
						|
	utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true")
 | 
						|
	defer utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
 | 
						|
 | 
						|
	for name, scenario := range scenarios {
 | 
						|
		glog.V(5).Infof("Running test case %q", name)
 | 
						|
 | 
						|
		// Setup
 | 
						|
		testEnv := newTestBinder(t)
 | 
						|
		testEnv.initVolumes(scenario.pvs, scenario.pvs)
 | 
						|
		if scenario.node == "" {
 | 
						|
			scenario.node = "node1"
 | 
						|
		}
 | 
						|
 | 
						|
		// a. Init pvc cache
 | 
						|
		if scenario.cachePVCs == nil {
 | 
						|
			scenario.cachePVCs = scenario.podPVCs
 | 
						|
		}
 | 
						|
		testEnv.initClaims(t, scenario.cachePVCs)
 | 
						|
 | 
						|
		// b. Generate pod with given claims
 | 
						|
		if scenario.pod == nil {
 | 
						|
			scenario.pod = makePod(scenario.podPVCs)
 | 
						|
		}
 | 
						|
 | 
						|
		// Execute
 | 
						|
		unboundSatisfied, boundSatisfied, err := testEnv.binder.FindPodVolumes(scenario.pod, scenario.node)
 | 
						|
 | 
						|
		// Validate
 | 
						|
		if !scenario.shouldFail && err != nil {
 | 
						|
			t.Errorf("Test %q failed: returned error: %v", name, err)
 | 
						|
		}
 | 
						|
		if scenario.shouldFail && err == nil {
 | 
						|
			t.Errorf("Test %q failed: returned success but expected error", name)
 | 
						|
		}
 | 
						|
		if boundSatisfied != scenario.expectedBound {
 | 
						|
			t.Errorf("Test %q failed: expected boundSatsified %v, got %v", name, scenario.expectedBound, boundSatisfied)
 | 
						|
		}
 | 
						|
		if unboundSatisfied != scenario.expectedUnbound {
 | 
						|
			t.Errorf("Test %q failed: expected unboundSatsified %v, got %v", name, scenario.expectedUnbound, unboundSatisfied)
 | 
						|
		}
 | 
						|
		testEnv.validatePodCache(t, name, scenario.node, scenario.pod, scenario.expectedBindings)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestAssumePodVolumes(t *testing.T) {
 | 
						|
	scenarios := map[string]struct {
 | 
						|
		// Inputs
 | 
						|
		podPVCs  []*v1.PersistentVolumeClaim
 | 
						|
		pvs      []*v1.PersistentVolume
 | 
						|
		bindings []*bindingInfo
 | 
						|
 | 
						|
		// Expected return values
 | 
						|
		shouldFail              bool
 | 
						|
		expectedBindingRequired bool
 | 
						|
		expectedAllBound        bool
 | 
						|
 | 
						|
		// if nil, use bindings
 | 
						|
		expectedBindings []*bindingInfo
 | 
						|
	}{
 | 
						|
		"all-bound": {
 | 
						|
			podPVCs:          []*v1.PersistentVolumeClaim{boundPVC},
 | 
						|
			pvs:              []*v1.PersistentVolume{pvBound},
 | 
						|
			expectedAllBound: true,
 | 
						|
		},
 | 
						|
		"prebound-pvc": {
 | 
						|
			podPVCs: []*v1.PersistentVolumeClaim{preboundPVC},
 | 
						|
			pvs:     []*v1.PersistentVolume{pvNode1a},
 | 
						|
		},
 | 
						|
		"one-binding": {
 | 
						|
			podPVCs:  []*v1.PersistentVolumeClaim{unboundPVC},
 | 
						|
			bindings: []*bindingInfo{binding1a},
 | 
						|
			pvs:      []*v1.PersistentVolume{pvNode1a},
 | 
						|
			expectedBindingRequired: true,
 | 
						|
		},
 | 
						|
		"two-bindings": {
 | 
						|
			podPVCs:  []*v1.PersistentVolumeClaim{unboundPVC, unboundPVC2},
 | 
						|
			bindings: []*bindingInfo{binding1a, binding1b},
 | 
						|
			pvs:      []*v1.PersistentVolume{pvNode1a, pvNode1b},
 | 
						|
			expectedBindingRequired: true,
 | 
						|
		},
 | 
						|
		"pv-already-bound": {
 | 
						|
			podPVCs:  []*v1.PersistentVolumeClaim{unboundPVC},
 | 
						|
			bindings: []*bindingInfo{binding1aBound},
 | 
						|
			pvs:      []*v1.PersistentVolume{pvNode1aBound},
 | 
						|
			expectedBindingRequired: false,
 | 
						|
			expectedBindings:        []*bindingInfo{},
 | 
						|
		},
 | 
						|
		"claimref-failed": {
 | 
						|
			podPVCs:                 []*v1.PersistentVolumeClaim{unboundPVC},
 | 
						|
			bindings:                []*bindingInfo{binding1a, bindingBad},
 | 
						|
			pvs:                     []*v1.PersistentVolume{pvNode1a, pvNode1b},
 | 
						|
			shouldFail:              true,
 | 
						|
			expectedBindingRequired: true,
 | 
						|
		},
 | 
						|
		"tmpupdate-failed": {
 | 
						|
			podPVCs:                 []*v1.PersistentVolumeClaim{unboundPVC},
 | 
						|
			bindings:                []*bindingInfo{binding1a, binding1b},
 | 
						|
			pvs:                     []*v1.PersistentVolume{pvNode1a},
 | 
						|
			shouldFail:              true,
 | 
						|
			expectedBindingRequired: true,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for name, scenario := range scenarios {
 | 
						|
		glog.V(5).Infof("Running test case %q", name)
 | 
						|
 | 
						|
		// Setup
 | 
						|
		testEnv := newTestBinder(t)
 | 
						|
		testEnv.initClaims(t, scenario.podPVCs)
 | 
						|
		pod := makePod(scenario.podPVCs)
 | 
						|
		testEnv.initPodCache(pod, "node1", scenario.bindings)
 | 
						|
		testEnv.initVolumes(scenario.pvs, scenario.pvs)
 | 
						|
 | 
						|
		// Execute
 | 
						|
		allBound, bindingRequired, err := testEnv.binder.AssumePodVolumes(pod, "node1")
 | 
						|
 | 
						|
		// Validate
 | 
						|
		if !scenario.shouldFail && err != nil {
 | 
						|
			t.Errorf("Test %q failed: returned error: %v", name, err)
 | 
						|
		}
 | 
						|
		if scenario.shouldFail && err == nil {
 | 
						|
			t.Errorf("Test %q failed: returned success but expected error", name)
 | 
						|
		}
 | 
						|
		if scenario.expectedBindingRequired != bindingRequired {
 | 
						|
			t.Errorf("Test %q failed: returned unexpected bindingRequired: %v", name, bindingRequired)
 | 
						|
		}
 | 
						|
		if scenario.expectedAllBound != allBound {
 | 
						|
			t.Errorf("Test %q failed: returned unexpected allBound: %v", name, allBound)
 | 
						|
		}
 | 
						|
		if scenario.expectedBindings == nil {
 | 
						|
			scenario.expectedBindings = scenario.bindings
 | 
						|
		}
 | 
						|
		if scenario.shouldFail {
 | 
						|
			testEnv.validateFailedAssume(t, name, pod, scenario.expectedBindings)
 | 
						|
		} else {
 | 
						|
			testEnv.validateAssume(t, name, pod, scenario.expectedBindings)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestBindPodVolumes(t *testing.T) {
 | 
						|
	scenarios := map[string]struct {
 | 
						|
		// Inputs
 | 
						|
		bindings  []*bindingInfo
 | 
						|
		cachedPVs []*v1.PersistentVolume
 | 
						|
		// if nil, use cachedPVs
 | 
						|
		apiPVs []*v1.PersistentVolume
 | 
						|
 | 
						|
		// Expected return values
 | 
						|
		shouldFail  bool
 | 
						|
		expectedPVs []*v1.PersistentVolume
 | 
						|
		// if nil, use expectedPVs
 | 
						|
		expectedAPIPVs []*v1.PersistentVolume
 | 
						|
	}{
 | 
						|
		"all-bound": {},
 | 
						|
		"not-fully-bound": {
 | 
						|
			bindings: []*bindingInfo{},
 | 
						|
		},
 | 
						|
		"one-binding": {
 | 
						|
			bindings:    []*bindingInfo{binding1aBound},
 | 
						|
			cachedPVs:   []*v1.PersistentVolume{pvNode1a},
 | 
						|
			expectedPVs: []*v1.PersistentVolume{pvNode1aBound},
 | 
						|
		},
 | 
						|
		"two-bindings": {
 | 
						|
			bindings:    []*bindingInfo{binding1aBound, binding1bBound},
 | 
						|
			cachedPVs:   []*v1.PersistentVolume{pvNode1a, pvNode1b},
 | 
						|
			expectedPVs: []*v1.PersistentVolume{pvNode1aBound, pvNode1bBound},
 | 
						|
		},
 | 
						|
		"api-update-failed": {
 | 
						|
			bindings:       []*bindingInfo{binding1aBound, binding1bBound},
 | 
						|
			cachedPVs:      []*v1.PersistentVolume{pvNode1a, pvNode1b},
 | 
						|
			apiPVs:         []*v1.PersistentVolume{pvNode1a, pvNode1bBoundHigherVersion},
 | 
						|
			expectedPVs:    []*v1.PersistentVolume{pvNode1aBound, pvNode1b},
 | 
						|
			expectedAPIPVs: []*v1.PersistentVolume{pvNode1aBound, pvNode1bBoundHigherVersion},
 | 
						|
			shouldFail:     true,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for name, scenario := range scenarios {
 | 
						|
		glog.V(5).Infof("Running test case %q", name)
 | 
						|
 | 
						|
		// Setup
 | 
						|
		testEnv := newTestBinder(t)
 | 
						|
		pod := makePod(nil)
 | 
						|
		if scenario.apiPVs == nil {
 | 
						|
			scenario.apiPVs = scenario.cachedPVs
 | 
						|
		}
 | 
						|
		testEnv.initVolumes(scenario.cachedPVs, scenario.apiPVs)
 | 
						|
		testEnv.assumeVolumes(t, name, "node1", pod, scenario.bindings)
 | 
						|
 | 
						|
		// Execute
 | 
						|
		err := testEnv.binder.BindPodVolumes(pod)
 | 
						|
 | 
						|
		// Validate
 | 
						|
		if !scenario.shouldFail && err != nil {
 | 
						|
			t.Errorf("Test %q failed: returned error: %v", name, err)
 | 
						|
		}
 | 
						|
		if scenario.shouldFail && err == nil {
 | 
						|
			t.Errorf("Test %q failed: returned success but expected error", name)
 | 
						|
		}
 | 
						|
		if scenario.expectedAPIPVs == nil {
 | 
						|
			scenario.expectedAPIPVs = scenario.expectedPVs
 | 
						|
		}
 | 
						|
		testEnv.validateBind(t, name, pod, scenario.expectedPVs, scenario.expectedAPIPVs)
 | 
						|
	}
 | 
						|
}
 |