Merge pull request #124012 from Jefftree/le-controller

Coordinated Leader Election
This commit is contained in:
Kubernetes Prow Robot 2024-07-25 13:05:53 -07:00 committed by GitHub
commit 5f5c02da51
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
137 changed files with 12173 additions and 119 deletions

View File

@ -1631,6 +1631,32 @@
}
],
"version": "v1"
},
{
"freshness": "Current",
"resources": [
{
"resource": "leasecandidates",
"responseKind": {
"group": "",
"kind": "LeaseCandidate",
"version": ""
},
"scope": "Namespaced",
"singularResource": "leasecandidate",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
}
],
"version": "v1alpha1"
}
]
},

View File

@ -1631,6 +1631,32 @@
}
],
"version": "v1"
},
{
"freshness": "Current",
"resources": [
{
"resource": "leasecandidates",
"responseKind": {
"group": "",
"kind": "LeaseCandidate",
"version": ""
},
"scope": "Namespaced",
"singularResource": "leasecandidate",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
}
],
"version": "v1alpha1"
}
]
},

View File

@ -242,6 +242,10 @@
{
"groupVersion": "coordination.k8s.io/v1",
"version": "v1"
},
{
"groupVersion": "coordination.k8s.io/v1alpha1",
"version": "v1alpha1"
}
]
},

View File

@ -10,6 +10,10 @@
{
"groupVersion": "coordination.k8s.io/v1",
"version": "v1"
},
{
"groupVersion": "coordination.k8s.io/v1alpha1",
"version": "v1alpha1"
}
]
}

View File

@ -0,0 +1,24 @@
{
"apiVersion": "v1",
"groupVersion": "coordination.k8s.io/v1alpha1",
"kind": "APIResourceList",
"resources": [
{
"kind": "LeaseCandidate",
"name": "leasecandidates",
"namespaced": true,
"singularName": "leasecandidate",
"storageVersionHash": "VM2ILh8OBCI=",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
}
]
}

View File

@ -5423,11 +5423,11 @@
"description": "acquireTime is a time when the current lease was acquired."
},
"holderIdentity": {
"description": "holderIdentity contains the identity of the holder of a current lease.",
"description": "holderIdentity contains the identity of the holder of a current lease. If Coordinated Leader Election is used, the holder identity must be equal to the elected LeaseCandidate.metadata.name field.",
"type": "string"
},
"leaseDurationSeconds": {
"description": "leaseDurationSeconds is a duration that candidates for a lease need to wait to force acquire it. This is measure against time of last observed renewTime.",
"description": "leaseDurationSeconds is a duration that candidates for a lease need to wait to force acquire it. This is measured against the time of last observed renewTime.",
"format": "int32",
"type": "integer"
},
@ -5436,13 +5436,123 @@
"format": "int32",
"type": "integer"
},
"preferredHolder": {
"description": "PreferredHolder signals to a lease holder that the lease has a more optimal holder and should be given up. This field can only be set if Strategy is also set.",
"type": "string"
},
"renewTime": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.MicroTime",
"description": "renewTime is a time when the current holder of a lease has last updated the lease."
},
"strategy": {
"description": "Strategy indicates the strategy for picking the leader for coordinated leader election. If the field is not specified, there is no active coordination for this lease. (Alpha) Using this field requires the CoordinatedLeaderElection feature gate to be enabled.",
"type": "string"
}
},
"type": "object"
},
"io.k8s.api.coordination.v1alpha1.LeaseCandidate": {
"description": "LeaseCandidate defines a candidate for a Lease object. Candidates are created such that coordinated leader election will pick the best leader from the list of candidates.",
"properties": {
"apiVersion": {
"description": "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: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
"type": "string"
},
"kind": {
"description": "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: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
"type": "string"
},
"metadata": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta",
"description": "More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata"
},
"spec": {
"$ref": "#/definitions/io.k8s.api.coordination.v1alpha1.LeaseCandidateSpec",
"description": "spec contains the specification of the Lease. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status"
}
},
"type": "object",
"x-kubernetes-group-version-kind": [
{
"group": "coordination.k8s.io",
"kind": "LeaseCandidate",
"version": "v1alpha1"
}
]
},
"io.k8s.api.coordination.v1alpha1.LeaseCandidateList": {
"description": "LeaseCandidateList is a list of Lease objects.",
"properties": {
"apiVersion": {
"description": "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: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
"type": "string"
},
"items": {
"description": "items is a list of schema objects.",
"items": {
"$ref": "#/definitions/io.k8s.api.coordination.v1alpha1.LeaseCandidate"
},
"type": "array"
},
"kind": {
"description": "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: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
"type": "string"
},
"metadata": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta",
"description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata"
}
},
"required": [
"items"
],
"type": "object",
"x-kubernetes-group-version-kind": [
{
"group": "coordination.k8s.io",
"kind": "LeaseCandidateList",
"version": "v1alpha1"
}
]
},
"io.k8s.api.coordination.v1alpha1.LeaseCandidateSpec": {
"description": "LeaseCandidateSpec is a specification of a Lease.",
"properties": {
"binaryVersion": {
"description": "BinaryVersion is the binary version. It must be in a semver format without leading `v`. This field is required when strategy is \"OldestEmulationVersion\"",
"type": "string"
},
"emulationVersion": {
"description": "EmulationVersion is the emulation version. It must be in a semver format without leading `v`. EmulationVersion must be less than or equal to BinaryVersion. This field is required when strategy is \"OldestEmulationVersion\"",
"type": "string"
},
"leaseName": {
"description": "LeaseName is the name of the lease for which this candidate is contending. This field is immutable.",
"type": "string"
},
"pingTime": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.MicroTime",
"description": "PingTime is the last time that the server has requested the LeaseCandidate to renew. It is only done during leader election to check if any LeaseCandidates have become ineligible. When PingTime is updated, the LeaseCandidate will respond by updating RenewTime."
},
"preferredStrategies": {
"description": "PreferredStrategies indicates the list of strategies for picking the leader for coordinated leader election. The list is ordered, and the first strategy supersedes all other strategies. The list is used by coordinated leader election to make a decision about the final election strategy. This follows as - If all clients have strategy X as the first element in this list, strategy X will be used. - If a candidate has strategy [X] and another candidate has strategy [Y, X], Y supersedes X and strategy Y\n will be used.\n- If a candidate has strategy [X, Y] and another candidate has strategy [Y, X], this is a user error and leader\n election will not operate the Lease until resolved.\n(Alpha) Using this field requires the CoordinatedLeaderElection feature gate to be enabled.",
"items": {
"type": "string"
},
"type": "array",
"x-kubernetes-list-type": "atomic"
},
"renewTime": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.MicroTime",
"description": "RenewTime is the time that the LeaseCandidate was last updated. Any time a Lease needs to do leader election, the PingTime field is updated to signal to the LeaseCandidate that they should update the RenewTime. Old LeaseCandidate objects are also garbage collected if it has been hours since the last renew. The PingTime field is updated regularly to prevent garbage collection for still active LeaseCandidates."
}
},
"required": [
"leaseName",
"preferredStrategies"
],
"type": "object"
},
"io.k8s.api.core.v1.AWSElasticBlockStoreVolumeSource": {
"description": "Represents a Persistent Disk resource in AWS.\n\nAn AWS EBS disk must exist before mounting to a container. The disk must also be in the same AWS zone as the kubelet. An AWS EBS disk can only be mounted as read/write once. AWS EBS volumes support ownership management and SELinux relabeling.",
"properties": {
@ -18127,6 +18237,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -18869,6 +18984,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
@ -55864,6 +55984,834 @@
}
]
},
"/apis/coordination.k8s.io/v1alpha1/": {
"get": {
"consumes": [
"application/json",
"application/yaml",
"application/vnd.kubernetes.protobuf"
],
"description": "get available resources",
"operationId": "getCoordinationV1alpha1APIResources",
"produces": [
"application/json",
"application/yaml",
"application/vnd.kubernetes.protobuf"
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList"
}
},
"401": {
"description": "Unauthorized"
}
},
"schemes": [
"https"
],
"tags": [
"coordination_v1alpha1"
]
}
},
"/apis/coordination.k8s.io/v1alpha1/leasecandidates": {
"get": {
"consumes": [
"*/*"
],
"description": "list or watch objects of kind LeaseCandidate",
"operationId": "listCoordinationV1alpha1LeaseCandidateForAllNamespaces",
"produces": [
"application/json",
"application/yaml",
"application/vnd.kubernetes.protobuf",
"application/json;stream=watch",
"application/vnd.kubernetes.protobuf;stream=watch"
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/io.k8s.api.coordination.v1alpha1.LeaseCandidateList"
}
},
"401": {
"description": "Unauthorized"
}
},
"schemes": [
"https"
],
"tags": [
"coordination_v1alpha1"
],
"x-kubernetes-action": "list",
"x-kubernetes-group-version-kind": {
"group": "coordination.k8s.io",
"kind": "LeaseCandidate",
"version": "v1alpha1"
}
},
"parameters": [
{
"$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J"
},
{
"$ref": "#/parameters/continue-QfD61s0i"
},
{
"$ref": "#/parameters/fieldSelector-xIcQKXFG"
},
{
"$ref": "#/parameters/labelSelector-5Zw57w4C"
},
{
"$ref": "#/parameters/limit-1NfNmdNH"
},
{
"$ref": "#/parameters/pretty-tJGM1-ng"
},
{
"$ref": "#/parameters/resourceVersion-5WAnf1kx"
},
{
"$ref": "#/parameters/resourceVersionMatch-t8XhRHeC"
},
{
"$ref": "#/parameters/sendInitialEvents-rLXlEK_k"
},
{
"$ref": "#/parameters/timeoutSeconds-yvYezaOC"
},
{
"$ref": "#/parameters/watch-XNNPZGbK"
}
]
},
"/apis/coordination.k8s.io/v1alpha1/namespaces/{namespace}/leasecandidates": {
"delete": {
"consumes": [
"*/*"
],
"description": "delete collection of LeaseCandidate",
"operationId": "deleteCoordinationV1alpha1CollectionNamespacedLeaseCandidate",
"parameters": [
{
"$ref": "#/parameters/body-2Y1dVQaQ"
},
{
"$ref": "#/parameters/continue-QfD61s0i"
},
{
"description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed",
"in": "query",
"name": "dryRun",
"type": "string",
"uniqueItems": true
},
{
"$ref": "#/parameters/fieldSelector-xIcQKXFG"
},
{
"$ref": "#/parameters/gracePeriodSeconds--K5HaBOS"
},
{
"$ref": "#/parameters/labelSelector-5Zw57w4C"
},
{
"$ref": "#/parameters/limit-1NfNmdNH"
},
{
"$ref": "#/parameters/orphanDependents-uRB25kX5"
},
{
"$ref": "#/parameters/propagationPolicy-6jk3prlO"
},
{
"$ref": "#/parameters/resourceVersion-5WAnf1kx"
},
{
"$ref": "#/parameters/resourceVersionMatch-t8XhRHeC"
},
{
"$ref": "#/parameters/sendInitialEvents-rLXlEK_k"
},
{
"$ref": "#/parameters/timeoutSeconds-yvYezaOC"
}
],
"produces": [
"application/json",
"application/yaml",
"application/vnd.kubernetes.protobuf"
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status"
}
},
"401": {
"description": "Unauthorized"
}
},
"schemes": [
"https"
],
"tags": [
"coordination_v1alpha1"
],
"x-kubernetes-action": "deletecollection",
"x-kubernetes-group-version-kind": {
"group": "coordination.k8s.io",
"kind": "LeaseCandidate",
"version": "v1alpha1"
}
},
"get": {
"consumes": [
"*/*"
],
"description": "list or watch objects of kind LeaseCandidate",
"operationId": "listCoordinationV1alpha1NamespacedLeaseCandidate",
"parameters": [
{
"$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J"
},
{
"$ref": "#/parameters/continue-QfD61s0i"
},
{
"$ref": "#/parameters/fieldSelector-xIcQKXFG"
},
{
"$ref": "#/parameters/labelSelector-5Zw57w4C"
},
{
"$ref": "#/parameters/limit-1NfNmdNH"
},
{
"$ref": "#/parameters/resourceVersion-5WAnf1kx"
},
{
"$ref": "#/parameters/resourceVersionMatch-t8XhRHeC"
},
{
"$ref": "#/parameters/sendInitialEvents-rLXlEK_k"
},
{
"$ref": "#/parameters/timeoutSeconds-yvYezaOC"
},
{
"$ref": "#/parameters/watch-XNNPZGbK"
}
],
"produces": [
"application/json",
"application/yaml",
"application/vnd.kubernetes.protobuf",
"application/json;stream=watch",
"application/vnd.kubernetes.protobuf;stream=watch"
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/io.k8s.api.coordination.v1alpha1.LeaseCandidateList"
}
},
"401": {
"description": "Unauthorized"
}
},
"schemes": [
"https"
],
"tags": [
"coordination_v1alpha1"
],
"x-kubernetes-action": "list",
"x-kubernetes-group-version-kind": {
"group": "coordination.k8s.io",
"kind": "LeaseCandidate",
"version": "v1alpha1"
}
},
"parameters": [
{
"$ref": "#/parameters/namespace-vgWSWtn3"
},
{
"$ref": "#/parameters/pretty-tJGM1-ng"
}
],
"post": {
"consumes": [
"*/*"
],
"description": "create a LeaseCandidate",
"operationId": "createCoordinationV1alpha1NamespacedLeaseCandidate",
"parameters": [
{
"in": "body",
"name": "body",
"required": true,
"schema": {
"$ref": "#/definitions/io.k8s.api.coordination.v1alpha1.LeaseCandidate"
}
},
{
"description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed",
"in": "query",
"name": "dryRun",
"type": "string",
"uniqueItems": true
},
{
"$ref": "#/parameters/fieldManager-Qy4HdaTW"
},
{
"description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.",
"in": "query",
"name": "fieldValidation",
"type": "string",
"uniqueItems": true
}
],
"produces": [
"application/json",
"application/yaml",
"application/vnd.kubernetes.protobuf"
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/io.k8s.api.coordination.v1alpha1.LeaseCandidate"
}
},
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/io.k8s.api.coordination.v1alpha1.LeaseCandidate"
}
},
"202": {
"description": "Accepted",
"schema": {
"$ref": "#/definitions/io.k8s.api.coordination.v1alpha1.LeaseCandidate"
}
},
"401": {
"description": "Unauthorized"
}
},
"schemes": [
"https"
],
"tags": [
"coordination_v1alpha1"
],
"x-kubernetes-action": "post",
"x-kubernetes-group-version-kind": {
"group": "coordination.k8s.io",
"kind": "LeaseCandidate",
"version": "v1alpha1"
}
}
},
"/apis/coordination.k8s.io/v1alpha1/namespaces/{namespace}/leasecandidates/{name}": {
"delete": {
"consumes": [
"*/*"
],
"description": "delete a LeaseCandidate",
"operationId": "deleteCoordinationV1alpha1NamespacedLeaseCandidate",
"parameters": [
{
"$ref": "#/parameters/body-2Y1dVQaQ"
},
{
"description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed",
"in": "query",
"name": "dryRun",
"type": "string",
"uniqueItems": true
},
{
"$ref": "#/parameters/gracePeriodSeconds--K5HaBOS"
},
{
"$ref": "#/parameters/orphanDependents-uRB25kX5"
},
{
"$ref": "#/parameters/propagationPolicy-6jk3prlO"
}
],
"produces": [
"application/json",
"application/yaml",
"application/vnd.kubernetes.protobuf"
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status"
}
},
"202": {
"description": "Accepted",
"schema": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status"
}
},
"401": {
"description": "Unauthorized"
}
},
"schemes": [
"https"
],
"tags": [
"coordination_v1alpha1"
],
"x-kubernetes-action": "delete",
"x-kubernetes-group-version-kind": {
"group": "coordination.k8s.io",
"kind": "LeaseCandidate",
"version": "v1alpha1"
}
},
"get": {
"consumes": [
"*/*"
],
"description": "read the specified LeaseCandidate",
"operationId": "readCoordinationV1alpha1NamespacedLeaseCandidate",
"produces": [
"application/json",
"application/yaml",
"application/vnd.kubernetes.protobuf"
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/io.k8s.api.coordination.v1alpha1.LeaseCandidate"
}
},
"401": {
"description": "Unauthorized"
}
},
"schemes": [
"https"
],
"tags": [
"coordination_v1alpha1"
],
"x-kubernetes-action": "get",
"x-kubernetes-group-version-kind": {
"group": "coordination.k8s.io",
"kind": "LeaseCandidate",
"version": "v1alpha1"
}
},
"parameters": [
{
"description": "name of the LeaseCandidate",
"in": "path",
"name": "name",
"required": true,
"type": "string",
"uniqueItems": true
},
{
"$ref": "#/parameters/namespace-vgWSWtn3"
},
{
"$ref": "#/parameters/pretty-tJGM1-ng"
}
],
"patch": {
"consumes": [
"application/json-patch+json",
"application/merge-patch+json",
"application/strategic-merge-patch+json",
"application/apply-patch+yaml"
],
"description": "partially update the specified LeaseCandidate",
"operationId": "patchCoordinationV1alpha1NamespacedLeaseCandidate",
"parameters": [
{
"$ref": "#/parameters/body-78PwaGsr"
},
{
"description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed",
"in": "query",
"name": "dryRun",
"type": "string",
"uniqueItems": true
},
{
"$ref": "#/parameters/fieldManager-7c6nTn1T"
},
{
"description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.",
"in": "query",
"name": "fieldValidation",
"type": "string",
"uniqueItems": true
},
{
"$ref": "#/parameters/force-tOGGb0Yi"
}
],
"produces": [
"application/json",
"application/yaml",
"application/vnd.kubernetes.protobuf"
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/io.k8s.api.coordination.v1alpha1.LeaseCandidate"
}
},
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/io.k8s.api.coordination.v1alpha1.LeaseCandidate"
}
},
"401": {
"description": "Unauthorized"
}
},
"schemes": [
"https"
],
"tags": [
"coordination_v1alpha1"
],
"x-kubernetes-action": "patch",
"x-kubernetes-group-version-kind": {
"group": "coordination.k8s.io",
"kind": "LeaseCandidate",
"version": "v1alpha1"
}
},
"put": {
"consumes": [
"*/*"
],
"description": "replace the specified LeaseCandidate",
"operationId": "replaceCoordinationV1alpha1NamespacedLeaseCandidate",
"parameters": [
{
"in": "body",
"name": "body",
"required": true,
"schema": {
"$ref": "#/definitions/io.k8s.api.coordination.v1alpha1.LeaseCandidate"
}
},
{
"description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed",
"in": "query",
"name": "dryRun",
"type": "string",
"uniqueItems": true
},
{
"$ref": "#/parameters/fieldManager-Qy4HdaTW"
},
{
"description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.",
"in": "query",
"name": "fieldValidation",
"type": "string",
"uniqueItems": true
}
],
"produces": [
"application/json",
"application/yaml",
"application/vnd.kubernetes.protobuf"
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/io.k8s.api.coordination.v1alpha1.LeaseCandidate"
}
},
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/io.k8s.api.coordination.v1alpha1.LeaseCandidate"
}
},
"401": {
"description": "Unauthorized"
}
},
"schemes": [
"https"
],
"tags": [
"coordination_v1alpha1"
],
"x-kubernetes-action": "put",
"x-kubernetes-group-version-kind": {
"group": "coordination.k8s.io",
"kind": "LeaseCandidate",
"version": "v1alpha1"
}
}
},
"/apis/coordination.k8s.io/v1alpha1/watch/leasecandidates": {
"get": {
"consumes": [
"*/*"
],
"description": "watch individual changes to a list of LeaseCandidate. deprecated: use the 'watch' parameter with a list operation instead.",
"operationId": "watchCoordinationV1alpha1LeaseCandidateListForAllNamespaces",
"produces": [
"application/json",
"application/yaml",
"application/vnd.kubernetes.protobuf",
"application/json;stream=watch",
"application/vnd.kubernetes.protobuf;stream=watch"
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent"
}
},
"401": {
"description": "Unauthorized"
}
},
"schemes": [
"https"
],
"tags": [
"coordination_v1alpha1"
],
"x-kubernetes-action": "watchlist",
"x-kubernetes-group-version-kind": {
"group": "coordination.k8s.io",
"kind": "LeaseCandidate",
"version": "v1alpha1"
}
},
"parameters": [
{
"$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J"
},
{
"$ref": "#/parameters/continue-QfD61s0i"
},
{
"$ref": "#/parameters/fieldSelector-xIcQKXFG"
},
{
"$ref": "#/parameters/labelSelector-5Zw57w4C"
},
{
"$ref": "#/parameters/limit-1NfNmdNH"
},
{
"$ref": "#/parameters/pretty-tJGM1-ng"
},
{
"$ref": "#/parameters/resourceVersion-5WAnf1kx"
},
{
"$ref": "#/parameters/resourceVersionMatch-t8XhRHeC"
},
{
"$ref": "#/parameters/sendInitialEvents-rLXlEK_k"
},
{
"$ref": "#/parameters/timeoutSeconds-yvYezaOC"
},
{
"$ref": "#/parameters/watch-XNNPZGbK"
}
]
},
"/apis/coordination.k8s.io/v1alpha1/watch/namespaces/{namespace}/leasecandidates": {
"get": {
"consumes": [
"*/*"
],
"description": "watch individual changes to a list of LeaseCandidate. deprecated: use the 'watch' parameter with a list operation instead.",
"operationId": "watchCoordinationV1alpha1NamespacedLeaseCandidateList",
"produces": [
"application/json",
"application/yaml",
"application/vnd.kubernetes.protobuf",
"application/json;stream=watch",
"application/vnd.kubernetes.protobuf;stream=watch"
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent"
}
},
"401": {
"description": "Unauthorized"
}
},
"schemes": [
"https"
],
"tags": [
"coordination_v1alpha1"
],
"x-kubernetes-action": "watchlist",
"x-kubernetes-group-version-kind": {
"group": "coordination.k8s.io",
"kind": "LeaseCandidate",
"version": "v1alpha1"
}
},
"parameters": [
{
"$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J"
},
{
"$ref": "#/parameters/continue-QfD61s0i"
},
{
"$ref": "#/parameters/fieldSelector-xIcQKXFG"
},
{
"$ref": "#/parameters/labelSelector-5Zw57w4C"
},
{
"$ref": "#/parameters/limit-1NfNmdNH"
},
{
"$ref": "#/parameters/namespace-vgWSWtn3"
},
{
"$ref": "#/parameters/pretty-tJGM1-ng"
},
{
"$ref": "#/parameters/resourceVersion-5WAnf1kx"
},
{
"$ref": "#/parameters/resourceVersionMatch-t8XhRHeC"
},
{
"$ref": "#/parameters/sendInitialEvents-rLXlEK_k"
},
{
"$ref": "#/parameters/timeoutSeconds-yvYezaOC"
},
{
"$ref": "#/parameters/watch-XNNPZGbK"
}
]
},
"/apis/coordination.k8s.io/v1alpha1/watch/namespaces/{namespace}/leasecandidates/{name}": {
"get": {
"consumes": [
"*/*"
],
"description": "watch changes to an object of kind LeaseCandidate. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.",
"operationId": "watchCoordinationV1alpha1NamespacedLeaseCandidate",
"produces": [
"application/json",
"application/yaml",
"application/vnd.kubernetes.protobuf",
"application/json;stream=watch",
"application/vnd.kubernetes.protobuf;stream=watch"
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent"
}
},
"401": {
"description": "Unauthorized"
}
},
"schemes": [
"https"
],
"tags": [
"coordination_v1alpha1"
],
"x-kubernetes-action": "watch",
"x-kubernetes-group-version-kind": {
"group": "coordination.k8s.io",
"kind": "LeaseCandidate",
"version": "v1alpha1"
}
},
"parameters": [
{
"$ref": "#/parameters/allowWatchBookmarks-HC2hJt-J"
},
{
"$ref": "#/parameters/continue-QfD61s0i"
},
{
"$ref": "#/parameters/fieldSelector-xIcQKXFG"
},
{
"$ref": "#/parameters/labelSelector-5Zw57w4C"
},
{
"$ref": "#/parameters/limit-1NfNmdNH"
},
{
"description": "name of the LeaseCandidate",
"in": "path",
"name": "name",
"required": true,
"type": "string",
"uniqueItems": true
},
{
"$ref": "#/parameters/namespace-vgWSWtn3"
},
{
"$ref": "#/parameters/pretty-tJGM1-ng"
},
{
"$ref": "#/parameters/resourceVersion-5WAnf1kx"
},
{
"$ref": "#/parameters/resourceVersionMatch-t8XhRHeC"
},
{
"$ref": "#/parameters/sendInitialEvents-rLXlEK_k"
},
{
"$ref": "#/parameters/timeoutSeconds-yvYezaOC"
},
{
"$ref": "#/parameters/watch-XNNPZGbK"
}
]
},
"/apis/discovery.k8s.io/": {
"get": {
"consumes": [

View File

@ -8963,6 +8963,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -9704,6 +9709,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -1443,6 +1443,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -2179,6 +2184,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -955,6 +955,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1691,6 +1696,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -957,6 +957,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1693,6 +1698,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -1166,6 +1166,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1845,6 +1850,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -299,6 +299,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -978,6 +983,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -5663,6 +5663,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -6399,6 +6404,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -492,6 +492,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1171,6 +1176,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -1144,6 +1144,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1880,6 +1885,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -4867,6 +4867,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -5603,6 +5608,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -528,6 +528,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1207,6 +1212,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -404,6 +404,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1083,6 +1088,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -97,11 +97,11 @@
"description": "acquireTime is a time when the current lease was acquired."
},
"holderIdentity": {
"description": "holderIdentity contains the identity of the holder of a current lease.",
"description": "holderIdentity contains the identity of the holder of a current lease. If Coordinated Leader Election is used, the holder identity must be equal to the elected LeaseCandidate.metadata.name field.",
"type": "string"
},
"leaseDurationSeconds": {
"description": "leaseDurationSeconds is a duration that candidates for a lease need to wait to force acquire it. This is measure against time of last observed renewTime.",
"description": "leaseDurationSeconds is a duration that candidates for a lease need to wait to force acquire it. This is measured against the time of last observed renewTime.",
"format": "int32",
"type": "integer"
},
@ -110,6 +110,10 @@
"format": "int32",
"type": "integer"
},
"preferredHolder": {
"description": "PreferredHolder signals to a lease holder that the lease has a more optimal holder and should be given up. This field can only be set if Strategy is also set.",
"type": "string"
},
"renewTime": {
"allOf": [
{
@ -117,6 +121,10 @@
}
],
"description": "renewTime is a time when the current holder of a lease has last updated the lease."
},
"strategy": {
"description": "Strategy indicates the strategy for picking the leader for coordinated leader election. If the field is not specified, there is no active coordination for this lease. (Alpha) Using this field requires the CoordinatedLeaderElection feature gate to be enabled.",
"type": "string"
}
},
"type": "object"
@ -419,6 +427,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1103,6 +1116,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

