From 68370c8aa65c78de5838064fc3bc4cae90fb05f4 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Fri, 19 Feb 2021 13:51:49 +0100 Subject: [PATCH] kubelet: more tests for generic ephemeral volumes This simulates various error scenarios (PVC not created for pod, feature disabled) and switching between feature disabled and enabled. --- .../desired_state_of_world_populator_test.go | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator_test.go b/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator_test.go index f2f6660b3fd..258bc9f66ba 100644 --- a/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator_test.go +++ b/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator_test.go @@ -22,6 +22,7 @@ import ( "fmt" + "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -454,6 +455,124 @@ func TestFindAndRemoveNonattachableVolumes(t *testing.T) { } } +func TestEphemeralVolumeOwnerCheck(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, true)() + + // create dswp + pod, pv, pvc := createEphemeralVolumeObjects("dswp-test-pod", "dswp-test-volume-name", false /* not owned */) + dswp, fakePodManager, _ := createDswpWithVolume(t, pv, pvc) + fakePodManager.AddPod(pod) + + podName := util.GetUniquePodName(pod) + + dswp.findAndAddNewPods() + if dswp.pods.processedPods[podName] { + t.Fatalf("%s should not have been processed by the populator", podName) + } + require.Equal(t, + []string{fmt.Sprintf("error processing PVC %s/%s: not the ephemeral PVC for the pod", pvc.Namespace, pvc.Name)}, + dswp.desiredStateOfWorld.PopPodErrors(podName), + ) +} + +func TestEphemeralVolumeEnablement(t *testing.T) { + // create dswp + pod, pv, pvc := createEphemeralVolumeObjects("dswp-test-pod", "dswp-test-volume-name", true /* owned */) + dswp, fakePodManager, fakesDSW := createDswpWithVolume(t, pv, pvc) + fakePodManager.AddPod(pod) + + podName := util.GetUniquePodName(pod) + volumeName := pod.Spec.Volumes[0].Name + generatedVolumeName := "fake-plugin/" + volumeName + + // Feature disabled -> refuse to process pod. + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, false)() + dswp.findAndAddNewPods() + if dswp.pods.processedPods[podName] { + t.Fatalf("%s should not have been processed by the populator", podName) + } + require.Equal(t, + []string{fmt.Sprintf("volume %s is a generic ephemeral volume, but that feature is disabled in kubelet", volumeName)}, + dswp.desiredStateOfWorld.PopPodErrors(podName), + ) + + // Enabled -> process pod. + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, true)() + dswp.findAndAddNewPods() + if !dswp.pods.processedPods[podName] { + t.Fatalf("Failed to record that the volumes for the specified pod: %s have been processed by the populator", podName) + } + + expectedVolumeName := v1.UniqueVolumeName(generatedVolumeName) + + volumeExists := fakesDSW.VolumeExists(expectedVolumeName) + if !volumeExists { + t.Fatalf( + "VolumeExists(%q) failed. Expected: Actual: <%v>", + expectedVolumeName, + volumeExists) + } + + if podExistsInVolume := fakesDSW.PodExistsInVolume( + podName, expectedVolumeName); !podExistsInVolume { + t.Fatalf( + "DSW PodExistsInVolume returned incorrect value. Expected: Actual: <%v>", + podExistsInVolume) + } + + verifyVolumeExistsInVolumesToMount( + t, v1.UniqueVolumeName(generatedVolumeName), false /* expectReportedInUse */, fakesDSW) + + //let the pod be terminated + podGet, exist := fakePodManager.GetPodByName(pod.Namespace, pod.Name) + if !exist { + t.Fatalf("Failed to get pod by pod name: %s and namespace: %s", pod.Name, pod.Namespace) + } + podGet.Status.Phase = v1.PodFailed + + // Pretend again that the feature is disabled. + // Removal of the pod and volumes is expected to work. + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, false)() + + dswp.findAndRemoveDeletedPods() + // Although Pod status is terminated, pod still exists in pod manager and actual state does not has this pod and volume information + // desired state populator will fail to delete this pod and volume first + volumeExists = fakesDSW.VolumeExists(expectedVolumeName) + if !volumeExists { + t.Fatalf( + "VolumeExists(%q) failed. Expected: Actual: <%v>", + expectedVolumeName, + volumeExists) + } + + if podExistsInVolume := fakesDSW.PodExistsInVolume( + podName, expectedVolumeName); !podExistsInVolume { + t.Fatalf( + "DSW PodExistsInVolume returned incorrect value. Expected: Actual: <%v>", + podExistsInVolume) + } + + // reconcile with actual state so that volume is added into the actual state + // desired state populator now can successfully delete the pod and volume + fakeASW := dswp.actualStateOfWorld + reconcileASW(fakeASW, fakesDSW, t) + dswp.findAndRemoveDeletedPods() + volumeExists = fakesDSW.VolumeExists(expectedVolumeName) + if volumeExists { + t.Fatalf( + "VolumeExists(%q) failed. Expected: Actual: <%v>", + expectedVolumeName, + volumeExists) + } + + if podExistsInVolume := fakesDSW.PodExistsInVolume( + podName, expectedVolumeName); podExistsInVolume { + t.Fatalf( + "DSW PodExistsInVolume returned incorrect value. Expected: Actual: <%v>", + podExistsInVolume) + } +} + func TestFindAndAddNewPods_FindAndRemoveDeletedPods_Valid_Block_VolumeDevices(t *testing.T) { // create dswp mode := v1.PersistentVolumeBlock @@ -1076,6 +1195,74 @@ func createPodWithVolume(pod, pv, pvc string, containers []v1.Container) *v1.Pod } } +func createEphemeralVolumeObjects(podName, volumeName string, owned bool) (pod *v1.Pod, pv *v1.PersistentVolume, pvc *v1.PersistentVolumeClaim) { + pod = &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + UID: "dswp-test-pod-uid", + Namespace: "dswp-test", + }, + Spec: v1.PodSpec{ + Volumes: []v1.Volume{ + { + Name: volumeName, + VolumeSource: v1.VolumeSource{ + Ephemeral: &v1.EphemeralVolumeSource{ + VolumeClaimTemplate: &v1.PersistentVolumeClaimTemplate{}, + }, + }, + }, + }, + Containers: []v1.Container{ + { + VolumeMounts: []v1.VolumeMount{ + { + Name: volumeName, + MountPath: "/mnt", + }, + }, + }, + }, + }, + Status: v1.PodStatus{ + Phase: v1.PodPhase("Running"), + }, + } + mode := v1.PersistentVolumeFilesystem + pv = &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: volumeName, + }, + Spec: v1.PersistentVolumeSpec{ + ClaimRef: &v1.ObjectReference{Namespace: "ns", Name: "file-bound"}, + VolumeMode: &mode, + }, + } + pvc = &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName + "-" + volumeName, + Namespace: pod.Namespace, + }, + Spec: v1.PersistentVolumeClaimSpec{ + VolumeName: "dswp-test-volume-name", + }, + Status: v1.PersistentVolumeClaimStatus{ + Phase: v1.ClaimBound, + }, + } + if owned { + controller := true + pvc.OwnerReferences = []metav1.OwnerReference{ + { + UID: pod.UID, + Name: podName, + Controller: &controller, + }, + } + } + return +} + func createDswpWithVolume(t *testing.T, pv *v1.PersistentVolume, pvc *v1.PersistentVolumeClaim) (*desiredStateOfWorldPopulator, kubepod.Manager, cache.DesiredStateOfWorld) { fakeVolumePluginMgr, _ := volumetesting.GetTestKubeletVolumePluginMgr(t) dswp, fakePodManager, fakesDSW := createDswpWithVolumeWithCustomPluginMgr(t, pv, pvc, fakeVolumePluginMgr)