Implement necessary API changes

Introduce feature gate for expanding PVs
Add a field to SC
Add new Conditions and feature tag pvc update
Add tests for size update via feature gate
register the resize admission plugin
Update golint failures
This commit is contained in:
Hemant Kumar
2017-09-04 09:02:34 +02:00
parent 034c40be6f
commit e78d433150
21 changed files with 818 additions and 8 deletions

View File

@@ -1588,10 +1588,31 @@ func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *api.PersistentVolumeCla
oldPvc.Spec.VolumeName = newPvc.Spec.VolumeName
defer func() { oldPvc.Spec.VolumeName = "" }()
}
// changes to Spec are not allowed, but updates to label/and some annotations are OK.
// no-op updates pass validation.
if !apiequality.Semantic.DeepEqual(newPvc.Spec, oldPvc.Spec) {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "field is immutable after creation"))
if utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) {
newPVCSpecCopy := newPvc.Spec.DeepCopy()
// lets make sure storage values are same.
if newPvc.Status.Phase == api.ClaimBound && newPVCSpecCopy.Resources.Requests != nil {
newPVCSpecCopy.Resources.Requests["storage"] = oldPvc.Spec.Resources.Requests["storage"]
}
oldSize := oldPvc.Spec.Resources.Requests["storage"]
newSize := newPvc.Spec.Resources.Requests["storage"]
if !apiequality.Semantic.DeepEqual(*newPVCSpecCopy, oldPvc.Spec) {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "is immutable after creation except resources.requests for bound claims"))
}
if newSize.Cmp(oldSize) < 0 {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "resources", "requests", "storage"), "field can not be less than previous value"))
}
} else {
// changes to Spec are not allowed, but updates to label/and some annotations are OK.
// no-op updates pass validation.
if !apiequality.Semantic.DeepEqual(newPvc.Spec, oldPvc.Spec) {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "field is immutable after creation"))
}
}
// storageclass annotation should be immutable after creation
@@ -1611,6 +1632,10 @@ func ValidatePersistentVolumeClaimStatusUpdate(newPvc, oldPvc *api.PersistentVol
if len(newPvc.Spec.AccessModes) == 0 {
allErrs = append(allErrs, field.Required(field.NewPath("Spec", "accessModes"), ""))
}
if !utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) && len(newPvc.Status.Conditions) > 0 {
conditionPath := field.NewPath("status", "conditions")
allErrs = append(allErrs, field.Forbidden(conditionPath, "invalid field"))
}
capPath := field.NewPath("status", "capacity")
for r, qty := range newPvc.Status.Capacity {
allErrs = append(allErrs, validateBasicResource(qty, capPath.Key(string(r)))...)

View File

@@ -530,6 +530,17 @@ func testVolumeClaim(name string, namespace string, spec api.PersistentVolumeCla
}
}
func testVolumeClaimWithStatus(
name, namespace string,
spec api.PersistentVolumeClaimSpec,
status api.PersistentVolumeClaimStatus) *api.PersistentVolumeClaim {
return &api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
Spec: spec,
Status: status,
}
}
func testVolumeClaimStorageClass(name string, namespace string, annval string, spec api.PersistentVolumeClaimSpec) *api.PersistentVolumeClaim {
annotations := map[string]string{
v1.BetaStorageClassAnnotation: annval,
@@ -728,7 +739,7 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
}
func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
validClaim := testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{
validClaim := testVolumeClaimWithStatus("foo", "ns", api.PersistentVolumeClaimSpec{
AccessModes: []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
api.ReadOnlyMany,
@@ -738,7 +749,10 @@ func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
},
}, api.PersistentVolumeClaimStatus{
Phase: api.ClaimBound,
})
validClaimStorageClass := testVolumeClaimStorageClass("foo", "ns", "fast", api.PersistentVolumeClaimSpec{
AccessModes: []api.PersistentVolumeAccessMode{
api.ReadOnlyMany,
@@ -828,50 +842,125 @@ func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
},
VolumeName: "volume",
})
validSizeUpdate := testVolumeClaimWithStatus("foo", "ns", api.PersistentVolumeClaimSpec{
AccessModes: []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
api.ReadOnlyMany,
},
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("15G"),
},
},
}, api.PersistentVolumeClaimStatus{
Phase: api.ClaimBound,
})
invalidSizeUpdate := testVolumeClaimWithStatus("foo", "ns", api.PersistentVolumeClaimSpec{
AccessModes: []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
api.ReadOnlyMany,
},
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("5G"),
},
},
}, api.PersistentVolumeClaimStatus{
Phase: api.ClaimBound,
})
unboundSizeUpdate := testVolumeClaimWithStatus("foo", "ns", api.PersistentVolumeClaimSpec{
AccessModes: []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
api.ReadOnlyMany,
},
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("12G"),
},
},
}, api.PersistentVolumeClaimStatus{
Phase: api.ClaimPending,
})
scenarios := map[string]struct {
isExpectedFailure bool
oldClaim *api.PersistentVolumeClaim
newClaim *api.PersistentVolumeClaim
enableResize bool
}{
"valid-update-volumeName-only": {
isExpectedFailure: false,
oldClaim: validClaim,
newClaim: validUpdateClaim,
enableResize: false,
},
"valid-no-op-update": {
isExpectedFailure: false,
oldClaim: validUpdateClaim,
newClaim: validUpdateClaim,
enableResize: false,
},
"invalid-update-change-resources-on-bound-claim": {
isExpectedFailure: true,
oldClaim: validUpdateClaim,
newClaim: invalidUpdateClaimResources,
enableResize: false,
},
"invalid-update-change-access-modes-on-bound-claim": {
isExpectedFailure: true,
oldClaim: validUpdateClaim,
newClaim: invalidUpdateClaimAccessModes,
enableResize: false,
},
"invalid-update-change-storage-class-annotation-after-creation": {
isExpectedFailure: true,
oldClaim: validClaimStorageClass,
newClaim: invalidUpdateClaimStorageClass,
enableResize: false,
},
"valid-update-mutable-annotation": {
isExpectedFailure: false,
oldClaim: validClaimAnnotation,
newClaim: validUpdateClaimMutableAnnotation,
enableResize: false,
},
"valid-update-add-annotation": {
isExpectedFailure: false,
oldClaim: validClaim,
newClaim: validAddClaimAnnotation,
enableResize: false,
},
"valid-size-update-resize-disabled": {
isExpectedFailure: true,
oldClaim: validClaim,
newClaim: validSizeUpdate,
enableResize: false,
},
"valid-size-update-resize-enabled": {
isExpectedFailure: false,
oldClaim: validClaim,
newClaim: validSizeUpdate,
enableResize: true,
},
"invalid-size-update-resize-enabled": {
isExpectedFailure: true,
oldClaim: validClaim,
newClaim: invalidSizeUpdate,
enableResize: true,
},
"unbound-size-update-resize-enabled": {
isExpectedFailure: true,
oldClaim: validClaim,
newClaim: unboundSizeUpdate,
enableResize: true,
},
}
for name, scenario := range scenarios {
// ensure we have a resource version specified for updates
togglePVExpandFeature(scenario.enableResize, t)
scenario.oldClaim.ResourceVersion = "1"
scenario.newClaim.ResourceVersion = "1"
errs := ValidatePersistentVolumeClaimUpdate(scenario.newClaim, scenario.oldClaim)
@@ -884,6 +973,23 @@ func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
}
}
func togglePVExpandFeature(toggleFlag bool, t *testing.T) {
if toggleFlag {
// Enable alpha feature LocalStorageCapacityIsolation
err := utilfeature.DefaultFeatureGate.Set("ExpandPersistentVolumes=true")
if err != nil {
t.Errorf("Failed to enable feature gate for ExpandPersistentVolumes: %v", err)
return
}
} else {
err := utilfeature.DefaultFeatureGate.Set("ExpandPersistentVolumes=false")
if err != nil {
t.Errorf("Failed to disable feature gate for ExpandPersistentVolumes: %v", err)
return
}
}
}
func TestValidateKeyToPath(t *testing.T) {
testCases := []struct {
kp api.KeyToPath
@@ -9232,6 +9338,68 @@ func TestValidateLimitRange(t *testing.T) {
}
func TestValidatePersistentVolumeClaimStatusUpdate(t *testing.T) {
validClaim := testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{
AccessModes: []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
api.ReadOnlyMany,
},
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
},
})
validConditionUpdate := testVolumeClaimWithStatus("foo", "ns", api.PersistentVolumeClaimSpec{
AccessModes: []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
api.ReadOnlyMany,
},
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
},
}, api.PersistentVolumeClaimStatus{
Phase: api.ClaimPending,
Conditions: []api.PersistentVolumeClaimCondition{
{Type: api.PersistentVolumeClaimResizing, Status: api.ConditionTrue},
},
})
scenarios := map[string]struct {
isExpectedFailure bool
oldClaim *api.PersistentVolumeClaim
newClaim *api.PersistentVolumeClaim
enableResize bool
}{
"condition-update-with-disabled-feature-gate": {
isExpectedFailure: true,
oldClaim: validClaim,
newClaim: validConditionUpdate,
enableResize: false,
},
"condition-update-with-enabled-feature-gate": {
isExpectedFailure: false,
oldClaim: validClaim,
newClaim: validConditionUpdate,
enableResize: true,
},
}
for name, scenario := range scenarios {
// ensure we have a resource version specified for updates
togglePVExpandFeature(scenario.enableResize, t)
scenario.oldClaim.ResourceVersion = "1"
scenario.newClaim.ResourceVersion = "1"
errs := ValidatePersistentVolumeClaimStatusUpdate(scenario.newClaim, scenario.oldClaim)
if len(errs) == 0 && scenario.isExpectedFailure {
t.Errorf("Unexpected success for scenario: %s", name)
}
if len(errs) > 0 && !scenario.isExpectedFailure {
t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
}
}
}
func TestValidateResourceQuota(t *testing.T) {
spec := api.ResourceQuotaSpec{
Hard: api.ResourceList{