File diff suppressed because it is too large Load Diff

View File

@ -582,6 +582,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1261,6 +1266,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -537,6 +537,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1221,6 +1226,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -1028,6 +1028,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1707,6 +1712,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -1029,6 +1029,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1708,6 +1713,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -518,6 +518,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1197,6 +1202,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -1100,6 +1100,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1836,6 +1841,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -604,6 +604,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1283,6 +1288,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -477,6 +477,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1156,6 +1161,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -539,6 +539,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1275,6 +1280,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -812,6 +812,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1548,6 +1553,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -1536,6 +1536,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -2215,6 +2220,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -395,6 +395,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1074,6 +1079,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -2257,6 +2257,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -2993,6 +2998,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -390,6 +390,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1069,6 +1074,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -390,6 +390,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1069,6 +1074,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -502,6 +502,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "DeleteOptions",
@ -1181,6 +1186,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",
"version": "v1alpha1"
},
{
"group": "coordination.k8s.io",
"kind": "WatchEvent",

View File

@ -28,8 +28,9 @@ import (
"sort"
"time"
"github.com/blang/semver/v4"
"github.com/spf13/cobra"
coordinationv1 "k8s.io/api/coordination/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -78,6 +79,7 @@ import (
kubectrlmgrconfig "k8s.io/kubernetes/pkg/controller/apis/config"
garbagecollector "k8s.io/kubernetes/pkg/controller/garbagecollector"
serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
kubefeatures "k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/serviceaccount"
)
@ -290,6 +292,34 @@ func Run(ctx context.Context, c *config.CompletedConfig) error {
}
}
if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.CoordinatedLeaderElection) {
binaryVersion, err := semver.ParseTolerant(utilversion.DefaultComponentGlobalsRegistry.EffectiveVersionFor(utilversion.DefaultKubeComponent).BinaryVersion().String())
if err != nil {
return err
}
emulationVersion, err := semver.ParseTolerant(utilversion.DefaultComponentGlobalsRegistry.EffectiveVersionFor(utilversion.DefaultKubeComponent).EmulationVersion().String())
if err != nil {
return err
}
// Start lease candidate controller for coordinated leader election
leaseCandidate, waitForSync, err := leaderelection.NewCandidate(
c.Client,
"kube-system",
id,
"kube-controller-manager",
binaryVersion.FinalizeVersion(),
emulationVersion.FinalizeVersion(),
[]coordinationv1.CoordinatedLeaseStrategy{coordinationv1.OldestEmulationVersion},
)
if err != nil {
return err
}
healthzHandler.AddHealthChecker(healthz.NewInformerSyncHealthz(waitForSync))
go leaseCandidate.Run(ctx)
}
// Start the main lock
go leaderElectAndRun(ctx, c, id, electionChecker,
c.ComponentConfig.Generic.LeaderElection.ResourceLock,
@ -886,6 +916,7 @@ func leaderElectAndRun(ctx context.Context, c *config.CompletedConfig, lockIdent
Callbacks: callbacks,
WatchDog: electionChecker,
Name: leaseName,
Coordinated: utilfeature.DefaultFeatureGate.Enabled(kubefeatures.CoordinatedLeaderElection),
})
panic("unreachable")

View File

@ -24,8 +24,10 @@ import (
"os"
goruntime "runtime"
"github.com/blang/semver/v4"
"github.com/spf13/cobra"
coordinationv1 "k8s.io/api/coordination/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/authentication/authenticator"
@ -58,6 +60,7 @@ import (
"k8s.io/klog/v2"
schedulerserverconfig "k8s.io/kubernetes/cmd/kube-scheduler/app/config"
"k8s.io/kubernetes/cmd/kube-scheduler/app/options"
kubefeatures "k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/scheduler"
kubeschedulerconfig "k8s.io/kubernetes/pkg/scheduler/apis/config"
"k8s.io/kubernetes/pkg/scheduler/apis/config/latest"
@ -207,6 +210,33 @@ func Run(ctx context.Context, cc *schedulerserverconfig.CompletedConfig, sched *
})
readyzChecks = append(readyzChecks, handlerSyncCheck)
if cc.LeaderElection != nil && utilfeature.DefaultFeatureGate.Enabled(kubefeatures.CoordinatedLeaderElection) {
binaryVersion, err := semver.ParseTolerant(utilversion.DefaultComponentGlobalsRegistry.EffectiveVersionFor(utilversion.DefaultKubeComponent).BinaryVersion().String())
if err != nil {
return err
}
emulationVersion, err := semver.ParseTolerant(utilversion.DefaultComponentGlobalsRegistry.EffectiveVersionFor(utilversion.DefaultKubeComponent).EmulationVersion().String())
if err != nil {
return err
}
// Start lease candidate controller for coordinated leader election
leaseCandidate, waitForSync, err := leaderelection.NewCandidate(
cc.Client,
metav1.NamespaceSystem,
cc.LeaderElection.Lock.Identity(),
"kube-scheduler",
binaryVersion.FinalizeVersion(),
emulationVersion.FinalizeVersion(),
[]coordinationv1.CoordinatedLeaseStrategy{coordinationv1.OldestEmulationVersion},
)
if err != nil {
return err
}
readyzChecks = append(readyzChecks, healthz.NewInformerSyncHealthz(waitForSync))
go leaseCandidate.Run(ctx)
}
// Start up the healthz server.
if cc.SecureServing != nil {
handler := buildHandlerChain(newHealthEndpointsAndMetricsHandler(&cc.ComponentConfig, cc.InformerFactory, isLeader, checks, readyzChecks), cc.Authentication.Authenticator, cc.Authorization.Authorizer)
@ -245,6 +275,9 @@ func Run(ctx context.Context, cc *schedulerserverconfig.CompletedConfig, sched *
}
// If leader election is enabled, runCommand via LeaderElector until done and exit.
if cc.LeaderElection != nil {
if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.CoordinatedLeaderElection) {
cc.LeaderElection.Coordinated = true
}
cc.LeaderElection.Callbacks = leaderelection.LeaderCallbacks{
OnStartedLeading: func(ctx context.Context) {
close(waitingForLeader)

View File

@ -89,6 +89,7 @@ batch/v1beta1 \
certificates.k8s.io/v1 \
certificates.k8s.io/v1beta1 \
certificates.k8s.io/v1alpha1 \
coordination.k8s.io/v1alpha1 \
coordination.k8s.io/v1beta1 \
coordination.k8s.io/v1 \
discovery.k8s.io/v1 \

View File

@ -24,6 +24,7 @@ import (
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/coordination"
v1 "k8s.io/kubernetes/pkg/apis/coordination/v1"
"k8s.io/kubernetes/pkg/apis/coordination/v1alpha1"
"k8s.io/kubernetes/pkg/apis/coordination/v1beta1"
)
@ -34,7 +35,8 @@ func init() {
// Install registers the API group and adds types to a scheme
func Install(scheme *runtime.Scheme) {
utilruntime.Must(coordination.AddToScheme(scheme))
utilruntime.Must(v1alpha1.AddToScheme(scheme))
utilruntime.Must(v1beta1.AddToScheme(scheme))
utilruntime.Must(v1.AddToScheme(scheme))
utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion, v1beta1.SchemeGroupVersion))
utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion, v1beta1.SchemeGroupVersion, v1alpha1.SchemeGroupVersion))
}

View File

@ -50,6 +50,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Lease{},
&LeaseList{},
&LeaseCandidate{},
&LeaseCandidateList{},
)
return nil
}

View File

@ -20,6 +20,18 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type CoordinatedLeaseStrategy string
// CoordinatedLeaseStrategy defines the strategy for picking the leader for coordinated leader election.
const (
// OldestEmulationVersion picks the oldest LeaseCandidate, where "oldest" is defined as follows
// 1) Select the candidate(s) with the lowest emulation version
// 2) If multiple candidates have the same emulation version, select the candidate(s) with the lowest binary version. (Note that binary version must be greater or equal to emulation version)
// 3) If multiple candidates have the same binary version, select the candidate with the oldest creationTimestamp.
// If a candidate does not specify the emulationVersion and binaryVersion fields, it will not be considered a candidate for the lease.
OldestEmulationVersion CoordinatedLeaseStrategy = "OldestEmulationVersion"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Lease defines a lease concept.
@ -36,6 +48,8 @@ type Lease struct {
// LeaseSpec is a specification of a Lease.
type LeaseSpec struct {
// holderIdentity contains the identity of the holder of a current lease.
// If Coordinated Leader Election is used, the holder identity must be
// equal to the elected LeaseCandidate.metadata.name field.
// +optional
HolderIdentity *string
// leaseDurationSeconds is a duration that candidates for a lease need
@ -54,6 +68,18 @@ type LeaseSpec struct {
// holders.
// +optional
LeaseTransitions *int32
// Strategy indicates the strategy for picking the leader for coordinated leader election.
// If the field is not specified, there is no active coordination for this lease.
// (Alpha) Using this field requires the CoordinatedLeaderElection feature gate to be enabled.
// +featureGate=CoordinatedLeaderElection
// +optional
Strategy *CoordinatedLeaseStrategy
// PreferredHolder signals to a lease holder that the lease has a
// more optimal holder and should be given up.
// This field can only be set if Strategy is also set.
// +featureGate=CoordinatedLeaderElection
// +optional
PreferredHolder *string
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@ -67,3 +93,70 @@ type LeaseList struct {
// items is a list of schema objects.
Items []Lease
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// LeaseCandidate defines a candidate for a Lease object.
// Candidates are created such that coordinated leader election will pick the best leader from the list of candidates.
type LeaseCandidate struct {
metav1.TypeMeta
// +optional
metav1.ObjectMeta
Spec LeaseCandidateSpec
}
// LeaseCandidateSpec is a specification of a Lease.
type LeaseCandidateSpec struct {
// LeaseName is the name of the lease for which this candidate is contending.
// This field is immutable.
// +required
LeaseName string
// PingTime is the last time that the server has requested the LeaseCandidate
// to renew. It is only done during leader election to check if any
// LeaseCandidates have become ineligible. When PingTime is updated, the
// LeaseCandidate will respond by updating RenewTime.
// +optional
PingTime *metav1.MicroTime
// RenewTime is the time that the LeaseCandidate was last updated. Any time
// a Lease needs to do leader election, the PingTime field is updated to
// signal to the LeaseCandidate that they should update the RenewTime. The
// PingTime field is also updated regularly and LeaseCandidates must update
// RenewTime to prevent garbage collection for still active LeaseCandidates.
// Old LeaseCandidate objects are periodically garbage collected.
// +optional
RenewTime *metav1.MicroTime
// BinaryVersion is the binary version. It must be in a semver format without leading `v`.
// This field is required when strategy is "OldestEmulationVersion"
// +optional
BinaryVersion string
// EmulationVersion is the emulation version. It must be in a semver format without leading `v`.
// EmulationVersion must be less than or equal to BinaryVersion.
// This field is required when strategy is "OldestEmulationVersion"
// +optional
EmulationVersion string
// PreferredStrategies indicates the list of strategies for picking the leader for coordinated leader election.
// The list is ordered, and the first strategy supersedes all other strategies. The list is used by coordinated
// leader election to make a decision about the final election strategy. This follows as
// - If all clients have strategy X as the first element in this list, strategy X will be used.
// - If a candidate has strategy [X] and another candidate has strategy [Y, X], Y supersedes X and strategy Y
// will be used.
// - If a candidate has strategy [X, Y] and another candidate has strategy [Y, X], this is a user error and leader
// election will not operate the Lease until resolved.
// (Alpha) Using this field requires the CoordinatedLeaderElection feature gate to be enabled.
// +featureGate=CoordinatedLeaderElection
// +listType=atomic
// +required
PreferredStrategies []CoordinatedLeaseStrategy
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// LeaseCandidateList is a list of LeaseCandidate objects.
type LeaseCandidateList struct {
metav1.TypeMeta
// +optional
metav1.ListMeta
// items is a list of schema objects.
Items []LeaseCandidate
}

View File

@ -125,6 +125,8 @@ func autoConvert_v1_LeaseSpec_To_coordination_LeaseSpec(in *v1.LeaseSpec, out *c
out.AcquireTime = (*metav1.MicroTime)(unsafe.Pointer(in.AcquireTime))
out.RenewTime = (*metav1.MicroTime)(unsafe.Pointer(in.RenewTime))
out.LeaseTransitions = (*int32)(unsafe.Pointer(in.LeaseTransitions))
out.Strategy = (*coordination.CoordinatedLeaseStrategy)(unsafe.Pointer(in.Strategy))
out.PreferredHolder = (*string)(unsafe.Pointer(in.PreferredHolder))
return nil
}
@ -139,6 +141,8 @@ func autoConvert_coordination_LeaseSpec_To_v1_LeaseSpec(in *coordination.LeaseSp
out.AcquireTime = (*metav1.MicroTime)(unsafe.Pointer(in.AcquireTime))
out.RenewTime = (*metav1.MicroTime)(unsafe.Pointer(in.RenewTime))
out.LeaseTransitions = (*int32)(unsafe.Pointer(in.LeaseTransitions))
out.Strategy = (*v1.CoordinatedLeaseStrategy)(unsafe.Pointer(in.Strategy))
out.PreferredHolder = (*string)(unsafe.Pointer(in.PreferredHolder))
return nil
}

View File

@ -0,0 +1,24 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/coordination
// +k8s:conversion-gen-external-types=k8s.io/api/coordination/v1alpha1
// +k8s:defaulter-gen=TypeMeta
// +k8s:defaulter-gen-input=k8s.io/api/coordination/v1alpha1
// +groupName=coordination.k8s.io
package v1alpha1 // import "k8s.io/kubernetes/pkg/apis/coordination/v1alpha1"

View File

@ -0,0 +1,46 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
coordinationv1alpha1 "k8s.io/api/coordination/v1alpha1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// GroupName is the group name use in this package
const GroupName = "coordination.k8s.io"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
var (
localSchemeBuilder = &coordinationv1alpha1.SchemeBuilder
// AddToScheme is a common registration function for mapping packaged scoped group & version keys to a scheme
AddToScheme = localSchemeBuilder.AddToScheme
)
func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(RegisterDefaults)
}

View File

@ -0,0 +1,151 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by conversion-gen. DO NOT EDIT.
package v1alpha1
import (
unsafe "unsafe"
coordinationv1 "k8s.io/api/coordination/v1"
v1alpha1 "k8s.io/api/coordination/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
coordination "k8s.io/kubernetes/pkg/apis/coordination"
)
func init() {
localSchemeBuilder.Register(RegisterConversions)
}
// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(s *runtime.Scheme) error {
if err := s.AddGeneratedConversionFunc((*v1alpha1.LeaseCandidate)(nil), (*coordination.LeaseCandidate)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_LeaseCandidate_To_coordination_LeaseCandidate(a.(*v1alpha1.LeaseCandidate), b.(*coordination.LeaseCandidate), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*coordination.LeaseCandidate)(nil), (*v1alpha1.LeaseCandidate)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_coordination_LeaseCandidate_To_v1alpha1_LeaseCandidate(a.(*coordination.LeaseCandidate), b.(*v1alpha1.LeaseCandidate), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v1alpha1.LeaseCandidateList)(nil), (*coordination.LeaseCandidateList)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_LeaseCandidateList_To_coordination_LeaseCandidateList(a.(*v1alpha1.LeaseCandidateList), b.(*coordination.LeaseCandidateList), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*coordination.LeaseCandidateList)(nil), (*v1alpha1.LeaseCandidateList)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_coordination_LeaseCandidateList_To_v1alpha1_LeaseCandidateList(a.(*coordination.LeaseCandidateList), b.(*v1alpha1.LeaseCandidateList), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v1alpha1.LeaseCandidateSpec)(nil), (*coordination.LeaseCandidateSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_LeaseCandidateSpec_To_coordination_LeaseCandidateSpec(a.(*v1alpha1.LeaseCandidateSpec), b.(*coordination.LeaseCandidateSpec), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*coordination.LeaseCandidateSpec)(nil), (*v1alpha1.LeaseCandidateSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_coordination_LeaseCandidateSpec_To_v1alpha1_LeaseCandidateSpec(a.(*coordination.LeaseCandidateSpec), b.(*v1alpha1.LeaseCandidateSpec), scope)
}); err != nil {
return err
}
return nil
}
func autoConvert_v1alpha1_LeaseCandidate_To_coordination_LeaseCandidate(in *v1alpha1.LeaseCandidate, out *coordination.LeaseCandidate, s conversion.Scope) error {
out.ObjectMeta = in.ObjectMeta
if err := Convert_v1alpha1_LeaseCandidateSpec_To_coordination_LeaseCandidateSpec(&in.Spec, &out.Spec, s); err != nil {
return err
}
return nil
}
// Convert_v1alpha1_LeaseCandidate_To_coordination_LeaseCandidate is an autogenerated conversion function.
func Convert_v1alpha1_LeaseCandidate_To_coordination_LeaseCandidate(in *v1alpha1.LeaseCandidate, out *coordination.LeaseCandidate, s conversion.Scope) error {
return autoConvert_v1alpha1_LeaseCandidate_To_coordination_LeaseCandidate(in, out, s)
}
func autoConvert_coordination_LeaseCandidate_To_v1alpha1_LeaseCandidate(in *coordination.LeaseCandidate, out *v1alpha1.LeaseCandidate, s conversion.Scope) error {
out.ObjectMeta = in.ObjectMeta
if err := Convert_coordination_LeaseCandidateSpec_To_v1alpha1_LeaseCandidateSpec(&in.Spec, &out.Spec, s); err != nil {
return err
}
return nil
}
// Convert_coordination_LeaseCandidate_To_v1alpha1_LeaseCandidate is an autogenerated conversion function.
func Convert_coordination_LeaseCandidate_To_v1alpha1_LeaseCandidate(in *coordination.LeaseCandidate, out *v1alpha1.LeaseCandidate, s conversion.Scope) error {
return autoConvert_coordination_LeaseCandidate_To_v1alpha1_LeaseCandidate(in, out, s)
}
func autoConvert_v1alpha1_LeaseCandidateList_To_coordination_LeaseCandidateList(in *v1alpha1.LeaseCandidateList, out *coordination.LeaseCandidateList, s conversion.Scope) error {
out.ListMeta = in.ListMeta
out.Items = *(*[]coordination.LeaseCandidate)(unsafe.Pointer(&in.Items))
return nil
}
// Convert_v1alpha1_LeaseCandidateList_To_coordination_LeaseCandidateList is an autogenerated conversion function.
func Convert_v1alpha1_LeaseCandidateList_To_coordination_LeaseCandidateList(in *v1alpha1.LeaseCandidateList, out *coordination.LeaseCandidateList, s conversion.Scope) error {
return autoConvert_v1alpha1_LeaseCandidateList_To_coordination_LeaseCandidateList(in, out, s)
}
func autoConvert_coordination_LeaseCandidateList_To_v1alpha1_LeaseCandidateList(in *coordination.LeaseCandidateList, out *v1alpha1.LeaseCandidateList, s conversion.Scope) error {
out.ListMeta = in.ListMeta
out.Items = *(*[]v1alpha1.LeaseCandidate)(unsafe.Pointer(&in.Items))
return nil
}
// Convert_coordination_LeaseCandidateList_To_v1alpha1_LeaseCandidateList is an autogenerated conversion function.
func Convert_coordination_LeaseCandidateList_To_v1alpha1_LeaseCandidateList(in *coordination.LeaseCandidateList, out *v1alpha1.LeaseCandidateList, s conversion.Scope) error {
return autoConvert_coordination_LeaseCandidateList_To_v1alpha1_LeaseCandidateList(in, out, s)
}
func autoConvert_v1alpha1_LeaseCandidateSpec_To_coordination_LeaseCandidateSpec(in *v1alpha1.LeaseCandidateSpec, out *coordination.LeaseCandidateSpec, s conversion.Scope) error {
out.LeaseName = in.LeaseName
out.PingTime = (*v1.MicroTime)(unsafe.Pointer(in.PingTime))
out.RenewTime = (*v1.MicroTime)(unsafe.Pointer(in.RenewTime))
out.BinaryVersion = in.BinaryVersion
out.EmulationVersion = in.EmulationVersion
out.PreferredStrategies = *(*[]coordination.CoordinatedLeaseStrategy)(unsafe.Pointer(&in.PreferredStrategies))
return nil
}
// Convert_v1alpha1_LeaseCandidateSpec_To_coordination_LeaseCandidateSpec is an autogenerated conversion function.
func Convert_v1alpha1_LeaseCandidateSpec_To_coordination_LeaseCandidateSpec(in *v1alpha1.LeaseCandidateSpec, out *coordination.LeaseCandidateSpec, s conversion.Scope) error {
return autoConvert_v1alpha1_LeaseCandidateSpec_To_coordination_LeaseCandidateSpec(in, out, s)
}
func autoConvert_coordination_LeaseCandidateSpec_To_v1alpha1_LeaseCandidateSpec(in *coordination.LeaseCandidateSpec, out *v1alpha1.LeaseCandidateSpec, s conversion.Scope) error {
out.LeaseName = in.LeaseName
out.PingTime = (*v1.MicroTime)(unsafe.Pointer(in.PingTime))
out.RenewTime = (*v1.MicroTime)(unsafe.Pointer(in.RenewTime))
out.BinaryVersion = in.BinaryVersion
out.EmulationVersion = in.EmulationVersion
out.PreferredStrategies = *(*[]coordinationv1.CoordinatedLeaseStrategy)(unsafe.Pointer(&in.PreferredStrategies))
return nil
}
// Convert_coordination_LeaseCandidateSpec_To_v1alpha1_LeaseCandidateSpec is an autogenerated conversion function.
func Convert_coordination_LeaseCandidateSpec_To_v1alpha1_LeaseCandidateSpec(in *coordination.LeaseCandidateSpec, out *v1alpha1.LeaseCandidateSpec, s conversion.Scope) error {
return autoConvert_coordination_LeaseCandidateSpec_To_v1alpha1_LeaseCandidateSpec(in, out, s)
}

View File

@ -0,0 +1,33 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by defaulter-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// RegisterDefaults adds defaulters functions to the given scheme.
// Public to allow building arbitrary schemes.
// All generated defaulters are covering - they call all nested defaulters.
func RegisterDefaults(scheme *runtime.Scheme) error {
return nil
}

View File

@ -24,6 +24,7 @@ package v1beta1
import (
unsafe "unsafe"
coordinationv1 "k8s.io/api/coordination/v1"
v1beta1 "k8s.io/api/coordination/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
@ -125,6 +126,8 @@ func autoConvert_v1beta1_LeaseSpec_To_coordination_LeaseSpec(in *v1beta1.LeaseSp
out.AcquireTime = (*v1.MicroTime)(unsafe.Pointer(in.AcquireTime))
out.RenewTime = (*v1.MicroTime)(unsafe.Pointer(in.RenewTime))
out.LeaseTransitions = (*int32)(unsafe.Pointer(in.LeaseTransitions))
out.Strategy = (*coordination.CoordinatedLeaseStrategy)(unsafe.Pointer(in.Strategy))
out.PreferredHolder = (*string)(unsafe.Pointer(in.PreferredHolder))
return nil
}
@ -139,6 +142,8 @@ func autoConvert_coordination_LeaseSpec_To_v1beta1_LeaseSpec(in *coordination.Le
out.AcquireTime = (*v1.MicroTime)(unsafe.Pointer(in.AcquireTime))
out.RenewTime = (*v1.MicroTime)(unsafe.Pointer(in.RenewTime))
out.LeaseTransitions = (*int32)(unsafe.Pointer(in.LeaseTransitions))
out.Strategy = (*coordinationv1.CoordinatedLeaseStrategy)(unsafe.Pointer(in.Strategy))
out.PreferredHolder = (*string)(unsafe.Pointer(in.PreferredHolder))
return nil
}

View File

@ -17,11 +17,20 @@ limitations under the License.
package validation
import (
"slices"
"strings"
"github.com/blang/semver/v4"
"k8s.io/apimachinery/pkg/api/validation"
utilvalidation "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/apis/coordination"
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
)
var validLeaseStrategies = []coordination.CoordinatedLeaseStrategy{coordination.OldestEmulationVersion}
// ValidateLease validates a Lease.
func ValidateLease(lease *coordination.Lease) field.ErrorList {
allErrs := validation.ValidateObjectMeta(&lease.ObjectMeta, true, validation.NameIsDNSSubdomain, field.NewPath("metadata"))
@ -48,5 +57,119 @@ func ValidateLeaseSpec(spec *coordination.LeaseSpec, fldPath *field.Path) field.
fld := fldPath.Child("leaseTransitions")
allErrs = append(allErrs, field.Invalid(fld, spec.LeaseTransitions, "must be greater than or equal to 0"))
}
if spec.Strategy != nil {
allErrs = append(allErrs, ValidateCoordinatedLeaseStrategy(*spec.Strategy, fldPath.Child("strategy"))...)
}
if spec.PreferredHolder != nil && *spec.PreferredHolder != "" && (spec.Strategy == nil || *spec.Strategy == "") {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("preferredHolder"), "may only be specified if `strategy` is defined"))
}
// spec.RenewTime is a MicroTime and doesn't need further validation
return allErrs
}
// ValidateLeaseCandidate validates a LeaseCandidate.
func ValidateLeaseCandidate(lease *coordination.LeaseCandidate) field.ErrorList {
allErrs := validation.ValidateObjectMeta(&lease.ObjectMeta, true, ValidLeaseCandidateName, field.NewPath("metadata"))
allErrs = append(allErrs, ValidateLeaseCandidateSpec(&lease.Spec, field.NewPath("spec"))...)
return allErrs
}
func ValidLeaseCandidateName(name string, prefix bool) []string {
// prefix is already handled by IsConfigMapKey, a trailing - is permitted.
return utilvalidation.IsConfigMapKey(name)
}
func ValidateLeaseCandidateSpecUpdate(leaseCandidateSpec, oldLeaseCandidateSpec *coordination.LeaseCandidateSpec) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateImmutableField(leaseCandidateSpec.LeaseName, oldLeaseCandidateSpec.LeaseName, field.NewPath("spec").Child("leaseName"))...)
return allErrs
}
// ValidateLeaseCandidateUpdate validates an update of LeaseCandidate object.
func ValidateLeaseCandidateUpdate(leaseCandidate, oldLeaseCandidate *coordination.LeaseCandidate) field.ErrorList {
allErrs := validation.ValidateObjectMetaUpdate(&leaseCandidate.ObjectMeta, &oldLeaseCandidate.ObjectMeta, field.NewPath("metadata"))
allErrs = append(allErrs, ValidateLeaseCandidateSpec(&leaseCandidate.Spec, field.NewPath("spec"))...)
allErrs = append(allErrs, ValidateLeaseCandidateSpecUpdate(&leaseCandidate.Spec, &oldLeaseCandidate.Spec)...)
return allErrs
}
// ValidateLeaseCandidateSpec validates spec of LeaseCandidate.
func ValidateLeaseCandidateSpec(spec *coordination.LeaseCandidateSpec, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(spec.LeaseName) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("leaseName"), ""))
}
ev := semver.Version{}
if spec.EmulationVersion != "" {
var err error
ev, err = semver.Parse(spec.EmulationVersion)
if err != nil {
fld := fldPath.Child("emulationVersion")
allErrs = append(allErrs, field.Invalid(fld, spec.EmulationVersion, "must be a valid semantic version"))
}
}
bv := semver.Version{}
if spec.BinaryVersion != "" {
var err error
bv, err = semver.Parse(spec.BinaryVersion)
if err != nil {
fld := fldPath.Child("binaryVersion")
allErrs = append(allErrs, field.Invalid(fld, spec.BinaryVersion, "must be a valid semantic version"))
}
}
if spec.BinaryVersion != "" && spec.EmulationVersion != "" && bv.LT(ev) {
fld := fldPath.Child("binaryVersion")
allErrs = append(allErrs, field.Invalid(fld, spec.BinaryVersion, "must be greater than or equal to `emulationVersion`"))
}
if len(spec.PreferredStrategies) > 0 {
for i, strategy := range spec.PreferredStrategies {
fld := fldPath.Child("preferredStrategies").Index(i)
strategySeen := make(map[coordination.CoordinatedLeaseStrategy]bool)
if _, ok := strategySeen[strategy]; ok {
allErrs = append(allErrs, field.Duplicate(fld, strategy))
} else {
strategySeen[strategy] = true
}
if strategy == coordination.OldestEmulationVersion {
zeroVersion := semver.Version{}
if bv.EQ(zeroVersion) {
allErrs = append(allErrs, field.Required(fldPath.Child("binaryVersion"), "must be specified when `strategy` is 'OldestEmulationVersion'"))
}
if ev.EQ(zeroVersion) {
allErrs = append(allErrs, field.Required(fldPath.Child("emulationVersion"), "must be specified when `strategy` is 'OldestEmulationVersion'"))
}
}
allErrs = append(allErrs, ValidateCoordinatedLeaseStrategy(strategy, fld)...)
}
}
// spec.PingTime is a MicroTime and doesn't need further validation
// spec.RenewTime is a MicroTime and doesn't need further validation
return allErrs
}
// ValidateCoordinatedLeaseStrategy validates the Strategy field in both the Lease and LeaseCandidate
func ValidateCoordinatedLeaseStrategy(strategy coordination.CoordinatedLeaseStrategy, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
parts := strings.Split(string(strategy), "/")
switch len(parts) {
case 1:
// Must be a Kubernetes-defined name.
if !slices.Contains(validLeaseStrategies, coordination.CoordinatedLeaseStrategy(parts[0])) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("strategy"), strategy, validLeaseStrategies))
}
default:
if msgs := utilvalidation.IsQualifiedName(string(strategy)); len(msgs) > 0 {
for _, msg := range msgs {
allErrs = append(allErrs, field.Invalid(fldPath.Child("strategy"), strategy, msg))
}
}
}
return allErrs
}

