mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 03:41:45 +00:00
Merge pull request #126108 from gnufied/changes-volume-recovery
Reduce state changes when expansion fails and mark certain failures as infeasible
This commit is contained in:
commit
107f621462
@ -498,7 +498,7 @@ func TestDropDisabledFieldsFromStatus(t *testing.T) {
|
|||||||
name: "for:newPVC=hasResizeStatus,oldPVC=nil, featuregate=false should drop field",
|
name: "for:newPVC=hasResizeStatus,oldPVC=nil, featuregate=false should drop field",
|
||||||
enableRecoverVolumeExpansionFailure: false,
|
enableRecoverVolumeExpansionFailure: false,
|
||||||
enableVolumeAttributesClass: false,
|
enableVolumeAttributesClass: false,
|
||||||
pvc: withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed),
|
pvc: withResizeStatus(core.PersistentVolumeClaimNodeResizeInfeasible),
|
||||||
oldPVC: nil,
|
oldPVC: nil,
|
||||||
expected: getPVC(),
|
expected: getPVC(),
|
||||||
},
|
},
|
||||||
@ -506,25 +506,25 @@ func TestDropDisabledFieldsFromStatus(t *testing.T) {
|
|||||||
name: "for:newPVC=hasResizeStatus,oldPVC=doesnot,featuregate=RecoverVolumeExpansionFailure=true; should keep field",
|
name: "for:newPVC=hasResizeStatus,oldPVC=doesnot,featuregate=RecoverVolumeExpansionFailure=true; should keep field",
|
||||||
enableRecoverVolumeExpansionFailure: true,
|
enableRecoverVolumeExpansionFailure: true,
|
||||||
enableVolumeAttributesClass: false,
|
enableVolumeAttributesClass: false,
|
||||||
pvc: withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed),
|
pvc: withResizeStatus(core.PersistentVolumeClaimNodeResizeInfeasible),
|
||||||
oldPVC: getPVC(),
|
oldPVC: getPVC(),
|
||||||
expected: withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed),
|
expected: withResizeStatus(core.PersistentVolumeClaimNodeResizeInfeasible),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "for:newPVC=hasResizeStatus,oldPVC=hasResizeStatus,featuregate=RecoverVolumeExpansionFailure=true; should keep field",
|
name: "for:newPVC=hasResizeStatus,oldPVC=hasResizeStatus,featuregate=RecoverVolumeExpansionFailure=true; should keep field",
|
||||||
enableRecoverVolumeExpansionFailure: true,
|
enableRecoverVolumeExpansionFailure: true,
|
||||||
enableVolumeAttributesClass: false,
|
enableVolumeAttributesClass: false,
|
||||||
pvc: withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed),
|
pvc: withResizeStatus(core.PersistentVolumeClaimNodeResizeInfeasible),
|
||||||
oldPVC: withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed),
|
oldPVC: withResizeStatus(core.PersistentVolumeClaimNodeResizeInfeasible),
|
||||||
expected: withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed),
|
expected: withResizeStatus(core.PersistentVolumeClaimNodeResizeInfeasible),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "for:newPVC=hasResizeStatus,oldPVC=hasResizeStatus,featuregate=false; should keep field",
|
name: "for:newPVC=hasResizeStatus,oldPVC=hasResizeStatus,featuregate=false; should keep field",
|
||||||
enableRecoverVolumeExpansionFailure: false,
|
enableRecoverVolumeExpansionFailure: false,
|
||||||
enableVolumeAttributesClass: false,
|
enableVolumeAttributesClass: false,
|
||||||
pvc: withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed),
|
pvc: withResizeStatus(core.PersistentVolumeClaimNodeResizeInfeasible),
|
||||||
oldPVC: withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed),
|
oldPVC: withResizeStatus(core.PersistentVolumeClaimNodeResizeInfeasible),
|
||||||
expected: withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed),
|
expected: withResizeStatus(core.PersistentVolumeClaimNodeResizeInfeasible),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "for:newPVC=hasVolumeAttributeClass,oldPVC=nil, featuregate=false should drop field",
|
name: "for:newPVC=hasVolumeAttributeClass,oldPVC=nil, featuregate=false should drop field",
|
||||||
|
@ -548,16 +548,28 @@ type TypedObjectReference struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PersistentVolumeClaimConditionType defines the condition of PV claim.
|
// PersistentVolumeClaimConditionType defines the condition of PV claim.
|
||||||
// Valid values are either "Resizing" or "FileSystemResizePending".
|
// Valid values are:
|
||||||
|
// - "Resizing", "FileSystemResizePending"
|
||||||
|
//
|
||||||
|
// If RecoverVolumeExpansionFailure feature gate is enabled, then following additional values can be expected:
|
||||||
|
// - "ControllerResizeError", "NodeResizeError"
|
||||||
|
//
|
||||||
|
// If VolumeAttributesClass feature gate is enabled, then following additional values can be expected:
|
||||||
|
// - "ModifyVolumeError", "ModifyingVolume"
|
||||||
type PersistentVolumeClaimConditionType string
|
type PersistentVolumeClaimConditionType string
|
||||||
|
|
||||||
// These are valid conditions of Pvc
|
// These are valid conditions of PVC
|
||||||
const (
|
const (
|
||||||
// An user trigger resize of pvc has been started
|
// An user trigger resize of pvc has been started
|
||||||
PersistentVolumeClaimResizing PersistentVolumeClaimConditionType = "Resizing"
|
PersistentVolumeClaimResizing PersistentVolumeClaimConditionType = "Resizing"
|
||||||
// PersistentVolumeClaimFileSystemResizePending - controller resize is finished and a file system resize is pending on node
|
// PersistentVolumeClaimFileSystemResizePending - controller resize is finished and a file system resize is pending on node
|
||||||
PersistentVolumeClaimFileSystemResizePending PersistentVolumeClaimConditionType = "FileSystemResizePending"
|
PersistentVolumeClaimFileSystemResizePending PersistentVolumeClaimConditionType = "FileSystemResizePending"
|
||||||
|
|
||||||
|
// PersistentVolumeClaimControllerResizeError indicates an error while resizing volume for size in the controller
|
||||||
|
PersistentVolumeClaimControllerResizeError PersistentVolumeClaimConditionType = "ControllerResizeError"
|
||||||
|
// PersistentVolumeClaimNodeResizeError indicates an error while resizing volume for size in the node.
|
||||||
|
PersistentVolumeClaimNodeResizeError PersistentVolumeClaimConditionType = "NodeResizeError"
|
||||||
|
|
||||||
// Applying the target VolumeAttributesClass encountered an error
|
// Applying the target VolumeAttributesClass encountered an error
|
||||||
PersistentVolumeClaimVolumeModifyVolumeError PersistentVolumeClaimConditionType = "ModifyVolumeError"
|
PersistentVolumeClaimVolumeModifyVolumeError PersistentVolumeClaimConditionType = "ModifyVolumeError"
|
||||||
// Volume is being modified
|
// Volume is being modified
|
||||||
@ -574,18 +586,19 @@ const (
|
|||||||
// State set when resize controller starts resizing the volume in control-plane
|
// State set when resize controller starts resizing the volume in control-plane
|
||||||
PersistentVolumeClaimControllerResizeInProgress ClaimResourceStatus = "ControllerResizeInProgress"
|
PersistentVolumeClaimControllerResizeInProgress ClaimResourceStatus = "ControllerResizeInProgress"
|
||||||
|
|
||||||
// State set when resize has failed in resize controller with a terminal error.
|
// State set when resize has failed in resize controller with a terminal unrecoverable error.
|
||||||
// Transient errors such as timeout should not set this status and should leave allocatedResourceStatus
|
// Transient errors such as timeout should not set this status and should leave allocatedResourceStatus
|
||||||
// unmodified, so as resize controller can resume the volume expansion.
|
// unmodified, so as resize controller can resume the volume expansion.
|
||||||
PersistentVolumeClaimControllerResizeFailed ClaimResourceStatus = "ControllerResizeFailed"
|
PersistentVolumeClaimControllerResizeInfeasible ClaimResourceStatus = "ControllerResizeInfeasible"
|
||||||
|
|
||||||
// State set when resize controller has finished resizing the volume but further resizing of volume
|
// State set when resize controller has finished resizing the volume but further resizing of volume
|
||||||
// is needed on the node.
|
// is needed on the node.
|
||||||
PersistentVolumeClaimNodeResizePending ClaimResourceStatus = "NodeResizePending"
|
PersistentVolumeClaimNodeResizePending ClaimResourceStatus = "NodeResizePending"
|
||||||
// State set when kubelet starts resizing the volume.
|
// State set when kubelet starts resizing the volume.
|
||||||
PersistentVolumeClaimNodeResizeInProgress ClaimResourceStatus = "NodeResizeInProgress"
|
PersistentVolumeClaimNodeResizeInProgress ClaimResourceStatus = "NodeResizeInProgress"
|
||||||
// State set when resizing has failed in kubelet with a terminal error. Transient errors don't set NodeResizeFailed
|
// State set when resizing has failed in kubelet with a terminal unrecoverable error. Transient errors
|
||||||
PersistentVolumeClaimNodeResizeFailed ClaimResourceStatus = "NodeResizeFailed"
|
// shouldn't set this status
|
||||||
|
PersistentVolumeClaimNodeResizeInfeasible ClaimResourceStatus = "NodeResizeInfeasible"
|
||||||
)
|
)
|
||||||
|
|
||||||
// +enum
|
// +enum
|
||||||
|
@ -2491,10 +2491,10 @@ func validatePersistentVolumeClaimResourceKey(value string, fldPath *field.Path)
|
|||||||
}
|
}
|
||||||
|
|
||||||
var resizeStatusSet = sets.New(core.PersistentVolumeClaimControllerResizeInProgress,
|
var resizeStatusSet = sets.New(core.PersistentVolumeClaimControllerResizeInProgress,
|
||||||
core.PersistentVolumeClaimControllerResizeFailed,
|
core.PersistentVolumeClaimControllerResizeInfeasible,
|
||||||
core.PersistentVolumeClaimNodeResizePending,
|
core.PersistentVolumeClaimNodeResizePending,
|
||||||
core.PersistentVolumeClaimNodeResizeInProgress,
|
core.PersistentVolumeClaimNodeResizeInProgress,
|
||||||
core.PersistentVolumeClaimNodeResizeFailed)
|
core.PersistentVolumeClaimNodeResizeInfeasible)
|
||||||
|
|
||||||
// ValidatePersistentVolumeClaimStatusUpdate validates an update to status of a PersistentVolumeClaim
|
// ValidatePersistentVolumeClaimStatusUpdate validates an update to status of a PersistentVolumeClaim
|
||||||
func ValidatePersistentVolumeClaimStatusUpdate(newPvc, oldPvc *core.PersistentVolumeClaim, validationOpts PersistentVolumeClaimSpecValidationOptions) field.ErrorList {
|
func ValidatePersistentVolumeClaimStatusUpdate(newPvc, oldPvc *core.PersistentVolumeClaim, validationOpts PersistentVolumeClaimSpecValidationOptions) field.ErrorList {
|
||||||
|
@ -19081,7 +19081,7 @@ func TestValidatePersistentVolumeClaimStatusUpdate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}, core.PersistentVolumeClaimStatus{
|
}, core.PersistentVolumeClaimStatus{
|
||||||
AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
|
AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
|
||||||
core.ResourceStorage: core.PersistentVolumeClaimControllerResizeFailed,
|
core.ResourceStorage: core.PersistentVolumeClaimControllerResizeInfeasible,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -19111,7 +19111,7 @@ func TestValidatePersistentVolumeClaimStatusUpdate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}, core.PersistentVolumeClaimStatus{
|
}, core.PersistentVolumeClaimStatus{
|
||||||
AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
|
AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
|
||||||
core.ResourceStorage: core.PersistentVolumeClaimNodeResizeFailed,
|
core.ResourceStorage: core.PersistentVolumeClaimNodeResizeInfeasible,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -19155,7 +19155,7 @@ func TestValidatePersistentVolumeClaimStatusUpdate(t *testing.T) {
|
|||||||
validResizeKeyCustom: resource.MustParse("10Gi"),
|
validResizeKeyCustom: resource.MustParse("10Gi"),
|
||||||
},
|
},
|
||||||
AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
|
AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
|
||||||
core.ResourceStorage: core.PersistentVolumeClaimControllerResizeFailed,
|
core.ResourceStorage: core.PersistentVolumeClaimControllerResizeInfeasible,
|
||||||
validResizeKeyCustom: core.PersistentVolumeClaimControllerResizeInProgress,
|
validResizeKeyCustom: core.PersistentVolumeClaimControllerResizeInProgress,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -68,6 +68,8 @@ type CSINameTranslator interface {
|
|||||||
GetCSINameFromInTreeName(pluginName string) (string, error)
|
GetCSINameFromInTreeName(pluginName string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: This controller is deprecated and for now exists for the sole purpose of adding
|
||||||
|
// necessary annotations if necessary, so as volume can be expanded externally in the control-plane
|
||||||
type expandController struct {
|
type expandController struct {
|
||||||
// kubeClient is the kube API client used by volumehost to communicate with
|
// kubeClient is the kube API client used by volumehost to communicate with
|
||||||
// the API server.
|
// the API server.
|
||||||
|
2
pkg/generated/openapi/zz_generated.openapi.go
generated
2
pkg/generated/openapi/zz_generated.openapi.go
generated
@ -25750,7 +25750,7 @@ func schema_k8sio_api_core_v1_PersistentVolumeClaimStatus(ref common.ReferenceCa
|
|||||||
Default: "",
|
Default: "",
|
||||||
Type: []string{"string"},
|
Type: []string{"string"},
|
||||||
Format: "",
|
Format: "",
|
||||||
Enum: []interface{}{"ControllerResizeFailed", "ControllerResizeInProgress", "NodeResizeFailed", "NodeResizeInProgress", "NodeResizePending"},
|
Enum: []interface{}{"ControllerResizeInProgress", "ControllerResizeInfeasible", "NodeResizeInProgress", "NodeResizeInfeasible", "NodeResizePending"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
"k8s.io/kubernetes/pkg/features"
|
"k8s.io/kubernetes/pkg/features"
|
||||||
@ -215,6 +216,7 @@ func NewActualStateOfWorld(
|
|||||||
attachedVolumes: make(map[v1.UniqueVolumeName]attachedVolume),
|
attachedVolumes: make(map[v1.UniqueVolumeName]attachedVolume),
|
||||||
foundDuringReconstruction: make(map[v1.UniqueVolumeName]map[volumetypes.UniquePodName]types.UID),
|
foundDuringReconstruction: make(map[v1.UniqueVolumeName]map[volumetypes.UniquePodName]types.UID),
|
||||||
volumePluginMgr: volumePluginMgr,
|
volumePluginMgr: volumePluginMgr,
|
||||||
|
volumesWithFinalExpansionErrors: sets.New[v1.UniqueVolumeName](),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,6 +249,8 @@ type actualStateOfWorld struct {
|
|||||||
// from kubelet root directory when kubelet was restarted.
|
// from kubelet root directory when kubelet was restarted.
|
||||||
foundDuringReconstruction map[v1.UniqueVolumeName]map[volumetypes.UniquePodName]types.UID
|
foundDuringReconstruction map[v1.UniqueVolumeName]map[volumetypes.UniquePodName]types.UID
|
||||||
|
|
||||||
|
volumesWithFinalExpansionErrors sets.Set[v1.UniqueVolumeName]
|
||||||
|
|
||||||
// volumePluginMgr is the volume plugin manager used to create volume
|
// volumePluginMgr is the volume plugin manager used to create volume
|
||||||
// plugin objects.
|
// plugin objects.
|
||||||
volumePluginMgr *volume.VolumePluginMgr
|
volumePluginMgr *volume.VolumePluginMgr
|
||||||
@ -396,6 +400,27 @@ func (asw *actualStateOfWorld) MarkVolumeAsDetached(
|
|||||||
asw.DeleteVolume(volumeName)
|
asw.DeleteVolume(volumeName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (asw *actualStateOfWorld) MarkVolumeExpansionFailedWithFinalError(volumeName v1.UniqueVolumeName) {
|
||||||
|
asw.Lock()
|
||||||
|
defer asw.Unlock()
|
||||||
|
|
||||||
|
asw.volumesWithFinalExpansionErrors.Insert(volumeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asw *actualStateOfWorld) RemoveVolumeFromFailedWithFinalErrors(volumeName v1.UniqueVolumeName) {
|
||||||
|
asw.Lock()
|
||||||
|
defer asw.Unlock()
|
||||||
|
|
||||||
|
asw.volumesWithFinalExpansionErrors.Delete(volumeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asw *actualStateOfWorld) CheckVolumeInFailedExpansionWithFinalErrors(volumeName v1.UniqueVolumeName) bool {
|
||||||
|
asw.RLock()
|
||||||
|
defer asw.RUnlock()
|
||||||
|
|
||||||
|
return asw.volumesWithFinalExpansionErrors.Has(volumeName)
|
||||||
|
}
|
||||||
|
|
||||||
func (asw *actualStateOfWorld) IsVolumeReconstructed(volumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName) bool {
|
func (asw *actualStateOfWorld) IsVolumeReconstructed(volumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName) bool {
|
||||||
volumeState := asw.GetVolumeMountState(volumeName, podName)
|
volumeState := asw.GetVolumeMountState(volumeName, podName)
|
||||||
|
|
||||||
|
@ -119,6 +119,11 @@ func (c *csiPlugin) nodeExpandWithClient(
|
|||||||
failedConditionErr := fmt.Errorf("Expander.NodeExpand failed to expand the volume : %w", volumetypes.NewFailedPreconditionError(err.Error()))
|
failedConditionErr := fmt.Errorf("Expander.NodeExpand failed to expand the volume : %w", volumetypes.NewFailedPreconditionError(err.Error()))
|
||||||
return false, failedConditionErr
|
return false, failedConditionErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isInfeasibleError(err) {
|
||||||
|
infeasibleError := volumetypes.NewInfeasibleError(fmt.Sprintf("Expander.NodeExpand failed to expand the volume %s", err.Error()))
|
||||||
|
return false, infeasibleError
|
||||||
|
}
|
||||||
return false, fmt.Errorf("Expander.NodeExpand failed to expand the volume : %w", err)
|
return false, fmt.Errorf("Expander.NodeExpand failed to expand the volume : %w", err)
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
@ -135,3 +140,25 @@ func inUseError(err error) bool {
|
|||||||
// More info - https://github.com/container-storage-interface/spec/blob/master/spec.md#controllerexpandvolume-errors
|
// More info - https://github.com/container-storage-interface/spec/blob/master/spec.md#controllerexpandvolume-errors
|
||||||
return st.Code() == codes.FailedPrecondition
|
return st.Code() == codes.FailedPrecondition
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsInfeasibleError returns true for grpc errors that are considered terminal in a way
|
||||||
|
// that they indicate CSI operation as infeasible.
|
||||||
|
// This function returns a subset of final errors. All infeasible errors are also final errors.
|
||||||
|
func isInfeasibleError(err error) bool {
|
||||||
|
st, ok := status.FromError(err)
|
||||||
|
if !ok {
|
||||||
|
// This is not gRPC error. The operation must have failed before gRPC
|
||||||
|
// method was called, otherwise we would get gRPC error.
|
||||||
|
// We don't know if any previous volume operation is in progress, be on the safe side.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch st.Code() {
|
||||||
|
case codes.InvalidArgument,
|
||||||
|
codes.OutOfRange,
|
||||||
|
codes.NotFound:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// All other errors mean that operation either did not
|
||||||
|
// even start or failed. It is for sure are not infeasible errors
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -88,7 +88,8 @@ const (
|
|||||||
|
|
||||||
FailVolumeExpansion = "fail-expansion-test"
|
FailVolumeExpansion = "fail-expansion-test"
|
||||||
|
|
||||||
AlwaysFailNodeExpansion = "always-fail-node-expansion"
|
InfeasibleNodeExpansion = "infeasible-fail-node-expansion"
|
||||||
|
OtherFinalNodeExpansionError = "other-final-node-expansion-error"
|
||||||
|
|
||||||
deviceNotMounted = "deviceNotMounted"
|
deviceNotMounted = "deviceNotMounted"
|
||||||
deviceMountUncertain = "deviceMountUncertain"
|
deviceMountUncertain = "deviceMountUncertain"
|
||||||
@ -179,6 +180,7 @@ type FakeVolumePlugin struct {
|
|||||||
Host volume.VolumeHost
|
Host volume.VolumeHost
|
||||||
Config volume.VolumeConfig
|
Config volume.VolumeConfig
|
||||||
LastProvisionerOptions volume.VolumeOptions
|
LastProvisionerOptions volume.VolumeOptions
|
||||||
|
LastResizeOptions volume.NodeResizeOptions
|
||||||
NewAttacherCallCount int
|
NewAttacherCallCount int
|
||||||
NewDetacherCallCount int
|
NewDetacherCallCount int
|
||||||
NodeExpandCallCount int
|
NodeExpandCallCount int
|
||||||
@ -493,6 +495,7 @@ func (plugin *FakeVolumePlugin) RequiresFSResize() bool {
|
|||||||
|
|
||||||
func (plugin *FakeVolumePlugin) NodeExpand(resizeOptions volume.NodeResizeOptions) (bool, error) {
|
func (plugin *FakeVolumePlugin) NodeExpand(resizeOptions volume.NodeResizeOptions) (bool, error) {
|
||||||
plugin.NodeExpandCallCount++
|
plugin.NodeExpandCallCount++
|
||||||
|
plugin.LastResizeOptions = resizeOptions
|
||||||
if resizeOptions.VolumeSpec.Name() == FailWithInUseVolumeName {
|
if resizeOptions.VolumeSpec.Name() == FailWithInUseVolumeName {
|
||||||
return false, volumetypes.NewFailedPreconditionError("volume-in-use")
|
return false, volumetypes.NewFailedPreconditionError("volume-in-use")
|
||||||
}
|
}
|
||||||
@ -500,8 +503,12 @@ func (plugin *FakeVolumePlugin) NodeExpand(resizeOptions volume.NodeResizeOption
|
|||||||
return false, volumetypes.NewOperationNotSupportedError("volume-unsupported")
|
return false, volumetypes.NewOperationNotSupportedError("volume-unsupported")
|
||||||
}
|
}
|
||||||
|
|
||||||
if resizeOptions.VolumeSpec.Name() == AlwaysFailNodeExpansion {
|
if resizeOptions.VolumeSpec.Name() == InfeasibleNodeExpansion {
|
||||||
return false, fmt.Errorf("test failure: NodeExpand")
|
return false, volumetypes.NewInfeasibleError("infeasible-expansion")
|
||||||
|
}
|
||||||
|
|
||||||
|
if resizeOptions.VolumeSpec.Name() == OtherFinalNodeExpansionError {
|
||||||
|
return false, fmt.Errorf("other-final-node-expansion-error")
|
||||||
}
|
}
|
||||||
|
|
||||||
if resizeOptions.VolumeSpec.Name() == FailVolumeExpansion {
|
if resizeOptions.VolumeSpec.Name() == FailVolumeExpansion {
|
||||||
|
@ -39,6 +39,10 @@ type NodeExpander struct {
|
|||||||
pvCap resource.Quantity
|
pvCap resource.Quantity
|
||||||
resizeStatus v1.ClaimResourceStatus
|
resizeStatus v1.ClaimResourceStatus
|
||||||
|
|
||||||
|
// indicates that if volume expansion failed on the node, then current expansion should be marked
|
||||||
|
// as infeasible so as controller can reconcile the resizing operation by using new user requested size.
|
||||||
|
markExpansionInfeasibleOnFailure bool
|
||||||
|
|
||||||
// pvcAlreadyUpdated if true indicates that although we are calling NodeExpandVolume on the kubelet
|
// pvcAlreadyUpdated if true indicates that although we are calling NodeExpandVolume on the kubelet
|
||||||
// PVC has already been updated - possibly because expansion already succeeded on different node.
|
// PVC has already been updated - possibly because expansion already succeeded on different node.
|
||||||
// This can happen when a RWX PVC is expanded.
|
// This can happen when a RWX PVC is expanded.
|
||||||
@ -79,6 +83,17 @@ func (ne *NodeExpander) runPreCheck() bool {
|
|||||||
ne.resizeStatus = currentStatus
|
ne.resizeStatus = currentStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pvcSpecCap := ne.pvc.Spec.Resources.Requests[v1.ResourceStorage]
|
||||||
|
|
||||||
|
// usually when are performing node expansion, we expect pv size and pvc spec size
|
||||||
|
// to be the same, but if user has edited pvc since then and volume expansion failed
|
||||||
|
// with final error, then we should let controller reconcile this state, by marking entire
|
||||||
|
// node expansion as infeasible.
|
||||||
|
if pvcSpecCap.Cmp(ne.pluginResizeOpts.NewSize) != 0 &&
|
||||||
|
ne.actualStateOfWorld.CheckVolumeInFailedExpansionWithFinalErrors(ne.vmt.VolumeName) {
|
||||||
|
ne.markExpansionInfeasibleOnFailure = true
|
||||||
|
}
|
||||||
|
|
||||||
// PVC is already expanded but we are still trying to expand the volume because
|
// PVC is already expanded but we are still trying to expand the volume because
|
||||||
// last recorded size in ASOW is older. This can happen for RWX volume types.
|
// last recorded size in ASOW is older. This can happen for RWX volume types.
|
||||||
if ne.pvcStatusCap.Cmp(ne.pluginResizeOpts.NewSize) >= 0 && ne.resizeStatus == "" {
|
if ne.pvcStatusCap.Cmp(ne.pluginResizeOpts.NewSize) >= 0 && ne.resizeStatus == "" {
|
||||||
@ -124,10 +139,18 @@ func (ne *NodeExpander) expandOnPlugin() (bool, error, testResponseData) {
|
|||||||
if resizeErr != nil {
|
if resizeErr != nil {
|
||||||
if volumetypes.IsOperationFinishedError(resizeErr) {
|
if volumetypes.IsOperationFinishedError(resizeErr) {
|
||||||
var markFailedError error
|
var markFailedError error
|
||||||
ne.pvc, markFailedError = util.MarkNodeExpansionFailed(ne.pvc, ne.kubeClient)
|
ne.actualStateOfWorld.MarkVolumeExpansionFailedWithFinalError(ne.vmt.VolumeName)
|
||||||
|
if volumetypes.IsInfeasibleError(resizeErr) || ne.markExpansionInfeasibleOnFailure {
|
||||||
|
ne.pvc, markFailedError = util.MarkNodeExpansionInfeasible(ne.pvc, ne.kubeClient, resizeErr)
|
||||||
if markFailedError != nil {
|
if markFailedError != nil {
|
||||||
klog.Errorf(ne.vmt.GenerateErrorDetailed("MountMount.NodeExpandVolume failed to mark node expansion as failed: %v", err).Error())
|
klog.Errorf(ne.vmt.GenerateErrorDetailed("MountMount.NodeExpandVolume failed to mark node expansion as failed: %v", err).Error())
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
ne.pvc, markFailedError = util.MarkNodeExpansionFailedCondition(ne.pvc, ne.kubeClient, resizeErr)
|
||||||
|
if markFailedError != nil {
|
||||||
|
klog.Errorf(ne.vmt.GenerateErrorDetailed("MountMount.NodeExpandVolume failed to mark node expansion as failed: %v", err).Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if driver returned FailedPrecondition error that means
|
// if driver returned FailedPrecondition error that means
|
||||||
|
@ -17,19 +17,35 @@ limitations under the License.
|
|||||||
package operationexecutor
|
package operationexecutor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
"k8s.io/kubernetes/pkg/features"
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/volume"
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
volumetesting "k8s.io/kubernetes/pkg/volume/testing"
|
volumetesting "k8s.io/kubernetes/pkg/volume/testing"
|
||||||
|
volumetypes "k8s.io/kubernetes/pkg/volume/util/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type fakeActualStateOfWorld struct {
|
||||||
|
volumesWithFinalExpansionErrors sets.Set[v1.UniqueVolumeName]
|
||||||
|
sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ActualStateOfWorldMounterUpdater = &fakeActualStateOfWorld{}
|
||||||
|
|
||||||
|
func newFakeActualStateOfWorld() *fakeActualStateOfWorld {
|
||||||
|
return &fakeActualStateOfWorld{
|
||||||
|
volumesWithFinalExpansionErrors: sets.New[v1.UniqueVolumeName](),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNodeExpander(t *testing.T) {
|
func TestNodeExpander(t *testing.T) {
|
||||||
nodeResizeFailed := v1.PersistentVolumeClaimNodeResizeFailed
|
nodeResizeFailed := v1.PersistentVolumeClaimNodeResizeInfeasible
|
||||||
|
|
||||||
nodeResizePending := v1.PersistentVolumeClaimNodeResizePending
|
nodeResizePending := v1.PersistentVolumeClaimNodeResizePending
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
@ -46,6 +62,7 @@ func TestNodeExpander(t *testing.T) {
|
|||||||
expectedResizeStatus v1.ClaimResourceStatus
|
expectedResizeStatus v1.ClaimResourceStatus
|
||||||
expectedStatusSize resource.Quantity
|
expectedStatusSize resource.Quantity
|
||||||
expectResizeCall bool
|
expectResizeCall bool
|
||||||
|
expectFinalErrors bool
|
||||||
assumeResizeOpAsFinished bool
|
assumeResizeOpAsFinished bool
|
||||||
expectError bool
|
expectError bool
|
||||||
}{
|
}{
|
||||||
@ -57,6 +74,7 @@ func TestNodeExpander(t *testing.T) {
|
|||||||
expectedResizeStatus: nodeResizeFailed,
|
expectedResizeStatus: nodeResizeFailed,
|
||||||
expectResizeCall: false,
|
expectResizeCall: false,
|
||||||
assumeResizeOpAsFinished: true,
|
assumeResizeOpAsFinished: true,
|
||||||
|
expectFinalErrors: false,
|
||||||
expectedStatusSize: resource.MustParse("1G"),
|
expectedStatusSize: resource.MustParse("1G"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -66,16 +84,29 @@ func TestNodeExpander(t *testing.T) {
|
|||||||
expectedResizeStatus: "",
|
expectedResizeStatus: "",
|
||||||
expectResizeCall: true,
|
expectResizeCall: true,
|
||||||
assumeResizeOpAsFinished: true,
|
assumeResizeOpAsFinished: true,
|
||||||
|
expectFinalErrors: false,
|
||||||
expectedStatusSize: resource.MustParse("2G"),
|
expectedStatusSize: resource.MustParse("2G"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "pv.spec.cap > pvc.status.cap, resizeStatus=node_expansion_pending, reize_op=failing",
|
name: "pv.spec.cap > pvc.status.cap, resizeStatus=node_expansion_pending, reize_op=infeasible",
|
||||||
pvc: getTestPVC(volumetesting.AlwaysFailNodeExpansion, "2G", "1G", "2G", &nodeResizePending),
|
pvc: getTestPVC(volumetesting.InfeasibleNodeExpansion, "2G", "1G", "2G", &nodeResizePending),
|
||||||
pv: getTestPV(volumetesting.AlwaysFailNodeExpansion, "2G"),
|
pv: getTestPV(volumetesting.InfeasibleNodeExpansion, "2G"),
|
||||||
expectError: true,
|
expectError: true,
|
||||||
expectedResizeStatus: nodeResizeFailed,
|
expectedResizeStatus: nodeResizeFailed,
|
||||||
expectResizeCall: true,
|
expectResizeCall: true,
|
||||||
assumeResizeOpAsFinished: true,
|
assumeResizeOpAsFinished: true,
|
||||||
|
expectFinalErrors: true,
|
||||||
|
expectedStatusSize: resource.MustParse("1G"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pv.spec.cap > pvc.status.cap, resizeStatus=node_expansion_pending, reize_op=failing",
|
||||||
|
pvc: getTestPVC(volumetesting.OtherFinalNodeExpansionError, "2G", "1G", "2G", &nodeResizePending),
|
||||||
|
pv: getTestPV(volumetesting.OtherFinalNodeExpansionError, "2G"),
|
||||||
|
expectError: true,
|
||||||
|
expectedResizeStatus: v1.PersistentVolumeClaimNodeResizeInProgress,
|
||||||
|
expectResizeCall: true,
|
||||||
|
assumeResizeOpAsFinished: true,
|
||||||
|
expectFinalErrors: true,
|
||||||
expectedStatusSize: resource.MustParse("1G"),
|
expectedStatusSize: resource.MustParse("1G"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -86,6 +117,7 @@ func TestNodeExpander(t *testing.T) {
|
|||||||
expectedResizeStatus: "",
|
expectedResizeStatus: "",
|
||||||
expectResizeCall: true,
|
expectResizeCall: true,
|
||||||
assumeResizeOpAsFinished: true,
|
assumeResizeOpAsFinished: true,
|
||||||
|
expectFinalErrors: false,
|
||||||
expectedStatusSize: resource.MustParse("2G"),
|
expectedStatusSize: resource.MustParse("2G"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -114,12 +146,13 @@ func TestNodeExpander(t *testing.T) {
|
|||||||
if actualSize == nil {
|
if actualSize == nil {
|
||||||
actualSize = pvc.Status.Capacity.Storage()
|
actualSize = pvc.Status.Capacity.Storage()
|
||||||
}
|
}
|
||||||
|
asow := newFakeActualStateOfWorld()
|
||||||
resizeOp := nodeResizeOperationOpts{
|
resizeOp := nodeResizeOperationOpts{
|
||||||
pvc: pvc,
|
pvc: pvc,
|
||||||
pv: pv,
|
pv: pv,
|
||||||
volumePlugin: fakePlugin,
|
volumePlugin: fakePlugin,
|
||||||
vmt: vmt,
|
vmt: vmt,
|
||||||
actualStateOfWorld: nil,
|
actualStateOfWorld: asow,
|
||||||
pluginResizeOpts: volume.NodeResizeOptions{
|
pluginResizeOpts: volume.NodeResizeOptions{
|
||||||
VolumeSpec: vmt.VolumeSpec,
|
VolumeSpec: vmt.VolumeSpec,
|
||||||
NewSize: *desiredSize,
|
NewSize: *desiredSize,
|
||||||
@ -153,9 +186,110 @@ func TestNodeExpander(t *testing.T) {
|
|||||||
if test.expectedResizeStatus != resizeStatus {
|
if test.expectedResizeStatus != resizeStatus {
|
||||||
t.Errorf("For test %s, expected resizeStatus %v, got %v", test.name, test.expectedResizeStatus, resizeStatus)
|
t.Errorf("For test %s, expected resizeStatus %v, got %v", test.name, test.expectedResizeStatus, resizeStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if test.expectFinalErrors != asow.CheckVolumeInFailedExpansionWithFinalErrors(vmt.VolumeName) {
|
||||||
|
t.Errorf("For test %s, expected final errors %t, got %t", test.name, test.expectFinalErrors, !test.expectFinalErrors)
|
||||||
|
}
|
||||||
if pvcStatusCap.Cmp(test.expectedStatusSize) != 0 {
|
if pvcStatusCap.Cmp(test.expectedStatusSize) != 0 {
|
||||||
t.Errorf("For test %s, expected status size %s, got %s", test.name, test.expectedStatusSize.String(), pvcStatusCap.String())
|
t.Errorf("For test %s, expected status size %s, got %s", test.name, test.expectedStatusSize.String(), pvcStatusCap.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckAndMarkDeviceUncertainViaReconstruction implements ActualStateOfWorldMounterUpdater.
|
||||||
|
func (f *fakeActualStateOfWorld) CheckAndMarkDeviceUncertainViaReconstruction(volumeName v1.UniqueVolumeName, deviceMountPath string) bool {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckAndMarkVolumeAsUncertainViaReconstruction implements ActualStateOfWorldMounterUpdater.
|
||||||
|
func (f *fakeActualStateOfWorld) CheckAndMarkVolumeAsUncertainViaReconstruction(opts MarkVolumeOpts) (bool, error) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckVolumeInFailedExpansionWithFinalErrors implements ActualStateOfWorldMounterUpdater.
|
||||||
|
func (f *fakeActualStateOfWorld) CheckVolumeInFailedExpansionWithFinalErrors(volumeName v1.UniqueVolumeName) bool {
|
||||||
|
f.RLock()
|
||||||
|
defer f.RUnlock()
|
||||||
|
return f.volumesWithFinalExpansionErrors.Has(volumeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDeviceMountState implements ActualStateOfWorldMounterUpdater.
|
||||||
|
func (f *fakeActualStateOfWorld) GetDeviceMountState(volumeName v1.UniqueVolumeName) DeviceMountState {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVolumeMountState implements ActualStateOfWorldMounterUpdater.
|
||||||
|
func (f *fakeActualStateOfWorld) GetVolumeMountState(volumName v1.UniqueVolumeName, podName volumetypes.UniquePodName) VolumeMountState {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVolumeDeviceReconstructed implements ActualStateOfWorldMounterUpdater.
|
||||||
|
func (f *fakeActualStateOfWorld) IsVolumeDeviceReconstructed(volumeName v1.UniqueVolumeName) bool {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVolumeMountedElsewhere implements ActualStateOfWorldMounterUpdater.
|
||||||
|
func (f *fakeActualStateOfWorld) IsVolumeMountedElsewhere(volumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName) bool {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVolumeReconstructed implements ActualStateOfWorldMounterUpdater.
|
||||||
|
func (f *fakeActualStateOfWorld) IsVolumeReconstructed(volumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName) bool {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkDeviceAsMounted implements ActualStateOfWorldMounterUpdater.
|
||||||
|
func (f *fakeActualStateOfWorld) MarkDeviceAsMounted(volumeName v1.UniqueVolumeName, devicePath string, deviceMountPath string, seLinuxMountContext string) error {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkDeviceAsUncertain implements ActualStateOfWorldMounterUpdater.
|
||||||
|
func (f *fakeActualStateOfWorld) MarkDeviceAsUncertain(volumeName v1.UniqueVolumeName, devicePath string, deviceMountPath string, seLinuxMountContext string) error {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkDeviceAsUnmounted implements ActualStateOfWorldMounterUpdater.
|
||||||
|
func (f *fakeActualStateOfWorld) MarkDeviceAsUnmounted(volumeName v1.UniqueVolumeName) error {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkForInUseExpansionError implements ActualStateOfWorldMounterUpdater.
|
||||||
|
func (f *fakeActualStateOfWorld) MarkForInUseExpansionError(volumeName v1.UniqueVolumeName) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkVolumeAsMounted implements ActualStateOfWorldMounterUpdater.
|
||||||
|
func (f *fakeActualStateOfWorld) MarkVolumeAsMounted(markVolumeOpts MarkVolumeOpts) error {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkVolumeAsResized implements ActualStateOfWorldMounterUpdater.
|
||||||
|
func (f *fakeActualStateOfWorld) MarkVolumeAsResized(volumeName v1.UniqueVolumeName, claimSize *resource.Quantity) bool {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkVolumeAsUnmounted implements ActualStateOfWorldMounterUpdater.
|
||||||
|
func (f *fakeActualStateOfWorld) MarkVolumeAsUnmounted(podName volumetypes.UniquePodName, volumeName v1.UniqueVolumeName) error {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkVolumeExpansionFailedWithFinalError implements ActualStateOfWorldMounterUpdater.
|
||||||
|
func (f *fakeActualStateOfWorld) MarkVolumeExpansionFailedWithFinalError(volumeName v1.UniqueVolumeName) {
|
||||||
|
f.Lock()
|
||||||
|
defer f.Unlock()
|
||||||
|
|
||||||
|
f.volumesWithFinalExpansionErrors.Insert(volumeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkVolumeMountAsUncertain implements ActualStateOfWorldMounterUpdater.
|
||||||
|
func (f *fakeActualStateOfWorld) MarkVolumeMountAsUncertain(markVolumeOpts MarkVolumeOpts) error {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveVolumeFromFailedWithFinalErrors implements ActualStateOfWorldMounterUpdater.
|
||||||
|
func (f *fakeActualStateOfWorld) RemoveVolumeFromFailedWithFinalErrors(volumeName v1.UniqueVolumeName) {
|
||||||
|
f.Lock()
|
||||||
|
defer f.Unlock()
|
||||||
|
f.volumesWithFinalExpansionErrors.Delete(volumeName)
|
||||||
|
}
|
||||||
|
@ -233,6 +233,18 @@ type ActualStateOfWorldMounterUpdater interface {
|
|||||||
// IsVolumeDeviceReconstructed returns true if volume device identified by volumeName has been
|
// IsVolumeDeviceReconstructed returns true if volume device identified by volumeName has been
|
||||||
// found during reconstruction.
|
// found during reconstruction.
|
||||||
IsVolumeDeviceReconstructed(volumeName v1.UniqueVolumeName) bool
|
IsVolumeDeviceReconstructed(volumeName v1.UniqueVolumeName) bool
|
||||||
|
|
||||||
|
// MarkVolumeExpansionFailedWithFinalError marks volume as failed with a final error, so as
|
||||||
|
// this state doesn't have to be recorded in the API server
|
||||||
|
MarkVolumeExpansionFailedWithFinalError(volumeName v1.UniqueVolumeName)
|
||||||
|
|
||||||
|
// RemoveVolumeFromFailedWithFinalErrors removes volume from list that indicates that volume
|
||||||
|
// has failed expansion with a final error
|
||||||
|
RemoveVolumeFromFailedWithFinalErrors(volumeName v1.UniqueVolumeName)
|
||||||
|
|
||||||
|
// CheckVolumeInFailedExpansionWithFinalErrors verifies if volume expansion has failed with a final
|
||||||
|
// error
|
||||||
|
CheckVolumeInFailedExpansionWithFinalErrors(volumeName v1.UniqueVolumeName) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActualStateOfWorldAttacherUpdater defines a set of operations updating the
|
// ActualStateOfWorldAttacherUpdater defines a set of operations updating the
|
||||||
|
@ -1679,6 +1679,8 @@ func (og *operationGenerator) GenerateExpandAndRecoverVolumeFunc(
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: This function should not called by any controller code in future and should be removed
|
||||||
|
// from kubernetes code
|
||||||
func (og *operationGenerator) expandAndRecoverFunction(resizeOpts inTreeResizeOpts) inTreeResizeResponse {
|
func (og *operationGenerator) expandAndRecoverFunction(resizeOpts inTreeResizeOpts) inTreeResizeResponse {
|
||||||
pvc := resizeOpts.pvc
|
pvc := resizeOpts.pvc
|
||||||
pv := resizeOpts.pv
|
pv := resizeOpts.pv
|
||||||
@ -1718,7 +1720,7 @@ func (og *operationGenerator) expandAndRecoverFunction(resizeOpts inTreeResizeOp
|
|||||||
case v1.PersistentVolumeClaimControllerResizeInProgress,
|
case v1.PersistentVolumeClaimControllerResizeInProgress,
|
||||||
v1.PersistentVolumeClaimNodeResizePending,
|
v1.PersistentVolumeClaimNodeResizePending,
|
||||||
v1.PersistentVolumeClaimNodeResizeInProgress,
|
v1.PersistentVolumeClaimNodeResizeInProgress,
|
||||||
v1.PersistentVolumeClaimNodeResizeFailed:
|
v1.PersistentVolumeClaimNodeResizeInfeasible:
|
||||||
if allocatedSize != nil {
|
if allocatedSize != nil {
|
||||||
newSize = *allocatedSize
|
newSize = *allocatedSize
|
||||||
}
|
}
|
||||||
@ -1742,14 +1744,14 @@ func (og *operationGenerator) expandAndRecoverFunction(resizeOpts inTreeResizeOp
|
|||||||
// we don't need to do any work. We could be here because of a spurious update event.
|
// we don't need to do any work. We could be here because of a spurious update event.
|
||||||
// This is case #1
|
// This is case #1
|
||||||
return resizeResponse
|
return resizeResponse
|
||||||
case v1.PersistentVolumeClaimNodeResizeFailed:
|
case v1.PersistentVolumeClaimNodeResizeInfeasible:
|
||||||
// This is case#3
|
// This is case#3
|
||||||
pvc, err = og.markForPendingNodeExpansion(pvc, pv)
|
pvc, err = og.markForPendingNodeExpansion(pvc, pv)
|
||||||
resizeResponse.pvc = pvc
|
resizeResponse.pvc = pvc
|
||||||
resizeResponse.err = err
|
resizeResponse.err = err
|
||||||
return resizeResponse
|
return resizeResponse
|
||||||
case v1.PersistentVolumeClaimControllerResizeInProgress,
|
case v1.PersistentVolumeClaimControllerResizeInProgress,
|
||||||
v1.PersistentVolumeClaimControllerResizeFailed:
|
v1.PersistentVolumeClaimControllerResizeInfeasible:
|
||||||
// This is case#2 or it could also be case#4 when user manually shrunk the PVC
|
// This is case#2 or it could also be case#4 when user manually shrunk the PVC
|
||||||
// after expanding it.
|
// after expanding it.
|
||||||
if allocatedSize != nil {
|
if allocatedSize != nil {
|
||||||
@ -2001,6 +2003,7 @@ func (og *operationGenerator) expandVolumeDuringMount(volumeToMount VolumeToMoun
|
|||||||
|
|
||||||
rsOpts.NewSize = pvSpecCap
|
rsOpts.NewSize = pvSpecCap
|
||||||
rsOpts.OldSize = pvcStatusCap
|
rsOpts.OldSize = pvcStatusCap
|
||||||
|
rsOpts.VolumeSpec = volumeToMount.VolumeSpec
|
||||||
resizeOp := nodeResizeOperationOpts{
|
resizeOp := nodeResizeOperationOpts{
|
||||||
vmt: volumeToMount,
|
vmt: volumeToMount,
|
||||||
pvc: pvc,
|
pvc: pvc,
|
||||||
|
@ -94,7 +94,7 @@ func TestOperationGenerator_GenerateUnmapVolumeFunc_PluginName(t *testing.T) {
|
|||||||
|
|
||||||
func TestOperationGenerator_GenerateExpandAndRecoverVolumeFunc(t *testing.T) {
|
func TestOperationGenerator_GenerateExpandAndRecoverVolumeFunc(t *testing.T) {
|
||||||
nodeResizePending := v1.PersistentVolumeClaimNodeResizePending
|
nodeResizePending := v1.PersistentVolumeClaimNodeResizePending
|
||||||
nodeResizeFailed := v1.PersistentVolumeClaimNodeResizeFailed
|
nodeResizeFailed := v1.PersistentVolumeClaimNodeResizeInfeasible
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
name string
|
name string
|
||||||
pvc *v1.PersistentVolumeClaim
|
pvc *v1.PersistentVolumeClaim
|
||||||
@ -194,7 +194,7 @@ func TestOperationGenerator_nodeExpandVolume(t *testing.T) {
|
|||||||
return &x
|
return &x
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeResizeFailed := v1.PersistentVolumeClaimNodeResizeFailed
|
nodeResizeFailed := v1.PersistentVolumeClaimNodeResizeInfeasible
|
||||||
nodeResizePending := v1.PersistentVolumeClaimNodeResizePending
|
nodeResizePending := v1.PersistentVolumeClaimNodeResizePending
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
name string
|
name string
|
||||||
@ -217,7 +217,7 @@ func TestOperationGenerator_nodeExpandVolume(t *testing.T) {
|
|||||||
pvc: getTestPVC("test-vol0", "2G", "1G", "", &nodeResizeFailed),
|
pvc: getTestPVC("test-vol0", "2G", "1G", "", &nodeResizeFailed),
|
||||||
pv: getTestPV("test-vol0", "2G"),
|
pv: getTestPV("test-vol0", "2G"),
|
||||||
|
|
||||||
expectedResizeStatus: v1.PersistentVolumeClaimNodeResizeFailed,
|
expectedResizeStatus: v1.PersistentVolumeClaimNodeResizeInfeasible,
|
||||||
resizeCallCount: 0,
|
resizeCallCount: 0,
|
||||||
expectedStatusSize: resource.MustParse("1G"),
|
expectedStatusSize: resource.MustParse("1G"),
|
||||||
},
|
},
|
||||||
@ -231,10 +231,10 @@ func TestOperationGenerator_nodeExpandVolume(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "pv.spec.cap > pvc.status.cap, resizeStatus=node_expansion_pending, reize_op=failing",
|
name: "pv.spec.cap > pvc.status.cap, resizeStatus=node_expansion_pending, reize_op=failing",
|
||||||
pvc: getTestPVC(volumetesting.AlwaysFailNodeExpansion, "2G", "1G", "2G", &nodeResizePending),
|
pvc: getTestPVC(volumetesting.InfeasibleNodeExpansion, "2G", "1G", "2G", &nodeResizePending),
|
||||||
pv: getTestPV(volumetesting.AlwaysFailNodeExpansion, "2G"),
|
pv: getTestPV(volumetesting.InfeasibleNodeExpansion, "2G"),
|
||||||
expectError: true,
|
expectError: true,
|
||||||
expectedResizeStatus: v1.PersistentVolumeClaimNodeResizeFailed,
|
expectedResizeStatus: v1.PersistentVolumeClaimNodeResizeInfeasible,
|
||||||
resizeCallCount: 1,
|
resizeCallCount: 1,
|
||||||
expectedStatusSize: resource.MustParse("1G"),
|
expectedStatusSize: resource.MustParse("1G"),
|
||||||
},
|
},
|
||||||
@ -265,7 +265,7 @@ func TestOperationGenerator_nodeExpandVolume(t *testing.T) {
|
|||||||
desiredSize: getSizeFunc("2G"),
|
desiredSize: getSizeFunc("2G"),
|
||||||
actualSize: getSizeFunc("1G"),
|
actualSize: getSizeFunc("1G"),
|
||||||
|
|
||||||
expectedResizeStatus: v1.PersistentVolumeClaimNodeResizeFailed,
|
expectedResizeStatus: v1.PersistentVolumeClaimNodeResizeInfeasible,
|
||||||
resizeCallCount: 0,
|
resizeCallCount: 0,
|
||||||
expectedStatusSize: resource.MustParse("2G"),
|
expectedStatusSize: resource.MustParse("2G"),
|
||||||
},
|
},
|
||||||
@ -302,9 +302,10 @@ func TestOperationGenerator_nodeExpandVolume(t *testing.T) {
|
|||||||
NewSize: *desiredSize,
|
NewSize: *desiredSize,
|
||||||
OldSize: *actualSize,
|
OldSize: *actualSize,
|
||||||
}
|
}
|
||||||
|
asow := newFakeActualStateOfWorld()
|
||||||
|
|
||||||
ogInstance, _ := og.(*operationGenerator)
|
ogInstance, _ := og.(*operationGenerator)
|
||||||
_, err := ogInstance.nodeExpandVolume(vmt, nil, pluginResizeOpts)
|
_, err := ogInstance.nodeExpandVolume(vmt, asow, pluginResizeOpts)
|
||||||
|
|
||||||
if !test.expectError && err != nil {
|
if !test.expectError && err != nil {
|
||||||
t.Errorf("For test %s, expected no error got: %v", test.name, err)
|
t.Errorf("For test %s, expected no error got: %v", test.name, err)
|
||||||
@ -319,6 +320,89 @@ func TestOperationGenerator_nodeExpandVolume(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExpandDuringMount(t *testing.T) {
|
||||||
|
nodeResizePending := v1.PersistentVolumeClaimNodeResizePending
|
||||||
|
var tests = []struct {
|
||||||
|
name string
|
||||||
|
pvc *v1.PersistentVolumeClaim
|
||||||
|
pv *v1.PersistentVolume
|
||||||
|
|
||||||
|
// desired size, defaults to pv.Spec.Capacity
|
||||||
|
desiredSize *resource.Quantity
|
||||||
|
// actualSize, defaults to pvc.Status.Capacity
|
||||||
|
actualSize *resource.Quantity
|
||||||
|
|
||||||
|
// expectations of test
|
||||||
|
expectedResizeStatus v1.ClaimResourceStatus
|
||||||
|
expectedStatusSize resource.Quantity
|
||||||
|
resizeCallCount int
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "pv.spec.cap > pvc.status.cap, resizeStatus=node_expansion_pending",
|
||||||
|
pvc: getTestPVC("test-vol0", "2G", "1G", "2G", &nodeResizePending),
|
||||||
|
pv: getTestPV("test-vol0", "2G"),
|
||||||
|
expectedResizeStatus: "",
|
||||||
|
resizeCallCount: 1,
|
||||||
|
expectedStatusSize: resource.MustParse("2G"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i := range tests {
|
||||||
|
test := tests[i]
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RecoverVolumeExpansionFailure, true)
|
||||||
|
volumePluginMgr, fakePlugin := volumetesting.GetTestKubeletVolumePluginMgr(t)
|
||||||
|
test.pv.Spec.ClaimRef = &v1.ObjectReference{
|
||||||
|
Namespace: test.pvc.Namespace,
|
||||||
|
Name: test.pvc.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
pvc := test.pvc
|
||||||
|
pv := test.pv
|
||||||
|
pod := getTestPod("test-pod", pvc.Name)
|
||||||
|
og := getTestOperatorGeneratorWithPVPVC(volumePluginMgr, pvc, pv)
|
||||||
|
vmt := VolumeToMount{
|
||||||
|
Pod: pod,
|
||||||
|
VolumeName: v1.UniqueVolumeName(pv.Name),
|
||||||
|
VolumeSpec: volume.NewSpecFromPersistentVolume(pv, false),
|
||||||
|
}
|
||||||
|
desiredSize := test.desiredSize
|
||||||
|
if desiredSize == nil {
|
||||||
|
desiredSize = pv.Spec.Capacity.Storage()
|
||||||
|
}
|
||||||
|
actualSize := test.actualSize
|
||||||
|
if actualSize == nil {
|
||||||
|
actualSize = pvc.Status.Capacity.Storage()
|
||||||
|
}
|
||||||
|
pluginResizeOpts := volume.NodeResizeOptions{
|
||||||
|
NewSize: *desiredSize,
|
||||||
|
OldSize: *actualSize,
|
||||||
|
}
|
||||||
|
asow := newFakeActualStateOfWorld()
|
||||||
|
|
||||||
|
ogInstance, _ := og.(*operationGenerator)
|
||||||
|
_, err := ogInstance.expandVolumeDuringMount(vmt, asow, pluginResizeOpts)
|
||||||
|
|
||||||
|
if !test.expectError && err != nil {
|
||||||
|
t.Errorf("For test %s, expected no error got: %v", test.name, err)
|
||||||
|
}
|
||||||
|
if test.expectError && err == nil {
|
||||||
|
t.Errorf("For test %s, expected error but got none", test.name)
|
||||||
|
}
|
||||||
|
if test.resizeCallCount != fakePlugin.NodeExpandCallCount {
|
||||||
|
t.Errorf("for test %s, expected node-expand call count to be %d, got %d", test.name, test.resizeCallCount, fakePlugin.NodeExpandCallCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.resizeCallCount > 0 {
|
||||||
|
resizeOptions := fakePlugin.LastResizeOptions
|
||||||
|
if resizeOptions.VolumeSpec == nil {
|
||||||
|
t.Errorf("for test %s, expected volume spec to be set", test.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getTestPod(podName, pvcName string) *v1.Pod {
|
func getTestPod(podName, pvcName string) *v1.Pod {
|
||||||
return &v1.Pod{
|
return &v1.Pod{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
@ -40,6 +40,8 @@ var (
|
|||||||
knownResizeConditions = map[v1.PersistentVolumeClaimConditionType]bool{
|
knownResizeConditions = map[v1.PersistentVolumeClaimConditionType]bool{
|
||||||
v1.PersistentVolumeClaimFileSystemResizePending: true,
|
v1.PersistentVolumeClaimFileSystemResizePending: true,
|
||||||
v1.PersistentVolumeClaimResizing: true,
|
v1.PersistentVolumeClaimResizing: true,
|
||||||
|
v1.PersistentVolumeClaimControllerResizeError: true,
|
||||||
|
v1.PersistentVolumeClaimNodeResizeError: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// AnnPreResizeCapacity annotation is added to a PV when expanding volume.
|
// AnnPreResizeCapacity annotation is added to a PV when expanding volume.
|
||||||
@ -140,7 +142,7 @@ func MarkResizeInProgressWithResizer(
|
|||||||
}
|
}
|
||||||
conditions := []v1.PersistentVolumeClaimCondition{progressCondition}
|
conditions := []v1.PersistentVolumeClaimCondition{progressCondition}
|
||||||
newPVC := pvc.DeepCopy()
|
newPVC := pvc.DeepCopy()
|
||||||
newPVC = MergeResizeConditionOnPVC(newPVC, conditions)
|
newPVC = MergeResizeConditionOnPVC(newPVC, conditions, false /* keepOldResizeConditions */)
|
||||||
newPVC = setResizer(newPVC, resizerName)
|
newPVC = setResizer(newPVC, resizerName)
|
||||||
return PatchPVCStatus(pvc /*oldPVC*/, newPVC, kubeClient)
|
return PatchPVCStatus(pvc /*oldPVC*/, newPVC, kubeClient)
|
||||||
}
|
}
|
||||||
@ -154,7 +156,7 @@ func MarkControllerReisizeInProgress(pvc *v1.PersistentVolumeClaim, resizerName
|
|||||||
}
|
}
|
||||||
conditions := []v1.PersistentVolumeClaimCondition{progressCondition}
|
conditions := []v1.PersistentVolumeClaimCondition{progressCondition}
|
||||||
newPVC := pvc.DeepCopy()
|
newPVC := pvc.DeepCopy()
|
||||||
newPVC = MergeResizeConditionOnPVC(newPVC, conditions)
|
newPVC = MergeResizeConditionOnPVC(newPVC, conditions, false /* keepOldResizeConditions */)
|
||||||
newPVC = mergeStorageResourceStatus(newPVC, v1.PersistentVolumeClaimControllerResizeInProgress)
|
newPVC = mergeStorageResourceStatus(newPVC, v1.PersistentVolumeClaimControllerResizeInProgress)
|
||||||
newPVC = mergeStorageAllocatedResources(newPVC, newSize)
|
newPVC = mergeStorageAllocatedResources(newPVC, newSize)
|
||||||
newPVC = setResizer(newPVC, resizerName)
|
newPVC = setResizer(newPVC, resizerName)
|
||||||
@ -196,7 +198,7 @@ func MarkForFSResize(
|
|||||||
newPVC = mergeStorageResourceStatus(newPVC, v1.PersistentVolumeClaimNodeResizePending)
|
newPVC = mergeStorageResourceStatus(newPVC, v1.PersistentVolumeClaimNodeResizePending)
|
||||||
}
|
}
|
||||||
|
|
||||||
newPVC = MergeResizeConditionOnPVC(newPVC, conditions)
|
newPVC = MergeResizeConditionOnPVC(newPVC, conditions, true /* keepOldResizeConditions */)
|
||||||
updatedPVC, err := PatchPVCStatus(pvc /*oldPVC*/, newPVC, kubeClient)
|
updatedPVC, err := PatchPVCStatus(pvc /*oldPVC*/, newPVC, kubeClient)
|
||||||
return updatedPVC, err
|
return updatedPVC, err
|
||||||
}
|
}
|
||||||
@ -229,16 +231,25 @@ func MarkFSResizeFinished(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newPVC = MergeResizeConditionOnPVC(newPVC, []v1.PersistentVolumeClaimCondition{})
|
newPVC = MergeResizeConditionOnPVC(newPVC, []v1.PersistentVolumeClaimCondition{}, false /* keepOldResizeConditions */)
|
||||||
updatedPVC, err := PatchPVCStatus(pvc /*oldPVC*/, newPVC, kubeClient)
|
updatedPVC, err := PatchPVCStatus(pvc /*oldPVC*/, newPVC, kubeClient)
|
||||||
return updatedPVC, err
|
return updatedPVC, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkNodeExpansionFailed marks a PVC for node expansion as failed. Kubelet should not retry expansion
|
// MarkNodeExpansionInfeasible marks a PVC for node expansion as failed. Kubelet should not retry expansion
|
||||||
// of volumes which are in failed state.
|
// of volumes which are in failed state.
|
||||||
func MarkNodeExpansionFailed(pvc *v1.PersistentVolumeClaim, kubeClient clientset.Interface) (*v1.PersistentVolumeClaim, error) {
|
func MarkNodeExpansionInfeasible(pvc *v1.PersistentVolumeClaim, kubeClient clientset.Interface, err error) (*v1.PersistentVolumeClaim, error) {
|
||||||
newPVC := pvc.DeepCopy()
|
newPVC := pvc.DeepCopy()
|
||||||
newPVC = mergeStorageResourceStatus(newPVC, v1.PersistentVolumeClaimNodeResizeFailed)
|
newPVC = mergeStorageResourceStatus(newPVC, v1.PersistentVolumeClaimNodeResizeInfeasible)
|
||||||
|
errorCondition := v1.PersistentVolumeClaimCondition{
|
||||||
|
Type: v1.PersistentVolumeClaimNodeResizeError,
|
||||||
|
Status: v1.ConditionTrue,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Message: fmt.Sprintf("failed to expand pvc with %v", err),
|
||||||
|
}
|
||||||
|
newPVC = MergeResizeConditionOnPVC(newPVC,
|
||||||
|
[]v1.PersistentVolumeClaimCondition{errorCondition},
|
||||||
|
true /* keepOldResizeConditions */)
|
||||||
|
|
||||||
patchBytes, err := createPVCPatch(pvc, newPVC, false /* addResourceVersionCheck */)
|
patchBytes, err := createPVCPatch(pvc, newPVC, false /* addResourceVersionCheck */)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -253,6 +264,30 @@ func MarkNodeExpansionFailed(pvc *v1.PersistentVolumeClaim, kubeClient clientset
|
|||||||
return updatedClaim, nil
|
return updatedClaim, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MarkNodeExpansionFailedCondition(pvc *v1.PersistentVolumeClaim, kubeClient clientset.Interface, err error) (*v1.PersistentVolumeClaim, error) {
|
||||||
|
newPVC := pvc.DeepCopy()
|
||||||
|
errorCondition := v1.PersistentVolumeClaimCondition{
|
||||||
|
Type: v1.PersistentVolumeClaimNodeResizeError,
|
||||||
|
Status: v1.ConditionTrue,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Message: fmt.Sprintf("failed to expand pvc with %v", err),
|
||||||
|
}
|
||||||
|
newPVC = MergeResizeConditionOnPVC(newPVC,
|
||||||
|
[]v1.PersistentVolumeClaimCondition{errorCondition},
|
||||||
|
true /* keepOldResizeConditions */)
|
||||||
|
patchBytes, err := createPVCPatch(pvc, newPVC, false /* addResourceVersionCheck */)
|
||||||
|
if err != nil {
|
||||||
|
return pvc, fmt.Errorf("patchPVCStatus failed to patch PVC %q: %w", pvc.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedClaim, updateErr := kubeClient.CoreV1().PersistentVolumeClaims(pvc.Namespace).
|
||||||
|
Patch(context.TODO(), pvc.Name, types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{}, "status")
|
||||||
|
if updateErr != nil {
|
||||||
|
return pvc, fmt.Errorf("patchPVCStatus failed to patch PVC %q: %w", pvc.Name, updateErr)
|
||||||
|
}
|
||||||
|
return updatedClaim, nil
|
||||||
|
}
|
||||||
|
|
||||||
// MarkNodeExpansionInProgress marks pvc expansion in progress on node
|
// MarkNodeExpansionInProgress marks pvc expansion in progress on node
|
||||||
func MarkNodeExpansionInProgress(pvc *v1.PersistentVolumeClaim, kubeClient clientset.Interface) (*v1.PersistentVolumeClaim, error) {
|
func MarkNodeExpansionInProgress(pvc *v1.PersistentVolumeClaim, kubeClient clientset.Interface) (*v1.PersistentVolumeClaim, error) {
|
||||||
newPVC := pvc.DeepCopy()
|
newPVC := pvc.DeepCopy()
|
||||||
@ -333,7 +368,7 @@ func addResourceVersion(patchBytes []byte, resourceVersion string) ([]byte, erro
|
|||||||
// leaving other conditions untouched.
|
// leaving other conditions untouched.
|
||||||
func MergeResizeConditionOnPVC(
|
func MergeResizeConditionOnPVC(
|
||||||
pvc *v1.PersistentVolumeClaim,
|
pvc *v1.PersistentVolumeClaim,
|
||||||
resizeConditions []v1.PersistentVolumeClaimCondition) *v1.PersistentVolumeClaim {
|
resizeConditions []v1.PersistentVolumeClaimCondition, keepOldResizeConditions bool) *v1.PersistentVolumeClaim {
|
||||||
resizeConditionMap := map[v1.PersistentVolumeClaimConditionType]*resizeProcessStatus{}
|
resizeConditionMap := map[v1.PersistentVolumeClaimConditionType]*resizeProcessStatus{}
|
||||||
|
|
||||||
for _, condition := range resizeConditions {
|
for _, condition := range resizeConditions {
|
||||||
@ -356,6 +391,10 @@ func MergeResizeConditionOnPVC(
|
|||||||
newConditions = append(newConditions, condition)
|
newConditions = append(newConditions, condition)
|
||||||
}
|
}
|
||||||
newCondition.processed = true
|
newCondition.processed = true
|
||||||
|
} else if keepOldResizeConditions {
|
||||||
|
// if keepOldResizeConditions is true, we keep the old resize conditions that were present in the
|
||||||
|
// existing pvc.Status.Conditions field.
|
||||||
|
newConditions = append(newConditions, condition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ import (
|
|||||||
type conditionMergeTestCase struct {
|
type conditionMergeTestCase struct {
|
||||||
description string
|
description string
|
||||||
pvc *v1.PersistentVolumeClaim
|
pvc *v1.PersistentVolumeClaim
|
||||||
|
keepOldResizeConditions bool
|
||||||
newConditions []v1.PersistentVolumeClaimCondition
|
newConditions []v1.PersistentVolumeClaimCondition
|
||||||
finalConditions []v1.PersistentVolumeClaimCondition
|
finalConditions []v1.PersistentVolumeClaimCondition
|
||||||
}
|
}
|
||||||
@ -132,10 +133,34 @@ func TestMergeResizeCondition(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "when adding new condition with existing resize conditions",
|
||||||
|
pvc: pvc.DeepCopy(),
|
||||||
|
keepOldResizeConditions: true,
|
||||||
|
newConditions: []v1.PersistentVolumeClaimCondition{
|
||||||
|
{
|
||||||
|
Type: v1.PersistentVolumeClaimNodeResizeError,
|
||||||
|
Status: v1.ConditionTrue,
|
||||||
|
LastTransitionTime: currentTime,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
finalConditions: []v1.PersistentVolumeClaimCondition{
|
||||||
|
{
|
||||||
|
Type: v1.PersistentVolumeClaimResizing,
|
||||||
|
Status: v1.ConditionTrue,
|
||||||
|
LastTransitionTime: currentTime,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: v1.PersistentVolumeClaimNodeResizeError,
|
||||||
|
Status: v1.ConditionTrue,
|
||||||
|
LastTransitionTime: currentTime,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testcase := range testCases {
|
for _, testcase := range testCases {
|
||||||
updatePVC := MergeResizeConditionOnPVC(testcase.pvc, testcase.newConditions)
|
updatePVC := MergeResizeConditionOnPVC(testcase.pvc, testcase.newConditions, testcase.keepOldResizeConditions)
|
||||||
|
|
||||||
updateConditions := updatePVC.Status.Conditions
|
updateConditions := updatePVC.Status.Conditions
|
||||||
if !reflect.DeepEqual(updateConditions, testcase.finalConditions) {
|
if !reflect.DeepEqual(updateConditions, testcase.finalConditions) {
|
||||||
@ -166,8 +191,8 @@ func TestResizeFunctions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "mark fs resize, when other resource statuses are present",
|
name: "mark fs resize, when other resource statuses are present",
|
||||||
pvc: basePVC.withResourceStatus(v1.ResourceCPU, v1.PersistentVolumeClaimControllerResizeFailed).get(),
|
pvc: basePVC.withResourceStatus(v1.ResourceCPU, v1.PersistentVolumeClaimControllerResizeInfeasible).get(),
|
||||||
expectedPVC: basePVC.withResourceStatus(v1.ResourceCPU, v1.PersistentVolumeClaimControllerResizeFailed).
|
expectedPVC: basePVC.withResourceStatus(v1.ResourceCPU, v1.PersistentVolumeClaimControllerResizeInfeasible).
|
||||||
withStorageResourceStatus(v1.PersistentVolumeClaimNodeResizePending).get(),
|
withStorageResourceStatus(v1.PersistentVolumeClaimNodeResizePending).get(),
|
||||||
testFunc: func(pvc *v1.PersistentVolumeClaim, c clientset.Interface, _ resource.Quantity) (*v1.PersistentVolumeClaim, error) {
|
testFunc: func(pvc *v1.PersistentVolumeClaim, c clientset.Interface, _ resource.Quantity) (*v1.PersistentVolumeClaim, error) {
|
||||||
return MarkForFSResize(pvc, c)
|
return MarkForFSResize(pvc, c)
|
||||||
@ -183,9 +208,9 @@ func TestResizeFunctions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "mark resize finished",
|
name: "mark resize finished",
|
||||||
pvc: basePVC.withResourceStatus(v1.ResourceCPU, v1.PersistentVolumeClaimControllerResizeFailed).
|
pvc: basePVC.withResourceStatus(v1.ResourceCPU, v1.PersistentVolumeClaimControllerResizeInfeasible).
|
||||||
withStorageResourceStatus(v1.PersistentVolumeClaimNodeResizePending).get(),
|
withStorageResourceStatus(v1.PersistentVolumeClaimNodeResizePending).get(),
|
||||||
expectedPVC: basePVC.withResourceStatus(v1.ResourceCPU, v1.PersistentVolumeClaimControllerResizeFailed).
|
expectedPVC: basePVC.withResourceStatus(v1.ResourceCPU, v1.PersistentVolumeClaimControllerResizeInfeasible).
|
||||||
withStorageResourceStatus("").get(),
|
withStorageResourceStatus("").get(),
|
||||||
testFunc: func(pvc *v1.PersistentVolumeClaim, i clientset.Interface, q resource.Quantity) (*v1.PersistentVolumeClaim, error) {
|
testFunc: func(pvc *v1.PersistentVolumeClaim, i clientset.Interface, q resource.Quantity) (*v1.PersistentVolumeClaim, error) {
|
||||||
return MarkFSResizeFinished(pvc, q, i)
|
return MarkFSResizeFinished(pvc, q, i)
|
||||||
|
@ -102,6 +102,27 @@ func IsFailedPreconditionError(err error) bool {
|
|||||||
return errors.As(err, &failedPreconditionError)
|
return errors.As(err, &failedPreconditionError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InfeasibleError errors are a subset of OperationFinished or final error
|
||||||
|
// codes. In terms of CSI - this usually means that, the operation is not possible
|
||||||
|
// in current state with given arguments.
|
||||||
|
type InfeasibleError struct {
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *InfeasibleError) Error() string {
|
||||||
|
return err.msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInfeasibleError returns a new instance of InfeasibleError
|
||||||
|
func NewInfeasibleError(msg string) *InfeasibleError {
|
||||||
|
return &InfeasibleError{msg: msg}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsInfeasibleError(err error) bool {
|
||||||
|
var infeasibleError *InfeasibleError
|
||||||
|
return errors.As(err, &infeasibleError)
|
||||||
|
}
|
||||||
|
|
||||||
type OperationNotSupported struct {
|
type OperationNotSupported struct {
|
||||||
msg string
|
msg string
|
||||||
}
|
}
|
||||||
|
@ -1496,7 +1496,7 @@ func TestAdmitPVCStatus(t *testing.T) {
|
|||||||
noExistingPods := corev1lister.NewPodLister(noExistingPodsIndex)
|
noExistingPods := corev1lister.NewPodLister(noExistingPodsIndex)
|
||||||
mynode := &user.DefaultInfo{Name: "system:node:mynode", Groups: []string{"system:nodes"}}
|
mynode := &user.DefaultInfo{Name: "system:node:mynode", Groups: []string{"system:nodes"}}
|
||||||
|
|
||||||
nodeExpansionFailed := api.PersistentVolumeClaimNodeResizeFailed
|
nodeExpansionFailed := api.PersistentVolumeClaimNodeResizeInfeasible
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -599,15 +599,29 @@ type TypedObjectReference struct {
|
|||||||
Namespace *string `json:"namespace,omitempty" protobuf:"bytes,4,opt,name=namespace"`
|
Namespace *string `json:"namespace,omitempty" protobuf:"bytes,4,opt,name=namespace"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PersistentVolumeClaimConditionType is a valid value of PersistentVolumeClaimCondition.Type
|
// PersistentVolumeClaimConditionType defines the condition of PV claim.
|
||||||
|
// Valid values are:
|
||||||
|
// - "Resizing", "FileSystemResizePending"
|
||||||
|
//
|
||||||
|
// If RecoverVolumeExpansionFailure feature gate is enabled, then following additional values can be expected:
|
||||||
|
// - "ControllerResizeError", "NodeResizeError"
|
||||||
|
//
|
||||||
|
// If VolumeAttributesClass feature gate is enabled, then following additional values can be expected:
|
||||||
|
// - "ModifyVolumeError", "ModifyingVolume"
|
||||||
type PersistentVolumeClaimConditionType string
|
type PersistentVolumeClaimConditionType string
|
||||||
|
|
||||||
|
// These are valid conditions of PVC
|
||||||
const (
|
const (
|
||||||
// PersistentVolumeClaimResizing - a user trigger resize of pvc has been started
|
// PersistentVolumeClaimResizing - a user trigger resize of pvc has been started
|
||||||
PersistentVolumeClaimResizing PersistentVolumeClaimConditionType = "Resizing"
|
PersistentVolumeClaimResizing PersistentVolumeClaimConditionType = "Resizing"
|
||||||
// PersistentVolumeClaimFileSystemResizePending - controller resize is finished and a file system resize is pending on node
|
// PersistentVolumeClaimFileSystemResizePending - controller resize is finished and a file system resize is pending on node
|
||||||
PersistentVolumeClaimFileSystemResizePending PersistentVolumeClaimConditionType = "FileSystemResizePending"
|
PersistentVolumeClaimFileSystemResizePending PersistentVolumeClaimConditionType = "FileSystemResizePending"
|
||||||
|
|
||||||
|
// PersistentVolumeClaimControllerResizeError indicates an error while resizing volume for size in the controller
|
||||||
|
PersistentVolumeClaimControllerResizeError PersistentVolumeClaimConditionType = "ControllerResizeError"
|
||||||
|
// PersistentVolumeClaimNodeResizeError indicates an error while resizing volume for size in the node.
|
||||||
|
PersistentVolumeClaimNodeResizeError PersistentVolumeClaimConditionType = "NodeResizeError"
|
||||||
|
|
||||||
// Applying the target VolumeAttributesClass encountered an error
|
// Applying the target VolumeAttributesClass encountered an error
|
||||||
PersistentVolumeClaimVolumeModifyVolumeError PersistentVolumeClaimConditionType = "ModifyVolumeError"
|
PersistentVolumeClaimVolumeModifyVolumeError PersistentVolumeClaimConditionType = "ModifyVolumeError"
|
||||||
// Volume is being modified
|
// Volume is being modified
|
||||||
@ -624,18 +638,19 @@ const (
|
|||||||
// State set when resize controller starts resizing the volume in control-plane.
|
// State set when resize controller starts resizing the volume in control-plane.
|
||||||
PersistentVolumeClaimControllerResizeInProgress ClaimResourceStatus = "ControllerResizeInProgress"
|
PersistentVolumeClaimControllerResizeInProgress ClaimResourceStatus = "ControllerResizeInProgress"
|
||||||
|
|
||||||
// State set when resize has failed in resize controller with a terminal error.
|
// State set when resize has failed in resize controller with a terminal unrecoverable error.
|
||||||
// Transient errors such as timeout should not set this status and should leave allocatedResourceStatus
|
// Transient errors such as timeout should not set this status and should leave allocatedResourceStatus
|
||||||
// unmodified, so as resize controller can resume the volume expansion.
|
// unmodified, so as resize controller can resume the volume expansion.
|
||||||
PersistentVolumeClaimControllerResizeFailed ClaimResourceStatus = "ControllerResizeFailed"
|
PersistentVolumeClaimControllerResizeInfeasible ClaimResourceStatus = "ControllerResizeInfeasible"
|
||||||
|
|
||||||
// State set when resize controller has finished resizing the volume but further resizing of volume
|
// State set when resize controller has finished resizing the volume but further resizing of volume
|
||||||
// is needed on the node.
|
// is needed on the node.
|
||||||
PersistentVolumeClaimNodeResizePending ClaimResourceStatus = "NodeResizePending"
|
PersistentVolumeClaimNodeResizePending ClaimResourceStatus = "NodeResizePending"
|
||||||
// State set when kubelet starts resizing the volume.
|
// State set when kubelet starts resizing the volume.
|
||||||
PersistentVolumeClaimNodeResizeInProgress ClaimResourceStatus = "NodeResizeInProgress"
|
PersistentVolumeClaimNodeResizeInProgress ClaimResourceStatus = "NodeResizeInProgress"
|
||||||
// State set when resizing has failed in kubelet with a terminal error. Transient errors don't set NodeResizeFailed
|
// State set when resizing has failed in kubelet with a terminal unrecoverable error. Transient errors
|
||||||
PersistentVolumeClaimNodeResizeFailed ClaimResourceStatus = "NodeResizeFailed"
|
// shouldn't set this status
|
||||||
|
PersistentVolumeClaimNodeResizeInfeasible ClaimResourceStatus = "NodeResizeInfeasible"
|
||||||
)
|
)
|
||||||
|
|
||||||
// +enum
|
// +enum
|
||||||
|
@ -411,7 +411,7 @@ var _ = utils.SIGDescribe("CSI Mock volume expansion", func() {
|
|||||||
pvcRequestSize: "11Gi", // expansion to 11Gi will cause expansion to fail on controller
|
pvcRequestSize: "11Gi", // expansion to 11Gi will cause expansion to fail on controller
|
||||||
allocatedResource: "11Gi",
|
allocatedResource: "11Gi",
|
||||||
simulatedCSIDriverError: expansionFailedOnController,
|
simulatedCSIDriverError: expansionFailedOnController,
|
||||||
expectedResizeStatus: v1.PersistentVolumeClaimControllerResizeFailed,
|
expectedResizeStatus: v1.PersistentVolumeClaimControllerResizeInfeasible,
|
||||||
recoverySize: resource.MustParse("4Gi"),
|
recoverySize: resource.MustParse("4Gi"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -419,7 +419,7 @@ var _ = utils.SIGDescribe("CSI Mock volume expansion", func() {
|
|||||||
pvcRequestSize: "9Gi", // expansion to 9Gi will cause expansion to fail on node
|
pvcRequestSize: "9Gi", // expansion to 9Gi will cause expansion to fail on node
|
||||||
allocatedResource: "9Gi",
|
allocatedResource: "9Gi",
|
||||||
simulatedCSIDriverError: expansionFailedOnNode,
|
simulatedCSIDriverError: expansionFailedOnNode,
|
||||||
expectedResizeStatus: v1.PersistentVolumeClaimNodeResizeFailed,
|
expectedResizeStatus: v1.PersistentVolumeClaimNodeResizeInfeasible,
|
||||||
recoverySize: resource.MustParse("5Gi"),
|
recoverySize: resource.MustParse("5Gi"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -473,11 +473,11 @@ var _ = utils.SIGDescribe("CSI Mock volume expansion", func() {
|
|||||||
func validateRecoveryBehaviour(ctx context.Context, pvc *v1.PersistentVolumeClaim, m *mockDriverSetup, test recoveryTest) {
|
func validateRecoveryBehaviour(ctx context.Context, pvc *v1.PersistentVolumeClaim, m *mockDriverSetup, test recoveryTest) {
|
||||||
var err error
|
var err error
|
||||||
ginkgo.By("Waiting for resizer to set allocated resource")
|
ginkgo.By("Waiting for resizer to set allocated resource")
|
||||||
err = waitForAllocatedResource(pvc, m, test.allocatedResource)
|
err = waitForAllocatedResource(ctx, pvc, m, test.allocatedResource)
|
||||||
framework.ExpectNoError(err, "While waiting for allocated resource to be updated")
|
framework.ExpectNoError(err, "While waiting for allocated resource to be updated")
|
||||||
|
|
||||||
ginkgo.By("Waiting for resizer to set resize status")
|
ginkgo.By("Waiting for resizer to set resize status")
|
||||||
err = waitForResizeStatus(pvc, m.cs, test.expectedResizeStatus)
|
err = waitForResizeStatus(ctx, pvc, m.cs, test.expectedResizeStatus)
|
||||||
framework.ExpectNoError(err, "While waiting for resize status to be set")
|
framework.ExpectNoError(err, "While waiting for resize status to be set")
|
||||||
|
|
||||||
ginkgo.By("Recover pvc size")
|
ginkgo.By("Recover pvc size")
|
||||||
@ -500,7 +500,7 @@ func validateRecoveryBehaviour(ctx context.Context, pvc *v1.PersistentVolumeClai
|
|||||||
// if expansion succeeded on controller but failed on the node
|
// if expansion succeeded on controller but failed on the node
|
||||||
if test.simulatedCSIDriverError == expansionFailedOnNode {
|
if test.simulatedCSIDriverError == expansionFailedOnNode {
|
||||||
ginkgo.By("Wait for expansion to fail on node again")
|
ginkgo.By("Wait for expansion to fail on node again")
|
||||||
err = waitForResizeStatus(pvc, m.cs, v1.PersistentVolumeClaimNodeResizeFailed)
|
err = waitForResizeStatus(ctx, pvc, m.cs, v1.PersistentVolumeClaimNodeResizeInfeasible)
|
||||||
framework.ExpectNoError(err, "While waiting for resize status to be set to expansion-failed-on-node")
|
framework.ExpectNoError(err, "While waiting for resize status to be set to expansion-failed-on-node")
|
||||||
|
|
||||||
ginkgo.By("verify allocated resources after recovery")
|
ginkgo.By("verify allocated resources after recovery")
|
||||||
@ -541,31 +541,31 @@ func validateExpansionSuccess(ctx context.Context, pvc *v1.PersistentVolumeClaim
|
|||||||
gomega.Expect(resizeStatus).To(gomega.BeZero(), "resize status should be empty")
|
gomega.Expect(resizeStatus).To(gomega.BeZero(), "resize status should be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForResizeStatus(pvc *v1.PersistentVolumeClaim, c clientset.Interface, expectedState v1.ClaimResourceStatus) error {
|
func waitForResizeStatus(ctx context.Context, pvc *v1.PersistentVolumeClaim, c clientset.Interface, expectedState v1.ClaimResourceStatus) error {
|
||||||
var actualResizeStatus *v1.ClaimResourceStatus
|
var actualResizeStatus v1.ClaimResourceStatus
|
||||||
|
|
||||||
waitErr := wait.PollImmediate(resizePollInterval, csiResizeWaitPeriod, func() (bool, error) {
|
waitErr := wait.PollUntilContextTimeout(ctx, resizePollInterval, csiResizeWaitPeriod, true, func(pollContext context.Context) (bool, error) {
|
||||||
var err error
|
var err error
|
||||||
updatedPVC, err := c.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(context.TODO(), pvc.Name, metav1.GetOptions{})
|
updatedPVC, err := c.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(pollContext, pvc.Name, metav1.GetOptions{})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("error fetching pvc %q for checking for resize status: %w", pvc.Name, err)
|
return false, fmt.Errorf("error fetching pvc %q for checking for resize status: %w", pvc.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
actualResizeStatus := updatedPVC.Status.AllocatedResourceStatuses[v1.ResourceStorage]
|
actualResizeStatus = updatedPVC.Status.AllocatedResourceStatuses[v1.ResourceStorage]
|
||||||
return (actualResizeStatus == expectedState), nil
|
return (actualResizeStatus == expectedState), nil
|
||||||
})
|
})
|
||||||
if waitErr != nil {
|
if waitErr != nil {
|
||||||
return fmt.Errorf("error while waiting for resize status to sync to %v, actualStatus %s: %v", expectedState, *actualResizeStatus, waitErr)
|
return fmt.Errorf("error while waiting for resize status to sync to %v, actualStatus %s: %w", expectedState, actualResizeStatus, waitErr)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForAllocatedResource(pvc *v1.PersistentVolumeClaim, m *mockDriverSetup, expectedSize string) error {
|
func waitForAllocatedResource(ctx context.Context, pvc *v1.PersistentVolumeClaim, m *mockDriverSetup, expectedSize string) error {
|
||||||
expectedQuantity := resource.MustParse(expectedSize)
|
expectedQuantity := resource.MustParse(expectedSize)
|
||||||
waitErr := wait.PollImmediate(resizePollInterval, csiResizeWaitPeriod, func() (bool, error) {
|
waitErr := wait.PollUntilContextTimeout(ctx, resizePollInterval, csiResizeWaitPeriod, true, func(pollContext context.Context) (bool, error) {
|
||||||
var err error
|
var err error
|
||||||
updatedPVC, err := m.cs.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(context.TODO(), pvc.Name, metav1.GetOptions{})
|
updatedPVC, err := m.cs.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(pollContext, pvc.Name, metav1.GetOptions{})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("error fetching pvc %q for checking for resize status: %w", pvc.Name, err)
|
return false, fmt.Errorf("error fetching pvc %q for checking for resize status: %w", pvc.Name, err)
|
||||||
|
Loading…
Reference in New Issue
Block a user