storage e2e: test volume attach limits of generic ephemeral volumes

There are unit tests for this particular code path in kube-scheduler, but no
E2E tests.
This commit is contained in:
Patrick Ohly 2021-10-13 15:57:55 +02:00
parent dea052ceba
commit 4568cdada2

View File

@ -32,6 +32,7 @@ import (
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
"k8s.io/component-helpers/storage/ephemeral"
migrationplugins "k8s.io/csi-translation-lib/plugins" // volume plugin names are exported nicely there migrationplugins "k8s.io/csi-translation-lib/plugins" // volume plugin names are exported nicely there
volumeutil "k8s.io/kubernetes/pkg/volume/util" volumeutil "k8s.io/kubernetes/pkg/volume/util"
"k8s.io/kubernetes/test/e2e/framework" "k8s.io/kubernetes/test/e2e/framework"
@ -74,6 +75,7 @@ func InitCustomVolumeLimitsTestSuite(patterns []storageframework.TestPattern) st
func InitVolumeLimitsTestSuite() storageframework.TestSuite { func InitVolumeLimitsTestSuite() storageframework.TestSuite {
patterns := []storageframework.TestPattern{ patterns := []storageframework.TestPattern{
storageframework.FsVolModeDynamicPV, storageframework.FsVolModeDynamicPV,
storageframework.DefaultFsGenericEphemeralVolume,
} }
return InitCustomVolumeLimitsTestSuite(patterns) return InitCustomVolumeLimitsTestSuite(patterns)
} }
@ -95,14 +97,14 @@ func (t *volumeLimitsTestSuite) DefineTests(driver storageframework.TestDriver,
// VolumeResource contains pv, pvc, sc, etc. of the first pod created // VolumeResource contains pv, pvc, sc, etc. of the first pod created
resource *storageframework.VolumeResource resource *storageframework.VolumeResource
// All created PVCs, incl. the one in resource // All created PVCs
pvcs []*v1.PersistentVolumeClaim pvcNames []string
// All created Pods
podNames []string
// All created PVs, incl. the one in resource // All created PVs, incl. the one in resource
pvNames sets.String pvNames sets.String
runningPod *v1.Pod
unschedulablePod *v1.Pod
} }
var ( var (
l local l local
@ -164,57 +166,64 @@ func (t *volumeLimitsTestSuite) DefineTests(driver storageframework.TestDriver,
framework.ExpectNoError(err, "while cleaning up resource") framework.ExpectNoError(err, "while cleaning up resource")
}() }()
defer func() { defer func() {
cleanupTest(l.cs, l.ns.Name, l.runningPod.Name, l.unschedulablePod.Name, l.pvcs, l.pvNames, testSlowMultiplier*f.Timeouts.PVDelete) cleanupTest(l.cs, l.ns.Name, l.podNames, l.pvcNames, l.pvNames, testSlowMultiplier*f.Timeouts.PVDelete)
}() }()
// Create <limit> PVCs for one gigantic pod.
ginkgo.By(fmt.Sprintf("Creating %d PVC(s)", limit))
for i := 0; i < limit; i++ {
pvc := e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{
ClaimSize: claimSize,
StorageClassName: &l.resource.Sc.Name,
}, l.ns.Name)
pvc, err = l.cs.CoreV1().PersistentVolumeClaims(l.ns.Name).Create(context.TODO(), pvc, metav1.CreateOptions{})
framework.ExpectNoError(err)
l.pvcs = append(l.pvcs, pvc)
}
ginkgo.By("Creating pod to use all PVC(s)")
selection := e2epod.NodeSelection{Name: nodeName} selection := e2epod.NodeSelection{Name: nodeName}
podConfig := e2epod.Config{
NS: l.ns.Name, if pattern.VolType == storageframework.GenericEphemeralVolume {
PVCs: l.pvcs, // Create <limit> Pods.
SeLinuxLabel: e2epv.SELinuxLabel, ginkgo.By(fmt.Sprintf("Creating %d Pod(s) with one volume each", limit))
NodeSelection: selection, for i := 0; i < limit; i++ {
pod := StartInPodWithVolumeSource(l.cs, *l.resource.VolSource, l.ns.Name, "volume-limits", "sleep 1000000", selection)
l.podNames = append(l.podNames, pod.Name)
l.pvcNames = append(l.pvcNames, ephemeral.VolumeClaimName(pod, &pod.Spec.Volumes[0]))
}
} else {
// Create <limit> PVCs for one gigantic pod.
var pvcs []*v1.PersistentVolumeClaim
ginkgo.By(fmt.Sprintf("Creating %d PVC(s)", limit))
for i := 0; i < limit; i++ {
pvc := e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{
ClaimSize: claimSize,
StorageClassName: &l.resource.Sc.Name,
}, l.ns.Name)
pvc, err = l.cs.CoreV1().PersistentVolumeClaims(l.ns.Name).Create(context.TODO(), pvc, metav1.CreateOptions{})
framework.ExpectNoError(err)
l.pvcNames = append(l.pvcNames, pvc.Name)
pvcs = append(pvcs, pvc)
}
ginkgo.By("Creating pod to use all PVC(s)")
podConfig := e2epod.Config{
NS: l.ns.Name,
PVCs: pvcs,
SeLinuxLabel: e2epv.SELinuxLabel,
NodeSelection: selection,
}
pod, err := e2epod.MakeSecPod(&podConfig)
framework.ExpectNoError(err)
pod, err = l.cs.CoreV1().Pods(l.ns.Name).Create(context.TODO(), pod, metav1.CreateOptions{})
framework.ExpectNoError(err)
l.podNames = append(l.podNames, pod.Name)
} }
pod, err := e2epod.MakeSecPod(&podConfig)
framework.ExpectNoError(err)
l.runningPod, err = l.cs.CoreV1().Pods(l.ns.Name).Create(context.TODO(), pod, metav1.CreateOptions{})
framework.ExpectNoError(err)
ginkgo.By("Waiting for all PVCs to get Bound") ginkgo.By("Waiting for all PVCs to get Bound")
l.pvNames, err = waitForAllPVCsBound(l.cs, testSlowMultiplier*f.Timeouts.PVBound, l.pvcs) l.pvNames, err = waitForAllPVCsBound(l.cs, testSlowMultiplier*f.Timeouts.PVBound, l.ns.Name, l.pvcNames)
framework.ExpectNoError(err) framework.ExpectNoError(err)
ginkgo.By("Waiting for the pod Running") ginkgo.By("Waiting for the pod(s) running")
err = e2epod.WaitTimeoutForPodRunningInNamespace(l.cs, l.runningPod.Name, l.ns.Name, testSlowMultiplier*f.Timeouts.PodStart) for _, podName := range l.podNames {
framework.ExpectNoError(err) err = e2epod.WaitTimeoutForPodRunningInNamespace(l.cs, podName, l.ns.Name, testSlowMultiplier*f.Timeouts.PodStart)
framework.ExpectNoError(err)
}
ginkgo.By("Creating an extra pod with one volume to exceed the limit") ginkgo.By("Creating an extra pod with one volume to exceed the limit")
podConfig = e2epod.Config{ pod := StartInPodWithVolumeSource(l.cs, *l.resource.VolSource, l.ns.Name, "volume-limits-exceeded", "sleep 10000", selection)
NS: l.ns.Name, l.podNames = append(l.podNames, pod.Name)
PVCs: []*v1.PersistentVolumeClaim{l.resource.Pvc},
SeLinuxLabel: e2epv.SELinuxLabel,
NodeSelection: selection,
}
pod, err = e2epod.MakeSecPod(&podConfig)
framework.ExpectNoError(err)
l.unschedulablePod, err = l.cs.CoreV1().Pods(l.ns.Name).Create(context.TODO(), pod, metav1.CreateOptions{})
framework.ExpectNoError(err, "Failed to create an extra pod with one volume to exceed the limit")
ginkgo.By("Waiting for the pod to get unschedulable with the right message") ginkgo.By("Waiting for the pod to get unschedulable with the right message")
err = e2epod.WaitForPodCondition(l.cs, l.ns.Name, l.unschedulablePod.Name, "Unschedulable", f.Timeouts.PodStart, func(pod *v1.Pod) (bool, error) { err = e2epod.WaitForPodCondition(l.cs, l.ns.Name, pod.Name, "Unschedulable", f.Timeouts.PodStart, func(pod *v1.Pod) (bool, error) {
if pod.Status.Phase == v1.PodPending { if pod.Status.Phase == v1.PodPending {
reg, err := regexp.Compile(`max.+volume.+count`) reg, err := regexp.Compile(`max.+volume.+count`)
if err != nil { if err != nil {
@ -270,24 +279,18 @@ func (t *volumeLimitsTestSuite) DefineTests(driver storageframework.TestDriver,
}) })
} }
func cleanupTest(cs clientset.Interface, ns string, runningPodName, unschedulablePodName string, pvcs []*v1.PersistentVolumeClaim, pvNames sets.String, timeout time.Duration) error { func cleanupTest(cs clientset.Interface, ns string, podNames, pvcNames []string, pvNames sets.String, timeout time.Duration) error {
var cleanupErrors []string var cleanupErrors []string
if runningPodName != "" { for _, podName := range podNames {
err := cs.CoreV1().Pods(ns).Delete(context.TODO(), runningPodName, metav1.DeleteOptions{}) err := cs.CoreV1().Pods(ns).Delete(context.TODO(), podName, metav1.DeleteOptions{})
if err != nil { if err != nil {
cleanupErrors = append(cleanupErrors, fmt.Sprintf("failed to delete pod %s: %s", runningPodName, err)) cleanupErrors = append(cleanupErrors, fmt.Sprintf("failed to delete pod %s: %s", podName, err))
} }
} }
if unschedulablePodName != "" { for _, pvcName := range pvcNames {
err := cs.CoreV1().Pods(ns).Delete(context.TODO(), unschedulablePodName, metav1.DeleteOptions{}) err := cs.CoreV1().PersistentVolumeClaims(ns).Delete(context.TODO(), pvcName, metav1.DeleteOptions{})
if err != nil { if !apierrors.IsNotFound(err) {
cleanupErrors = append(cleanupErrors, fmt.Sprintf("failed to delete pod %s: %s", unschedulablePodName, err)) cleanupErrors = append(cleanupErrors, fmt.Sprintf("failed to delete PVC %s: %s", pvcName, err))
}
}
for _, pvc := range pvcs {
err := cs.CoreV1().PersistentVolumeClaims(ns).Delete(context.TODO(), pvc.Name, metav1.DeleteOptions{})
if err != nil {
cleanupErrors = append(cleanupErrors, fmt.Sprintf("failed to delete PVC %s: %s", pvc.Name, err))
} }
} }
// Wait for the PVs to be deleted. It includes also pod and PVC deletion because of PVC protection. // Wait for the PVs to be deleted. It includes also pod and PVC deletion because of PVC protection.
@ -323,12 +326,12 @@ func cleanupTest(cs clientset.Interface, ns string, runningPodName, unschedulabl
} }
// waitForAllPVCsBound waits until the given PVCs are all bound. It then returns the bound PVC names as a set. // waitForAllPVCsBound waits until the given PVCs are all bound. It then returns the bound PVC names as a set.
func waitForAllPVCsBound(cs clientset.Interface, timeout time.Duration, pvcs []*v1.PersistentVolumeClaim) (sets.String, error) { func waitForAllPVCsBound(cs clientset.Interface, timeout time.Duration, ns string, pvcNames []string) (sets.String, error) {
pvNames := sets.NewString() pvNames := sets.NewString()
err := wait.Poll(5*time.Second, timeout, func() (bool, error) { err := wait.Poll(5*time.Second, timeout, func() (bool, error) {
unbound := 0 unbound := 0
for _, pvc := range pvcs { for _, pvcName := range pvcNames {
pvc, err := cs.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(context.TODO(), pvc.Name, metav1.GetOptions{}) pvc, err := cs.CoreV1().PersistentVolumeClaims(ns).Get(context.TODO(), pvcName, metav1.GetOptions{})
if err != nil { if err != nil {
return false, err return false, err
} }
@ -339,7 +342,7 @@ func waitForAllPVCsBound(cs clientset.Interface, timeout time.Duration, pvcs []*
} }
} }
if unbound > 0 { if unbound > 0 {
framework.Logf("%d/%d of PVCs are Bound", pvNames.Len(), len(pvcs)) framework.Logf("%d/%d of PVCs are Bound", pvNames.Len(), len(pvcNames))
return false, nil return false, nil
} }
return true, nil return true, nil