View File

@ -22,6 +22,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/apis/coordination"
"k8s.io/utils/ptr"
)
func TestValidateLease(t *testing.T) {
@ -41,14 +42,58 @@ func TestValidateLeaseSpec(t *testing.T) {
holder := "holder"
leaseDuration := int32(0)
leaseTransitions := int32(-1)
spec := &coordination.LeaseSpec{
HolderIdentity: &holder,
LeaseDurationSeconds: &leaseDuration,
LeaseTransitions: &leaseTransitions,
preferredHolder := "holder2"
testcases := []struct {
spec coordination.LeaseSpec
err bool
}{
{
// valid
coordination.LeaseSpec{
HolderIdentity: &holder,
LeaseDurationSeconds: ptr.To[int32](10),
LeaseTransitions: ptr.To[int32](1),
},
false,
},
{
// valid with PreferredHolder
coordination.LeaseSpec{
HolderIdentity: &holder,
LeaseDurationSeconds: ptr.To[int32](10),
LeaseTransitions: ptr.To[int32](1),
Strategy: ptr.To(coordination.OldestEmulationVersion),
PreferredHolder: ptr.To("someotherholder"),
},
false,
},
{
coordination.LeaseSpec{
HolderIdentity: &holder,
LeaseDurationSeconds: &leaseDuration,
LeaseTransitions: &leaseTransitions,
},
true,
},
{
coordination.LeaseSpec{
HolderIdentity: &holder,
LeaseDurationSeconds: &leaseDuration,
LeaseTransitions: &leaseTransitions,
PreferredHolder: &preferredHolder,
},
true,
},
}
errs := ValidateLeaseSpec(spec, field.NewPath("foo"))
if len(errs) != 2 {
t.Errorf("unexpected list of errors: %#v", errs.ToAggregate().Error())
for _, tc := range testcases {
errs := ValidateLeaseSpec(&tc.spec, field.NewPath("foo"))
if tc.err && len(errs) == 0 {
t.Error("Expected err, got no err")
} else if !tc.err && len(errs) != 0 {
t.Errorf("Expected no err, got err %v", errs)
}
}
}
@ -102,3 +147,188 @@ func TestValidateLeaseSpecUpdate(t *testing.T) {
t.Errorf("unexpected list of errors for valid update: %#v", errs.ToAggregate().Error())
}
}
func TestValidateLeaseCandidate(t *testing.T) {
lease := &coordination.LeaseCandidate{
ObjectMeta: metav1.ObjectMeta{
Name: "invalidName++",
Namespace: "==invalid_Namespace==",
},
}
errs := ValidateLeaseCandidate(lease)
if len(errs) == 0 {
t.Errorf("expected invalid LeaseCandidate")
}
}
func TestValidateLeaseCandidateSpec(t *testing.T) {
testcases := []struct {
name string
shouldErr bool
spec *coordination.LeaseCandidateSpec
}{
{
"valid",
false,
&coordination.LeaseCandidateSpec{
BinaryVersion: "1.30.0",
EmulationVersion: "1.30.0",
LeaseName: "test",
PreferredStrategies: []coordination.CoordinatedLeaseStrategy{coordination.OldestEmulationVersion},
},
},
{
"valid custom strategy should not require binaryVersion and emulationVersion",
false,
&coordination.LeaseCandidateSpec{
LeaseName: "test",
PreferredStrategies: []coordination.CoordinatedLeaseStrategy{"custom.com/foo"},
},
},
{
"no lease name",
true,
&coordination.LeaseCandidateSpec{
EmulationVersion: "1.30.0",
},
},
{
"bad binaryVersion",
true,
&coordination.LeaseCandidateSpec{
BinaryVersion: "1.30.1.6",
LeaseName: "test",
},
},
{
"emulation should be greater than or equal to binary version",
true,
&coordination.LeaseCandidateSpec{
EmulationVersion: "1.30.0",
BinaryVersion: "1.29.0",
LeaseName: "test",
},
},
{
"preferredStrategies bad",
true,
&coordination.LeaseCandidateSpec{
BinaryVersion: "1.30.1",
EmulationVersion: "1.30.1",
LeaseName: "test",
PreferredStrategies: []coordination.CoordinatedLeaseStrategy{"foo"},
},
},
{
"preferredStrategies good but emulationVersion missing",
true,
&coordination.LeaseCandidateSpec{
BinaryVersion: "1.30.1",
LeaseName: "test",
PreferredStrategies: []coordination.CoordinatedLeaseStrategy{coordination.OldestEmulationVersion},
},
},
}
for _, tc := range testcases {
errs := ValidateLeaseCandidateSpec(tc.spec, field.NewPath("foo"))
if len(errs) > 0 && !tc.shouldErr {
t.Errorf("unexpected list of errors: %#v", errs.ToAggregate().Error())
} else if len(errs) == 0 && tc.shouldErr {
t.Errorf("Expected err, got no error for tc: %s", tc.name)
}
}
}
func TestValidateLeaseCandidateUpdate(t *testing.T) {
testcases := []struct {
name string
old coordination.LeaseCandidate
update coordination.LeaseCandidate
err bool
}{
{
name: "valid update",
old: coordination.LeaseCandidate{
Spec: coordination.LeaseCandidateSpec{
BinaryVersion: "1.30.0",
EmulationVersion: "1.30.0",
LeaseName: "test",
},
},
update: coordination.LeaseCandidate{
Spec: coordination.LeaseCandidateSpec{
BinaryVersion: "1.30.0",
EmulationVersion: "1.30.0",
LeaseName: "test",
},
},
err: false,
},
{
name: "update LeaseName should fail",
old: coordination.LeaseCandidate{
Spec: coordination.LeaseCandidateSpec{
BinaryVersion: "1.30.0",
EmulationVersion: "1.30.0",
LeaseName: "test",
},
},
update: coordination.LeaseCandidate{
Spec: coordination.LeaseCandidateSpec{
BinaryVersion: "1.30.0",
EmulationVersion: "1.30.0",
LeaseName: "test-update",
},
},
err: true,
},
}
for _, tc := range testcases {
tc.old.ResourceVersion = "1"
tc.update.ResourceVersion = "1"
errs := ValidateLeaseCandidateUpdate(&tc.update, &tc.old)
if tc.err && len(errs) == 0 {
t.Errorf("Expected err, got no err for tc: %s", tc.name)
} else if !tc.err && len(errs) != 0 {
t.Errorf("Expected no err, got err %v for tc: %s", errs, tc.name)
}
}
}
func TestValidateCoordinatedLeaseStrategy(t *testing.T) {
testcases := []struct {
strategy coordination.CoordinatedLeaseStrategy
err bool
}{
{
coordination.CoordinatedLeaseStrategy("foobar"),
true,
},
{
coordination.CoordinatedLeaseStrategy("example.com/foobar/toomanyslashes"),
true,
},
{
coordination.CoordinatedLeaseStrategy(coordination.OldestEmulationVersion),
false,
},
{
coordination.CoordinatedLeaseStrategy("example.com/foobar"),
false,
},
}
for _, tc := range testcases {
errs := ValidateCoordinatedLeaseStrategy(tc.strategy, field.NewPath("foo"))
if tc.err && len(errs) == 0 {
t.Error("Expected err, got no err")
} else if !tc.err && len(errs) != 0 {
t.Errorf("Expected no err, got err %v", errs)
}
}
}

View File

@ -52,6 +52,95 @@ func (in *Lease) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LeaseCandidate) DeepCopyInto(out *LeaseCandidate) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaseCandidate.
func (in *LeaseCandidate) DeepCopy() *LeaseCandidate {
if in == nil {
return nil
}
out := new(LeaseCandidate)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *LeaseCandidate) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LeaseCandidateList) DeepCopyInto(out *LeaseCandidateList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]LeaseCandidate, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaseCandidateList.
func (in *LeaseCandidateList) DeepCopy() *LeaseCandidateList {
if in == nil {
return nil
}
out := new(LeaseCandidateList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *LeaseCandidateList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LeaseCandidateSpec) DeepCopyInto(out *LeaseCandidateSpec) {
*out = *in
if in.PingTime != nil {
in, out := &in.PingTime, &out.PingTime
*out = (*in).DeepCopy()
}
if in.RenewTime != nil {
in, out := &in.RenewTime, &out.RenewTime
*out = (*in).DeepCopy()
}
if in.PreferredStrategies != nil {
in, out := &in.PreferredStrategies, &out.PreferredStrategies
*out = make([]CoordinatedLeaseStrategy, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaseCandidateSpec.
func (in *LeaseCandidateSpec) DeepCopy() *LeaseCandidateSpec {
if in == nil {
return nil
}
out := new(LeaseCandidateSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LeaseList) DeepCopyInto(out *LeaseList) {
*out = *in
@ -111,6 +200,16 @@ func (in *LeaseSpec) DeepCopyInto(out *LeaseSpec) {
*out = new(int32)
**out = **in
}
if in.Strategy != nil {
in, out := &in.Strategy, &out.Strategy
*out = new(CoordinatedLeaseStrategy)
**out = **in
}
if in.PreferredHolder != nil {
in, out := &in.PreferredHolder, &out.PreferredHolder
*out = new(string)
**out = **in
}
return
}

View File

@ -284,6 +284,7 @@ func DefaultGenericAPIServicePriorities() map[schema.GroupVersion]APIServicePrio
{Group: "admissionregistration.k8s.io", Version: "v1beta1"}: {Group: 16700, Version: 12},
{Group: "admissionregistration.k8s.io", Version: "v1alpha1"}: {Group: 16700, Version: 9},
{Group: "coordination.k8s.io", Version: "v1"}: {Group: 16500, Version: 15},
{Group: "coordination.k8s.io", Version: "v1alpha1"}: {Group: 16500, Version: 9},
{Group: "discovery.k8s.io", Version: "v1"}: {Group: 16200, Version: 15},
{Group: "discovery.k8s.io", Version: "v1beta1"}: {Group: 16200, Version: 12},
{Group: "flowcontrol.apiserver.k8s.io", Version: "v1"}: {Group: 16100, Version: 21},

View File

@ -17,6 +17,7 @@ limitations under the License.
package apiserver
import (
"context"
"fmt"
"os"
"time"
@ -41,6 +42,7 @@ import (
"k8s.io/kubernetes/pkg/controlplane/controller/apiserverleasegc"
"k8s.io/kubernetes/pkg/controlplane/controller/clusterauthenticationtrust"
"k8s.io/kubernetes/pkg/controlplane/controller/leaderelection"
"k8s.io/kubernetes/pkg/controlplane/controller/legacytokentracking"
"k8s.io/kubernetes/pkg/controlplane/controller/systemnamespaces"
"k8s.io/kubernetes/pkg/features"
@ -58,6 +60,10 @@ var (
// IdentityLeaseRenewIntervalPeriod is the interval of kube-apiserver renewing its lease in seconds
// IdentityLeaseRenewIntervalPeriod is exposed so integration tests can tune this value.
IdentityLeaseRenewIntervalPeriod = 10 * time.Second
// LeaseCandidateGCPeriod is the interval which the leasecandidate GC controller checks for expired leases
// This is exposed so integration tests can tune this value.
LeaseCandidateGCPeriod = 30 * time.Minute
)
const (
@ -145,6 +151,35 @@ func (c completedConfig) New(name string, delegationTarget genericapiserver.Dele
return nil, fmt.Errorf("failed to get listener address: %w", err)
}
if utilfeature.DefaultFeatureGate.Enabled(apiserverfeatures.CoordinatedLeaderElection) {
leaseInformer := s.VersionedInformers.Coordination().V1().Leases()
lcInformer := s.VersionedInformers.Coordination().V1alpha1().LeaseCandidates()
// Ensure that informers are registered before starting. Coordinated Leader Election leader-elected
// and may register informer handlers after they are started.
_ = leaseInformer.Informer()
_ = lcInformer.Informer()
s.GenericAPIServer.AddPostStartHookOrDie("start-kube-apiserver-coordinated-leader-election-controller", func(hookContext genericapiserver.PostStartHookContext) error {
go leaderelection.RunWithLeaderElection(hookContext, s.GenericAPIServer.LoopbackClientConfig, func() (func(ctx context.Context, workers int), error) {
controller, err := leaderelection.NewController(
leaseInformer,
lcInformer,
client.CoordinationV1(),
client.CoordinationV1alpha1(),
)
gccontroller := leaderelection.NewLeaseCandidateGC(
client,
LeaseCandidateGCPeriod,
lcInformer,
)
return func(ctx context.Context, workers int) {
go controller.Run(ctx, workers)
go gccontroller.Run(ctx)
}, err
})
return nil
})
}
if utilfeature.DefaultFeatureGate.Enabled(features.UnknownVersionInteroperabilityProxy) {
peeraddress := getPeerAddress(c.Extra.PeerAdvertiseAddress, c.Generic.PublicAddress, publicServicePort)
peerEndpointCtrl := peerreconcilers.New(

View File

@ -0,0 +1,170 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package leaderelection
import (
"fmt"
"time"
"github.com/blang/semver/v4"
v1 "k8s.io/api/coordination/v1"
v1alpha1 "k8s.io/api/coordination/v1alpha1"
"k8s.io/klog/v2"
)
func pickBestLeaderOldestEmulationVersion(candidates []*v1alpha1.LeaseCandidate) *v1alpha1.LeaseCandidate {
var electee *v1alpha1.LeaseCandidate
for _, c := range candidates {
if !validLeaseCandidateForOldestEmulationVersion(c) {
continue
}
if electee == nil || compare(electee, c) > 0 {
electee = c
}
}
if electee == nil {
klog.Infof("pickBestLeader: none found")
} else {
klog.Infof("pickBestLeader: %s %s", electee.Namespace, electee.Name)
}
return electee
}
// topologicalSortWithOneRoot has a caveat that there may only be one root (indegree=0) node in a valid ordering.
func topologicalSortWithOneRoot(graph map[v1.CoordinatedLeaseStrategy][]v1.CoordinatedLeaseStrategy) []v1.CoordinatedLeaseStrategy {
inDegree := make(map[v1.CoordinatedLeaseStrategy]int)
for node := range graph {
inDegree[node] = 0
}
for _, neighbors := range graph {
for _, neighbor := range neighbors {
inDegree[neighbor]++
}
}
var queue []v1.CoordinatedLeaseStrategy
for vertex, degree := range inDegree {
if degree == 0 {
queue = append(queue, vertex)
}
}
// If multiple nodes have indegree of 0, multiple strategies are non-superceding and is a conflict.
if len(queue) > 1 {
return nil
}
var sorted []v1.CoordinatedLeaseStrategy
for len(queue) > 0 {
vertex := queue[0]
queue = queue[1:]
sorted = append(sorted, vertex)
for _, neighbor := range graph[vertex] {
inDegree[neighbor]--
if inDegree[neighbor] == 0 {
queue = append(queue, neighbor)
}
}
}
if len(sorted) != len(graph) {
fmt.Printf("%s", (sorted))
return nil // Cycle detected
}
return sorted
}
func pickBestStrategy(candidates []*v1alpha1.LeaseCandidate) (v1.CoordinatedLeaseStrategy, error) {
graph := make(map[v1.CoordinatedLeaseStrategy][]v1.CoordinatedLeaseStrategy)
nilStrategy := v1.CoordinatedLeaseStrategy("")
for _, c := range candidates {
for i := range len(c.Spec.PreferredStrategies) - 1 {
graph[c.Spec.PreferredStrategies[i]] = append(graph[c.Spec.PreferredStrategies[i]], c.Spec.PreferredStrategies[i+1])
}
if _, ok := graph[c.Spec.PreferredStrategies[len(c.Spec.PreferredStrategies)-1]]; !ok {
graph[c.Spec.PreferredStrategies[len(c.Spec.PreferredStrategies)-1]] = []v1.CoordinatedLeaseStrategy{}
}
}
sorted := topologicalSortWithOneRoot(graph)
if sorted == nil {
return nilStrategy, fmt.Errorf("invalid strategy")
}
return sorted[0], nil
}
func validLeaseCandidateForOldestEmulationVersion(l *v1alpha1.LeaseCandidate) bool {
_, err := semver.ParseTolerant(l.Spec.EmulationVersion)
if err != nil {
return false
}
_, err = semver.ParseTolerant(l.Spec.BinaryVersion)
return err == nil
}
func getEmulationVersionOrZero(l *v1alpha1.LeaseCandidate) semver.Version {
value := l.Spec.EmulationVersion
v, err := semver.ParseTolerant(value)
if err != nil {
return semver.Version{}
}
return v
}
func getBinaryVersionOrZero(l *v1alpha1.LeaseCandidate) semver.Version {
value := l.Spec.BinaryVersion
v, err := semver.ParseTolerant(value)
if err != nil {
return semver.Version{}
}
return v
}
// -1: lhs better, 1: rhs better
func compare(lhs, rhs *v1alpha1.LeaseCandidate) int {
l := getEmulationVersionOrZero(lhs)
r := getEmulationVersionOrZero(rhs)
result := l.Compare(r)
if result == 0 {
l := getBinaryVersionOrZero(lhs)
r := getBinaryVersionOrZero(rhs)
result = l.Compare(r)
}
if result == 0 {
if lhs.CreationTimestamp.After(rhs.CreationTimestamp.Time) {
return 1
}
return -1
}
return result
}
func isLeaseExpired(lease *v1.Lease) bool {
currentTime := time.Now()
return lease.Spec.RenewTime == nil ||
lease.Spec.LeaseDurationSeconds == nil ||
lease.Spec.RenewTime.Add(time.Duration(*lease.Spec.LeaseDurationSeconds)*time.Second).Before(currentTime)
}
func isLeaseCandidateExpired(lease *v1alpha1.LeaseCandidate) bool {
currentTime := time.Now()
return lease.Spec.RenewTime == nil ||
lease.Spec.RenewTime.Add(leaseCandidateValidDuration).Before(currentTime)
}

View File

@ -0,0 +1,758 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package leaderelection
import (
"testing"
"time"
"github.com/blang/semver/v4"
v1 "k8s.io/api/coordination/v1"
v1alpha1 "k8s.io/api/coordination/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestPickBestLeaderOldestEmulationVersion(t *testing.T) {
tests := []struct {
name string
candidates []*v1alpha1.LeaseCandidate
want *v1alpha1.LeaseCandidate
}{
{
name: "empty",
candidates: []*v1alpha1.LeaseCandidate{},
want: nil,
},
{
name: "single candidate",
candidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate1",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now()},
},
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "0.1.0",
BinaryVersion: "0.1.0",
},
},
},
want: &v1alpha1.LeaseCandidate{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate1",
Namespace: "default",
},
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "0.1.0",
BinaryVersion: "0.1.0",
},
},
},
{
name: "multiple candidates, different emulation versions",
candidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate1",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now().Add(-1 * time.Hour)},
},
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "0.1.0",
BinaryVersion: "0.1.0",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate2",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now()},
},
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "0.2.0",
BinaryVersion: "0.2.0",
},
},
},
want: &v1alpha1.LeaseCandidate{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate1",
Namespace: "default",
},
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "v1",
BinaryVersion: "v1",
},
},
},
{
name: "multiple candidates, same emulation versions, different binary versions",
candidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate1",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now().Add(-1 * time.Hour)},
},
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "0.1.0",
BinaryVersion: "0.1.0",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate2",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now()},
},
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "0.1.0",
BinaryVersion: "0.2.0",
},
},
},
want: &v1alpha1.LeaseCandidate{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate1",
Namespace: "default",
},
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "0.1.0",
BinaryVersion: "0.1.0",
},
},
},
{
name: "multiple candidates, same emulation versions, same binary versions, different creation timestamps",
candidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate1",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now().Add(-1 * time.Hour)},
},
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "0.1.0",
BinaryVersion: "0.1.0",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate2",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now()},
},
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "0.1.0",
BinaryVersion: "0.1.0",
},
},
},
want: &v1alpha1.LeaseCandidate{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate1",
Namespace: "default",
},
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "0.1.0",
BinaryVersion: "0.1.0",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := pickBestLeaderOldestEmulationVersion(tt.candidates)
if got != nil && tt.want != nil {
if got.Name != tt.want.Name || got.Namespace != tt.want.Namespace {
t.Errorf("pickBestLeaderOldestEmulationVersion() = %v, want %v", got, tt.want)
}
} else if got != tt.want {
t.Errorf("pickBestLeaderOldestEmulationVersion() = %v, want %v", got, tt.want)
}
})
}
}
func TestValidLeaseCandidateForOldestEmulationVersion(t *testing.T) {
tests := []struct {
name string
candidate *v1alpha1.LeaseCandidate
want bool
}{
{
name: "valid emulation and binary versions",
candidate: &v1alpha1.LeaseCandidate{
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "0.1.0",
BinaryVersion: "0.1.0",
},
},
want: true,
},
{
name: "invalid emulation version",
candidate: &v1alpha1.LeaseCandidate{
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "invalid",
BinaryVersion: "0.1.0",
},
},
want: false,
},
{
name: "invalid binary version",
candidate: &v1alpha1.LeaseCandidate{
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "0.1.0",
BinaryVersion: "invalid",
},
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := validLeaseCandidateForOldestEmulationVersion(tt.candidate)
if got != tt.want {
t.Errorf("validLeaseCandidateForOldestEmulationVersion() = %v, want %v", got, tt.want)
}
})
}
}
func TestGetEmulationVersion(t *testing.T) {
tests := []struct {
name string
candidate *v1alpha1.LeaseCandidate
want semver.Version
}{
{
name: "valid emulation version",
candidate: &v1alpha1.LeaseCandidate{
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "0.1.0",
},
},
want: semver.MustParse("0.1.0"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := getEmulationVersionOrZero(tt.candidate)
if got.FinalizeVersion() != tt.want.FinalizeVersion() {
t.Errorf("getEmulationVersion() = %v, want %v", got, tt.want)
}
})
}
}
func TestGetBinaryVersion(t *testing.T) {
tests := []struct {
name string
candidate *v1alpha1.LeaseCandidate
want semver.Version
}{
{
name: "valid binary version",
candidate: &v1alpha1.LeaseCandidate{
Spec: v1alpha1.LeaseCandidateSpec{
BinaryVersion: "0.3.0",
},
},
want: semver.MustParse("0.3.0"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := getBinaryVersionOrZero(tt.candidate)
if got.FinalizeVersion() != tt.want.FinalizeVersion() {
t.Errorf("getBinaryVersion() = %v, want %v", got, tt.want)
}
})
}
}
func TestCompare(t *testing.T) {
nowTime := time.Now()
cases := []struct {
name string
lhs *v1alpha1.LeaseCandidate
rhs *v1alpha1.LeaseCandidate
expectedResult int
}{
{
name: "identical versions earlier timestamp",
lhs: &v1alpha1.LeaseCandidate{
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "1.20.0",
BinaryVersion: "1.21.0",
},
ObjectMeta: metav1.ObjectMeta{
CreationTimestamp: metav1.Time{Time: nowTime.Add(time.Duration(1))},
},
},
rhs: &v1alpha1.LeaseCandidate{
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "1.20.0",
BinaryVersion: "1.21.0",
},
ObjectMeta: metav1.ObjectMeta{
CreationTimestamp: metav1.Time{Time: nowTime},
},
},
expectedResult: 1,
},
{
name: "no lhs version",
lhs: &v1alpha1.LeaseCandidate{},
rhs: &v1alpha1.LeaseCandidate{
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "1.20.0",
BinaryVersion: "1.21.0",
},
},
expectedResult: -1,
},
{
name: "no rhs version",
lhs: &v1alpha1.LeaseCandidate{
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "1.20.0",
BinaryVersion: "1.21.0",
},
},
rhs: &v1alpha1.LeaseCandidate{},
expectedResult: 1,
},
{
name: "invalid lhs version",
lhs: &v1alpha1.LeaseCandidate{
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "xyz",
BinaryVersion: "xyz",
},
},
rhs: &v1alpha1.LeaseCandidate{
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "1.20.0",
BinaryVersion: "1.21.0",
},
},
expectedResult: -1,
},
{
name: "invalid rhs version",
lhs: &v1alpha1.LeaseCandidate{
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "1.20.0",
BinaryVersion: "1.21.0",
},
},
rhs: &v1alpha1.LeaseCandidate{
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "xyz",
BinaryVersion: "xyz",
},
},
expectedResult: 1,
},
{
name: "lhs less than rhs",
lhs: &v1alpha1.LeaseCandidate{
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "1.19.0",
BinaryVersion: "1.20.0",
},
},
rhs: &v1alpha1.LeaseCandidate{
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "1.20.0",
BinaryVersion: "1.20.0",
},
},
expectedResult: -1,
},
{
name: "rhs less than lhs",
lhs: &v1alpha1.LeaseCandidate{
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "1.20.0",
BinaryVersion: "1.20.0",
},
},
rhs: &v1alpha1.LeaseCandidate{
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "1.19.0",
BinaryVersion: "1.20.0",
},
},
expectedResult: 1,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
result := compare(tc.lhs, tc.rhs)
if result != tc.expectedResult {
t.Errorf("Expected comparison result of %d but got %d", tc.expectedResult, result)
}
})
}
}
func TestShouldReelect(t *testing.T) {
cases := []struct {
name string
candidates []*v1alpha1.LeaseCandidate
currentLeader *v1alpha1.LeaseCandidate
expectResult bool
}{
{
name: "candidate with newer binary version",
candidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Name: "component-identity-1",
},
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "component-identity-2",
},
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "1.19.0",
BinaryVersion: "1.20.0",
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
},
currentLeader: &v1alpha1.LeaseCandidate{
ObjectMeta: metav1.ObjectMeta{
Name: "component-identity-1",
},
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
expectResult: false,
},
{
name: "no newer candidates",
candidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Name: "component-identity-1",
},
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "component-identity-2",
},
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
},
currentLeader: &v1alpha1.LeaseCandidate{
ObjectMeta: metav1.ObjectMeta{
Name: "component-identity-1",
},
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
expectResult: false,
},
{
name: "no candidates",
candidates: []*v1alpha1.LeaseCandidate{},
currentLeader: &v1alpha1.LeaseCandidate{
ObjectMeta: metav1.ObjectMeta{
Name: "component-identity-1",
},
Spec: v1alpha1.LeaseCandidateSpec{
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
expectResult: false,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
result := shouldReelect(tc.candidates, tc.currentLeader)
if tc.expectResult != result {
t.Errorf("Expected %t but got %t", tc.expectResult, result)
}
})
}
}
func TestTopologicalSortWithOneRoot(t *testing.T) {
tests := []struct {
name string
graph map[v1.CoordinatedLeaseStrategy][]v1.CoordinatedLeaseStrategy
want []v1.CoordinatedLeaseStrategy
}{
{
name: "simple DAG",
graph: map[v1.CoordinatedLeaseStrategy][]v1.CoordinatedLeaseStrategy{
v1.OldestEmulationVersion: {"foo"},
"foo": {"bar"},
"bar": {},
},
want: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion, "foo", "bar"},
},
{
name: "cycle",
graph: map[v1.CoordinatedLeaseStrategy][]v1.CoordinatedLeaseStrategy{
v1.OldestEmulationVersion: {"foo"},
"foo": {v1.OldestEmulationVersion},
},
want: nil,
},
{
name: "multiple",
graph: map[v1.CoordinatedLeaseStrategy][]v1.CoordinatedLeaseStrategy{
v1.OldestEmulationVersion: {"foo", "baz"},
"foo": {"baz"},
"baz": {},
},
want: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion, "foo", "baz"},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := topologicalSortWithOneRoot(tc.graph)
if !equalStrategies(got, tc.want) {
t.Errorf("topologicalSortWithOneRoot() = %v, want %v", got, tc.want)
}
})
}
}
func TestPickBestStrategy(t *testing.T) {
tests := []struct {
name string
candidates []*v1alpha1.LeaseCandidate
wantStrategy v1.CoordinatedLeaseStrategy
wantError bool
}{
{
name: "single candidate, single preferred strategy",
candidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate1",
Namespace: "default",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
},
wantStrategy: v1.OldestEmulationVersion,
wantError: false,
},
{
name: "multiple candidates, different preferred strategies should fail",
candidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate1",
Namespace: "default",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate2",
Namespace: "default",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
PreferredStrategies: []v1.CoordinatedLeaseStrategy{"foo.com/bar"},
},
},
},
wantError: true,
},
{
name: "multiple candidates, multiple resolved preferred strategy",
candidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate1",
Namespace: "default",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion, "foo.com/bar"},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate2",
Namespace: "default",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
PreferredStrategies: []v1.CoordinatedLeaseStrategy{"foo.com/bar"},
},
},
},
wantStrategy: v1.OldestEmulationVersion,
wantError: false,
},
{
name: "multiple candidates, same preferred strategy",
candidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate1",
Namespace: "default",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate2",
Namespace: "default",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
},
wantStrategy: v1.OldestEmulationVersion,
wantError: false,
},
{
name: "multiple candidates, conflicting preferred strategy",
candidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate1",
Namespace: "default",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate2",
Namespace: "default",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
PreferredStrategies: []v1.CoordinatedLeaseStrategy{"foo.com/bar"},
},
},
},
wantStrategy: "",
wantError: true,
},
{
name: "multiple candidates, cycle in preferred strategies",
candidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate1",
Namespace: "default",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
PreferredStrategies: []v1.CoordinatedLeaseStrategy{"foo.com/bar", v1.OldestEmulationVersion},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate2",
Namespace: "default",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion, "foo.com/bar"},
},
},
},
wantError: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
gotStrategy, err := pickBestStrategy(tc.candidates)
gotError := err != nil
if gotError != tc.wantError {
t.Errorf("pickBestStrategy() error = %v,:%v want %v", gotError, err, tc.wantError)
}
if !gotError && gotStrategy != tc.wantStrategy {
t.Errorf("pickBestStrategy() = %v, want %v", gotStrategy, tc.wantStrategy)
}
})
}
}
func equalStrategies(s1, s2 []v1.CoordinatedLeaseStrategy) bool {
if len(s1) != len(s2) {
return false
}
for i := range s1 {
if s1[i] != s2[i] {
return false
}
}
return true
}
func shouldReelect(candidates []*v1alpha1.LeaseCandidate, currentLeader *v1alpha1.LeaseCandidate) bool {
pickedLeader := pickBestLeaderOldestEmulationVersion(candidates)
if pickedLeader == nil {
return false
}
return compare(currentLeader, pickedLeader) > 0
}

