mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-14 13:45:06 +00:00
Allow update without resource version
This commit is contained in:
@@ -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{
|
||||
|
@@ -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}
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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) {
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
@@ -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) {
|
||||
|
Reference in New Issue
Block a user