12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748 |
- /*
- Copyright 2016 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 reconciler
- import (
- "fmt"
- "testing"
- "time"
- "github.com/stretchr/testify/assert"
- "k8s.io/klog"
- "k8s.io/utils/mount"
- v1 "k8s.io/api/core/v1"
- "k8s.io/apimachinery/pkg/api/resource"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/runtime"
- k8stypes "k8s.io/apimachinery/pkg/types"
- "k8s.io/apimachinery/pkg/util/wait"
- utilfeature "k8s.io/apiserver/pkg/util/feature"
- "k8s.io/client-go/kubernetes/fake"
- core "k8s.io/client-go/testing"
- "k8s.io/client-go/tools/record"
- featuregatetesting "k8s.io/component-base/featuregate/testing"
- "k8s.io/kubernetes/pkg/features"
- "k8s.io/kubernetes/pkg/kubelet/volumemanager/cache"
- "k8s.io/kubernetes/pkg/volume"
- volumetesting "k8s.io/kubernetes/pkg/volume/testing"
- "k8s.io/kubernetes/pkg/volume/util"
- "k8s.io/kubernetes/pkg/volume/util/hostutil"
- "k8s.io/kubernetes/pkg/volume/util/operationexecutor"
- )
- const (
- // reconcilerLoopSleepDuration is the amount of time the reconciler loop
- // waits between successive executions
- reconcilerLoopSleepDuration time.Duration = 1 * time.Nanosecond
- // waitForAttachTimeout is the maximum amount of time a
- // operationexecutor.Mount call will wait for a volume to be attached.
- waitForAttachTimeout time.Duration = 1 * time.Second
- nodeName k8stypes.NodeName = k8stypes.NodeName("mynodename")
- kubeletPodsDir string = "fake-dir"
- testOperationBackOffDuration time.Duration = 100 * time.Millisecond
- reconcilerSyncWaitDuration time.Duration = 10 * time.Second
- )
- func hasAddedPods() bool { return true }
- // Calls Run()
- // Verifies there are no calls to attach, detach, mount, unmount, etc.
- func Test_Run_Positive_DoNothing(t *testing.T) {
- // Arrange
- volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t)
- dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
- asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr)
- kubeClient := createTestClient()
- fakeRecorder := &record.FakeRecorder{}
- fakeHandler := volumetesting.NewBlockVolumePathHandler()
- oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
- kubeClient,
- volumePluginMgr,
- fakeRecorder,
- false, /* checkNodeCapabilitiesBeforeMount */
- fakeHandler,
- ))
- reconciler := NewReconciler(
- kubeClient,
- false, /* controllerAttachDetachEnabled */
- reconcilerLoopSleepDuration,
- waitForAttachTimeout,
- nodeName,
- dsw,
- asw,
- hasAddedPods,
- oex,
- mount.NewFakeMounter(nil),
- hostutil.NewFakeHostUtil(nil),
- volumePluginMgr,
- kubeletPodsDir)
- // Act
- runReconciler(reconciler)
- // Assert
- assert.NoError(t, volumetesting.VerifyZeroAttachCalls(fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroWaitForAttachCallCount(fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroMountDeviceCallCount(fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroSetUpCallCount(fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroTearDownCallCount(fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroDetachCallCount(fakePlugin))
- }
- // Populates desiredStateOfWorld cache with one volume/pod.
- // Calls Run()
- // Verifies there is are attach/mount/etc calls and no detach/unmount calls.
- func Test_Run_Positive_VolumeAttachAndMount(t *testing.T) {
- // Arrange
- volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t)
- dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
- asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr)
- kubeClient := createTestClient()
- fakeRecorder := &record.FakeRecorder{}
- fakeHandler := volumetesting.NewBlockVolumePathHandler()
- oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
- kubeClient,
- volumePluginMgr,
- fakeRecorder,
- false, /* checkNodeCapabilitiesBeforeMount */
- fakeHandler))
- reconciler := NewReconciler(
- kubeClient,
- false, /* controllerAttachDetachEnabled */
- reconcilerLoopSleepDuration,
- waitForAttachTimeout,
- nodeName,
- dsw,
- asw,
- hasAddedPods,
- oex,
- mount.NewFakeMounter(nil),
- hostutil.NewFakeHostUtil(nil),
- volumePluginMgr,
- kubeletPodsDir)
- pod := &v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod1",
- UID: "pod1uid",
- },
- Spec: v1.PodSpec{
- Volumes: []v1.Volume{
- {
- Name: "volume-name",
- VolumeSource: v1.VolumeSource{
- GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{
- PDName: "fake-device1",
- },
- },
- },
- },
- },
- }
- volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]}
- podName := util.GetUniquePodName(pod)
- generatedVolumeName, err := dsw.AddPodToVolume(
- podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */)
- // Assert
- if err != nil {
- t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
- }
- // Act
- runReconciler(reconciler)
- waitForMount(t, fakePlugin, generatedVolumeName, asw)
- // Assert
- assert.NoError(t, volumetesting.VerifyAttachCallCount(
- 1 /* expectedAttachCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyWaitForAttachCallCount(
- 1 /* expectedWaitForAttachCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyMountDeviceCallCount(
- 1 /* expectedMountDeviceCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifySetUpCallCount(
- 1 /* expectedSetUpCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroTearDownCallCount(fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroDetachCallCount(fakePlugin))
- }
- // Populates desiredStateOfWorld cache with one volume/pod.
- // Enables controllerAttachDetachEnabled.
- // Calls Run()
- // Verifies there is one mount call and no unmount calls.
- // Verifies there are no attach/detach calls.
- func Test_Run_Positive_VolumeMountControllerAttachEnabled(t *testing.T) {
- // Arrange
- volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t)
- dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
- asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr)
- kubeClient := createTestClient()
- fakeRecorder := &record.FakeRecorder{}
- fakeHandler := volumetesting.NewBlockVolumePathHandler()
- oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
- kubeClient,
- volumePluginMgr,
- fakeRecorder,
- false, /* checkNodeCapabilitiesBeforeMount */
- fakeHandler))
- reconciler := NewReconciler(
- kubeClient,
- true, /* controllerAttachDetachEnabled */
- reconcilerLoopSleepDuration,
- waitForAttachTimeout,
- nodeName,
- dsw,
- asw,
- hasAddedPods,
- oex,
- mount.NewFakeMounter(nil),
- hostutil.NewFakeHostUtil(nil),
- volumePluginMgr,
- kubeletPodsDir)
- pod := &v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod1",
- UID: "pod1uid",
- },
- Spec: v1.PodSpec{
- Volumes: []v1.Volume{
- {
- Name: "volume-name",
- VolumeSource: v1.VolumeSource{
- GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{
- PDName: "fake-device1",
- },
- },
- },
- },
- },
- }
- volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]}
- podName := util.GetUniquePodName(pod)
- generatedVolumeName, err := dsw.AddPodToVolume(
- podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */)
- dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeName})
- // Assert
- if err != nil {
- t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
- }
- // Act
- runReconciler(reconciler)
- waitForMount(t, fakePlugin, generatedVolumeName, asw)
- // Assert
- assert.NoError(t, volumetesting.VerifyZeroAttachCalls(fakePlugin))
- assert.NoError(t, volumetesting.VerifyWaitForAttachCallCount(
- 1 /* expectedWaitForAttachCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyMountDeviceCallCount(
- 1 /* expectedMountDeviceCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifySetUpCallCount(
- 1 /* expectedSetUpCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroTearDownCallCount(fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroDetachCallCount(fakePlugin))
- }
- // Populates desiredStateOfWorld cache with one volume/pod.
- // Calls Run()
- // Verifies there is one attach/mount/etc call and no detach calls.
- // Deletes volume/pod from desired state of world.
- // Verifies detach/unmount calls are issued.
- func Test_Run_Positive_VolumeAttachMountUnmountDetach(t *testing.T) {
- // Arrange
- volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t)
- dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
- asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr)
- kubeClient := createTestClient()
- fakeRecorder := &record.FakeRecorder{}
- fakeHandler := volumetesting.NewBlockVolumePathHandler()
- oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
- kubeClient,
- volumePluginMgr,
- fakeRecorder,
- false, /* checkNodeCapabilitiesBeforeMount */
- fakeHandler))
- reconciler := NewReconciler(
- kubeClient,
- false, /* controllerAttachDetachEnabled */
- reconcilerLoopSleepDuration,
- waitForAttachTimeout,
- nodeName,
- dsw,
- asw,
- hasAddedPods,
- oex,
- mount.NewFakeMounter(nil),
- hostutil.NewFakeHostUtil(nil),
- volumePluginMgr,
- kubeletPodsDir)
- pod := &v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod1",
- UID: "pod1uid",
- },
- Spec: v1.PodSpec{
- Volumes: []v1.Volume{
- {
- Name: "volume-name",
- VolumeSource: v1.VolumeSource{
- GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{
- PDName: "fake-device1",
- },
- },
- },
- },
- },
- }
- volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]}
- podName := util.GetUniquePodName(pod)
- generatedVolumeName, err := dsw.AddPodToVolume(
- podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */)
- // Assert
- if err != nil {
- t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
- }
- // Act
- runReconciler(reconciler)
- waitForMount(t, fakePlugin, generatedVolumeName, asw)
- // Assert
- assert.NoError(t, volumetesting.VerifyAttachCallCount(
- 1 /* expectedAttachCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyWaitForAttachCallCount(
- 1 /* expectedWaitForAttachCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyMountDeviceCallCount(
- 1 /* expectedMountDeviceCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifySetUpCallCount(
- 1 /* expectedSetUpCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroTearDownCallCount(fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroDetachCallCount(fakePlugin))
- // Act
- dsw.DeletePodFromVolume(podName, generatedVolumeName)
- waitForDetach(t, generatedVolumeName, asw)
- // Assert
- assert.NoError(t, volumetesting.VerifyTearDownCallCount(
- 1 /* expectedTearDownCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyDetachCallCount(
- 1 /* expectedDetachCallCount */, fakePlugin))
- }
- // Populates desiredStateOfWorld cache with one volume/pod.
- // Enables controllerAttachDetachEnabled.
- // Calls Run()
- // Verifies one mount call is made and no unmount calls.
- // Deletes volume/pod from desired state of world.
- // Verifies one unmount call is made.
- // Verifies there are no attach/detach calls made.
- func Test_Run_Positive_VolumeUnmountControllerAttachEnabled(t *testing.T) {
- // Arrange
- volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t)
- dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
- asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr)
- kubeClient := createTestClient()
- fakeRecorder := &record.FakeRecorder{}
- fakeHandler := volumetesting.NewBlockVolumePathHandler()
- oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
- kubeClient,
- volumePluginMgr,
- fakeRecorder,
- false, /* checkNodeCapabilitiesBeforeMount */
- fakeHandler))
- reconciler := NewReconciler(
- kubeClient,
- true, /* controllerAttachDetachEnabled */
- reconcilerLoopSleepDuration,
- waitForAttachTimeout,
- nodeName,
- dsw,
- asw,
- hasAddedPods,
- oex,
- mount.NewFakeMounter(nil),
- hostutil.NewFakeHostUtil(nil),
- volumePluginMgr,
- kubeletPodsDir)
- pod := &v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod1",
- UID: "pod1uid",
- },
- Spec: v1.PodSpec{
- Volumes: []v1.Volume{
- {
- Name: "volume-name",
- VolumeSource: v1.VolumeSource{
- GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{
- PDName: "fake-device1",
- },
- },
- },
- },
- },
- }
- volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]}
- podName := util.GetUniquePodName(pod)
- generatedVolumeName, err := dsw.AddPodToVolume(
- podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */)
- // Assert
- if err != nil {
- t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
- }
- // Act
- runReconciler(reconciler)
- dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeName})
- waitForMount(t, fakePlugin, generatedVolumeName, asw)
- // Assert
- assert.NoError(t, volumetesting.VerifyZeroAttachCalls(fakePlugin))
- assert.NoError(t, volumetesting.VerifyWaitForAttachCallCount(
- 1 /* expectedWaitForAttachCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyMountDeviceCallCount(
- 1 /* expectedMountDeviceCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifySetUpCallCount(
- 1 /* expectedSetUpCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroTearDownCallCount(fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroDetachCallCount(fakePlugin))
- // Act
- dsw.DeletePodFromVolume(podName, generatedVolumeName)
- waitForDetach(t, generatedVolumeName, asw)
- // Assert
- assert.NoError(t, volumetesting.VerifyTearDownCallCount(
- 1 /* expectedTearDownCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroDetachCallCount(fakePlugin))
- }
- // Populates desiredStateOfWorld cache with one volume/pod.
- // Calls Run()
- // Verifies there are attach/get map paths/setupDevice calls and
- // no detach/teardownDevice calls.
- func Test_Run_Positive_VolumeAttachAndMap(t *testing.T) {
- // Enable BlockVolume feature gate
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, true)()
- pod := &v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod1",
- UID: "pod1uid",
- Namespace: "ns",
- },
- Spec: v1.PodSpec{},
- }
- mode := v1.PersistentVolumeBlock
- gcepv := &v1.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{UID: "001", Name: "volume-name"},
- Spec: v1.PersistentVolumeSpec{
- Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G")},
- PersistentVolumeSource: v1.PersistentVolumeSource{GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{PDName: "fake-device1"}},
- AccessModes: []v1.PersistentVolumeAccessMode{
- v1.ReadWriteOnce,
- v1.ReadOnlyMany,
- },
- VolumeMode: &mode,
- ClaimRef: &v1.ObjectReference{Namespace: "ns", Name: "pvc-volume-name"},
- },
- }
- gcepvc := &v1.PersistentVolumeClaim{
- ObjectMeta: metav1.ObjectMeta{UID: "pvc-001", Name: "pvc-volume-name", Namespace: "ns"},
- Spec: v1.PersistentVolumeClaimSpec{
- VolumeName: "volume-name",
- VolumeMode: &mode,
- },
- Status: v1.PersistentVolumeClaimStatus{
- Phase: v1.ClaimBound,
- Capacity: gcepv.Spec.Capacity,
- },
- }
- // Arrange
- volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t)
- dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
- asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr)
- kubeClient := createtestClientWithPVPVC(gcepv, gcepvc)
- fakeRecorder := &record.FakeRecorder{}
- fakeHandler := volumetesting.NewBlockVolumePathHandler()
- oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
- kubeClient,
- volumePluginMgr,
- fakeRecorder,
- false, /* checkNodeCapabilitiesBeforeMount */
- fakeHandler))
- reconciler := NewReconciler(
- kubeClient,
- false, /* controllerAttachDetachEnabled */
- reconcilerLoopSleepDuration,
- waitForAttachTimeout,
- nodeName,
- dsw,
- asw,
- hasAddedPods,
- oex,
- mount.NewFakeMounter(nil),
- hostutil.NewFakeHostUtil(nil),
- volumePluginMgr,
- kubeletPodsDir)
- volumeSpec := &volume.Spec{
- PersistentVolume: gcepv,
- }
- podName := util.GetUniquePodName(pod)
- generatedVolumeName, err := dsw.AddPodToVolume(
- podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */)
- // Assert
- if err != nil {
- t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
- }
- // Act
- runReconciler(reconciler)
- waitForMount(t, fakePlugin, generatedVolumeName, asw)
- // Assert
- assert.NoError(t, volumetesting.VerifyAttachCallCount(
- 1 /* expectedAttachCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyWaitForAttachCallCount(
- 1 /* expectedWaitForAttachCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyGetMapPodDeviceCallCount(
- 1 /* expectedGetMapDeviceCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroTearDownDeviceCallCount(fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroDetachCallCount(fakePlugin))
- }
- // Populates desiredStateOfWorld cache with one volume/pod.
- // Enables controllerAttachDetachEnabled.
- // Calls Run()
- // Verifies there are two get map path calls, a setupDevice call
- // and no teardownDevice call.
- // Verifies there are no attach/detach calls.
- func Test_Run_Positive_BlockVolumeMapControllerAttachEnabled(t *testing.T) {
- // Enable BlockVolume feature gate
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, true)()
- pod := &v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod1",
- UID: "pod1uid",
- Namespace: "ns",
- },
- Spec: v1.PodSpec{},
- }
- mode := v1.PersistentVolumeBlock
- gcepv := &v1.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{UID: "001", Name: "volume-name"},
- Spec: v1.PersistentVolumeSpec{
- Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G")},
- PersistentVolumeSource: v1.PersistentVolumeSource{GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{PDName: "fake-device1"}},
- AccessModes: []v1.PersistentVolumeAccessMode{
- v1.ReadWriteOnce,
- v1.ReadOnlyMany,
- },
- VolumeMode: &mode,
- ClaimRef: &v1.ObjectReference{Namespace: "ns", Name: "pvc-volume-name"},
- },
- }
- gcepvc := &v1.PersistentVolumeClaim{
- ObjectMeta: metav1.ObjectMeta{UID: "pvc-001", Name: "pvc-volume-name", Namespace: "ns"},
- Spec: v1.PersistentVolumeClaimSpec{
- VolumeName: "volume-name",
- VolumeMode: &mode,
- },
- Status: v1.PersistentVolumeClaimStatus{
- Phase: v1.ClaimBound,
- Capacity: gcepv.Spec.Capacity,
- },
- }
- volumeSpec := &volume.Spec{
- PersistentVolume: gcepv,
- }
- // Arrange
- volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t)
- dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
- asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr)
- kubeClient := createtestClientWithPVPVC(gcepv, gcepvc, v1.AttachedVolume{
- Name: "fake-plugin/fake-device1",
- DevicePath: "/fake/path",
- })
- fakeRecorder := &record.FakeRecorder{}
- fakeHandler := volumetesting.NewBlockVolumePathHandler()
- oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
- kubeClient,
- volumePluginMgr,
- fakeRecorder,
- false, /* checkNodeCapabilitiesBeforeMount */
- fakeHandler))
- reconciler := NewReconciler(
- kubeClient,
- true, /* controllerAttachDetachEnabled */
- reconcilerLoopSleepDuration,
- waitForAttachTimeout,
- nodeName,
- dsw,
- asw,
- hasAddedPods,
- oex,
- mount.NewFakeMounter(nil),
- hostutil.NewFakeHostUtil(nil),
- volumePluginMgr,
- kubeletPodsDir)
- podName := util.GetUniquePodName(pod)
- generatedVolumeName, err := dsw.AddPodToVolume(
- podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */)
- dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeName})
- // Assert
- if err != nil {
- t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
- }
- // Act
- runReconciler(reconciler)
- waitForMount(t, fakePlugin, generatedVolumeName, asw)
- // Assert
- assert.NoError(t, volumetesting.VerifyZeroAttachCalls(fakePlugin))
- assert.NoError(t, volumetesting.VerifyWaitForAttachCallCount(
- 1 /* expectedWaitForAttachCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyGetMapPodDeviceCallCount(
- 1 /* expectedGetMapDeviceCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroTearDownDeviceCallCount(fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroDetachCallCount(fakePlugin))
- }
- // Populates desiredStateOfWorld cache with one volume/pod.
- // Calls Run()
- // Verifies there is one attach call, two get map path calls,
- // setupDevice call and no detach calls.
- // Deletes volume/pod from desired state of world.
- // Verifies one detach/teardownDevice calls are issued.
- func Test_Run_Positive_BlockVolumeAttachMapUnmapDetach(t *testing.T) {
- // Enable BlockVolume feature gate
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, true)()
- pod := &v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod1",
- UID: "pod1uid",
- Namespace: "ns",
- },
- Spec: v1.PodSpec{},
- }
- mode := v1.PersistentVolumeBlock
- gcepv := &v1.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{UID: "001", Name: "volume-name"},
- Spec: v1.PersistentVolumeSpec{
- Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G")},
- PersistentVolumeSource: v1.PersistentVolumeSource{GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{PDName: "fake-device1"}},
- AccessModes: []v1.PersistentVolumeAccessMode{
- v1.ReadWriteOnce,
- v1.ReadOnlyMany,
- },
- VolumeMode: &mode,
- ClaimRef: &v1.ObjectReference{Namespace: "ns", Name: "pvc-volume-name"},
- },
- }
- gcepvc := &v1.PersistentVolumeClaim{
- ObjectMeta: metav1.ObjectMeta{UID: "pvc-001", Name: "pvc-volume-name", Namespace: "ns"},
- Spec: v1.PersistentVolumeClaimSpec{
- VolumeName: "volume-name",
- VolumeMode: &mode,
- },
- Status: v1.PersistentVolumeClaimStatus{
- Phase: v1.ClaimBound,
- Capacity: gcepv.Spec.Capacity,
- },
- }
- volumeSpec := &volume.Spec{
- PersistentVolume: gcepv,
- }
- // Arrange
- volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t)
- dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
- asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr)
- kubeClient := createtestClientWithPVPVC(gcepv, gcepvc)
- fakeRecorder := &record.FakeRecorder{}
- fakeHandler := volumetesting.NewBlockVolumePathHandler()
- oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
- kubeClient,
- volumePluginMgr,
- fakeRecorder,
- false, /* checkNodeCapabilitiesBeforeMount */
- fakeHandler))
- reconciler := NewReconciler(
- kubeClient,
- false, /* controllerAttachDetachEnabled */
- reconcilerLoopSleepDuration,
- waitForAttachTimeout,
- nodeName,
- dsw,
- asw,
- hasAddedPods,
- oex,
- mount.NewFakeMounter(nil),
- hostutil.NewFakeHostUtil(nil),
- volumePluginMgr,
- kubeletPodsDir)
- podName := util.GetUniquePodName(pod)
- generatedVolumeName, err := dsw.AddPodToVolume(
- podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */)
- // Assert
- if err != nil {
- t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
- }
- // Act
- runReconciler(reconciler)
- waitForMount(t, fakePlugin, generatedVolumeName, asw)
- // Assert
- assert.NoError(t, volumetesting.VerifyAttachCallCount(
- 1 /* expectedAttachCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyWaitForAttachCallCount(
- 1 /* expectedWaitForAttachCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyGetMapPodDeviceCallCount(
- 1 /* expectedGetMapDeviceCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroTearDownDeviceCallCount(fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroDetachCallCount(fakePlugin))
- // Act
- dsw.DeletePodFromVolume(podName, generatedVolumeName)
- waitForDetach(t, generatedVolumeName, asw)
- // Assert
- assert.NoError(t, volumetesting.VerifyTearDownDeviceCallCount(
- 1 /* expectedTearDownDeviceCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyDetachCallCount(
- 1 /* expectedDetachCallCount */, fakePlugin))
- }
- // Populates desiredStateOfWorld cache with one volume/pod.
- // Enables controllerAttachDetachEnabled.
- // Calls Run()
- // Verifies two map path calls are made and no teardownDevice/detach calls.
- // Deletes volume/pod from desired state of world.
- // Verifies one teardownDevice call is made.
- // Verifies there are no attach/detach calls made.
- func Test_Run_Positive_VolumeUnmapControllerAttachEnabled(t *testing.T) {
- // Enable BlockVolume feature gate
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, true)()
- pod := &v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod1",
- UID: "pod1uid",
- Namespace: "ns",
- },
- Spec: v1.PodSpec{},
- }
- mode := v1.PersistentVolumeBlock
- gcepv := &v1.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{UID: "001", Name: "volume-name"},
- Spec: v1.PersistentVolumeSpec{
- Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G")},
- PersistentVolumeSource: v1.PersistentVolumeSource{GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{PDName: "fake-device1"}},
- AccessModes: []v1.PersistentVolumeAccessMode{
- v1.ReadWriteOnce,
- v1.ReadOnlyMany,
- },
- VolumeMode: &mode,
- ClaimRef: &v1.ObjectReference{Namespace: "ns", Name: "pvc-volume-name"},
- },
- }
- gcepvc := &v1.PersistentVolumeClaim{
- ObjectMeta: metav1.ObjectMeta{UID: "pvc-001", Name: "pvc-volume-name", Namespace: "ns"},
- Spec: v1.PersistentVolumeClaimSpec{
- VolumeName: "volume-name",
- VolumeMode: &mode,
- },
- Status: v1.PersistentVolumeClaimStatus{
- Phase: v1.ClaimBound,
- Capacity: gcepv.Spec.Capacity,
- },
- }
- volumeSpec := &volume.Spec{
- PersistentVolume: gcepv,
- }
- // Arrange
- volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t)
- dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
- asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr)
- kubeClient := createtestClientWithPVPVC(gcepv, gcepvc, v1.AttachedVolume{
- Name: "fake-plugin/fake-device1",
- DevicePath: "/fake/path",
- })
- fakeRecorder := &record.FakeRecorder{}
- fakeHandler := volumetesting.NewBlockVolumePathHandler()
- oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
- kubeClient,
- volumePluginMgr,
- fakeRecorder,
- false, /* checkNodeCapabilitiesBeforeMount */
- fakeHandler))
- reconciler := NewReconciler(
- kubeClient,
- true, /* controllerAttachDetachEnabled */
- reconcilerLoopSleepDuration,
- waitForAttachTimeout,
- nodeName,
- dsw,
- asw,
- hasAddedPods,
- oex,
- mount.NewFakeMounter(nil),
- hostutil.NewFakeHostUtil(nil),
- volumePluginMgr,
- kubeletPodsDir)
- podName := util.GetUniquePodName(pod)
- generatedVolumeName, err := dsw.AddPodToVolume(
- podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */)
- // Assert
- if err != nil {
- t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
- }
- // Act
- runReconciler(reconciler)
- dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeName})
- waitForMount(t, fakePlugin, generatedVolumeName, asw)
- // Assert
- assert.NoError(t, volumetesting.VerifyZeroAttachCalls(fakePlugin))
- assert.NoError(t, volumetesting.VerifyWaitForAttachCallCount(
- 1 /* expectedWaitForAttachCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyGetMapPodDeviceCallCount(
- 1 /* expectedGetMapDeviceCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroTearDownDeviceCallCount(fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroDetachCallCount(fakePlugin))
- // Act
- dsw.DeletePodFromVolume(podName, generatedVolumeName)
- waitForDetach(t, generatedVolumeName, asw)
- // Assert
- assert.NoError(t, volumetesting.VerifyTearDownDeviceCallCount(
- 1 /* expectedTearDownDeviceCallCount */, fakePlugin))
- assert.NoError(t, volumetesting.VerifyZeroDetachCallCount(fakePlugin))
- }
- func Test_GenerateMapVolumeFunc_Plugin_Not_Found(t *testing.T) {
- testCases := map[string]struct {
- volumePlugins []volume.VolumePlugin
- expectErr bool
- expectedErrMsg string
- }{
- "volumePlugin is nil": {
- volumePlugins: []volume.VolumePlugin{},
- expectErr: true,
- expectedErrMsg: "MapVolume.FindMapperPluginBySpec failed",
- },
- "blockVolumePlugin is nil": {
- volumePlugins: volumetesting.NewFakeFileVolumePlugin(),
- expectErr: true,
- expectedErrMsg: "MapVolume.FindMapperPluginBySpec failed to find BlockVolumeMapper plugin. Volume plugin is nil.",
- },
- }
- // Enable BlockVolume feature gate
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, true)()
- for name, tc := range testCases {
- t.Run(name, func(t *testing.T) {
- volumePluginMgr := &volume.VolumePluginMgr{}
- volumePluginMgr.InitPlugins(tc.volumePlugins, nil, nil)
- asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr)
- oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
- nil, /* kubeClient */
- volumePluginMgr,
- nil, /* fakeRecorder */
- false, /* checkNodeCapabilitiesBeforeMount */
- nil))
- pod := &v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod1",
- UID: "pod1uid",
- },
- Spec: v1.PodSpec{},
- }
- volumeMode := v1.PersistentVolumeBlock
- tmpSpec := &volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{VolumeMode: &volumeMode}}}
- volumeToMount := operationexecutor.VolumeToMount{
- Pod: pod,
- VolumeSpec: tmpSpec}
- err := oex.MountVolume(waitForAttachTimeout, volumeToMount, asw, false)
- // Assert
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), tc.expectedErrMsg)
- }
- })
- }
- }
- func Test_GenerateUnmapVolumeFunc_Plugin_Not_Found(t *testing.T) {
- testCases := map[string]struct {
- volumePlugins []volume.VolumePlugin
- expectErr bool
- expectedErrMsg string
- }{
- "volumePlugin is nil": {
- volumePlugins: []volume.VolumePlugin{},
- expectErr: true,
- expectedErrMsg: "UnmapVolume.FindMapperPluginByName failed",
- },
- "blockVolumePlugin is nil": {
- volumePlugins: volumetesting.NewFakeFileVolumePlugin(),
- expectErr: true,
- expectedErrMsg: "UnmapVolume.FindMapperPluginByName failed to find BlockVolumeMapper plugin. Volume plugin is nil.",
- },
- }
- // Enable BlockVolume feature gate
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, true)()
- for name, tc := range testCases {
- t.Run(name, func(t *testing.T) {
- volumePluginMgr := &volume.VolumePluginMgr{}
- volumePluginMgr.InitPlugins(tc.volumePlugins, nil, nil)
- asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr)
- oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
- nil, /* kubeClient */
- volumePluginMgr,
- nil, /* fakeRecorder */
- false, /* checkNodeCapabilitiesBeforeMount */
- nil))
- volumeMode := v1.PersistentVolumeBlock
- tmpSpec := &volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{VolumeMode: &volumeMode}}}
- volumeToUnmount := operationexecutor.MountedVolume{
- PluginName: "fake-file-plugin",
- VolumeSpec: tmpSpec}
- err := oex.UnmountVolume(volumeToUnmount, asw, "" /* podsDir */)
- // Assert
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), tc.expectedErrMsg)
- }
- })
- }
- }
- func Test_GenerateUnmapDeviceFunc_Plugin_Not_Found(t *testing.T) {
- testCases := map[string]struct {
- volumePlugins []volume.VolumePlugin
- expectErr bool
- expectedErrMsg string
- }{
- "volumePlugin is nil": {
- volumePlugins: []volume.VolumePlugin{},
- expectErr: true,
- expectedErrMsg: "UnmapDevice.FindMapperPluginByName failed",
- },
- "blockVolumePlugin is nil": {
- volumePlugins: volumetesting.NewFakeFileVolumePlugin(),
- expectErr: true,
- expectedErrMsg: "UnmapDevice.FindMapperPluginByName failed to find BlockVolumeMapper plugin. Volume plugin is nil.",
- },
- }
- // Enable BlockVolume feature gate
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, true)()
- for name, tc := range testCases {
- t.Run(name, func(t *testing.T) {
- volumePluginMgr := &volume.VolumePluginMgr{}
- volumePluginMgr.InitPlugins(tc.volumePlugins, nil, nil)
- asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr)
- oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
- nil, /* kubeClient */
- volumePluginMgr,
- nil, /* fakeRecorder */
- false, /* checkNodeCapabilitiesBeforeMount */
- nil))
- var hostutil hostutil.HostUtils
- volumeMode := v1.PersistentVolumeBlock
- tmpSpec := &volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{VolumeMode: &volumeMode}}}
- deviceToDetach := operationexecutor.AttachedVolume{VolumeSpec: tmpSpec, PluginName: "fake-file-plugin"}
- err := oex.UnmountDevice(deviceToDetach, asw, hostutil)
- // Assert
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), tc.expectedErrMsg)
- }
- })
- }
- }
- // Populates desiredStateOfWorld cache with one volume/pod.
- // Enables controllerAttachDetachEnabled.
- // Calls Run()
- // Wait for volume mounted.
- // Mark volume as fsResizeRequired in ASW.
- // Verifies volume's fsResizeRequired flag is cleared later.
- func Test_Run_Positive_VolumeFSResizeControllerAttachEnabled(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExpandInUsePersistentVolumes, true)()
- blockMode := v1.PersistentVolumeBlock
- fsMode := v1.PersistentVolumeFilesystem
- var tests = []struct {
- name string
- volumeMode *v1.PersistentVolumeMode
- }{
- {
- name: "expand-fs-volume",
- volumeMode: &fsMode,
- },
- {
- name: "expand-raw-block",
- volumeMode: &blockMode,
- },
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- pv := &v1.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pv",
- UID: "pvuid",
- },
- Spec: v1.PersistentVolumeSpec{
- ClaimRef: &v1.ObjectReference{Name: "pvc"},
- VolumeMode: tc.volumeMode,
- },
- }
- pvc := &v1.PersistentVolumeClaim{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pvc",
- UID: "pvcuid",
- },
- Spec: v1.PersistentVolumeClaimSpec{
- VolumeName: "pv",
- VolumeMode: tc.volumeMode,
- },
- }
- pod := &v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod1",
- UID: "pod1uid",
- },
- Spec: v1.PodSpec{
- Volumes: []v1.Volume{
- {
- Name: "volume-name",
- VolumeSource: v1.VolumeSource{
- PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
- ClaimName: pvc.Name,
- },
- },
- },
- },
- },
- }
- volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t)
- dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
- asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr)
- kubeClient := createtestClientWithPVPVC(pv, pvc)
- fakeRecorder := &record.FakeRecorder{}
- fakeHandler := volumetesting.NewBlockVolumePathHandler()
- oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
- kubeClient,
- volumePluginMgr,
- fakeRecorder,
- false, /* checkNodeCapabilitiesBeforeMount */
- fakeHandler))
- reconciler := NewReconciler(
- kubeClient,
- true, /* controllerAttachDetachEnabled */
- reconcilerLoopSleepDuration,
- waitForAttachTimeout,
- nodeName,
- dsw,
- asw,
- hasAddedPods,
- oex,
- mount.NewFakeMounter(nil),
- hostutil.NewFakeHostUtil(nil),
- volumePluginMgr,
- kubeletPodsDir)
- volumeSpec := &volume.Spec{PersistentVolume: pv}
- podName := util.GetUniquePodName(pod)
- volumeName, err := dsw.AddPodToVolume(
- podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */)
- // Assert
- if err != nil {
- t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
- }
- dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{volumeName})
- // Start the reconciler to fill ASW.
- stopChan, stoppedChan := make(chan struct{}), make(chan struct{})
- go func() {
- reconciler.Run(stopChan)
- close(stoppedChan)
- }()
- waitForMount(t, fakePlugin, volumeName, asw)
- // Stop the reconciler.
- close(stopChan)
- <-stoppedChan
- // Mark volume as fsResizeRequired.
- asw.MarkFSResizeRequired(volumeName, podName)
- _, _, podExistErr := asw.PodExistsInVolume(podName, volumeName)
- if !cache.IsFSResizeRequiredError(podExistErr) {
- t.Fatalf("Volume should be marked as fsResizeRequired, but receive unexpected error: %v", podExistErr)
- }
- // Start the reconciler again, we hope reconciler will perform the
- // resize operation and clear the fsResizeRequired flag for volume.
- go reconciler.Run(wait.NeverStop)
- waitErr := retryWithExponentialBackOff(testOperationBackOffDuration, func() (done bool, err error) {
- mounted, _, err := asw.PodExistsInVolume(podName, volumeName)
- return mounted && err == nil, nil
- })
- if waitErr != nil {
- t.Fatal("Volume resize should succeeded")
- }
- })
- }
- }
- func Test_UncertainDeviceGlobalMounts(t *testing.T) {
- fsMode := v1.PersistentVolumeFilesystem
- var tests = []struct {
- name string
- deviceState operationexecutor.DeviceMountState
- unmountDeviceCallCount int
- volumeName string
- supportRemount bool
- }{
- {
- name: "timed out operations should result in device marked as uncertain",
- deviceState: operationexecutor.DeviceMountUncertain,
- unmountDeviceCallCount: 1,
- volumeName: volumetesting.TimeoutOnMountDeviceVolumeName,
- },
- {
- name: "failed operation should result in not-mounted device",
- deviceState: operationexecutor.DeviceNotMounted,
- unmountDeviceCallCount: 0,
- volumeName: volumetesting.FailMountDeviceVolumeName,
- },
- {
- name: "timeout followed by failed operation should result in non-mounted device",
- deviceState: operationexecutor.DeviceNotMounted,
- unmountDeviceCallCount: 0,
- volumeName: volumetesting.TimeoutAndFailOnMountDeviceVolumeName,
- },
- {
- name: "success followed by timeout operation should result in mounted device",
- deviceState: operationexecutor.DeviceGloballyMounted,
- unmountDeviceCallCount: 1,
- volumeName: volumetesting.SuccessAndTimeoutDeviceName,
- supportRemount: true,
- },
- {
- name: "success followed by failed operation should result in mounted device",
- deviceState: operationexecutor.DeviceGloballyMounted,
- unmountDeviceCallCount: 1,
- volumeName: volumetesting.SuccessAndFailOnMountDeviceName,
- supportRemount: true,
- },
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- pv := &v1.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: tc.volumeName,
- UID: "pvuid",
- },
- Spec: v1.PersistentVolumeSpec{
- ClaimRef: &v1.ObjectReference{Name: "pvc"},
- VolumeMode: &fsMode,
- },
- }
- pvc := &v1.PersistentVolumeClaim{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pvc",
- UID: "pvcuid",
- },
- Spec: v1.PersistentVolumeClaimSpec{
- VolumeName: tc.volumeName,
- },
- }
- pod := &v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod1",
- UID: "pod1uid",
- },
- Spec: v1.PodSpec{
- Volumes: []v1.Volume{
- {
- Name: "volume-name",
- VolumeSource: v1.VolumeSource{
- PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
- ClaimName: pvc.Name,
- },
- },
- },
- },
- },
- }
- volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t)
- fakePlugin.SupportsRemount = tc.supportRemount
- dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
- asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr)
- kubeClient := createtestClientWithPVPVC(pv, pvc, v1.AttachedVolume{
- Name: v1.UniqueVolumeName(fmt.Sprintf("fake-plugin/%s", tc.volumeName)),
- DevicePath: "fake/path",
- })
- fakeRecorder := &record.FakeRecorder{}
- fakeHandler := volumetesting.NewBlockVolumePathHandler()
- oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
- kubeClient,
- volumePluginMgr,
- fakeRecorder,
- false, /* checkNodeCapabilitiesBeforeMount */
- fakeHandler))
- reconciler := NewReconciler(
- kubeClient,
- true, /* controllerAttachDetachEnabled */
- reconcilerLoopSleepDuration,
- waitForAttachTimeout,
- nodeName,
- dsw,
- asw,
- hasAddedPods,
- oex,
- &mount.FakeMounter{},
- hostutil.NewFakeHostUtil(nil),
- volumePluginMgr,
- kubeletPodsDir)
- volumeSpec := &volume.Spec{PersistentVolume: pv}
- podName := util.GetUniquePodName(pod)
- volumeName, err := dsw.AddPodToVolume(
- podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */)
- // Assert
- if err != nil {
- t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
- }
- dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{volumeName})
- // Start the reconciler to fill ASW.
- stopChan, stoppedChan := make(chan struct{}), make(chan struct{})
- go func() {
- reconciler.Run(stopChan)
- close(stoppedChan)
- }()
- waitForVolumeToExistInASW(t, volumeName, asw)
- if tc.volumeName == volumetesting.TimeoutAndFailOnMountDeviceVolumeName {
- // Wait upto 10s for reconciler to catchup
- time.Sleep(reconcilerSyncWaitDuration)
- }
- if tc.volumeName == volumetesting.SuccessAndFailOnMountDeviceName ||
- tc.volumeName == volumetesting.SuccessAndTimeoutDeviceName {
- // wait for mount and then break it via remount
- waitForMount(t, fakePlugin, volumeName, asw)
- asw.MarkRemountRequired(podName)
- time.Sleep(reconcilerSyncWaitDuration)
- }
- if tc.deviceState == operationexecutor.DeviceMountUncertain {
- waitForUncertainGlobalMount(t, volumeName, asw)
- }
- if tc.deviceState == operationexecutor.DeviceGloballyMounted {
- waitForMount(t, fakePlugin, volumeName, asw)
- }
- dsw.DeletePodFromVolume(podName, volumeName)
- waitForDetach(t, volumeName, asw)
- err = volumetesting.VerifyUnmountDeviceCallCount(tc.unmountDeviceCallCount, fakePlugin)
- if err != nil {
- t.Errorf("Error verifying UnMountDeviceCallCount: %v", err)
- }
- })
- }
- }
- func Test_UncertainVolumeMountState(t *testing.T) {
- fsMode := v1.PersistentVolumeFilesystem
- var tests = []struct {
- name string
- volumeState operationexecutor.VolumeMountState
- unmountDeviceCallCount int
- unmountVolumeCount int
- volumeName string
- supportRemount bool
- }{
- {
- name: "timed out operations should result in volume marked as uncertain",
- volumeState: operationexecutor.VolumeMountUncertain,
- unmountDeviceCallCount: 1,
- unmountVolumeCount: 1,
- volumeName: volumetesting.TimeoutOnSetupVolumeName,
- },
- {
- name: "failed operation should result in not-mounted volume",
- volumeState: operationexecutor.VolumeNotMounted,
- unmountDeviceCallCount: 0,
- unmountVolumeCount: 0,
- volumeName: volumetesting.FailOnSetupVolumeName,
- },
- {
- name: "timeout followed by failed operation should result in non-mounted volume",
- volumeState: operationexecutor.VolumeNotMounted,
- unmountDeviceCallCount: 0,
- unmountVolumeCount: 0,
- volumeName: volumetesting.TimeoutAndFailOnSetupVolumeName,
- },
- {
- name: "success followed by timeout operation should result in mounted volume",
- volumeState: operationexecutor.VolumeMounted,
- unmountDeviceCallCount: 1,
- unmountVolumeCount: 1,
- volumeName: volumetesting.SuccessAndTimeoutSetupVolumeName,
- supportRemount: true,
- },
- {
- name: "success followed by failed operation should result in mounted volume",
- volumeState: operationexecutor.VolumeMounted,
- unmountDeviceCallCount: 1,
- unmountVolumeCount: 1,
- volumeName: volumetesting.SuccessAndFailOnSetupVolumeName,
- supportRemount: true,
- },
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- pv := &v1.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: tc.volumeName,
- UID: "pvuid",
- },
- Spec: v1.PersistentVolumeSpec{
- ClaimRef: &v1.ObjectReference{Name: "pvc"},
- VolumeMode: &fsMode,
- },
- }
- pvc := &v1.PersistentVolumeClaim{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pvc",
- UID: "pvcuid",
- },
- Spec: v1.PersistentVolumeClaimSpec{
- VolumeName: tc.volumeName,
- },
- }
- pod := &v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod1",
- UID: "pod1uid",
- },
- Spec: v1.PodSpec{
- Volumes: []v1.Volume{
- {
- Name: "volume-name",
- VolumeSource: v1.VolumeSource{
- PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
- ClaimName: pvc.Name,
- },
- },
- },
- },
- },
- }
- volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t)
- fakePlugin.SupportsRemount = tc.supportRemount
- dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
- asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr)
- kubeClient := createtestClientWithPVPVC(pv, pvc, v1.AttachedVolume{
- Name: v1.UniqueVolumeName(fmt.Sprintf("fake-plugin/%s", tc.volumeName)),
- DevicePath: "fake/path",
- })
- fakeRecorder := &record.FakeRecorder{}
- fakeHandler := volumetesting.NewBlockVolumePathHandler()
- oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
- kubeClient,
- volumePluginMgr,
- fakeRecorder,
- false, /* checkNodeCapabilitiesBeforeMount */
- fakeHandler))
- reconciler := NewReconciler(
- kubeClient,
- true, /* controllerAttachDetachEnabled */
- reconcilerLoopSleepDuration,
- waitForAttachTimeout,
- nodeName,
- dsw,
- asw,
- hasAddedPods,
- oex,
- &mount.FakeMounter{},
- hostutil.NewFakeHostUtil(nil),
- volumePluginMgr,
- kubeletPodsDir)
- volumeSpec := &volume.Spec{PersistentVolume: pv}
- podName := util.GetUniquePodName(pod)
- volumeName, err := dsw.AddPodToVolume(
- podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */)
- // Assert
- if err != nil {
- t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
- }
- dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{volumeName})
- // Start the reconciler to fill ASW.
- stopChan, stoppedChan := make(chan struct{}), make(chan struct{})
- go func() {
- reconciler.Run(stopChan)
- close(stoppedChan)
- }()
- waitForVolumeToExistInASW(t, volumeName, asw)
- if tc.volumeName == volumetesting.TimeoutAndFailOnSetupVolumeName {
- // Wait upto 10s for reconciler to catchup
- time.Sleep(reconcilerSyncWaitDuration)
- }
- if tc.volumeName == volumetesting.SuccessAndFailOnSetupVolumeName ||
- tc.volumeName == volumetesting.SuccessAndTimeoutSetupVolumeName {
- // wait for mount and then break it via remount
- waitForMount(t, fakePlugin, volumeName, asw)
- asw.MarkRemountRequired(podName)
- time.Sleep(reconcilerSyncWaitDuration)
- }
- if tc.volumeState == operationexecutor.VolumeMountUncertain {
- waitForUncertainPodMount(t, volumeName, asw)
- }
- if tc.volumeState == operationexecutor.VolumeMounted {
- waitForMount(t, fakePlugin, volumeName, asw)
- }
- dsw.DeletePodFromVolume(podName, volumeName)
- waitForDetach(t, volumeName, asw)
- volumetesting.VerifyUnmountDeviceCallCount(tc.unmountDeviceCallCount, fakePlugin)
- volumetesting.VerifyTearDownCallCount(tc.unmountVolumeCount, fakePlugin)
- })
- }
- }
- func waitForUncertainGlobalMount(t *testing.T, volumeName v1.UniqueVolumeName, asw cache.ActualStateOfWorld) {
- // check if volume is globally mounted in uncertain state
- err := retryWithExponentialBackOff(
- testOperationBackOffDuration,
- func() (bool, error) {
- unmountedVolumes := asw.GetUnmountedVolumes()
- for _, v := range unmountedVolumes {
- if v.VolumeName == volumeName && v.DeviceMountState == operationexecutor.DeviceMountUncertain {
- return true, nil
- }
- }
- return false, nil
- },
- )
- if err != nil {
- t.Fatalf("expected volumes %s to be mounted in uncertain state globally", volumeName)
- }
- }
- func waitForUncertainPodMount(t *testing.T, volumeName v1.UniqueVolumeName, asw cache.ActualStateOfWorld) {
- // check if volume is locally pod mounted in uncertain state
- err := retryWithExponentialBackOff(
- testOperationBackOffDuration,
- func() (bool, error) {
- allMountedVolumes := asw.GetAllMountedVolumes()
- for _, v := range allMountedVolumes {
- if v.VolumeName == volumeName {
- return true, nil
- }
- }
- return false, nil
- },
- )
- if err != nil {
- t.Fatalf("expected volumes %s to be mounted in uncertain state for pod", volumeName)
- }
- }
- func waitForMount(
- t *testing.T,
- fakePlugin *volumetesting.FakeVolumePlugin,
- volumeName v1.UniqueVolumeName,
- asw cache.ActualStateOfWorld) {
- err := retryWithExponentialBackOff(
- testOperationBackOffDuration,
- func() (bool, error) {
- mountedVolumes := asw.GetMountedVolumes()
- for _, mountedVolume := range mountedVolumes {
- if mountedVolume.VolumeName == volumeName {
- return true, nil
- }
- }
- return false, nil
- },
- )
- if err != nil {
- t.Fatalf("Timed out waiting for volume %q to be attached.", volumeName)
- }
- }
- func waitForVolumeToExistInASW(t *testing.T, volumeName v1.UniqueVolumeName, asw cache.ActualStateOfWorld) {
- err := retryWithExponentialBackOff(
- testOperationBackOffDuration,
- func() (bool, error) {
- if asw.VolumeExists(volumeName) {
- return true, nil
- }
- return false, nil
- },
- )
- if err != nil {
- t.Fatalf("Timed out waiting for volume %q to be exist in asw.", volumeName)
- }
- }
- func waitForDetach(
- t *testing.T,
- volumeName v1.UniqueVolumeName,
- asw cache.ActualStateOfWorld) {
- err := retryWithExponentialBackOff(
- testOperationBackOffDuration,
- func() (bool, error) {
- if asw.VolumeExists(volumeName) {
- return false, nil
- }
- return true, nil
- },
- )
- if err != nil {
- t.Fatalf("Timed out waiting for volume %q to be detached.", volumeName)
- }
- }
- func retryWithExponentialBackOff(initialDuration time.Duration, fn wait.ConditionFunc) error {
- backoff := wait.Backoff{
- Duration: initialDuration,
- Factor: 3,
- Jitter: 0,
- Steps: 6,
- }
- return wait.ExponentialBackoff(backoff, fn)
- }
- func createTestClient() *fake.Clientset {
- fakeClient := &fake.Clientset{}
- fakeClient.AddReactor("get", "nodes",
- func(action core.Action) (bool, runtime.Object, error) {
- return true, &v1.Node{
- ObjectMeta: metav1.ObjectMeta{Name: string(nodeName)},
- Status: v1.NodeStatus{
- VolumesAttached: []v1.AttachedVolume{
- {
- Name: "fake-plugin/fake-device1",
- DevicePath: "/fake/path",
- },
- }},
- }, nil
- })
- fakeClient.AddReactor("*", "*", func(action core.Action) (bool, runtime.Object, error) {
- return true, nil, fmt.Errorf("no reaction implemented for %s", action)
- })
- return fakeClient
- }
- func runReconciler(reconciler Reconciler) {
- go reconciler.Run(wait.NeverStop)
- }
- func createtestClientWithPVPVC(pv *v1.PersistentVolume, pvc *v1.PersistentVolumeClaim, attachedVolumes ...v1.AttachedVolume) *fake.Clientset {
- fakeClient := &fake.Clientset{}
- if len(attachedVolumes) == 0 {
- attachedVolumes = append(attachedVolumes, v1.AttachedVolume{
- Name: "fake-plugin/pv",
- DevicePath: "fake/path",
- })
- }
- fakeClient.AddReactor("get", "nodes",
- func(action core.Action) (bool, runtime.Object, error) {
- return true, &v1.Node{
- ObjectMeta: metav1.ObjectMeta{Name: string(nodeName)},
- Status: v1.NodeStatus{
- VolumesAttached: attachedVolumes,
- },
- }, nil
- })
- fakeClient.AddReactor("get", "persistentvolumeclaims", func(action core.Action) (bool, runtime.Object, error) {
- return true, pvc, nil
- })
- fakeClient.AddReactor("get", "persistentvolumes", func(action core.Action) (bool, runtime.Object, error) {
- return true, pv, nil
- })
- fakeClient.AddReactor("*", "*", func(action core.Action) (bool, runtime.Object, error) {
- return true, nil, fmt.Errorf("no reaction implemented for %s", action)
- })
- return fakeClient
- }
- func Test_Run_Positive_VolumeMountControllerAttachEnabledRace(t *testing.T) {
- // Arrange
- volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t)
- dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
- asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr)
- kubeClient := createTestClient()
- fakeRecorder := &record.FakeRecorder{}
- fakeHandler := volumetesting.NewBlockVolumePathHandler()
- oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
- kubeClient,
- volumePluginMgr,
- fakeRecorder,
- false, /* checkNodeCapabilitiesBeforeMount */
- fakeHandler))
- reconciler := NewReconciler(
- kubeClient,
- true, /* controllerAttachDetachEnabled */
- reconcilerLoopSleepDuration,
- waitForAttachTimeout,
- nodeName,
- dsw,
- asw,
- hasAddedPods,
- oex,
- mount.NewFakeMounter(nil),
- hostutil.NewFakeHostUtil(nil),
- volumePluginMgr,
- kubeletPodsDir)
- pod := &v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod1",
- UID: "pod1uid",
- },
- Spec: v1.PodSpec{
- Volumes: []v1.Volume{
- {
- Name: "volume-name",
- VolumeSource: v1.VolumeSource{
- GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{
- PDName: "fake-device1",
- },
- },
- },
- },
- },
- }
- // Some steps are executes out of order in callbacks, follow the numbers.
- // 1. Add a volume to DSW and wait until it's mounted
- volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]}
- podName := util.GetUniquePodName(pod)
- generatedVolumeName, err := dsw.AddPodToVolume(
- podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */)
- dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeName})
- if err != nil {
- t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
- }
- // Start the reconciler to fill ASW.
- stopChan, stoppedChan := make(chan struct{}), make(chan struct{})
- go func() {
- reconciler.Run(stopChan)
- close(stoppedChan)
- }()
- waitForMount(t, fakePlugin, generatedVolumeName, asw)
- // Stop the reconciler.
- close(stopChan)
- <-stoppedChan
- finished := make(chan interface{})
- fakePlugin.UnmountDeviceHook = func(mountPath string) error {
- // Act:
- // 3. While a volume is being unmounted, add it back to the desired state of world
- klog.Infof("UnmountDevice called")
- generatedVolumeName, err = dsw.AddPodToVolume(
- podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */)
- dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeName})
- return nil
- }
- fakePlugin.WaitForAttachHook = func(spec *volume.Spec, devicePath string, pod *v1.Pod, spectimeout time.Duration) (string, error) {
- // Assert
- // 4. When the volume is mounted again, expect that UnmountDevice operation did not clear devicePath
- if devicePath == "" {
- t.Errorf("Expected WaitForAttach called with devicePath from Node.Status")
- close(finished)
- return "", fmt.Errorf("Expected devicePath from Node.Status")
- }
- close(finished)
- return devicePath, nil
- }
- // Start the reconciler again.
- go reconciler.Run(wait.NeverStop)
- // 2. Delete the volume from DSW (and wait for callbacks)
- dsw.DeletePodFromVolume(podName, generatedVolumeName)
- <-finished
- waitForMount(t, fakePlugin, generatedVolumeName, asw)
- }
|