View File

@ -0,0 +1,430 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package leaderelection
import (
"context"
"fmt"
"reflect"
"time"
v1 "k8s.io/api/coordination/v1"
v1alpha1 "k8s.io/api/coordination/v1alpha1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
coordinationv1informers "k8s.io/client-go/informers/coordination/v1"
coordinationv1alpha1 "k8s.io/client-go/informers/coordination/v1alpha1"
coordinationv1client "k8s.io/client-go/kubernetes/typed/coordination/v1"
coordinationv1alpha1client "k8s.io/client-go/kubernetes/typed/coordination/v1alpha1"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
"k8s.io/klog/v2"
"k8s.io/utils/ptr"
)
const (
controllerName = "leader-election-controller"
// Requeue interval is the interval at which a Lease is requeued to verify that it is
// being renewed properly.
defaultRequeueInterval = 5 * time.Second
noRequeue = 0
defaultLeaseDurationSeconds int32 = 5
electionDuration = 5 * time.Second
leaseCandidateValidDuration = 30 * time.Minute
)
// Controller is the leader election controller, which observes component identity leases for
// components that have self-nominated as candidate leaders for leases and elects leaders
// for those leases, favoring candidates with higher versions.
type Controller struct {
leaseInformer coordinationv1informers.LeaseInformer
leaseClient coordinationv1client.CoordinationV1Interface
leaseRegistration cache.ResourceEventHandlerRegistration
leaseCandidateInformer coordinationv1alpha1.LeaseCandidateInformer
leaseCandidateClient coordinationv1alpha1client.CoordinationV1alpha1Interface
leaseCandidateRegistration cache.ResourceEventHandlerRegistration
queue workqueue.TypedRateLimitingInterface[types.NamespacedName]
}
func (c *Controller) Run(ctx context.Context, workers int) {
defer utilruntime.HandleCrash()
defer c.queue.ShutDown()
defer func() {
err := c.leaseInformer.Informer().RemoveEventHandler(c.leaseRegistration)
if err != nil {
klog.Warning("error removing leaseInformer eventhandler")
}
err = c.leaseCandidateInformer.Informer().RemoveEventHandler(c.leaseCandidateRegistration)
if err != nil {
klog.Warning("error removing leaseCandidateInformer eventhandler")
}
}()
if !cache.WaitForNamedCacheSync(controllerName, ctx.Done(), c.leaseRegistration.HasSynced, c.leaseCandidateRegistration.HasSynced) {
return
}
// This controller is leader elected and may start after informers have already started. List on startup.
lcs, err := c.leaseCandidateInformer.Lister().List(labels.Everything())
if err != nil {
utilruntime.HandleError(err)
return
}
for _, lc := range lcs {
c.enqueueCandidate(lc)
}
klog.Infof("Workers: %d", workers)
for i := 0; i < workers; i++ {
klog.Infof("Starting worker")
go wait.UntilWithContext(ctx, c.runElectionWorker, time.Second)
}
<-ctx.Done()
}
func NewController(leaseInformer coordinationv1informers.LeaseInformer, leaseCandidateInformer coordinationv1alpha1.LeaseCandidateInformer, leaseClient coordinationv1client.CoordinationV1Interface, leaseCandidateClient coordinationv1alpha1client.CoordinationV1alpha1Interface) (*Controller, error) {
c := &Controller{
leaseInformer: leaseInformer,
leaseCandidateInformer: leaseCandidateInformer,
leaseClient: leaseClient,
leaseCandidateClient: leaseCandidateClient,
queue: workqueue.NewTypedRateLimitingQueueWithConfig(workqueue.DefaultTypedControllerRateLimiter[types.NamespacedName](), workqueue.TypedRateLimitingQueueConfig[types.NamespacedName]{Name: controllerName}),
}
leaseSynced, err := leaseInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
c.enqueueLease(obj)
},
UpdateFunc: func(oldObj, newObj interface{}) {
c.enqueueLease(newObj)
},
DeleteFunc: func(oldObj interface{}) {
c.enqueueLease(oldObj)
},
})
if err != nil {
return nil, err
}
leaseCandidateSynced, err := leaseCandidateInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
c.enqueueCandidate(obj)
},
UpdateFunc: func(oldObj, newObj interface{}) {
c.enqueueCandidate(newObj)
},
DeleteFunc: func(oldObj interface{}) {
c.enqueueCandidate(oldObj)
},
})
if err != nil {
return nil, err
}
c.leaseRegistration = leaseSynced
c.leaseCandidateRegistration = leaseCandidateSynced
return c, nil
}
func (c *Controller) runElectionWorker(ctx context.Context) {
for c.processNextElectionItem(ctx) {
}
}
func (c *Controller) processNextElectionItem(ctx context.Context) bool {
key, shutdown := c.queue.Get()
if shutdown {
return false
}
intervalForRequeue, err := c.reconcileElectionStep(ctx, key)
utilruntime.HandleError(err)
if intervalForRequeue != noRequeue {
defer c.queue.AddAfter(key, intervalForRequeue)
}
c.queue.Done(key)
return true
}
func (c *Controller) enqueueCandidate(obj any) {
lc, ok := obj.(*v1alpha1.LeaseCandidate)
if !ok {
return
}
if lc == nil {
return
}
// Ignore candidates that transitioned to Pending because reelection is already in progress
if lc.Spec.PingTime != nil && lc.Spec.RenewTime.Before(lc.Spec.PingTime) {
return
}
c.queue.Add(types.NamespacedName{Namespace: lc.Namespace, Name: lc.Spec.LeaseName})
}
func (c *Controller) enqueueLease(obj any) {
lease, ok := obj.(*v1.Lease)
if !ok {
return
}
c.queue.Add(types.NamespacedName{Namespace: lease.Namespace, Name: lease.Name})
}
func (c *Controller) electionNeeded(candidates []*v1alpha1.LeaseCandidate, leaseNN types.NamespacedName) (bool, error) {
lease, err := c.leaseInformer.Lister().Leases(leaseNN.Namespace).Get(leaseNN.Name)
if err != nil && !apierrors.IsNotFound(err) {
return false, fmt.Errorf("error reading lease: %w", err)
} else if apierrors.IsNotFound(err) {
return true, nil
}
if isLeaseExpired(lease) || lease.Spec.HolderIdentity == nil || *lease.Spec.HolderIdentity == "" {
return true, nil
}
// every 15min enforce an election to update all candidates. Every 30min we garbage collect.
for _, candidate := range candidates {
if candidate.Spec.RenewTime != nil && candidate.Spec.RenewTime.Add(leaseCandidateValidDuration/2).Before(time.Now()) {
return true, nil
}
}
prelimStrategy, err := pickBestStrategy(candidates)
if err != nil {
return false, err
}
if prelimStrategy != v1.OldestEmulationVersion {
klog.V(5).Infof("Strategy %q is ignored by CLE", prelimStrategy)
return false, nil
}
prelimElectee := pickBestLeaderOldestEmulationVersion(candidates)
if prelimElectee == nil {
return false, nil
} else if lease != nil && lease.Spec.HolderIdentity != nil && prelimElectee.Name == *lease.Spec.HolderIdentity {
klog.V(5).Infof("Leader %s is already most optimal for lease %s", prelimElectee.Name, leaseNN)
return false, nil
}
return true, nil
}
// reconcileElectionStep steps through a step in an election.
// A step looks at the current state of Lease and LeaseCandidates and takes one of the following action
// - do nothing (because leader is already optimal or still waiting for an event)
// - request ack from candidates (update LeaseCandidate PingTime)
// - finds the most optimal candidate and elect (update the Lease object)
// Instead of keeping a map and lock on election, the state is
// calculated every time by looking at the lease, and set of available candidates.
// PingTime + electionDuration > time.Now: We just asked all candidates to ack and are still waiting for response
// PingTime + electionDuration < time.Now: Candidate has not responded within the appropriate PingTime. Continue the election.
// RenewTime + 5 seconds > time.Now: All candidates acked in the last 5 seconds, continue the election.
func (c *Controller) reconcileElectionStep(ctx context.Context, leaseNN types.NamespacedName) (requeue time.Duration, err error) {
candidates, err := c.listAdmissableCandidates(leaseNN)
if err != nil {
return defaultRequeueInterval, err
} else if len(candidates) == 0 {
return noRequeue, nil
}
klog.V(6).Infof("Reconciling election for %s, candidates: %d", leaseNN, len(candidates))
// Check if an election is really needed by looking at the current lease and candidates
needElection, err := c.electionNeeded(candidates, leaseNN)
if !needElection {
return defaultRequeueInterval, err
}
if err != nil {
return defaultRequeueInterval, err
}
now := time.Now()
canVoteYet := true
for _, candidate := range candidates {
if candidate.Spec.PingTime != nil && candidate.Spec.PingTime.Add(electionDuration).After(now) &&
candidate.Spec.RenewTime != nil && candidate.Spec.RenewTime.Before(candidate.Spec.PingTime) {
// continue waiting for the election to timeout
canVoteYet = false
continue
}
if candidate.Spec.RenewTime != nil && candidate.Spec.RenewTime.Add(electionDuration).After(now) {
continue
}
if candidate.Spec.PingTime == nil ||
// If PingTime is outdated, send another PingTime only if it already acked the first one.
(candidate.Spec.PingTime.Add(electionDuration).Before(now) && candidate.Spec.PingTime.Before(candidate.Spec.RenewTime)) {
// TODO(jefftree): We should randomize the order of sending pings and do them in parallel
// so that all candidates have equal opportunity to ack.
clone := candidate.DeepCopy()
clone.Spec.PingTime = &metav1.MicroTime{Time: now}
_, err := c.leaseCandidateClient.LeaseCandidates(clone.Namespace).Update(ctx, clone, metav1.UpdateOptions{})
if err != nil {
return defaultRequeueInterval, err
}
canVoteYet = false
}
}
if !canVoteYet {
return defaultRequeueInterval, nil
}
// election is ongoing as long as unexpired PingTimes exist
for _, candidate := range candidates {
if candidate.Spec.PingTime == nil {
continue // shouldn't be the case after the above
}
if candidate.Spec.RenewTime != nil && candidate.Spec.PingTime.Before(candidate.Spec.RenewTime) {
continue // this has renewed already
}
// If a candidate has a PingTime within the election duration, they have not acked
// and we should wait until we receive their response
if candidate.Spec.PingTime.Add(electionDuration).After(now) {
// continue waiting for the election to timeout
return noRequeue, nil
}
}
var ackedCandidates []*v1alpha1.LeaseCandidate
for _, candidate := range candidates {
if candidate.Spec.RenewTime.Add(electionDuration).After(now) {
ackedCandidates = append(ackedCandidates, candidate)
}
}
if len(ackedCandidates) == 0 {
return noRequeue, fmt.Errorf("no available candidates")
}
strategy, err := pickBestStrategy(ackedCandidates)
if err != nil {
return noRequeue, err
}
leaderLease := &v1.Lease{
ObjectMeta: metav1.ObjectMeta{
Namespace: leaseNN.Namespace,
Name: leaseNN.Name,
},
Spec: v1.LeaseSpec{
Strategy: &strategy,
LeaseDurationSeconds: ptr.To(defaultLeaseDurationSeconds),
RenewTime: &metav1.MicroTime{Time: time.Now()},
},
}
switch strategy {
case v1.OldestEmulationVersion:
electee := pickBestLeaderOldestEmulationVersion(ackedCandidates)
if electee == nil {
return noRequeue, fmt.Errorf("should not happen, could not find suitable electee")
}
leaderLease.Spec.HolderIdentity = &electee.Name
default:
// do not set the holder identity, but leave it to some other controller. But fall
// through to create the lease (without holder).
klog.V(2).Infof("Election for strategy %q is not handled by %s", strategy, controllerName)
}
// create the leader election lease
_, err = c.leaseClient.Leases(leaseNN.Namespace).Create(ctx, leaderLease, metav1.CreateOptions{})
if err == nil {
if leaderLease.Spec.HolderIdentity != nil {
klog.Infof("Created lease %s for %q", leaseNN, *leaderLease.Spec.HolderIdentity)
} else {
klog.Infof("Created lease %s without leader", leaseNN)
}
return defaultRequeueInterval, nil
} else if !apierrors.IsAlreadyExists(err) {
return noRequeue, err
}
// Get existing lease
existing, err := c.leaseClient.Leases(leaseNN.Namespace).Get(ctx, leaseNN.Name, metav1.GetOptions{})
if err != nil {
return noRequeue, err
}
orig := existing.DeepCopy()
isExpired := isLeaseExpired(existing)
noHolderIdentity := leaderLease.Spec.HolderIdentity != nil && existing.Spec.HolderIdentity == nil || *existing.Spec.HolderIdentity == ""
expiredAndNewHolder := isExpired && leaderLease.Spec.HolderIdentity != nil && *existing.Spec.HolderIdentity != *leaderLease.Spec.HolderIdentity
strategyChanged := existing.Spec.Strategy == nil || *existing.Spec.Strategy != strategy
differentHolder := leaderLease.Spec.HolderIdentity != nil && *leaderLease.Spec.HolderIdentity != *existing.Spec.HolderIdentity
// Update lease
if strategyChanged {
klog.Infof("Lease %s strategy changed to %q", leaseNN, strategy)
existing.Spec.Strategy = &strategy
}
if noHolderIdentity || expiredAndNewHolder {
if noHolderIdentity {
klog.Infof("Lease %s had no holder, setting holder to %q", leaseNN, *leaderLease.Spec.HolderIdentity)
} else {
klog.Infof("Lease %s expired, resetting it and setting holder to %q", leaseNN, *leaderLease.Spec.HolderIdentity)
}
existing.Spec.PreferredHolder = nil
existing.Spec.HolderIdentity = leaderLease.Spec.HolderIdentity
existing.Spec.RenewTime = &metav1.MicroTime{Time: time.Now()}
existing.Spec.LeaseDurationSeconds = ptr.To(defaultLeaseDurationSeconds)
existing.Spec.AcquireTime = nil
} else if differentHolder {
klog.Infof("Lease %s holder changed from %q to %q", leaseNN, *existing.Spec.HolderIdentity, *leaderLease.Spec.HolderIdentity)
existing.Spec.PreferredHolder = leaderLease.Spec.HolderIdentity
}
if reflect.DeepEqual(existing, orig) {
klog.V(5).Infof("Lease %s already has the most optimal leader %q", leaseNN, *existing.Spec.HolderIdentity)
// We need to requeue to ensure that we are aware of an expired lease
return defaultRequeueInterval, nil
}
_, err = c.leaseClient.Leases(leaseNN.Namespace).Update(ctx, existing, metav1.UpdateOptions{})
if err != nil {
return noRequeue, err
}
return defaultRequeueInterval, nil
}
func (c *Controller) listAdmissableCandidates(leaseNN types.NamespacedName) ([]*v1alpha1.LeaseCandidate, error) {
leases, err := c.leaseCandidateInformer.Lister().LeaseCandidates(leaseNN.Namespace).List(labels.Everything())
if err != nil {
return nil, err
}
var results []*v1alpha1.LeaseCandidate
for _, l := range leases {
if l.Spec.LeaseName != leaseNN.Name {
continue
}
if !isLeaseCandidateExpired(l) {
results = append(results, l)
} else {
klog.Infof("LeaseCandidate %s is expired", l.Name)
}
}
return results, nil
}

View File

