Compare commits

...

49 Commits

Author SHA1 Message Date
Kubernetes Publisher
7f4bc0ee62 Update dependencies to v0.30.0-alpha.3 tag 2024-02-28 11:00:18 +00:00
Kubernetes Publisher
8c4efe8d07 Merge pull request #123348 from hoskeri/update-go-x-crypto-19
Update x/crypto to 0.19.

Kubernetes-commit: 9a9028983806af26e7b48223f3a92922e94725df
2024-02-21 20:26:51 +00:00
Kubernetes Publisher
e88f4481f2 Merge pull request #123392 from thockin/depreciate
Cleanup: s/depreciated/deprecated/g

Kubernetes-commit: 11785bb815d58eb553be3a1fa305464c35d860cc
2024-02-21 12:27:26 +00:00
Tim Hockin
7087b65a74 Cleanup: s/depreciated/deprecated/g
Kubernetes-commit: 9f4b82bf3b079fe868effbd2498b61464db6d459
2024-02-18 14:50:55 -08:00
Abhijit Hoskeri
bb04dc47e3 Update x/crypto to 0.19.
Main reason is to pick up updated CA roots.

Full diff: https://github.com/golang/crypto/compare/v0.16.0...v0.19.0

Kubernetes-commit: d3a0e296defbb0b55e591e273004e79e7ebfb1fd
2024-02-16 20:18:14 +00:00
Kubernetes Publisher
306b201a2d Merge pull request #123250 from benluddy/dep-bump-cbor-v2.6.0
Bump github.com/fxamacker/cbor/v2 to v2.6.0.

Kubernetes-commit: e305e773bbfe8c5bdf9c57881a875e168b004b8c
2024-02-15 01:24:42 +00:00
Ben Luddy
d23a110967 Bump github.com/fxamacker/cbor/v2 to v2.6.0.
Kubernetes-commit: aac43dc96f2b679f0ab030fd3512c7e03b0f2df4
2024-02-12 15:46:17 -05:00
Kubernetes Prow Robot
dc532e7975 Merge pull request #1294 from dnwe/compatibility-matrix
chore(docs): refresh compatibility matrix
2024-02-13 10:38:36 -08:00
Kubernetes Publisher
5fb8d886b5 Merge pull request #122152 from pohly/client-go-event-context-fix
client-go events: support context.Background() as context

Kubernetes-commit: e51e3c74f720cd299e10a5e0c500a749bb36c251
2024-02-12 17:23:58 +00:00
Kubernetes Publisher
790cfadf62 Merge pull request #121486 from benluddy/cbor-stub
KEP-4222: Add stub CBOR serializer.

