Merge pull request #19868 from jsafrane/devel/syncclaim-twice

Auto commit by PR queue bot
This commit is contained in:
k8s-merge-robot 2016-02-12 06:21:21 -08:00
commit 678958a706
5 changed files with 81 additions and 5 deletions

View File

@ -335,7 +335,15 @@ func syncVolume(volumeIndex *persistentVolumeOrderedIndex, binderClient binderCl
} }
func syncClaim(volumeIndex *persistentVolumeOrderedIndex, binderClient binderClient, claim *api.PersistentVolumeClaim) (err error) { func syncClaim(volumeIndex *persistentVolumeOrderedIndex, binderClient binderClient, claim *api.PersistentVolumeClaim) (err error) {
glog.V(5).Infof("Synchronizing PersistentVolumeClaim[%s]\n", claim.Name) glog.V(5).Infof("Synchronizing PersistentVolumeClaim[%s] for binding", claim.Name)
// The claim may have been modified by parallel call to syncClaim, load
// the current version.
newClaim, err := binderClient.GetPersistentVolumeClaim(claim.Namespace, claim.Name)
if err != nil {
return fmt.Errorf("Cannot reload claim %s/%s: %v", claim.Namespace, claim.Name, err)
}
claim = newClaim
switch claim.Status.Phase { switch claim.Status.Phase {
case api.ClaimPending: case api.ClaimPending:

View File

@ -123,7 +123,6 @@ func TestClaimRace(t *testing.T) {
plugMgr := volume.VolumePluginMgr{} plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(host_path.ProbeRecyclableVolumePlugins(newMockRecycler, volume.VolumeConfig{}), volume.NewFakeVolumeHost(tmpDir, nil, nil)) plugMgr.InitPlugins(host_path.ProbeRecyclableVolumePlugins(newMockRecycler, volume.VolumeConfig{}), volume.NewFakeVolumeHost(tmpDir, nil, nil))
// adds the volume to the index, making the volume available // adds the volume to the index, making the volume available
syncVolume(volumeIndex, mockClient, v) syncVolume(volumeIndex, mockClient, v)
if mockClient.volume.Status.Phase != api.VolumeAvailable { if mockClient.volume.Status.Phase != api.VolumeAvailable {
@ -133,6 +132,8 @@ func TestClaimRace(t *testing.T) {
t.Errorf("Expected to find volume in index but it did not exist") t.Errorf("Expected to find volume in index but it did not exist")
} }
// add the claim to fake API server
mockClient.UpdatePersistentVolumeClaim(c1)
// an initial sync for a claim matches the volume // an initial sync for a claim matches the volume
err = syncClaim(volumeIndex, mockClient, c1) err = syncClaim(volumeIndex, mockClient, c1)
if err != nil { if err != nil {
@ -143,6 +144,8 @@ func TestClaimRace(t *testing.T) {
} }
// before the volume gets updated w/ claimRef, a 2nd claim can attempt to bind and find the same volume // before the volume gets updated w/ claimRef, a 2nd claim can attempt to bind and find the same volume
// add the 2nd claim to fake API server
mockClient.UpdatePersistentVolumeClaim(c2)
err = syncClaim(volumeIndex, mockClient, c2) err = syncClaim(volumeIndex, mockClient, c2)
if err != nil { if err != nil {
t.Errorf("unexpected error for unmatched claim: %v", err) t.Errorf("unexpected error for unmatched claim: %v", err)
@ -470,6 +473,8 @@ func TestBindingWithExamples(t *testing.T) {
t.Errorf("Expected phase %s but got %s", api.VolumeAvailable, mockClient.volume.Status.Phase) 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 // an initial sync for a claim will bind it to an unbound volume
syncClaim(volumeIndex, mockClient, claim) syncClaim(volumeIndex, mockClient, claim)
@ -539,6 +544,14 @@ func TestCasting(t *testing.T) {
Status: api.PersistentVolumeClaimStatus{Phase: api.ClaimBound}, Status: api.PersistentVolumeClaimStatus{Phase: api.ClaimBound},
} }
// Inject mockClient into the binder. This prevents weird errors on stderr
// as the binder wants to load PV/PVC from API server.
mockClient := &mockBinderClient{
volume: pv,
claim: pvc,
}
binder.client = mockClient
// none of these should fail casting. // none of these should fail casting.
// the real test is not failing when passed DeletedFinalStateUnknown in the deleteHandler // the real test is not failing when passed DeletedFinalStateUnknown in the deleteHandler
binder.addVolume(pv) binder.addVolume(pv)

View File

@ -153,6 +153,20 @@ func (controller *PersistentVolumeProvisionerController) handleUpdateClaim(oldOb
} }
func (controller *PersistentVolumeProvisionerController) reconcileClaim(claim *api.PersistentVolumeClaim) error { func (controller *PersistentVolumeProvisionerController) reconcileClaim(claim *api.PersistentVolumeClaim) error {
glog.V(5).Infof("Synchronizing PersistentVolumeClaim[%s] for dynamic provisioning", claim.Name)
// The claim may have been modified by parallel call to reconcileClaim, load
// the current version.
newClaim, err := controller.client.GetPersistentVolumeClaim(claim.Namespace, claim.Name)
if err != nil {
return fmt.Errorf("Cannot reload claim %s/%s: %v", claim.Namespace, claim.Name, err)
}
claim = newClaim
err = controller.claimStore.Update(claim)
if err != nil {
return fmt.Errorf("Cannot update claim %s/%s: %v", claim.Namespace, claim.Name, err)
}
if controller.provisioner == nil { if controller.provisioner == nil {
return fmt.Errorf("No provisioner configured for controller") return fmt.Errorf("No provisioner configured for controller")
} }

View File

@ -105,6 +105,9 @@ func TestReconcileClaim(t *testing.T) {
// watch would have added the claim to the store // watch would have added the claim to the store
controller.claimStore.Add(pvc) controller.claimStore.Add(pvc)
// store it in fake API server
mockClient.UpdatePersistentVolumeClaim(pvc)
err := controller.reconcileClaim(pvc) err := controller.reconcileClaim(pvc)
if err != nil { if err != nil {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected error: %v", err)
@ -116,6 +119,8 @@ func TestReconcileClaim(t *testing.T) {
} }
pvc.Annotations[qosProvisioningKey] = "foo" pvc.Annotations[qosProvisioningKey] = "foo"
// store it in fake API server
mockClient.UpdatePersistentVolumeClaim(pvc)
err = controller.reconcileClaim(pvc) err = controller.reconcileClaim(pvc)
if err != nil { if err != nil {
@ -130,6 +135,40 @@ func TestReconcileClaim(t *testing.T) {
if mockClient.volume.Spec.ClaimRef.Name != pvc.Name { if mockClient.volume.Spec.ClaimRef.Name != pvc.Name {
t.Errorf("Expected PV to be bound to %s but got %s", mockClient.volume.Spec.ClaimRef.Name, pvc.Name) t.Errorf("Expected PV to be bound to %s but got %s", mockClient.volume.Spec.ClaimRef.Name, pvc.Name)
} }
// the PVC should have correct annotation
if mockClient.claim.Annotations[pvProvisioningRequiredAnnotationKey] != pvProvisioningCompletedAnnotationValue {
t.Errorf("Annotation %q not set", pvProvisioningRequiredAnnotationKey)
}
// Run the syncClaim 2nd time to simulate periodic sweep running in parallel
// to the previous syncClaim. There is a lock in handleUpdateVolume(), so
// they will be called sequentially, but the second call will have old
// version of the claim.
oldPVName := mockClient.volume.Name
// Make the "old" claim
pvc2 := makeTestClaim()
pvc2.Annotations[qosProvisioningKey] = "foo"
// Add a dummy annotation so we recognize the claim was updated (i.e.
// stored in mockClient)
pvc2.Annotations["test"] = "test"
err = controller.reconcileClaim(pvc2)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// The 2nd PVC should be ignored, no new PV was created
if val, found := pvc2.Annotations[pvProvisioningRequiredAnnotationKey]; found {
t.Errorf("2nd PVC got unexpected annotation %q: %q", pvProvisioningRequiredAnnotationKey, val)
}
if mockClient.volume.Name != oldPVName {
t.Errorf("2nd PVC unexpectedly provisioned a new volume")
}
if _, found := mockClient.claim.Annotations["test"]; found {
t.Errorf("2nd PVC was unexpectedly updated")
}
} }
func checkTagValue(t *testing.T, tags map[string]string, tag string, expectedValue string) { func checkTagValue(t *testing.T, tags map[string]string, tag string, expectedValue string) {

View File

@ -46,9 +46,11 @@ func TestPersistentVolumeRecycler(t *testing.T) {
defer s.Close() defer s.Close()
deleteAllEtcdKeys() deleteAllEtcdKeys()
binderClient := clientset.NewForConfigOrDie(&client.Config{Host: s.URL, ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) // Use higher QPS and Burst, there is a test for race condition below, which
recyclerClient := clientset.NewForConfigOrDie(&client.Config{Host: s.URL, ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) // creates many claims and default values were too low.
testClient := clientset.NewForConfigOrDie(&client.Config{Host: s.URL, ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) binderClient := clientset.NewForConfigOrDie(&client.Config{Host: s.URL, ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}, QPS: 1000, Burst: 100000})
recyclerClient := clientset.NewForConfigOrDie(&client.Config{Host: s.URL, ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}, QPS: 1000, Burst: 100000})
testClient := clientset.NewForConfigOrDie(&client.Config{Host: s.URL, ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}, QPS: 1000, Burst: 100000})
host := volume.NewFakeVolumeHost("/tmp/fake", nil, nil) host := volume.NewFakeVolumeHost("/tmp/fake", nil, nil)
plugins := []volume.VolumePlugin{&volume.FakeVolumePlugin{"plugin-name", host, volume.VolumeConfig{}, volume.VolumeOptions{}, 0, 0}} plugins := []volume.VolumePlugin{&volume.FakeVolumePlugin{"plugin-name", host, volume.VolumeConfig{}, volume.VolumeOptions{}, 0, 0}}