From 31b425b3a189fc4ceee377038ff4337a6dca481c Mon Sep 17 00:00:00 2001 From: Chao Xu Date: Sun, 20 Mar 2016 23:15:00 -0700 Subject: [PATCH] add delete precondition --- api/swagger-spec/autoscaling_v1.json | 21 +- api/swagger-spec/batch_v1.json | 21 +- api/swagger-spec/extensions_v1beta1.json | 21 +- api/swagger-spec/v1.json | 21 +- cmd/genconversion/conversion.go | 1 + cmd/libs/go2idl/.import-restrictions | 3 +- cmd/libs/go2idl/generator/execute.go | 2 +- cmd/libs/go2idl/import-boss/main.go | 1 - .../autoscaling/v1/definitions.html | 49 ++- docs/api-reference/batch/v1/definitions.html | 49 ++- .../extensions/v1beta1/definitions.html | 227 ++++++---- docs/api-reference/v1/definitions.html | 139 ++++--- pkg/api/deep_copy_generated.go | 26 ++ pkg/api/errors/errors.go | 2 +- pkg/api/errors/storage/storage.go | 18 + pkg/api/helpers.go | 14 + pkg/api/rest/delete.go | 22 +- pkg/api/rest/resttest/resttest.go | 59 +++ pkg/api/types.generated.go | 390 +++++++++++++++--- pkg/api/types.go | 12 +- pkg/api/unversioned/types.go | 8 +- pkg/api/v1/conversion_generated.go | 55 +++ pkg/api/v1/deep_copy_generated.go | 26 ++ pkg/api/v1/types.generated.go | 390 +++++++++++++++--- pkg/api/v1/types.go | 12 +- pkg/api/v1/types_swagger_doc_generated.go | 10 + pkg/apiserver/errors_test.go | 2 +- pkg/apiserver/resthandler_test.go | 2 +- pkg/registry/configmap/etcd/etcd.go | 1 + pkg/registry/controller/etcd/etcd.go | 1 + pkg/registry/controller/etcd/etcd_test.go | 5 - pkg/registry/daemonset/etcd/etcd.go | 1 + pkg/registry/daemonset/etcd/etcd_test.go | 5 - pkg/registry/deployment/etcd/etcd.go | 3 +- pkg/registry/deployment/etcd/etcd_test.go | 5 - pkg/registry/endpoint/etcd/etcd.go | 1 + pkg/registry/event/etcd/etcd.go | 1 + pkg/registry/generic/etcd/etcd.go | 20 +- pkg/registry/generic/etcd/etcd_test.go | 3 +- .../horizontalpodautoscaler/etcd/etcd.go | 1 + pkg/registry/ingress/etcd/etcd.go | 1 + pkg/registry/ingress/etcd/etcd_test.go | 6 - pkg/registry/job/etcd/etcd.go | 1 + pkg/registry/limitrange/etcd/etcd.go | 1 + pkg/registry/namespace/etcd/etcd.go | 1 + pkg/registry/node/etcd/etcd.go | 1 + pkg/registry/persistentvolume/etcd/etcd.go | 1 + .../persistentvolumeclaim/etcd/etcd.go | 1 + pkg/registry/pod/etcd/etcd.go | 2 +- pkg/registry/pod/etcd/etcd_test.go | 2 +- pkg/registry/podsecuritypolicy/etcd/etcd.go | 1 + pkg/registry/podtemplate/etcd/etcd.go | 1 + pkg/registry/replicaset/etcd/etcd.go | 1 + pkg/registry/replicaset/etcd/etcd_test.go | 5 - pkg/registry/resourcequota/etcd/etcd.go | 1 + pkg/registry/secret/etcd/etcd.go | 1 + pkg/registry/service/allocator/etcd/etcd.go | 4 +- pkg/registry/service/etcd/etcd.go | 1 + pkg/registry/serviceaccount/etcd/etcd.go | 1 + pkg/registry/thirdpartyresource/etcd/etcd.go | 1 + .../thirdpartyresourcedata/etcd/etcd.go | 1 + pkg/storage/cacher.go | 8 +- pkg/storage/cacher_test.go | 4 +- pkg/storage/errors.go | 50 ++- pkg/storage/etcd/etcd_helper.go | 79 +++- pkg/storage/etcd/etcd_helper_test.go | 110 ++++- pkg/storage/etcd/etcd_watcher.go | 3 +- pkg/storage/interfaces.go | 17 +- pkg/storage/util.go | 4 +- pkg/storage/util_test.go | 8 +- test/integration/etcd_tools_test.go | 4 +- 71 files changed, 1600 insertions(+), 372 deletions(-) mode change 100755 => 100644 pkg/registry/daemonset/etcd/etcd_test.go mode change 100755 => 100644 pkg/registry/ingress/etcd/etcd_test.go diff --git a/api/swagger-spec/autoscaling_v1.json b/api/swagger-spec/autoscaling_v1.json index 9c78c1a8976..f66a5d8b96e 100644 --- a/api/swagger-spec/autoscaling_v1.json +++ b/api/swagger-spec/autoscaling_v1.json @@ -1169,9 +1169,6 @@ "v1.DeleteOptions": { "id": "v1.DeleteOptions", "description": "DeleteOptions may be provided when deleting an API object", - "required": [ - "gracePeriodSeconds" - ], "properties": { "kind": { "type": "string", @@ -1185,9 +1182,27 @@ "type": "integer", "format": "int64", "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately." + }, + "preconditions": { + "$ref": "v1.Preconditions", + "description": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned." } } }, + "v1.Preconditions": { + "id": "v1.Preconditions", + "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", + "properties": { + "uid": { + "$ref": "types.UID", + "description": "Specifies the target UID." + } + } + }, + "types.UID": { + "id": "types.UID", + "properties": {} + }, "unversioned.APIResourceList": { "id": "unversioned.APIResourceList", "description": "APIResourceList is a list of APIResource, it is used to expose the name of the resources supported in a specific group and version, and if the resource is namespaced.", diff --git a/api/swagger-spec/batch_v1.json b/api/swagger-spec/batch_v1.json index 23b8a391ade..43e4df182b3 100644 --- a/api/swagger-spec/batch_v1.json +++ b/api/swagger-spec/batch_v1.json @@ -2410,9 +2410,6 @@ "v1.DeleteOptions": { "id": "v1.DeleteOptions", "description": "DeleteOptions may be provided when deleting an API object", - "required": [ - "gracePeriodSeconds" - ], "properties": { "kind": { "type": "string", @@ -2426,9 +2423,27 @@ "type": "integer", "format": "int64", "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately." + }, + "preconditions": { + "$ref": "v1.Preconditions", + "description": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned." } } }, + "v1.Preconditions": { + "id": "v1.Preconditions", + "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", + "properties": { + "uid": { + "$ref": "types.UID", + "description": "Specifies the target UID." + } + } + }, + "types.UID": { + "id": "types.UID", + "properties": {} + }, "unversioned.APIResourceList": { "id": "unversioned.APIResourceList", "description": "APIResourceList is a list of APIResource, it is used to expose the name of the resources supported in a specific group and version, and if the resource is namespaced.", diff --git a/api/swagger-spec/extensions_v1beta1.json b/api/swagger-spec/extensions_v1beta1.json index 45e532ae6a2..35a6a1b0e39 100644 --- a/api/swagger-spec/extensions_v1beta1.json +++ b/api/swagger-spec/extensions_v1beta1.json @@ -7783,9 +7783,6 @@ "v1.DeleteOptions": { "id": "v1.DeleteOptions", "description": "DeleteOptions may be provided when deleting an API object", - "required": [ - "gracePeriodSeconds" - ], "properties": { "kind": { "type": "string", @@ -7799,9 +7796,27 @@ "type": "integer", "format": "int64", "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately." + }, + "preconditions": { + "$ref": "v1.Preconditions", + "description": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned." } } }, + "v1.Preconditions": { + "id": "v1.Preconditions", + "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", + "properties": { + "uid": { + "$ref": "types.UID", + "description": "Specifies the target UID." + } + } + }, + "types.UID": { + "id": "types.UID", + "properties": {} + }, "v1beta1.DeploymentList": { "id": "v1beta1.DeploymentList", "description": "DeploymentList is a list of Deployments.", diff --git a/api/swagger-spec/v1.json b/api/swagger-spec/v1.json index 806dd57199b..0b99b992750 100644 --- a/api/swagger-spec/v1.json +++ b/api/swagger-spec/v1.json @@ -15118,9 +15118,6 @@ "v1.DeleteOptions": { "id": "v1.DeleteOptions", "description": "DeleteOptions may be provided when deleting an API object", - "required": [ - "gracePeriodSeconds" - ], "properties": { "kind": { "type": "string", @@ -15134,9 +15131,27 @@ "type": "integer", "format": "int64", "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately." + }, + "preconditions": { + "$ref": "v1.Preconditions", + "description": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned." } } }, + "v1.Preconditions": { + "id": "v1.Preconditions", + "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", + "properties": { + "uid": { + "$ref": "types.UID", + "description": "Specifies the target UID." + } + } + }, + "types.UID": { + "id": "types.UID", + "properties": {} + }, "v1.EndpointsList": { "id": "v1.EndpointsList", "description": "EndpointsList is a list of endpoints.", diff --git a/cmd/genconversion/conversion.go b/cmd/genconversion/conversion.go index a9445bc7f22..dcaa66d80d1 100644 --- a/cmd/genconversion/conversion.go +++ b/cmd/genconversion/conversion.go @@ -97,6 +97,7 @@ func main() { generator := kruntime.NewConversionGenerator(api.Scheme, versionPath) apiShort := generator.AddImport(path.Join(pkgBase, "api")) generator.AddImport(path.Join(pkgBase, "api/resource")) + generator.AddImport(path.Join(pkgBase, "types")) // TODO(wojtek-t): Change the overwrites to a flag. generator.OverwritePackage(gv.Version, "") for _, knownType := range api.Scheme.KnownTypes(gv) { diff --git a/cmd/libs/go2idl/.import-restrictions b/cmd/libs/go2idl/.import-restrictions index 937fec69fe8..371604f6de7 100644 --- a/cmd/libs/go2idl/.import-restrictions +++ b/cmd/libs/go2idl/.import-restrictions @@ -4,7 +4,8 @@ "SelectorRegexp": "k8s[.]io", "AllowedPrefixes": [ "k8s.io/kubernetes/cmd/libs/go2idl", - "k8s.io/kubernetes/third_party" + "k8s.io/kubernetes/third_party", + "k8s.io/kubernetes/pkg/util/sets" ] } ] diff --git a/cmd/libs/go2idl/generator/execute.go b/cmd/libs/go2idl/generator/execute.go index bb9bbb440f2..3c03221c4e3 100644 --- a/cmd/libs/go2idl/generator/execute.go +++ b/cmd/libs/go2idl/generator/execute.go @@ -286,7 +286,7 @@ func (c *Context) ExecutePackage(outDir string, p Package) error { } } if len(errors) > 0 { - return fmt.Errorf("errors in package %q:\n%v\n", p.Name(), strings.Join(errs2strings(errors), "\n")) + return fmt.Errorf("errors in package %q:\n%v\n", p.Path(), strings.Join(errs2strings(errors), "\n")) } return nil } diff --git a/cmd/libs/go2idl/import-boss/main.go b/cmd/libs/go2idl/import-boss/main.go index 7d10171ebc6..5efc2d9bdb2 100644 --- a/cmd/libs/go2idl/import-boss/main.go +++ b/cmd/libs/go2idl/import-boss/main.go @@ -74,7 +74,6 @@ func main() { "k8s.io/kubernetes/cmd/", "k8s.io/kubernetes/plugin/", } - arguments.OutputBase = "" arguments.Recursive = true // arguments.VerifyOnly = true diff --git a/docs/api-reference/autoscaling/v1/definitions.html b/docs/api-reference/autoscaling/v1/definitions.html index 5eb07515795..c172d092c22 100755 --- a/docs/api-reference/autoscaling/v1/definitions.html +++ b/docs/api-reference/autoscaling/v1/definitions.html @@ -428,10 +428,17 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

gracePeriodSeconds

The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.

-

true

+

false

integer (int64)

+ +

preconditions

+

Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.

+

false

+

v1.Preconditions

+ + @@ -593,6 +600,40 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } + +
+

v1.Preconditions

+
+

Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.

+
+ +++++++ + + + + + + + + + + + + + + + + + + +
NameDescriptionRequiredSchemaDefault

uid

Specifies the target UID.

false

types.UID

+

v1.CrossVersionObjectReference

@@ -1131,6 +1172,10 @@ Populated by the system when a graceful deletion is requested. Read-only. More i +
+
+

types.UID

+

unversioned.StatusCause

@@ -1233,7 +1278,7 @@ Examples:
diff --git a/docs/api-reference/batch/v1/definitions.html b/docs/api-reference/batch/v1/definitions.html index 9236484c153..2b7670f1d83 100755 --- a/docs/api-reference/batch/v1/definitions.html +++ b/docs/api-reference/batch/v1/definitions.html @@ -383,6 +383,40 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

Definitions

+

v1.Preconditions

+
+

Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.

+
+ +++++++ + + + + + + + + + + + + + + + + + + +
NameDescriptionRequiredSchemaDefault

uid

Specifies the target UID.

false

types.UID

+ +
+

v1.SELinuxOptions

SELinuxOptions are the labels to be applied to the container

@@ -1693,6 +1727,10 @@ Populated by the system when a graceful deletion is requested. Read-only. More i +
+
+

types.UID

+

v1.HostPathVolumeSource

@@ -2875,10 +2913,17 @@ Populated by the system when a graceful deletion is requested. Read-only. More i

gracePeriodSeconds

The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.

-

true

+

false

integer (int64)

+ +

preconditions

+

Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.

+

false

+

v1.Preconditions

+ + @@ -3875,7 +3920,7 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
diff --git a/docs/api-reference/extensions/v1beta1/definitions.html b/docs/api-reference/extensions/v1beta1/definitions.html index bdc10fe97d8..70a8c73e04a 100755 --- a/docs/api-reference/extensions/v1beta1/definitions.html +++ b/docs/api-reference/extensions/v1beta1/definitions.html @@ -595,6 +595,40 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } +
+
+

v1.Preconditions

+
+

Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.

+
+ +++++++ + + + + + + + + + + + + + + + + + + +
NameDescriptionRequiredSchemaDefault

uid

Specifies the target UID.

false

types.UID

+

v1.ObjectFieldSelector

@@ -828,6 +862,61 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } +
+
+