@ -0,0 +1,747 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package leaderelection
import (
"context"
"fmt"
"testing"
"time"
v1 "k8s.io/api/coordination/v1"
v1alpha1 "k8s.io/api/coordination/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/utils/ptr"
"k8s.io/client-go/tools/cache"
)
func TestReconcileElectionStep(t *testing.T) {
tests := []struct {
name string
leaseNN types.NamespacedName
candidates []*v1alpha1.LeaseCandidate
existingLease *v1.Lease
expectLease bool
expectedHolderIdentity *string
expectedPreferredHolder *string
expectedRequeue bool
expectedError bool
expectedStrategy *v1.CoordinatedLeaseStrategy
candidatesPinged bool
}{
{
name: "no candidates, no lease, noop",
leaseNN: types.NamespacedName{Namespace: "default", Name: "component-A"},
candidates: []*v1alpha1.LeaseCandidate{},
existingLease: nil,
expectLease: false,
expectedHolderIdentity: nil,
expectedStrategy: nil,
expectedRequeue: false,
expectedError: false,
},
{
name: "no candidates, lease exists. noop, not managed by CLE",
leaseNN: types.NamespacedName{Namespace: "default", Name: "component-A"},
candidates: []*v1alpha1.LeaseCandidate{},
existingLease: &v1.Lease{},
expectLease: false,
expectedHolderIdentity: nil,
expectedStrategy: nil,
expectedRequeue: false,
expectedError: false,
},
{
name: "candidates exist, no existing lease should create lease",
leaseNN: types.NamespacedName{Namespace: "default", Name: "component-A"},
candidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "component-identity-1",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
RenewTime: ptr.To(metav1.NewMicroTime(time.Now())),
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
},
existingLease: nil,
expectLease: true,
expectedHolderIdentity: ptr.To("component-identity-1"),
expectedStrategy: ptr.To[v1.CoordinatedLeaseStrategy]("OldestEmulationVersion"),
expectedRequeue: true,
expectedError: false,
},
{
name: "candidates exist, lease exists, unoptimal should set preferredHolder",
leaseNN: types.NamespacedName{Namespace: "default", Name: "component-A"},
candidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "component-identity-1",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
RenewTime: ptr.To(metav1.NewMicroTime(time.Now())),
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "component-identity-2",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
EmulationVersion: "1.18.0",
BinaryVersion: "1.18.0",
RenewTime: ptr.To(metav1.NewMicroTime(time.Now())),
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
},
existingLease: &v1.Lease{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "component-A",
},
Spec: v1.LeaseSpec{
HolderIdentity: ptr.To("component-identity-1"),
LeaseDurationSeconds: ptr.To(int32(10)),
RenewTime: ptr.To(metav1.NewMicroTime(time.Now())),
},
},
expectLease: true,
expectedHolderIdentity: ptr.To("component-identity-1"),
expectedPreferredHolder: ptr.To("component-identity-2"),
expectedStrategy: ptr.To[v1.CoordinatedLeaseStrategy]("OldestEmulationVersion"),
expectedRequeue: true,
expectedError: false,
},
{
name: "candidates exist, should only elect leader from acked candidates",
leaseNN: types.NamespacedName{Namespace: "default", Name: "component-A"},
candidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "component-identity-1",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
PingTime: ptr.To(metav1.NewMicroTime(time.Now().Add(-2 * electionDuration))),
RenewTime: ptr.To(metav1.NewMicroTime(time.Now().Add(-4 * electionDuration))),
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "component-identity-2",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
EmulationVersion: "1.20.0",
BinaryVersion: "1.20.0",
PingTime: ptr.To(metav1.NewMicroTime(time.Now())),
RenewTime: ptr.To(metav1.NewMicroTime(time.Now())),
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
},
existingLease: nil,
expectLease: true,
expectedHolderIdentity: ptr.To("component-identity-2"),
expectedStrategy: ptr.To[v1.CoordinatedLeaseStrategy]("OldestEmulationVersion"),
expectedRequeue: true,
expectedError: false,
},
{
name: "candidates exist, lease exists, lease expired",
leaseNN: types.NamespacedName{Namespace: "default", Name: "component-A"},
candidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "component-identity-1",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
RenewTime: ptr.To(metav1.NewMicroTime(time.Now())),
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
},
existingLease: &v1.Lease{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "component-A",
},
Spec: v1.LeaseSpec{
HolderIdentity: ptr.To("component-identity-expired"),
LeaseDurationSeconds: ptr.To(int32(10)),
RenewTime: ptr.To(metav1.NewMicroTime(time.Now().Add(-1 * time.Minute))),
},
},
expectLease: true,
expectedHolderIdentity: ptr.To("component-identity-1"),
expectedStrategy: ptr.To[v1.CoordinatedLeaseStrategy]("OldestEmulationVersion"),
expectedRequeue: true,
expectedError: false,
},
{
name: "candidates exist, no acked candidates should return error",
leaseNN: types.NamespacedName{Namespace: "default", Name: "component-A"},
candidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "component-identity-1",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
PingTime: ptr.To(metav1.NewMicroTime(time.Now().Add(-1 * time.Minute))),
RenewTime: ptr.To(metav1.NewMicroTime(time.Now().Add(-2 * time.Minute))),
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
},
existingLease: nil,
expectLease: false,
expectedHolderIdentity: nil,
expectedRequeue: false,
expectedError: true,
},
{
name: "candidates exist, should ping on election",
leaseNN: types.NamespacedName{Namespace: "default", Name: "component-A"},
candidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "component-identity-1",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
RenewTime: ptr.To(metav1.NewMicroTime(time.Now().Add(-2 * electionDuration))),
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
},
existingLease: nil,
expectLease: false,
expectedHolderIdentity: nil,
expectedStrategy: nil,
expectedRequeue: true,
expectedError: false,
candidatesPinged: true,
},
{
name: "candidate exist, pinged candidate should have until electionDuration until election decision is made",
leaseNN: types.NamespacedName{Namespace: "default", Name: "component-A"},
candidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "component-identity-1",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
PingTime: ptr.To(metav1.NewMicroTime(time.Now())),
RenewTime: ptr.To(metav1.NewMicroTime(time.Now().Add(-1 * time.Minute))),
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
},
existingLease: nil,
expectLease: false,
expectedHolderIdentity: nil,
expectedRequeue: true,
expectedError: false,
},
{
name: "candidates exist, lease exists, lease expired, 3rdparty strategy",
leaseNN: types.NamespacedName{Namespace: "default", Name: "component-A"},
candidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "component-identity-1",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
RenewTime: ptr.To(metav1.NewMicroTime(time.Now())),
PreferredStrategies: []v1.CoordinatedLeaseStrategy{"foo.com/bar"},
},
},
},
existingLease: &v1.Lease{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "component-A",
},
Spec: v1.LeaseSpec{
HolderIdentity: ptr.To("component-identity-expired"),
LeaseDurationSeconds: ptr.To(int32(10)),
RenewTime: ptr.To(metav1.NewMicroTime(time.Now().Add(-1 * time.Minute))),
},
},
expectLease: true,
expectedHolderIdentity: ptr.To("component-identity-expired"),
expectedStrategy: ptr.To[v1.CoordinatedLeaseStrategy]("foo.com/bar"),
expectedRequeue: true,
expectedError: false,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ctx := context.Background()
client := fake.NewSimpleClientset()
informerFactory := informers.NewSharedInformerFactory(client, 0)
_ = informerFactory.Coordination().V1alpha1().LeaseCandidates().Lister()
controller, err := NewController(
informerFactory.Coordination().V1().Leases(),
informerFactory.Coordination().V1alpha1().LeaseCandidates(),
client.CoordinationV1(),
client.CoordinationV1alpha1(),
)
if err != nil {
t.Fatal(err)
}
go informerFactory.Start(ctx.Done())
informerFactory.WaitForCacheSync(ctx.Done())
// Set up the fake client with the existing lease
if tc.existingLease != nil {
_, err = client.CoordinationV1().Leases(tc.existingLease.Namespace).Create(ctx, tc.existingLease, metav1.CreateOptions{})
if err != nil {
t.Fatal(err)
}
}
// Set up the fake client with the candidates
for _, candidate := range tc.candidates {
_, err = client.CoordinationV1alpha1().LeaseCandidates(candidate.Namespace).Create(ctx, candidate, metav1.CreateOptions{})
if err != nil {
t.Fatal(err)
}
}
cache.WaitForCacheSync(ctx.Done(), controller.leaseCandidateInformer.Informer().HasSynced)
requeue, err := controller.reconcileElectionStep(ctx, tc.leaseNN)
if (requeue != 0) != tc.expectedRequeue {
t.Errorf("reconcileElectionStep() requeue = %v, want %v", requeue, tc.expectedRequeue)
}
if tc.expectedError && err == nil {
t.Errorf("reconcileElectionStep() error = %v, want error", err)
} else if !tc.expectedError && err != nil {
t.Errorf("reconcileElectionStep() error = %v, want nil", err)
}
lease, err := client.CoordinationV1().Leases(tc.leaseNN.Namespace).Get(ctx, tc.leaseNN.Name, metav1.GetOptions{})
if tc.expectLease {
if err != nil {
t.Fatal(err)
}
// Check the lease holder identity
if tc.expectedHolderIdentity != nil && (lease.Spec.HolderIdentity == nil || *lease.Spec.HolderIdentity != *tc.expectedHolderIdentity) {
t.Errorf("reconcileElectionStep() holderIdentity = %s, want %s", strOrNil(lease.Spec.HolderIdentity), *tc.expectedHolderIdentity)
} else if tc.expectedHolderIdentity == nil && lease.Spec.HolderIdentity != nil && *lease.Spec.HolderIdentity != "" {
t.Errorf("reconcileElectionStep() holderIdentity = %s, want nil", *lease.Spec.HolderIdentity)
}
if tc.expectedPreferredHolder != nil && (lease.Spec.PreferredHolder == nil || *lease.Spec.PreferredHolder != *tc.expectedPreferredHolder) {
t.Errorf("reconcileElectionStep() preferredHolder = %s, want %s", strOrNil(lease.Spec.PreferredHolder), *tc.expectedPreferredHolder)
} else if tc.expectedPreferredHolder == nil && lease.Spec.PreferredHolder != nil && *lease.Spec.PreferredHolder != "" {
t.Errorf("reconcileElectionStep() preferredHolder = %s, want nil", *lease.Spec.PreferredHolder)
}
// Check chosen strategy in the Lease
if tc.expectedStrategy != nil && (lease.Spec.Strategy == nil || *lease.Spec.Strategy != *tc.expectedStrategy) {
t.Errorf("reconcileElectionStep() strategy = %s, want %s", strOrNil(lease.Spec.Strategy), *tc.expectedStrategy)
} else if tc.expectedStrategy == nil && lease.Spec.Strategy != nil && *lease.Spec.Strategy != "" {
t.Errorf("reconcileElectionStep() strategy = %s, want nil", *lease.Spec.Strategy)
}
} else if err == nil {
t.Errorf("reconcileElectionStep() expected no lease to be created")
}
// Verify that ping to candidate was issued
if tc.candidatesPinged {
pinged := false
candidatesList, err := client.CoordinationV1alpha1().LeaseCandidates(tc.leaseNN.Namespace).List(ctx, metav1.ListOptions{})
if err != nil {
t.Fatal(err)
}
oldCandidateMap := make(map[string]*v1alpha1.LeaseCandidate)
for _, candidate := range tc.candidates {
oldCandidateMap[candidate.Name] = candidate
}
for _, candidate := range candidatesList.Items {
if candidate.Spec.PingTime != nil {
if oldCandidateMap[candidate.Name].Spec.PingTime == nil {
pinged = true
break
}
}
}
if !pinged {
t.Errorf("reconcileElectionStep() expected candidates to be pinged")
}
}
})
}
}
func TestController(t *testing.T) {
cases := []struct {
name string
leaseNN types.NamespacedName
createAfterControllerStart []*v1alpha1.LeaseCandidate
deleteAfterControllerStart []types.NamespacedName
expectedLeaderLeases []*v1.Lease
}{
{
name: "single candidate leader election",
leaseNN: types.NamespacedName{Namespace: "kube-system", Name: "component-A"},
createAfterControllerStart: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "kube-system",
Name: "component-identity-1",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
RenewTime: ptr.To(metav1.NewMicroTime(time.Now())),
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
},
expectedLeaderLeases: []*v1.Lease{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "kube-system",
Name: "component-A",
},
Spec: v1.LeaseSpec{
HolderIdentity: ptr.To("component-identity-1"),
},
},
},
},
{
name: "multiple candidate leader election",
leaseNN: types.NamespacedName{Namespace: "kube-system", Name: "component-A"},
createAfterControllerStart: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "kube-system",
Name: "component-identity-1",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
RenewTime: ptr.To(metav1.NewMicroTime(time.Now())),
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "kube-system",
Name: "component-identity-2",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
EmulationVersion: "1.19.0",
BinaryVersion: "1.20.0",
RenewTime: ptr.To(metav1.NewMicroTime(time.Now())),
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "kube-system",
Name: "component-identity-3",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
EmulationVersion: "1.20.0",
BinaryVersion: "1.20.0",
RenewTime: ptr.To(metav1.NewMicroTime(time.Now())),
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
},
expectedLeaderLeases: []*v1.Lease{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "kube-system",
Name: "component-A",
},
Spec: v1.LeaseSpec{
HolderIdentity: ptr.To("component-identity-1"),
},
},
},
},
{
name: "deletion of lease triggers reelection",
leaseNN: types.NamespacedName{Namespace: "kube-system", Name: "component-A"},
createAfterControllerStart: []*v1alpha1.LeaseCandidate{
{
// Leader lease
ObjectMeta: metav1.ObjectMeta{
Namespace: "kube-system",
Name: "component-A",
},
Spec: v1alpha1.LeaseCandidateSpec{},
},
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "kube-system",
Name: "component-identity-1",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
RenewTime: ptr.To(metav1.NewMicroTime(time.Now())),
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
},
deleteAfterControllerStart: []types.NamespacedName{
{Namespace: "kube-system", Name: "component-A"},
},
expectedLeaderLeases: []*v1.Lease{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "kube-system",
Name: "component-A",
},
Spec: v1.LeaseSpec{
HolderIdentity: ptr.To("component-identity-1"),
},
},
},
},
{
name: "better candidate triggers reelection",
leaseNN: types.NamespacedName{Namespace: "kube-system", Name: "component-A"},
createAfterControllerStart: []*v1alpha1.LeaseCandidate{
{
// Leader lease
ObjectMeta: metav1.ObjectMeta{
Namespace: "kube-system",
Name: "component-A",
},
Spec: v1alpha1.LeaseCandidateSpec{},
},
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "kube-system",
Name: "component-identity-1",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
EmulationVersion: "1.20.0",
BinaryVersion: "1.20.0",
RenewTime: ptr.To(metav1.NewMicroTime(time.Now())),
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "kube-system",
Name: "component-identity-2",
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
RenewTime: ptr.To(metav1.NewMicroTime(time.Now())),
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
},
expectedLeaderLeases: []*v1.Lease{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "kube-system",
Name: "component-A",
},
Spec: v1.LeaseSpec{
HolderIdentity: ptr.To("component-identity-2"),
},
},
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
client := fake.NewSimpleClientset()
informerFactory := informers.NewSharedInformerFactory(client, 0)
controller, err := NewController(
informerFactory.Coordination().V1().Leases(),
informerFactory.Coordination().V1alpha1().LeaseCandidates(),
client.CoordinationV1(),
client.CoordinationV1alpha1(),
)
if err != nil {
t.Fatal(err)
}
go informerFactory.Start(ctx.Done())
go controller.Run(ctx, 1)
go func() {
ticker := time.NewTicker(10 * time.Millisecond)
// Mock out the removal of preferredHolder leases.
// When controllers are running, they are expected to do this voluntarily
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
for _, expectedLease := range tc.expectedLeaderLeases {
lease, err := client.CoordinationV1().Leases(expectedLease.Namespace).Get(ctx, expectedLease.Name, metav1.GetOptions{})
if err == nil {
if preferredHolder := lease.Spec.PreferredHolder; preferredHolder != nil {
err = client.CoordinationV1().Leases(expectedLease.Namespace).Delete(ctx, expectedLease.Name, metav1.DeleteOptions{})
if err != nil {
runtime.HandleError(err)
}
}
}
}
}
}
}()
go func() {
ticker := time.NewTicker(10 * time.Millisecond)
// Mock out leasecandidate ack.
// When controllers are running, they are expected to watch and ack
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
for _, lc := range tc.createAfterControllerStart {
lease, err := client.CoordinationV1alpha1().LeaseCandidates(lc.Namespace).Get(ctx, lc.Name, metav1.GetOptions{})
if err == nil {
if lease.Spec.PingTime != nil {
c := lease.DeepCopy()
c.Spec.RenewTime = &metav1.MicroTime{Time: time.Now()}
_, err = client.CoordinationV1alpha1().LeaseCandidates(lc.Namespace).Update(ctx, c, metav1.UpdateOptions{})
if err != nil {
runtime.HandleError(err)
}
}
}
}
}
}
}()
for _, obj := range tc.createAfterControllerStart {
_, err := client.CoordinationV1alpha1().LeaseCandidates(obj.Namespace).Create(ctx, obj, metav1.CreateOptions{})
if err != nil {
t.Fatal(err)
}
}
for _, obj := range tc.deleteAfterControllerStart {
err := client.CoordinationV1alpha1().LeaseCandidates(obj.Namespace).Delete(ctx, obj.Name, metav1.DeleteOptions{})
if err != nil {
t.Fatal(err)
}
}
for _, expectedLease := range tc.expectedLeaderLeases {
var lease *v1.Lease
err = wait.PollUntilContextTimeout(ctx, 100*time.Millisecond, 600*time.Second, true, func(ctx context.Context) (done bool, err error) {
lease, err = client.CoordinationV1().Leases(expectedLease.Namespace).Get(ctx, expectedLease.Name, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return false, nil
}
return true, err
}
if expectedLease.Spec.HolderIdentity == nil || lease.Spec.HolderIdentity == nil {
return expectedLease.Spec.HolderIdentity == nil && lease.Spec.HolderIdentity == nil, nil
}
if expectedLease.Spec.HolderIdentity != nil && lease.Spec.HolderIdentity != nil && *expectedLease.Spec.HolderIdentity != *lease.Spec.HolderIdentity {
return false, nil
}
return true, nil
})
if err != nil {
t.Fatal(err)
}
if lease.Spec.HolderIdentity == nil {
t.Fatalf("Expected HolderIdentity of %s but got nil", expectedLease.Name)
}
if *lease.Spec.HolderIdentity != *expectedLease.Spec.HolderIdentity {
t.Errorf("Expected HolderIdentity of %s but got %s", *expectedLease.Spec.HolderIdentity, *lease.Spec.HolderIdentity)
}
}
})
}
}
func strOrNil[T any](s *T) string {
if s == nil {
return "<nil>"
}
return fmt.Sprintf("%v", *s)
}

View File

@ -0,0 +1,100 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package leaderelection
import (
"context"
"fmt"
"time"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
coordinationv1alpha1informers "k8s.io/client-go/informers/coordination/v1alpha1"
"k8s.io/client-go/kubernetes"
listers "k8s.io/client-go/listers/coordination/v1alpha1"
"k8s.io/client-go/tools/cache"
"k8s.io/klog/v2"
)
type LeaseCandidateGCController struct {
kubeclientset kubernetes.Interface
leaseCandidateLister listers.LeaseCandidateLister
leaseCandidateInformer coordinationv1alpha1informers.LeaseCandidateInformer
leaseCandidatesSynced cache.InformerSynced
gcCheckPeriod time.Duration
}
// NewLeaseCandidateGC creates a new LeaseCandidateGCController.
func NewLeaseCandidateGC(clientset kubernetes.Interface, gcCheckPeriod time.Duration, leaseCandidateInformer coordinationv1alpha1informers.LeaseCandidateInformer) *LeaseCandidateGCController {
return &LeaseCandidateGCController{
kubeclientset: clientset,
leaseCandidateLister: leaseCandidateInformer.Lister(),
leaseCandidateInformer: leaseCandidateInformer,
leaseCandidatesSynced: leaseCandidateInformer.Informer().HasSynced,
gcCheckPeriod: gcCheckPeriod,
}
}
// Run starts one worker.
func (c *LeaseCandidateGCController) Run(ctx context.Context) {
defer utilruntime.HandleCrash()
defer klog.Infof("Shutting down apiserver leasecandidate garbage collector")
klog.Infof("Starting apiserver leasecandidate garbage collector")
if !cache.WaitForCacheSync(ctx.Done(), c.leaseCandidatesSynced) {
utilruntime.HandleError(fmt.Errorf("timed out waiting for caches to sync"))
return
}
go wait.UntilWithContext(ctx, c.gc, c.gcCheckPeriod)
<-ctx.Done()
}
func (c *LeaseCandidateGCController) gc(ctx context.Context) {
lcs, err := c.leaseCandidateLister.List(labels.Everything())
if err != nil {
klog.ErrorS(err, "Error while listing lease candidates")
return
}
for _, leaseCandidate := range lcs {
// evaluate lease from cache
if !isLeaseCandidateExpired(leaseCandidate) {
continue
}
lc, err := c.kubeclientset.CoordinationV1alpha1().LeaseCandidates(leaseCandidate.Namespace).Get(ctx, leaseCandidate.Name, metav1.GetOptions{})
if err != nil {
klog.ErrorS(err, "Error getting lc")
continue
}
// evaluate lease from apiserver
if !isLeaseCandidateExpired(lc) {
continue
}
if err := c.kubeclientset.CoordinationV1alpha1().LeaseCandidates(lc.Namespace).Delete(
ctx, lc.Name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
klog.ErrorS(err, "Error deleting lease")
}
}
}

View File

@ -0,0 +1,144 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package leaderelection
import (
"context"
"testing"
"time"
v1 "k8s.io/api/coordination/v1"
v1alpha1 "k8s.io/api/coordination/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/tools/cache"
"k8s.io/utils/ptr"
)
func TestLeaseCandidateGCController(t *testing.T) {
tests := []struct {
name string
leaseCandidates []*v1alpha1.LeaseCandidate
expectedDeletedCount int
}{
{
name: "delete expired lease candidates",
leaseCandidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate1",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now().Add(-1 * leaseCandidateValidDuration)},
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
RenewTime: ptr.To(metav1.NewMicroTime(time.Now().Add(-1 * leaseCandidateValidDuration))),
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate2",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now().Add(-1 * leaseCandidateValidDuration)},
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-B",
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
RenewTime: ptr.To(metav1.NewMicroTime(time.Now().Add(-1 * leaseCandidateValidDuration))),
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate3",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now()},
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-C",
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
RenewTime: ptr.To(metav1.NewMicroTime(time.Now())),
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
},
expectedDeletedCount: 2,
},
{
name: "no expired lease candidates",
leaseCandidates: []*v1alpha1.LeaseCandidate{
{
ObjectMeta: metav1.ObjectMeta{
Name: "candidate1",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now()},
},
Spec: v1alpha1.LeaseCandidateSpec{
LeaseName: "component-A",
EmulationVersion: "1.19.0",
BinaryVersion: "1.19.0",
RenewTime: ptr.To(metav1.NewMicroTime(time.Now())),
PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
},
},
},
expectedDeletedCount: 0,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ctx := context.Background()
client := fake.NewSimpleClientset()
informerFactory := informers.NewSharedInformerFactory(client, 0)
leaseCandidateInformer := informerFactory.Coordination().V1alpha1().LeaseCandidates()
controller := NewLeaseCandidateGC(client, 10*time.Millisecond, leaseCandidateInformer)
go informerFactory.Start(ctx.Done())
informerFactory.WaitForCacheSync(ctx.Done())
// Create lease candidates
for _, lc := range tc.leaseCandidates {
_, err := client.CoordinationV1alpha1().LeaseCandidates(lc.Namespace).Create(ctx, lc, metav1.CreateOptions{})
if err != nil {
t.Fatal(err)
}
}
cache.WaitForCacheSync(ctx.Done(), controller.leaseCandidatesSynced)
go controller.Run(ctx)
err := wait.PollUntilContextTimeout(ctx, 100*time.Millisecond, 600*time.Second, true, func(ctx context.Context) (done bool, err error) {
lcs, err := client.CoordinationV1alpha1().LeaseCandidates("default").List(ctx, metav1.ListOptions{})
if err != nil {
return true, err
}
return len(lcs.Items) == len(tc.leaseCandidates)-tc.expectedDeletedCount, nil
})
if err != nil {
t.Fatal(err)
}
})
}
}

View File

@ -0,0 +1,91 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package leaderelection
import (
"context"
"os"
"time"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/leaderelection"
"k8s.io/client-go/tools/leaderelection/resourcelock"
"k8s.io/klog/v2"
)
type NewRunner func() (func(ctx context.Context, workers int), error)
// RunWithLeaderElection runs the provided runner function with leader election.
// newRunnerFn might be called multiple times, and it should return another
// controller instance's Run method each time.
// RunWithLeaderElection only returns when the context is done, or initial
// leader election fails.
func RunWithLeaderElection(ctx context.Context, config *rest.Config, newRunnerFn NewRunner) {
var cancel context.CancelFunc
callbacks := leaderelection.LeaderCallbacks{
OnStartedLeading: func(ctx context.Context) {
ctx, cancel = context.WithCancel(ctx)
var err error
run, err := newRunnerFn()
if err != nil {
klog.Infof("Error creating runner: %v", err)
return
}
run(ctx, 1)
},
OnStoppedLeading: func() {
cancel()
},
}
hostname, err := os.Hostname()
if err != nil {
klog.Infof("Error parsing hostname: %v", err)
return
}
rl, err := resourcelock.NewFromKubeconfig(
"leases",
"kube-system",
controllerName,
resourcelock.ResourceLockConfig{
Identity: hostname + "_" + string(uuid.NewUUID()),
},
config,
10,
)
if err != nil {
klog.Infof("Error creating resourcelock: %v", err)
return
}
le, err := leaderelection.NewLeaderElector(leaderelection.LeaderElectionConfig{
Lock: rl,
LeaseDuration: 15 * time.Second,
RenewDeadline: 10 * time.Second,
RetryPeriod: 2 * time.Second,
Callbacks: callbacks,
Name: controllerName,
})
if err != nil {
klog.Infof("Error creating leader elector: %v", err)
return
}
le.Run(ctx)
}

View File

@ -38,6 +38,7 @@ import (
certificatesapiv1 "k8s.io/api/certificates/v1"
certificatesv1alpha1 "k8s.io/api/certificates/v1alpha1"
coordinationapiv1 "k8s.io/api/coordination/v1"
coordinationv1alpha1 "k8s.io/api/coordination/v1alpha1"
apiv1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
eventsv1 "k8s.io/api/events/v1"
@ -475,6 +476,8 @@ var (
admissionregistrationv1alpha1.SchemeGroupVersion,
apiserverinternalv1alpha1.SchemeGroupVersion,
authenticationv1alpha1.SchemeGroupVersion,
apiserverinternalv1alpha1.SchemeGroupVersion,
coordinationv1alpha1.SchemeGroupVersion,
resourceapi.SchemeGroupVersion,
certificatesv1alpha1.SchemeGroupVersion,
networkingapiv1alpha1.SchemeGroupVersion,

View File

@ -152,6 +152,13 @@ const (
// Allow the usage of options to fine-tune the cpumanager policies.
CPUManagerPolicyOptions featuregate.Feature = "CPUManagerPolicyOptions"
// owner: @jefftree
// kep: https://kep.k8s.io/4355
// alpha: v1.31
//
// Enables coordinated leader election in the API server
CoordinatedLeaderElection featuregate.Feature = "CoordinatedLeaderElection"
// owner: @trierra
// kep: http://kep.k8s.io/2589
// alpha: v1.23
@ -1256,6 +1263,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
genericfeatures.ConsistentListFromCache: {Default: false, PreRelease: featuregate.Alpha},
genericfeatures.CoordinatedLeaderElection: {Default: false, PreRelease: featuregate.Alpha},
genericfeatures.EfficientWatchResumption: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
genericfeatures.KMSv1: {Default: false, PreRelease: featuregate.Deprecated},

View File

@ -376,6 +376,9 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"k8s.io/api/coordination/v1.Lease": schema_k8sio_api_coordination_v1_Lease(ref),
"k8s.io/api/coordination/v1.LeaseList": schema_k8sio_api_coordination_v1_LeaseList(ref),
"k8s.io/api/coordination/v1.LeaseSpec": schema_k8sio_api_coordination_v1_LeaseSpec(ref),
"k8s.io/api/coordination/v1alpha1.LeaseCandidate": schema_k8sio_api_coordination_v1alpha1_LeaseCandidate(ref),
"k8s.io/api/coordination/v1alpha1.LeaseCandidateList": schema_k8sio_api_coordination_v1alpha1_LeaseCandidateList(ref),
"k8s.io/api/coordination/v1alpha1.LeaseCandidateSpec": schema_k8sio_api_coordination_v1alpha1_LeaseCandidateSpec(ref),
"k8s.io/api/coordination/v1beta1.Lease": schema_k8sio_api_coordination_v1beta1_Lease(ref),
"k8s.io/api/coordination/v1beta1.LeaseList": schema_k8sio_api_coordination_v1beta1_LeaseList(ref),
"k8s.io/api/coordination/v1beta1.LeaseSpec": schema_k8sio_api_coordination_v1beta1_LeaseSpec(ref),
@ -18845,14 +18848,14 @@ func schema_k8sio_api_coordination_v1_LeaseSpec(ref common.ReferenceCallback) co
Properties: map[string]spec.Schema{
"holderIdentity": {
SchemaProps: spec.SchemaProps{
Description: "holderIdentity contains the identity of the holder of a current lease.",
Description: "holderIdentity contains the identity of the holder of a current lease. If Coordinated Leader Election is used, the holder identity must be equal to the elected LeaseCandidate.metadata.name field.",
Type: []string{"string"},
Format: "",
},
},
"leaseDurationSeconds": {
SchemaProps: spec.SchemaProps{
Description: "leaseDurationSeconds is a duration that candidates for a lease need to wait to force acquire it. This is measure against time of last observed renewTime.",
Description: "leaseDurationSeconds is a duration that candidates for a lease need to wait to force acquire it. This is measured against the time of last observed renewTime.",
Type: []string{"integer"},
Format: "int32",
},
@ -18876,6 +18879,20 @@ func schema_k8sio_api_coordination_v1_LeaseSpec(ref common.ReferenceCallback) co
Format: "int32",
},
},
"strategy": {
SchemaProps: spec.SchemaProps{
Description: "Strategy indicates the strategy for picking the leader for coordinated leader election. If the field is not specified, there is no active coordination for this lease. (Alpha) Using this field requires the CoordinatedLeaderElection feature gate to be enabled.",
Type: []string{"string"},
Format: "",
},
},
"preferredHolder": {
SchemaProps: spec.SchemaProps{
Description: "PreferredHolder signals to a lease holder that the lease has a more optimal holder and should be given up. This field can only be set if Strategy is also set.",
Type: []string{"string"},
Format: "",
},
},
},
},
},
@ -18884,6 +18901,170 @@ func schema_k8sio_api_coordination_v1_LeaseSpec(ref common.ReferenceCallback) co
}
}
func schema_k8sio_api_coordination_v1alpha1_LeaseCandidate(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "LeaseCandidate defines a candidate for a Lease object. Candidates are created such that coordinated leader election will pick the best leader from the list of candidates.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "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: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "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: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Description: "More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata",
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
},
},
"spec": {
SchemaProps: spec.SchemaProps{
Description: "spec contains the specification of the Lease. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status",
Default: map[string]interface{}{},
Ref: ref("k8s.io/api/coordination/v1alpha1.LeaseCandidateSpec"),
},
},
},
},
},
Dependencies: []string{
"k8s.io/api/coordination/v1alpha1.LeaseCandidateSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
func schema_k8sio_api_coordination_v1alpha1_LeaseCandidateList(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "LeaseCandidateList is a list of Lease objects.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "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: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "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: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata",
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
},
},
"items": {
SchemaProps: spec.SchemaProps{
Description: "items is a list of schema objects.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/api/coordination/v1alpha1.LeaseCandidate"),
},
},
},
},
},
},
Required: []string{"items"},
},
},
Dependencies: []string{
"k8s.io/api/coordination/v1alpha1.LeaseCandidate", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
}
}
func schema_k8sio_api_coordination_v1alpha1_LeaseCandidateSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "LeaseCandidateSpec is a specification of a Lease.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"leaseName": {
SchemaProps: spec.SchemaProps{
Description: "LeaseName is the name of the lease for which this candidate is contending. This field is immutable.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"pingTime": {
SchemaProps: spec.SchemaProps{
Description: "PingTime is the last time that the server has requested the LeaseCandidate to renew. It is only done during leader election to check if any LeaseCandidates have become ineligible. When PingTime is updated, the LeaseCandidate will respond by updating RenewTime.",
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.MicroTime"),
},
},
"renewTime": {
SchemaProps: spec.SchemaProps{
Description: "RenewTime is the time that the LeaseCandidate was last updated. Any time a Lease needs to do leader election, the PingTime field is updated to signal to the LeaseCandidate that they should update the RenewTime. Old LeaseCandidate objects are also garbage collected if it has been hours since the last renew. The PingTime field is updated regularly to prevent garbage collection for still active LeaseCandidates.",
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.MicroTime"),
},
},
"binaryVersion": {
SchemaProps: spec.SchemaProps{
Description: "BinaryVersion is the binary version. It must be in a semver format without leading `v`. This field is required when strategy is \"OldestEmulationVersion\"",
Type: []string{"string"},
Format: "",
},
},
"emulationVersion": {
SchemaProps: spec.SchemaProps{
Description: "EmulationVersion is the emulation version. It must be in a semver format without leading `v`. EmulationVersion must be less than or equal to BinaryVersion. This field is required when strategy is \"OldestEmulationVersion\"",
Type: []string{"string"},
Format: "",
},
},
"preferredStrategies": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
SchemaProps: spec.SchemaProps{
Description: "PreferredStrategies indicates the list of strategies for picking the leader for coordinated leader election. The list is ordered, and the first strategy supersedes all other strategies. The list is used by coordinated leader election to make a decision about the final election strategy. This follows as - If all clients have strategy X as the first element in this list, strategy X will be used. - If a candidate has strategy [X] and another candidate has strategy [Y, X], Y supersedes X and strategy Y\n will be used.\n- If a candidate has strategy [X, Y] and another candidate has strategy [Y, X], this is a user error and leader\n election will not operate the Lease until resolved.\n(Alpha) Using this field requires the CoordinatedLeaderElection feature gate to be enabled.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
Required: []string{"leaseName", "preferredStrategies"},
},
},
Dependencies: []string{
"k8s.io/apimachinery/pkg/apis/meta/v1.MicroTime"},
}
}
func schema_k8sio_api_coordination_v1beta1_Lease(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@ -18987,7 +19168,7 @@ func schema_k8sio_api_coordination_v1beta1_LeaseSpec(ref common.ReferenceCallbac
Properties: map[string]spec.Schema{
"holderIdentity": {
SchemaProps: spec.SchemaProps{
Description: "holderIdentity contains the identity of the holder of a current lease.",
Description: "holderIdentity contains the identity of the holder of a current lease. If Coordinated Leader Election is used, the holder identity must be equal to the elected LeaseCandidate.metadata.name field.",
Type: []string{"string"},
Format: "",
},
@ -19018,6 +19199,20 @@ func schema_k8sio_api_coordination_v1beta1_LeaseSpec(ref common.ReferenceCallbac
Format: "int32",
},
},
"strategy": {
SchemaProps: spec.SchemaProps{
Description: "Strategy indicates the strategy for picking the leader for coordinated leader election (Alpha) Using this field requires the CoordinatedLeaderElection feature gate to be enabled.",
Type: []string{"string"},
Format: "",
},
},
"preferredHolder": {
SchemaProps: spec.SchemaProps{
Description: "PreferredHolder signals to a lease holder that the lease has a more optimal holder and should be given up.",
Type: []string{"string"},
Format: "",
},
},
},
},
},

