func TestRecyclingRetry(t *testing.T) {
	// Test that recycler controller retries to recycle a volume several times, which succeeds eventually
	pv := preparePV()

	mockClient := &mockBinderClient{
		volume: pv,
	}

	plugMgr := volume.VolumePluginMgr{}
	// Use a fake NewRecycler function
	plugMgr.InitPlugins(host_path.ProbeRecyclableVolumePlugins(newFailingMockRecycler, volume.VolumeConfig{}), volume.NewFakeVolumeHost("/tmp/fake", nil, nil))
	// Reset a global call counter
	failedCallCount = 0

	recycler := &PersistentVolumeRecycler{
		kubeClient:      fake.NewSimpleClientset(),
		client:          mockClient,
		pluginMgr:       plugMgr,
		syncPeriod:      mySyncPeriod,
		maximumRetry:    myMaximumRetry,
		releasedVolumes: make(map[string]releasedVolumeStatus),
	}

	// All but the last attempt will fail
	testRecycleFailures(t, recycler, mockClient, pv, myMaximumRetry-1)

	// The last attempt should succeed
	err := recycler.reclaimVolume(pv)
	if err != nil {
		t.Errorf("Last step: Recycler failed: %v", err)
	}

	if mockClient.volume.Status.Phase != api.VolumePending {
		t.Errorf("Last step: The volume should be Pending, but is %s instead", mockClient.volume.Status.Phase)
	}
	// Check the cache, it should not have any entry
	status, found := recycler.releasedVolumes[pv.Name]
	if found {
		t.Errorf("Last step: Expected PV to be removed from cache, got %v", status)
	}
}
func TestBindingWithExamples(t *testing.T) {
	api.ForTesting_ReferencesAllowBlankSelfLinks = true
	o := testclient.NewObjects(api.Scheme, api.Scheme)
	if err := testclient.AddObjectsFromPath("../../../docs/user-guide/persistent-volumes/claims/claim-01.yaml", o, api.Scheme); err != nil {
		t.Fatal(err)
	}
	if err := testclient.AddObjectsFromPath("../../../docs/user-guide/persistent-volumes/volumes/local-01.yaml", o, api.Scheme); err != nil {
		t.Fatal(err)
	}

	client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, api.RESTMapper)}

	pv, err := client.PersistentVolumes().Get("any")
	pv.Spec.PersistentVolumeReclaimPolicy = api.PersistentVolumeReclaimRecycle
	if err != nil {
		t.Errorf("Unexpected error getting PV from client: %v", err)
	}

	claim, error := client.PersistentVolumeClaims("ns").Get("any")
	if error != nil {
		t.Errorf("Unexpected error getting PVC from client: %v", err)
	}

	volumeIndex := NewPersistentVolumeOrderedIndex()
	mockClient := &mockBinderClient{
		volume: pv,
		claim:  claim,
	}

	plugMgr := volume.VolumePluginMgr{}
	plugMgr.InitPlugins(host_path.ProbeRecyclableVolumePlugins(newMockRecycler), volume.NewFakeVolumeHost("/tmp/fake", nil, nil))

	recycler := &PersistentVolumeRecycler{
		kubeClient: client,
		client:     mockClient,
		pluginMgr:  plugMgr,
	}

	// adds the volume to the index, making the volume available
	syncVolume(volumeIndex, mockClient, pv)
	if pv.Status.Phase != api.VolumeAvailable {
		t.Errorf("Expected phase %s but got %s", api.VolumeBound, pv.Status.Phase)
	}

	// an initial sync for a claim will bind it to an unbound volume, triggers state change
	syncClaim(volumeIndex, mockClient, claim)
	// state change causes another syncClaim to update statuses
	syncClaim(volumeIndex, mockClient, claim)
	// claim updated volume's status, causing an update and syncVolume call
	syncVolume(volumeIndex, mockClient, pv)

	if pv.Spec.ClaimRef == nil {
		t.Errorf("Expected ClaimRef but got nil for pv.Status.ClaimRef: %+v\n", pv)
	}

	if pv.Status.Phase != api.VolumeBound {
		t.Errorf("Expected phase %s but got %s", api.VolumeBound, pv.Status.Phase)
	}

	if claim.Status.Phase != api.ClaimBound {
		t.Errorf("Expected phase %s but got %s", api.ClaimBound, claim.Status.Phase)
	}
	if len(claim.Status.AccessModes) != len(pv.Spec.AccessModes) {
		t.Errorf("Expected phase %s but got %s", api.ClaimBound, claim.Status.Phase)
	}
	if claim.Status.AccessModes[0] != pv.Spec.AccessModes[0] {
		t.Errorf("Expected access mode %s but got %s", claim.Status.AccessModes[0], pv.Spec.AccessModes[0])
	}

	// pretend the user deleted their claim
	mockClient.claim = nil
	syncVolume(volumeIndex, mockClient, pv)

	if pv.Status.Phase != api.VolumeReleased {
		t.Errorf("Expected phase %s but got %s", api.VolumeReleased, pv.Status.Phase)
	}
	if pv.Spec.ClaimRef == nil {
		t.Errorf("Expected non-nil ClaimRef: %+v", pv.Spec)
	}

	mockClient.volume = pv

	// released volumes with a PersistentVolumeReclaimPolicy (recycle/delete) can have further processing
	err = recycler.reclaimVolume(pv)
	if err != nil {
		t.Errorf("Unexpected error reclaiming volume: %+v", err)
	}
	if pv.Status.Phase != api.VolumePending {
		t.Errorf("Expected phase %s but got %s", api.VolumePending, pv.Status.Phase)
	}

	// after the recycling changes the phase to Pending, the binder picks up again
	// to remove any vestiges of binding and make the volume Available again
	syncVolume(volumeIndex, mockClient, pv)

	if pv.Status.Phase != api.VolumeAvailable {
		t.Errorf("Expected phase %s but got %s", api.VolumeAvailable, pv.Status.Phase)
	}
	if pv.Spec.ClaimRef != nil {
		t.Errorf("Expected nil ClaimRef: %+v", pv.Spec)
	}
}
func TestClaimRace(t *testing.T) {
	c1 := &api.PersistentVolumeClaim{
		ObjectMeta: api.ObjectMeta{
			Name: "c1",
		},
		Spec: api.PersistentVolumeClaimSpec{
			AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
			Resources: api.ResourceRequirements{
				Requests: api.ResourceList{
					api.ResourceName(api.ResourceStorage): resource.MustParse("3Gi"),
				},
			},
		},
		Status: api.PersistentVolumeClaimStatus{
			Phase: api.ClaimPending,
		},
	}
	c1.ObjectMeta.SelfLink = testapi.Default.SelfLink("pvc", "")

	c2 := &api.PersistentVolumeClaim{
		ObjectMeta: api.ObjectMeta{
			Name: "c2",
		},
		Spec: api.PersistentVolumeClaimSpec{
			AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
			Resources: api.ResourceRequirements{
				Requests: api.ResourceList{
					api.ResourceName(api.ResourceStorage): resource.MustParse("3Gi"),
				},
			},
		},
		Status: api.PersistentVolumeClaimStatus{
			Phase: api.ClaimPending,
		},
	}
	c2.ObjectMeta.SelfLink = testapi.Default.SelfLink("pvc", "")

	v := &api.PersistentVolume{
		ObjectMeta: api.ObjectMeta{
			Name: "foo",
		},
		Spec: api.PersistentVolumeSpec{
			AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
			Capacity: api.ResourceList{
				api.ResourceName(api.ResourceStorage): resource.MustParse("10Gi"),
			},
			PersistentVolumeSource: api.PersistentVolumeSource{
				HostPath: &api.HostPathVolumeSource{
					Path: "/tmp/data01",
				},
			},
		},
		Status: api.PersistentVolumeStatus{
			Phase: api.VolumePending,
		},
	}

	volumeIndex := NewPersistentVolumeOrderedIndex()
	mockClient := &mockBinderClient{}

	plugMgr := volume.VolumePluginMgr{}
	plugMgr.InitPlugins(host_path.ProbeRecyclableVolumePlugins(newMockRecycler, volume.VolumeConfig{}), volume.NewFakeVolumeHost("/tmp/fake", nil, nil))

	// adds the volume to the index, making the volume available
	syncVolume(volumeIndex, mockClient, v)
	if mockClient.volume.Status.Phase != api.VolumeAvailable {
		t.Errorf("Expected phase %s but got %s", api.VolumeAvailable, mockClient.volume.Status.Phase)
	}
	if _, exists, _ := volumeIndex.Get(v); !exists {
		t.Errorf("Expected to find volume in index but it did not exist")
	}

	// an initial sync for a claim matches the volume
	err := syncClaim(volumeIndex, mockClient, c1)
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
	}
	if c1.Status.Phase != api.ClaimBound {
		t.Errorf("Expected phase %s but got %s", api.ClaimBound, c1.Status.Phase)
	}

	// before the volume gets updated w/ claimRef, a 2nd claim can attempt to bind and find the same volume
	err = syncClaim(volumeIndex, mockClient, c2)
	if err != nil {
		t.Errorf("unexpected error for unmatched claim: %v", err)
	}
	if c2.Status.Phase != api.ClaimPending {
		t.Errorf("Expected phase %s but got %s", api.ClaimPending, c2.Status.Phase)
	}
}
func TestBindingWithExamples(t *testing.T) {
	o := testclient.NewObjects(api.Scheme, api.Scheme)
	if err := testclient.AddObjectsFromPath("../../../docs/user-guide/persistent-volumes/claims/claim-01.yaml", o, api.Scheme); err != nil {
		t.Fatal(err)
	}
	if err := testclient.AddObjectsFromPath("../../../docs/user-guide/persistent-volumes/volumes/local-01.yaml", o, api.Scheme); err != nil {
		t.Fatal(err)
	}

	client := &testclient.Fake{}
	client.AddReactor("*", "*", testclient.ObjectReaction(o, api.RESTMapper))

	pv, err := client.PersistentVolumes().Get("any")
	pv.Spec.PersistentVolumeReclaimPolicy = api.PersistentVolumeReclaimRecycle
	if err != nil {
		t.Errorf("Unexpected error getting PV from client: %v", err)
	}
	pv.ObjectMeta.SelfLink = testapi.Default.SelfLink("pv", "")

	// the default value of the PV is Pending. if processed at least once, its status in etcd is Available.
	// There was a bug where only Pending volumes were being indexed and made ready for claims.
	// Test that !Pending gets correctly added
	pv.Status.Phase = api.VolumeAvailable

	claim, error := client.PersistentVolumeClaims("ns").Get("any")
	if error != nil {
		t.Errorf("Unexpected error getting PVC from client: %v", err)
	}
	claim.ObjectMeta.SelfLink = testapi.Default.SelfLink("pvc", "")

	volumeIndex := NewPersistentVolumeOrderedIndex()
	mockClient := &mockBinderClient{
		volume: pv,
		claim:  claim,
	}

	plugMgr := volume.VolumePluginMgr{}
	plugMgr.InitPlugins(host_path.ProbeRecyclableVolumePlugins(newMockRecycler, volume.VolumeConfig{}), volume.NewFakeVolumeHost("/tmp/fake", nil, nil))

	recycler := &PersistentVolumeRecycler{
		kubeClient: client,
		client:     mockClient,
		pluginMgr:  plugMgr,
	}

	// adds the volume to the index, making the volume available
	syncVolume(volumeIndex, mockClient, pv)
	if mockClient.volume.Status.Phase != api.VolumeAvailable {
		t.Errorf("Expected phase %s but got %s", api.VolumeAvailable, mockClient.volume.Status.Phase)
	}

	// an initial sync for a claim will bind it to an unbound volume
	syncClaim(volumeIndex, mockClient, claim)

	// bind expected on pv.Spec but status update hasn't happened yet
	if mockClient.volume.Spec.ClaimRef == nil {
		t.Errorf("Expected ClaimRef but got nil for pv.Status.ClaimRef\n")
	}
	if mockClient.volume.Status.Phase != api.VolumeAvailable {
		t.Errorf("Expected phase %s but got %s", api.VolumeAvailable, mockClient.volume.Status.Phase)
	}
	if mockClient.claim.Spec.VolumeName != pv.Name {
		t.Errorf("Expected claim.Spec.VolumeName %s but got %s", mockClient.claim.Spec.VolumeName, pv.Name)
	}
	if mockClient.claim.Status.Phase != api.ClaimBound {
		t.Errorf("Expected phase %s but got %s", api.ClaimBound, claim.Status.Phase)
	}

	// state changes in pvc triggers sync that sets pv attributes to pvc.Status
	syncClaim(volumeIndex, mockClient, claim)
	if len(mockClient.claim.Status.AccessModes) == 0 {
		t.Errorf("Expected %d access modes but got 0", len(pv.Spec.AccessModes))
	}

	// persisting the bind to pv.Spec.ClaimRef triggers a sync
	syncVolume(volumeIndex, mockClient, mockClient.volume)
	if mockClient.volume.Status.Phase != api.VolumeBound {
		t.Errorf("Expected phase %s but got %s", api.VolumeBound, mockClient.volume.Status.Phase)
	}

	// pretend the user deleted their claim. periodic resync picks it up.
	mockClient.claim = nil
	syncVolume(volumeIndex, mockClient, mockClient.volume)

	if mockClient.volume.Status.Phase != api.VolumeReleased {
		t.Errorf("Expected phase %s but got %s", api.VolumeReleased, mockClient.volume.Status.Phase)
	}

	// released volumes with a PersistentVolumeReclaimPolicy (recycle/delete) can have further processing
	err = recycler.reclaimVolume(mockClient.volume)
	if err != nil {
		t.Errorf("Unexpected error reclaiming volume: %+v", err)
	}
	if mockClient.volume.Status.Phase != api.VolumePending {
		t.Errorf("Expected phase %s but got %s", api.VolumePending, mockClient.volume.Status.Phase)
	}

	// after the recycling changes the phase to Pending, the binder picks up again
	// to remove any vestiges of binding and make the volume Available again
	syncVolume(volumeIndex, mockClient, mockClient.volume)

	if mockClient.volume.Status.Phase != api.VolumeAvailable {
		t.Errorf("Expected phase %s but got %s", api.VolumeAvailable, mockClient.volume.Status.Phase)
	}
	if mockClient.volume.Spec.ClaimRef != nil {
		t.Errorf("Expected nil ClaimRef: %+v", mockClient.volume.Spec.ClaimRef)
	}
}
func TestClaimSyncAfterVolumeProvisioning(t *testing.T) {
	tmpDir, err := utiltesting.MkTmpdir("claimbinder-test")
	if err != nil {
		t.Fatalf("error creating temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDir)

	// Tests that binder.syncVolume will also syncClaim if the PV has completed
	// provisioning but the claim is still Pending.  We want to advance to Bound
	// without having to wait until the binder's next sync period.
	claim := &api.PersistentVolumeClaim{
		ObjectMeta: api.ObjectMeta{
			Name:      "foo",
			Namespace: "bar",
			Annotations: map[string]string{
				qosProvisioningKey: "foo",
			},
		},
		Spec: api.PersistentVolumeClaimSpec{
			AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
			Resources: api.ResourceRequirements{
				Requests: api.ResourceList{
					api.ResourceName(api.ResourceStorage): resource.MustParse("3Gi"),
				},
			},
		},
		Status: api.PersistentVolumeClaimStatus{
			Phase: api.ClaimPending,
		},
	}
	claim.ObjectMeta.SelfLink = testapi.Default.SelfLink("pvc", "")
	claimRef, _ := api.GetReference(claim)

	pv := &api.PersistentVolume{
		ObjectMeta: api.ObjectMeta{
			Name: "foo",
			Annotations: map[string]string{
				pvProvisioningRequiredAnnotationKey: pvProvisioningCompletedAnnotationValue,
			},
		},
		Spec: api.PersistentVolumeSpec{
			AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
			Capacity: api.ResourceList{
				api.ResourceName(api.ResourceStorage): resource.MustParse("10Gi"),
			},
			PersistentVolumeSource: api.PersistentVolumeSource{
				HostPath: &api.HostPathVolumeSource{
					Path: fmt.Sprintf("%s/data01", tmpDir),
				},
			},
			ClaimRef: claimRef,
		},
		Status: api.PersistentVolumeStatus{
			Phase: api.VolumePending,
		},
	}

	volumeIndex := NewPersistentVolumeOrderedIndex()
	mockClient := &mockBinderClient{
		claim:  claim,
		volume: pv,
	}

	plugMgr := volume.VolumePluginMgr{}
	plugMgr.InitPlugins(host_path.ProbeRecyclableVolumePlugins(newMockRecycler, volume.VolumeConfig{}), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))

	// adds the volume to the index, making the volume available.
	// pv also completed provisioning, so syncClaim should cause claim's phase to advance to Bound
	syncVolume(volumeIndex, mockClient, pv)
	if mockClient.volume.Status.Phase != api.VolumeAvailable {
		t.Errorf("Expected phase %s but got %s", api.VolumeAvailable, mockClient.volume.Status.Phase)
	}
	if mockClient.claim.Status.Phase != api.ClaimBound {
		t.Errorf("Expected phase %s but got %s", api.ClaimBound, claim.Status.Phase)
	}
}
func TestNewClaimWithSameNameAsOldClaim(t *testing.T) {
	c1 := &api.PersistentVolumeClaim{
		ObjectMeta: api.ObjectMeta{
			Name:      "c1",
			Namespace: "foo",
			UID:       "12345",
		},
		Spec: api.PersistentVolumeClaimSpec{
			AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
			Resources: api.ResourceRequirements{
				Requests: api.ResourceList{
					api.ResourceName(api.ResourceStorage): resource.MustParse("3Gi"),
				},
			},
		},
		Status: api.PersistentVolumeClaimStatus{
			Phase: api.ClaimBound,
		},
	}
	c1.ObjectMeta.SelfLink = testapi.Default.SelfLink("pvc", "")

	v := &api.PersistentVolume{
		ObjectMeta: api.ObjectMeta{
			Name: "foo",
		},
		Spec: api.PersistentVolumeSpec{
			ClaimRef: &api.ObjectReference{
				Name:      c1.Name,
				Namespace: c1.Namespace,
				UID:       "45678",
			},
			AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
			Capacity: api.ResourceList{
				api.ResourceName(api.ResourceStorage): resource.MustParse("10Gi"),
			},
			PersistentVolumeSource: api.PersistentVolumeSource{
				HostPath: &api.HostPathVolumeSource{
					Path: "/tmp/data01",
				},
			},
		},
		Status: api.PersistentVolumeStatus{
			Phase: api.VolumeBound,
		},
	}

	volumeIndex := NewPersistentVolumeOrderedIndex()
	mockClient := &mockBinderClient{
		claim:  c1,
		volume: v,
	}

	plugMgr := volume.VolumePluginMgr{}
	plugMgr.InitPlugins(host_path.ProbeRecyclableVolumePlugins(newMockRecycler, volume.VolumeConfig{}), volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil))

	syncVolume(volumeIndex, mockClient, v)
	if mockClient.volume.Status.Phase != api.VolumeReleased {
		t.Errorf("Expected phase %s but got %s", api.VolumeReleased, mockClient.volume.Status.Phase)
	}

}
func TestRecycledPersistentVolumeUID(t *testing.T) {
	tmpDir, err := utiltesting.MkTmpdir("claimbinder-test")
	if err != nil {
		t.Fatalf("error creating temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDir)

	codec := api.Codecs.UniversalDecoder()
	o := core.NewObjects(api.Scheme, codec)
	if err := core.AddObjectsFromPath("../../../docs/user-guide/persistent-volumes/claims/claim-01.yaml", o, codec); err != nil {
		t.Fatal(err)
	}
	if err := core.AddObjectsFromPath("../../../docs/user-guide/persistent-volumes/volumes/local-01.yaml", o, codec); err != nil {
		t.Fatal(err)
	}

	clientset := &fake.Clientset{}
	clientset.AddReactor("*", "*", core.ObjectReaction(o, registered.RESTMapper()))

	pv, err := clientset.Core().PersistentVolumes().Get("any")
	if err != nil {
		t.Errorf("Unexpected error getting PV from client: %v", err)
	}
	pv.Spec.PersistentVolumeReclaimPolicy = api.PersistentVolumeReclaimRecycle
	if err != nil {
		t.Errorf("Unexpected error getting PV from client: %v", err)
	}
	pv.ObjectMeta.SelfLink = testapi.Default.SelfLink("pv", "")

	// the default value of the PV is Pending. if processed at least once, its status in etcd is Available.
	// There was a bug where only Pending volumes were being indexed and made ready for claims.
	// Test that !Pending gets correctly added
	pv.Status.Phase = api.VolumeAvailable

	claim, error := clientset.Core().PersistentVolumeClaims("ns").Get("any")
	if error != nil {
		t.Errorf("Unexpected error getting PVC from client: %v", err)
	}
	claim.ObjectMeta.SelfLink = testapi.Default.SelfLink("pvc", "")
	claim.ObjectMeta.UID = types.UID("uid1")

	volumeIndex := NewPersistentVolumeOrderedIndex()
	mockClient := &mockBinderClient{
		volume: pv,
		claim:  claim,
	}

	plugMgr := volume.VolumePluginMgr{}
	plugMgr.InitPlugins(host_path.ProbeRecyclableVolumePlugins(newMockRecycler, volume.VolumeConfig{}), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))

	recycler := &PersistentVolumeRecycler{
		kubeClient: clientset,
		client:     mockClient,
		pluginMgr:  plugMgr,
	}

	// adds the volume to the index, making the volume available
	syncVolume(volumeIndex, mockClient, pv)
	if mockClient.volume.Status.Phase != api.VolumeAvailable {
		t.Errorf("Expected phase %s but got %s", api.VolumeAvailable, mockClient.volume.Status.Phase)
	}

	// add the claim to fake API server
	mockClient.UpdatePersistentVolumeClaim(claim)
	// an initial sync for a claim will bind it to an unbound volume
	syncClaim(volumeIndex, mockClient, claim)

	// pretend the user deleted their claim. periodic resync picks it up.
	mockClient.claim = nil
	syncVolume(volumeIndex, mockClient, mockClient.volume)

	if mockClient.volume.Status.Phase != api.VolumeReleased {
		t.Errorf("Expected phase %s but got %s", api.VolumeReleased, mockClient.volume.Status.Phase)
	}

	// released volumes with a PersistentVolumeReclaimPolicy (recycle/delete) can have further processing
	err = recycler.reclaimVolume(mockClient.volume)
	if err != nil {
		t.Errorf("Unexpected error reclaiming volume: %+v", err)
	}
	if mockClient.volume.Status.Phase != api.VolumePending {
		t.Errorf("Expected phase %s but got %s", api.VolumePending, mockClient.volume.Status.Phase)
	}

	// after the recycling changes the phase to Pending, the binder picks up again
	// to remove any vestiges of binding and make the volume Available again
	//
	// explicitly set the claim's UID to a different value to ensure that a new claim with the same
	// name as what the PV was previously bound still yields an available volume
	claim.ObjectMeta.UID = types.UID("uid2")
	mockClient.claim = claim
	syncVolume(volumeIndex, mockClient, mockClient.volume)

	if mockClient.volume.Status.Phase != api.VolumeAvailable {
		t.Errorf("Expected phase %s but got %s", api.VolumeAvailable, mockClient.volume.Status.Phase)
	}
	if mockClient.volume.Spec.ClaimRef != nil {
		t.Errorf("Expected nil ClaimRef: %+v", mockClient.volume.Spec.ClaimRef)
	}
}