v1beta1.ReplicaSetList

+
+

ReplicaSetList is a collection of ReplicaSets.

+
+ +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescriptionRequiredSchemaDefault

kind

Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds

false

string

apiVersion

APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources

false

string

metadata

Standard list metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds

false

unversioned.ListMeta

items

List of ReplicaSets. More info: http://releases.k8s.io/HEAD/docs/user-guide/replication-controller.md

true

v1beta1.ReplicaSet array

+

v1.CephFSVolumeSource

@@ -897,61 +986,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } -
-
-

v1beta1.ReplicaSetList

-
-

ReplicaSetList is a collection of ReplicaSets.

-
- ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameDescriptionRequiredSchemaDefault

kind

Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds

false

string

apiVersion

APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources

false

string

metadata

Standard list metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds

false

unversioned.ListMeta

items

List of ReplicaSets. More info: http://releases.k8s.io/HEAD/docs/user-guide/replication-controller.md

true

v1beta1.ReplicaSet array

-

v1beta1.IngressStatus

@@ -1799,6 +1833,10 @@ Populated by the system when a graceful deletion is requested. Read-only. More i +
+
+

types.UID

+

v1.ISCSIVolumeSource

@@ -2579,10 +2617,17 @@ Populated by the system when a graceful deletion is requested. Read-only. More i

gracePeriodSeconds

The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.

-

true

+

false

integer (int64)

+ +

preconditions

+

Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.

+

false

+

v1.Preconditions

+ + @@ -4843,40 +4888,6 @@ Both these may change in the future. Incoming requests are matched against the h -
-
-

v1.HostPathVolumeSource

-
-

Represents a host path mapped into a pod. Host path volumes do not support ownership management or SELinux relabeling.

-
- ------- - - - - - - - - - - - - - - - - - - -
NameDescriptionRequiredSchemaDefault

path

Path of the directory on the host. More info: http://releases.k8s.io/HEAD/docs/user-guide/volumes.md#hostpath

true

string

-

v1beta1.ReplicaSet

@@ -4939,6 +4950,40 @@ Both these may change in the future. Incoming requests are matched against the h +
+
+

v1.HostPathVolumeSource

+
+

Represents a host path mapped into a pod. Host path volumes do not support ownership management or SELinux relabeling.

+
+ +++++++ + + + + + + + + + + + + + + + + + + +
NameDescriptionRequiredSchemaDefault

path

Path of the directory on the host. More info: http://releases.k8s.io/HEAD/docs/user-guide/volumes.md#hostpath

true

string

+

v1beta1.DaemonSet

@@ -5865,7 +5910,7 @@ Both these may change in the future. Incoming requests are matched against the h
diff --git a/docs/api-reference/v1/definitions.html b/docs/api-reference/v1/definitions.html index 3e1ece13bd5..a5a09ff33a9 100755 --- a/docs/api-reference/v1/definitions.html +++ b/docs/api-reference/v1/definitions.html @@ -594,6 +594,40 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } +
+
+

v1.Preconditions

+
+

Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.

+
+ +++++++ + + + + + + + + + + + + + + + + + + +
NameDescriptionRequiredSchemaDefault

uid

Specifies the target UID.

false

types.UID

+

v1.SELinuxOptions

@@ -1495,6 +1529,47 @@ Examples:
+
+
+

v1.Capabilities

+
+

Adds and removes POSIX capabilities from running containers.

+
+ +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescriptionRequiredSchemaDefault

add

Added capabilities

false

v1.Capability array

drop

Removed capabilities

false

v1.Capability array

+

v1.ConfigMap

@@ -1550,47 +1625,6 @@ Examples:
-
-
-

v1.Capabilities

-
-

Adds and removes POSIX capabilities from running containers.

-
- ------- - - - - - - - - - - - - - - - - - - - - - - - - - -
NameDescriptionRequiredSchemaDefault

add

Added capabilities

false

v1.Capability array

drop

Removed capabilities

false

v1.Capability array

-

v1.PodTemplateList

@@ -1983,6 +2017,10 @@ Populated by the system when a graceful deletion is requested. Read-only. More i +
+
+

types.UID

+

v1.AzureFileVolumeSource

@@ -3321,10 +3359,17 @@ The resulting set of endpoints can be viewed as:

gracePeriodSeconds

The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.

-

true

+

false

integer (int64)

+ +

preconditions

+

Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.

+

false

+

v1.Preconditions

+ + @@ -6749,6 +6794,10 @@ The resulting set of endpoints can be viewed as:
+
+
+

v1.PersistentVolumeAccessMode

+

v1.ResourceRequirements

@@ -6790,10 +6839,6 @@ The resulting set of endpoints can be viewed as:
-
-
-

v1.PersistentVolumeAccessMode

-

v1.ComponentStatus

