Added pending phase for volumes. added defaults for PV/PVC. refactored to better phase transitioning in control loops

pull/6/head
markturansky 2015-04-21 11:05:15 -04:00
parent 37d7f3f4f1
commit a04420e548
16 changed files with 318 additions and 178 deletions

View File

@ -201,6 +201,14 @@ func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer {
c.FuzzNoCustom(s) // fuzz self without calling this function again c.FuzzNoCustom(s) // fuzz self without calling this function again
s.Type = api.SecretTypeOpaque s.Type = api.SecretTypeOpaque
}, },
func(pv *api.PersistentVolume, c fuzz.Continue) {
c.FuzzNoCustom(pv) // fuzz self without calling this function again
pv.Status.Phase = api.VolumePending
},
func(pvc *api.PersistentVolumeClaim, c fuzz.Continue) {
c.FuzzNoCustom(pvc) // fuzz self without calling this function again
pvc.Status.Phase = api.ClaimPending
},
func(s *api.NamespaceSpec, c fuzz.Continue) { func(s *api.NamespaceSpec, c fuzz.Continue) {
s.Finalizers = []api.FinalizerName{api.FinalizerKubernetes} s.Finalizers = []api.FinalizerName{api.FinalizerKubernetes}
}, },

View File

@ -317,6 +317,8 @@ const (
type PersistentVolumePhase string type PersistentVolumePhase string
const ( const (
// used for PersistentVolumes that are not available
VolumePending PersistentVolumePhase = "Pending"
// used for PersistentVolumes that are not yet bound // used for PersistentVolumes that are not yet bound
VolumeAvailable PersistentVolumePhase = "Available" VolumeAvailable PersistentVolumePhase = "Available"
// used for PersistentVolumes that are bound // used for PersistentVolumes that are bound

View File

@ -111,6 +111,16 @@ func init() {
obj.Type = SecretTypeOpaque obj.Type = SecretTypeOpaque
} }
}, },
func(obj *PersistentVolume) {
if obj.Status.Phase == "" {
obj.Status.Phase = VolumePending
}
},
func(obj *PersistentVolumeClaim) {
if obj.Status.Phase == "" {
obj.Status.Phase = ClaimPending
}
},
func(obj *Endpoints) { func(obj *Endpoints) {
if obj.Protocol == "" { if obj.Protocol == "" {
obj.Protocol = ProtocolTCP obj.Protocol = ProtocolTCP

View File

@ -164,6 +164,26 @@ func TestSetDefaultSecret(t *testing.T) {
} }
} }
func TestSetDefaultPersistentVolume(t *testing.T) {
pv := &current.PersistentVolume{}
obj2 := roundTrip(t, runtime.Object(pv))
pv2 := obj2.(*current.PersistentVolume)
if pv2.Status.Phase != current.VolumePending {
t.Errorf("Expected volume phase %v, got %v", current.VolumePending, pv2.Status.Phase)
}
}
func TestSetDefaultPersistentVolumeClaim(t *testing.T) {
pvc := &current.PersistentVolumeClaim{}
obj2 := roundTrip(t, runtime.Object(pvc))
pvc2 := obj2.(*current.PersistentVolumeClaim)
if pvc2.Status.Phase != current.ClaimPending {
t.Errorf("Expected claim phase %v, got %v", current.ClaimPending, pvc2.Status.Phase)
}
}
// Test that we use "legacy" fields if "modern" fields are not provided. // Test that we use "legacy" fields if "modern" fields are not provided.
func TestSetDefaulEndpointsLegacy(t *testing.T) { func TestSetDefaulEndpointsLegacy(t *testing.T) {
in := &current.Endpoints{ in := &current.Endpoints{

View File

@ -229,6 +229,8 @@ const (
type PersistentVolumePhase string type PersistentVolumePhase string
const ( const (
// used for PersistentVolumes that are not available
VolumePending PersistentVolumePhase = "Pending"
// used for PersistentVolumes that are not yet bound // used for PersistentVolumes that are not yet bound
VolumeAvailable PersistentVolumePhase = "Available" VolumeAvailable PersistentVolumePhase = "Available"
// used for PersistentVolumes that are bound // used for PersistentVolumes that are bound

View File

@ -112,6 +112,16 @@ func init() {
obj.Type = SecretTypeOpaque obj.Type = SecretTypeOpaque
} }
}, },
func(obj *PersistentVolume) {
if obj.Status.Phase == "" {
obj.Status.Phase = VolumePending
}
},
func(obj *PersistentVolumeClaim) {
if obj.Status.Phase == "" {
obj.Status.Phase = ClaimPending
}
},
func(obj *Endpoints) { func(obj *Endpoints) {
if obj.Protocol == "" { if obj.Protocol == "" {
obj.Protocol = ProtocolTCP obj.Protocol = ProtocolTCP

View File

@ -154,6 +154,26 @@ func TestSetDefaultService(t *testing.T) {
} }
} }
func TestSetDefaultPersistentVolume(t *testing.T) {
pv := &current.PersistentVolume{}
obj2 := roundTrip(t, runtime.Object(pv))
pv2 := obj2.(*current.PersistentVolume)
if pv2.Status.Phase != current.VolumePending {
t.Errorf("Expected volume phase %v, got %v", current.VolumePending, pv2.Status.Phase)
}
}
func TestSetDefaultPersistentVolumeClaim(t *testing.T) {
pvc := &current.PersistentVolumeClaim{}
obj2 := roundTrip(t, runtime.Object(pvc))
pvc2 := obj2.(*current.PersistentVolumeClaim)
if pvc2.Status.Phase != current.ClaimPending {
t.Errorf("Expected claim phase %v, got %v", current.ClaimPending, pvc2.Status.Phase)
}
}
func TestSetDefaultSecret(t *testing.T) { func TestSetDefaultSecret(t *testing.T) {
s := &current.Secret{} s := &current.Secret{}
obj2 := roundTrip(t, runtime.Object(s)) obj2 := roundTrip(t, runtime.Object(s))

View File

@ -198,6 +198,8 @@ const (
type PersistentVolumePhase string type PersistentVolumePhase string
const ( const (
// used for PersistentVolumes that are not available
VolumePending PersistentVolumePhase = "Pending"
// used for PersistentVolumes that are not yet bound // used for PersistentVolumes that are not yet bound
VolumeAvailable PersistentVolumePhase = "Available" VolumeAvailable PersistentVolumePhase = "Available"
// used for PersistentVolumes that are bound // used for PersistentVolumes that are bound

View File

@ -102,6 +102,16 @@ func init() {
obj.Type = SecretTypeOpaque obj.Type = SecretTypeOpaque
} }
}, },
func(obj *PersistentVolume) {
if obj.Status.Phase == "" {
obj.Status.Phase = VolumePending
}
},
func(obj *PersistentVolumeClaim) {
if obj.Status.Phase == "" {
obj.Status.Phase = ClaimPending
}
},
func(obj *Endpoints) { func(obj *Endpoints) {
for i := range obj.Subsets { for i := range obj.Subsets {
ss := &obj.Subsets[i] ss := &obj.Subsets[i]

View File

@ -174,6 +174,26 @@ func TestSetDefaultSecret(t *testing.T) {
} }
} }
func TestSetDefaultPersistentVolume(t *testing.T) {
pv := &current.PersistentVolume{}
obj2 := roundTrip(t, runtime.Object(pv))
pv2 := obj2.(*current.PersistentVolume)
if pv2.Status.Phase != current.VolumePending {
t.Errorf("Expected volume phase %v, got %v", current.VolumePending, pv2.Status.Phase)
}
}
func TestSetDefaultPersistentVolumeClaim(t *testing.T) {
pvc := &current.PersistentVolumeClaim{}
obj2 := roundTrip(t, runtime.Object(pvc))
pvc2 := obj2.(*current.PersistentVolumeClaim)
if pvc2.Status.Phase != current.ClaimPending {
t.Errorf("Expected claim phase %v, got %v", current.ClaimPending, pvc2.Status.Phase)
}
}
func TestSetDefaulEndpointsProtocol(t *testing.T) { func TestSetDefaulEndpointsProtocol(t *testing.T) {
in := &current.Endpoints{Subsets: []current.EndpointSubset{ in := &current.Endpoints{Subsets: []current.EndpointSubset{
{Ports: []current.EndpointPort{{}, {Protocol: "UDP"}, {}}}, {Ports: []current.EndpointPort{{}, {Protocol: "UDP"}, {}}},

View File

@ -334,6 +334,8 @@ const (
type PersistentVolumePhase string type PersistentVolumePhase string
const ( const (
// used for PersistentVolumes that are not available
VolumePending PersistentVolumePhase = "Pending"
// used for PersistentVolumes that are not yet bound // used for PersistentVolumes that are not yet bound
VolumeAvailable PersistentVolumePhase = "Available" VolumeAvailable PersistentVolumePhase = "Available"
// used for PersistentVolumes that are bound // used for PersistentVolumes that are bound

View File

@ -58,6 +58,9 @@ func validNewPersistentVolume(name string) *api.PersistentVolume {
HostPath: &api.HostPathVolumeSource{Path: "/foo"}, HostPath: &api.HostPathVolumeSource{Path: "/foo"},
}, },
}, },
Status: api.PersistentVolumeStatus{
Phase: api.VolumePending,
},
} }
return pv return pv
} }

View File

@ -59,6 +59,9 @@ func validNewPersistentVolumeClaim(name, ns string) *api.PersistentVolumeClaim {
}, },
}, },
}, },
Status: api.PersistentVolumeClaimStatus{
Phase: api.ClaimPending,
},
} }
return pv return pv
} }

View File

@ -18,10 +18,11 @@ package volumeclaimbinder
import ( import (
"fmt" "fmt"
"reflect" "sync"
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache"
"github.com/GoogleCloudPlatform/kubernetes/pkg/controller/framework" "github.com/GoogleCloudPlatform/kubernetes/pkg/controller/framework"
@ -40,12 +41,17 @@ type PersistentVolumeClaimBinder struct {
claimController *framework.Controller claimController *framework.Controller
client binderClient client binderClient
stopChannels map[string]chan struct{} stopChannels map[string]chan struct{}
lock sync.RWMutex
} }
// NewPersistentVolumeClaimBinder creates a new PersistentVolumeClaimBinder // NewPersistentVolumeClaimBinder creates a new PersistentVolumeClaimBinder
func NewPersistentVolumeClaimBinder(kubeClient client.Interface, syncPeriod time.Duration) *PersistentVolumeClaimBinder { func NewPersistentVolumeClaimBinder(kubeClient client.Interface, syncPeriod time.Duration) *PersistentVolumeClaimBinder {
volumeIndex := NewPersistentVolumeOrderedIndex() volumeIndex := NewPersistentVolumeOrderedIndex()
binderClient := NewBinderClient(kubeClient) binderClient := NewBinderClient(kubeClient)
binder := &PersistentVolumeClaimBinder{
volumeIndex: volumeIndex,
client: binderClient,
}
_, volumeController := framework.NewInformer( _, volumeController := framework.NewInformer(
&cache.ListWatch{ &cache.ListWatch{
@ -59,23 +65,9 @@ func NewPersistentVolumeClaimBinder(kubeClient client.Interface, syncPeriod time
&api.PersistentVolume{}, &api.PersistentVolume{},
syncPeriod, syncPeriod,
framework.ResourceEventHandlerFuncs{ framework.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) { AddFunc: binder.addVolume,
volume := obj.(*api.PersistentVolume) UpdateFunc: binder.updateVolume,
volumeIndex.Indexer.Add(volume) DeleteFunc: binder.deleteVolume,
syncVolume(binderClient, volume)
},
UpdateFunc: func(oldObj, newObj interface{}) {
oldVolume := oldObj.(*api.PersistentVolume)
newVolume := newObj.(*api.PersistentVolume)
volumeIndex.Indexer.Update(newVolume)
if updateRequired(oldVolume, newVolume) {
syncVolume(binderClient, newVolume)
}
},
DeleteFunc: func(obj interface{}) {
volume := obj.(*api.PersistentVolume)
volumeIndex.Indexer.Delete(volume)
},
}, },
) )
_, claimController := framework.NewInformer( _, claimController := framework.NewInformer(
@ -90,75 +82,186 @@ func NewPersistentVolumeClaimBinder(kubeClient client.Interface, syncPeriod time
&api.PersistentVolumeClaim{}, &api.PersistentVolumeClaim{},
syncPeriod, syncPeriod,
framework.ResourceEventHandlerFuncs{ framework.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) { AddFunc: binder.addClaim,
claim := obj.(*api.PersistentVolumeClaim) UpdateFunc: binder.updateClaim,
syncClaim(volumeIndex, binderClient, claim)
},
UpdateFunc: func(oldObj, newObj interface{}) {
newClaim := newObj.(*api.PersistentVolumeClaim)
if newClaim.Status.VolumeRef == nil {
syncClaim(volumeIndex, binderClient, newClaim)
}
},
}, },
) )
binder := &PersistentVolumeClaimBinder{ binder.claimController = claimController
volumeController: volumeController, binder.volumeController = volumeController
claimController: claimController,
volumeIndex: volumeIndex,
client: binderClient,
}
return binder return binder
} }
func updateRequired(oldVolume, newVolume *api.PersistentVolume) bool { func (binder *PersistentVolumeClaimBinder) addVolume(obj interface{}) {
// Spec changes affect indexing and sorting volumes binder.lock.Lock()
if !reflect.DeepEqual(oldVolume.Spec, newVolume.Spec) { defer binder.lock.Unlock()
return true volume := obj.(*api.PersistentVolume)
} syncVolume(binder.volumeIndex, binder.client, volume)
if !reflect.DeepEqual(oldVolume.Status, newVolume.Status) {
return true
}
return false
} }
func syncVolume(binderClient binderClient, volume *api.PersistentVolume) (err error) { func (binder *PersistentVolumeClaimBinder) updateVolume(oldObj, newObj interface{}) {
binder.lock.Lock()
defer binder.lock.Unlock()
newVolume := newObj.(*api.PersistentVolume)
binder.volumeIndex.Update(newVolume)
syncVolume(binder.volumeIndex, binder.client, newVolume)
}
func (binder *PersistentVolumeClaimBinder) deleteVolume(obj interface{}) {
binder.lock.Lock()
defer binder.lock.Unlock()
volume := obj.(*api.PersistentVolume)
binder.volumeIndex.Delete(volume)
}
func (binder *PersistentVolumeClaimBinder) addClaim(obj interface{}) {
binder.lock.Lock()
defer binder.lock.Unlock()
claim := obj.(*api.PersistentVolumeClaim)
syncClaim(binder.volumeIndex, binder.client, claim)
}
func (binder *PersistentVolumeClaimBinder) updateClaim(oldObj, newObj interface{}) {
binder.lock.Lock()
defer binder.lock.Unlock()
newClaim := newObj.(*api.PersistentVolumeClaim)
syncClaim(binder.volumeIndex, binder.client, newClaim)
}
func syncVolume(volumeIndex *persistentVolumeOrderedIndex, binderClient binderClient, volume *api.PersistentVolume) (err error) {
glog.V(5).Infof("Synchronizing PersistentVolume[%s]\n", volume.Name) glog.V(5).Infof("Synchronizing PersistentVolume[%s]\n", volume.Name)
if volume.Spec.ClaimRef != nil { // volumes can be in one of the following states:
if volume.Status.Phase == api.VolumeAvailable { //
volume.Status.Phase = api.VolumeBound // VolumePending -- default value -- not bound to a claim and not yet processed through this controller.
_, err := binderClient.UpdatePersistentVolumeStatus(volume) // VolumeAvailable -- not bound to a claim, but processed at least once and found in this controller's volumeIndex.
if err != nil { // VolumeBound -- bound to a claim because volume.Spec.ClaimRef != nil. Claim status may not be correct.
return fmt.Errorf("Error updating pv.status: %v\n", err) // VolumeReleased -- volume.Spec.ClaimRef != nil but the claim has been deleted by the user.
currentPhase := volume.Status.Phase
nextPhase := currentPhase
switch currentPhase {
// pending volumes are available only after indexing in order to be matched to claims.
case api.VolumePending:
_, exists, err := volumeIndex.Get(volume)
if err != nil {
return err
}
if !exists {
volumeIndex.Add(volume)
}
glog.V(5).Infof("PersistentVolume[%s] is now available\n", volume.Name)
nextPhase = api.VolumeAvailable
// available volumes await a claim
case api.VolumeAvailable:
if volume.Spec.ClaimRef != nil {
_, err := binderClient.GetPersistentVolumeClaim(volume.Spec.ClaimRef.Namespace, volume.Spec.ClaimRef.Name)
if err == nil {
// change of phase will trigger an update event with the newly bound volume
glog.V(5).Infof("PersistentVolume[%s] is now bound\n", volume.Name)
nextPhase = api.VolumeBound
} else {
if errors.IsNotFound(err) {
nextPhase = api.VolumeReleased
}
} }
} }
//bound volumes require verification of their bound claims
case api.VolumeBound:
if volume.Spec.ClaimRef == nil {
return fmt.Errorf("PersistentVolume[%s] expected to be bound but found nil claimRef: %+v", volume)
} else {
claim, err := binderClient.GetPersistentVolumeClaim(volume.Spec.ClaimRef.Namespace, volume.Spec.ClaimRef.Name)
if err == nil {
// bound and active. Build claim status as needed.
if claim.Status.VolumeRef == nil {
syncClaimStatus(binderClient, volume, claim)
}
} else {
if errors.IsNotFound(err) {
nextPhase = api.VolumeReleased
} else {
return err
}
}
}
// released volumes require recycling
case api.VolumeReleased:
if volume.Spec.ClaimRef == nil {
return fmt.Errorf("PersistentVolume[%s] expected to be bound but found nil claimRef: %+v", volume)
} else {
// TODO: implement Recycle method on plugins
}
}
if currentPhase != nextPhase {
volume.Status.Phase = nextPhase
volume, err := binderClient.UpdatePersistentVolumeStatus(volume)
if err != nil {
// Rollback to previous phase
volume.Status.Phase = currentPhase
}
volumeIndex.Update(volume)
}
return nil
}
func syncClaim(volumeIndex *persistentVolumeOrderedIndex, binderClient binderClient, claim *api.PersistentVolumeClaim) (err error) {
glog.V(5).Infof("Synchronizing PersistentVolumeClaim[%s]\n", claim.Name)
// claims can be in one of the following states:
//
// ClaimPending -- default value -- not bound to a claim. A volume that matches the claim may not exist.
// ClaimBound -- bound to a volume. claim.Status.VolumeRef != nil
currentPhase := claim.Status.Phase
nextPhase := currentPhase
switch currentPhase {
// pending claims await a matching volume
case api.ClaimPending:
volume, err := volumeIndex.FindBestMatchForClaim(claim)
if err != nil {
return err
}
if volume == nil {
return fmt.Errorf("A volume match does not exist for persistent claim: %s", claim.Name)
}
// verify the volume is still claimed by a user claimRef, err := api.GetReference(claim)
if claim, err := binderClient.GetPersistentVolumeClaim(volume.Spec.ClaimRef.Namespace, volume.Spec.ClaimRef.Name); err == nil {
glog.V(5).Infof("PersistentVolume[%s] is bound to PersistentVolumeClaim[%s]\n", volume.Name, volume.Spec.ClaimRef.Name)
// rebuild the Claim's Status as needed
if claim.Status.VolumeRef == nil {
syncClaimStatus(binderClient, volume, claim)
}
} else {
//claim was deleted by user.
glog.V(3).Infof("PersistentVolumeClaim[%s] unbound from PersistentVolume[%s]\n", volume.Spec.ClaimRef.Name, volume.Name)
// volume.Spec.ClaimRef is deliberately left non-nil so that another process can recycle the newly released volume
volume.Status.Phase = api.VolumeReleased
volume, err = binderClient.UpdatePersistentVolumeStatus(volume)
if err != nil {
return fmt.Errorf("Error updating pv: %+v\n", err)
}
}
} else {
volume.Status.Phase = api.VolumeAvailable
_, err := binderClient.UpdatePersistentVolumeStatus(volume)
if err != nil { if err != nil {
return fmt.Errorf("Error updating pv.status: %v\n", err) return fmt.Errorf("Unexpected error getting claim reference: %v\n", err)
} }
// make a binding reference to the claim.
// triggers update of the volume in this controller, which builds claim status
volume.Spec.ClaimRef = claimRef
volume, err = binderClient.UpdatePersistentVolume(volume)
if err == nil {
nextPhase = api.ClaimBound
}
if err != nil {
// Rollback by unsetting the ClaimRef on the volume pointer.
// the volume in the index will be unbound again and ready to be matched.
volume.Spec.ClaimRef = nil
// Rollback by restoring original phase to claim pointer
nextPhase = api.ClaimPending
return fmt.Errorf("Error updating volume: %+v\n", err)
}
// bound claims requires no maintenance. Deletion by the user is the last lifecycle phase.
case api.ClaimBound:
// This is the end of a claim's lifecycle.
// After claim deletion, a volume is recycled when it verifies its claim is unbound
glog.V(5).Infof("PersistentVolumeClaime[%s] is bound\n", claim.Name)
}
if currentPhase != nextPhase {
claim.Status.Phase = nextPhase
binderClient.UpdatePersistentVolumeClaimStatus(claim)
} }
return nil return nil
} }
@ -168,6 +271,7 @@ func syncClaimStatus(binderClient binderClient, volume *api.PersistentVolume, cl
if err != nil { if err != nil {
return fmt.Errorf("Unexpected error getting volume reference: %v\n", err) return fmt.Errorf("Unexpected error getting volume reference: %v\n", err)
} }
// all "actuals" are transferred from PV to PVC so the user knows what // all "actuals" are transferred from PV to PVC so the user knows what
// type of volume they actually got for their claim // type of volume they actually got for their claim
claim.Status.Phase = api.ClaimBound claim.Status.Phase = api.ClaimBound
@ -176,63 +280,16 @@ func syncClaimStatus(binderClient binderClient, volume *api.PersistentVolume, cl
claim.Status.Capacity = volume.Spec.Capacity claim.Status.Capacity = volume.Spec.Capacity
_, err = binderClient.UpdatePersistentVolumeClaimStatus(claim) _, err = binderClient.UpdatePersistentVolumeClaimStatus(claim)
if err != nil { if err != nil {
claim.Status.Phase = api.ClaimPending claim.Status.Phase = api.ClaimPending
claim.Status.VolumeRef = nil claim.Status.VolumeRef = nil
claim.Status.AccessModes = nil claim.Status.AccessModes = nil
claim.Status.Capacity = nil claim.Status.Capacity = nil
} }
return err return err
} }
func syncClaim(volumeIndex *persistentVolumeOrderedIndex, binderClient binderClient, claim *api.PersistentVolumeClaim) (err error) { // Run starts all of this binder's control loops
glog.V(5).Infof("Synchronizing PersistentVolumeClaim[%s]\n", claim.Name)
if claim.Status.VolumeRef != nil {
glog.V(5).Infof("PersistentVolumeClaim[%s] is bound to PersistentVolume[%s]\n", claim.Name, claim.Status.VolumeRef.Name)
return nil
}
volume, err := volumeIndex.FindBestMatchForClaim(claim)
if err != nil {
return err
}
if volume != nil {
claimRef, err := api.GetReference(claim)
if err != nil {
return fmt.Errorf("Unexpected error getting claim reference: %v\n", err)
}
// make a binding reference to the claim
volume.Spec.ClaimRef = claimRef
volume, err = binderClient.UpdatePersistentVolume(volume)
if err != nil {
// volume no longer bound
volume.Spec.ClaimRef = nil
return fmt.Errorf("Error updating volume: %+v\n", err)
} else {
err = syncClaimStatus(binderClient, volume, claim)
if err != nil {
return fmt.Errorf("Error update claim.status: %+v\n", err)
}
}
} else {
glog.V(5).Infof("No volume match found for PersistentVolumeClaim[%s]\n", claim.UID)
if claim.Status.Phase != api.ClaimPending {
claim.Status.Phase = api.ClaimPending
_, err := binderClient.UpdatePersistentVolumeClaimStatus(claim)
if err != nil {
return fmt.Errorf("Error updating pvclaim.status: %v\n", err)
}
}
}
return nil
}
func (controller *PersistentVolumeClaimBinder) Run() { func (controller *PersistentVolumeClaimBinder) Run() {
glog.V(5).Infof("Starting PersistentVolumeClaimBinder\n") glog.V(5).Infof("Starting PersistentVolumeClaimBinder\n")
if controller.stopChannels == nil { if controller.stopChannels == nil {
@ -250,6 +307,7 @@ func (controller *PersistentVolumeClaimBinder) Run() {
} }
} }
// Stop gracefully shuts down this binder
func (controller *PersistentVolumeClaimBinder) Stop() { func (controller *PersistentVolumeClaimBinder) Stop() {
glog.V(5).Infof("Stopping PersistentVolumeClaimBinder\n") glog.V(5).Infof("Stopping PersistentVolumeClaimBinder\n")
for name, stopChan := range controller.stopChannels { for name, stopChan := range controller.stopChannels {

View File

@ -17,12 +17,12 @@ limitations under the License.
package volumeclaimbinder package volumeclaimbinder
import ( import (
"fmt"
"reflect" "reflect"
"testing" "testing"
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/testclient" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/testclient"
@ -51,7 +51,6 @@ func TestRunStop(t *testing.T) {
} }
func TestExampleObjects(t *testing.T) { func TestExampleObjects(t *testing.T) {
scenarios := map[string]struct { scenarios := map[string]struct {
expected interface{} expected interface{}
}{ }{
@ -167,56 +166,7 @@ func TestExampleObjects(t *testing.T) {
} }
} }
func TestRequiresUpdate(t *testing.T) {
old := &api.PersistentVolume{
Spec: api.PersistentVolumeSpec{
AccessModes: []api.AccessModeType{api.ReadWriteOnce},
Capacity: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("5Gi"),
},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{
Path: "/tmp/data02",
},
},
},
}
new := &api.PersistentVolume{
Spec: api.PersistentVolumeSpec{
AccessModes: []api.AccessModeType{api.ReadWriteOnce},
Capacity: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("5Gi"),
},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{
Path: "/tmp/data02",
},
},
ClaimRef: &api.ObjectReference{Name: "foo"},
},
}
if !updateRequired(old, new) {
t.Errorf("Update expected for the new volume with added ClaimRef")
}
old.Spec.ClaimRef = new.Spec.ClaimRef
old.Status.Phase = api.VolumeBound
if !updateRequired(old, new) {
t.Errorf("Update expected for the new volume with added Status")
}
new.Status.Phase = old.Status.Phase
if updateRequired(old, new) {
t.Errorf("No updated expected for identical objects")
}
}
func TestBindingWithExamples(t *testing.T) { func TestBindingWithExamples(t *testing.T) {
api.ForTesting_ReferencesAllowBlankSelfLinks = true api.ForTesting_ReferencesAllowBlankSelfLinks = true
o := testclient.NewObjects(api.Scheme) o := testclient.NewObjects(api.Scheme)
if err := testclient.AddObjectsFromPath("../../examples/persistent-volumes/claims/claim-01.yaml", o); err != nil { if err := testclient.AddObjectsFromPath("../../examples/persistent-volumes/claims/claim-01.yaml", o); err != nil {
@ -245,7 +195,7 @@ func TestBindingWithExamples(t *testing.T) {
} }
volumeIndex.Add(pv) volumeIndex.Add(pv)
syncVolume(mockClient, pv) syncVolume(volumeIndex, mockClient, pv)
if pv.Status.Phase != api.VolumeAvailable { if pv.Status.Phase != api.VolumeAvailable {
t.Errorf("Expected phase %s but got %s", api.VolumeBound, pv.Status.Phase) t.Errorf("Expected phase %s but got %s", api.VolumeBound, pv.Status.Phase)
@ -261,10 +211,13 @@ func TestBindingWithExamples(t *testing.T) {
t.Errorf("Expected ClaimRef but got nil for volume: %+v\n", pv) t.Errorf("Expected ClaimRef but got nil for volume: %+v\n", pv)
} }
syncVolume(mockClient, pv) // first sync verifies the new bound claim, advances state, triggering update
syncVolume(volumeIndex, mockClient, pv)
// second sync verifies claim, sees missing claim status and builds it
syncVolume(volumeIndex, mockClient, pv)
if claim.Status.VolumeRef == nil { if claim.Status.VolumeRef == nil {
t.Error("Expected claim to be bound to volume") t.Fatalf("Expected claim to be bound to volume")
} }
if pv.Status.Phase != api.VolumeBound { if pv.Status.Phase != api.VolumeBound {
@ -283,7 +236,7 @@ func TestBindingWithExamples(t *testing.T) {
// pretend the user deleted their claim // pretend the user deleted their claim
mockClient.claim = nil mockClient.claim = nil
syncVolume(mockClient, pv) syncVolume(volumeIndex, mockClient, pv)
if pv.Status.Phase != api.VolumeReleased { if pv.Status.Phase != api.VolumeReleased {
t.Errorf("Expected phase %s but got %s", api.VolumeReleased, pv.Status.Phase) t.Errorf("Expected phase %s but got %s", api.VolumeReleased, pv.Status.Phase)
@ -311,7 +264,7 @@ func (c *mockBinderClient) GetPersistentVolumeClaim(namespace, name string) (*ap
if c.claim != nil { if c.claim != nil {
return c.claim, nil return c.claim, nil
} else { } else {
return nil, fmt.Errorf("Claim does not exist") return nil, errors.NewNotFound("persistentVolume", name)
} }
} }

View File

@ -76,8 +76,25 @@ func TestPersistentVolumeClaimBinder(t *testing.T) {
t.Errorf("expected 3 PVCs, got %#v", len(claims.Items)) t.Errorf("expected 3 PVCs, got %#v", len(claims.Items))
} }
// make sure the binder has caught up // the binder will eventually catch up and set status on Claims
time.Sleep(2 * time.Second) watch, err := client.PersistentVolumeClaims(api.NamespaceDefault).Watch(labels.Everything(), fields.Everything(), "0")
if err != nil {
t.Fatalf("Couldn't subscribe to PersistentVolumeClaims: %v", err)
}
defer watch.Stop()
boundCount := 0
expectedBoundCount := 2
for {
event := <-watch.ResultChan()
claim := event.Object.(*api.PersistentVolumeClaim)
if claim.Status.VolumeRef != nil {
boundCount++
}
if boundCount == expectedBoundCount {
break
}
}
for _, claim := range createTestClaims() { for _, claim := range createTestClaims() {
claim, err := client.PersistentVolumeClaims(api.NamespaceDefault).Get(claim.Name) claim, err := client.PersistentVolumeClaims(api.NamespaceDefault).Get(claim.Name)
@ -86,7 +103,7 @@ func TestPersistentVolumeClaimBinder(t *testing.T) {
} }
if (claim.Name == "claim01" || claim.Name == "claim02") && claim.Status.VolumeRef == nil { if (claim.Name == "claim01" || claim.Name == "claim02") && claim.Status.VolumeRef == nil {
t.Errorf("Expected claim to be bound: %v", claim) t.Errorf("Expected claim to be bound: %+v", claim)
} }
if claim.Name == "claim03" && claim.Status.VolumeRef != nil { if claim.Name == "claim03" && claim.Status.VolumeRef != nil {
t.Errorf("Expected claim03 to be unbound: %v", claim) t.Errorf("Expected claim03 to be unbound: %v", claim)