View File

@ -29,6 +29,7 @@ import (
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/certificates"
"k8s.io/kubernetes/pkg/apis/coordination"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/events"
"k8s.io/kubernetes/pkg/apis/extensions"
@ -71,6 +72,7 @@ func NewStorageFactoryConfig() *StorageFactoryConfig {
//
// TODO (https://github.com/kubernetes/kubernetes/issues/108451): remove the override in 1.25.
// apisstorage.Resource("csistoragecapacities").WithVersion("v1beta1"),
coordination.Resource("leasecandidates").WithVersion("v1alpha1"),
networking.Resource("ipaddresses").WithVersion("v1beta1"),
networking.Resource("servicecidrs").WithVersion("v1beta1"),
certificates.Resource("clustertrustbundles").WithVersion("v1alpha1"),

View File

@ -34,6 +34,7 @@ import (
certificatesv1alpha1 "k8s.io/api/certificates/v1alpha1"
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
coordinationv1 "k8s.io/api/coordination/v1"
coordinationv1alpha1 "k8s.io/api/coordination/v1alpha1"
apiv1 "k8s.io/api/core/v1"
discoveryv1beta1 "k8s.io/api/discovery/v1beta1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
@ -51,6 +52,7 @@ import (
"k8s.io/apimachinery/pkg/util/duration"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/util/certificate/csr"
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
"k8s.io/kubernetes/pkg/apis/admissionregistration"
"k8s.io/kubernetes/pkg/apis/apiserverinternal"
@ -430,6 +432,16 @@ func AddHandlers(h printers.PrintHandler) {
_ = h.TableHandler(leaseColumnDefinitions, printLease)
_ = h.TableHandler(leaseColumnDefinitions, printLeaseList)
leaseCandidateColumnDefinitions := []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "LeaseName", Type: "string", Description: coordinationv1alpha1.LeaseCandidateSpec{}.SwaggerDoc()["leaseName"]},
{Name: "BinaryVersion", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["binaryVersion"]},
{Name: "EmulationVersion", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["emulationVersion"]},
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
}
_ = h.TableHandler(leaseCandidateColumnDefinitions, printLeaseCandidate)
_ = h.TableHandler(leaseCandidateColumnDefinitions, printLeaseCandidateList)
storageClassColumnDefinitions := []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Provisioner", Type: "string", Description: storagev1.StorageClass{}.SwaggerDoc()["provisioner"]},
@ -2567,6 +2579,27 @@ func printLeaseList(list *coordination.LeaseList, options printers.GenerateOptio
return rows, nil
}
func printLeaseCandidate(obj *coordination.LeaseCandidate, options printers.GenerateOptions) ([]metav1.TableRow, error) {
row := metav1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, obj.Name, obj.Spec.LeaseName, obj.Spec.BinaryVersion, obj.Spec.EmulationVersion, translateTimestampSince(obj.CreationTimestamp))
return []metav1.TableRow{row}, nil
}
func printLeaseCandidateList(list *coordination.LeaseCandidateList, options printers.GenerateOptions) ([]metav1.TableRow, error) {
rows := make([]metav1.TableRow, 0, len(list.Items))
for i := range list.Items {
r, err := printLeaseCandidate(&list.Items[i], options)
if err != nil {
return nil, err
}
rows = append(rows, r...)
}
return rows, nil
}
func printStatus(obj *metav1.Status, options printers.GenerateOptions) ([]metav1.TableRow, error) {
row := metav1.TableRow{
Object: runtime.RawExtension{Object: obj},

View File

@ -22,9 +22,11 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/storage/names"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/coordination"
"k8s.io/kubernetes/pkg/apis/coordination/validation"
"k8s.io/kubernetes/pkg/features"
)
// leaseStrategy implements verification logic for Leases.
@ -43,10 +45,26 @@ func (leaseStrategy) NamespaceScoped() bool {
// PrepareForCreate prepares Lease for creation.
func (leaseStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
lease := obj.(*coordination.Lease)
if !utilfeature.DefaultFeatureGate.Enabled(features.CoordinatedLeaderElection) {
lease.Spec.Strategy = nil
lease.Spec.PreferredHolder = nil
}
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (leaseStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
oldLease := old.(*coordination.Lease)
newLease := obj.(*coordination.Lease)
if !utilfeature.DefaultFeatureGate.Enabled(features.CoordinatedLeaderElection) {
if oldLease == nil || oldLease.Spec.Strategy == nil {
newLease.Spec.Strategy = nil
}
if oldLease == nil || oldLease.Spec.PreferredHolder == nil {
newLease.Spec.PreferredHolder = nil
}
}
}
// Validate validates a new Lease.

View File

@ -0,0 +1,17 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package leasecandidate

View File

@ -0,0 +1,56 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package storage
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
coordinationapi "k8s.io/kubernetes/pkg/apis/coordination"
"k8s.io/kubernetes/pkg/printers"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/coordination/leasecandidate"
)
// REST implements a RESTStorage for leasecandidates against etcd
type REST struct {
*genericregistry.Store
}
// NewREST returns a RESTStorage object that will work against leasecandidates.
func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) {
store := &genericregistry.Store{
NewFunc: func() runtime.Object { return &coordinationapi.LeaseCandidate{} },
NewListFunc: func() runtime.Object { return &coordinationapi.LeaseCandidateList{} },
DefaultQualifiedResource: coordinationapi.Resource("leasecandidates"),
SingularQualifiedResource: coordinationapi.Resource("leasecandidate"),
CreateStrategy: leasecandidate.Strategy,
UpdateStrategy: leasecandidate.Strategy,
DeleteStrategy: leasecandidate.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: leasecandidate.GetAttrs}
if err := store.CompleteWithOptions(options); err != nil {
return nil, err
}
return &REST{store}, nil
}

View File

@ -0,0 +1,109 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package leasecandidate
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/registry/generic"
"k8s.io/apiserver/pkg/storage/names"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/coordination"
"k8s.io/kubernetes/pkg/apis/coordination/validation"
)
// LeaseCandidateStrategy implements verification logic for leasecandidates.
type LeaseCandidateStrategy struct {
runtime.ObjectTyper
names.NameGenerator
}
// Strategy is the default logic that applies when creating and updating leasecandidate objects.
var Strategy = LeaseCandidateStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
// NamespaceScoped returns true because all leasecandidate' need to be within a namespace.
func (LeaseCandidateStrategy) NamespaceScoped() bool {
return true
}
// PrepareForCreate prepares leasecandidate for creation.
func (LeaseCandidateStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (LeaseCandidateStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
}
// Validate validates a new leasecandidate.
func (LeaseCandidateStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
leasecandidate := obj.(*coordination.LeaseCandidate)
return validation.ValidateLeaseCandidate(leasecandidate)
}
// WarningsOnCreate returns warnings for the creation of the given object.
func (LeaseCandidateStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
return nil
}
// Canonicalize normalizes the object after validation.
func (LeaseCandidateStrategy) Canonicalize(obj runtime.Object) {
}
// AllowCreateOnUpdate is true for leasecandidate; this means you may create one with a PUT request.
func (LeaseCandidateStrategy) AllowCreateOnUpdate() bool {
return true
}
// ValidateUpdate is the default update validation for an end user.
func (LeaseCandidateStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
return validation.ValidateLeaseCandidateUpdate(obj.(*coordination.LeaseCandidate), old.(*coordination.LeaseCandidate))
}
// WarningsOnUpdate returns warnings for the given update.
func (LeaseCandidateStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
return nil
}
// AllowUnconditionalUpdate is the default update policy for leasecandidate objects.
func (LeaseCandidateStrategy) AllowUnconditionalUpdate() bool {
return false
}
// GetAttrs returns labels and fields of a given object for filtering purposes.
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
leasecandidate, ok := obj.(*coordination.LeaseCandidate)
if !ok {
return nil, nil, fmt.Errorf("not a leaseCandidate")
}
return labels.Set(leasecandidate.ObjectMeta.Labels), ToSelectableFields(leasecandidate), nil
}
// ToSelectableFields returns a field set that represents the object
// TODO: fields are not labels, and the validation rules for them do not apply.
func ToSelectableFields(leasecandidate *coordination.LeaseCandidate) fields.Set {
objectMetaFieldsSet := generic.ObjectMetaFieldsSet(&leasecandidate.ObjectMeta, true)
specificFieldsSet := fields.Set{
"spec.leaseName": string(leasecandidate.Spec.LeaseName),
}
return generic.MergeFieldsSets(objectMetaFieldsSet, specificFieldsSet)
}

View File

@ -18,6 +18,7 @@ package rest
import (
coordinationv1 "k8s.io/api/coordination/v1"
coordinationv1alpha1 "k8s.io/api/coordination/v1alpha1"
"k8s.io/apiserver/pkg/registry/generic"
"k8s.io/apiserver/pkg/registry/rest"
genericapiserver "k8s.io/apiserver/pkg/server"
@ -25,6 +26,7 @@ import (
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/coordination"
leasestorage "k8s.io/kubernetes/pkg/registry/coordination/lease/storage"
leasecandidatestorage "k8s.io/kubernetes/pkg/registry/coordination/leasecandidate/storage"
)
type RESTStorageProvider struct{}
@ -39,6 +41,13 @@ func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorag
} else if len(storageMap) > 0 {
apiGroupInfo.VersionedResourcesStorageMap[coordinationv1.SchemeGroupVersion.Version] = storageMap
}
if storageMap, err := p.v1alpha1Storage(apiResourceConfigSource, restOptionsGetter); err != nil {
return genericapiserver.APIGroupInfo{}, err
} else if len(storageMap) > 0 {
apiGroupInfo.VersionedResourcesStorageMap[coordinationv1alpha1.SchemeGroupVersion.Version] = storageMap
}
return apiGroupInfo, nil
}
@ -56,6 +65,20 @@ func (p RESTStorageProvider) v1Storage(apiResourceConfigSource serverstorage.API
return storage, nil
}
func (p RESTStorageProvider) v1alpha1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (map[string]rest.Storage, error) {
storage := map[string]rest.Storage{}
// identity
if resource := "leasecandidates"; apiResourceConfigSource.ResourceEnabled(coordinationv1alpha1.SchemeGroupVersion.WithResource(resource)) {
leaseCandidateStorage, err := leasecandidatestorage.NewREST(restOptionsGetter)
if err != nil {
return storage, err
}
storage[resource] = leaseCandidateStorage
}
return storage, nil
}
func (p RESTStorageProvider) GroupName() string {
return coordination.GroupName
}

View File

@ -19,11 +19,11 @@ package bootstrappolicy
import (
"strings"
"k8s.io/klog/v2"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/klog/v2"
rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1"
)
@ -110,6 +110,8 @@ func init() {
Rules: []rbacv1.PolicyRule{
rbacv1helpers.NewRule("watch").Groups(legacyGroup).Resources("configmaps").RuleOrDie(),
rbacv1helpers.NewRule("get", "update").Groups(legacyGroup).Resources("configmaps").Names("kube-controller-manager").RuleOrDie(),
rbacv1helpers.NewRule("get", "watch", "list", "create", "update").Groups("coordination.k8s.io").Resources("leases").RuleOrDie(),
rbacv1helpers.NewRule("get", "watch", "list", "create", "update").Groups("coordination.k8s.io").Resources("leasecandidates").RuleOrDie(),
},
})
addNamespaceRole(metav1.NamespaceSystem, rbacv1.Role{
@ -118,6 +120,8 @@ func init() {
Rules: []rbacv1.PolicyRule{
rbacv1helpers.NewRule("watch").Groups(legacyGroup).Resources("configmaps").RuleOrDie(),
rbacv1helpers.NewRule("get", "update").Groups(legacyGroup).Resources("configmaps").Names("kube-scheduler").RuleOrDie(),
rbacv1helpers.NewRule("get", "watch", "list", "create", "update").Groups("coordination.k8s.io").Resources("leases").RuleOrDie(),
rbacv1helpers.NewRule("get", "watch", "list", "create", "update").Groups("coordination.k8s.io").Resources("leasecandidates").RuleOrDie(),
},
})
@ -125,8 +129,9 @@ func init() {
delegatedAuthBinding.Name = "system::extension-apiserver-authentication-reader"
addNamespaceRoleBinding(metav1.NamespaceSystem, delegatedAuthBinding)
// E1201 20:39:34.550004 1182102 controller.go:231] leases.coordination.k8s.io is forbidden: User "system:serviceaccount:kube-system:leader-election-controller" cannot create resource "leases" in API group "coordination.k8s.io" in the namespace "kube-system"
addNamespaceRoleBinding(metav1.NamespaceSystem,
rbacv1helpers.NewRoleBinding("system::leader-locking-kube-controller-manager", metav1.NamespaceSystem).Users(user.KubeControllerManager).SAs(metav1.NamespaceSystem, "kube-controller-manager").BindingOrDie())
rbacv1helpers.NewRoleBinding("system::leader-locking-kube-controller-manager", metav1.NamespaceSystem).Users(user.KubeControllerManager).SAs(metav1.NamespaceSystem, "kube-controller-manager", "leader-election-controller").BindingOrDie())
addNamespaceRoleBinding(metav1.NamespaceSystem,
rbacv1helpers.NewRoleBinding("system::leader-locking-kube-scheduler", metav1.NamespaceSystem).Users(user.KubeScheduler).SAs(metav1.NamespaceSystem, "kube-scheduler").BindingOrDie())
addNamespaceRoleBinding(metav1.NamespaceSystem,

View File

@ -25,6 +25,7 @@ import (
"k8s.io/apiserver/pkg/authentication/serviceaccount"
"k8s.io/apiserver/pkg/authentication/user"
utilfeature "k8s.io/apiserver/pkg/util/feature"
rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1"
"k8s.io/kubernetes/pkg/features"
)
@ -551,7 +552,8 @@ func ClusterRoles() []rbacv1.ClusterRole {
// This is for leaderlease access
// TODO: scope this to the kube-system namespace
rbacv1helpers.NewRule("create").Groups(coordinationGroup).Resources("leases").RuleOrDie(),
rbacv1helpers.NewRule("get", "update").Groups(coordinationGroup).Resources("leases").Names("kube-scheduler").RuleOrDie(),
rbacv1helpers.NewRule("get", "update", "list", "watch").Groups(coordinationGroup).Resources("leases").Names("kube-scheduler").RuleOrDie(),
rbacv1helpers.NewRule(ReadWrite...).Groups(coordinationGroup).Resources("leasecandidates").RuleOrDie(),
// Fundamental resources
rbacv1helpers.NewRule(Read...).Groups(legacyGroup).Resources("nodes").RuleOrDie(),

View File

@ -741,7 +741,22 @@ items:
- leases
verbs:
- get
- list
- update
- watch
- apiGroups:
- coordination.k8s.io
resources:
- leasecandidates
verbs:
- create
- delete
- deletecollection
- get
- list
- patch
- update
- watch
- apiGroups:
- ""
resources:

View File

@ -60,6 +60,9 @@ items:
- kind: ServiceAccount
name: kube-controller-manager
namespace: kube-system
- kind: ServiceAccount
name: leader-election-controller
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:

View File

@ -83,6 +83,26 @@ items:
verbs:
- get
- update
- apiGroups:
- coordination.k8s.io
resources:
- leases
verbs:
- create
- get
- list
- update
- watch
- apiGroups:
- coordination.k8s.io
resources:
- leasecandidates
verbs:
- create
- get
- list
- update
- watch
- apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
@ -109,6 +129,26 @@ items:
verbs:
- get
- update
- apiGroups:
- coordination.k8s.io
resources:
- leases
verbs:
- create
- get
- list
- update
- watch
- apiGroups:
- coordination.k8s.io
resources:
- leasecandidates
verbs:
- create
- get
- list
- update
- watch
- apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:

View File

@ -91,6 +91,7 @@
ignoredSubTrees:
- "./staging/src/k8s.io/client-go/tools/cache/testing"
- "./staging/src/k8s.io/client-go/tools/leaderelection/resourcelock"
- "./staging/src/k8s.io/client-go/tools/leaderelection"
- "./staging/src/k8s.io/client-go/tools/portforward"
- "./staging/src/k8s.io/client-go/tools/record"
- "./staging/src/k8s.io/client-go/tools/events"

View File

@ -139,40 +139,44 @@ func init() {
}
var fileDescriptor_239d5a4df3139dce = []byte{
// 524 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0x4f, 0x6f, 0xd3, 0x30,
0x18, 0xc6, 0x9b, 0xb5, 0x95, 0x56, 0x97, 0x8d, 0x2a, 0xea, 0x21, 0xea, 0x21, 0x19, 0x95, 0x90,
0x26, 0x24, 0x1c, 0x3a, 0x21, 0x84, 0x38, 0x8d, 0x08, 0x01, 0x93, 0x3a, 0x21, 0x65, 0x3b, 0xa1,
0x1d, 0x70, 0x93, 0x97, 0xd4, 0x74, 0x89, 0x83, 0xed, 0x16, 0xed, 0xc6, 0x47, 0xe0, 0xca, 0xc7,
0x80, 0x4f, 0xd1, 0xe3, 0x8e, 0x3b, 0x45, 0xd4, 0x7c, 0x11, 0x64, 0xb7, 0x5b, 0x4b, 0xff, 0x68,
0xd3, 0x6e, 0xf1, 0xeb, 0xe7, 0xf9, 0xbd, 0x8f, 0x9f, 0x43, 0xd0, 0x93, 0xc1, 0x4b, 0x81, 0x29,
0xf3, 0x49, 0x4e, 0xfd, 0x88, 0x31, 0x1e, 0xd3, 0x8c, 0x48, 0xca, 0x32, 0x7f, 0xd4, 0xf1, 0x13,
0xc8, 0x80, 0x13, 0x09, 0x31, 0xce, 0x39, 0x93, 0xcc, 0x6e, 0x4d, 0xb5, 0x98, 0xe4, 0x14, 0x2f,
0x6a, 0xf1, 0xa8, 0xd3, 0x7a, 0x9a, 0x50, 0xd9, 0x1f, 0xf6, 0x70, 0xc4, 0x52, 0x3f, 0x61, 0x09,
0xf3, 0x8d, 0xa5, 0x37, 0xfc, 0x6c, 0x4e, 0xe6, 0x60, 0xbe, 0xa6, 0xa8, 0xd6, 0xf3, 0xf9, 0xda,
0x94, 0x44, 0x7d, 0x9a, 0x01, 0xbf, 0xf0, 0xf3, 0x41, 0xa2, 0x07, 0xc2, 0x4f, 0x41, 0x92, 0x35,
0x01, 0x5a, 0xfe, 0x26, 0x17, 0x1f, 0x66, 0x92, 0xa6, 0xb0, 0x62, 0x78, 0x71, 0x9b, 0x41, 0x44,
0x7d, 0x48, 0xc9, 0xb2, 0xaf, 0xfd, 0xdb, 0x42, 0xd5, 0x2e, 0x10, 0x01, 0xf6, 0x27, 0xb4, 0xad,
0xd3, 0xc4, 0x44, 0x12, 0xc7, 0xda, 0xb3, 0xf6, 0xeb, 0x07, 0xcf, 0xf0, 0xbc, 0x86, 0x1b, 0x28,
0xce, 0x07, 0x89, 0x1e, 0x08, 0xac, 0xd5, 0x78, 0xd4, 0xc1, 0x1f, 0x7a, 0x5f, 0x20, 0x92, 0xc7,
0x20, 0x49, 0x60, 0x8f, 0x0b, 0xaf, 0xa4, 0x0a, 0x0f, 0xcd, 0x67, 0xe1, 0x0d, 0xd5, 0x7e, 0x87,
0x2a, 0x22, 0x87, 0xc8, 0xd9, 0x32, 0xf4, 0xc7, 0x78, 0x73, 0xc9, 0xd8, 0x44, 0x3a, 0xc9, 0x21,
0x0a, 0x1e, 0xcc, 0x90, 0x15, 0x7d, 0x0a, 0x0d, 0xa0, 0xfd, 0xcb, 0x42, 0x35, 0xa3, 0xe8, 0x52,
0x21, 0xed, 0xb3, 0x95, 0xe0, 0xf8, 0x6e, 0xc1, 0xb5, 0xdb, 0xc4, 0x6e, 0xcc, 0x76, 0x6c, 0x5f,
0x4f, 0x16, 0x42, 0xbf, 0x45, 0x55, 0x2a, 0x21, 0x15, 0xce, 0xd6, 0x5e, 0x79, 0xbf, 0x7e, 0xf0,
0xe8, 0xd6, 0xd4, 0xc1, 0xce, 0x8c, 0x56, 0x3d, 0xd2, 0xbe, 0x70, 0x6a, 0x6f, 0xff, 0x2c, 0xcf,
0x32, 0xeb, 0x77, 0xd8, 0xaf, 0xd0, 0x6e, 0x9f, 0x9d, 0xc7, 0xc0, 0x8f, 0x62, 0xc8, 0x24, 0x95,
0x17, 0x26, 0x79, 0x2d, 0xb0, 0x55, 0xe1, 0xed, 0xbe, 0xff, 0xef, 0x26, 0x5c, 0x52, 0xda, 0x5d,
0xd4, 0x3c, 0xd7, 0xa0, 0x37, 0x43, 0x6e, 0x36, 0x9f, 0x40, 0xc4, 0xb2, 0x58, 0x98, 0x5a, 0xab,
0x81, 0xa3, 0x0a, 0xaf, 0xd9, 0x5d, 0x73, 0x1f, 0xae, 0x75, 0xd9, 0x3d, 0x54, 0x27, 0xd1, 0xd7,
0x21, 0xe5, 0x70, 0x4a, 0x53, 0x70, 0xca, 0xa6, 0x40, 0xff, 0x6e, 0x05, 0x1e, 0xd3, 0x88, 0x33,
0x6d, 0x0b, 0x1e, 0xaa, 0xc2, 0xab, 0xbf, 0x9e, 0x73, 0xc2, 0x45, 0xa8, 0x7d, 0x86, 0x6a, 0x1c,
0x32, 0xf8, 0x66, 0x36, 0x54, 0xee, 0xb7, 0x61, 0x47, 0x15, 0x5e, 0x2d, 0xbc, 0xa6, 0x84, 0x73,
0xa0, 0x7d, 0x88, 0x1a, 0xe6, 0x65, 0xa7, 0x9c, 0x64, 0x82, 0xea, 0xb7, 0x09, 0xa7, 0x6a, 0xba,
0x68, 0xaa, 0xc2, 0x6b, 0x74, 0x97, 0xee, 0xc2, 0x15, 0x75, 0x70, 0x38, 0x9e, 0xb8, 0xa5, 0xcb,
0x89, 0x5b, 0xba, 0x9a, 0xb8, 0xa5, 0xef, 0xca, 0xb5, 0xc6, 0xca, 0xb5, 0x2e, 0x95, 0x6b, 0x5d,
0x29, 0xd7, 0xfa, 0xa3, 0x5c, 0xeb, 0xc7, 0x5f, 0xb7, 0xf4, 0xb1, 0xb5, 0xf9, 0x07, 0xf2, 0x2f,
0x00, 0x00, 0xff, 0xff, 0xb0, 0xb0, 0x3a, 0x46, 0x5d, 0x04, 0x00, 0x00,
// 588 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0xdf, 0x4e, 0xd4, 0x40,
0x14, 0xc6, 0xb7, 0xb0, 0xab, 0xec, 0xac, 0xfc, 0xc9, 0xc8, 0x45, 0xb3, 0x17, 0x2d, 0x92, 0x98,
0x10, 0x13, 0xa7, 0x42, 0x8c, 0x31, 0x26, 0x26, 0x58, 0x89, 0x4a, 0xb2, 0x44, 0x53, 0xb8, 0x32,
0x5c, 0x38, 0xdb, 0x1e, 0xba, 0x23, 0xb4, 0x53, 0x67, 0x66, 0x31, 0xdc, 0xf9, 0x08, 0x3e, 0x81,
0xef, 0xa0, 0x4f, 0xc1, 0x25, 0x97, 0x5c, 0x35, 0x32, 0xbe, 0x85, 0x57, 0x66, 0x66, 0x0b, 0x0b,
0xcb, 0x6e, 0x20, 0xde, 0x75, 0xce, 0x39, 0xdf, 0xef, 0x7c, 0x73, 0x4e, 0x5b, 0xf4, 0x68, 0xff,
0xb9, 0x24, 0x8c, 0x07, 0xb4, 0x60, 0x41, 0xcc, 0xb9, 0x48, 0x58, 0x4e, 0x15, 0xe3, 0x79, 0x70,
0xb8, 0x1a, 0xa4, 0x90, 0x83, 0xa0, 0x0a, 0x12, 0x52, 0x08, 0xae, 0x38, 0x6e, 0x0f, 0x6a, 0x09,
0x2d, 0x18, 0xb9, 0x5c, 0x4b, 0x0e, 0x57, 0xdb, 0x8f, 0x53, 0xa6, 0x7a, 0xfd, 0x2e, 0x89, 0x79,
0x16, 0xa4, 0x3c, 0xe5, 0x81, 0x95, 0x74, 0xfb, 0x7b, 0xf6, 0x64, 0x0f, 0xf6, 0x69, 0x80, 0x6a,
0x3f, 0x1d, 0xb6, 0xcd, 0x68, 0xdc, 0x63, 0x39, 0x88, 0xa3, 0xa0, 0xd8, 0x4f, 0x4d, 0x40, 0x06,
0x19, 0x28, 0x3a, 0xc6, 0x40, 0x3b, 0x98, 0xa4, 0x12, 0xfd, 0x5c, 0xb1, 0x0c, 0xae, 0x09, 0x9e,
0xdd, 0x24, 0x90, 0x71, 0x0f, 0x32, 0x3a, 0xaa, 0x5b, 0xfe, 0xe5, 0xa0, 0x46, 0x07, 0xa8, 0x04,
0xfc, 0x09, 0xcd, 0x18, 0x37, 0x09, 0x55, 0xd4, 0x75, 0x96, 0x9c, 0x95, 0xd6, 0xda, 0x13, 0x32,
0x1c, 0xc3, 0x05, 0x94, 0x14, 0xfb, 0xa9, 0x09, 0x48, 0x62, 0xaa, 0xc9, 0xe1, 0x2a, 0x79, 0xdf,
0xfd, 0x0c, 0xb1, 0xda, 0x02, 0x45, 0x43, 0x7c, 0x5c, 0xfa, 0x35, 0x5d, 0xfa, 0x68, 0x18, 0x8b,
0x2e, 0xa8, 0xf8, 0x2d, 0xaa, 0xcb, 0x02, 0x62, 0x77, 0xca, 0xd2, 0x1f, 0x92, 0xc9, 0x43, 0x26,
0xd6, 0xd2, 0x76, 0x01, 0x71, 0x78, 0xaf, 0x42, 0xd6, 0xcd, 0x29, 0xb2, 0x80, 0xe5, 0x9f, 0x0e,
0x6a, 0xda, 0x8a, 0x0e, 0x93, 0x0a, 0xef, 0x5e, 0x33, 0x4e, 0x6e, 0x67, 0xdc, 0xa8, 0xad, 0xed,
0x85, 0xaa, 0xc7, 0xcc, 0x79, 0xe4, 0x92, 0xe9, 0x37, 0xa8, 0xc1, 0x14, 0x64, 0xd2, 0x9d, 0x5a,
0x9a, 0x5e, 0x69, 0xad, 0x3d, 0xb8, 0xd1, 0x75, 0x38, 0x5b, 0xd1, 0x1a, 0x9b, 0x46, 0x17, 0x0d,
0xe4, 0xcb, 0x3f, 0xea, 0x95, 0x67, 0x73, 0x0f, 0xfc, 0x02, 0xcd, 0xf5, 0xf8, 0x41, 0x02, 0x62,
0x33, 0x81, 0x5c, 0x31, 0x75, 0x64, 0x9d, 0x37, 0x43, 0xac, 0x4b, 0x7f, 0xee, 0xdd, 0x95, 0x4c,
0x34, 0x52, 0x89, 0x3b, 0x68, 0xf1, 0xc0, 0x80, 0x36, 0xfa, 0xc2, 0x76, 0xde, 0x86, 0x98, 0xe7,
0x89, 0xb4, 0x63, 0x6d, 0x84, 0xae, 0x2e, 0xfd, 0xc5, 0xce, 0x98, 0x7c, 0x34, 0x56, 0x85, 0xbb,
0xa8, 0x45, 0xe3, 0x2f, 0x7d, 0x26, 0x60, 0x87, 0x65, 0xe0, 0x4e, 0xdb, 0x01, 0x06, 0xb7, 0x1b,
0xe0, 0x16, 0x8b, 0x05, 0x37, 0xb2, 0x70, 0x5e, 0x97, 0x7e, 0xeb, 0xd5, 0x90, 0x13, 0x5d, 0x86,
0xe2, 0x5d, 0xd4, 0x14, 0x90, 0xc3, 0x57, 0xdb, 0xa1, 0xfe, 0x7f, 0x1d, 0x66, 0x75, 0xe9, 0x37,
0xa3, 0x73, 0x4a, 0x34, 0x04, 0xe2, 0x75, 0xb4, 0x60, 0x6f, 0xb6, 0x23, 0x68, 0x2e, 0x99, 0xb9,
0x9b, 0x74, 0x1b, 0x76, 0x16, 0x8b, 0xba, 0xf4, 0x17, 0x3a, 0x23, 0xb9, 0xe8, 0x5a, 0x35, 0xde,
0x40, 0x33, 0x52, 0x99, 0xaf, 0x22, 0x3d, 0x72, 0xef, 0xd8, 0x3d, 0xac, 0x98, 0xb7, 0x61, 0xbb,
0x8a, 0xfd, 0x2d, 0x7d, 0xf7, 0xf5, 0xf9, 0xaa, 0x21, 0x19, 0x6c, 0xb1, 0xca, 0x45, 0x17, 0x4a,
0xfc, 0x12, 0xcd, 0x17, 0x02, 0xf6, 0x40, 0x08, 0x48, 0x06, 0x2b, 0x74, 0xef, 0x5a, 0xd8, 0x7d,
0x5d, 0xfa, 0xf3, 0x1f, 0xae, 0xa6, 0xa2, 0xd1, 0xda, 0x70, 0xfd, 0xf8, 0xcc, 0xab, 0x9d, 0x9c,
0x79, 0xb5, 0xd3, 0x33, 0xaf, 0xf6, 0x4d, 0x7b, 0xce, 0xb1, 0xf6, 0x9c, 0x13, 0xed, 0x39, 0xa7,
0xda, 0x73, 0x7e, 0x6b, 0xcf, 0xf9, 0xfe, 0xc7, 0xab, 0x7d, 0x6c, 0x4f, 0xfe, 0x8b, 0xfd, 0x0b,
0x00, 0x00, 0xff, 0xff, 0xf8, 0xf4, 0xd4, 0x78, 0xe2, 0x04, 0x00, 0x00,
}
func (m *Lease) Marshal() (dAtA []byte, err error) {
@ -285,6 +289,20 @@ func (m *LeaseSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if m.PreferredHolder != nil {
i -= len(*m.PreferredHolder)
copy(dAtA[i:], *m.PreferredHolder)
i = encodeVarintGenerated(dAtA, i, uint64(len(*m.PreferredHolder)))
i--
dAtA[i] = 0x3a
}
if m.Strategy != nil {
i -= len(*m.Strategy)
copy(dAtA[i:], *m.Strategy)
i = encodeVarintGenerated(dAtA, i, uint64(len(*m.Strategy)))
i--
dAtA[i] = 0x32
}
if m.LeaseTransitions != nil {
i = encodeVarintGenerated(dAtA, i, uint64(*m.LeaseTransitions))
i--
@ -394,6 +412,14 @@ func (m *LeaseSpec) Size() (n int) {
if m.LeaseTransitions != nil {
n += 1 + sovGenerated(uint64(*m.LeaseTransitions))
}
if m.Strategy != nil {
l = len(*m.Strategy)
n += 1 + l + sovGenerated(uint64(l))
}
if m.PreferredHolder != nil {
l = len(*m.PreferredHolder)
n += 1 + l + sovGenerated(uint64(l))
}
return n
}
@ -440,6 +466,8 @@ func (this *LeaseSpec) String() string {
`AcquireTime:` + strings.Replace(fmt.Sprintf("%v", this.AcquireTime), "MicroTime", "v1.MicroTime", 1) + `,`,
`RenewTime:` + strings.Replace(fmt.Sprintf("%v", this.RenewTime), "MicroTime", "v1.MicroTime", 1) + `,`,
`LeaseTransitions:` + valueToStringGenerated(this.LeaseTransitions) + `,`,
`Strategy:` + valueToStringGenerated(this.Strategy) + `,`,
`PreferredHolder:` + valueToStringGenerated(this.PreferredHolder) + `,`,
`}`,
}, "")
return s
@ -859,6 +887,72 @@ func (m *LeaseSpec) Unmarshal(dAtA []byte) error {
}
}
m.LeaseTransitions = &v
case 6:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Strategy", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
s := CoordinatedLeaseStrategy(dAtA[iNdEx:postIndex])
m.Strategy = &s
iNdEx = postIndex
case 7:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field PreferredHolder", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
s := string(dAtA[iNdEx:postIndex])
m.PreferredHolder = &s
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])

View File

@ -54,11 +54,13 @@ message LeaseList {
// LeaseSpec is a specification of a Lease.
message LeaseSpec {
// holderIdentity contains the identity of the holder of a current lease.
// If Coordinated Leader Election is used, the holder identity must be
// equal to the elected LeaseCandidate.metadata.name field.
// +optional
optional string holderIdentity = 1;
// leaseDurationSeconds is a duration that candidates for a lease need
// to wait to force acquire it. This is measure against time of last
// to wait to force acquire it. This is measured against the time of last
// observed renewTime.
// +optional
optional int32 leaseDurationSeconds = 2;
@ -76,5 +78,19 @@ message LeaseSpec {
// holders.
// +optional
optional int32 leaseTransitions = 5;
// Strategy indicates the strategy for picking the leader for coordinated leader election.
// If the field is not specified, there is no active coordination for this lease.
// (Alpha) Using this field requires the CoordinatedLeaderElection feature gate to be enabled.
// +featureGate=CoordinatedLeaderElection
// +optional
optional string strategy = 6;
// PreferredHolder signals to a lease holder that the lease has a
// more optimal holder and should be given up.
// This field can only be set if Strategy is also set.
// +featureGate=CoordinatedLeaderElection
// +optional
optional string preferredHolder = 7;
}

View File

@ -20,6 +20,18 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type CoordinatedLeaseStrategy string
// CoordinatedLeaseStrategy defines the strategy for picking the leader for coordinated leader election.
const (
// OldestEmulationVersion picks the oldest LeaseCandidate, where "oldest" is defined as follows
// 1) Select the candidate(s) with the lowest emulation version
// 2) If multiple candidates have the same emulation version, select the candidate(s) with the lowest binary version. (Note that binary version must be greater or equal to emulation version)
// 3) If multiple candidates have the same binary version, select the candidate with the oldest creationTimestamp.
// If a candidate does not specify the emulationVersion and binaryVersion fields, it will not be considered a candidate for the lease.
OldestEmulationVersion CoordinatedLeaseStrategy = "OldestEmulationVersion"
)
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:prerelease-lifecycle-gen:introduced=1.14
@ -40,10 +52,12 @@ type Lease struct {
// LeaseSpec is a specification of a Lease.
type LeaseSpec struct {
// holderIdentity contains the identity of the holder of a current lease.
// If Coordinated Leader Election is used, the holder identity must be
// equal to the elected LeaseCandidate.metadata.name field.
// +optional
HolderIdentity *string `json:"holderIdentity,omitempty" protobuf:"bytes,1,opt,name=holderIdentity"`
// leaseDurationSeconds is a duration that candidates for a lease need
// to wait to force acquire it. This is measure against time of last
// to wait to force acquire it. This is measured against the time of last
// observed renewTime.
// +optional
LeaseDurationSeconds *int32 `json:"leaseDurationSeconds,omitempty" protobuf:"varint,2,opt,name=leaseDurationSeconds"`
@ -58,6 +72,18 @@ type LeaseSpec struct {
// holders.
// +optional
LeaseTransitions *int32 `json:"leaseTransitions,omitempty" protobuf:"varint,5,opt,name=leaseTransitions"`
// Strategy indicates the strategy for picking the leader for coordinated leader election.
// If the field is not specified, there is no active coordination for this lease.
// (Alpha) Using this field requires the CoordinatedLeaderElection feature gate to be enabled.
// +featureGate=CoordinatedLeaderElection
// +optional
Strategy *CoordinatedLeaseStrategy `json:"strategy,omitempty" protobuf:"bytes,6,opt,name=strategy"`
// PreferredHolder signals to a lease holder that the lease has a
// more optimal holder and should be given up.
// This field can only be set if Strategy is also set.
// +featureGate=CoordinatedLeaderElection
// +optional
PreferredHolder *string `json:"preferredHolder,omitempty" protobuf:"bytes,7,opt,name=preferredHolder"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View File

@ -49,11 +49,13 @@ func (LeaseList) SwaggerDoc() map[string]string {
var map_LeaseSpec = map[string]string{
"": "LeaseSpec is a specification of a Lease.",
"holderIdentity": "holderIdentity contains the identity of the holder of a current lease.",
"leaseDurationSeconds": "leaseDurationSeconds is a duration that candidates for a lease need to wait to force acquire it. This is measure against time of last observed renewTime.",
"holderIdentity": "holderIdentity contains the identity of the holder of a current lease. If Coordinated Leader Election is used, the holder identity must be equal to the elected LeaseCandidate.metadata.name field.",
"leaseDurationSeconds": "leaseDurationSeconds is a duration that candidates for a lease need to wait to force acquire it. This is measured against the time of last observed renewTime.",
"acquireTime": "acquireTime is a time when the current lease was acquired.",
"renewTime": "renewTime is a time when the current holder of a lease has last updated the lease.",
"leaseTransitions": "leaseTransitions is the number of transitions of a lease between holders.",
"strategy": "Strategy indicates the strategy for picking the leader for coordinated leader election. If the field is not specified, there is no active coordination for this lease. (Alpha) Using this field requires the CoordinatedLeaderElection feature gate to be enabled.",
"preferredHolder": "PreferredHolder signals to a lease holder that the lease has a more optimal holder and should be given up. This field can only be set if Strategy is also set.",
}
func (LeaseSpec) SwaggerDoc() map[string]string {

View File

@ -111,6 +111,16 @@ func (in *LeaseSpec) DeepCopyInto(out *LeaseSpec) {
*out = new(int32)
**out = **in
}
if in.Strategy != nil {
in, out := &in.Strategy, &out.Strategy
*out = new(CoordinatedLeaseStrategy)
**out = **in
}
if in.PreferredHolder != nil {
in, out := &in.PreferredHolder, &out.PreferredHolder
*out = new(string)
**out = **in
}
return
}

View File

@ -0,0 +1,24 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:deepcopy-gen=package
// +k8s:protobuf-gen=package
// +k8s:openapi-gen=true
// +k8s:prerelease-lifecycle-gen=true
// +groupName=coordination.k8s.io
package v1alpha1 // import "k8s.io/api/coordination/v1alpha1"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,105 @@
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// This file was autogenerated by go-to-protobuf. Do not edit it manually!
syntax = "proto2";
package k8s.io.api.coordination.v1alpha1;
import "k8s.io/api/coordination/v1/generated.proto";
import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto";
import "k8s.io/apimachinery/pkg/runtime/generated.proto";
import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto";
// Package-wide variables from generator "generated".
option go_package = "k8s.io/api/coordination/v1alpha1";
// LeaseCandidate defines a candidate for a Lease object.
// Candidates are created such that coordinated leader election will pick the best leader from the list of candidates.
message LeaseCandidate {
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
// +optional
optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1;
// spec contains the specification of the Lease.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
// +optional
optional LeaseCandidateSpec spec = 2;
}
// LeaseCandidateList is a list of Lease objects.
message LeaseCandidateList {
// Standard list metadata.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
// +optional
optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1;
// items is a list of schema objects.
repeated LeaseCandidate items = 2;
}
// LeaseCandidateSpec is a specification of a Lease.
message LeaseCandidateSpec {
// LeaseName is the name of the lease for which this candidate is contending.
// This field is immutable.
// +required
optional string leaseName = 1;
// PingTime is the last time that the server has requested the LeaseCandidate
// to renew. It is only done during leader election to check if any
// LeaseCandidates have become ineligible. When PingTime is updated, the
// LeaseCandidate will respond by updating RenewTime.
// +optional
optional .k8s.io.apimachinery.pkg.apis.meta.v1.MicroTime pingTime = 2;
// RenewTime is the time that the LeaseCandidate was last updated.
// Any time a Lease needs to do leader election, the PingTime field
// is updated to signal to the LeaseCandidate that they should update
// the RenewTime.
// Old LeaseCandidate objects are also garbage collected if it has been hours
// since the last renew. The PingTime field is updated regularly to prevent
// garbage collection for still active LeaseCandidates.
// +optional
optional .k8s.io.apimachinery.pkg.apis.meta.v1.MicroTime renewTime = 3;
// BinaryVersion is the binary version. It must be in a semver format without leading `v`.
// This field is required when strategy is "OldestEmulationVersion"
// +optional
optional string binaryVersion = 4;
// EmulationVersion is the emulation version. It must be in a semver format without leading `v`.
// EmulationVersion must be less than or equal to BinaryVersion.
// This field is required when strategy is "OldestEmulationVersion"
// +optional
optional string emulationVersion = 5;
// PreferredStrategies indicates the list of strategies for picking the leader for coordinated leader election.
// The list is ordered, and the first strategy supersedes all other strategies. The list is used by coordinated
// leader election to make a decision about the final election strategy. This follows as
// - If all clients have strategy X as the first element in this list, strategy X will be used.
// - If a candidate has strategy [X] and another candidate has strategy [Y, X], Y supersedes X and strategy Y
// will be used.
// - If a candidate has strategy [X, Y] and another candidate has strategy [Y, X], this is a user error and leader
// election will not operate the Lease until resolved.
// (Alpha) Using this field requires the CoordinatedLeaderElection feature gate to be enabled.
// +featureGate=CoordinatedLeaderElection
// +listType=atomic
// +required
repeated string preferredStrategies = 6;
}

View File

@ -0,0 +1,53 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// GroupName is the group name use in this package
const GroupName = "coordination.k8s.io"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
var (
// TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api.
// localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes.
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
localSchemeBuilder = &SchemeBuilder
AddToScheme = localSchemeBuilder.AddToScheme
)
// Adds the list of known types to api.Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&LeaseCandidate{},
&LeaseCandidateList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}

View File

@ -0,0 +1,100 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
v1 "k8s.io/api/coordination/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:prerelease-lifecycle-gen:introduced=1.31
// LeaseCandidate defines a candidate for a Lease object.
// Candidates are created such that coordinated leader election will pick the best leader from the list of candidates.
type LeaseCandidate struct {
metav1.TypeMeta `json:",inline"`
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
// +optional
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// spec contains the specification of the Lease.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
// +optional
Spec LeaseCandidateSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
}
// LeaseCandidateSpec is a specification of a Lease.
type LeaseCandidateSpec struct {
// LeaseName is the name of the lease for which this candidate is contending.
// This field is immutable.
// +required
LeaseName string `json:"leaseName" protobuf:"bytes,1,name=leaseName"`
// PingTime is the last time that the server has requested the LeaseCandidate
// to renew. It is only done during leader election to check if any
// LeaseCandidates have become ineligible. When PingTime is updated, the
// LeaseCandidate will respond by updating RenewTime.
// +optional
PingTime *metav1.MicroTime `json:"pingTime,omitempty" protobuf:"bytes,2,opt,name=pingTime"`
// RenewTime is the time that the LeaseCandidate was last updated.
// Any time a Lease needs to do leader election, the PingTime field
// is updated to signal to the LeaseCandidate that they should update
// the RenewTime.
// Old LeaseCandidate objects are also garbage collected if it has been hours
// since the last renew. The PingTime field is updated regularly to prevent
// garbage collection for still active LeaseCandidates.
// +optional
RenewTime *metav1.MicroTime `json:"renewTime,omitempty" protobuf:"bytes,3,opt,name=renewTime"`
// BinaryVersion is the binary version. It must be in a semver format without leading `v`.
// This field is required when strategy is "OldestEmulationVersion"
// +optional
BinaryVersion string `json:"binaryVersion,omitempty" protobuf:"bytes,4,opt,name=binaryVersion"`
// EmulationVersion is the emulation version. It must be in a semver format without leading `v`.
// EmulationVersion must be less than or equal to BinaryVersion.
// This field is required when strategy is "OldestEmulationVersion"
// +optional
EmulationVersion string `json:"emulationVersion,omitempty" protobuf:"bytes,5,opt,name=emulationVersion"`
// PreferredStrategies indicates the list of strategies for picking the leader for coordinated leader election.
// The list is ordered, and the first strategy supersedes all other strategies. The list is used by coordinated
// leader election to make a decision about the final election strategy. This follows as
// - If all clients have strategy X as the first element in this list, strategy X will be used.
// - If a candidate has strategy [X] and another candidate has strategy [Y, X], Y supersedes X and strategy Y
// will be used.
// - If a candidate has strategy [X, Y] and another candidate has strategy [Y, X], this is a user error and leader
// election will not operate the Lease until resolved.
// (Alpha) Using this field requires the CoordinatedLeaderElection feature gate to be enabled.
// +featureGate=CoordinatedLeaderElection
// +listType=atomic
// +required
PreferredStrategies []v1.CoordinatedLeaseStrategy `json:"preferredStrategies,omitempty" protobuf:"bytes,6,opt,name=preferredStrategies"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:prerelease-lifecycle-gen:introduced=1.31
// LeaseCandidateList is a list of Lease objects.
type LeaseCandidateList struct {
metav1.TypeMeta `json:",inline"`
// Standard list metadata.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
// +optional
metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// items is a list of schema objects.
Items []LeaseCandidate `json:"items" protobuf:"bytes,2,rep,name=items"`
}

View File

@ -0,0 +1,64 @@
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
// This file contains a collection of methods that can be used from go-restful to
// generate Swagger API documentation for its models. Please read this PR for more
// information on the implementation: https://github.com/emicklei/go-restful/pull/215
//
// TODOs are ignored from the parser (e.g. TODO(andronat):... || TODO:...) if and only if
// they are on one line! For multiple line or blocks that you want to ignore use ---.
// Any context after a --- is ignored.
//
// Those methods can be generated by using hack/update-codegen.sh
// AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT.
var map_LeaseCandidate = map[string]string{
"": "LeaseCandidate defines a candidate for a Lease object. Candidates are created such that coordinated leader election will pick the best leader from the list of candidates.",
"metadata": "More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata",
"spec": "spec contains the specification of the Lease. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status",
}
func (LeaseCandidate) SwaggerDoc() map[string]string {
return map_LeaseCandidate
}
var map_LeaseCandidateList = map[string]string{
"": "LeaseCandidateList is a list of Lease objects.",
"metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata",
"items": "items is a list of schema objects.",
}
func (LeaseCandidateList) SwaggerDoc() map[string]string {
return map_LeaseCandidateList
}
var map_LeaseCandidateSpec = map[string]string{
"": "LeaseCandidateSpec is a specification of a Lease.",
"leaseName": "LeaseName is the name of the lease for which this candidate is contending. This field is immutable.",
"pingTime": "PingTime is the last time that the server has requested the LeaseCandidate to renew. It is only done during leader election to check if any LeaseCandidates have become ineligible. When PingTime is updated, the LeaseCandidate will respond by updating RenewTime.",
"renewTime": "RenewTime is the time that the LeaseCandidate was last updated. Any time a Lease needs to do leader election, the PingTime field is updated to signal to the LeaseCandidate that they should update the RenewTime. Old LeaseCandidate objects are also garbage collected if it has been hours since the last renew. The PingTime field is updated regularly to prevent garbage collection for still active LeaseCandidates.",
"binaryVersion": "BinaryVersion is the binary version. It must be in a semver format without leading `v`. This field is required when strategy is \"OldestEmulationVersion\"",
"emulationVersion": "EmulationVersion is the emulation version. It must be in a semver format without leading `v`. EmulationVersion must be less than or equal to BinaryVersion. This field is required when strategy is \"OldestEmulationVersion\"",
"preferredStrategies": "PreferredStrategies indicates the list of strategies for picking the leader for coordinated leader election. The list is ordered, and the first strategy supersedes all other strategies. The list is used by coordinated leader election to make a decision about the final election strategy. This follows as - If all clients have strategy X as the first element in this list, strategy X will be used. - If a candidate has strategy [X] and another candidate has strategy [Y, X], Y supersedes X and strategy Y\n will be used.\n- If a candidate has strategy [X, Y] and another candidate has strategy [Y, X], this is a user error and leader\n election will not operate the Lease until resolved.\n(Alpha) Using this field requires the CoordinatedLeaderElection feature gate to be enabled.",
}
func (LeaseCandidateSpec) SwaggerDoc() map[string]string {
return map_LeaseCandidateSpec
}
// AUTO-GENERATED FUNCTIONS END HERE

View File

@ -0,0 +1,116 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1alpha1
import (
v1 "k8s.io/api/coordination/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LeaseCandidate) DeepCopyInto(out *LeaseCandidate) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaseCandidate.
func (in *LeaseCandidate) DeepCopy() *LeaseCandidate {
if in == nil {
return nil
}
out := new(LeaseCandidate)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *LeaseCandidate) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LeaseCandidateList) DeepCopyInto(out *LeaseCandidateList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]LeaseCandidate, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaseCandidateList.
func (in *LeaseCandidateList) DeepCopy() *LeaseCandidateList {
if in == nil {
return nil
}
out := new(LeaseCandidateList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *LeaseCandidateList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LeaseCandidateSpec) DeepCopyInto(out *LeaseCandidateSpec) {
*out = *in
if in.PingTime != nil {
in, out := &in.PingTime, &out.PingTime
*out = (*in).DeepCopy()
}
if in.RenewTime != nil {
in, out := &in.RenewTime, &out.RenewTime
*out = (*in).DeepCopy()
}
if in.PreferredStrategies != nil {
in, out := &in.PreferredStrategies, &out.PreferredStrategies
*out = make([]v1.CoordinatedLeaseStrategy, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaseCandidateSpec.
func (in *LeaseCandidateSpec) DeepCopy() *LeaseCandidateSpec {
if in == nil {
return nil
}
out := new(LeaseCandidateSpec)
in.DeepCopyInto(out)
return out
}

View File

@ -0,0 +1,58 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by prerelease-lifecycle-gen. DO NOT EDIT.
package v1alpha1
// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison.
// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go.
func (in *LeaseCandidate) APILifecycleIntroduced() (major, minor int) {
return 1, 31
}
// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison.
// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor.
func (in *LeaseCandidate) APILifecycleDeprecated() (major, minor int) {
return 1, 34
}
// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison.
// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor.
func (in *LeaseCandidate) APILifecycleRemoved() (major, minor int) {
return 1, 37
}
// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison.
// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go.
func (in *LeaseCandidateList) APILifecycleIntroduced() (major, minor int) {
return 1, 31
}
// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison.
// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor.
func (in *LeaseCandidateList) APILifecycleDeprecated() (major, minor int) {
return 1, 34
}
// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison.
// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor.
func (in *LeaseCandidateList) APILifecycleRemoved() (major, minor int) {
return 1, 37
}

View File

@ -25,6 +25,8 @@ import (
io "io"
proto "github.com/gogo/protobuf/proto"
k8s_io_api_coordination_v1 "k8s.io/api/coordination/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
math "math"
@ -139,40 +141,45 @@ func init() {
}
var fileDescriptor_8d4e223b8bb23da3 = []byte{
// 527 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0x41, 0x6f, 0xd3, 0x30,
0x14, 0xc7, 0x9b, 0xb5, 0x95, 0x56, 0x97, 0x8d, 0x2a, 0xea, 0x21, 0xea, 0x21, 0x99, 0x7a, 0x40,
0x13, 0x12, 0x36, 0x9d, 0x10, 0x42, 0x9c, 0x20, 0x02, 0x89, 0x89, 0x4c, 0x48, 0xd9, 0x4e, 0x68,
0x07, 0xdc, 0xe4, 0x91, 0x9a, 0x2e, 0x71, 0x88, 0xdd, 0xa2, 0xdd, 0xf8, 0x08, 0x5c, 0xf9, 0x22,
0xf0, 0x15, 0x7a, 0xdc, 0x71, 0xa7, 0x88, 0x9a, 0x2f, 0x82, 0xec, 0x76, 0x6b, 0x69, 0x87, 0x5a,
0x71, 0x8b, 0x9f, 0xdf, 0xef, 0xf7, 0xfe, 0x7e, 0x87, 0x20, 0x32, 0x7c, 0x26, 0x30, 0xe3, 0x84,
0xe6, 0x8c, 0x44, 0x9c, 0x17, 0x31, 0xcb, 0xa8, 0x64, 0x3c, 0x23, 0xe3, 0x5e, 0x1f, 0x24, 0xed,
0x91, 0x04, 0x32, 0x28, 0xa8, 0x84, 0x18, 0xe7, 0x05, 0x97, 0xdc, 0xf6, 0x66, 0x00, 0xa6, 0x39,
0xc3, 0xcb, 0x00, 0x9e, 0x03, 0x9d, 0x47, 0x09, 0x93, 0x83, 0x51, 0x1f, 0x47, 0x3c, 0x25, 0x09,
0x4f, 0x38, 0x31, 0x5c, 0x7f, 0xf4, 0xd1, 0x9c, 0xcc, 0xc1, 0x7c, 0xcd, 0x7c, 0x9d, 0x27, 0x8b,
0x00, 0x29, 0x8d, 0x06, 0x2c, 0x83, 0xe2, 0x92, 0xe4, 0xc3, 0x44, 0x17, 0x04, 0x49, 0x41, 0x52,
0x32, 0x5e, 0x4b, 0xd1, 0x21, 0xff, 0xa2, 0x8a, 0x51, 0x26, 0x59, 0x0a, 0x6b, 0xc0, 0xd3, 0x4d,
0x80, 0x88, 0x06, 0x90, 0xd2, 0x55, 0xae, 0xfb, 0xd3, 0x42, 0xf5, 0x00, 0xa8, 0x00, 0xfb, 0x03,
0xda, 0xd5, 0x69, 0x62, 0x2a, 0xa9, 0x63, 0x1d, 0x58, 0x87, 0xcd, 0xa3, 0xc7, 0x78, 0xb1, 0x8b,
0x5b, 0x29, 0xce, 0x87, 0x89, 0x2e, 0x08, 0xac, 0xbb, 0xf1, 0xb8, 0x87, 0xdf, 0xf5, 0x3f, 0x41,
0x24, 0x4f, 0x40, 0x52, 0xdf, 0x9e, 0x94, 0x5e, 0x45, 0x95, 0x1e, 0x5a, 0xd4, 0xc2, 0x5b, 0xab,
0x1d, 0xa0, 0x9a, 0xc8, 0x21, 0x72, 0x76, 0x8c, 0xfd, 0x21, 0xde, 0xb0, 0x69, 0x6c, 0x72, 0x9d,
0xe6, 0x10, 0xf9, 0xf7, 0xe6, 0xde, 0x9a, 0x3e, 0x85, 0xc6, 0xd2, 0xfd, 0x61, 0xa1, 0x86, 0xe9,
0x08, 0x98, 0x90, 0xf6, 0xf9, 0x5a, 0x7a, 0xbc, 0x5d, 0x7a, 0x4d, 0x9b, 0xec, 0xad, 0xf9, 0x8c,
0xdd, 0x9b, 0xca, 0x52, 0xf2, 0xb7, 0xa8, 0xce, 0x24, 0xa4, 0xc2, 0xd9, 0x39, 0xa8, 0x1e, 0x36,
0x8f, 0x1e, 0x6c, 0x17, 0xdd, 0xdf, 0x9b, 0x2b, 0xeb, 0xc7, 0x1a, 0x0e, 0x67, 0x8e, 0xee, 0xf7,
0xea, 0x3c, 0xb8, 0x7e, 0x8c, 0xfd, 0x1c, 0xed, 0x0f, 0xf8, 0x45, 0x0c, 0xc5, 0x71, 0x0c, 0x99,
0x64, 0xf2, 0xd2, 0xc4, 0x6f, 0xf8, 0xb6, 0x2a, 0xbd, 0xfd, 0x37, 0x7f, 0xdd, 0x84, 0x2b, 0x9d,
0x76, 0x80, 0xda, 0x17, 0x5a, 0xf4, 0x6a, 0x54, 0x98, 0xf1, 0xa7, 0x10, 0xf1, 0x2c, 0x16, 0x66,
0xc1, 0x75, 0xdf, 0x51, 0xa5, 0xd7, 0x0e, 0xee, 0xb8, 0x0f, 0xef, 0xa4, 0xec, 0x3e, 0x6a, 0xd2,
0xe8, 0xf3, 0x88, 0x15, 0x70, 0xc6, 0x52, 0x70, 0xaa, 0x66, 0x8b, 0x64, 0xbb, 0x2d, 0x9e, 0xb0,
0xa8, 0xe0, 0x1a, 0xf3, 0xef, 0xab, 0xd2, 0x6b, 0xbe, 0x5c, 0x78, 0xc2, 0x65, 0xa9, 0x7d, 0x8e,
0x1a, 0x05, 0x64, 0xf0, 0xc5, 0x4c, 0xa8, 0xfd, 0xdf, 0x84, 0x3d, 0x55, 0x7a, 0x8d, 0xf0, 0xc6,
0x12, 0x2e, 0x84, 0xf6, 0x0b, 0xd4, 0x32, 0x2f, 0x3b, 0x2b, 0x68, 0x26, 0x98, 0x7e, 0x9b, 0x70,
0xea, 0x66, 0x17, 0x6d, 0x55, 0x7a, 0xad, 0x60, 0xe5, 0x2e, 0x5c, 0xeb, 0xf6, 0x5f, 0x4f, 0xa6,
0x6e, 0xe5, 0x6a, 0xea, 0x56, 0xae, 0xa7, 0x6e, 0xe5, 0xab, 0x72, 0xad, 0x89, 0x72, 0xad, 0x2b,
0xe5, 0x5a, 0xd7, 0xca, 0xb5, 0x7e, 0x29, 0xd7, 0xfa, 0xf6, 0xdb, 0xad, 0xbc, 0xf7, 0x36, 0xfc,
0x54, 0xfe, 0x04, 0x00, 0x00, 0xff, 0xff, 0x5d, 0x81, 0x42, 0xfe, 0x76, 0x04, 0x00, 0x00,
// 600 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0xdf, 0x4e, 0xd4, 0x4e,
0x14, 0xc7, 0xb7, 0xb0, 0xfb, 0xfb, 0xb1, 0xb3, 0xf2, 0x27, 0x23, 0x17, 0x0d, 0x17, 0x2d, 0xe1,
0xc2, 0x10, 0x12, 0xa7, 0x82, 0xc6, 0x18, 0x13, 0x13, 0x2d, 0x9a, 0x48, 0x2c, 0xd1, 0x14, 0xae,
0x0c, 0x89, 0xce, 0xb6, 0x87, 0xee, 0x08, 0xed, 0xd4, 0x99, 0x59, 0x0c, 0x77, 0x3e, 0x82, 0x4f,
0xa3, 0xf1, 0x0d, 0xb8, 0xe4, 0x92, 0xab, 0x46, 0xc6, 0xb7, 0xf0, 0xca, 0xcc, 0x6c, 0x61, 0x61,
0x81, 0xb0, 0xf1, 0x6e, 0xe7, 0x9c, 0xf3, 0xfd, 0x9c, 0xef, 0x9c, 0xb3, 0x53, 0x14, 0xec, 0x3d,
0x91, 0x84, 0xf1, 0x80, 0x96, 0x2c, 0x48, 0x38, 0x17, 0x29, 0x2b, 0xa8, 0x62, 0xbc, 0x08, 0x0e,
0x56, 0xbb, 0xa0, 0xe8, 0x6a, 0x90, 0x41, 0x01, 0x82, 0x2a, 0x48, 0x49, 0x29, 0xb8, 0xe2, 0xd8,
0x1f, 0x08, 0x08, 0x2d, 0x19, 0xb9, 0x28, 0x20, 0xb5, 0x60, 0xe1, 0x7e, 0xc6, 0x54, 0xaf, 0xdf,
0x25, 0x09, 0xcf, 0x83, 0x8c, 0x67, 0x3c, 0xb0, 0xba, 0x6e, 0x7f, 0xd7, 0x9e, 0xec, 0xc1, 0xfe,
0x1a, 0xf0, 0x16, 0x56, 0x6e, 0x36, 0x30, 0xda, 0x7b, 0xe1, 0xd1, 0xb0, 0x36, 0xa7, 0x49, 0x8f,
0x15, 0x20, 0x0e, 0x83, 0x72, 0x2f, 0x33, 0x01, 0x19, 0xe4, 0xa0, 0xe8, 0x75, 0xaa, 0xe0, 0x26,
0x95, 0xe8, 0x17, 0x8a, 0xe5, 0x70, 0x45, 0xf0, 0xf8, 0x36, 0x81, 0x4c, 0x7a, 0x90, 0xd3, 0x51,
0xdd, 0xd2, 0x0f, 0x07, 0xb5, 0x22, 0xa0, 0x12, 0xf0, 0x47, 0x34, 0x65, 0xdc, 0xa4, 0x54, 0x51,
0xd7, 0x59, 0x74, 0x96, 0x3b, 0x6b, 0x0f, 0xc8, 0x70, 0x6e, 0xe7, 0x50, 0x52, 0xee, 0x65, 0x26,
0x20, 0x89, 0xa9, 0x26, 0x07, 0xab, 0xe4, 0x6d, 0xf7, 0x13, 0x24, 0x6a, 0x13, 0x14, 0x0d, 0xf1,
0x51, 0xe5, 0x37, 0x74, 0xe5, 0xa3, 0x61, 0x2c, 0x3e, 0xa7, 0xe2, 0x08, 0x35, 0x65, 0x09, 0x89,
0x3b, 0x61, 0xe9, 0x2b, 0xe4, 0x96, 0xad, 0x10, 0xeb, 0x6b, 0xab, 0x84, 0x24, 0xbc, 0x53, 0x73,
0x9b, 0xe6, 0x14, 0x5b, 0xca, 0xd2, 0x77, 0x07, 0xb5, 0x6d, 0x45, 0xc4, 0xa4, 0xc2, 0x3b, 0x57,
0xdc, 0x93, 0xf1, 0xdc, 0x1b, 0xb5, 0xf5, 0x3e, 0x57, 0xf7, 0x98, 0x3a, 0x8b, 0x5c, 0x70, 0xfe,
0x06, 0xb5, 0x98, 0x82, 0x5c, 0xba, 0x13, 0x8b, 0x93, 0xcb, 0x9d, 0xb5, 0x7b, 0xe3, 0x59, 0x0f,
0xa7, 0x6b, 0x64, 0x6b, 0xc3, 0x88, 0xe3, 0x01, 0x63, 0xe9, 0x67, 0xb3, 0x36, 0x6e, 0x2e, 0x83,
0x9f, 0xa2, 0x99, 0x1e, 0xdf, 0x4f, 0x41, 0x6c, 0xa4, 0x50, 0x28, 0xa6, 0x0e, 0xad, 0xfd, 0x76,
0x88, 0x75, 0xe5, 0xcf, 0xbc, 0xbe, 0x94, 0x89, 0x47, 0x2a, 0x71, 0x84, 0xe6, 0xf7, 0x0d, 0xe8,
0x65, 0x5f, 0xd8, 0xf6, 0x5b, 0x90, 0xf0, 0x22, 0x95, 0x76, 0xc0, 0xad, 0xd0, 0xd5, 0x95, 0x3f,
0x1f, 0x5d, 0x93, 0x8f, 0xaf, 0x55, 0xe1, 0x2e, 0xea, 0xd0, 0xe4, 0x73, 0x9f, 0x09, 0xd8, 0x66,
0x39, 0xb8, 0x93, 0x76, 0x8a, 0xc1, 0x78, 0x53, 0xdc, 0x64, 0x89, 0xe0, 0x46, 0x16, 0xce, 0xea,
0xca, 0xef, 0xbc, 0x18, 0x72, 0xe2, 0x8b, 0x50, 0xbc, 0x83, 0xda, 0x02, 0x0a, 0xf8, 0x62, 0x3b,
0x34, 0xff, 0xad, 0xc3, 0xb4, 0xae, 0xfc, 0x76, 0x7c, 0x46, 0x89, 0x87, 0x40, 0xfc, 0x1c, 0xcd,
0xd9, 0x9b, 0x6d, 0x0b, 0x5a, 0x48, 0x66, 0xee, 0x26, 0xdd, 0x96, 0x9d, 0xc5, 0xbc, 0xae, 0xfc,
0xb9, 0x68, 0x24, 0x17, 0x5f, 0xa9, 0xc6, 0x1f, 0xd0, 0x94, 0x54, 0xe6, 0x7d, 0x64, 0x87, 0xee,
0x7f, 0x76, 0x0f, 0xeb, 0xe6, 0x2f, 0xb1, 0x55, 0xc7, 0xfe, 0x54, 0xfe, 0xc3, 0x9b, 0xdf, 0x3e,
0x59, 0x3f, 0x3b, 0x43, 0x3a, 0x58, 0x70, 0x2d, 0x8b, 0xcf, 0xa1, 0xf8, 0x19, 0x9a, 0x2d, 0x05,
0xec, 0x82, 0x10, 0x90, 0x0e, 0xb6, 0xeb, 0xfe, 0x6f, 0xfb, 0xdc, 0xd5, 0x95, 0x3f, 0xfb, 0xee,
0x72, 0x2a, 0x1e, 0xad, 0x0d, 0x5f, 0x1d, 0x9d, 0x7a, 0x8d, 0xe3, 0x53, 0xaf, 0x71, 0x72, 0xea,
0x35, 0xbe, 0x6a, 0xcf, 0x39, 0xd2, 0x9e, 0x73, 0xac, 0x3d, 0xe7, 0x44, 0x7b, 0xce, 0x2f, 0xed,
0x39, 0xdf, 0x7e, 0x7b, 0x8d, 0xf7, 0xfe, 0x2d, 0x1f, 0xc8, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff,
0x57, 0x93, 0xf3, 0xef, 0x42, 0x05, 0x00, 0x00,
}
func (m *Lease) Marshal() (dAtA []byte, err error) {
@ -285,6 +292,20 @@ func (m *LeaseSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if m.PreferredHolder != nil {
i -= len(*m.PreferredHolder)
copy(dAtA[i:], *m.PreferredHolder)
i = encodeVarintGenerated(dAtA, i, uint64(len(*m.PreferredHolder)))
i--
dAtA[i] = 0x3a
}
if m.Strategy != nil {
i -= len(*m.Strategy)
copy(dAtA[i:], *m.Strategy)
i = encodeVarintGenerated(dAtA, i, uint64(len(*m.Strategy)))
i--
dAtA[i] = 0x32
}
if m.LeaseTransitions != nil {
i = encodeVarintGenerated(dAtA, i, uint64(*m.LeaseTransitions))
i--
@ -394,6 +415,14 @@ func (m *LeaseSpec) Size() (n int) {
if m.LeaseTransitions != nil {
n += 1 + sovGenerated(uint64(*m.LeaseTransitions))
}
if m.Strategy != nil {
l = len(*m.Strategy)
n += 1 + l + sovGenerated(uint64(l))
}
if m.PreferredHolder != nil {
l = len(*m.PreferredHolder)
n += 1 + l + sovGenerated(uint64(l))
}
return n
}
@ -440,6 +469,8 @@ func (this *LeaseSpec) String() string {
`AcquireTime:` + strings.Replace(fmt.Sprintf("%v", this.AcquireTime), "MicroTime", "v1.MicroTime", 1) + `,`,
`RenewTime:` + strings.Replace(fmt.Sprintf("%v", this.RenewTime), "MicroTime", "v1.MicroTime", 1) + `,`,
`LeaseTransitions:` + valueToStringGenerated(this.LeaseTransitions) + `,`,
`Strategy:` + valueToStringGenerated(this.Strategy) + `,`,
`PreferredHolder:` + valueToStringGenerated(this.PreferredHolder) + `,`,
`}`,
}, "")
return s
@ -859,6 +890,72 @@ func (m *LeaseSpec) Unmarshal(dAtA []byte) error {
}
}
m.LeaseTransitions = &v
case 6:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Strategy", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
s := k8s_io_api_coordination_v1.CoordinatedLeaseStrategy(dAtA[iNdEx:postIndex])
m.Strategy = &s
iNdEx = postIndex
case 7:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field PreferredHolder", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
s := string(dAtA[iNdEx:postIndex])
m.PreferredHolder = &s
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])

View File

@ -21,6 +21,7 @@ syntax = "proto2";
package k8s.io.api.coordination.v1beta1;
import "k8s.io/api/coordination/v1/generated.proto";
import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto";
import "k8s.io/apimachinery/pkg/runtime/generated.proto";
import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto";
@ -54,6 +55,8 @@ message LeaseList {
// LeaseSpec is a specification of a Lease.
message LeaseSpec {
// holderIdentity contains the identity of the holder of a current lease.
// If Coordinated Leader Election is used, the holder identity must be
// equal to the elected LeaseCandidate.metadata.name field.
// +optional
optional string holderIdentity = 1;
@ -76,5 +79,17 @@ message LeaseSpec {
// holders.
// +optional
optional int32 leaseTransitions = 5;
// Strategy indicates the strategy for picking the leader for coordinated leader election
// (Alpha) Using this field requires the CoordinatedLeaderElection feature gate to be enabled.
// +featureGate=CoordinatedLeaderElection
// +optional
optional string strategy = 6;
// PreferredHolder signals to a lease holder that the lease has a
// more optimal holder and should be given up.
// +featureGate=CoordinatedLeaderElection
// +optional
optional string preferredHolder = 7;
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package v1beta1
import (
v1 "k8s.io/api/coordination/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -42,6 +43,8 @@ type Lease struct {
// LeaseSpec is a specification of a Lease.
type LeaseSpec struct {
// holderIdentity contains the identity of the holder of a current lease.
// If Coordinated Leader Election is used, the holder identity must be
// equal to the elected LeaseCandidate.metadata.name field.
// +optional
HolderIdentity *string `json:"holderIdentity,omitempty" protobuf:"bytes,1,opt,name=holderIdentity"`
// leaseDurationSeconds is a duration that candidates for a lease need
@ -60,6 +63,16 @@ type LeaseSpec struct {
// holders.
// +optional
LeaseTransitions *int32 `json:"leaseTransitions,omitempty" protobuf:"varint,5,opt,name=leaseTransitions"`
// Strategy indicates the strategy for picking the leader for coordinated leader election
// (Alpha) Using this field requires the CoordinatedLeaderElection feature gate to be enabled.
// +featureGate=CoordinatedLeaderElection
// +optional
Strategy *v1.CoordinatedLeaseStrategy `json:"strategy,omitempty" protobuf:"bytes,6,opt,name=strategy"`
// PreferredHolder signals to a lease holder that the lease has a
// more optimal holder and should be given up.
// +featureGate=CoordinatedLeaderElection
// +optional
PreferredHolder *string `json:"preferredHolder,omitempty" protobuf:"bytes,7,opt,name=preferredHolder"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View File

@ -49,11 +49,13 @@ func (LeaseList) SwaggerDoc() map[string]string {
var map_LeaseSpec = map[string]string{
"": "LeaseSpec is a specification of a Lease.",
"holderIdentity": "holderIdentity contains the identity of the holder of a current lease.",
"holderIdentity": "holderIdentity contains the identity of the holder of a current lease. If Coordinated Leader Election is used, the holder identity must be equal to the elected LeaseCandidate.metadata.name field.",
"leaseDurationSeconds": "leaseDurationSeconds is a duration that candidates for a lease need to wait to force acquire it. This is measure against time of last observed renewTime.",
"acquireTime": "acquireTime is a time when the current lease was acquired.",
"renewTime": "renewTime is a time when the current holder of a lease has last updated the lease.",
"leaseTransitions": "leaseTransitions is the number of transitions of a lease between holders.",
"strategy": "Strategy indicates the strategy for picking the leader for coordinated leader election (Alpha) Using this field requires the CoordinatedLeaderElection feature gate to be enabled.",
"preferredHolder": "PreferredHolder signals to a lease holder that the lease has a more optimal holder and should be given up.",
}
func (LeaseSpec) SwaggerDoc() map[string]string {

View File

@ -22,6 +22,7 @@ limitations under the License.
package v1beta1
import (
v1 "k8s.io/api/coordination/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
@ -111,6 +112,16 @@ func (in *LeaseSpec) DeepCopyInto(out *LeaseSpec) {
*out = new(int32)
**out = **in
}
if in.Strategy != nil {
in, out := &in.Strategy, &out.Strategy
*out = new(v1.CoordinatedLeaseStrategy)
**out = **in
}
if in.PreferredHolder != nil {
in, out := &in.PreferredHolder, &out.PreferredHolder
*out = new(string)
**out = **in
}
return
}

View File

@ -45,6 +45,7 @@ import (
certificatesv1alpha1 "k8s.io/api/certificates/v1alpha1"
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
coordinationv1 "k8s.io/api/coordination/v1"
coordinationv1alpha1 "k8s.io/api/coordination/v1alpha1"
coordinationv1beta1 "k8s.io/api/coordination/v1beta1"
corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
@ -112,6 +113,7 @@ var groups = []runtime.SchemeBuilder{
certificatesv1alpha1.SchemeBuilder,
coordinationv1.SchemeBuilder,
coordinationv1beta1.SchemeBuilder,
coordinationv1alpha1.SchemeBuilder,
corev1.SchemeBuilder,
discoveryv1.SchemeBuilder,
discoveryv1beta1.SchemeBuilder,

View File

@ -48,6 +48,8 @@
"leaseDurationSeconds": 2,
"acquireTime": "2003-01-01T01:01:01.000003Z",
"renewTime": "2004-01-01T01:01:01.000004Z",
"leaseTransitions": 5
"leaseTransitions": 5,
"strategy": "strategyValue",
"preferredHolder": "preferredHolderValue"
}
}

View File

@ -37,4 +37,6 @@ spec:
holderIdentity: holderIdentityValue
leaseDurationSeconds: 2
leaseTransitions: 5
preferredHolder: preferredHolderValue
renewTime: "2004-01-01T01:01:01.000004Z"
strategy: strategyValue

View File

@ -0,0 +1,56 @@
{
"kind": "LeaseCandidate",
"apiVersion": "coordination.k8s.io/v1alpha1",
"metadata": {
"name": "nameValue",
"generateName": "generateNameValue",
"namespace": "namespaceValue",
"selfLink": "selfLinkValue",
"uid": "uidValue",
"resourceVersion": "resourceVersionValue",
"generation": 7,
"creationTimestamp": "2008-01-01T01:01:01Z",
"deletionTimestamp": "2009-01-01T01:01:01Z",
"deletionGracePeriodSeconds": 10,
"labels": {
"labelsKey": "labelsValue"
},
"annotations": {
"annotationsKey": "annotationsValue"
},
"ownerReferences": [
{
"apiVersion": "apiVersionValue",
"kind": "kindValue",
"name": "nameValue",
"uid": "uidValue",
"controller": true,
"blockOwnerDeletion": true
}
],
"finalizers": [
"finalizersValue"
],
"managedFields": [
{
"manager": "managerValue",
"operation": "operationValue",
"apiVersion": "apiVersionValue",
"time": "2004-01-01T01:01:01Z",
"fieldsType": "fieldsTypeValue",
"fieldsV1": {},
"subresource": "subresourceValue"
}
]
},
"spec": {
"leaseName": "leaseNameValue",
"pingTime": "2002-01-01T01:01:01.000002Z",
"renewTime": "2003-01-01T01:01:01.000003Z",
"binaryVersion": "binaryVersionValue",
"emulationVersion": "emulationVersionValue",
"preferredStrategies": [
"preferredStrategiesValue"
]
}
}

Some files were not shown because too many files have changed in this diff Show More