// NewFixtureClients returns mocks of the OpenShift and Kubernetes clients func NewFixtureClients(o testclient.ObjectRetriever) (osclient.Interface, kclient.Interface) { oc := &Fake{} oc.AddReactor("*", "*", testclient.ObjectReaction(o, kapi.RESTMapper)) kc := &testclient.Fake{} kc.AddReactor("*", "*", testclient.ObjectReaction(o, kapi.RESTMapper)) return oc, kc }
func TestPersistentClaimReadOnlyFlag(t *testing.T) { pv := &api.PersistentVolume{ ObjectMeta: api.ObjectMeta{ Name: "pvA", }, Spec: api.PersistentVolumeSpec{ PersistentVolumeSource: api.PersistentVolumeSource{ Glusterfs: &api.GlusterfsVolumeSource{EndpointsName: "ep", Path: "vol", ReadOnly: false}, }, ClaimRef: &api.ObjectReference{ Name: "claimA", }, }, } claim := &api.PersistentVolumeClaim{ ObjectMeta: api.ObjectMeta{ Name: "claimA", Namespace: "nsA", }, Spec: api.PersistentVolumeClaimSpec{ VolumeName: "pvA", }, Status: api.PersistentVolumeClaimStatus{ Phase: api.ClaimBound, }, } ep := &api.Endpoints{ ObjectMeta: api.ObjectMeta{ Name: "ep", }, Subsets: []api.EndpointSubset{{ Addresses: []api.EndpointAddress{{IP: "127.0.0.1"}}, Ports: []api.EndpointPort{{"foo", 80, api.ProtocolTCP}}, }}, } o := testclient.NewObjects(api.Scheme, api.Scheme) o.Add(pv) o.Add(claim) o.Add(ep) client := &testclient.Fake{} client.AddReactor("*", "*", testclient.ObjectReaction(o, testapi.Default.RESTMapper())) plugMgr := volume.VolumePluginMgr{} plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeVolumeHost("/tmp/fake", client, nil)) plug, _ := plugMgr.FindPluginByName(glusterfsPluginName) // readOnly bool is supplied by persistent-claim volume source when its builder creates other volumes spec := volume.NewSpecFromPersistentVolume(pv, true) pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}} builder, _ := plug.NewBuilder(spec, pod, volume.VolumeOptions{}) if !builder.IsReadOnly() { t.Errorf("Expected true for builder.IsReadOnly") } }
func TestPersistentClaimReadOnlyFlag(t *testing.T) { lun := 0 pv := &api.PersistentVolume{ ObjectMeta: api.ObjectMeta{ Name: "pvA", }, Spec: api.PersistentVolumeSpec{ PersistentVolumeSource: api.PersistentVolumeSource{ FC: &api.FCVolumeSource{ TargetWWNs: []string{"some_wwn"}, FSType: "ext4", Lun: &lun, }, }, ClaimRef: &api.ObjectReference{ Name: "claimA", }, }, } claim := &api.PersistentVolumeClaim{ ObjectMeta: api.ObjectMeta{ Name: "claimA", Namespace: "nsA", }, Spec: api.PersistentVolumeClaimSpec{ VolumeName: "pvA", }, Status: api.PersistentVolumeClaimStatus{ Phase: api.ClaimBound, }, } o := testclient.NewObjects(api.Scheme, api.Scheme) o.Add(pv) o.Add(claim) client := &testclient.Fake{} client.AddReactor("*", "*", testclient.ObjectReaction(o, testapi.Default.RESTMapper())) plugMgr := volume.VolumePluginMgr{} plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeVolumeHost("/tmp/fake", client, nil)) plug, _ := plugMgr.FindPluginByName(fcPluginName) // readOnly bool is supplied by persistent-claim volume source when its builder creates other volumes spec := volume.NewSpecFromPersistentVolume(pv, true) pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}} builder, _ := plug.NewBuilder(spec, pod, volume.VolumeOptions{}) if !builder.IsReadOnly() { t.Errorf("Expected true for builder.IsReadOnly") } }
func TestPersistentClaimReadOnlyFlag(t *testing.T) { pv := &api.PersistentVolume{ ObjectMeta: api.ObjectMeta{ Name: "pvA", }, Spec: api.PersistentVolumeSpec{ PersistentVolumeSource: api.PersistentVolumeSource{ GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}, }, ClaimRef: &api.ObjectReference{ Name: "claimA", }, }, } claim := &api.PersistentVolumeClaim{ ObjectMeta: api.ObjectMeta{ Name: "claimA", Namespace: "nsA", }, Spec: api.PersistentVolumeClaimSpec{ VolumeName: "pvA", }, Status: api.PersistentVolumeClaimStatus{ Phase: api.ClaimBound, }, } o := testclient.NewObjects(api.Scheme, api.Scheme) o.Add(pv) o.Add(claim) client := &testclient.Fake{} client.AddReactor("*", "*", testclient.ObjectReaction(o, testapi.Default.RESTMapper())) tmpDir, err := ioutil.TempDir(os.TempDir(), "gcepdTest") if err != nil { t.Fatalf("can't make a temp dir: %v", err) } defer os.RemoveAll(tmpDir) plugMgr := volume.VolumePluginMgr{} plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeVolumeHost(tmpDir, client, nil)) plug, _ := plugMgr.FindPluginByName(gcePersistentDiskPluginName) // readOnly bool is supplied by persistent-claim volume source when its builder creates other volumes spec := volume.NewSpecFromPersistentVolume(pv, true) pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}} builder, _ := plug.NewBuilder(spec, pod, volume.VolumeOptions{}) if !builder.GetAttributes().ReadOnly { t.Errorf("Expected true for builder.IsReadOnly") } }
func TestPersistentClaimReadOnlyFlag(t *testing.T) { pv := &api.PersistentVolume{ ObjectMeta: api.ObjectMeta{ Name: "pvA", }, Spec: api.PersistentVolumeSpec{ PersistentVolumeSource: api.PersistentVolumeSource{ ISCSI: &api.ISCSIVolumeSource{ TargetPortal: "127.0.0.1:3260", IQN: "iqn.2014-12.server:storage.target01", FSType: "ext4", Lun: 0, }, }, ClaimRef: &api.ObjectReference{ Name: "claimA", }, }, } claim := &api.PersistentVolumeClaim{ ObjectMeta: api.ObjectMeta{ Name: "claimA", Namespace: "nsA", }, Spec: api.PersistentVolumeClaimSpec{ VolumeName: "pvA", }, Status: api.PersistentVolumeClaimStatus{ Phase: api.ClaimBound, }, } o := testclient.NewObjects(api.Scheme, api.Scheme) o.Add(pv) o.Add(claim) client := &testclient.Fake{} client.AddReactor("*", "*", testclient.ObjectReaction(o, latest.RESTMapper)) plugMgr := volume.VolumePluginMgr{} plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeVolumeHost("/tmp/fake", client, nil)) plug, _ := plugMgr.FindPluginByName(iscsiPluginName) // readOnly bool is supplied by persistent-claim volume source when its builder creates other volumes spec := volume.NewSpecFromPersistentVolume(pv, true) pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}} builder, _ := plug.NewBuilder(spec, pod, volume.VolumeOptions{}, nil) if !builder.IsReadOnly() { t.Errorf("Expected true for builder.IsReadOnly") } }
// NewSimpleFake returns a client that will respond with the provided objects func NewSimpleFake(objects ...runtime.Object) *Fake { o := ktestclient.NewObjects(kapi.Scheme, kapi.Codecs.UniversalDecoder()) for _, obj := range objects { if err := o.Add(obj); err != nil { panic(err) } } fakeClient := &Fake{} fakeClient.AddReactor("*", "*", ktestclient.ObjectReaction(o, registered.RESTMapper())) return fakeClient }
// NewSimpleFake returns a client that will respond with the provided objects func NewSimpleFake(objects ...runtime.Object) *Fake { o := ktestclient.NewObjects(kapi.Scheme, kapi.Scheme) for _, obj := range objects { if err := o.Add(obj); err != nil { panic(err) } } fakeClient := &Fake{} fakeClient.AddReactor("*", "*", ktestclient.ObjectReaction(o, latest.RESTMapper)) return fakeClient }
func TestPersistentClaimReadOnlyFlag(t *testing.T) { pv := &api.PersistentVolume{ ObjectMeta: api.ObjectMeta{ Name: "pvA", }, Spec: api.PersistentVolumeSpec{ PersistentVolumeSource: api.PersistentVolumeSource{ RBD: &api.RBDVolumeSource{ CephMonitors: []string{"a", "b"}, RBDImage: "bar", FSType: "ext4", }, }, ClaimRef: &api.ObjectReference{ Name: "claimA", }, }, } claim := &api.PersistentVolumeClaim{ ObjectMeta: api.ObjectMeta{ Name: "claimA", Namespace: "nsA", }, Spec: api.PersistentVolumeClaimSpec{ VolumeName: "pvA", }, Status: api.PersistentVolumeClaimStatus{ Phase: api.ClaimBound, }, } o := testclient.NewObjects(api.Scheme, api.Scheme) o.Add(pv) o.Add(claim) client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, latest.RESTMapper)} plugMgr := volume.VolumePluginMgr{} plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeVolumeHost("/tmp/fake", client, nil)) plug, _ := plugMgr.FindPluginByName(rbdPluginName) // readOnly bool is supplied by persistent-claim volume source when its builder creates other volumes spec := volume.NewSpecFromPersistentVolume(pv, true) pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}} builder, _ := plug.NewBuilder(spec, pod, volume.VolumeOptions{}, nil) if !builder.IsReadOnly() { t.Errorf("Expected true for builder.IsReadOnly") } }
func TestNewBuilderClaimNotBound(t *testing.T) { pv := &api.PersistentVolume{ ObjectMeta: api.ObjectMeta{ Name: "pvC", }, Spec: api.PersistentVolumeSpec{ PersistentVolumeSource: api.PersistentVolumeSource{ GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}, }, }, } claim := &api.PersistentVolumeClaim{ ObjectMeta: api.ObjectMeta{ Name: "claimC", Namespace: "nsA", }, } podVolume := api.VolumeSource{ PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{ ReadOnly: false, ClaimName: "claimC", }, } o := testclient.NewObjects(api.Scheme, api.Scheme) o.Add(pv) o.Add(claim) client := &testclient.Fake{} client.AddReactor("*", "*", testclient.ObjectReaction(o, api.RESTMapper)) plugMgr := volume.VolumePluginMgr{} plugMgr.InitPlugins(testProbeVolumePlugins(), newTestHost(t, client)) plug, err := plugMgr.FindPluginByName("kubernetes.io/persistent-claim") if err != nil { t.Errorf("Can't find the plugin by name") } spec := &volume.Spec{ Name: "vol1", VolumeSource: podVolume, } pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}} builder, err := plug.NewBuilder(spec, pod, volume.VolumeOptions{}, nil) if builder != nil { t.Errorf("Expected a nil builder if the claim wasn't bound") } }
func TestRunStop(t *testing.T) { o := testclient.NewObjects(api.Scheme, api.Scheme) client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, api.RESTMapper)} binder := NewPersistentVolumeClaimBinder(client, 1*time.Second) if len(binder.stopChannels) != 0 { t.Errorf("Non-running binder should not have any stopChannels. Got %v", len(binder.stopChannels)) } binder.Run() if len(binder.stopChannels) != 2 { t.Errorf("Running binder should have exactly 2 stopChannels. Got %v", len(binder.stopChannels)) } binder.Stop() if len(binder.stopChannels) != 0 { t.Errorf("Non-running binder should not have any stopChannels. Got %v", len(binder.stopChannels)) } }
func TestRunStop(t *testing.T) { o := testclient.NewObjects(api.Scheme, api.Scheme) client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, api.RESTMapper)} nsController := NewNamespaceController(client, 1*time.Second) if nsController.StopEverything != nil { t.Errorf("Non-running manager should not have a stop channel. Got %v", nsController.StopEverything) } nsController.Run() if nsController.StopEverything == nil { t.Errorf("Running manager should have a stop channel. Got nil") } nsController.Stop() if nsController.StopEverything != nil { t.Errorf("Non-running manager should not have a stop channel. Got %v", nsController.StopEverything) } }
func TestExampleObjects(t *testing.T) { scenarios := map[string]struct { expected interface{} }{ "claims/claim-01.yaml": { expected: &api.PersistentVolumeClaim{ Spec: api.PersistentVolumeClaimSpec{ AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, Resources: api.ResourceRequirements{ Requests: api.ResourceList{ api.ResourceName(api.ResourceStorage): resource.MustParse("3Gi"), }, }, }, }, }, "claims/claim-02.yaml": { expected: &api.PersistentVolumeClaim{ Spec: api.PersistentVolumeClaimSpec{ AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, Resources: api.ResourceRequirements{ Requests: api.ResourceList{ api.ResourceName(api.ResourceStorage): resource.MustParse("8Gi"), }, }, }, }, }, "volumes/local-01.yaml": { expected: &api.PersistentVolume{ 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", }, }, }, }, }, "volumes/local-02.yaml": { expected: &api.PersistentVolume{ Spec: api.PersistentVolumeSpec{ AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, Capacity: api.ResourceList{ api.ResourceName(api.ResourceStorage): resource.MustParse("8Gi"), }, PersistentVolumeSource: api.PersistentVolumeSource{ HostPath: &api.HostPathVolumeSource{ Path: "/tmp/data02", }, }, PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimRecycle, }, }, }, } for name, scenario := range scenarios { o := testclient.NewObjects(api.Scheme, api.Scheme) if err := testclient.AddObjectsFromPath("../../../docs/user-guide/persistent-volumes/"+name, o, api.Scheme); err != nil { t.Fatal(err) } client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, api.RESTMapper)} if reflect.TypeOf(scenario.expected) == reflect.TypeOf(&api.PersistentVolumeClaim{}) { pvc, err := client.PersistentVolumeClaims("ns").Get("doesntmatter") if err != nil { t.Errorf("Error retrieving object: %v", err) } expected := scenario.expected.(*api.PersistentVolumeClaim) if pvc.Spec.AccessModes[0] != expected.Spec.AccessModes[0] { t.Errorf("Unexpected mismatch. Got %v wanted %v", pvc.Spec.AccessModes[0], expected.Spec.AccessModes[0]) } aQty := pvc.Spec.Resources.Requests[api.ResourceStorage] bQty := expected.Spec.Resources.Requests[api.ResourceStorage] aSize := aQty.Value() bSize := bQty.Value() if aSize != bSize { t.Errorf("Unexpected mismatch. Got %v wanted %v", aSize, bSize) } } if reflect.TypeOf(scenario.expected) == reflect.TypeOf(&api.PersistentVolume{}) { pv, err := client.PersistentVolumes().Get("doesntmatter") if err != nil { t.Errorf("Error retrieving object: %v", err) } expected := scenario.expected.(*api.PersistentVolume) if pv.Spec.AccessModes[0] != expected.Spec.AccessModes[0] { t.Errorf("Unexpected mismatch. Got %v wanted %v", pv.Spec.AccessModes[0], expected.Spec.AccessModes[0]) } aQty := pv.Spec.Capacity[api.ResourceStorage] bQty := expected.Spec.Capacity[api.ResourceStorage] aSize := aQty.Value() bSize := bQty.Value() if aSize != bSize { t.Errorf("Unexpected mismatch. Got %v wanted %v", aSize, bSize) } if pv.Spec.HostPath.Path != expected.Spec.HostPath.Path { t.Errorf("Unexpected mismatch. Got %v wanted %v", pv.Spec.HostPath.Path, expected.Spec.HostPath.Path) } } } }
func TestMissingFromIndex(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") 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, } // the default value of the PV is Pending. // if has previously been processed by the binder, it's status in etcd would be Available. // Only Pending volumes were being indexed and made ready for claims. pv.Status.Phase = api.VolumeAvailable // 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 err = syncClaim(volumeIndex, mockClient, claim) if err != nil { t.Fatalf("Expected Clam to be bound, instead got an error: %+v\n", err) } // 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) } }
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 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 TestNewBuilder(t *testing.T) { tests := []struct { pv *api.PersistentVolume claim *api.PersistentVolumeClaim plugin volume.VolumePlugin podVolume api.VolumeSource testFunc func(builder volume.Builder, plugin volume.VolumePlugin) error expectedFailure bool }{ { pv: &api.PersistentVolume{ ObjectMeta: api.ObjectMeta{ Name: "pvA", }, Spec: api.PersistentVolumeSpec{ PersistentVolumeSource: api.PersistentVolumeSource{ GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}, }, ClaimRef: &api.ObjectReference{ Name: "claimA", }, }, }, claim: &api.PersistentVolumeClaim{ ObjectMeta: api.ObjectMeta{ Name: "claimA", Namespace: "nsA", }, Spec: api.PersistentVolumeClaimSpec{ VolumeName: "pvA", }, Status: api.PersistentVolumeClaimStatus{ Phase: api.ClaimBound, }, }, podVolume: api.VolumeSource{ PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{ ReadOnly: false, ClaimName: "claimA", }, }, plugin: gce_pd.ProbeVolumePlugins()[0], testFunc: func(builder volume.Builder, plugin volume.VolumePlugin) error { if !strings.Contains(builder.GetPath(), util.EscapeQualifiedNameForDisk(plugin.Name())) { return fmt.Errorf("builder path expected to contain plugin name. Got: %s", builder.GetPath()) } return nil }, expectedFailure: false, }, { pv: &api.PersistentVolume{ ObjectMeta: api.ObjectMeta{ Name: "pvB", }, Spec: api.PersistentVolumeSpec{ PersistentVolumeSource: api.PersistentVolumeSource{ HostPath: &api.HostPathVolumeSource{Path: "/tmp"}, }, ClaimRef: &api.ObjectReference{ Name: "claimB", }, }, }, claim: &api.PersistentVolumeClaim{ ObjectMeta: api.ObjectMeta{ Name: "claimB", Namespace: "nsB", }, Spec: api.PersistentVolumeClaimSpec{ VolumeName: "pvA", }, }, podVolume: api.VolumeSource{ PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{ ReadOnly: false, ClaimName: "claimB", }, }, plugin: host_path.ProbeVolumePlugins()[0], testFunc: func(builder volume.Builder, plugin volume.VolumePlugin) error { if builder.GetPath() != "/tmp" { return fmt.Errorf("Expected HostPath.Path /tmp, got: %s", builder.GetPath()) } return nil }, expectedFailure: false, }, { pv: &api.PersistentVolume{ ObjectMeta: api.ObjectMeta{ Name: "pvA", }, Spec: api.PersistentVolumeSpec{ PersistentVolumeSource: api.PersistentVolumeSource{ GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}, }, }, }, claim: &api.PersistentVolumeClaim{ ObjectMeta: api.ObjectMeta{ Name: "claimA", Namespace: "nsA", }, Spec: api.PersistentVolumeClaimSpec{ VolumeName: "pvA", }, Status: api.PersistentVolumeClaimStatus{ Phase: api.ClaimBound, }, }, podVolume: api.VolumeSource{ PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{ ReadOnly: false, ClaimName: "claimA", }, }, plugin: gce_pd.ProbeVolumePlugins()[0], testFunc: func(builder volume.Builder, plugin volume.VolumePlugin) error { if builder != nil { return fmt.Errorf("Unexpected non-nil builder: %+v", builder) } return nil }, expectedFailure: true, // missing pv.Spec.ClaimRef }, { pv: &api.PersistentVolume{ ObjectMeta: api.ObjectMeta{ Name: "pvA", }, Spec: api.PersistentVolumeSpec{ PersistentVolumeSource: api.PersistentVolumeSource{ GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}, }, ClaimRef: &api.ObjectReference{ Name: "claimB", UID: types.UID("abc123"), }, }, }, claim: &api.PersistentVolumeClaim{ ObjectMeta: api.ObjectMeta{ Name: "claimA", Namespace: "nsA", UID: types.UID("def456"), }, Spec: api.PersistentVolumeClaimSpec{ VolumeName: "pvA", }, Status: api.PersistentVolumeClaimStatus{ Phase: api.ClaimBound, }, }, podVolume: api.VolumeSource{ PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{ ReadOnly: false, ClaimName: "claimA", }, }, plugin: gce_pd.ProbeVolumePlugins()[0], testFunc: func(builder volume.Builder, plugin volume.VolumePlugin) error { if builder != nil { return fmt.Errorf("Unexpected non-nil builder: %+v", builder) } return nil }, expectedFailure: true, // mismatched pv.Spec.ClaimRef and pvc }, } for _, item := range tests { o := testclient.NewObjects(api.Scheme, api.Scheme) o.Add(item.pv) o.Add(item.claim) client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, api.RESTMapper)} plugMgr := volume.VolumePluginMgr{} plugMgr.InitPlugins(testProbeVolumePlugins(), newTestHost(t, client)) plug, err := plugMgr.FindPluginByName("kubernetes.io/persistent-claim") if err != nil { t.Errorf("Can't find the plugin by name") } spec := &volume.Spec{ Name: "vol1", VolumeSource: item.podVolume, } pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}} builder, err := plug.NewBuilder(spec, pod, volume.VolumeOptions{}, nil) if !item.expectedFailure { if err != nil { t.Errorf("Failed to make a new Builder: %v", err) } if builder == nil { t.Errorf("Got a nil Builder: %v", builder) } } if err := item.testFunc(builder, item.plugin); err != nil { t.Errorf("Unexpected error %+v", err) } } }