Merge pull request #121902 from carlory/kep-3751-pv-controller

[kep-3751] pvc bind pv with vac
This commit is contained in:
Kubernetes Prow Robot
2024-07-23 11:02:13 -07:00
committed by GitHub
12 changed files with 534 additions and 37 deletions

View File

@@ -30,13 +30,16 @@ import (
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
ref "k8s.io/client-go/tools/reference"
featuregatetesting "k8s.io/component-base/featuregate/testing"
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
"k8s.io/kubernetes/pkg/api/legacyscheme"
persistentvolumecontroller "k8s.io/kubernetes/pkg/controller/volume/persistentvolume"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/volume"
volumetest "k8s.io/kubernetes/pkg/volume/testing"
"k8s.io/kubernetes/pkg/volume/util"
@@ -569,6 +572,180 @@ func TestPersistentVolumeMultiPVs(t *testing.T) {
t.Log("volumes released")
}
// TestPersistentVolumeClaimVolumeAttirbutesClassName test binding using volume attributes
// class name.
func TestPersistentVolumeClaimVolumeAttirbutesClassName(t *testing.T) {
var (
err error
modes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
reclaim = v1.PersistentVolumeReclaimRetain
namespaceName = "pvc-volume-attributes-class-name"
classEmpty = ""
classGold = "gold"
classSilver = "silver"
pv = createCSIPV("pv", "1G", modes, reclaim)
pvGold = createCSIPV("pv-gold", "1G", modes, reclaim)
pv2Gold = createCSIPV("pv2-gold", "1G", modes, reclaim)
pvSilver = createCSIPV("pv-silver", "1G", modes, reclaim)
pvc = createPVC("pvc", namespaceName, "1G", modes, "")
pvcEmpty = createPVC("pvc", namespaceName, "1G", modes, "")
pvcGold = createPVC("pvc-gold", namespaceName, "1G", modes, "")
)
// prepare PVs and PVCs
pv.Spec.VolumeAttributesClassName = nil
pvGold.Spec.VolumeAttributesClassName = &classGold
pv2Gold.Spec.VolumeAttributesClassName = &classGold
pvSilver.Spec.VolumeAttributesClassName = &classSilver
pvc.Spec.VolumeAttributesClassName = nil
pvcEmpty.Spec.VolumeAttributesClassName = &classEmpty
pvcGold.Spec.VolumeAttributesClassName = &classGold
testCases := []struct {
featureEnabled bool
name string
volumes []*v1.PersistentVolume
claim *v1.PersistentVolumeClaim
expectVolumeName string
}{
{
featureEnabled: true,
name: "claim with nil class bind to a pv",
volumes: []*v1.PersistentVolume{pv, pvGold, pvSilver},
claim: pvc,
expectVolumeName: pv.Name,
},
{
featureEnabled: true,
name: "claim with empty class bind to a pv",
volumes: []*v1.PersistentVolume{pv, pvGold, pvSilver},
claim: pvcEmpty,
expectVolumeName: pv.Name,
},
{
featureEnabled: true,
name: "claim bind to a pv with same class name",
volumes: []*v1.PersistentVolume{pv, pvGold, pvSilver},
claim: pvcGold,
expectVolumeName: pvGold.Name,
},
{
featureEnabled: true,
name: "claim bind to a user-asked pv with same class name",
volumes: []*v1.PersistentVolume{pv, pvGold, pv2Gold, pvSilver},
claim: func() *v1.PersistentVolumeClaim {
pvcGoldClone := pvcGold.DeepCopy()
pvcGoldClone.Spec.VolumeName = pv2Gold.Name
return pvcGoldClone
}(),
expectVolumeName: pv2Gold.Name,
},
{
featureEnabled: false,
name: "claim bind to a pv due to class name is dropped by kube-apiserver",
volumes: []*v1.PersistentVolume{pvGold},
claim: pvcGold,
expectVolumeName: pvGold.Name,
},
{
featureEnabled: false,
name: "claim with nil class bind to a pv",
volumes: []*v1.PersistentVolume{pv},
claim: pvc,
expectVolumeName: pv.Name,
},
{
featureEnabled: false,
name: "claim with empty class bind to a pv",
volumes: []*v1.PersistentVolume{pv},
claim: pvcEmpty,
expectVolumeName: pv.Name,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, tc.featureEnabled)
s := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount,StorageObjectInUseProtection"}, framework.SharedEtcd())
defer s.TearDownFn()
tCtx := ktesting.Init(t)
defer tCtx.Cancel("test has completed")
testClient, controller, informers, watchPV, watchPVC := createClients(tCtx, namespaceName, t, s, defaultSyncPeriod)
defer watchPV.Stop()
defer watchPVC.Stop()
ns := framework.CreateNamespaceOrDie(testClient, namespaceName, t)
defer framework.DeleteNamespaceOrDie(testClient, ns, t)
// NOTE: This test cannot run in parallel, because it is creating and deleting
// non-namespaced objects (PersistenceVolumes).
defer func() {
_ = testClient.CoreV1().PersistentVolumes().DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{})
}()
informers.Start(tCtx.Done())
go controller.Run(tCtx)
for _, volume := range tc.volumes {
_, err = testClient.CoreV1().PersistentVolumes().Create(context.TODO(), volume, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Failed to create PersistentVolume: %v", err)
}
}
t.Log("volumes created")
_, err = testClient.CoreV1().PersistentVolumeClaims(ns.Name).Create(context.TODO(), tc.claim, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Failed to create PersistentVolumeClaim: %v", err)
}
t.Log("claim created")
waitForAnyPersistentVolumePhase(watchPV, v1.VolumeBound)
t.Log("volume bound")
waitForPersistentVolumeClaimPhase(testClient, tc.claim.Name, ns.Name, watchPVC, v1.ClaimBound)
t.Log("claim bound")
gotClaim, err := testClient.CoreV1().PersistentVolumeClaims(ns.Name).Get(context.TODO(), tc.claim.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("Unexpected error getting pvc: %v", err)
}
if !tc.featureEnabled {
if gotClaim.Spec.VolumeAttributesClassName != nil || gotClaim.Status.CurrentVolumeAttributesClassName != nil {
t.Fatalf("unexpected volume class name on claim %q", gotClaim.Name)
}
}
for _, volume := range tc.volumes {
gotVolume, err := testClient.CoreV1().PersistentVolumes().Get(context.TODO(), volume.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("Unexpected error getting pv: %v", err)
}
if !tc.featureEnabled {
if gotVolume.Spec.VolumeAttributesClassName != nil {
t.Fatalf("unexpected volume class name on volume %q", gotVolume.Name)
}
}
if volume.Name == tc.expectVolumeName {
if gotVolume.Spec.ClaimRef == nil {
t.Fatalf("%s PV should be bound", volume.Name)
}
if gotVolume.Spec.ClaimRef.Namespace != tc.claim.Namespace || gotVolume.Spec.ClaimRef.Name != tc.claim.Name {
t.Fatalf("Bind mismatch! Expected %s/%s but got %s/%s", tc.claim.Namespace, tc.claim.Name, gotVolume.Spec.ClaimRef.Namespace, gotVolume.Spec.ClaimRef.Name)
}
} else if gotVolume.Spec.ClaimRef != nil {
t.Fatalf("%s PV shouldn't be bound", volume.Name)
}
}
})
}
}
// TestPersistentVolumeMultiPVsPVCs tests binding of 100 PVC to 100 PVs.
// This test is configurable by KUBE_INTEGRATION_PV_* variables.
func TestPersistentVolumeMultiPVsPVCs(t *testing.T) {
@@ -1433,3 +1610,15 @@ func createPVCWithNilStorageClass(name, namespace, cap string, mode []v1.Persist
},
}
}
func createCSIPV(name, cap string, mode []v1.PersistentVolumeAccessMode, reclaim v1.PersistentVolumeReclaimPolicy) *v1.PersistentVolume {
return &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: name},
Spec: v1.PersistentVolumeSpec{
PersistentVolumeSource: v1.PersistentVolumeSource{CSI: &v1.CSIPersistentVolumeSource{Driver: "mock-driver", VolumeHandle: "volume-handle"}},
Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse(cap)},
AccessModes: mode,
PersistentVolumeReclaimPolicy: reclaim,
},
}
}