@@ -7735,7 +7780,7 @@ The resulting set of endpoints can be viewed as:
diff --git a/pkg/api/deep_copy_generated.go b/pkg/api/deep_copy_generated.go index a40873a39c7..0458e6bb8d9 100644 --- a/pkg/api/deep_copy_generated.go +++ b/pkg/api/deep_copy_generated.go @@ -27,6 +27,7 @@ import ( fields "k8s.io/kubernetes/pkg/fields" labels "k8s.io/kubernetes/pkg/labels" runtime "k8s.io/kubernetes/pkg/runtime" + types "k8s.io/kubernetes/pkg/types" intstr "k8s.io/kubernetes/pkg/util/intstr" ) @@ -140,6 +141,7 @@ func init() { DeepCopy_api_PodTemplate, DeepCopy_api_PodTemplateList, DeepCopy_api_PodTemplateSpec, + DeepCopy_api_Preconditions, DeepCopy_api_PreferredSchedulingTerm, DeepCopy_api_Probe, DeepCopy_api_RBDVolumeSource, @@ -617,6 +619,15 @@ func DeepCopy_api_DeleteOptions(in DeleteOptions, out *DeleteOptions, c *convers } else { out.GracePeriodSeconds = nil } + if in.Preconditions != nil { + in, out := in.Preconditions, &out.Preconditions + *out = new(Preconditions) + if err := DeepCopy_api_Preconditions(*in, *out, c); err != nil { + return err + } + } else { + out.Preconditions = nil + } return nil } @@ -2247,6 +2258,21 @@ func DeepCopy_api_PodTemplateSpec(in PodTemplateSpec, out *PodTemplateSpec, c *c return nil } +func DeepCopy_api_Preconditions(in Preconditions, out *Preconditions, c *conversion.Cloner) error { + if in.UID != nil { + in, out := in.UID, &out.UID + *out = new(types.UID) + if newVal, err := c.DeepCopy(*in); err != nil { + return err + } else { + **out = newVal.(types.UID) + } + } else { + out.UID = nil + } + return nil +} + func DeepCopy_api_PreferredSchedulingTerm(in PreferredSchedulingTerm, out *PreferredSchedulingTerm, c *conversion.Cloner) error { out.Weight = in.Weight if err := DeepCopy_api_NodeSelectorTerm(in.Preference, &out.Preference, c); err != nil { diff --git a/pkg/api/errors/errors.go b/pkg/api/errors/errors.go index a78b7bc240e..345ad0e0435 100644 --- a/pkg/api/errors/errors.go +++ b/pkg/api/errors/errors.go @@ -163,7 +163,7 @@ func NewConflict(qualifiedResource unversioned.GroupResource, name string, err e Kind: qualifiedResource.Resource, Name: name, }, - Message: fmt.Sprintf("%s %q cannot be updated: %v", qualifiedResource.String(), name, err), + Message: fmt.Sprintf("Operation cannot be fulfilled on %s %q: %v", qualifiedResource.String(), name, err), }} } diff --git a/pkg/api/errors/storage/storage.go b/pkg/api/errors/storage/storage.go index ebb23e91c39..d257cc131a0 100644 --- a/pkg/api/errors/storage/storage.go +++ b/pkg/api/errors/storage/storage.go @@ -71,6 +71,8 @@ func InterpretUpdateError(err error, qualifiedResource unversioned.GroupResource return errors.NewServerTimeout(qualifiedResource, "update", 2) // TODO: make configurable or handled at a higher level case storage.IsNotFound(err): return errors.NewNotFound(qualifiedResource, name) + case storage.IsInternalError(err): + return errors.NewInternalError(err) default: return err } @@ -84,6 +86,22 @@ func InterpretDeleteError(err error, qualifiedResource unversioned.GroupResource return errors.NewNotFound(qualifiedResource, name) case storage.IsUnreachable(err): return errors.NewServerTimeout(qualifiedResource, "delete", 2) // TODO: make configurable or handled at a higher level + case storage.IsTestFailed(err), storage.IsNodeExist(err): + return errors.NewConflict(qualifiedResource, name, err) + case storage.IsInternalError(err): + return errors.NewInternalError(err) + default: + return err + } +} + +// InterpretWatchError converts a generic error on a watch +// operation into the appropriate API error. +func InterpretWatchError(err error, resource unversioned.GroupResource, name string) error { + switch { + case storage.IsInvalidError(err): + invalidError, _ := err.(storage.InvalidError) + return errors.NewInvalid(unversioned.GroupKind{resource.Group, resource.Resource}, name, invalidError.Errs) default: return err } diff --git a/pkg/api/helpers.go b/pkg/api/helpers.go index aed3c5a7aa3..21e5ab420ba 100644 --- a/pkg/api/helpers.go +++ b/pkg/api/helpers.go @@ -30,6 +30,7 @@ import ( "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/types" "k8s.io/kubernetes/pkg/util/sets" "github.com/davecgh/go-spew/spew" @@ -204,6 +205,19 @@ func NewDeleteOptions(grace int64) *DeleteOptions { return &DeleteOptions{GracePeriodSeconds: &grace} } +// NewPreconditionDeleteOptions returns a DeleteOptions with a UID precondition set. +func NewPreconditionDeleteOptions(uid string) *DeleteOptions { + u := types.UID(uid) + p := Preconditions{UID: &u} + return &DeleteOptions{Preconditions: &p} +} + +// NewUIDPreconditions returns a Preconditions with UID set. +func NewUIDPreconditions(uid string) *Preconditions { + u := types.UID(uid) + return &Preconditions{UID: &u} +} + // this function aims to check if the service's ClusterIP is set or not // the objective is not to perform validation here func IsServiceIPSet(service *Service) bool { diff --git a/pkg/api/rest/delete.go b/pkg/api/rest/delete.go index c05f3446db3..b4377d64643 100644 --- a/pkg/api/rest/delete.go +++ b/pkg/api/rest/delete.go @@ -17,9 +17,11 @@ limitations under the License. package rest import ( + "fmt" "time" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" ) @@ -28,7 +30,11 @@ import ( // API conventions. type RESTDeleteStrategy interface { runtime.ObjectTyper +} +// RESTGracefulDeleteStrategy must be implemented by the registry that supports +// graceful deletion. +type RESTGracefulDeleteStrategy interface { // CheckGracefulDelete should return true if the object can be gracefully deleted and set // any default values on the DeleteOptions. CheckGracefulDelete(obj runtime.Object, options *api.DeleteOptions) bool @@ -40,14 +46,18 @@ type RESTDeleteStrategy interface { // condition cannot be checked or the gracePeriodSeconds is invalid. The options argument may be updated with // default values if graceful is true. func BeforeDelete(strategy RESTDeleteStrategy, ctx api.Context, obj runtime.Object, options *api.DeleteOptions) (graceful, gracefulPending bool, err error) { - if strategy == nil { - return false, false, nil - } - objectMeta, _, kerr := objectMetaAndKind(strategy, obj) + objectMeta, gvk, kerr := objectMetaAndKind(strategy, obj) if kerr != nil { return false, false, kerr } - + // Checking the Preconditions here to fail early. They'll be enforced later on when we actually do the deletion, too. + if options.Preconditions != nil && options.Preconditions.UID != nil && *options.Preconditions.UID != objectMeta.UID { + return false, false, errors.NewConflict(unversioned.GroupResource{gvk.Group, gvk.Kind}, objectMeta.Name, fmt.Errorf("the UID in the precondition (%s) does not match the UID in record (%s). The object might have been deleted and then recreated", *options.Preconditions.UID, objectMeta.UID)) + } + gracefulStrategy, ok := strategy.(RESTGracefulDeleteStrategy) + if !ok { + return false, false, nil + } // if the object is already being deleted if objectMeta.DeletionTimestamp != nil { // if we are already being deleted, we may only shorten the deletion grace period @@ -73,7 +83,7 @@ func BeforeDelete(strategy RESTDeleteStrategy, ctx api.Context, obj runtime.Obje return false, true, nil } - if !strategy.CheckGracefulDelete(obj, options) { + if !gracefulStrategy.CheckGracefulDelete(obj, options) { return false, false, nil } now := unversioned.NewTime(unversioned.Now().Add(time.Second * time.Duration(*options.GracePeriodSeconds))) diff --git a/pkg/api/rest/resttest/resttest.go b/pkg/api/rest/resttest/resttest.go index c7005b73db4..de1b6c6893e 100644 --- a/pkg/api/rest/resttest/resttest.go +++ b/pkg/api/rest/resttest/resttest.go @@ -32,6 +32,7 @@ import ( "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/types" "k8s.io/kubernetes/pkg/util/wait" ) @@ -154,12 +155,14 @@ func (t *Tester) TestUpdate(valid runtime.Object, setFn SetFunc, getFn GetFunc, t.testUpdateRejectsMismatchedNamespace(copyOrDie(valid), setFn) } t.testUpdateInvokesValidation(copyOrDie(valid), setFn, invalidUpdateFn...) + t.testUpdateWithWrongUID(copyOrDie(valid), setFn, getFn) } // Test deleting an object. func (t *Tester) TestDelete(valid runtime.Object, setFn SetFunc, getFn GetFunc, isNotFoundFn IsErrorFunc) { t.testDeleteNonExist(copyOrDie(valid)) t.testDeleteNoGraceful(copyOrDie(valid), setFn, getFn, isNotFoundFn) + t.testDeleteWithUID(copyOrDie(valid), setFn, getFn, isNotFoundFn) } // Test gracefully deleting an object. @@ -474,6 +477,26 @@ func (t *Tester) testUpdateInvokesValidation(obj runtime.Object, setFn SetFunc, } } +func (t *Tester) testUpdateWithWrongUID(obj runtime.Object, setFn SetFunc, getFn GetFunc) { + ctx := t.TestContext() + foo := copyOrDie(obj) + t.setObjectMeta(foo, "foo5") + objectMeta := t.getObjectMetaOrFail(foo) + objectMeta.UID = types.UID("UID0000") + if err := setFn(ctx, foo); err != nil { + t.Errorf("unexpected error: %v", err) + } + objectMeta.UID = types.UID("UID1111") + + obj, created, err := t.storage.(rest.Updater).Update(ctx, foo) + if created || obj != nil { + t.Errorf("expected nil object and no creation for object: %v", foo) + } + if err == nil || !errors.IsConflict(err) { + t.Errorf("unexpected error: %v", err) + } +} + func (t *Tester) testUpdateOnNotFound(obj runtime.Object) { t.setObjectMeta(obj, "foo") _, created, err := t.storage.(rest.Updater).Update(t.TestContext(), obj) @@ -557,6 +580,42 @@ func (t *Tester) testDeleteNonExist(obj runtime.Object) { } +// This test the fast-fail path. We test that the precondition gets verified +// again before deleting the object in tests of pkg/storage/etcd. +func (t *Tester) testDeleteWithUID(obj runtime.Object, setFn SetFunc, getFn GetFunc, isNotFoundFn IsErrorFunc) { + ctx := t.TestContext() + + foo := copyOrDie(obj) + t.setObjectMeta(foo, "foo1") + objectMeta := t.getObjectMetaOrFail(foo) + objectMeta.UID = types.UID("UID0000") + if err := setFn(ctx, foo); err != nil { + t.Errorf("unexpected error: %v", err) + } + obj, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, api.NewPreconditionDeleteOptions("UID1111")) + if err == nil || !errors.IsConflict(err) { + t.Errorf("unexpected error: %v", err) + } + + obj, err = t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, api.NewPreconditionDeleteOptions("UID0000")) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if !t.returnDeletedObject { + if status, ok := obj.(*unversioned.Status); !ok { + t.Errorf("expected status of delete, got %v", status) + } else if status.Status != unversioned.StatusSuccess { + t.Errorf("expected success, got: %v", status.Status) + } + } + + _, err = getFn(ctx, foo) + if err == nil || !isNotFoundFn(err) { + t.Errorf("unexpected error: %v", err) + } +} + // ============================================================================= // Graceful Deletion tests. diff --git a/pkg/api/types.generated.go b/pkg/api/types.generated.go index b4ccb56ccd6..c599335ca4d 100644 --- a/pkg/api/types.generated.go +++ b/pkg/api/types.generated.go @@ -37015,6 +37015,209 @@ func (x *Binding) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } +func (x *Preconditions) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [1]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = x.UID != nil + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(1) + } else { + yynn2 = 0 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + if x.UID == nil { + r.EncodeNil() + } else { + yy4 := *x.UID + yym5 := z.EncBinary() + _ = yym5 + if false { + } else if z.HasExtensions() && z.EncExt(yy4) { + } else { + r.EncodeString(codecSelferC_UTF81234, string(yy4)) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("uid")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.UID == nil { + r.EncodeNil() + } else { + yy6 := *x.UID + yym7 := z.EncBinary() + _ = yym7 + if false { + } else if z.HasExtensions() && z.EncExt(yy6) { + } else { + r.EncodeString(codecSelferC_UTF81234, string(yy6)) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *Preconditions) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *Preconditions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "uid": + if r.TryDecodeAsNil() { + if x.UID != nil { + x.UID = nil + } + } else { + if x.UID == nil { + x.UID = new(pkg1_types.UID) + } + yym5 := z.DecBinary() + _ = yym5 + if false { + } else if z.HasExtensions() && z.DecExt(x.UID) { + } else { + *((*string)(x.UID)) = r.DecodeString() + } + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *Preconditions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj6 int + var yyb6 bool + var yyhl6 bool = l >= 0 + yyj6++ + if yyhl6 { + yyb6 = yyj6 > l + } else { + yyb6 = r.CheckBreak() + } + if yyb6 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.UID != nil { + x.UID = nil + } + } else { + if x.UID == nil { + x.UID = new(pkg1_types.UID) + } + yym8 := z.DecBinary() + _ = yym8 + if false { + } else if z.HasExtensions() && z.DecExt(x.UID) { + } else { + *((*string)(x.UID)) = r.DecodeString() + } + } + for { + yyj6++ + if yyhl6 { + yyb6 = yyj6 > l + } else { + yyb6 = r.CheckBreak() + } + if yyb6 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj6-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + func (x *DeleteOptions) CodecEncodeSelf(e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) @@ -37029,16 +37232,18 @@ func (x *DeleteOptions) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [3]bool + var yyq2 [4]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false - yyq2[1] = x.Kind != "" - yyq2[2] = x.APIVersion != "" + yyq2[0] = x.GracePeriodSeconds != nil + yyq2[1] = x.Preconditions != nil + yyq2[2] = x.Kind != "" + yyq2[3] = x.APIVersion != "" var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(3) + r.EncodeArrayStart(4) } else { - yynn2 = 1 + yynn2 = 0 for _, b := range yyq2 { if b { yynn2++ @@ -37049,55 +37254,59 @@ func (x *DeleteOptions) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if x.GracePeriodSeconds == nil { - r.EncodeNil() - } else { - yy4 := *x.GracePeriodSeconds - yym5 := z.EncBinary() - _ = yym5 - if false { + if yyq2[0] { + if x.GracePeriodSeconds == nil { + r.EncodeNil() } else { - r.EncodeInt(int64(yy4)) + yy4 := *x.GracePeriodSeconds + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + r.EncodeInt(int64(yy4)) + } } + } else { + r.EncodeNil() } } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("gracePeriodSeconds")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.GracePeriodSeconds == nil { - r.EncodeNil() - } else { - yy6 := *x.GracePeriodSeconds - yym7 := z.EncBinary() - _ = yym7 - if false { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("gracePeriodSeconds")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.GracePeriodSeconds == nil { + r.EncodeNil() } else { - r.EncodeInt(int64(yy6)) + yy6 := *x.GracePeriodSeconds + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + r.EncodeInt(int64(yy6)) + } } } } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) if yyq2[1] { - yym9 := z.EncBinary() - _ = yym9 - if false { + if x.Preconditions == nil { + r.EncodeNil() } else { - r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + x.Preconditions.CodecEncodeSelf(e) } } else { - r.EncodeString(codecSelferC_UTF81234, "") + r.EncodeNil() } } else { if yyq2[1] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("kind")) + r.EncodeString(codecSelferC_UTF81234, string("preconditions")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym10 := z.EncBinary() - _ = yym10 - if false { + if x.Preconditions == nil { + r.EncodeNil() } else { - r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + x.Preconditions.CodecEncodeSelf(e) } } } @@ -37108,7 +37317,7 @@ func (x *DeleteOptions) CodecEncodeSelf(e *codec1978.Encoder) { _ = yym12 if false { } else { - r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) } } else { r.EncodeString(codecSelferC_UTF81234, "") @@ -37116,11 +37325,36 @@ func (x *DeleteOptions) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[2] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + r.EncodeString(codecSelferC_UTF81234, string("kind")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym13 := z.EncBinary() _ = yym13 if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + yym15 := z.EncBinary() + _ = yym15 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym16 := z.EncBinary() + _ = yym16 + if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) } @@ -37203,6 +37437,17 @@ func (x *DeleteOptions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { *((*int64)(x.GracePeriodSeconds)) = int64(r.DecodeInt(64)) } } + case "preconditions": + if r.TryDecodeAsNil() { + if x.Preconditions != nil { + x.Preconditions = nil + } + } else { + if x.Preconditions == nil { + x.Preconditions = new(Preconditions) + } + x.Preconditions.CodecDecodeSelf(d) + } case "kind": if r.TryDecodeAsNil() { x.Kind = "" @@ -37226,16 +37471,16 @@ func (x *DeleteOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj8 int - var yyb8 bool - var yyhl8 bool = l >= 0 - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + var yyj9 int + var yyb9 bool + var yyhl9 bool = l >= 0 + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -37248,20 +37493,41 @@ func (x *DeleteOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { if x.GracePeriodSeconds == nil { x.GracePeriodSeconds = new(int64) } - yym10 := z.DecBinary() - _ = yym10 + yym11 := z.DecBinary() + _ = yym11 if false { } else { *((*int64)(x.GracePeriodSeconds)) = int64(r.DecodeInt(64)) } } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.Preconditions != nil { + x.Preconditions = nil + } + } else { + if x.Preconditions == nil { + x.Preconditions = new(Preconditions) + } + x.Preconditions.CodecDecodeSelf(d) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -37271,13 +37537,13 @@ func (x *DeleteOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } else { x.Kind = string(r.DecodeString()) } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -37288,17 +37554,17 @@ func (x *DeleteOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { x.APIVersion = string(r.DecodeString()) } for { - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj8-1, "") + z.DecStructFieldNotFound(yyj9-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } diff --git a/pkg/api/types.go b/pkg/api/types.go index a82aa45c075..dfdfe4adc96 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -1887,6 +1887,12 @@ type Binding struct { Target ObjectReference `json:"target"` } +// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out. +type Preconditions struct { + // Specifies the target UID. + UID *types.UID `json:"uid,omitempty"` +} + // DeleteOptions may be provided when deleting an API object type DeleteOptions struct { unversioned.TypeMeta `json:",inline"` @@ -1894,7 +1900,11 @@ type DeleteOptions struct { // Optional duration in seconds before the object should be deleted. Value must be non-negative integer. // The value zero indicates delete immediately. If this value is nil, the default grace period for the // specified type will be used. - GracePeriodSeconds *int64 `json:"gracePeriodSeconds"` + GracePeriodSeconds *int64 `json:"gracePeriodSeconds,omitempty"` + + // Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be + // returned. + Preconditions *Preconditions `json:"preconditions,omitempty"` } // ExportOptions is the query options to the standard REST get call. diff --git a/pkg/api/unversioned/types.go b/pkg/api/unversioned/types.go index 4d7a8f7d8e4..3a3b4147d67 100644 --- a/pkg/api/unversioned/types.go +++ b/pkg/api/unversioned/types.go @@ -174,10 +174,10 @@ const ( // Status code 409 StatusReasonAlreadyExists StatusReason = "AlreadyExists" - // StatusReasonConflict means the requested update operation cannot be completed - // due to a conflict in the operation. The client may need to alter the request. - // Each resource may define custom details that indicate the nature of the - // conflict. + // StatusReasonConflict means the requested operation cannot be completed + // due to a conflict in the operation. The client may need to alter the + // request. Each resource may define custom details that indicate the + // nature of the conflict. // Status code 409 StatusReasonConflict StatusReason = "Conflict" diff --git a/pkg/api/v1/conversion_generated.go b/pkg/api/v1/conversion_generated.go index 1587527a3eb..04f29767c43 100644 --- a/pkg/api/v1/conversion_generated.go +++ b/pkg/api/v1/conversion_generated.go @@ -26,6 +26,7 @@ import ( unversioned "k8s.io/kubernetes/pkg/api/unversioned" conversion "k8s.io/kubernetes/pkg/conversion" runtime "k8s.io/kubernetes/pkg/runtime" + types "k8s.io/kubernetes/pkg/types" ) func autoConvert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(in *api.AWSElasticBlockStoreVolumeSource, out *AWSElasticBlockStoreVolumeSource, s conversion.Scope) error { @@ -567,6 +568,15 @@ func autoConvert_api_DeleteOptions_To_v1_DeleteOptions(in *api.DeleteOptions, ou } else { out.GracePeriodSeconds = nil } + // unable to generate simple pointer conversion for api.Preconditions -> v1.Preconditions + if in.Preconditions != nil { + out.Preconditions = new(Preconditions) + if err := Convert_api_Preconditions_To_v1_Preconditions(in.Preconditions, out.Preconditions, s); err != nil { + return err + } + } else { + out.Preconditions = nil + } return nil } @@ -2451,6 +2461,23 @@ func Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(in *api.PodTemplateSpec, return autoConvert_api_PodTemplateSpec_To_v1_PodTemplateSpec(in, out, s) } +func autoConvert_api_Preconditions_To_v1_Preconditions(in *api.Preconditions, out *Preconditions, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Preconditions))(in) + } + if in.UID != nil { + out.UID = new(types.UID) + *out.UID = *in.UID + } else { + out.UID = nil + } + return nil +} + +func Convert_api_Preconditions_To_v1_Preconditions(in *api.Preconditions, out *Preconditions, s conversion.Scope) error { + return autoConvert_api_Preconditions_To_v1_Preconditions(in, out, s) +} + func autoConvert_api_Probe_To_v1_Probe(in *api.Probe, out *Probe, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*api.Probe))(in) @@ -3872,6 +3899,15 @@ func autoConvert_v1_DeleteOptions_To_api_DeleteOptions(in *DeleteOptions, out *a } else { out.GracePeriodSeconds = nil } + // unable to generate simple pointer conversion for v1.Preconditions -> api.Preconditions + if in.Preconditions != nil { + out.Preconditions = new(api.Preconditions) + if err := Convert_v1_Preconditions_To_api_Preconditions(in.Preconditions, out.Preconditions, s); err != nil { + return err + } + } else { + out.Preconditions = nil + } return nil } @@ -5692,6 +5728,23 @@ func Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(in *PodTemplateSpec, out return autoConvert_v1_PodTemplateSpec_To_api_PodTemplateSpec(in, out, s) } +func autoConvert_v1_Preconditions_To_api_Preconditions(in *Preconditions, out *api.Preconditions, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*Preconditions))(in) + } + if in.UID != nil { + out.UID = new(types.UID) + *out.UID = *in.UID + } else { + out.UID = nil + } + return nil +} + +func Convert_v1_Preconditions_To_api_Preconditions(in *Preconditions, out *api.Preconditions, s conversion.Scope) error { + return autoConvert_v1_Preconditions_To_api_Preconditions(in, out, s) +} + func autoConvert_v1_Probe_To_api_Probe(in *Probe, out *api.Probe, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*Probe))(in) @@ -6616,6 +6669,7 @@ func init() { autoConvert_api_PodTemplateSpec_To_v1_PodTemplateSpec, autoConvert_api_PodTemplate_To_v1_PodTemplate, autoConvert_api_Pod_To_v1_Pod, + autoConvert_api_Preconditions_To_v1_Preconditions, autoConvert_api_Probe_To_v1_Probe, autoConvert_api_RBDVolumeSource_To_v1_RBDVolumeSource, autoConvert_api_RangeAllocation_To_v1_RangeAllocation, @@ -6748,6 +6802,7 @@ func init() { autoConvert_v1_PodTemplateSpec_To_api_PodTemplateSpec, autoConvert_v1_PodTemplate_To_api_PodTemplate, autoConvert_v1_Pod_To_api_Pod, + autoConvert_v1_Preconditions_To_api_Preconditions, autoConvert_v1_Probe_To_api_Probe, autoConvert_v1_RBDVolumeSource_To_api_RBDVolumeSource, autoConvert_v1_RangeAllocation_To_api_RangeAllocation, diff --git a/pkg/api/v1/deep_copy_generated.go b/pkg/api/v1/deep_copy_generated.go index 69efc49b4b8..1597f9f3e15 100644 --- a/pkg/api/v1/deep_copy_generated.go +++ b/pkg/api/v1/deep_copy_generated.go @@ -26,6 +26,7 @@ import ( unversioned "k8s.io/kubernetes/pkg/api/unversioned" conversion "k8s.io/kubernetes/pkg/conversion" runtime "k8s.io/kubernetes/pkg/runtime" + types "k8s.io/kubernetes/pkg/types" intstr "k8s.io/kubernetes/pkg/util/intstr" ) @@ -137,6 +138,7 @@ func init() { DeepCopy_v1_PodTemplate, DeepCopy_v1_PodTemplateList, DeepCopy_v1_PodTemplateSpec, + DeepCopy_v1_Preconditions, DeepCopy_v1_PreferredSchedulingTerm, DeepCopy_v1_Probe, DeepCopy_v1_RBDVolumeSource, @@ -595,6 +597,15 @@ func DeepCopy_v1_DeleteOptions(in DeleteOptions, out *DeleteOptions, c *conversi } else { out.GracePeriodSeconds = nil } + if in.Preconditions != nil { + in, out := in.Preconditions, &out.Preconditions + *out = new(Preconditions) + if err := DeepCopy_v1_Preconditions(*in, *out, c); err != nil { + return err + } + } else { + out.Preconditions = nil + } return nil } @@ -2195,6 +2206,21 @@ func DeepCopy_v1_PodTemplateSpec(in PodTemplateSpec, out *PodTemplateSpec, c *co return nil } +func DeepCopy_v1_Preconditions(in Preconditions, out *Preconditions, c *conversion.Cloner) error { + if in.UID != nil { + in, out := in.UID, &out.UID + *out = new(types.UID) + if newVal, err := c.DeepCopy(*in); err != nil { + return err + } else { + **out = newVal.(types.UID) + } + } else { + out.UID = nil + } + return nil +} + func DeepCopy_v1_PreferredSchedulingTerm(in PreferredSchedulingTerm, out *PreferredSchedulingTerm, c *conversion.Cloner) error { out.Weight = in.Weight if err := DeepCopy_v1_NodeSelectorTerm(in.Preference, &out.Preference, c); err != nil { diff --git a/pkg/api/v1/types.generated.go b/pkg/api/v1/types.generated.go index 1dc4938a892..1621fee40d0 100644 --- a/pkg/api/v1/types.generated.go +++ b/pkg/api/v1/types.generated.go @@ -36735,6 +36735,209 @@ func (x *Binding) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } +func (x *Preconditions) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [1]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = x.UID != nil + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(1) + } else { + yynn2 = 0 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + if x.UID == nil { + r.EncodeNil() + } else { + yy4 := *x.UID + yym5 := z.EncBinary() + _ = yym5 + if false { + } else if z.HasExtensions() && z.EncExt(yy4) { + } else { + r.EncodeString(codecSelferC_UTF81234, string(yy4)) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("uid")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.UID == nil { + r.EncodeNil() + } else { + yy6 := *x.UID + yym7 := z.EncBinary() + _ = yym7 + if false { + } else if z.HasExtensions() && z.EncExt(yy6) { + } else { + r.EncodeString(codecSelferC_UTF81234, string(yy6)) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *Preconditions) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *Preconditions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "uid": + if r.TryDecodeAsNil() { + if x.UID != nil { + x.UID = nil + } + } else { + if x.UID == nil { + x.UID = new(pkg1_types.UID) + } + yym5 := z.DecBinary() + _ = yym5 + if false { + } else if z.HasExtensions() && z.DecExt(x.UID) { + } else { + *((*string)(x.UID)) = r.DecodeString() + } + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *Preconditions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj6 int + var yyb6 bool + var yyhl6 bool = l >= 0 + yyj6++ + if yyhl6 { + yyb6 = yyj6 > l + } else { + yyb6 = r.CheckBreak() + } + if yyb6 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.UID != nil { + x.UID = nil + } + } else { + if x.UID == nil { + x.UID = new(pkg1_types.UID) + } + yym8 := z.DecBinary() + _ = yym8 + if false { + } else if z.HasExtensions() && z.DecExt(x.UID) { + } else { + *((*string)(x.UID)) = r.DecodeString() + } + } + for { + yyj6++ + if yyhl6 { + yyb6 = yyj6 > l + } else { + yyb6 = r.CheckBreak() + } + if yyb6 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj6-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + func (x *DeleteOptions) CodecEncodeSelf(e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) @@ -36749,16 +36952,18 @@ func (x *DeleteOptions) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [3]bool + var yyq2 [4]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false - yyq2[1] = x.Kind != "" - yyq2[2] = x.APIVersion != "" + yyq2[0] = x.GracePeriodSeconds != nil + yyq2[1] = x.Preconditions != nil + yyq2[2] = x.Kind != "" + yyq2[3] = x.APIVersion != "" var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(3) + r.EncodeArrayStart(4) } else { - yynn2 = 1 + yynn2 = 0 for _, b := range yyq2 { if b { yynn2++ @@ -36769,55 +36974,59 @@ func (x *DeleteOptions) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if x.GracePeriodSeconds == nil { - r.EncodeNil() - } else { - yy4 := *x.GracePeriodSeconds - yym5 := z.EncBinary() - _ = yym5 - if false { + if yyq2[0] { + if x.GracePeriodSeconds == nil { + r.EncodeNil() } else { - r.EncodeInt(int64(yy4)) + yy4 := *x.GracePeriodSeconds + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + r.EncodeInt(int64(yy4)) + } } + } else { + r.EncodeNil() } } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("gracePeriodSeconds")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.GracePeriodSeconds == nil { - r.EncodeNil() - } else { - yy6 := *x.GracePeriodSeconds - yym7 := z.EncBinary() - _ = yym7 - if false { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("gracePeriodSeconds")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.GracePeriodSeconds == nil { + r.EncodeNil() } else { - r.EncodeInt(int64(yy6)) + yy6 := *x.GracePeriodSeconds + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + r.EncodeInt(int64(yy6)) + } } } } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) if yyq2[1] { - yym9 := z.EncBinary() - _ = yym9 - if false { + if x.Preconditions == nil { + r.EncodeNil() } else { - r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + x.Preconditions.CodecEncodeSelf(e) } } else { - r.EncodeString(codecSelferC_UTF81234, "") + r.EncodeNil() } } else { if yyq2[1] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("kind")) + r.EncodeString(codecSelferC_UTF81234, string("preconditions")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym10 := z.EncBinary() - _ = yym10 - if false { + if x.Preconditions == nil { + r.EncodeNil() } else { - r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + x.Preconditions.CodecEncodeSelf(e) } } } @@ -36828,7 +37037,7 @@ func (x *DeleteOptions) CodecEncodeSelf(e *codec1978.Encoder) { _ = yym12 if false { } else { - r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) } } else { r.EncodeString(codecSelferC_UTF81234, "") @@ -36836,11 +37045,36 @@ func (x *DeleteOptions) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[2] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + r.EncodeString(codecSelferC_UTF81234, string("kind")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym13 := z.EncBinary() _ = yym13 if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + yym15 := z.EncBinary() + _ = yym15 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym16 := z.EncBinary() + _ = yym16 + if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) } @@ -36923,6 +37157,17 @@ func (x *DeleteOptions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { *((*int64)(x.GracePeriodSeconds)) = int64(r.DecodeInt(64)) } } + case "preconditions": + if r.TryDecodeAsNil() { + if x.Preconditions != nil { + x.Preconditions = nil + } + } else { + if x.Preconditions == nil { + x.Preconditions = new(Preconditions) + } + x.Preconditions.CodecDecodeSelf(d) + } case "kind": if r.TryDecodeAsNil() { x.Kind = "" @@ -36946,16 +37191,16 @@ func (x *DeleteOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj8 int - var yyb8 bool - var yyhl8 bool = l >= 0 - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + var yyj9 int + var yyb9 bool + var yyhl9 bool = l >= 0 + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -36968,20 +37213,41 @@ func (x *DeleteOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { if x.GracePeriodSeconds == nil { x.GracePeriodSeconds = new(int64) } - yym10 := z.DecBinary() - _ = yym10 + yym11 := z.DecBinary() + _ = yym11 if false { } else { *((*int64)(x.GracePeriodSeconds)) = int64(r.DecodeInt(64)) } } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.Preconditions != nil { + x.Preconditions = nil + } + } else { + if x.Preconditions == nil { + x.Preconditions = new(Preconditions) + } + x.Preconditions.CodecDecodeSelf(d) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -36991,13 +37257,13 @@ func (x *DeleteOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } else { x.Kind = string(r.DecodeString()) } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -37008,17 +37274,17 @@ func (x *DeleteOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { x.APIVersion = string(r.DecodeString()) } for { - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj8-1, "") + z.DecStructFieldNotFound(yyj9-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } diff --git a/pkg/api/v1/types.go b/pkg/api/v1/types.go index ff8f12971fe..e062acfe917 100644 --- a/pkg/api/v1/types.go +++ b/pkg/api/v1/types.go @@ -2292,6 +2292,12 @@ type Binding struct { Target ObjectReference `json:"target"` } +// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out. +type Preconditions struct { + // Specifies the target UID. + UID *types.UID `json:"uid,omitempty"` +} + // DeleteOptions may be provided when deleting an API object type DeleteOptions struct { unversioned.TypeMeta `json:",inline"` @@ -2300,7 +2306,11 @@ type DeleteOptions struct { // The value zero indicates delete immediately. If this value is nil, the default grace period for the // specified type will be used. // Defaults to a per object value if not specified. zero means delete immediately. - GracePeriodSeconds *int64 `json:"gracePeriodSeconds"` + GracePeriodSeconds *int64 `json:"gracePeriodSeconds,omitempty"` + + // Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be + // returned. + Preconditions *Preconditions `json:"preconditions,omitempty"` } // ExportOptions is the query options to the standard REST get call. diff --git a/pkg/api/v1/types_swagger_doc_generated.go b/pkg/api/v1/types_swagger_doc_generated.go index 4b7756d87b7..b36490982c1 100644 --- a/pkg/api/v1/types_swagger_doc_generated.go +++ b/pkg/api/v1/types_swagger_doc_generated.go @@ -296,6 +296,7 @@ func (DaemonEndpoint) SwaggerDoc() map[string]string { var map_DeleteOptions = map[string]string{ "": "DeleteOptions may be provided when deleting an API object", "gracePeriodSeconds": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "preconditions": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.", } func (DeleteOptions) SwaggerDoc() map[string]string { @@ -1237,6 +1238,15 @@ func (PodTemplateSpec) SwaggerDoc() map[string]string { return map_PodTemplateSpec } +var map_Preconditions = map[string]string{ + "": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", + "uid": "Specifies the target UID.", +} + +func (Preconditions) SwaggerDoc() map[string]string { + return map_Preconditions +} + var map_PreferredSchedulingTerm = map[string]string{ "": "An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).", "weight": "Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100.", diff --git a/pkg/apiserver/errors_test.go b/pkg/apiserver/errors_test.go index 4dbf789a344..0b83f4122c1 100644 --- a/pkg/apiserver/errors_test.go +++ b/pkg/apiserver/errors_test.go @@ -55,7 +55,7 @@ func TestErrorsToAPIStatus(t *testing.T) { Status: unversioned.StatusFailure, Code: http.StatusConflict, Reason: "Conflict", - Message: "foos \"bar\" cannot be updated: failure", + Message: "Operation cannot be fulfilled on foos \"bar\": failure", Details: &unversioned.StatusDetails{ Group: "", Kind: "foos", diff --git a/pkg/apiserver/resthandler_test.go b/pkg/apiserver/resthandler_test.go index 8cebbf558e2..30e8b9c2c42 100644 --- a/pkg/apiserver/resthandler_test.go +++ b/pkg/apiserver/resthandler_test.go @@ -317,7 +317,7 @@ func TestPatchResourceWithConflict(t *testing.T) { changedPod: &api.Pod{}, updatePod: &api.Pod{}, - expectedError: `pods "foo" cannot be updated: existing 2, new 1`, + expectedError: `Operation cannot be fulfilled on pods "foo": existing 2, new 1`, } tc.startingPod.Name = name diff --git a/pkg/registry/configmap/etcd/etcd.go b/pkg/registry/configmap/etcd/etcd.go index 1e47491c4ca..2e0702c2544 100644 --- a/pkg/registry/configmap/etcd/etcd.go +++ b/pkg/registry/configmap/etcd/etcd.go @@ -72,6 +72,7 @@ func NewREST(opts generic.RESTOptions) *REST { CreateStrategy: configmap.Strategy, UpdateStrategy: configmap.Strategy, + DeleteStrategy: configmap.Strategy, Storage: storageInterface, } diff --git a/pkg/registry/controller/etcd/etcd.go b/pkg/registry/controller/etcd/etcd.go index 456efc8a579..6886e973bf7 100644 --- a/pkg/registry/controller/etcd/etcd.go +++ b/pkg/registry/controller/etcd/etcd.go @@ -97,6 +97,7 @@ func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { // Used to validate controller updates UpdateStrategy: controller.Strategy, + DeleteStrategy: controller.Strategy, Storage: storageInterface, } diff --git a/pkg/registry/controller/etcd/etcd_test.go b/pkg/registry/controller/etcd/etcd_test.go index b1c7e17ca06..00c7be629fb 100644 --- a/pkg/registry/controller/etcd/etcd_test.go +++ b/pkg/registry/controller/etcd/etcd_test.go @@ -118,11 +118,6 @@ func TestUpdate(t *testing.T) { return object }, // invalid updateFunc - func(obj runtime.Object) runtime.Object { - object := obj.(*api.ReplicationController) - object.UID = "newUID" - return object - }, func(obj runtime.Object) runtime.Object { object := obj.(*api.ReplicationController) object.Name = "" diff --git a/pkg/registry/daemonset/etcd/etcd.go b/pkg/registry/daemonset/etcd/etcd.go index 2d17c527b23..a24951f5b4a 100644 --- a/pkg/registry/daemonset/etcd/etcd.go +++ b/pkg/registry/daemonset/etcd/etcd.go @@ -72,6 +72,7 @@ func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { // Used to validate daemon set updates UpdateStrategy: daemonset.Strategy, + DeleteStrategy: daemonset.Strategy, Storage: storageInterface, } diff --git a/pkg/registry/daemonset/etcd/etcd_test.go b/pkg/registry/daemonset/etcd/etcd_test.go old mode 100755 new mode 100644 index d2b0c42360f..18c8c31c41d --- a/pkg/registry/daemonset/etcd/etcd_test.go +++ b/pkg/registry/daemonset/etcd/etcd_test.go @@ -108,11 +108,6 @@ func TestUpdate(t *testing.T) { return object }, // invalid updateFunc - func(obj runtime.Object) runtime.Object { - object := obj.(*extensions.DaemonSet) - object.UID = "newUID" - return object - }, func(obj runtime.Object) runtime.Object { object := obj.(*extensions.DaemonSet) object.Name = "" diff --git a/pkg/registry/deployment/etcd/etcd.go b/pkg/registry/deployment/etcd/etcd.go index 27a5236e3e2..5335e0fec1a 100644 --- a/pkg/registry/deployment/etcd/etcd.go +++ b/pkg/registry/deployment/etcd/etcd.go @@ -97,6 +97,7 @@ func NewREST(opts generic.RESTOptions) (*REST, *StatusREST, *RollbackREST) { // Used to validate deployment updates. UpdateStrategy: deployment.Strategy, + DeleteStrategy: deployment.Strategy, Storage: storageInterface, } @@ -162,7 +163,7 @@ func (r *RollbackREST) setDeploymentRollback(ctx api.Context, deploymentID strin if err != nil { return nil, err } - err = r.store.Storage.GuaranteedUpdate(ctx, dKey, &extensions.Deployment{}, false, storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { + err = r.store.Storage.GuaranteedUpdate(ctx, dKey, &extensions.Deployment{}, false, nil, storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { d, ok := obj.(*extensions.Deployment) if !ok { return nil, fmt.Errorf("unexpected object: %#v", obj) diff --git a/pkg/registry/deployment/etcd/etcd_test.go b/pkg/registry/deployment/etcd/etcd_test.go index ed2ad6c5848..173a2edeb5b 100644 --- a/pkg/registry/deployment/etcd/etcd_test.go +++ b/pkg/registry/deployment/etcd/etcd_test.go @@ -114,11 +114,6 @@ func TestUpdate(t *testing.T) { return object }, // invalid updateFunc - func(obj runtime.Object) runtime.Object { - object := obj.(*extensions.Deployment) - object.UID = "newUID" - return object - }, func(obj runtime.Object) runtime.Object { object := obj.(*extensions.Deployment) object.Name = "" diff --git a/pkg/registry/endpoint/etcd/etcd.go b/pkg/registry/endpoint/etcd/etcd.go index 09799b95103..8528ece0ba6 100644 --- a/pkg/registry/endpoint/etcd/etcd.go +++ b/pkg/registry/endpoint/etcd/etcd.go @@ -59,6 +59,7 @@ func NewREST(opts generic.RESTOptions) *REST { CreateStrategy: endpoint.Strategy, UpdateStrategy: endpoint.Strategy, + DeleteStrategy: endpoint.Strategy, Storage: storageInterface, } diff --git a/pkg/registry/event/etcd/etcd.go b/pkg/registry/event/etcd/etcd.go index 19b1df70539..ac84e2ef935 100644 --- a/pkg/registry/event/etcd/etcd.go +++ b/pkg/registry/event/etcd/etcd.go @@ -61,6 +61,7 @@ func NewREST(opts generic.RESTOptions, ttl uint64) *REST { CreateStrategy: event.Strategy, UpdateStrategy: event.Strategy, + DeleteStrategy: event.Strategy, Storage: storageInterface, } diff --git a/pkg/registry/generic/etcd/etcd.go b/pkg/registry/generic/etcd/etcd.go index 857b24972b2..62734bb4c2e 100644 --- a/pkg/registry/generic/etcd/etcd.go +++ b/pkg/registry/generic/etcd/etcd.go @@ -259,7 +259,17 @@ func (e *Etcd) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool // TODO: expose TTL creating := false out := e.NewFunc() - err = e.Storage.GuaranteedUpdate(ctx, key, out, true, func(existing runtime.Object, res storage.ResponseMeta) (runtime.Object, *uint64, error) { + meta, err := api.ObjectMetaFor(obj) + if err != nil { + return nil, false, kubeerr.NewInternalError(err) + } + var preconditions *storage.Preconditions + // If the UID of the new object is specified, we use it as an Update precondition. + if len(meta.UID) != 0 { + UIDCopy := meta.UID + preconditions = &storage.Preconditions{UID: &UIDCopy} + } + err = e.Storage.GuaranteedUpdate(ctx, key, out, true, preconditions, func(existing runtime.Object, res storage.ResponseMeta) (runtime.Object, *uint64, error) { // Since we return 'obj' from this function and it can be modified outside this // function, we are resetting resourceVersion to the initial value here. // @@ -395,6 +405,10 @@ func (e *Etcd) Delete(ctx api.Context, name string, options *api.DeleteOptions) if options == nil { options = api.NewDeleteOptions(0) } + var preconditions storage.Preconditions + if options.Preconditions != nil { + preconditions.UID = options.Preconditions.UID + } graceful, pendingGraceful, err := rest.BeforeDelete(e.DeleteStrategy, ctx, obj, options) if err != nil { return nil, err @@ -408,7 +422,7 @@ func (e *Etcd) Delete(ctx api.Context, name string, options *api.DeleteOptions) out := e.NewFunc() lastGraceful := int64(0) err := e.Storage.GuaranteedUpdate( - ctx, key, out, false, + ctx, key, out, false, &preconditions, storage.SimpleUpdate(func(existing runtime.Object) (runtime.Object, error) { graceful, pendingGraceful, err := rest.BeforeDelete(e.DeleteStrategy, ctx, existing, options) if err != nil { @@ -451,7 +465,7 @@ func (e *Etcd) Delete(ctx api.Context, name string, options *api.DeleteOptions) // delete immediately, or no graceful deletion supported out := e.NewFunc() - if err := e.Storage.Delete(ctx, key, out); err != nil { + if err := e.Storage.Delete(ctx, key, out, &preconditions); err != nil { // Please refer to the place where we set ignoreNotFound for the reason // why we ignore the NotFound error . if storage.IsNotFound(err) && ignoreNotFound && lastExisting != nil { diff --git a/pkg/registry/generic/etcd/etcd_test.go b/pkg/registry/generic/etcd/etcd_test.go index 431e455aa51..f3d6508a1ac 100644 --- a/pkg/registry/generic/etcd/etcd_test.go +++ b/pkg/registry/generic/etcd/etcd_test.go @@ -99,6 +99,7 @@ func NewTestGenericEtcdRegistry(t *testing.T) (*etcdtesting.EtcdTestServer, *Etc QualifiedResource: api.Resource("pods"), CreateStrategy: strategy, UpdateStrategy: strategy, + DeleteStrategy: strategy, KeyRootFunc: func(ctx api.Context) string { return podPrefix }, @@ -323,7 +324,7 @@ func TestEtcdUpdate(t *testing.T) { // Test3 outofDate _, _, err = registry.Update(testContext, podAWithResourceVersion) if !errors.IsConflict(err) { - t.Errorf("Unexpected error: %v", err) + t.Errorf("Unexpected error updating podAWithResourceVersion: %v", err) } // Test4 normal update and verify diff --git a/pkg/registry/horizontalpodautoscaler/etcd/etcd.go b/pkg/registry/horizontalpodautoscaler/etcd/etcd.go index 27393142a70..143d47f9ff9 100644 --- a/pkg/registry/horizontalpodautoscaler/etcd/etcd.go +++ b/pkg/registry/horizontalpodautoscaler/etcd/etcd.go @@ -70,6 +70,7 @@ func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { // Used to validate autoscaler updates UpdateStrategy: horizontalpodautoscaler.Strategy, + DeleteStrategy: horizontalpodautoscaler.Strategy, Storage: storageInterface, } diff --git a/pkg/registry/ingress/etcd/etcd.go b/pkg/registry/ingress/etcd/etcd.go index c99847cbcb4..21cb8980809 100644 --- a/pkg/registry/ingress/etcd/etcd.go +++ b/pkg/registry/ingress/etcd/etcd.go @@ -72,6 +72,7 @@ func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { // Used to validate controller updates UpdateStrategy: ingress.Strategy, + DeleteStrategy: ingress.Strategy, Storage: storageInterface, } diff --git a/pkg/registry/ingress/etcd/etcd_test.go b/pkg/registry/ingress/etcd/etcd_test.go old mode 100755 new mode 100644 index 7d33e93a2c7..9a1a6b4cb8b --- a/pkg/registry/ingress/etcd/etcd_test.go +++ b/pkg/registry/ingress/etcd/etcd_test.go @@ -151,12 +151,6 @@ func TestUpdate(t *testing.T) { return object }, // invalid updateFunc: ObjeceMeta is not to be tampered with. - func(obj runtime.Object) runtime.Object { - object := obj.(*extensions.Ingress) - object.UID = "newUID" - return object - }, - func(obj runtime.Object) runtime.Object { object := obj.(*extensions.Ingress) object.Name = "" diff --git a/pkg/registry/job/etcd/etcd.go b/pkg/registry/job/etcd/etcd.go index c819ed7824d..8f8f6a55683 100644 --- a/pkg/registry/job/etcd/etcd.go +++ b/pkg/registry/job/etcd/etcd.go @@ -72,6 +72,7 @@ func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { // Used to validate job updates UpdateStrategy: job.Strategy, + DeleteStrategy: job.Strategy, Storage: storageInterface, } diff --git a/pkg/registry/limitrange/etcd/etcd.go b/pkg/registry/limitrange/etcd/etcd.go index a1a5166437e..1fb7ecbe9fd 100644 --- a/pkg/registry/limitrange/etcd/etcd.go +++ b/pkg/registry/limitrange/etcd/etcd.go @@ -59,6 +59,7 @@ func NewREST(opts generic.RESTOptions) *REST { CreateStrategy: limitrange.Strategy, UpdateStrategy: limitrange.Strategy, + DeleteStrategy: limitrange.Strategy, ExportStrategy: limitrange.Strategy, Storage: storageInterface, diff --git a/pkg/registry/namespace/etcd/etcd.go b/pkg/registry/namespace/etcd/etcd.go index f76e288b727..8955cbbdd3f 100644 --- a/pkg/registry/namespace/etcd/etcd.go +++ b/pkg/registry/namespace/etcd/etcd.go @@ -75,6 +75,7 @@ func NewREST(opts generic.RESTOptions) (*REST, *StatusREST, *FinalizeREST) { CreateStrategy: namespace.Strategy, UpdateStrategy: namespace.Strategy, + DeleteStrategy: namespace.Strategy, ReturnDeletedObject: true, Storage: storageInterface, diff --git a/pkg/registry/node/etcd/etcd.go b/pkg/registry/node/etcd/etcd.go index 2fe177b7307..835fcc1224b 100644 --- a/pkg/registry/node/etcd/etcd.go +++ b/pkg/registry/node/etcd/etcd.go @@ -85,6 +85,7 @@ func NewStorage(opts generic.RESTOptions, connection client.ConnectionInfoGetter CreateStrategy: node.Strategy, UpdateStrategy: node.Strategy, + DeleteStrategy: node.Strategy, ExportStrategy: node.Strategy, Storage: storageInterface, diff --git a/pkg/registry/persistentvolume/etcd/etcd.go b/pkg/registry/persistentvolume/etcd/etcd.go index 877f3039d0b..7f30cc20b80 100644 --- a/pkg/registry/persistentvolume/etcd/etcd.go +++ b/pkg/registry/persistentvolume/etcd/etcd.go @@ -59,6 +59,7 @@ func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { CreateStrategy: persistentvolume.Strategy, UpdateStrategy: persistentvolume.Strategy, + DeleteStrategy: persistentvolume.Strategy, ReturnDeletedObject: true, Storage: storageInterface, diff --git a/pkg/registry/persistentvolumeclaim/etcd/etcd.go b/pkg/registry/persistentvolumeclaim/etcd/etcd.go index 8a77b0316b1..e81c825cdda 100644 --- a/pkg/registry/persistentvolumeclaim/etcd/etcd.go +++ b/pkg/registry/persistentvolumeclaim/etcd/etcd.go @@ -59,6 +59,7 @@ func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { CreateStrategy: persistentvolumeclaim.Strategy, UpdateStrategy: persistentvolumeclaim.Strategy, + DeleteStrategy: persistentvolumeclaim.Strategy, ReturnDeletedObject: true, Storage: storageInterface, diff --git a/pkg/registry/pod/etcd/etcd.go b/pkg/registry/pod/etcd/etcd.go index c404fc48afd..33dfb949bf4 100644 --- a/pkg/registry/pod/etcd/etcd.go +++ b/pkg/registry/pod/etcd/etcd.go @@ -148,7 +148,7 @@ func (r *BindingREST) setPodHostAndAnnotations(ctx api.Context, podID, oldMachin if err != nil { return nil, err } - err = r.store.Storage.GuaranteedUpdate(ctx, podKey, &api.Pod{}, false, storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { + err = r.store.Storage.GuaranteedUpdate(ctx, podKey, &api.Pod{}, false, nil, storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { pod, ok := obj.(*api.Pod) if !ok { return nil, fmt.Errorf("unexpected object: %#v", obj) diff --git a/pkg/registry/pod/etcd/etcd_test.go b/pkg/registry/pod/etcd/etcd_test.go index ea146ddf497..fc655790f08 100644 --- a/pkg/registry/pod/etcd/etcd_test.go +++ b/pkg/registry/pod/etcd/etcd_test.go @@ -137,7 +137,7 @@ type FailDeletionStorage struct { Called *bool } -func (f FailDeletionStorage) Delete(ctx context.Context, key string, out runtime.Object) error { +func (f FailDeletionStorage) Delete(ctx context.Context, key string, out runtime.Object, precondition *storage.Preconditions) error { *f.Called = true return storage.NewKeyNotFoundError(key, 0) } diff --git a/pkg/registry/podsecuritypolicy/etcd/etcd.go b/pkg/registry/podsecuritypolicy/etcd/etcd.go index 80bf0e09977..34839dd4870 100644 --- a/pkg/registry/podsecuritypolicy/etcd/etcd.go +++ b/pkg/registry/podsecuritypolicy/etcd/etcd.go @@ -60,6 +60,7 @@ func NewREST(opts generic.RESTOptions) *REST { CreateStrategy: podsecuritypolicy.Strategy, UpdateStrategy: podsecuritypolicy.Strategy, + DeleteStrategy: podsecuritypolicy.Strategy, ReturnDeletedObject: true, Storage: storageInterface, } diff --git a/pkg/registry/podtemplate/etcd/etcd.go b/pkg/registry/podtemplate/etcd/etcd.go index 93e8cfde752..88203ece03c 100644 --- a/pkg/registry/podtemplate/etcd/etcd.go +++ b/pkg/registry/podtemplate/etcd/etcd.go @@ -59,6 +59,7 @@ func NewREST(opts generic.RESTOptions) *REST { CreateStrategy: podtemplate.Strategy, UpdateStrategy: podtemplate.Strategy, + DeleteStrategy: podtemplate.Strategy, ExportStrategy: podtemplate.Strategy, ReturnDeletedObject: true, diff --git a/pkg/registry/replicaset/etcd/etcd.go b/pkg/registry/replicaset/etcd/etcd.go index 37f90362897..caa2540669e 100644 --- a/pkg/registry/replicaset/etcd/etcd.go +++ b/pkg/registry/replicaset/etcd/etcd.go @@ -96,6 +96,7 @@ func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { // Used to validate ReplicaSet updates UpdateStrategy: replicaset.Strategy, + DeleteStrategy: replicaset.Strategy, Storage: storageInterface, } diff --git a/pkg/registry/replicaset/etcd/etcd_test.go b/pkg/registry/replicaset/etcd/etcd_test.go index c6de66324c9..f61efaa72f6 100644 --- a/pkg/registry/replicaset/etcd/etcd_test.go +++ b/pkg/registry/replicaset/etcd/etcd_test.go @@ -121,11 +121,6 @@ func TestUpdate(t *testing.T) { return object }, // invalid updateFunc - func(obj runtime.Object) runtime.Object { - object := obj.(*extensions.ReplicaSet) - object.UID = "newUID" - return object - }, func(obj runtime.Object) runtime.Object { object := obj.(*extensions.ReplicaSet) object.Name = "" diff --git a/pkg/registry/resourcequota/etcd/etcd.go b/pkg/registry/resourcequota/etcd/etcd.go index 1cffeeebdaa..41322819730 100644 --- a/pkg/registry/resourcequota/etcd/etcd.go +++ b/pkg/registry/resourcequota/etcd/etcd.go @@ -59,6 +59,7 @@ func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { CreateStrategy: resourcequota.Strategy, UpdateStrategy: resourcequota.Strategy, + DeleteStrategy: resourcequota.Strategy, ReturnDeletedObject: true, Storage: storageInterface, diff --git a/pkg/registry/secret/etcd/etcd.go b/pkg/registry/secret/etcd/etcd.go index 3aaea7009ea..36cc252bebc 100644 --- a/pkg/registry/secret/etcd/etcd.go +++ b/pkg/registry/secret/etcd/etcd.go @@ -59,6 +59,7 @@ func NewREST(opts generic.RESTOptions) *REST { CreateStrategy: secret.Strategy, UpdateStrategy: secret.Strategy, + DeleteStrategy: secret.Strategy, Storage: storageInterface, } diff --git a/pkg/registry/service/allocator/etcd/etcd.go b/pkg/registry/service/allocator/etcd/etcd.go index b261c69b556..2c1c74984b2 100644 --- a/pkg/registry/service/allocator/etcd/etcd.go +++ b/pkg/registry/service/allocator/etcd/etcd.go @@ -143,7 +143,7 @@ func (e *Etcd) Release(item int) error { // tryUpdate performs a read-update to persist the latest snapshot state of allocation. func (e *Etcd) tryUpdate(fn func() error) error { - err := e.storage.GuaranteedUpdate(context.TODO(), e.baseKey, &api.RangeAllocation{}, true, + err := e.storage.GuaranteedUpdate(context.TODO(), e.baseKey, &api.RangeAllocation{}, true, nil, storage.SimpleUpdate(func(input runtime.Object) (output runtime.Object, err error) { existing := input.(*api.RangeAllocation) if len(existing.ResourceVersion) == 0 { @@ -200,7 +200,7 @@ func (e *Etcd) CreateOrUpdate(snapshot *api.RangeAllocation) error { defer e.lock.Unlock() last := "" - err := e.storage.GuaranteedUpdate(context.TODO(), e.baseKey, &api.RangeAllocation{}, true, + err := e.storage.GuaranteedUpdate(context.TODO(), e.baseKey, &api.RangeAllocation{}, true, nil, storage.SimpleUpdate(func(input runtime.Object) (output runtime.Object, err error) { existing := input.(*api.RangeAllocation) switch { diff --git a/pkg/registry/service/etcd/etcd.go b/pkg/registry/service/etcd/etcd.go index f244e6d742f..866ef1c6b01 100644 --- a/pkg/registry/service/etcd/etcd.go +++ b/pkg/registry/service/etcd/etcd.go @@ -59,6 +59,7 @@ func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { CreateStrategy: service.Strategy, UpdateStrategy: service.Strategy, + DeleteStrategy: service.Strategy, ExportStrategy: service.Strategy, Storage: storageInterface, diff --git a/pkg/registry/serviceaccount/etcd/etcd.go b/pkg/registry/serviceaccount/etcd/etcd.go index 81925bd723e..e057104f14d 100644 --- a/pkg/registry/serviceaccount/etcd/etcd.go +++ b/pkg/registry/serviceaccount/etcd/etcd.go @@ -59,6 +59,7 @@ func NewREST(opts generic.RESTOptions) *REST { CreateStrategy: serviceaccount.Strategy, UpdateStrategy: serviceaccount.Strategy, + DeleteStrategy: serviceaccount.Strategy, ReturnDeletedObject: true, Storage: storageInterface, diff --git a/pkg/registry/thirdpartyresource/etcd/etcd.go b/pkg/registry/thirdpartyresource/etcd/etcd.go index 95fc9fb5a4a..edae2ea4511 100644 --- a/pkg/registry/thirdpartyresource/etcd/etcd.go +++ b/pkg/registry/thirdpartyresource/etcd/etcd.go @@ -58,6 +58,7 @@ func NewREST(opts generic.RESTOptions) *REST { DeleteCollectionWorkers: opts.DeleteCollectionWorkers, CreateStrategy: thirdpartyresource.Strategy, UpdateStrategy: thirdpartyresource.Strategy, + DeleteStrategy: thirdpartyresource.Strategy, Storage: storageInterface, } diff --git a/pkg/registry/thirdpartyresourcedata/etcd/etcd.go b/pkg/registry/thirdpartyresourcedata/etcd/etcd.go index 8b5e0bc16eb..4dd4adfb97c 100644 --- a/pkg/registry/thirdpartyresourcedata/etcd/etcd.go +++ b/pkg/registry/thirdpartyresourcedata/etcd/etcd.go @@ -61,6 +61,7 @@ func NewREST(opts generic.RESTOptions, group, kind string) *REST { DeleteCollectionWorkers: opts.DeleteCollectionWorkers, CreateStrategy: thirdpartyresourcedata.Strategy, UpdateStrategy: thirdpartyresourcedata.Strategy, + DeleteStrategy: thirdpartyresourcedata.Strategy, Storage: storageInterface, } diff --git a/pkg/storage/cacher.go b/pkg/storage/cacher.go index d215f175efd..1895821b381 100644 --- a/pkg/storage/cacher.go +++ b/pkg/storage/cacher.go @@ -239,8 +239,8 @@ func (c *Cacher) Set(ctx context.Context, key string, obj, out runtime.Object, t } // Implements storage.Interface. -func (c *Cacher) Delete(ctx context.Context, key string, out runtime.Object) error { - return c.storage.Delete(ctx, key, out) +func (c *Cacher) Delete(ctx context.Context, key string, out runtime.Object, preconditions *Preconditions) error { + return c.storage.Delete(ctx, key, out, preconditions) } // Implements storage.Interface. @@ -347,8 +347,8 @@ func (c *Cacher) List(ctx context.Context, key string, resourceVersion string, f } // Implements storage.Interface. -func (c *Cacher) GuaranteedUpdate(ctx context.Context, key string, ptrToType runtime.Object, ignoreNotFound bool, tryUpdate UpdateFunc) error { - return c.storage.GuaranteedUpdate(ctx, key, ptrToType, ignoreNotFound, tryUpdate) +func (c *Cacher) GuaranteedUpdate(ctx context.Context, key string, ptrToType runtime.Object, ignoreNotFound bool, preconditions *Preconditions, tryUpdate UpdateFunc) error { + return c.storage.GuaranteedUpdate(ctx, key, ptrToType, ignoreNotFound, preconditions, tryUpdate) } // Implements storage.Interface. diff --git a/pkg/storage/cacher_test.go b/pkg/storage/cacher_test.go index d15a17f06e4..39392985a5f 100644 --- a/pkg/storage/cacher_test.go +++ b/pkg/storage/cacher_test.go @@ -107,7 +107,7 @@ func TestList(t *testing.T) { _ = updatePod(t, etcdStorage, podFooPrime, fooCreated) deleted := api.Pod{} - if err := etcdStorage.Delete(context.TODO(), etcdtest.AddPrefix("pods/ns/bar"), &deleted); err != nil { + if err := etcdStorage.Delete(context.TODO(), etcdtest.AddPrefix("pods/ns/bar"), &deleted, nil); err != nil { t.Errorf("Unexpected error: %v", err) } @@ -315,7 +315,7 @@ func TestFiltering(t *testing.T) { _ = updatePod(t, etcdStorage, podFooPrime, fooUnfiltered) deleted := api.Pod{} - if err := etcdStorage.Delete(context.TODO(), etcdtest.AddPrefix("pods/ns/foo"), &deleted); err != nil { + if err := etcdStorage.Delete(context.TODO(), etcdtest.AddPrefix("pods/ns/foo"), &deleted, nil); err != nil { t.Errorf("Unexpected error: %v", err) } diff --git a/pkg/storage/errors.go b/pkg/storage/errors.go index 64f2f716996..89d55980ba9 100644 --- a/pkg/storage/errors.go +++ b/pkg/storage/errors.go @@ -16,7 +16,11 @@ limitations under the License. package storage -import "fmt" +import ( + "fmt" + + "k8s.io/kubernetes/pkg/util/validation/field" +) const ( ErrCodeKeyNotFound int = iota + 1 @@ -104,3 +108,47 @@ func isErrCode(err error, code int) bool { } return false } + +// InvalidError is generated when an error caused by invalid API object occurs +// in the storage package. +type InvalidError struct { + Errs field.ErrorList +} + +func (e InvalidError) Error() string { + return e.Errs.ToAggregate().Error() +} + +// IsInvalidError returns true if and only if err is an InvalidError. +func IsInvalidError(err error) bool { + _, ok := err.(InvalidError) + return ok +} + +func NewInvalidError(errors field.ErrorList) InvalidError { + return InvalidError{errors} +} + +// InternalError is generated when an error occurs in the storage package, i.e., +// not from the underlying storage backend (e.g., etcd). +type InternalError struct { + Reason string +} + +func (e InternalError) Error() string { + return e.Reason +} + +// IsInternalError returns true if and only if err is an InternalError. +func IsInternalError(err error) bool { + _, ok := err.(InternalError) + return ok +} + +func NewInternalError(reason string) InternalError { + return InternalError{reason} +} + +func NewInternalErrorf(format string, a ...interface{}) InternalError { + return InternalError{fmt.Sprintf(format, a)} +} diff --git a/pkg/storage/etcd/etcd_helper.go b/pkg/storage/etcd/etcd_helper.go index c86fe7ac7d8..f3ef8f8c7e5 100644 --- a/pkg/storage/etcd/etcd_helper.go +++ b/pkg/storage/etcd/etcd_helper.go @@ -291,26 +291,76 @@ func (h *etcdHelper) Set(ctx context.Context, key string, obj, out runtime.Objec return err } +func checkPreconditions(preconditions *storage.Preconditions, out runtime.Object) error { + if preconditions == nil { + return nil + } + objMeta, err := api.ObjectMetaFor(out) + if err != nil { + return storage.NewInternalErrorf("can't enforce preconditions %v on un-introspectable object %v, got error: %v", *preconditions, out, err) + } + if preconditions.UID != nil && *preconditions.UID != objMeta.UID { + return etcd.Error{Code: etcd.ErrorCodeTestFailed, Message: fmt.Sprintf("the UID in the precondition (%s) does not match the UID in record (%s). The object might have been deleted and then recreated", *preconditions.UID, objMeta.UID)} + } + return nil +} + // Implements storage.Interface. -func (h *etcdHelper) Delete(ctx context.Context, key string, out runtime.Object) error { +func (h *etcdHelper) Delete(ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions) error { if ctx == nil { glog.Errorf("Context is nil") } key = h.prefixEtcdKey(key) - if _, err := conversion.EnforcePtr(out); err != nil { + v, err := conversion.EnforcePtr(out) + if err != nil { panic("unable to convert output object to pointer") } - startTime := time.Now() - response, err := h.etcdKeysAPI.Delete(ctx, key, nil) - metrics.RecordEtcdRequestLatency("delete", getTypeName(out), startTime) - if !etcdutil.IsEtcdNotFound(err) { - // if the object that existed prior to the delete is returned by etcd, update out. - if err != nil || response.PrevNode != nil { - _, _, err = h.extractObj(response, err, out, false, true) + if preconditions == nil { + startTime := time.Now() + response, err := h.etcdKeysAPI.Delete(ctx, key, nil) + metrics.RecordEtcdRequestLatency("delete", getTypeName(out), startTime) + if !etcdutil.IsEtcdNotFound(err) { + // if the object that existed prior to the delete is returned by etcd, update the out object. + if err != nil || response.PrevNode != nil { + _, _, err = h.extractObj(response, err, out, false, true) + } + } + return toStorageErr(err, key, 0) + } + + // Check the preconditions match. + obj := reflect.New(v.Type()).Interface().(runtime.Object) + for { + _, node, res, err := h.bodyAndExtractObj(ctx, key, obj, false) + if err != nil { + return toStorageErr(err, key, 0) + } + if err := checkPreconditions(preconditions, obj); err != nil { + return toStorageErr(err, key, 0) + } + index := uint64(0) + if node != nil { + index = node.ModifiedIndex + } else if res != nil { + index = res.Index + } + opt := etcd.DeleteOptions{PrevIndex: index} + startTime := time.Now() + response, err := h.etcdKeysAPI.Delete(ctx, key, &opt) + metrics.RecordEtcdRequestLatency("delete", getTypeName(out), startTime) + if etcdutil.IsEtcdTestFailed(err) { + glog.Infof("deletion of %s failed because of a conflict, going to retry", key) + } else { + if !etcdutil.IsEtcdNotFound(err) { + // if the object that existed prior to the delete is returned by etcd, update the out object. + if err != nil || response.PrevNode != nil { + _, _, err = h.extractObj(response, err, out, false, true) + } + } + return toStorageErr(err, key, 0) } } - return toStorageErr(err, key, 0) } // Implements storage.Interface. @@ -547,7 +597,7 @@ func (h *etcdHelper) listEtcdNode(ctx context.Context, key string) ([]*etcd.Node } // Implements storage.Interface. -func (h *etcdHelper) GuaranteedUpdate(ctx context.Context, key string, ptrToType runtime.Object, ignoreNotFound bool, tryUpdate storage.UpdateFunc) error { +func (h *etcdHelper) GuaranteedUpdate(ctx context.Context, key string, ptrToType runtime.Object, ignoreNotFound bool, preconditions *storage.Preconditions, tryUpdate storage.UpdateFunc) error { if ctx == nil { glog.Errorf("Context is nil") } @@ -561,7 +611,10 @@ func (h *etcdHelper) GuaranteedUpdate(ctx context.Context, key string, ptrToType obj := reflect.New(v.Type()).Interface().(runtime.Object) origBody, node, res, err := h.bodyAndExtractObj(ctx, key, obj, ignoreNotFound) if err != nil { - return err + return toStorageErr(err, key, 0) + } + if err := checkPreconditions(preconditions, obj); err != nil { + return toStorageErr(err, key, 0) } meta := storage.ResponseMeta{} if node != nil { @@ -574,7 +627,7 @@ func (h *etcdHelper) GuaranteedUpdate(ctx context.Context, key string, ptrToType // Get the object to be written by calling tryUpdate. ret, newTTL, err := tryUpdate(obj, meta) if err != nil { - return err + return toStorageErr(err, key, 0) } index := uint64(0) diff --git a/pkg/storage/etcd/etcd_helper_test.go b/pkg/storage/etcd/etcd_helper_test.go index ca02a18f77f..109cf9fc843 100644 --- a/pkg/storage/etcd/etcd_helper_test.go +++ b/pkg/storage/etcd/etcd_helper_test.go @@ -371,7 +371,7 @@ func TestGuaranteedUpdate(t *testing.T) { helper := newEtcdHelper(server.Client, codec, key) obj := &storagetesting.TestResource{ObjectMeta: api.ObjectMeta{Name: "foo"}, Value: 1} - err := helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { + err := helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, nil, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { return obj, nil })) if err != nil { @@ -381,7 +381,7 @@ func TestGuaranteedUpdate(t *testing.T) { // Update an existing node. callbackCalled := false objUpdate := &storagetesting.TestResource{ObjectMeta: api.ObjectMeta{Name: "foo"}, Value: 2} - err = helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { + err = helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, nil, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { callbackCalled = true if in.(*storagetesting.TestResource).Value != 1 { @@ -416,7 +416,7 @@ func TestGuaranteedUpdateNoChange(t *testing.T) { helper := newEtcdHelper(server.Client, codec, key) obj := &storagetesting.TestResource{ObjectMeta: api.ObjectMeta{Name: "foo"}, Value: 1} - err := helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { + err := helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, nil, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { return obj, nil })) if err != nil { @@ -426,7 +426,7 @@ func TestGuaranteedUpdateNoChange(t *testing.T) { // Update an existing node with the same data callbackCalled := false objUpdate := &storagetesting.TestResource{ObjectMeta: api.ObjectMeta{Name: "foo"}, Value: 1} - err = helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { + err = helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, nil, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { callbackCalled = true return objUpdate, nil })) @@ -453,13 +453,13 @@ func TestGuaranteedUpdateKeyNotFound(t *testing.T) { }) ignoreNotFound := false - err := helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, ignoreNotFound, f) + err := helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, ignoreNotFound, nil, f) if err == nil { t.Errorf("Expected error for key not found.") } ignoreNotFound = true - err = helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, ignoreNotFound, f) + err = helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, ignoreNotFound, nil, f) if err != nil { t.Errorf("Unexpected error %v.", err) } @@ -484,7 +484,7 @@ func TestGuaranteedUpdate_CreateCollision(t *testing.T) { defer wgDone.Done() firstCall := true - err := helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { + err := helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, nil, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { defer func() { firstCall = false }() if firstCall { @@ -514,6 +514,26 @@ func TestGuaranteedUpdate_CreateCollision(t *testing.T) { } } +func TestGuaranteedUpdateUIDMismatch(t *testing.T) { + server := etcdtesting.NewEtcdTestClientServer(t) + defer server.Terminate(t) + prefix := path.Join("/", etcdtest.PathPrefix()) + helper := newEtcdHelper(server.Client, testapi.Default.Codec(), prefix) + + obj := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", UID: "A"}} + podPtr := &api.Pod{} + err := helper.Create(context.TODO(), "/some/key", obj, podPtr, 0) + if err != nil { + t.Fatalf("Unexpected error %#v", err) + } + err = helper.GuaranteedUpdate(context.TODO(), "/some/key", podPtr, true, storage.NewUIDPreconditions("B"), storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { + return obj, nil + })) + if !storage.IsTestFailed(err) { + t.Fatalf("Expect a Test Failed (write conflict) error, got: %v", err) + } +} + func TestPrefixEtcdKey(t *testing.T) { server := etcdtesting.NewEtcdTestClientServer(t) defer server.Terminate(t) @@ -534,3 +554,79 @@ func TestPrefixEtcdKey(t *testing.T) { assert.Equal(t, keyBefore, keyAfter, "Prefix incorrectly added by EtcdHelper") } + +func TestDeleteUIDMismatch(t *testing.T) { + server := etcdtesting.NewEtcdTestClientServer(t) + defer server.Terminate(t) + prefix := path.Join("/", etcdtest.PathPrefix()) + helper := newEtcdHelper(server.Client, testapi.Default.Codec(), prefix) + + obj := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", UID: "A"}} + podPtr := &api.Pod{} + err := helper.Create(context.TODO(), "/some/key", obj, podPtr, 0) + if err != nil { + t.Fatalf("Unexpected error %#v", err) + } + err = helper.Delete(context.TODO(), "/some/key", obj, storage.NewUIDPreconditions("B")) + if !storage.IsTestFailed(err) { + t.Fatalf("Expect a Test Failed (write conflict) error, got: %v", err) + } +} + +type getFunc func(ctx context.Context, key string, opts *etcd.GetOptions) (*etcd.Response, error) + +type fakeDeleteKeysAPI struct { + etcd.KeysAPI + fakeGetFunc getFunc + getCount int + // The fakeGetFunc will be called fakeGetCap times before the KeysAPI's Get will be called. + fakeGetCap int +} + +func (f *fakeDeleteKeysAPI) Get(ctx context.Context, key string, opts *etcd.GetOptions) (*etcd.Response, error) { + f.getCount++ + if f.getCount < f.fakeGetCap { + return f.fakeGetFunc(ctx, key, opts) + } + return f.KeysAPI.Get(ctx, key, opts) +} + +// This is to emulate the case where another party updates the object when +// etcdHelper.Delete has verified the preconditions, but hasn't carried out the +// deletion yet. Etcd will fail the deletion and report the conflict. etcdHelper +// should retry until there is no conflict. +func TestDeleteWithRetry(t *testing.T) { + server := etcdtesting.NewEtcdTestClientServer(t) + defer server.Terminate(t) + prefix := path.Join("/", etcdtest.PathPrefix()) + + obj := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", UID: "A"}} + // fakeGet returns a large ModifiedIndex to emulate the case that another + // party has updated the object. + fakeGet := func(ctx context.Context, key string, opts *etcd.GetOptions) (*etcd.Response, error) { + data, _ := runtime.Encode(testapi.Default.Codec(), obj) + return &etcd.Response{Node: &etcd.Node{Value: string(data), ModifiedIndex: 99}}, nil + } + expectedRetries := 3 + helper := newEtcdHelper(server.Client, testapi.Default.Codec(), prefix) + fake := &fakeDeleteKeysAPI{KeysAPI: helper.etcdKeysAPI, fakeGetCap: expectedRetries, fakeGetFunc: fakeGet} + helper.etcdKeysAPI = fake + + returnedObj := &api.Pod{} + err := helper.Create(context.TODO(), "/some/key", obj, returnedObj, 0) + if err != nil { + t.Errorf("Unexpected error %#v", err) + } + + err = helper.Delete(context.TODO(), "/some/key", obj, storage.NewUIDPreconditions("A")) + if err != nil { + t.Errorf("Unexpected error %#v", err) + } + if fake.getCount != expectedRetries { + t.Errorf("Expect %d retries, got %d", expectedRetries, fake.getCount) + } + err = helper.Get(context.TODO(), "/some/key", obj, false) + if !storage.IsNotFound(err) { + t.Errorf("Expect an NotFound error, got %v", err) + } +} diff --git a/pkg/storage/etcd/etcd_watcher.go b/pkg/storage/etcd/etcd_watcher.go index eff5ed6e6c1..6d574a0cd59 100644 --- a/pkg/storage/etcd/etcd_watcher.go +++ b/pkg/storage/etcd/etcd_watcher.go @@ -42,6 +42,7 @@ const ( EtcdSet = "set" EtcdCAS = "compareAndSwap" EtcdDelete = "delete" + EtcdCAD = "compareAndDelete" EtcdExpire = "expire" ) @@ -450,7 +451,7 @@ func (w *etcdWatcher) sendResult(res *etcd.Response) { w.sendAdd(res) case EtcdSet, EtcdCAS: w.sendModify(res) - case EtcdDelete, EtcdExpire: + case EtcdDelete, EtcdExpire, EtcdCAD: w.sendDelete(res) default: utilruntime.HandleError(fmt.Errorf("unknown action: %v", res.Action)) diff --git a/pkg/storage/interfaces.go b/pkg/storage/interfaces.go index 7c5b1539564..7ac812bd451 100644 --- a/pkg/storage/interfaces.go +++ b/pkg/storage/interfaces.go @@ -21,6 +21,7 @@ import ( "golang.org/x/net/context" "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/types" "k8s.io/kubernetes/pkg/watch" ) @@ -69,6 +70,18 @@ func Everything(runtime.Object) bool { // See the comment for GuaranteedUpdate for more details. type UpdateFunc func(input runtime.Object, res ResponseMeta) (output runtime.Object, ttl *uint64, err error) +// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out. +type Preconditions struct { + // Specifies the target UID. + UID *types.UID `json:"uid,omitempty"` +} + +// NewUIDPreconditions returns a Preconditions with UID set. +func NewUIDPreconditions(uid string) *Preconditions { + u := types.UID(uid) + return &Preconditions{UID: &u} +} + // Interface offers a common interface for object marshaling/unmarshling operations and // hides all the storage-related operations behind it. type Interface interface { @@ -91,7 +104,7 @@ type Interface interface { Set(ctx context.Context, key string, obj, out runtime.Object, ttl uint64) error // Delete removes the specified key and returns the value that existed at that spot. - Delete(ctx context.Context, key string, out runtime.Object) error + Delete(ctx context.Context, key string, out runtime.Object, preconditions *Preconditions) error // Watch begins watching the specified key. Events are decoded into API objects, // and any items passing 'filter' are sent down to returned watch.Interface. @@ -146,7 +159,7 @@ type Interface interface { // return cur, nil, nil // } // }) - GuaranteedUpdate(ctx context.Context, key string, ptrToType runtime.Object, ignoreNotFound bool, tryUpdate UpdateFunc) error + GuaranteedUpdate(ctx context.Context, key string, ptrToType runtime.Object, ignoreNotFound bool, precondtions *Preconditions, tryUpdate UpdateFunc) error // Codec provides access to the underlying codec being used by the implementation. Codec() runtime.Codec diff --git a/pkg/storage/util.go b/pkg/storage/util.go index 43056c3da01..c8f3b02f198 100644 --- a/pkg/storage/util.go +++ b/pkg/storage/util.go @@ -20,9 +20,7 @@ import ( "fmt" "strconv" - "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/meta" - "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util/validation/field" @@ -48,7 +46,7 @@ func ParseWatchResourceVersion(resourceVersion string) (uint64, error) { } version, err := strconv.ParseUint(resourceVersion, 10, 64) if err != nil { - return 0, errors.NewInvalid(unversioned.GroupKind{}, "", field.ErrorList{ + return 0, NewInvalidError(field.ErrorList{ // Validation errors are supposed to return version-specific field // paths, but this is probably close enough. field.Invalid(field.NewPath("resourceVersion"), resourceVersion, err.Error()), diff --git a/pkg/storage/util_test.go b/pkg/storage/util_test.go index d24ae5d5f75..7d0675cceed 100644 --- a/pkg/storage/util_test.go +++ b/pkg/storage/util_test.go @@ -16,11 +16,7 @@ limitations under the License. package storage -import ( - "testing" - - "k8s.io/kubernetes/pkg/api/errors" -) +import "testing" func TestEtcdParseWatchResourceVersion(t *testing.T) { testCases := []struct { @@ -42,7 +38,7 @@ func TestEtcdParseWatchResourceVersion(t *testing.T) { t.Errorf("%s: unexpected non-error", testCase.Version) continue } - if !errors.IsInvalid(err) { + if !IsInvalidError(err) { t.Errorf("%s: unexpected error: %v", testCase.Version, err) continue } diff --git a/test/integration/etcd_tools_test.go b/test/integration/etcd_tools_test.go index 7964bbd818b..073cf176685 100644 --- a/test/integration/etcd_tools_test.go +++ b/test/integration/etcd_tools_test.go @@ -98,7 +98,7 @@ func TestWriteTTL(t *testing.T) { t.Fatalf("unexpected error: %v", err) } result := &api.ServiceAccount{} - err := etcdStorage.GuaranteedUpdate(ctx, key, result, false, func(obj runtime.Object, res storage.ResponseMeta) (runtime.Object, *uint64, error) { + err := etcdStorage.GuaranteedUpdate(ctx, key, result, false, nil, func(obj runtime.Object, res storage.ResponseMeta) (runtime.Object, *uint64, error) { if in, ok := obj.(*api.ServiceAccount); !ok || in.Name != "foo" { t.Fatalf("unexpected existing object: %v", obj) } @@ -120,7 +120,7 @@ func TestWriteTTL(t *testing.T) { } result = &api.ServiceAccount{} - err = etcdStorage.GuaranteedUpdate(ctx, key, result, false, func(obj runtime.Object, res storage.ResponseMeta) (runtime.Object, *uint64, error) { + err = etcdStorage.GuaranteedUpdate(ctx, key, result, false, nil, func(obj runtime.Object, res storage.ResponseMeta) (runtime.Object, *uint64, error) { if in, ok := obj.(*api.ServiceAccount); !ok || in.Name != "out" { t.Fatalf("unexpected existing object: %v", obj) }