diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go index 10f0b83563c..3dac56297d9 100644 --- a/pkg/apis/core/validation/validation.go +++ b/pkg/apis/core/validation/validation.go @@ -2686,57 +2686,6 @@ var resizeStatusSet = sets.New(core.PersistentVolumeClaimControllerResizeInProgr core.PersistentVolumeClaimNodeResizeInProgress, core.PersistentVolumeClaimNodeResizeInfeasible) -var validVolumeHealthStatusTypes = sets.New( - core.VolumeHealthInaccessible, - core.VolumeHealthDataLoss, - core.VolumeHealthDegraded, -) - -const maxVolumeHealthConditions = 16 - -func validateVolumeHealthCondition(condition core.VolumeHealthCondition, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if !validVolumeHealthStatusTypes.Has(condition.Status) { - allErrs = append(allErrs, field.NotSupported(fldPath.Child("status"), condition.Status, sets.List(validVolumeHealthStatusTypes))) - } - if len(condition.Reason) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("reason"), "")) - } else if len(condition.Reason) > 256 { - allErrs = append(allErrs, field.TooLong(fldPath.Child("reason"), condition.Reason, 256)) - } - if len(condition.Message) > 1024 { - allErrs = append(allErrs, field.TooLong(fldPath.Child("message"), condition.Message, 1024)) - } - return allErrs -} - -func validateVolumeHealthConditions(conditions []core.VolumeHealthCondition, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(conditions) > maxVolumeHealthConditions { - allErrs = append(allErrs, field.TooMany(fldPath, len(conditions), maxVolumeHealthConditions)) - } - seen := sets.New[string]() - for i, condition := range conditions { - key := string(condition.Status) + "/" + condition.Reason - idxPath := fldPath.Index(i) - if seen.Has(key) { - allErrs = append(allErrs, field.Duplicate(idxPath, key)) - } - seen.Insert(key) - allErrs = append(allErrs, validateVolumeHealthCondition(condition, idxPath)...) - } - return allErrs -} - -func validateVolumeHealthStatus(healthStatus *core.VolumeHealthStatus, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if healthStatus == nil { - return allErrs - } - allErrs = append(allErrs, validateVolumeHealthConditions(healthStatus.HealthConditions, fldPath.Child("healthConditions"))...) - return allErrs -} - func validatePodVolumeHealth(volumeHealth []core.PodVolumeHealth, spec *core.PodSpec, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} volumeNames := sets.New[string]() @@ -2745,20 +2694,11 @@ func validatePodVolumeHealth(volumeHealth []core.PodVolumeHealth, spec *core.Pod volumeNames.Insert(v.Name) } } - seenNames := sets.New[string]() for i, vh := range volumeHealth { idxPath := fldPath.Index(i) - if len(vh.Name) == 0 { - allErrs = append(allErrs, field.Required(idxPath.Child("name"), "")) - } - if seenNames.Has(vh.Name) { - allErrs = append(allErrs, field.Duplicate(idxPath.Child("name"), vh.Name)) - } - seenNames.Insert(vh.Name) if len(vh.Name) > 0 && !volumeNames.Has(vh.Name) { allErrs = append(allErrs, field.NotFound(idxPath.Child("name"), vh.Name)) } - allErrs = append(allErrs, validateVolumeHealthConditions(vh.HealthConditions, idxPath.Child("healthConditions"))...) } return allErrs } @@ -2805,7 +2745,6 @@ func ValidatePersistentVolumeClaimStatusUpdate(newPvc, oldPvc *core.PersistentVo } } } - allErrs = append(allErrs, validateVolumeHealthStatus(newPvc.Status.HealthStatus, field.NewPath("status", "healthStatus"))...) return allErrs } diff --git a/pkg/apis/core/validation/validation_test.go b/pkg/apis/core/validation/validation_test.go index dfc2c96f7d4..1f426b78ba7 100644 --- a/pkg/apis/core/validation/validation_test.go +++ b/pkg/apis/core/validation/validation_test.go @@ -22805,137 +22805,6 @@ func TestValidatePersistentVolumeClaimStatusUpdate(t *testing.T) { } } -func makeVolumeHealthConditions(n int) []core.VolumeHealthCondition { - conditions := make([]core.VolumeHealthCondition, n) - for i := range conditions { - conditions[i] = core.VolumeHealthCondition{ - Status: core.VolumeHealthDegraded, - Reason: fmt.Sprintf("Reason%d", i), - } - } - return conditions -} - -func TestValidateVolumeHealthStatus(t *testing.T) { - tests := []struct { - name string - healthStatus *core.VolumeHealthStatus - isErr bool - expectedErr string - }{ - { - name: "valid health status", - healthStatus: &core.VolumeHealthStatus{ - HealthConditions: []core.VolumeHealthCondition{ - {Status: core.VolumeHealthDegraded, Reason: "DiskSlow"}, - }, - }, - }, - { - name: "valid health status with multiple conditions", - healthStatus: &core.VolumeHealthStatus{ - HealthConditions: []core.VolumeHealthCondition{ - {Status: core.VolumeHealthDegraded, Reason: "DiskSlow"}, - {Status: core.VolumeHealthInaccessible, Reason: "VolumeNotFound"}, - }, - }, - }, - { - name: "nil health status is valid", - healthStatus: nil, - }, - { - name: "invalid status type", - healthStatus: &core.VolumeHealthStatus{ - HealthConditions: []core.VolumeHealthCondition{ - {Status: "InvalidType", Reason: "SomeReason"}, - }, - }, - isErr: true, - expectedErr: "healthConditions[0].status", - }, - { - name: "empty reason", - healthStatus: &core.VolumeHealthStatus{ - HealthConditions: []core.VolumeHealthCondition{ - {Status: core.VolumeHealthDegraded, Reason: ""}, - }, - }, - isErr: true, - expectedErr: "healthConditions[0].reason", - }, - { - name: "duplicate conditions", - healthStatus: &core.VolumeHealthStatus{ - HealthConditions: []core.VolumeHealthCondition{ - {Status: core.VolumeHealthDegraded, Reason: "DiskSlow"}, - {Status: core.VolumeHealthDegraded, Reason: "DiskSlow"}, - }, - }, - isErr: true, - expectedErr: "healthConditions[1]", - }, - { - name: "reason too long", - healthStatus: &core.VolumeHealthStatus{ - HealthConditions: []core.VolumeHealthCondition{ - {Status: core.VolumeHealthDegraded, Reason: strings.Repeat("a", 257)}, - }, - }, - isErr: true, - expectedErr: "healthConditions[0].reason", - }, - { - name: "message too long", - healthStatus: &core.VolumeHealthStatus{ - HealthConditions: []core.VolumeHealthCondition{ - {Status: core.VolumeHealthDegraded, Reason: "DiskSlow", Message: strings.Repeat("a", 1025)}, - }, - }, - isErr: true, - expectedErr: "healthConditions[0].message", - }, - { - name: "exactly max health conditions is valid", - healthStatus: &core.VolumeHealthStatus{ - HealthConditions: makeVolumeHealthConditions(maxVolumeHealthConditions), - }, - }, - { - name: "too many health conditions", - healthStatus: &core.VolumeHealthStatus{ - HealthConditions: makeVolumeHealthConditions(maxVolumeHealthConditions + 1), - }, - isErr: true, - expectedErr: "healthConditions", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - errs := validateVolumeHealthStatus(tt.healthStatus, field.NewPath("healthStatus")) - if tt.isErr && len(errs) == 0 { - t.Errorf("expected error but got none") - } - if !tt.isErr && len(errs) > 0 { - t.Errorf("unexpected errors: %v", errs) - } - if tt.isErr && len(errs) > 0 && tt.expectedErr != "" { - found := false - for _, err := range errs { - if strings.Contains(err.Field, tt.expectedErr) { - found = true - break - } - } - if !found { - t.Errorf("expected error containing %q but got: %v", tt.expectedErr, errs) - } - } - }) - } -} - func TestValidatePodVolumeHealth(t *testing.T) { podSpec := &core.PodSpec{ Volumes: []core.Volume{ @@ -22973,56 +22842,6 @@ func TestValidatePodVolumeHealth(t *testing.T) { isErr: true, expectedErr: "volumeHealth[0].name", }, - { - name: "duplicate volume names", - volumeHealth: []core.PodVolumeHealth{ - {Name: "vol1", HealthConditions: []core.VolumeHealthCondition{{Status: core.VolumeHealthDegraded, Reason: "DiskSlow"}}}, - {Name: "vol1", HealthConditions: []core.VolumeHealthCondition{{Status: core.VolumeHealthInaccessible, Reason: "NotFound"}}}, - }, - isErr: true, - expectedErr: "volumeHealth[1].name", - }, - { - name: "empty volume name", - volumeHealth: []core.PodVolumeHealth{ - {Name: "", HealthConditions: []core.VolumeHealthCondition{{Status: core.VolumeHealthDegraded, Reason: "DiskSlow"}}}, - }, - isErr: true, - expectedErr: "volumeHealth[0].name", - }, - { - name: "invalid condition status in pod volume health", - volumeHealth: []core.PodVolumeHealth{ - { - Name: "vol1", - HealthConditions: []core.VolumeHealthCondition{ - {Status: "BadStatus", Reason: "SomeReason"}, - }, - }, - }, - isErr: true, - expectedErr: "volumeHealth[0].healthConditions[0].status", - }, - { - name: "exactly max health conditions is valid", - volumeHealth: []core.PodVolumeHealth{ - { - Name: "vol1", - HealthConditions: makeVolumeHealthConditions(maxVolumeHealthConditions), - }, - }, - }, - { - name: "too many health conditions", - volumeHealth: []core.PodVolumeHealth{ - { - Name: "vol1", - HealthConditions: makeVolumeHealthConditions(maxVolumeHealthConditions + 1), - }, - }, - isErr: true, - expectedErr: "volumeHealth[0].healthConditions", - }, } for _, tt := range tests { diff --git a/staging/src/k8s.io/api/core/v1/types.go b/staging/src/k8s.io/api/core/v1/types.go index eeb62bd93d3..86cdc8267b0 100644 --- a/staging/src/k8s.io/api/core/v1/types.go +++ b/staging/src/k8s.io/api/core/v1/types.go @@ -767,6 +767,7 @@ type PersistentVolumeClaimCondition struct { // VolumeHealthStatusType describes the health status category of a volume. // +enum +// +k8s:enum type VolumeHealthStatusType string const ( @@ -782,15 +783,21 @@ const ( type VolumeHealthCondition struct { // status is the machine-parseable health category. // One of "Inaccessible", "DataLoss", "Degraded". + // +required + // +k8s:required Status VolumeHealthStatusType `json:"status" protobuf:"bytes,1,opt,name=status,casttype=VolumeHealthStatusType"` // reason is a brief CamelCase machine-parseable reason. // Together with status it forms the unique identity of a condition entry. - // Maximum permitted length of a reason is 256 characters.a + // Maximum permitted length of a reason is 256 bytes. // +required + // +k8s:required + // +k8s:maxBytes=256 Reason string `json:"reason" protobuf:"bytes,2,opt,name=reason"` // message is a human-readable description. + // Maximum permitted length of a message is 1024 bytes. // +optional - // Maximum permitted length of a message is 1024 characters. + // +k8s:optional + // +k8s:maxBytes=1024 Message string `json:"message,omitempty" protobuf:"bytes,3,opt,name=message"` } @@ -806,6 +813,11 @@ type VolumeHealthStatus struct { // +patchMergeKey=status // +patchStrategy=merge // +listMapKey=reason + // +k8s:optional + // +k8s:listType=map + // +k8s:listMapKey=status + // +k8s:listMapKey=reason + // +k8s:maxItems=16 HealthConditions []VolumeHealthCondition `json:"healthConditions,omitempty" patchStrategy:"merge" patchMergeKey:"status" protobuf:"bytes,1,rep,name=healthConditions"` // lastTransitionTime is when the current set of conditions first appeared. // +optional @@ -816,6 +828,8 @@ type VolumeHealthStatus struct { // reported by the CSI node plugin via the kubelet. type PodVolumeHealth struct { // name matches an entry in pod.spec.volumes. + // +required + // +k8s:required Name string `json:"name" protobuf:"bytes,1,opt,name=name"` // conditions is the set of adverse conditions reported by // the CSI node plugin for this volume on this node. @@ -826,6 +840,11 @@ type PodVolumeHealth struct { // +patchMergeKey=status // +patchStrategy=merge // +listMapKey=reason + // +k8s:optional + // +k8s:listType=map + // +k8s:listMapKey=status + // +k8s:listMapKey=reason + // +k8s:maxItems=16 HealthConditions []VolumeHealthCondition `json:"healthConditions,omitempty" patchStrategy:"merge" patchMergeKey:"status" protobuf:"bytes,2,rep,name=healthConditions"` // lastTransitionTime is when the current set of conditions first appeared. // +optional @@ -929,6 +948,7 @@ type PersistentVolumeClaimStatus struct { // for the volume bound to this claim. // +featureGate=CSIVolumeHealth // +optional + // +k8s:optional HealthStatus *VolumeHealthStatus `json:"healthStatus,omitempty" protobuf:"bytes,10,opt,name=healthStatus"` } @@ -5618,6 +5638,9 @@ type PodStatus struct { // +optional // +listType=map // +listMapKey=name + // +k8s:optional + // +k8s:listType=map + // +k8s:listMapKey=name VolumeHealth []PodVolumeHealth `json:"volumeHealth,omitempty" protobuf:"bytes,22,rep,name=volumeHealth"` }