Kubernetes-commit: 48228bf9dbac308f43abd59a53fdc069fbddee0f
2024-02-10 01:28:08 +00:00
José Carlos Chávez
76174b8af8 chore: adds consistent vanity import to files and provides tooling for verifying and updating them. (#120642)
* chore: drops update vanity imports from script.

* chore: changes copyright year to 2024.

* chore: makes lint happy.

Kubernetes-commit: 6d6398ef9266abce3518a4c9a3d4e4d8feeffdc1
2024-02-08 13:38:07 +00:00
Dominic Evans
3d92ad924f chore(docs): refresh compatibility matrix
Bring the compatibility matrix up-to-date with more recent versions of
Kubernetes

Signed-off-by: Dominic Evans <dominic.evans@uk.ibm.com>
2024-01-24 18:28:43 +00:00
Kubernetes Publisher
8092c71d36 Merge pull request #119398 from IvoGoman/feat/clientconfig-override-raw
k8s.io/client-go: add ClientConfig option to override raw config

Kubernetes-commit: a1ffdedf782edf1472102b0b99c1467d4ed39753
2024-01-24 02:12:19 +01:00
Patrick Ohly
2231ff5ae4 client-go events: support context.Background() as context
If, for whatever reason, the context was context.Background(), the additional
goroutine was started and then got stuck forever because
context.Background().Done() is a nil channel. Found when indirectly
instantiating a broadcaster with such a context:

    found unexpected goroutines:
    [Goroutine 9106 in state chan receive (nil chan), with k8s.io/kubernetes/vendor/k8s.io/client-go/tools/record.NewBroadcaster.func1 on top of the stack:
	goroutine 9106 [chan receive (nil chan)]:
	k8s.io/kubernetes/vendor/k8s.io/client-go/tools/record.NewBroadcaster.func1()
		/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/k8s.io/client-go/tools/record/event.go:206 +0x2c
	created by k8s.io/kubernetes/vendor/k8s.io/client-go/tools/record.NewBroadcaster in goroutine 8957
		/home/prow/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/vendor/k8s.io/client-go/tools/record/event.go:205 +0x1a5

This can be fixed by checking for a nil channel.

Another problem also gets addressed: if Shutdown was called without canceling
the context, the goroutine also didn't stop. Now it waits for the cancelation
context and thus terminates in both cases.

Kubernetes-commit: eed6e29a5b8cfaa20fbc426541d9c74105d430ee
2023-12-01 18:35:28 +01:00
Ben Luddy
dc967a1ca9 Update vendoring to take new CBOR library dependency.
Kubernetes-commit: 09a1abda998fc37e2e29a120a82be7c6271656e0
2023-10-17 16:51:52 -04:00
Ivo Gosemann
89528c43be k8s.io/client-go: add OverridingClientConfig overriding RawConfig
Kubernetes-commit: 740b4c456d731922196f8231df9ab585198696d6
2023-07-18 13:40:12 +02:00
Ivo Gosemann
17b5405ddb k8s.io/client-go: add ClientConfig option to override raw config
Kubernetes-commit: db80aa56ed8bcf4115e30fc62d27c8a8d8ec7f92
2023-07-18 13:40:12 +02:00
Kubernetes Publisher
657d7be98b Merge pull request #122873 from p0lyn0mial/upstream-reflector-usewatchlist-pointer
client-go/reflector: make UseWatchList a pointer

Kubernetes-commit: 445869a59bdbd1c587b72b52c5da94c1d1c316a1
2024-01-22 18:20:58 +01:00
Lukasz Szaszkiewicz
202c415847 client-go/reflector: make UseWatchList a pointer
until #115478(use streaming against the etcd storage)
is resolved the cacher need a way to disable the streaming.

Kubernetes-commit: 41e706600aea7468f486150d951d3b8948ce89d5
2024-01-19 13:48:29 +01:00
Kubernetes Publisher
ffe7bf60eb Merge pull request #122842 from pohly/klog-update
dependencies: klog v2.120.1

Kubernetes-commit: b27b56a46c4c1e6be0dc2b1a0230d86223a7e903
2024-01-18 21:21:59 +00:00
Kubernetes Publisher
7e651e598d Merge pull request #122839 from pohly/ginkgo-gomega-update
dependencies: ginkgo v2.15.0, gomega v1.31.0

Kubernetes-commit: c82da711b0e2184f851675aac4596bbd0f74763f
2024-01-18 21:21:57 +00:00
Patrick Ohly
b2c0677f40 dependencies: klog v2.120.1
Kubernetes-commit: e2222f1e304831cbbc57b61afa373612297055fb
2024-01-18 16:58:40 +01:00
Patrick Ohly
eab7383fd8 dependencies: ginkgo v2.15.0, gomega v1.31.0
The main reason for updating is support for reporting the cause of context
cancellation: Ginkgo provides that information when canceling a context and
Gomega polling code includes that when generating a failure message.

Kubernetes-commit: 18f0af1f000f95749ca1ea075d62ca89e86bb7da
2024-01-18 12:45:55 +01:00
Kubernetes Publisher
aa81cb55f0 Merge pull request #122142 from pohly/client-go-events-adapter-context
client-go events: also support context for NewEventBroadcasterAdapter

Kubernetes-commit: a02a00ecd76fb99ed43a054ed6eb6698426b01f9
2024-01-18 09:21:31 +00:00
Kubernetes Publisher
b13c4f4b00 Merge pull request #122571 from p0lyn0mial/upstream-client-go-watch-list-fg
client-go/features: introduce WatchListClient feature gate

Kubernetes-commit: 026a27e5eb431c553d1204817cbf09f1005eca9b
2024-01-15 21:21:53 +00:00
Kubernetes Publisher
fb1e77b992 Merge pull request #122706 from pacoxu/klog-upgrade
bump klog to  v2.120.0

Kubernetes-commit: 823ecb58f68fbe0a4b37b32e11e75c6f2e0f467c
2024-01-11 21:21:47 +00:00
Paco Xu
a3cbf5a7be bump klog to v2.120.0
Kubernetes-commit: 3c86d21316c25b52a1cf3f9703a0bc2cbe97131c
2024-01-11 17:35:07 +08:00
Kubernetes Publisher
b0cf21f3e8 Merge pull request #122555 from p0lyn0mial/upstream-client-go-fg-provider-with-types
client-go/features: introduce feature gates

Kubernetes-commit: 0341e8294abffdfbdd4a038e0fd49d7f35ddc30a
2024-01-10 20:49:12 +01:00
Lukasz Szaszkiewicz
e8a81a3a43 client-go/features: warn when ordering initialization issue
ReplaceFeatureGates logs a warning when the default env var
implementation has been already used.
Such a situation indicates a potential ordering issue and usually is unwanted.

Kubernetes-commit: 04bbd3481f70825eea54b4b154a04d2496dcf652
2024-01-10 17:15:01 +01:00
Lukasz Szaszkiewicz
49ba51431a client-go/features: introduce WatchListClient feature gate
Kubernetes-commit: 7773b0f53fd5003c1e018321efb791e8980cd02f
2024-01-03 13:32:02 +01:00
Lukasz Szaszkiewicz
9f8ed7bc90 client-go/features: move the defaultKubernetesFeatureGates variable to the new file
Kubernetes-commit: 57ec7d20e89990d62ef85835cd37cc32ea6a418e
2024-01-03 13:24:35 +01:00
Lukasz Szaszkiewicz
ca4f3a73f7 client-go/features: introduce feature gates
This PR add a feature gates mechanisim to client-go
as described in https://docs.google.com/document/d/1g9BGCRw-7ucUxO6OtCWbb3lfzUGA_uU9178wLdXAIfs

In particular:
  - Adds a default feature gate implementation based on environment variables.
  - Adds a set of methods for reading, overwriting the default implementation, and adding features to an external registry.

Co-authored-by: deads2k <deads@redhat.com>
Co-authored-by: Ben Luddy <bluddy@redhat.com>

Kubernetes-commit: d74c57d4f592d20a992afb54b1ee64f56215210e
2024-01-02 12:46:15 +01:00
Kubernetes Publisher
5a0a424792 Merge pull request #122412 from MadhavJivrajani/bump-go-tools
[go1.22] .*: bump golang.org/x/tools to v0.16.1

Kubernetes-commit: 8a4403a9e5127d2ec3f596c4ce75663e5392cb18
2023-12-20 17:30:06 +00:00
Madhav Jivrajani
4106282f90 .*: bump golang.org/x/tools to v0.16.1
Bumping tools to include the fix for a nil pointer
deref error in go/types. See golang/go#64812
for more details.

This fix is needed for when we bump to go1.22.

Signed-off-by: Madhav Jivrajani <madhav.jiv@gmail.com>

Kubernetes-commit: a8da4202c0ac785d57b545e6e310fd754888b50e
2023-12-20 14:31:31 +05:30
Kubernetes Publisher
96e9c8d6f1 Merge pull request #122395 from pohly/ginkgo-gomega-update
dependencies: gomega v1.30.0 + ginkgo v2.13.2

Kubernetes-commit: 7897910469aa091ebf6576740d055a7137fa147c
2023-12-20 09:21:10 +00:00
Patrick Ohly
abce78fd54 dependencies: gomega v1.30.0 + ginkgo v2.13.2
The new gomega.BeTrueBecause and gomega.BeFalseBecause are going to be useful
for https://github.com/kubernetes/kubernetes/issues/105678.

Kubernetes-commit: c8f9ebfb72b6569b4e2ec9733f6998afc6602135
2023-12-19 16:16:02 +01:00
Kubernetes Publisher
0a514be22c Merge pull request #122310 from weilaaa/use_buildin_max_min_instead
use build-in max and min func to instead of k8s.io/utils/integer funcs

Kubernetes-commit: 2b5c0c281d7d3b75ed7df683f09339147c4a17e9
2023-12-18 21:19:57 +00:00
weilaaa
c609c97b33 use build-in max and min func to instead of k8s.io/utils/integer funcs
Kubernetes-commit: eb8f3f194fed16484162aebdaab69168e02f8cb4
2023-12-15 15:09:11 +08:00
Ricardo Lopes
12b0e099db Migrate client-go/metadata to contextual logging (#122225)
* client-go: migrate metadata to contextual logging

Signed-off-by: Ricardo Lopes <ricardoapl.dev@gmail.com>

* client-go: test for metadata contextual logs

Signed-off-by: Ricardo Lopes <ricardoapl.dev@gmail.com>

* refactor: extract context for table driven testing

Signed-off-by: Ricardo Lopes <ricardoapl.dev@gmail.com>

* refactor: pass context as first parameter

Signed-off-by: Ricardo Lopes <ricardoapl.dev@gmail.com>

---------

Signed-off-by: Ricardo Lopes <ricardoapl.dev@gmail.com>

Kubernetes-commit: 86ab185fa1e98e249fe3e380217099832fe22a4e
2023-12-14 06:26:45 +00:00
Kubernetes Publisher
e3d7e06bd6 Merge pull request #122069 from linxiulei/le_opt0
Optimistically update leader lock

Kubernetes-commit: 583a79a4569b5cbbff12131ebcc612d5cf659ae1
2023-12-14 05:20:06 +00:00
Kubernetes Publisher
feecac4b44 Merge pull request #121759 from thockin/fix_api_violations
Fix "list_type_missing" API violations in meta/v1

Kubernetes-commit: 8a22571ebb7628b2dd2c2faf453293040b7efaef
2023-12-14 01:20:15 +00:00
Kubernetes Publisher
9434e7539b Merge pull request #117046 from howardjohn/client/add-indexer-after-start
client-go: allow adding indexes after informer starts

Kubernetes-commit: db82260c65df9cedaf93e924a2401b4e55a8a2a0
2023-12-13 21:20:02 +00:00
Patrick Ohly
8468c261bc client-go events: also support context for NewEventBroadcasterAdapter
27a68aee3a4834 introduced context support. In order to use that
also with NewEventBroadcasterAdapter, a variant of the call is needed to allow
the caller to specify the context.

The `logcheck:context` comment ensures that code which is meant to be
contextual uses the new call.

Kubernetes-commit: f8e25eff926c640c86daa46222bfaf8d625e75d7
2023-12-01 09:00:59 +01:00
Eric Lin
2a48f1ee02 leaderelection: Instrument for when slowpath is exercised
Signed-off-by: Eric Lin <exlin@google.com>

Kubernetes-commit: 1e54c050936be1a1e3e5758718ebca86096dbaea
2023-11-27 13:10:24 +00:00
Eric Lin
3c7c00d2d6 leaderelection: optimistically update leader lock
Signed-off-by: Eric Lin <exlin@google.com>

Kubernetes-commit: 1d9f7fd516b4787f5ef32692711d5ae3031e794e
2023-11-25 22:04:32 +00:00
Tim Hockin
e9d1484a8e Re-vendor k8s.io/kube-openapi
./hack/pin-dependency.sh k8s.io/kube-openapi 778a5567bc1edaed92a4de9c07f90199c67953fa

./hack/update-vendor.sh

Kubernetes-commit: 1f55357d9937f076f532a2c1aa104593b9f6c49a
2023-11-13 10:59:57 -08:00
Kubernetes Publisher
84a6fe7e40 Merge pull request #121808 from cpanato/go-update-main
[go] Bump images, dependencies and versions to go 1.21.4

Kubernetes-commit: 6ba7258a0f3f73629560fc30016b2e35c8e7ae9c
2023-11-13 17:17:42 +00:00
cpanato
7f07a956f8 update go.mod
Signed-off-by: cpanato <ctadeu@gmail.com>

Kubernetes-commit: 9e5b8402bb95eb82541099e77c3a8b0ccd31297f
2023-11-08 08:46:15 -06:00
John Howard
785e19661f client-go: allow adding indexes after informer starts
Kubernetes-commit: d96a9858d396d7f418d24ea47bdc92ef8429f707
2023-03-31 15:57:18 -07:00
40 changed files with 1205 additions and 181 deletions

View File

@@ -75,14 +75,14 @@ We will backport bugfixes--but not new features--into older versions of
#### Compatibility matrix
| | Kubernetes 1.15 | Kubernetes 1.16 | Kubernetes 1.17 | Kubernetes 1.18 | Kubernetes 1.19 | Kubernetes 1.20 |
|-------------------------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|
| `kubernetes-1.15.0` | ✓ | +- | +- | +- | +- | +- |
| `kubernetes-1.16.0` | +- | ✓ | +- | +- | +- | +- |
| `kubernetes-1.17.0`/`v0.17.0` | +- | +- | ✓ | +- | +- | +- |
| `kubernetes-1.18.0`/`v0.18.0` | +- | +- | +- | ✓ | +- | +- |
| `kubernetes-1.19.0`/`v0.19.0` | +- | +- | +- | +- | ✓ | +- |
| `kubernetes-1.20.0`/`v0.20.0` | +- | +- | +- | +- | +- | ✓ |
| | Kubernetes 1.23 | Kubernetes 1.24 | Kubernetes 1.25 | Kubernetes 1.26 | Kubernetes 1.27 | Kubernetes 1.28 |
| ----------------------------- | --------------- | --------------- | --------------- | --------------- | --------------- | --------------- |
| `kubernetes-1.23.0`/`v0.23.0` | ✓ | +- | +- | +- | +- | +- |
| `kubernetes-1.24.0`/`v0.24.0` | +- | ✓ | +- | +- | +- | +- |
| `kubernetes-1.25.0`/`v0.25.0` | +- | +- | ✓ | +- | +- | +- |
| `kubernetes-1.26.0`/`v0.26.0` | +- | +- | +- | ✓ | +- | +- |
| `kubernetes-1.27.0`/`v0.27.0` | +- | +- | +- | +- | ✓ | +- |
| `kubernetes-1.28.0`/`v0.28.0` | +- | +- | +- | +- | +- | ✓ |
| `HEAD` | +- | +- | +- | +- | +- | +- |
Key:
@@ -102,27 +102,19 @@ Key:
See the [CHANGELOG](./CHANGELOG.md) for a detailed description of changes
between client-go versions.
| Branch | Canonical source code location | Maintenance status |
|----------------|--------------------------------------|-------------------------------|
| `release-1.4` | Kubernetes main repo, 1.4 branch | = - |
| `release-1.5` | Kubernetes main repo, 1.5 branch | = - |
| `release-2.0` | Kubernetes main repo, 1.5 branch | = - |
| `release-3.0` | Kubernetes main repo, 1.6 branch | = - |
| `release-4.0` | Kubernetes main repo, 1.7 branch | = - |
| `release-5.0` | Kubernetes main repo, 1.8 branch | = - |
| `release-6.0` | Kubernetes main repo, 1.9 branch | = - |
| `release-7.0` | Kubernetes main repo, 1.10 branch | = - |
| `release-8.0` | Kubernetes main repo, 1.11 branch | =- |
| `release-9.0` | Kubernetes main repo, 1.12 branch | =- |
| `release-10.0` | Kubernetes main repo, 1.13 branch | =- |
| `release-11.0` | Kubernetes main repo, 1.14 branch | =- |
| `release-12.0` | Kubernetes main repo, 1.15 branch | =- |
| `release-13.0` | Kubernetes main repo, 1.16 branch | =- |
| `release-14.0` | Kubernetes main repo, 1.17 branch | ✓ |
| `release-1.18` | Kubernetes main repo, 1.18 branch | ✓ |
| `release-1.19` | Kubernetes main repo, 1.19 branch | ✓ |
| `release-1.20` | Kubernetes main repo, 1.20 branch | ✓ |
| client-go HEAD | Kubernetes main repo, master branch | ✓ |
| Branch | Canonical source code location | Maintenance status |
| -------------- | ----------------------------------- | ------------------ |
| `release-1.19` | Kubernetes main repo, 1.19 branch | =- |
| `release-1.20` | Kubernetes main repo, 1.20 branch | =- |
| `release-1.21` | Kubernetes main repo, 1.21 branch | =- |
| `release-1.22` | Kubernetes main repo, 1.22 branch | =- |
| `release-1.23` | Kubernetes main repo, 1.23 branch | =- |
| `release-1.24` | Kubernetes main repo, 1.24 branch | =- |
| `release-1.25` | Kubernetes main repo, 1.25 branch | ✓ |
| `release-1.26` | Kubernetes main repo, 1.26 branch | |
| `release-1.27` | Kubernetes main repo, 1.27 branch | |
| `release-1.28` | Kubernetes main repo, 1.28 branch | |
| client-go HEAD | Kubernetes main repo, master branch | ✓ |
Key:

View File

@@ -148,4 +148,4 @@ reconciliation code that performs a "read/modify-in-place/update" (or patch) wor
// apply
applied, err := deploymentClient.Apply(ctx, extractedDeployment, metav1.ApplyOptions{FieldManager: fieldMgr})
*/
package applyconfigurations
package applyconfigurations // import "k8s.io/client-go/applyconfigurations"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -17,4 +17,4 @@ limitations under the License.
// Package fakeclient contains examples on how to use fakeclient in tests.
// Note: This file is here to avoid warnings on go build since there are no
// non-test files in this package.
package fakeclient
package fakeclient // import "k8s.io/client-go/examples/fake-client"

138
features/envvar.go Normal file
View File

@@ -0,0 +1,138 @@
/*
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 features
import (
"fmt"
"os"
"strconv"
"sync"
"sync/atomic"
"k8s.io/apimachinery/pkg/util/naming"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/klog/v2"
)
// internalPackages are packages that ignored when creating a name for featureGates. These packages are in the common
// call chains, so they'd be unhelpful as names.
var internalPackages = []string{"k8s.io/client-go/features/envvar.go"}
var _ Gates = &envVarFeatureGates{}
// newEnvVarFeatureGates creates a feature gate that allows for registration
// of features and checking if the features are enabled.
//
// On the first call to Enabled, the effective state of all known features is loaded from
// environment variables. The environment variable read for a given feature is formed by
// concatenating the prefix "KUBE_FEATURE_" with the feature's name.
//
// For example, if you have a feature named "MyFeature"
// setting an environmental variable "KUBE_FEATURE_MyFeature"
// will allow you to configure the state of that feature.
//
// Please note that environmental variables can only be set to the boolean value.
// Incorrect values will be ignored and logged.
func newEnvVarFeatureGates(features map[Feature]FeatureSpec) *envVarFeatureGates {
known := map[Feature]FeatureSpec{}
for name, spec := range features {
known[name] = spec
}
fg := &envVarFeatureGates{
callSiteName: naming.GetNameFromCallsite(internalPackages...),
known: known,
}
fg.enabled.Store(map[Feature]bool{})
return fg
}
// envVarFeatureGates implements Gates and allows for feature registration.
type envVarFeatureGates struct {
// callSiteName holds the name of the file
// that created this instance
callSiteName string
// readEnvVarsOnce guards reading environmental variables
readEnvVarsOnce sync.Once
// known holds known feature gates
known map[Feature]FeatureSpec
// enabled holds a map[Feature]bool
// with values explicitly set via env var
enabled atomic.Value
// readEnvVars holds the boolean value which
// indicates whether readEnvVarsOnce has been called.
readEnvVars atomic.Bool
}
// Enabled returns true if the key is enabled. If the key is not known, this call will panic.
func (f *envVarFeatureGates) Enabled(key Feature) bool {
if v, ok := f.getEnabledMapFromEnvVar()[key]; ok {
return v
}
if v, ok := f.known[key]; ok {
return v.Default
}
panic(fmt.Errorf("feature %q is not registered in FeatureGates %q", key, f.callSiteName))
}
// getEnabledMapFromEnvVar will fill the enabled map on the first call.
// This is the only time a known feature can be set to a value
// read from the corresponding environmental variable.
func (f *envVarFeatureGates) getEnabledMapFromEnvVar() map[Feature]bool {
f.readEnvVarsOnce.Do(func() {
featureGatesState := map[Feature]bool{}
for feature, featureSpec := range f.known {
featureState, featureStateSet := os.LookupEnv(fmt.Sprintf("KUBE_FEATURE_%s", feature))
if !featureStateSet {
continue
}
boolVal, boolErr := strconv.ParseBool(featureState)
switch {
case boolErr != nil:
utilruntime.HandleError(fmt.Errorf("cannot set feature gate %q to %q, due to %v", feature, featureState, boolErr))
case featureSpec.LockToDefault:
if boolVal != featureSpec.Default {
utilruntime.HandleError(fmt.Errorf("cannot set feature gate %q to %q, feature is locked to %v", feature, featureState, featureSpec.Default))
break
}
featureGatesState[feature] = featureSpec.Default
default:
featureGatesState[feature] = boolVal
}
}
f.enabled.Store(featureGatesState)
f.readEnvVars.Store(true)
for feature, featureSpec := range f.known {
if featureState, ok := featureGatesState[feature]; ok {
klog.V(1).InfoS("Feature gate updated state", "feature", feature, "enabled", featureState)
continue
}
klog.V(1).InfoS("Feature gate default state", "feature", feature, "enabled", featureSpec.Default)
}
})
return f.enabled.Load().(map[Feature]bool)
}
func (f *envVarFeatureGates) hasAlreadyReadEnvVar() bool {
return f.readEnvVars.Load()
}

156
features/envvar_test.go Normal file
View File

@@ -0,0 +1,156 @@
/*
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 features
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
)
func TestEnvVarFeatureGates(t *testing.T) {
defaultTestFeatures := map[Feature]FeatureSpec{
"TestAlpha": {
Default: false,
LockToDefault: false,
PreRelease: "Alpha",
},
"TestBeta": {
Default: true,
LockToDefault: false,
PreRelease: "Beta",
},
}
expectedDefaultFeaturesState := map[Feature]bool{"TestAlpha": false, "TestBeta": true}
copyExpectedStateMap := func(toCopy map[Feature]bool) map[Feature]bool {
m := map[Feature]bool{}
for k, v := range toCopy {
m[k] = v
}
return m
}
scenarios := []struct {
name string
features map[Feature]FeatureSpec
envVariables map[string]string
expectedFeaturesState map[Feature]bool
expectedInternalEnabledFeatureState map[Feature]bool
}{
{
name: "can add empty features",
},
{
name: "no env var, features get Defaults assigned",
features: defaultTestFeatures,
expectedFeaturesState: expectedDefaultFeaturesState,
},
{
name: "incorrect env var, feature gets Default assigned",
features: defaultTestFeatures,
envVariables: map[string]string{"TestAlpha": "true"},
expectedFeaturesState: expectedDefaultFeaturesState,
},
{
name: "correct env var changes the feature gets state",
features: defaultTestFeatures,
envVariables: map[string]string{"KUBE_FEATURE_TestAlpha": "true"},
expectedFeaturesState: func() map[Feature]bool {
expectedDefaultFeaturesStateCopy := copyExpectedStateMap(expectedDefaultFeaturesState)
expectedDefaultFeaturesStateCopy["TestAlpha"] = true
return expectedDefaultFeaturesStateCopy
}(),
expectedInternalEnabledFeatureState: map[Feature]bool{"TestAlpha": true},
},
{
name: "incorrect env var value gets ignored",
features: defaultTestFeatures,
envVariables: map[string]string{"KUBE_FEATURE_TestAlpha": "TrueFalse"},
expectedFeaturesState: expectedDefaultFeaturesState,
},
{
name: "empty env var value gets ignored",
features: defaultTestFeatures,
envVariables: map[string]string{"KUBE_FEATURE_TestAlpha": ""},
expectedFeaturesState: expectedDefaultFeaturesState,
},
{
name: "a feature LockToDefault wins",
features: map[Feature]FeatureSpec{
"TestAlpha": {
Default: true,
LockToDefault: true,
PreRelease: "Alpha",
},
},
envVariables: map[string]string{"KUBE_FEATURE_TestAlpha": "False"},
expectedFeaturesState: map[Feature]bool{"TestAlpha": true},
},
{
name: "setting a feature to LockToDefault changes the internal state",
features: map[Feature]FeatureSpec{
"TestAlpha": {
Default: true,
LockToDefault: true,
PreRelease: "Alpha",
},
},
envVariables: map[string]string{"KUBE_FEATURE_TestAlpha": "True"},
expectedFeaturesState: map[Feature]bool{"TestAlpha": true},
expectedInternalEnabledFeatureState: map[Feature]bool{"TestAlpha": true},
},
}
for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
for k, v := range scenario.envVariables {
t.Setenv(k, v)
}
target := newEnvVarFeatureGates(scenario.features)
for expectedFeature, expectedValue := range scenario.expectedFeaturesState {
actualValue := target.Enabled(expectedFeature)
require.Equal(t, actualValue, expectedValue, "expected feature=%v, to be=%v, not=%v", expectedFeature, expectedValue, actualValue)
}
enabledInternalMap := target.enabled.Load().(map[Feature]bool)
require.Len(t, enabledInternalMap, len(scenario.expectedInternalEnabledFeatureState))
for expectedFeature, expectedInternalPresence := range scenario.expectedInternalEnabledFeatureState {
featureInternalValue, featureSet := enabledInternalMap[expectedFeature]
require.Equal(t, expectedInternalPresence, featureSet, "feature %v present = %v, expected = %v", expectedFeature, featureSet, expectedInternalPresence)
expectedFeatureInternalValue := scenario.expectedFeaturesState[expectedFeature]
require.Equal(t, expectedFeatureInternalValue, featureInternalValue)
}
})
}
}
func TestEnvVarFeatureGatesEnabledPanic(t *testing.T) {
target := newEnvVarFeatureGates(nil)
require.PanicsWithError(t, fmt.Errorf("feature %q is not registered in FeatureGates %q", "UnknownFeature", target.callSiteName).Error(), func() { target.Enabled("UnknownFeature") })
}
func TestHasAlreadyReadEnvVar(t *testing.T) {
target := newEnvVarFeatureGates(nil)
require.False(t, target.hasAlreadyReadEnvVar())
_ = target.getEnabledMapFromEnvVar()
require.True(t, target.hasAlreadyReadEnvVar())
}

143
features/features.go Normal file
View File

@@ -0,0 +1,143 @@
/*
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 features
import (
"errors"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"sync/atomic"
)
// NOTE: types Feature, FeatureSpec, prerelease (and its values)
// were duplicated from the component-base repository
//
// for more information please refer to https://docs.google.com/document/d/1g9BGCRw-7ucUxO6OtCWbb3lfzUGA_uU9178wLdXAIfs
const (
// Values for PreRelease.
Alpha = prerelease("ALPHA")
Beta = prerelease("BETA")
GA = prerelease("")
// Deprecated
Deprecated = prerelease("DEPRECATED")
)
type prerelease string
type Feature string
type FeatureSpec struct {
// Default is the default enablement state for the feature
Default bool
// LockToDefault indicates that the feature is locked to its default and cannot be changed
LockToDefault bool
// PreRelease indicates the maturity level of the feature
PreRelease prerelease
}
// Gates indicates whether a given feature is enabled or not.
type Gates interface {
// Enabled returns true if the key is enabled.
Enabled(key Feature) bool
}
// Registry represents an external feature gates registry.
type Registry interface {
// Add adds existing feature gates to the provided registry.
//
// As of today, this method is used by AddFeaturesToExistingFeatureGates and
// ReplaceFeatureGates to take control of the features exposed by this library.
Add(map[Feature]FeatureSpec) error
}
// FeatureGates returns the feature gates exposed by this library.
//
// By default, only the default features gates will be returned.
// The default implementation allows controlling the features
// via environmental variables.
// For example, if you have a feature named "MyFeature"
// setting an environmental variable "KUBE_FEATURE_MyFeature"
// will allow you to configure the state of that feature.
//
// Please note that the actual set of the feature gates
// might be overwritten by calling ReplaceFeatureGates method.
func FeatureGates() Gates {
return featureGates.Load().(*featureGatesWrapper).Gates
}
// AddFeaturesToExistingFeatureGates adds the default feature gates to the provided registry.
// Usually this function is combined with ReplaceFeatureGates to take control of the
// features exposed by this library.
func AddFeaturesToExistingFeatureGates(registry Registry) error {
return registry.Add(defaultKubernetesFeatureGates)
}
// ReplaceFeatureGates overwrites the default implementation of the feature gates
// used by this library.
//
// Useful for binaries that would like to have full control of the features
// exposed by this library, such as allowing consumers of a binary
// to interact with the features via a command line flag.
//
// For example:
//
// // first, register client-go's features to your registry.
// clientgofeaturegate.AddFeaturesToExistingFeatureGates(utilfeature.DefaultMutableFeatureGate)
// // then replace client-go's feature gates implementation with your implementation
// clientgofeaturegate.ReplaceFeatureGates(utilfeature.DefaultMutableFeatureGate)
func ReplaceFeatureGates(newFeatureGates Gates) {
if replaceFeatureGatesWithWarningIndicator(newFeatureGates) {
utilruntime.HandleError(errors.New("the default feature gates implementation has already been used and now it's being overwritten. This might lead to unexpected behaviour. Check your initialization order"))
}
}
func replaceFeatureGatesWithWarningIndicator(newFeatureGates Gates) bool {
shouldProduceWarning := false
if defaultFeatureGates, ok := FeatureGates().(*envVarFeatureGates); ok {
if defaultFeatureGates.hasAlreadyReadEnvVar() {
shouldProduceWarning = true
}
}
wrappedFeatureGates := &featureGatesWrapper{newFeatureGates}
featureGates.Store(wrappedFeatureGates)
return shouldProduceWarning
}
func init() {
envVarGates := newEnvVarFeatureGates(defaultKubernetesFeatureGates)
wrappedFeatureGates := &featureGatesWrapper{envVarGates}
featureGates.Store(wrappedFeatureGates)
}
// featureGatesWrapper a thin wrapper to satisfy featureGates variable (atomic.Value).
// That is, all calls to Store for a given Value must use values of the same concrete type.
type featureGatesWrapper struct {
Gates
}
var (
// featureGates is a shared global FeatureGates.
//
// Top-level commands/options setup that needs to modify this feature gates
// should use AddFeaturesToExistingFeatureGates followed by ReplaceFeatureGates.
featureGates = &atomic.Value{}
)

40
features/features_test.go Normal file
View File

@@ -0,0 +1,40 @@
/*
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 features
import (
"testing"
"github.com/stretchr/testify/require"
)
// TestAddFeaturesToExistingFeatureGates ensures that
// the defaultKubernetesFeatureGates are added to a test feature gates registry.
func TestAddFeaturesToExistingFeatureGates(t *testing.T) {
fakeFeatureGates := &fakeRegistry{}
require.NoError(t, AddFeaturesToExistingFeatureGates(fakeFeatureGates))
require.Equal(t, defaultKubernetesFeatureGates, fakeFeatureGates.specs)
}
type fakeRegistry struct {
specs map[Feature]FeatureSpec
}
func (f *fakeRegistry) Add(specs map[Feature]FeatureSpec) error {
f.specs = specs
return nil
}

View File

@@ -0,0 +1,49 @@
/*
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 features
const (
// Every feature gate should add method here following this template:
//
// // owner: @username
// // alpha: v1.4
// MyFeature featuregate.Feature = "MyFeature"
//
// Feature gates should be listed in alphabetical, case-sensitive
// (upper before any lower case character) order. This reduces the risk
// of code conflicts because changes are more likely to be scattered
// across the file.
// owner: @p0lyn0mial
// beta: v1.30
//
// Allow the client to get a stream of individual items instead of chunking from the server.
//
// NOTE:
// The feature is disabled in Beta by default because
// it will only be turned on for selected control plane component(s).
WatchListClient Feature = "WatchListClient"
)
// defaultKubernetesFeatureGates consists of all known Kubernetes-specific feature keys.
//
// To add a new feature, define a key for it above and add it here.
// After registering with the binary, the features are, by default, controllable using environment variables.
// For more details, please see envVarFeatureGates implementation.
var defaultKubernetesFeatureGates = map[Feature]FeatureSpec{
WatchListClient: {Default: false, PreRelease: Beta},
}

View File

@@ -0,0 +1,79 @@
/*
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 testing
import (
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/require"
"k8s.io/client-go/features"
)
func TestDriveInitDefaultFeatureGates(t *testing.T) {
featureGates := features.FeatureGates()
assertFunctionPanicsWithMessage(t, func() { featureGates.Enabled("FakeFeatureGate") }, "features.FeatureGates().Enabled", fmt.Sprintf("feature %q is not registered in FeatureGate", "FakeFeatureGate"))
fakeFeatureGates := &alwaysEnabledFakeGates{}
require.True(t, fakeFeatureGates.Enabled("FakeFeatureGate"))
features.ReplaceFeatureGates(fakeFeatureGates)
featureGates = features.FeatureGates()
assertFeatureGatesType(t, featureGates)
require.True(t, featureGates.Enabled("FakeFeatureGate"))
}
type alwaysEnabledFakeGates struct{}
func (f *alwaysEnabledFakeGates) Enabled(features.Feature) bool {
return true
}
func assertFeatureGatesType(t *testing.T, fg features.Gates) {
_, ok := fg.(*alwaysEnabledFakeGates)
if !ok {
t.Fatalf("passed features.FeatureGates() is NOT of type *alwaysEnabledFakeGates, it is of type = %T", fg)
}
}
func assertFunctionPanicsWithMessage(t *testing.T, f func(), fName, errMessage string) {
didPanic, panicMessage := didFunctionPanic(f)
if !didPanic {
t.Fatalf("function %q did not panicked", fName)
}
panicError, ok := panicMessage.(error)
if !ok || !strings.Contains(panicError.Error(), errMessage) {
t.Fatalf("func %q should panic with error message:\t%#v\n\tPanic value:\t%#v\n", fName, errMessage, panicMessage)
}
}
func didFunctionPanic(f func()) (didPanic bool, panicMessage interface{}) {
didPanic = true
defer func() {
panicMessage = recover()
}()
f()
didPanic = false
return
}

24
go.mod
View File

@@ -2,7 +2,7 @@
module k8s.io/client-go
go 1.21.3
go 1.21
require (
github.com/evanphx/json-patch v4.12.0+incompatible
@@ -19,15 +19,15 @@ require (
github.com/peterbourgon/diskv v2.0.1+incompatible
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.4
golang.org/x/net v0.17.0
golang.org/x/net v0.19.0
golang.org/x/oauth2 v0.10.0
golang.org/x/term v0.13.0
golang.org/x/term v0.17.0
golang.org/x/time v0.3.0
google.golang.org/protobuf v1.31.0
k8s.io/api v0.0.0-20231104011324-cca653eefe74
k8s.io/apimachinery v0.0.0-20231104004456-12dc3f82eb47
k8s.io/klog/v2 v2.110.1
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00
k8s.io/api v0.30.0-alpha.3
k8s.io/apimachinery v0.30.0-alpha.3
k8s.io/klog/v2 v2.120.1
k8s.io/kube-openapi v0.0.0-20231113174909-778a5567bc1e
k8s.io/utils v0.0.0-20230726121419-3b25d923346b
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd
sigs.k8s.io/structured-merge-diff/v4 v4.4.1
@@ -37,7 +37,7 @@ require (
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/go-logr/logr v1.3.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
@@ -52,8 +52,8 @@ require (
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
@@ -61,6 +61,6 @@ require (
)
replace (
k8s.io/api => k8s.io/api v0.0.0-20231104011324-cca653eefe74
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20231104004456-12dc3f82eb47
k8s.io/api => k8s.io/api v0.30.0-alpha.3
k8s.io/apimachinery => k8s.io/apimachinery v0.30.0-alpha.3
)

48
go.sum
View File

@@ -8,8 +8,8 @@ github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxER
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
@@ -74,10 +74,10 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE=
github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -109,8 +109,8 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -119,23 +119,23 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -157,14 +157,14 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.0.0-20231104011324-cca653eefe74 h1:A/WZDLAcaGLXkFBbekgwUWKJBuRtPShuANcONbv3OrA=
k8s.io/api v0.0.0-20231104011324-cca653eefe74/go.mod h1:6Z3XP8HABddpcRWQa0OQUhKH7fQ9DkfDiAfwsZYoav8=
k8s.io/apimachinery v0.0.0-20231104004456-12dc3f82eb47 h1:wWtw59NclKzCKEt2E5e65INhGVzoUW0ll6X2AyDrsVU=
k8s.io/apimachinery v0.0.0-20231104004456-12dc3f82eb47/go.mod h1:yFk3nwBh/jXlkMvRKH7BKtX7saT1lRmmGV6Ru0cTSUA=
k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=
k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
k8s.io/api v0.30.0-alpha.3 h1:EcbaDi8WjoR8QdQS6LKd8oP0qjODWxfKdVj5U8WM1P0=
k8s.io/api v0.30.0-alpha.3/go.mod h1:gUziZ7QreMQgwigIm0O6q1xN4w2DPIs6PwP9Ha3c9dQ=
k8s.io/apimachinery v0.30.0-alpha.3 h1:9FoqT1Wc+48DJ+mYkbmZd3n4351u9YbGnQSPnVWUwWM=
k8s.io/apimachinery v0.30.0-alpha.3/go.mod h1:/862Kkwje5hhHGJWPKiaHuov2c6mw6uCXWikV9kOIP4=
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20231113174909-778a5567bc1e h1:snPmy96t93RredGRjKfMFt+gvxuVAncqSAyBveJtr4Q=
k8s.io/kube-openapi v0.0.0-20231113174909-778a5567bc1e/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=

View File

@@ -15,4 +15,4 @@ limitations under the License.
*/
// Package informers provides generated informers for Kubernetes APIs.
package informers
package informers // import "k8s.io/client-go/informers"

View File

@@ -16,4 +16,4 @@ limitations under the License.
// Package kubernetes holds packages which implement a clientset for Kubernetes
// APIs.
package kubernetes
package kubernetes // import "k8s.io/client-go/kubernetes"

View File

@@ -15,4 +15,4 @@ limitations under the License.
*/
// Package listers provides generated listers for Kubernetes APIs.
package listers
package listers // import "k8s.io/client-go/listers"

View File

@@ -191,7 +191,7 @@ func (c *client) Get(ctx context.Context, name string, opts metav1.GetOptions, s
}
obj, err := result.Get()
if runtime.IsNotRegisteredError(err) {
klog.V(5).Infof("Unable to retrieve PartialObjectMetadata: %#v", err)
klog.FromContext(ctx).V(5).Info("Could not retrieve PartialObjectMetadata", "err", err)
rawBytes, err := result.Raw()
if err != nil {
return nil, err
@@ -227,7 +227,7 @@ func (c *client) List(ctx context.Context, opts metav1.ListOptions) (*metav1.Par
}
obj, err := result.Get()
if runtime.IsNotRegisteredError(err) {
klog.V(5).Infof("Unable to retrieve PartialObjectMetadataList: %#v", err)
klog.FromContext(ctx).V(5).Info("Could not retrieve PartialObjectMetadataList", "err", err)
rawBytes, err := result.Raw()
if err != nil {
return nil, err

View File

@@ -32,6 +32,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/rest"
"k8s.io/klog/v2/ktesting"
)
func TestClient(t *testing.T) {
@@ -55,7 +56,7 @@ func TestClient(t *testing.T) {
testCases := []struct {
name string
handler func(t *testing.T, w http.ResponseWriter, req *http.Request)
want func(t *testing.T, client *Client)
want func(ctx context.Context, t *testing.T, client *Client)
}{
{
name: "GET is able to convert a JSON object to PartialObjectMetadata",
@@ -77,8 +78,8 @@ func TestClient(t *testing.T) {
},
})
},
want: func(t *testing.T, client *Client) {
obj, err := client.Resource(gvr).Namespace("ns").Get(context.TODO(), "name", metav1.GetOptions{})
want: func(ctx context.Context, t *testing.T, client *Client) {
obj, err := client.Resource(gvr).Namespace("ns").Get(ctx, "name", metav1.GetOptions{})
if err != nil {
t.Fatal(err)
}
@@ -125,8 +126,8 @@ func TestClient(t *testing.T) {
},
})
},
want: func(t *testing.T, client *Client) {
objs, err := client.Resource(gvr).Namespace("ns").List(context.TODO(), metav1.ListOptions{})
want: func(ctx context.Context, t *testing.T, client *Client) {
objs, err := client.Resource(gvr).Namespace("ns").List(ctx, metav1.ListOptions{})
if err != nil {
t.Fatal(err)
}
@@ -167,8 +168,8 @@ func TestClient(t *testing.T) {
},
})
},
want: func(t *testing.T, client *Client) {
obj, err := client.Resource(gvr).Namespace("ns").Get(context.TODO(), "name", metav1.GetOptions{})
want: func(ctx context.Context, t *testing.T, client *Client) {
obj, err := client.Resource(gvr).Namespace("ns").Get(ctx, "name", metav1.GetOptions{})
if err == nil || !runtime.IsMissingKind(err) {
t.Fatal(err)
}
@@ -196,8 +197,8 @@ func TestClient(t *testing.T) {
},
})
},
want: func(t *testing.T, client *Client) {
obj, err := client.Resource(gvr).Namespace("ns").Get(context.TODO(), "name", metav1.GetOptions{})
want: func(ctx context.Context, t *testing.T, client *Client) {
obj, err := client.Resource(gvr).Namespace("ns").Get(ctx, "name", metav1.GetOptions{})
if err == nil || !runtime.IsMissingVersion(err) {
t.Fatal(err)
}
@@ -224,8 +225,8 @@ func TestClient(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{},
})
},
want: func(t *testing.T, client *Client) {
obj, err := client.Resource(gvr).Namespace("ns").Get(context.TODO(), "name", metav1.GetOptions{})
want: func(ctx context.Context, t *testing.T, client *Client) {
obj, err := client.Resource(gvr).Namespace("ns").Get(ctx, "name", metav1.GetOptions{})
if err == nil || !strings.Contains(err.Error(), "object does not appear to match the ObjectMeta schema") {
t.Fatal(err)
}
@@ -254,8 +255,8 @@ func TestClient(t *testing.T) {
}
writeJSON(t, w, statusOK)
},
want: func(t *testing.T, client *Client) {
err := client.Resource(gvr).Namespace("ns").Delete(context.TODO(), "name", metav1.DeleteOptions{})
want: func(ctx context.Context, t *testing.T, client *Client) {
err := client.Resource(gvr).Namespace("ns").Delete(ctx, "name", metav1.DeleteOptions{})
if err != nil {
t.Fatal(err)
}
@@ -282,8 +283,8 @@ func TestClient(t *testing.T) {
writeJSON(t, w, statusOK)
},
want: func(t *testing.T, client *Client) {
err := client.Resource(gvr).Namespace("ns").DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{})
want: func(ctx context.Context, t *testing.T, client *Client) {
err := client.Resource(gvr).Namespace("ns").DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{})
if err != nil {
t.Fatal(err)
}
@@ -296,9 +297,10 @@ func TestClient(t *testing.T) {
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { tt.handler(t, w, req) }))
defer s.Close()
_, ctx := ktesting.NewTestContext(t)
cfg := ConfigFor(&rest.Config{Host: s.URL})
client := NewForConfigOrDie(cfg).(*Client)
tt.want(t, client)
tt.want(ctx, t, client)
})
}
}

View File

@@ -5235,7 +5235,7 @@
"description": "SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field."
},
"serviceAccount": {
"description": "DeprecatedServiceAccount is a depreciated alias for ServiceAccountName. Deprecated: Use serviceAccountName instead.",
"description": "DeprecatedServiceAccount is a deprecated alias for ServiceAccountName. Deprecated: Use serviceAccountName instead.",
"type": "string"
},
"serviceAccountName": {

View File

@@ -3677,7 +3677,7 @@
"description": "SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field."
},
"serviceAccount": {
"description": "DeprecatedServiceAccount is a depreciated alias for ServiceAccountName. Deprecated: Use serviceAccountName instead.",
"description": "DeprecatedServiceAccount is a deprecated alias for ServiceAccountName. Deprecated: Use serviceAccountName instead.",
"type": "string"
},
"serviceAccountName": {

View File

@@ -2851,7 +2851,7 @@
"description": "SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field."
},
"serviceAccount": {
"description": "DeprecatedServiceAccount is a depreciated alias for ServiceAccountName. Deprecated: Use serviceAccountName instead.",
"description": "DeprecatedServiceAccount is a deprecated alias for ServiceAccountName. Deprecated: Use serviceAccountName instead.",
"type": "string"
},
"serviceAccountName": {

View File

@@ -19,4 +19,4 @@ limitations under the License.
// It doesn't have any of its own types -- it's just necessary to
// get the expected behavior out of runtime.Scheme.ConvertToVersion
// and associated methods.
package appsint
package appsint // import "k8s.io/client-go/scale/scheme/appsint"

View File

@@ -19,4 +19,4 @@ limitations under the License.
// Package scheme contains a runtime.Scheme to be used for serializing
// and deserializing different versions of Scale, and for converting
// in between them.
package scheme
package scheme // import "k8s.io/client-go/scale/scheme"

View File

@@ -19,4 +19,4 @@ limitations under the License.
// It doesn't have any of its own types -- it's just necessary to
// get the expected behavior out of runtime.Scheme.ConvertToVersion
// and associated methods.
package extensionsint
package extensionsint // import "k8s.io/client-go/scale/scheme/extensionsint"

View File

@@ -50,8 +50,7 @@ type Indexer interface {
// GetIndexers return the indexers
GetIndexers() Indexers
// AddIndexers adds more indexers to this store. If you call this after you already have data
// in the store, the results are undefined.
// AddIndexers adds more indexers to this store. This supports adding indexes after the store already has items.
AddIndexers(newIndexers Indexers) error
}

View File

@@ -43,6 +43,7 @@ import (
"k8s.io/klog/v2"
"k8s.io/utils/clock"
"k8s.io/utils/pointer"
"k8s.io/utils/ptr"
"k8s.io/utils/trace"
)
@@ -107,7 +108,9 @@ type Reflector struct {
// might result in an increased memory consumption of the APIServer.
//
// See https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/3157-watch-list#design-details
UseWatchList bool
//
// TODO(#115478): Consider making reflector.UseWatchList a private field. Since we implemented "api streaming" on the etcd storage layer it should work.
UseWatchList *bool
}
// ResourceVersionUpdater is an interface that allows store implementation to
@@ -237,8 +240,12 @@ func NewReflectorWithOptions(lw ListerWatcher, expectedType interface{}, store S
r.expectedGVK = getExpectedGVKFromObject(expectedType)
}
if s := os.Getenv("ENABLE_CLIENT_GO_WATCH_LIST_ALPHA"); len(s) > 0 {
r.UseWatchList = true
// don't overwrite UseWatchList if already set
// because the higher layers (e.g. storage/cacher) disabled it on purpose
if r.UseWatchList == nil {
if s := os.Getenv("ENABLE_CLIENT_GO_WATCH_LIST_ALPHA"); len(s) > 0 {
r.UseWatchList = ptr.To(true)
}
}
return r
@@ -325,9 +332,10 @@ func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
klog.V(3).Infof("Listing and watching %v from %s", r.typeDescription, r.name)
var err error
var w watch.Interface
fallbackToList := !r.UseWatchList
useWatchList := ptr.Deref(r.UseWatchList, false)
fallbackToList := !useWatchList
if r.UseWatchList {
if useWatchList {
w, err = r.watchList(stopCh)
if w == nil && err == nil {
// stopCh was closed

View File

@@ -33,6 +33,7 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/utils/pointer"
"k8s.io/utils/ptr"
)
func TestWatchList(t *testing.T) {
@@ -415,7 +416,7 @@ func TestWatchList(t *testing.T) {
listWatcher.customListResponse = scenario.podList
listWatcher.closeAfterListRequests = scenario.closeAfterListRequests
if scenario.disableUseWatchList {
reflector.UseWatchList = false
reflector.UseWatchList = ptr.To(false)
}
err := reflector.ListAndWatch(stopCh)
@@ -505,7 +506,7 @@ func testData() (*fakeListWatcher, Store, *Reflector, chan struct{}) {
},
}
r := NewReflector(lw, &v1.Pod{}, s, 0)
r.UseWatchList = true
r.UseWatchList = ptr.To(true)
return lw, s, r, stopCh
}

View File

@@ -540,8 +540,8 @@ func (s *sharedIndexInformer) AddIndexers(indexers Indexers) error {
s.startedLock.Lock()
defer s.startedLock.Unlock()
if s.started {
return fmt.Errorf("informer has already started")
if s.stopped {
return fmt.Errorf("indexer was not added because it has stopped already")
}
return s.indexer.AddIndexers(indexers)

View File

@@ -26,6 +26,9 @@ import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -117,6 +120,81 @@ func isRegistered(i SharedInformer, h ResourceEventHandlerRegistration) bool {
return s.processor.getListener(h) != nil
}
func TestIndexer(t *testing.T) {
assert := assert.New(t)
// source simulates an apiserver object endpoint.
source := fcache.NewFakeControllerSource()
pod1 := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod1", Labels: map[string]string{"a": "a-val", "b": "b-val1"}}}
pod2 := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod2", Labels: map[string]string{"b": "b-val2"}}}
pod3 := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod3", Labels: map[string]string{"a": "a-val2"}}}
source.Add(pod1)
source.Add(pod2)
// create the shared informer and resync every 1s
informer := NewSharedInformer(source, &v1.Pod{}, 1*time.Second).(*sharedIndexInformer)
err := informer.AddIndexers(map[string]IndexFunc{
"labels": func(obj interface{}) ([]string, error) {
res := []string{}
for k := range obj.(*v1.Pod).Labels {
res = append(res, k)
}
return res, nil
},
})
if err != nil {
t.Fatal(err)
}
stop := make(chan struct{})
defer close(stop)
go informer.Run(stop)
WaitForCacheSync(stop, informer.HasSynced)
cmpOps := cmpopts.SortSlices(func(a, b any) bool {
return a.(*v1.Pod).Name < b.(*v1.Pod).Name
})
// We should be able to lookup by index
res, err := informer.GetIndexer().ByIndex("labels", "a")
assert.NoError(err)
if diff := cmp.Diff([]any{pod1}, res); diff != "" {
t.Fatal(diff)
}
// Adding an item later is fine as well
source.Add(pod3)
// Event is async, need to poll
assert.Eventually(func() bool {
res, _ := informer.GetIndexer().ByIndex("labels", "a")
return cmp.Diff([]any{pod1, pod3}, res, cmpOps) == ""
}, time.Second*3, time.Millisecond)
// Adding an index later is also fine
err = informer.AddIndexers(map[string]IndexFunc{
"labels-again": func(obj interface{}) ([]string, error) {
res := []string{}
for k := range obj.(*v1.Pod).Labels {
res = append(res, k)
}
return res, nil
},
})
assert.NoError(err)
// Should be immediately available
res, err = informer.GetIndexer().ByIndex("labels-again", "a")
assert.NoError(err)
if diff := cmp.Diff([]any{pod1, pod3}, res, cmpOps); diff != "" {
t.Fatal(diff)
}
if got := informer.GetIndexer().ListIndexFuncValues("labels"); !sets.New(got...).Equal(sets.New("a", "b")) {
t.Fatalf("got %v", got)
}
if got := informer.GetIndexer().ListIndexFuncValues("labels-again"); !sets.New(got...).Equal(sets.New("a", "b")) {
t.Fatalf("got %v", got)
}
}
func TestListenerResyncPeriods(t *testing.T) {
// source simulates an apiserver object endpoint.
source := fcache.NewFakeControllerSource()

View File

@@ -52,8 +52,7 @@ type ThreadSafeStore interface {
ByIndex(indexName, indexedValue string) ([]interface{}, error)
GetIndexers() Indexers
// AddIndexers adds more indexers to this store. If you call this after you already have data
// in the store, the results are undefined.
// AddIndexers adds more indexers to this store. This supports adding indexes after the store already has items.
AddIndexers(newIndexers Indexers) error
// Resync is a no-op and is deprecated
Resync() error
@@ -135,50 +134,66 @@ func (i *storeIndex) addIndexers(newIndexers Indexers) error {
return nil
}
// updateSingleIndex modifies the objects location in the named index:
// - for create you must provide only the newObj
// - for update you must provide both the oldObj and the newObj
// - for delete you must provide only the oldObj
// updateSingleIndex must be called from a function that already has a lock on the cache
func (i *storeIndex) updateSingleIndex(name string, oldObj interface{}, newObj interface{}, key string) {
var oldIndexValues, indexValues []string
indexFunc, ok := i.indexers[name]
if !ok {
// Should never happen. Caller is responsible for ensuring this exists, and should call with lock
// held to avoid any races.
panic(fmt.Errorf("indexer %q does not exist", name))
}
if oldObj != nil {
var err error
oldIndexValues, err = indexFunc(oldObj)
if err != nil {
panic(fmt.Errorf("unable to calculate an index entry for key %q on index %q: %v", key, name, err))
}
} else {
oldIndexValues = oldIndexValues[:0]
}
if newObj != nil {
var err error
indexValues, err = indexFunc(newObj)
if err != nil {
panic(fmt.Errorf("unable to calculate an index entry for key %q on index %q: %v", key, name, err))
}
} else {
indexValues = indexValues[:0]
}
index := i.indices[name]
if index == nil {
index = Index{}
i.indices[name] = index
}
if len(indexValues) == 1 && len(oldIndexValues) == 1 && indexValues[0] == oldIndexValues[0] {
// We optimize for the most common case where indexFunc returns a single value which has not been changed
return
}
for _, value := range oldIndexValues {
i.deleteKeyFromIndex(key, value, index)
}
for _, value := range indexValues {
i.addKeyToIndex(key, value, index)
}
}
// updateIndices modifies the objects location in the managed indexes:
// - for create you must provide only the newObj
// - for update you must provide both the oldObj and the newObj
// - for delete you must provide only the oldObj
// updateIndices must be called from a function that already has a lock on the cache
func (i *storeIndex) updateIndices(oldObj interface{}, newObj interface{}, key string) {
var oldIndexValues, indexValues []string
var err error
for name, indexFunc := range i.indexers {
if oldObj != nil {
oldIndexValues, err = indexFunc(oldObj)
} else {
oldIndexValues = oldIndexValues[:0]
}
if err != nil {
panic(fmt.Errorf("unable to calculate an index entry for key %q on index %q: %v", key, name, err))
}
if newObj != nil {
indexValues, err = indexFunc(newObj)
} else {
indexValues = indexValues[:0]
}
if err != nil {
panic(fmt.Errorf("unable to calculate an index entry for key %q on index %q: %v", key, name, err))
}
index := i.indices[name]
if index == nil {
index = Index{}
i.indices[name] = index
}
if len(indexValues) == 1 && len(oldIndexValues) == 1 && indexValues[0] == oldIndexValues[0] {
// We optimize for the most common case where indexFunc returns a single value which has not been changed
continue
}
for _, value := range oldIndexValues {
i.deleteKeyFromIndex(key, value, index)
}
for _, value := range indexValues {
i.addKeyToIndex(key, value, index)
}
for name := range i.indexers {
i.updateSingleIndex(name, oldObj, newObj, key)
}
}
@@ -339,11 +354,18 @@ func (c *threadSafeMap) AddIndexers(newIndexers Indexers) error {
c.lock.Lock()
defer c.lock.Unlock()
if len(c.items) > 0 {
return fmt.Errorf("cannot add indexers to running index")
if err := c.index.addIndexers(newIndexers); err != nil {
return err
}
return c.index.addIndexers(newIndexers)
// If there are already items, index them
for key, item := range c.items {
for name := range newIndexers {
c.index.updateSingleIndex(name, nil, item, key)
}
}
return nil
}
func (c *threadSafeMap) Resync() error {

View File

@@ -16,4 +16,4 @@ limitations under the License.
// +k8s:deepcopy-gen=package
package api
package api // import "k8s.io/client-go/tools/clientcmd/api"

View File

@@ -18,4 +18,4 @@ limitations under the License.
// +k8s:deepcopy-gen=package
// +k8s:defaulter-gen=Kind
package v1
package v1 // import "k8s.io/client-go/tools/clientcmd/api/v1"

View File

@@ -72,6 +72,13 @@ type ClientConfig interface {
ConfigAccess() ConfigAccess
}
// OverridingClientConfig is used to enable overrriding the raw KubeConfig
type OverridingClientConfig interface {
ClientConfig
// MergedRawConfig return the RawConfig merged with all overrides.
MergedRawConfig() (clientcmdapi.Config, error)
}
type PersistAuthProviderConfigForUser func(user string) restclient.AuthProviderConfigPersister
type promptedCredentials struct {
@@ -91,22 +98,22 @@ type DirectClientConfig struct {
}
// NewDefaultClientConfig creates a DirectClientConfig using the config.CurrentContext as the context name
func NewDefaultClientConfig(config clientcmdapi.Config, overrides *ConfigOverrides) ClientConfig {
func NewDefaultClientConfig(config clientcmdapi.Config, overrides *ConfigOverrides) OverridingClientConfig {
return &DirectClientConfig{config, config.CurrentContext, overrides, nil, NewDefaultClientConfigLoadingRules(), promptedCredentials{}}
}
// NewNonInteractiveClientConfig creates a DirectClientConfig using the passed context name and does not have a fallback reader for auth information
func NewNonInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, configAccess ConfigAccess) ClientConfig {
func NewNonInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, configAccess ConfigAccess) OverridingClientConfig {
return &DirectClientConfig{config, contextName, overrides, nil, configAccess, promptedCredentials{}}
}
// NewInteractiveClientConfig creates a DirectClientConfig using the passed context name and a reader in case auth information is not provided via files or flags
func NewInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, fallbackReader io.Reader, configAccess ConfigAccess) ClientConfig {
func NewInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, fallbackReader io.Reader, configAccess ConfigAccess) OverridingClientConfig {
return &DirectClientConfig{config, contextName, overrides, fallbackReader, configAccess, promptedCredentials{}}
}
// NewClientConfigFromBytes takes your kubeconfig and gives you back a ClientConfig
func NewClientConfigFromBytes(configBytes []byte) (ClientConfig, error) {
func NewClientConfigFromBytes(configBytes []byte) (OverridingClientConfig, error) {
config, err := Load(configBytes)
if err != nil {
return nil, err
@@ -129,6 +136,40 @@ func (config *DirectClientConfig) RawConfig() (clientcmdapi.Config, error) {
return config.config, nil
}
// MergedRawConfig returns the raw kube config merged with the overrides
func (config *DirectClientConfig) MergedRawConfig() (clientcmdapi.Config, error) {
if err := config.ConfirmUsable(); err != nil {
return clientcmdapi.Config{}, err
}
merged := config.config.DeepCopy()
// set the AuthInfo merged with overrides in the merged config
mergedAuthInfo, err := config.getAuthInfo()
if err != nil {
return clientcmdapi.Config{}, err
}
mergedAuthInfoName, _ := config.getAuthInfoName()
merged.AuthInfos[mergedAuthInfoName] = &mergedAuthInfo
// set the Context merged with overrides in the merged config
mergedContext, err := config.getContext()
if err != nil {
return clientcmdapi.Config{}, err
}
mergedContextName, _ := config.getContextName()
merged.Contexts[mergedContextName] = &mergedContext
merged.CurrentContext = mergedContextName
// set the Cluster merged with overrides in the merged config
configClusterInfo, err := config.getCluster()
if err != nil {
return clientcmdapi.Config{}, err
}
configClusterName, _ := config.getClusterName()
merged.Clusters[configClusterName] = &configClusterInfo
return *merged, nil
}
// ClientConfig implements ClientConfig
func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
// check that getAuthInfo, getContext, and getCluster do not return an error.

View File

@@ -1013,3 +1013,68 @@ func TestCleanANSIEscapeCodes(t *testing.T) {
})
}
}
func TestMergeRawConfigDoOverride(t *testing.T) {
const (
server = "https://anything.com:8080"
token = "the-token"
modifiedServer = "http://localhost:8081"
modifiedToken = "modified-token"
)
config := createValidTestConfig()
// add another context which to modify with overrides
config.Clusters["modify"] = &clientcmdapi.Cluster{
Server: server,
}
config.AuthInfos["modify"] = &clientcmdapi.AuthInfo{
Token: token,
}
config.Contexts["modify"] = &clientcmdapi.Context{
Cluster: "modify",
AuthInfo: "modify",
Namespace: "modify",
}
// create overrides for the modify context
overrides := &ConfigOverrides{
ClusterInfo: clientcmdapi.Cluster{
Server: modifiedServer,
},
Context: clientcmdapi.Context{
Namespace: "foobar",
Cluster: "modify",
AuthInfo: "modify",
},
AuthInfo: clientcmdapi.AuthInfo{
Token: modifiedToken,
},
CurrentContext: "modify",
}
cut := NewDefaultClientConfig(*config, overrides)
act, err := cut.MergedRawConfig()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// ensure overrides were applied to "modify"
actContext := act.CurrentContext
if actContext != "modify" {
t.Errorf("Expected context %v, got %v", "modify", actContext)
}
if act.Clusters[actContext].Server != "http://localhost:8081" {
t.Errorf("Expected server %v, got %v", "http://localhost:8081", act.Clusters[actContext].Server)
}
if act.Contexts[actContext].Namespace != "foobar" {
t.Errorf("Expected namespace %v, got %v", "foobar", act.Contexts[actContext].Namespace)
}
// ensure context "clean" was not touched
if act.Clusters["clean"].Server != config.Clusters["clean"].Server {
t.Errorf("Expected server %v, got %v", config.Clusters["clean"].Server, act.Clusters["clean"].Server)
}
if act.Contexts["clean"].Namespace != config.Contexts["clean"].Namespace {
t.Errorf("Expected namespace %v, got %v", config.Contexts["clean"].Namespace, act.Contexts["clean"].Namespace)
}
}

View File

@@ -404,7 +404,15 @@ type eventBroadcasterAdapterImpl struct {
// NewEventBroadcasterAdapter creates a wrapper around new and legacy broadcasters to simplify
// migration of individual components to the new Event API.
//
//logcheck:context // NewEventBroadcasterAdapterWithContext should be used instead because record.NewBroadcaster is called and works better when a context is supplied (contextual logging, cancellation).
func NewEventBroadcasterAdapter(client clientset.Interface) EventBroadcasterAdapter {
return NewEventBroadcasterAdapterWithContext(context.Background(), client)
}
// NewEventBroadcasterAdapterWithContext creates a wrapper around new and legacy broadcasters to simplify
// migration of individual components to the new Event API.
func NewEventBroadcasterAdapterWithContext(ctx context.Context, client clientset.Interface) EventBroadcasterAdapter {
eventClient := &eventBroadcasterAdapterImpl{}
if _, err := client.Discovery().ServerResourcesForGroupVersion(eventsv1.SchemeGroupVersion.String()); err == nil {
eventClient.eventsv1Client = client.EventsV1()
@@ -414,7 +422,7 @@ func NewEventBroadcasterAdapter(client clientset.Interface) EventBroadcasterAdap
// we create it unconditionally because its overhead is minor and will simplify using usage
// patterns of this library in all components.
eventClient.coreClient = client.CoreV1()
eventClient.coreBroadcaster = record.NewBroadcaster()
eventClient.coreBroadcaster = record.NewBroadcaster(record.WithContext(ctx))
return eventClient
}

View File

@@ -325,7 +325,22 @@ func (le *LeaderElector) tryAcquireOrRenew(ctx context.Context) bool {
AcquireTime: now,
}
// 1. obtain or create the ElectionRecord
// 1. fast path for the leader to update optimistically assuming that the record observed
// last time is the current version.
if le.IsLeader() && le.isLeaseValid(now.Time) {
oldObservedRecord := le.getObservedRecord()
leaderElectionRecord.AcquireTime = oldObservedRecord.AcquireTime
leaderElectionRecord.LeaderTransitions = oldObservedRecord.LeaderTransitions
err := le.config.Lock.Update(ctx, leaderElectionRecord)
if err == nil {
le.setObservedRecord(&leaderElectionRecord)
return true
}
klog.Errorf("Failed to update lock optimitically: %v, falling back to slow path", err)
}
// 2. obtain or create the ElectionRecord
oldLeaderElectionRecord, oldLeaderElectionRawRecord, err := le.config.Lock.Get(ctx)
if err != nil {
if !errors.IsNotFound(err) {
@@ -342,24 +357,23 @@ func (le *LeaderElector) tryAcquireOrRenew(ctx context.Context) bool {
return true
}
// 2. Record obtained, check the Identity & Time
// 3. Record obtained, check the Identity & Time
if !bytes.Equal(le.observedRawRecord, oldLeaderElectionRawRecord) {
le.setObservedRecord(oldLeaderElectionRecord)
le.observedRawRecord = oldLeaderElectionRawRecord
}
if len(oldLeaderElectionRecord.HolderIdentity) > 0 &&
le.observedTime.Add(time.Second*time.Duration(oldLeaderElectionRecord.LeaseDurationSeconds)).After(now.Time) &&
!le.IsLeader() {
if len(oldLeaderElectionRecord.HolderIdentity) > 0 && le.isLeaseValid(now.Time) && !le.IsLeader() {
klog.V(4).Infof("lock is held by %v and has not yet expired", oldLeaderElectionRecord.HolderIdentity)
return false
}
// 3. We're going to try to update. The leaderElectionRecord is set to it's default
// 4. We're going to try to update. The leaderElectionRecord is set to it's default
// here. Let's correct it before updating.
if le.IsLeader() {
leaderElectionRecord.AcquireTime = oldLeaderElectionRecord.AcquireTime
leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions
le.metrics.slowpathExercised(le.config.Name)
} else {
leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions + 1
}
@@ -400,6 +414,10 @@ func (le *LeaderElector) Check(maxTolerableExpiredLease time.Duration) error {
return nil
}
func (le *LeaderElector) isLeaseValid(now time.Time) bool {
return le.observedTime.Add(time.Second * time.Duration(le.getObservedRecord().LeaseDurationSeconds)).After(now)
}
// setObservedRecord will set a new observedRecord and update observedTime to the current time.
// Protect critical sections with lock.
func (le *LeaderElector) setObservedRecord(observedRecord *rl.LeaderElectionRecord) {

View File

@@ -315,6 +315,7 @@ func testTryAcquireOrRenew(t *testing.T, objectType string) {
observedRawRecord: observedRawRecord,
observedTime: test.observedTime,
clock: clock,
metrics: globalMetricsFactory.newLeaderMetrics(),
}
if test.expectSuccess != le.tryAcquireOrRenew(context.Background()) {
if test.retryAfter != 0 {
@@ -491,6 +492,7 @@ func testReleaseLease(t *testing.T, objectType string) {
observedRawRecord: observedRawRecord,
observedTime: test.observedTime,
clock: clock.RealClock{},
metrics: globalMetricsFactory.newLeaderMetrics(),
}
if !le.tryAcquireOrRenew(context.Background()) {
t.Errorf("unexpected result of tryAcquireOrRenew: [succeeded=%v]", true)
@@ -610,14 +612,18 @@ func testReleaseOnCancellation(t *testing.T, objectType string) {
objectType: objectType,
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
updates++
// Skip initial two fast path renews
if updates%2 == 1 && updates < 5 {
return true, nil, context.Canceled
}
// Second update (first renew) should return our canceled error
// FakeClient doesn't do anything with the context so we're doing this ourselves
if updates == 2 {
if updates == 4 {
close(onRenewCalled)
<-onRenewResume
return true, nil, context.Canceled
} else if updates == 3 {
} else if updates == 5 {
// We update the lock after the cancellation to release it
// This wg is to avoid the data race on lockObj
defer wg.Done()
@@ -668,8 +674,12 @@ func testReleaseOnCancellation(t *testing.T, objectType string) {
objectType: objectType,
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
updates++
// Always skip fast path renew
if updates%2 == 1 {
return true, nil, context.Canceled
}
// Second update (first renew) should release the lock
if updates == 2 {
if updates == 4 {
// We update the lock after the cancellation to release it
// This wg is to avoid the data race on lockObj
defer wg.Done()
@@ -813,3 +823,156 @@ func assertEqualEvents(t *testing.T, expected []string, actual <-chan string) {
}
}
}
func TestFastPathLeaderElection(t *testing.T) {
objectType := "leases"
var (
lockObj runtime.Object
updates int
lockOps []string
cancelFunc func()
)
resetVars := func() {
lockObj = nil
updates = 0
lockOps = []string{}
cancelFunc = nil
}
lec := LeaderElectionConfig{
LeaseDuration: 15 * time.Second,
RenewDeadline: 2 * time.Second,
RetryPeriod: 1 * time.Second,
Callbacks: LeaderCallbacks{
OnNewLeader: func(identity string) {},
OnStoppedLeading: func() {},
OnStartedLeading: func(context.Context) {
},
},
}
tests := []struct {
name string
reactors []Reactor
expectedLockOps []string
}{
{
name: "Exercise fast path after lock acquired",
reactors: []Reactor{
{
verb: "get",
objectType: objectType,
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
lockOps = append(lockOps, "get")
if lockObj != nil {
return true, lockObj, nil
}
return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName())
},
},
{
verb: "create",
objectType: objectType,
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
lockOps = append(lockOps, "create")
lockObj = action.(fakeclient.CreateAction).GetObject()
return true, lockObj, nil
},
},
{
verb: "update",
objectType: objectType,
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
updates++
lockOps = append(lockOps, "update")
if updates == 2 {
cancelFunc()
}
lockObj = action.(fakeclient.UpdateAction).GetObject()
return true, lockObj, nil
},
},
},
expectedLockOps: []string{"get", "create", "update", "update"},
},
{
name: "Fallback to slow path after fast path fails",
reactors: []Reactor{
{
verb: "get",
objectType: objectType,
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
lockOps = append(lockOps, "get")
if lockObj != nil {
return true, lockObj, nil
}
return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName())
},
},
{
verb: "create",
objectType: objectType,
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
lockOps = append(lockOps, "create")
lockObj = action.(fakeclient.CreateAction).GetObject()
return true, lockObj, nil
},
},
{
verb: "update",
objectType: objectType,
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
updates++
lockOps = append(lockOps, "update")
switch updates {
case 2:
return true, nil, errors.NewConflict(action.(fakeclient.UpdateAction).GetResource().GroupResource(), "fake conflict", nil)
case 4:
cancelFunc()
}
lockObj = action.(fakeclient.UpdateAction).GetObject()
return true, lockObj, nil
},
},
},
expectedLockOps: []string{"get", "create", "update", "update", "get", "update", "update"},
},
}
for i := range tests {
test := &tests[i]
t.Run(test.name, func(t *testing.T) {
resetVars()
recorder := record.NewFakeRecorder(100)
resourceLockConfig := rl.ResourceLockConfig{
Identity: "baz",
EventRecorder: recorder,
}
c := &fake.Clientset{}
for _, reactor := range test.reactors {
c.AddReactor(reactor.verb, objectType, reactor.reaction)
}
c.AddReactor("*", "*", func(action fakeclient.Action) (bool, runtime.Object, error) {
t.Errorf("unreachable action. testclient called too many times: %+v", action)
return true, nil, fmt.Errorf("unreachable action")
})
lock, err := rl.New("leases", "foo", "bar", c.CoreV1(), c.CoordinationV1(), resourceLockConfig)
if err != nil {
t.Fatal("resourcelock.New() = ", err)
}
lec.Lock = lock
elector, err := NewLeaderElector(lec)
if err != nil {
t.Fatal("Failed to create leader elector: ", err)
}
ctx, cancel := context.WithCancel(context.Background())
cancelFunc = cancel
elector.Run(ctx)
assert.Equal(t, test.expectedLockOps, lockOps, "Expected lock ops %q, got %q", test.expectedLockOps, lockOps)
})
}
}

View File

@@ -26,24 +26,26 @@ import (
type leaderMetricsAdapter interface {
leaderOn(name string)
leaderOff(name string)
slowpathExercised(name string)
}
// GaugeMetric represents a single numerical value that can arbitrarily go up
// and down.
type SwitchMetric interface {
// LeaderMetric instruments metrics used in leader election.
type LeaderMetric interface {
On(name string)
Off(name string)
SlowpathExercised(name string)
}
type noopMetric struct{}
func (noopMetric) On(name string) {}
func (noopMetric) Off(name string) {}
func (noopMetric) On(name string) {}
func (noopMetric) Off(name string) {}
func (noopMetric) SlowpathExercised(name string) {}
// defaultLeaderMetrics expects the caller to lock before setting any metrics.
type defaultLeaderMetrics struct {
// leader's value indicates if the current process is the owner of name lease
leader SwitchMetric
leader LeaderMetric
}
func (m *defaultLeaderMetrics) leaderOn(name string) {
@@ -60,19 +62,27 @@ func (m *defaultLeaderMetrics) leaderOff(name string) {
m.leader.Off(name)
}
func (m *defaultLeaderMetrics) slowpathExercised(name string) {
if m == nil {
return
}
m.leader.SlowpathExercised(name)
}
type noMetrics struct{}
func (noMetrics) leaderOn(name string) {}
func (noMetrics) leaderOff(name string) {}
func (noMetrics) leaderOn(name string) {}
func (noMetrics) leaderOff(name string) {}
func (noMetrics) slowpathExercised(name string) {}
// MetricsProvider generates various metrics used by the leader election.
type MetricsProvider interface {
NewLeaderMetric() SwitchMetric
NewLeaderMetric() LeaderMetric
}
type noopMetricsProvider struct{}
func (_ noopMetricsProvider) NewLeaderMetric() SwitchMetric {
func (noopMetricsProvider) NewLeaderMetric() LeaderMetric {
return noopMetric{}
}

View File

@@ -198,16 +198,29 @@ func NewBroadcaster(opts ...BroadcasterOption) EventBroadcaster {
ctx := c.Context
if ctx == nil {
ctx = context.Background()
} else {
}
// The are two scenarios where it makes no sense to wait for context cancelation:
// - The context was nil.
// - The context was context.Background() to begin with.
//
// Both cases get checked here.
haveCtxCancelation := ctx.Done() == nil
eventBroadcaster.cancelationCtx, eventBroadcaster.cancel = context.WithCancel(ctx)
if haveCtxCancelation {
// Calling Shutdown is not required when a context was provided:
// when the context is canceled, this goroutine will shut down
// the broadcaster.
//
// If Shutdown is called first, then this goroutine will
// also stop.
go func() {
<-ctx.Done()
<-eventBroadcaster.cancelationCtx.Done()
eventBroadcaster.Broadcaster.Shutdown()
}()
}
eventBroadcaster.cancelationCtx, eventBroadcaster.cancel = context.WithCancel(ctx)
return eventBroadcaster
}

View File

@@ -23,7 +23,6 @@ import (
"k8s.io/utils/clock"
testingclock "k8s.io/utils/clock/testing"
"k8s.io/utils/integer"
)
type backoffEntry struct {
@@ -100,7 +99,7 @@ func (p *Backoff) Next(id string, eventTime time.Time) {
} else {
delay := entry.backoff * 2 // exponential
delay += p.jitter(entry.backoff) // add some jitter to the delay
entry.backoff = time.Duration(integer.Int64Min(int64(delay), int64(p.maxDuration)))
entry.backoff = min(delay, p.maxDuration)
}
entry.lastUpdate = p.Clock.Now()
}