Change:
Is this a BUG REPORT or FEATURE REQUEST?: BUG REPORT /kind bug What happened: If someone requests a zero (0) capacity PVC and an administrator creates a zero (0) capacity PV, Kubernetes will happily bind them together. Unfortunately, any use of this PVC/PV combination by a pod will fail. It fails because the zero capacity PV cannot be formatted or mounted successfully. Note: The same issue happens for dynamically provisioned PVs. What you expected to happen: I expect that Kubernetes would not allow creation of invalid PVC and PV objects, aka zero (0) capacity PVs and PVCs. How to reproduce it (as minimally and precisely as possible): Create a pod that requests a zero capacity PVC, using dynamic PV provisioning is simpler than creating your own zero (0) capacity PV. The pod will fail to reach a running state, since the related PV fails to be formatted and mounted successfully. Here is the error that I received for a test I performed requesting a zero capacity PVC using ceph rbd: Anything else we need to know?: Here is a related item with a specific fix for external ceph rbd provisioner: Environment: Nothing specific to environment
/sig storage
/assign

Discussion:
const isInvalidQuotaResource string = `must be a standard resource for quota` const fieldImmutableErrorMsg string = genericvalidation.FieldImmutableErrorMsg const isNotIntegerErrorMsg string = `must be an integer` <del> const isZeroErrorMsg string = `must be greater than zero` </del> <ins> const isNotPositiveErrorMsg string = `must be greater than zero` </ins> var pdPartitionErrorMsg string = validation.InclusiveRangeError(1, 255) var volumeModeErrorMsg string = "must be a number between 0 and 0777 (octal), both inclusive"

return allErrs } <ins> // Validates that a Quantity is positive func ValidatePositiveQuantityValue(value resource.Quantity, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if value.Cmp(resource.Quantity{}) <= 0 { allErrs = append(allErrs, field.Invalid(fldPath, value.String(), isNotPositiveErrorMsg)) } return allErrs } </ins> func ValidateImmutableField(newVal, oldVal interface{}, fldPath *field.Path) field.ErrorList { return genericvalidation.ValidateImmutableField(newVal, oldVal, fldPath) }

capPath := specPath.Child("capacity") for r, qty := range pv.Spec.Capacity { allErrs = append(allErrs, validateBasicResource(qty, capPath.Key(string(r)))...) <ins> allErrs = append(allErrs, ValidatePositiveQuantityValue(qty, capPath.Key(string(r)))...) </ins> } if len(string(pv.Spec.PersistentVolumeReclaimPolicy)) > 0 { if !supportedReclaimPolicy.Has(string(pv.Spec.PersistentVolumeReclaimPolicy)) {

allErrs = append(allErrs, field.Required(fldPath.Child("resources").Key(string(core.ResourceStorage)), "")) } else { allErrs = append(allErrs, ValidateResourceQuantityValue(string(core.ResourceStorage), storageValue, fldPath.Child("resources").Key(string(core.ResourceStorage)))...) <del> if storageValue.Value() == int64(0) { allErrs = append(allErrs, field.Invalid(fldPath, storageValue, isZeroErrorMsg)) } </del> <ins> allErrs = append(allErrs, ValidatePositiveQuantityValue(storageValue, fldPath.Child("resources").Key(string(core.ResourceStorage)))...) </ins> } if spec.StorageClassName != nil && len(*spec.StorageClassName) > 0 {

}, }), }, <ins> "bad-volume-zero-capacity": { isExpectedFailure: true, volume: testVolume("foo", "", core.PersistentVolumeSpec{ Capacity: core.ResourceList{ core.ResourceName(core.ResourceStorage): resource.MustParse("0"), }, AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, PersistentVolumeSource: core.PersistentVolumeSource{ HostPath: &core.HostPathVolumeSource{ Path: "/foo", Type: newHostPathType(string(core.HostPathDirectory)), }, }, }), }, </ins> "missing-accessmodes": { isExpectedFailure: true, volume: testVolume("goodname", "missing-accessmodes", core.PersistentVolumeSpec{

StorageClassName: &validClassName, }), }, <ins> "invalid-claim-zero-capacity": { isExpectedFailure: true, claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ Selector: &metav1.LabelSelector{ MatchExpressions: []metav1.LabelSelectorRequirement{ { Key:      "key2", Operator: "Exists", }, }, }, AccessModes: []core.PersistentVolumeAccessMode{ core.ReadWriteOnce, core.ReadOnlyMany, }, Resources: core.ResourceRequirements{ Requests: core.ResourceList{ core.ResourceName(core.ResourceStorage): resource.MustParse("0G"), }, }, StorageClassName: &validClassName, }), }, </ins> "invalid-label-selector": { isExpectedFailure: true, claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{

pvs := make([]*v1.PersistentVolume, maxPVs) for i := 0; i < maxPVs; i++ { // This PV will be claimed, released, and deleted <del> pvs[i] = createPV("pv-"+strconv.Itoa(i), "/tmp/foo"+strconv.Itoa(i), strconv.Itoa(i)+"G", </del> <ins> pvs[i] = createPV("pv-"+strconv.Itoa(i), "/tmp/foo"+strconv.Itoa(i), strconv.Itoa(i+1)+"G", </ins> []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, v1.PersistentVolumeReclaimRetain) }