Merge pull request #10074 from nikhiljindal/retryUpdate

Allow update without resource version
This commit is contained in:
Maxwell Forbes
2015-06-24 10:55:41 -07:00
20 changed files with 135 additions and 36 deletions

View File

@@ -97,6 +97,10 @@ func (rcStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field
return append(validationErrorList, updateErrorList...)
}
func (rcStrategy) AllowUnconditionalUpdate() bool {
return true
}
// ControllerToSelectableFields returns a label set that represents the object.
func ControllerToSelectableFields(controller *api.ReplicationController) fields.Set {
return fields.Set{

View File

@@ -73,6 +73,10 @@ func (endpointsStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object
return append(errorList, validation.ValidateEndpointsUpdate(old.(*api.Endpoints), obj.(*api.Endpoints))...)
}
func (endpointsStrategy) AllowUnconditionalUpdate() bool {
return true
}
// MatchEndpoints returns a generic matcher for a given label and field selector.
func MatchEndpoints(label labels.Selector, field fields.Selector) generic.Matcher {
return &generic.SelectionPredicate{label, field, EndpointsAttributes}

View File

@@ -271,6 +271,14 @@ func (e *Etcd) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool
if err != nil {
return nil, false, err
}
// If AllowUnconditionalUpdate() is true and the object specified by the user does not have a resource version,
// then we populate it with the latest version.
// Else, we check that the version specified by the user matches the version of latest etcd object.
resourceVersion, err := e.Helper.Versioner.ObjectResourceVersion(obj)
if err != nil {
return nil, false, err
}
doUnconditionalUpdate := resourceVersion == 0 && e.UpdateStrategy.AllowUnconditionalUpdate()
// TODO: expose TTL
creating := false
out := e.NewFunc()
@@ -295,13 +303,21 @@ func (e *Etcd) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool
}
creating = false
newVersion, err := e.Helper.Versioner.ObjectResourceVersion(obj)
if err != nil {
return nil, nil, err
}
if newVersion != version {
// TODO: return the most recent version to a client?
return nil, nil, kubeerr.NewConflict(e.EndpointName, name, fmt.Errorf("the object has been modified; please apply your changes to the latest version and try again"))
if doUnconditionalUpdate {
// Update the object's resource version to match the latest etcd object's resource version.
err = e.Helper.Versioner.UpdateObject(obj, res.Expiration, res.ResourceVersion)
if err != nil {
return nil, nil, err
}
} else {
// Check if the object's resource version matches the latest resource version.
newVersion, err := e.Helper.Versioner.ObjectResourceVersion(obj)
if err != nil {
return nil, nil, err
}
if newVersion != version {
return nil, nil, kubeerr.NewConflict(e.EndpointName, name, fmt.Errorf("the object has been modified; please apply your changes to the latest version and try again"))
}
}
if err := rest.BeforeUpdate(e.UpdateStrategy, ctx, obj, existing); err != nil {
return nil, nil, err

View File

@@ -37,12 +37,14 @@ import (
type testRESTStrategy struct {
runtime.ObjectTyper
api.NameGenerator
namespaceScoped bool
allowCreateOnUpdate bool
namespaceScoped bool
allowCreateOnUpdate bool
allowUnconditionalUpdate bool
}
func (t *testRESTStrategy) NamespaceScoped() bool { return t.namespaceScoped }
func (t *testRESTStrategy) AllowCreateOnUpdate() bool { return t.allowCreateOnUpdate }
func (t *testRESTStrategy) NamespaceScoped() bool { return t.namespaceScoped }
func (t *testRESTStrategy) AllowCreateOnUpdate() bool { return t.allowCreateOnUpdate }
func (t *testRESTStrategy) AllowUnconditionalUpdate() bool { return t.allowUnconditionalUpdate }
func (t *testRESTStrategy) PrepareForCreate(obj runtime.Object) {}
func (t *testRESTStrategy) PrepareForUpdate(obj, old runtime.Object) {}
@@ -68,7 +70,7 @@ func NewTestGenericEtcdRegistry(t *testing.T) (*tools.FakeEtcdClient, *Etcd) {
f := tools.NewFakeEtcdClient(t)
f.TestIndex = true
h := tools.NewEtcdHelper(f, testapi.Codec(), etcdtest.PathPrefix())
strategy := &testRESTStrategy{api.Scheme, api.SimpleNameGenerator, true, false}
strategy := &testRESTStrategy{api.Scheme, api.SimpleNameGenerator, true, false, true}
podPrefix := "/pods"
return f, &Etcd{
NewFunc: func() runtime.Object { return &api.Pod{} },
@@ -390,7 +392,10 @@ func TestEtcdUpdate(t *testing.T) {
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault, ResourceVersion: "1"},
Spec: api.PodSpec{NodeName: "machine2"},
}
podAWithResourceVersion := &api.Pod{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault, ResourceVersion: "3"},
Spec: api.PodSpec{NodeName: "machine"},
}
nodeWithPodA := tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
@@ -424,18 +429,29 @@ func TestEtcdUpdate(t *testing.T) {
E: nil,
}
nodeWithPodAWithResourceVersion := tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Value: runtime.EncodeOrDie(testapi.Codec(), podAWithResourceVersion),
ModifiedIndex: 3,
CreatedIndex: 1,
},
},
E: nil,
}
emptyNode := tools.EtcdResponseWithError{
R: &etcd.Response{},
E: tools.EtcdErrorNotFound,
}
table := map[string]struct {
existing tools.EtcdResponseWithError
expect tools.EtcdResponseWithError
toUpdate runtime.Object
allowCreate bool
objOK func(obj runtime.Object) bool
errOK func(error) bool
existing tools.EtcdResponseWithError
expect tools.EtcdResponseWithError
toUpdate runtime.Object
allowCreate bool
allowUnconditionalUpdate bool
objOK func(obj runtime.Object) bool
errOK func(error) bool
}{
"normal": {
existing: nodeWithPodA,
@@ -462,11 +478,19 @@ func TestEtcdUpdate(t *testing.T) {
toUpdate: podB,
errOK: func(err error) bool { return errors.IsConflict(err) },
},
"unconditionalUpdate": {
existing: nodeWithPodAWithResourceVersion,
allowUnconditionalUpdate: true,
toUpdate: podA,
objOK: func(obj runtime.Object) bool { return true },
errOK: func(err error) bool { return err == nil },
},
}
for name, item := range table {
fakeClient, registry := NewTestGenericEtcdRegistry(t)
registry.UpdateStrategy.(*testRESTStrategy).allowCreateOnUpdate = item.allowCreate
registry.UpdateStrategy.(*testRESTStrategy).allowUnconditionalUpdate = item.allowUnconditionalUpdate
path := etcdtest.AddPrefix("pods/foo")
fakeClient.Data[path] = item.existing
obj, _, err := registry.Update(api.NewDefaultContext(), item.toUpdate)

View File

@@ -82,6 +82,10 @@ func (nodeStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) fie
return append(errorList, validation.ValidateNodeUpdate(old.(*api.Node), obj.(*api.Node))...)
}
func (nodeStrategy) AllowUnconditionalUpdate() bool {
return true
}
type nodeStatusStrategy struct {
nodeStrategy
}

View File

@@ -93,6 +93,10 @@ func (namespaceStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object
return append(errorList, validation.ValidateNamespaceUpdate(obj.(*api.Namespace), old.(*api.Namespace))...)
}
func (namespaceStrategy) AllowUnconditionalUpdate() bool {
return true
}
type namespaceStatusStrategy struct {
namespaceStrategy
}

View File

@@ -69,6 +69,10 @@ func (persistentvolumeStrategy) ValidateUpdate(ctx api.Context, obj, old runtime
return append(errorList, validation.ValidatePersistentVolumeUpdate(obj.(*api.PersistentVolume), old.(*api.PersistentVolume))...)
}
func (persistentvolumeStrategy) AllowUnconditionalUpdate() bool {
return true
}
type persistentvolumeStatusStrategy struct {
persistentvolumeStrategy
}

View File

@@ -69,6 +69,10 @@ func (persistentvolumeclaimStrategy) ValidateUpdate(ctx api.Context, obj, old ru
return append(errorList, validation.ValidatePersistentVolumeClaimUpdate(obj.(*api.PersistentVolumeClaim), old.(*api.PersistentVolumeClaim))...)
}
func (persistentvolumeclaimStrategy) AllowUnconditionalUpdate() bool {
return true
}
type persistentvolumeclaimStatusStrategy struct {
persistentvolumeclaimStrategy
}

View File

@@ -81,6 +81,10 @@ func (podStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) fiel
return append(errorList, validation.ValidatePodUpdate(obj.(*api.Pod), old.(*api.Pod))...)
}
func (podStrategy) AllowUnconditionalUpdate() bool {
return true
}
// CheckGracefulDelete allows a pod to be gracefully deleted.
func (podStrategy) CheckGracefulDelete(obj runtime.Object, options *api.DeleteOptions) bool {
return false

View File

@@ -69,6 +69,10 @@ func (podTemplateStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Obje
return validation.ValidatePodTemplateUpdate(obj.(*api.PodTemplate), old.(*api.PodTemplate))
}
func (podTemplateStrategy) AllowUnconditionalUpdate() bool {
return true
}
// MatchPodTemplate returns a generic matcher for a given label and field selector.
func MatchPodTemplate(label labels.Selector, field fields.Selector) generic.Matcher {
return generic.MatcherFunc(func(obj runtime.Object) (bool, error) {

View File

@@ -73,6 +73,10 @@ func (resourcequotaStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Ob
return append(errorList, validation.ValidateResourceQuotaUpdate(obj.(*api.ResourceQuota), old.(*api.ResourceQuota))...)
}
func (resourcequotaStrategy) AllowUnconditionalUpdate() bool {
return true
}
type resourcequotaStatusStrategy struct {
resourcequotaStrategy
}

View File

@@ -65,6 +65,10 @@ func (strategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) fielder
return validation.ValidateSecretUpdate(old.(*api.Secret), obj.(*api.Secret))
}
func (strategy) AllowUnconditionalUpdate() bool {
return true
}
// Matcher returns a generic matcher for a given label and field selector.
func Matcher(label labels.Selector, field fields.Selector) generic.Matcher {
return generic.MatcherFunc(func(obj runtime.Object) (bool, error) {

View File

@@ -68,6 +68,10 @@ func (strategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) fielder
return validation.ValidateServiceAccountUpdate(old.(*api.ServiceAccount), obj.(*api.ServiceAccount))
}
func (strategy) AllowUnconditionalUpdate() bool {
return true
}
// Matcher returns a generic matcher for a given label and field selector.
func Matcher(label labels.Selector, field fields.Selector) generic.Matcher {
return generic.MatcherFunc(func(obj runtime.Object) (bool, error) {