mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 20:24:09 +00:00
Merge pull request #79696 from yittg/fix-pv-controller-affinity
fix pv-controller sync check node affinity for scheduled claim
This commit is contained in:
commit
6f8944aa54
@ -248,6 +248,17 @@ func TestSync(t *testing.T) {
|
|||||||
},
|
},
|
||||||
noevents, noerrors, testSyncClaim,
|
noevents, noerrors, testSyncClaim,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// syncClaim that scheduled to a selected node
|
||||||
|
"1-18 - successful pre-bound PV to PVC provisioning",
|
||||||
|
newVolumeArray("volume1-18", "1Gi", "uid1-18", "claim1-18", v1.VolumeAvailable, v1.PersistentVolumeReclaimRetain, classWait),
|
||||||
|
newVolumeArray("volume1-18", "1Gi", "uid1-18", "claim1-18", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classWait),
|
||||||
|
claimWithAnnotation(pvutil.AnnSelectedNode, "node1",
|
||||||
|
newClaimArray("claim1-18", "uid1-18", "1Gi", "", v1.ClaimPending, &classWait)),
|
||||||
|
claimWithAnnotation(pvutil.AnnSelectedNode, "node1",
|
||||||
|
newClaimArray("claim1-18", "uid1-18", "1Gi", "volume1-18", v1.ClaimBound, &classWait, pvutil.AnnBoundByController, pvutil.AnnBindCompleted)),
|
||||||
|
noevents, noerrors, testSyncClaim,
|
||||||
|
},
|
||||||
|
|
||||||
// [Unit test set 2] User asked for a specific PV.
|
// [Unit test set 2] User asked for a specific PV.
|
||||||
// Test the binding when pv.ClaimRef is already set by controller or
|
// Test the binding when pv.ClaimRef is already set by controller or
|
||||||
|
@ -471,9 +471,11 @@ const operationRecycle = "Recycle"
|
|||||||
var (
|
var (
|
||||||
classGold string = "gold"
|
classGold string = "gold"
|
||||||
classSilver string = "silver"
|
classSilver string = "silver"
|
||||||
|
classCopper string = "copper"
|
||||||
classEmpty string = ""
|
classEmpty string = ""
|
||||||
classNonExisting string = "non-existing"
|
classNonExisting string = "non-existing"
|
||||||
classExternal string = "external"
|
classExternal string = "external"
|
||||||
|
classExternalWait string = "external-wait"
|
||||||
classUnknownInternal string = "unknown-internal"
|
classUnknownInternal string = "unknown-internal"
|
||||||
classUnsupportedMountOptions string = "unsupported-mountoptions"
|
classUnsupportedMountOptions string = "unsupported-mountoptions"
|
||||||
classLarge string = "large"
|
classLarge string = "large"
|
||||||
|
@ -24,6 +24,8 @@ import (
|
|||||||
storage "k8s.io/api/storage/v1"
|
storage "k8s.io/api/storage/v1"
|
||||||
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
corelisters "k8s.io/client-go/listers/core/v1"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
pvtesting "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/testing"
|
pvtesting "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/testing"
|
||||||
pvutil "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/util"
|
pvutil "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/util"
|
||||||
@ -64,6 +66,18 @@ var storageClasses = []*storage.StorageClass{
|
|||||||
ReclaimPolicy: &deleteReclaimPolicy,
|
ReclaimPolicy: &deleteReclaimPolicy,
|
||||||
VolumeBindingMode: &modeImmediate,
|
VolumeBindingMode: &modeImmediate,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: "StorageClass",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "copper",
|
||||||
|
},
|
||||||
|
Provisioner: mockPluginName,
|
||||||
|
Parameters: class1Parameters,
|
||||||
|
ReclaimPolicy: &deleteReclaimPolicy,
|
||||||
|
VolumeBindingMode: &modeWait,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
TypeMeta: metav1.TypeMeta{
|
TypeMeta: metav1.TypeMeta{
|
||||||
Kind: "StorageClass",
|
Kind: "StorageClass",
|
||||||
@ -76,6 +90,18 @@ var storageClasses = []*storage.StorageClass{
|
|||||||
ReclaimPolicy: &deleteReclaimPolicy,
|
ReclaimPolicy: &deleteReclaimPolicy,
|
||||||
VolumeBindingMode: &modeImmediate,
|
VolumeBindingMode: &modeImmediate,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: "StorageClass",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "external-wait",
|
||||||
|
},
|
||||||
|
Provisioner: "vendor.com/my-volume-wait",
|
||||||
|
Parameters: class1Parameters,
|
||||||
|
ReclaimPolicy: &deleteReclaimPolicy,
|
||||||
|
VolumeBindingMode: &modeWait,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
TypeMeta: metav1.TypeMeta{
|
TypeMeta: metav1.TypeMeta{
|
||||||
Kind: "StorageClass",
|
Kind: "StorageClass",
|
||||||
@ -443,6 +469,41 @@ func TestProvisionSync(t *testing.T) {
|
|||||||
noevents,
|
noevents,
|
||||||
noerrors, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
|
noerrors, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// volume provision for PVC scheduled
|
||||||
|
"11-23 - skip finding PV and provision for PVC annotated with AnnSelectedNode",
|
||||||
|
newVolumeArray("volume11-23", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimDelete, classCopper),
|
||||||
|
[]*v1.PersistentVolume{
|
||||||
|
newVolume("volume11-23", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimDelete, classCopper),
|
||||||
|
newVolume("pvc-uid11-23", "1Gi", "uid11-23", "claim11-23", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classCopper, pvutil.AnnDynamicallyProvisioned, pvutil.AnnBoundByController),
|
||||||
|
},
|
||||||
|
claimWithAnnotation(pvutil.AnnSelectedNode, "node1",
|
||||||
|
newClaimArray("claim11-23", "uid11-23", "1Gi", "", v1.ClaimPending, &classCopper)),
|
||||||
|
claimWithAnnotation(pvutil.AnnSelectedNode, "node1",
|
||||||
|
newClaimArray("claim11-23", "uid11-23", "1Gi", "", v1.ClaimPending, &classCopper, pvutil.AnnStorageProvisioner)),
|
||||||
|
[]string{"Normal ProvisioningSucceeded"},
|
||||||
|
noerrors,
|
||||||
|
wrapTestWithInjectedOperation(wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
|
||||||
|
func(ctrl *PersistentVolumeController, reactor *pvtesting.VolumeReactor) {
|
||||||
|
nodesIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
|
||||||
|
node := &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node1"}}
|
||||||
|
nodesIndexer.Add(node)
|
||||||
|
ctrl.NodeLister = corelisters.NewNodeLister(nodesIndexer)
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// volume provision for PVC that scheduled
|
||||||
|
"11-24 - skip finding PV and wait external provisioner for PVC annotated with AnnSelectedNode",
|
||||||
|
newVolumeArray("volume11-24", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimDelete, classExternalWait),
|
||||||
|
newVolumeArray("volume11-24", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimDelete, classExternalWait),
|
||||||
|
claimWithAnnotation(pvutil.AnnSelectedNode, "node1",
|
||||||
|
newClaimArray("claim11-24", "uid11-24", "1Gi", "", v1.ClaimPending, &classExternalWait)),
|
||||||
|
claimWithAnnotation(pvutil.AnnStorageProvisioner, "vendor.com/my-volume-wait",
|
||||||
|
claimWithAnnotation(pvutil.AnnSelectedNode, "node1",
|
||||||
|
newClaimArray("claim11-24", "uid11-24", "1Gi", "", v1.ClaimPending, &classExternalWait))),
|
||||||
|
[]string{"Normal ExternalProvisioning"},
|
||||||
|
noerrors, testSyncClaim,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
runSyncTests(t, tests, storageClasses, []*v1.Pod{})
|
runSyncTests(t, tests, storageClasses, []*v1.Pod{})
|
||||||
}
|
}
|
||||||
|
@ -281,27 +281,6 @@ func checkVolumeSatisfyClaim(volume *v1.PersistentVolume, claim *v1.PersistentVo
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctrl *PersistentVolumeController) isDelayBindingProvisioning(claim *v1.PersistentVolumeClaim) bool {
|
|
||||||
// When feature VolumeScheduling enabled,
|
|
||||||
// Scheduler signal to the PV controller to start dynamic
|
|
||||||
// provisioning by setting the "AnnSelectedNode" annotation
|
|
||||||
// in the PVC
|
|
||||||
_, ok := claim.Annotations[pvutil.AnnSelectedNode]
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// shouldDelayBinding returns true if binding of claim should be delayed, false otherwise.
|
|
||||||
// If binding of claim should be delayed, only claims pbound by scheduler
|
|
||||||
func (ctrl *PersistentVolumeController) shouldDelayBinding(claim *v1.PersistentVolumeClaim) (bool, error) {
|
|
||||||
// If claim has already been assigned a node by scheduler for dynamic provisioning.
|
|
||||||
if ctrl.isDelayBindingProvisioning(claim) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If claim is in delay binding mode.
|
|
||||||
return pvutil.IsDelayBindingMode(claim, ctrl.classLister)
|
|
||||||
}
|
|
||||||
|
|
||||||
// syncUnboundClaim is the main controller method to decide what to do with an
|
// syncUnboundClaim is the main controller method to decide what to do with an
|
||||||
// unbound claim.
|
// unbound claim.
|
||||||
func (ctrl *PersistentVolumeController) syncUnboundClaim(claim *v1.PersistentVolumeClaim) error {
|
func (ctrl *PersistentVolumeController) syncUnboundClaim(claim *v1.PersistentVolumeClaim) error {
|
||||||
@ -309,7 +288,7 @@ func (ctrl *PersistentVolumeController) syncUnboundClaim(claim *v1.PersistentVol
|
|||||||
// OBSERVATION: pvc is "Pending"
|
// OBSERVATION: pvc is "Pending"
|
||||||
if claim.Spec.VolumeName == "" {
|
if claim.Spec.VolumeName == "" {
|
||||||
// User did not care which PV they get.
|
// User did not care which PV they get.
|
||||||
delayBinding, err := ctrl.shouldDelayBinding(claim)
|
delayBinding, err := pvutil.IsDelayBindingMode(claim, ctrl.classLister)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -325,7 +304,7 @@ func (ctrl *PersistentVolumeController) syncUnboundClaim(claim *v1.PersistentVol
|
|||||||
// No PV could be found
|
// No PV could be found
|
||||||
// OBSERVATION: pvc is "Pending", will retry
|
// OBSERVATION: pvc is "Pending", will retry
|
||||||
switch {
|
switch {
|
||||||
case delayBinding:
|
case delayBinding && !pvutil.IsDelayBindingProvisioning(claim):
|
||||||
ctrl.eventRecorder.Event(claim, v1.EventTypeNormal, events.WaitForFirstConsumer, "waiting for first consumer to be created before binding")
|
ctrl.eventRecorder.Event(claim, v1.EventTypeNormal, events.WaitForFirstConsumer, "waiting for first consumer to be created before binding")
|
||||||
case v1helper.GetPersistentVolumeClaimClass(claim) != "":
|
case v1helper.GetPersistentVolumeClaimClass(claim) != "":
|
||||||
if err = ctrl.provisionClaim(claim); err != nil {
|
if err = ctrl.provisionClaim(claim); err != nil {
|
||||||
|
@ -374,7 +374,7 @@ func TestControllerCacheParsingError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makePVCClass(scName *string, hasSelectNodeAnno bool) *v1.PersistentVolumeClaim {
|
func makePVCClass(scName *string) *v1.PersistentVolumeClaim {
|
||||||
claim := &v1.PersistentVolumeClaim{
|
claim := &v1.PersistentVolumeClaim{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Annotations: map[string]string{},
|
Annotations: map[string]string{},
|
||||||
@ -384,10 +384,6 @@ func makePVCClass(scName *string, hasSelectNodeAnno bool) *v1.PersistentVolumeCl
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasSelectNodeAnno {
|
|
||||||
claim.Annotations[pvutil.AnnSelectedNode] = "node-name"
|
|
||||||
}
|
|
||||||
|
|
||||||
return claim
|
return claim
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,37 +396,33 @@ func makeStorageClass(scName string, mode *storagev1.VolumeBindingMode) *storage
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDelayBinding(t *testing.T) {
|
func TestDelayBindingMode(t *testing.T) {
|
||||||
tests := map[string]struct {
|
tests := map[string]struct {
|
||||||
pvc *v1.PersistentVolumeClaim
|
pvc *v1.PersistentVolumeClaim
|
||||||
shouldDelay bool
|
shouldDelay bool
|
||||||
shouldFail bool
|
shouldFail bool
|
||||||
}{
|
}{
|
||||||
"nil-class": {
|
"nil-class": {
|
||||||
pvc: makePVCClass(nil, false),
|
pvc: makePVCClass(nil),
|
||||||
shouldDelay: false,
|
shouldDelay: false,
|
||||||
},
|
},
|
||||||
"class-not-found": {
|
"class-not-found": {
|
||||||
pvc: makePVCClass(&classNotHere, false),
|
pvc: makePVCClass(&classNotHere),
|
||||||
shouldDelay: false,
|
shouldDelay: false,
|
||||||
},
|
},
|
||||||
"no-mode-class": {
|
"no-mode-class": {
|
||||||
pvc: makePVCClass(&classNoMode, false),
|
pvc: makePVCClass(&classNoMode),
|
||||||
shouldDelay: false,
|
shouldDelay: false,
|
||||||
shouldFail: true,
|
shouldFail: true,
|
||||||
},
|
},
|
||||||
"immediate-mode-class": {
|
"immediate-mode-class": {
|
||||||
pvc: makePVCClass(&classImmediateMode, false),
|
pvc: makePVCClass(&classImmediateMode),
|
||||||
shouldDelay: false,
|
shouldDelay: false,
|
||||||
},
|
},
|
||||||
"wait-mode-class": {
|
"wait-mode-class": {
|
||||||
pvc: makePVCClass(&classWaitMode, false),
|
pvc: makePVCClass(&classWaitMode),
|
||||||
shouldDelay: true,
|
shouldDelay: true,
|
||||||
},
|
},
|
||||||
"wait-mode-class-with-selectedNode": {
|
|
||||||
pvc: makePVCClass(&classWaitMode, true),
|
|
||||||
shouldDelay: false,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
classes := []*storagev1.StorageClass{
|
classes := []*storagev1.StorageClass{
|
||||||
@ -453,7 +445,7 @@ func TestDelayBinding(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for name, test := range tests {
|
for name, test := range tests {
|
||||||
shouldDelay, err := ctrl.shouldDelayBinding(test.pvc)
|
shouldDelay, err := pvutil.IsDelayBindingMode(test.pvc, ctrl.classLister)
|
||||||
if err != nil && !test.shouldFail {
|
if err != nil && !test.shouldFail {
|
||||||
t.Errorf("Test %q returned error: %v", name, err)
|
t.Errorf("Test %q returned error: %v", name, err)
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,16 @@ const (
|
|||||||
AnnStorageProvisioner = "volume.beta.kubernetes.io/storage-provisioner"
|
AnnStorageProvisioner = "volume.beta.kubernetes.io/storage-provisioner"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IsDelayBindingProvisioning checks if claim provisioning with selected-node annotation
|
||||||
|
func IsDelayBindingProvisioning(claim *v1.PersistentVolumeClaim) bool {
|
||||||
|
// When feature VolumeScheduling enabled,
|
||||||
|
// Scheduler signal to the PV controller to start dynamic
|
||||||
|
// provisioning by setting the "AnnSelectedNode" annotation
|
||||||
|
// in the PVC
|
||||||
|
_, ok := claim.Annotations[AnnSelectedNode]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
// IsDelayBindingMode checks if claim is in delay binding mode.
|
// IsDelayBindingMode checks if claim is in delay binding mode.
|
||||||
func IsDelayBindingMode(claim *v1.PersistentVolumeClaim, classLister storagelisters.StorageClassLister) (bool, error) {
|
func IsDelayBindingMode(claim *v1.PersistentVolumeClaim, classLister storagelisters.StorageClassLister) (bool, error) {
|
||||||
className := v1helper.GetPersistentVolumeClaimClass(claim)
|
className := v1helper.GetPersistentVolumeClaimClass(claim)
|
||||||
|
Loading…
Reference in New Issue
Block a user