Bi-directional bind between pv.Spec.ClaimRef and pvc.Spec.VolumeName

This commit is contained in:
markturansky
2015-05-12 20:44:29 -04:00
parent 9454d58547
commit 0191574f7e
18 changed files with 247 additions and 295 deletions

View File

@@ -20,7 +20,6 @@ import (
"fmt"
"net"
"path"
"reflect"
"strings"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
@@ -29,7 +28,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
errs "github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume"
"github.com/golang/glog"
)
@@ -447,6 +445,10 @@ func ValidatePersistentVolume(pv *api.PersistentVolume) errs.ValidationErrorList
allErrs := errs.ValidationErrorList{}
allErrs = append(allErrs, ValidateObjectMeta(&pv.ObjectMeta, false, ValidatePersistentVolumeName).Prefix("metadata")...)
if len(pv.Spec.AccessModes) == 0 {
allErrs = append(allErrs, errs.NewFieldRequired("persistentVolume.AccessModes"))
}
if len(pv.Spec.Capacity) == 0 {
allErrs = append(allErrs, errs.NewFieldRequired("persistentVolume.Capacity"))
}
@@ -455,6 +457,10 @@ func ValidatePersistentVolume(pv *api.PersistentVolume) errs.ValidationErrorList
allErrs = append(allErrs, errs.NewFieldInvalid("", pv.Spec.Capacity, fmt.Sprintf("only %s is expected", api.ResourceStorage)))
}
for _, qty := range pv.Spec.Capacity {
allErrs = append(allErrs, validateBasicResource(qty)...)
}
numVolumes := 0
if pv.Spec.HostPath != nil {
numVolumes++
@@ -517,16 +523,6 @@ func ValidatePersistentVolumeClaim(pvc *api.PersistentVolumeClaim) errs.Validati
func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *api.PersistentVolumeClaim) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
allErrs = ValidatePersistentVolumeClaim(newPvc)
if oldPvc.Status.VolumeRef != nil {
oldModesAsString := volume.GetAccessModesAsString(oldPvc.Spec.AccessModes)
newModesAsString := volume.GetAccessModesAsString(newPvc.Spec.AccessModes)
if oldModesAsString != newModesAsString {
allErrs = append(allErrs, errs.NewFieldInvalid("spec.AccessModes", oldPvc.Spec.AccessModes, "field is immutable"))
}
if !reflect.DeepEqual(oldPvc.Spec.Resources, newPvc.Spec.Resources) {
allErrs = append(allErrs, errs.NewFieldInvalid("spec.Resources", oldPvc.Spec.Resources, "field is immutable"))
}
}
newPvc.Status = oldPvc.Status
return allErrs
}
@@ -537,6 +533,12 @@ func ValidatePersistentVolumeClaimStatusUpdate(newPvc, oldPvc *api.PersistentVol
if newPvc.ResourceVersion == "" {
allErrs = append(allErrs, errs.NewFieldRequired("resourceVersion"))
}
if len(newPvc.Spec.AccessModes) == 0 {
allErrs = append(allErrs, errs.NewFieldRequired("persistentVolume.AccessModes"))
}
for _, qty := range newPvc.Status.Capacity {
allErrs = append(allErrs, validateBasicResource(qty)...)
}
newPvc.Spec = oldPvc.Spec
return allErrs
}

View File

@@ -228,6 +228,7 @@ func TestValidatePersistentVolumes(t *testing.T) {
Capacity: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
AccessModes: []api.AccessModeType{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{Path: "/foo"},
},
@@ -239,6 +240,7 @@ func TestValidatePersistentVolumes(t *testing.T) {
Capacity: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
AccessModes: []api.AccessModeType{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{Path: "/foo"},
},
@@ -250,11 +252,11 @@ func TestValidatePersistentVolumes(t *testing.T) {
Capacity: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
AccessModes: []api.AccessModeType{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{Path: "/foo"},
},
},
),
}),
},
"missing-name": {
isExpectedFailure: true,
@@ -262,12 +264,24 @@ func TestValidatePersistentVolumes(t *testing.T) {
Capacity: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
AccessModes: []api.AccessModeType{api.ReadWriteOnce},
}),
},
"missing-capacity": {
isExpectedFailure: true,
volume: testVolume("foo", "", api.PersistentVolumeSpec{}),
},
"missing-accessmodes": {
isExpectedFailure: true,
volume: testVolume("goodname", "missing-accessmodes", api.PersistentVolumeSpec{
Capacity: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{Path: "/foo"},
},
}),
},
"too-many-sources": {
isExpectedFailure: true,
volume: testVolume("", "", api.PersistentVolumeSpec{
@@ -379,136 +393,6 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
}
}
func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
pvcA := &api.PersistentVolumeClaim{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: "ns",
Labels: map[string]string{
"nice-label": "fizzbuzz",
},
},
Spec: api.PersistentVolumeClaimSpec{
AccessModes: []api.AccessModeType{
api.ReadWriteOnce,
},
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
},
},
}
// AccessModes differ from pvcA
pvcB := &api.PersistentVolumeClaim{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: "ns",
Labels: map[string]string{
"nice-label": "fizzbuzz",
},
},
Spec: api.PersistentVolumeClaimSpec{
AccessModes: []api.AccessModeType{
api.ReadWriteMany,
},
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
},
},
}
// Resources differ from pvcA
pvcC := &api.PersistentVolumeClaim{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: "ns",
Labels: map[string]string{
"nice-label": "fizzbuzz",
},
},
Spec: api.PersistentVolumeClaimSpec{
AccessModes: []api.AccessModeType{
api.ReadWriteOnce,
},
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("7G"),
},
},
},
}
// Labels differ from pvcA
pvcD := &api.PersistentVolumeClaim{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: "ns",
Labels: map[string]string{
"nice-label": "buzzfizz",
},
},
Spec: api.PersistentVolumeClaimSpec{
AccessModes: []api.AccessModeType{
api.ReadWriteOnce,
},
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
},
},
}
scenarios := map[string]struct {
isExpectedFailure bool
oldClaim *api.PersistentVolumeClaim
newClaim *api.PersistentVolumeClaim
}{
"invalid-accessmodes-change": {
isExpectedFailure: true,
oldClaim: pvcA,
newClaim: pvcB,
},
"invalid-resources-change": {
isExpectedFailure: true,
oldClaim: pvcA,
newClaim: pvcC,
},
"valid-label-change": {
isExpectedFailure: false,
oldClaim: pvcA,
newClaim: pvcD,
},
}
// validation errors on Update only occur if the Claim is already bound.
// failures are only expected after binding
for name, scenario := range scenarios {
errs := ValidatePersistentVolumeClaimUpdate(scenario.newClaim, scenario.oldClaim)
if len(errs) > 0 && !scenario.isExpectedFailure {
t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
}
}
// 2 of 3 scenarios above are invalid if the old PVC has a bound PV
for name, scenario := range scenarios {
scenario.oldClaim.Status.VolumeRef = &api.ObjectReference{Name: "foo", Namespace: "ns"}
errs := ValidatePersistentVolumeClaimUpdate(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 TestValidateVolumes(t *testing.T) {
successCase := []api.Volume{
{Name: "abc", VolumeSource: api.VolumeSource{HostPath: &api.HostPathVolumeSource{"/mnt/path1"}}},