mirror of
https://github.com/kubernetes/client-go.git
synced 2026-06-15 06:20:08 +00:00
Compare commits
86 Commits
v0.18.0-al
...
v0.17.6-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b330d7ca3 | ||
|
|
585dacffb2 | ||
|
|
07e69aceac | ||
|
|
9ffc329553 | ||
|
|
7e5a0c583e | ||
|
|
2a689211e5 | ||
|
|
fef9ee0446 | ||
|
|
ba11e8f3d1 | ||
|
|
516a933b48 | ||
|
|
19522ff288 | ||
|
|
0cdd283dfd | ||
|
|
5ee463b7af | ||
|
|
b5eac8e744 | ||
|
|
7985654fe8 | ||
|
|
fa41b0c76b | ||
|
|
9f2f78dfcb | ||
|
|
27494d4fc8 | ||
|
|
62a43cf1f4 | ||
|
|
1b3201d12d | ||
|
|
61c8f156a0 | ||
|
|
6e35722da5 | ||
|
|
e3970fe37d | ||
|
|
3fe849aec4 | ||
|
|
3a262fe58a | ||
|
|
98f2fa70a1 | ||
|
|
e7b5e74ed4 | ||
|
|
d6c3975d42 | ||
|
|
cb7acd771d | ||
|
|
a4c677cde0 | ||
|
|
ce851c0bab | ||
|
|
e6529cdf92 | ||
|
|
e18bdbc334 | ||
|
|
f3cdcaf970 | ||
|
|
6e9b2dbbe2 | ||
|
|
7bd04c470b | ||
|
|
abd95e9c0a | ||
|
|
e5778855b5 | ||
|
|
eded95fdfd | ||
|
|
46af7b589b | ||
|
|
b761009965 | ||
|
|
a56e581315 | ||
|
|
bc842b510e | ||
|
|
83d8f38ea7 | ||
|
|
41d0b92a78 | ||
|
|
357aaec420 | ||
|
|
fed41f524f | ||
|
|
d78350395c | ||
|
|
af7d282afb | ||
|
|
7100912c84 | ||
|
|
9155fb5a6d | ||
|
|
43649d11a3 | ||
|
|
c7ba5618fd | ||
|
|
f1f7f7d673 | ||
|
|
fcde0a10c8 | ||
|
|
8d80aca827 | ||
|
|
14c92e3dc9 | ||
|
|
4ad43eb493 | ||
|
|
902f3aa269 | ||
|
|
c22145c02d | ||
|
|
4fb1ca18ca | ||
|
|
e706f8232c | ||
|
|
7e3bd58445 | ||
|
|
e0e2ffb8d0 | ||
|
|
5fc7a9e1ae | ||
|
|
eaf5bb0b36 | ||
|
|
86bac3ac1a | ||
|
|
00967f30df | ||
|
|
b132d4e8c2 | ||
|
|
100eed2f5c | ||
|
|
737b0684f7 | ||
|
|
7579be31b1 | ||
|
|
1e021533f5 | ||
|
|
60db0a7b4e | ||
|
|
d26f3bbd8a | ||
|
|
20cbac8786 | ||
|
|
7727ca7e78 | ||
|
|
15d0792143 | ||
|
|
64d0d9280f | ||
|
|
e58f47ad45 | ||
|
|
dc0ddc68f2 | ||
|
|
678d0436c4 | ||
|
|
4b3ee58116 | ||
|
|
f7396e6e2b | ||
|
|
1706989ec7 | ||
|
|
2225ff19d9 | ||
|
|
346cddd7c2 |
@@ -1,6 +1,8 @@
|
||||
# Contributing guidelines
|
||||
|
||||
Do not open pull requests directly against kubernetes/client-go repository (except for README.md); they will be ignored. Instead, please open pull requests and issues against [kubernetes/kubernetes](https://git.k8s.io/kubernetes/). Please follow the same [contributing guide](https://git.k8s.io/kubernetes/CONTRIBUTING.md) you would follow for any other pull request made to kubernetes/kubernetes.
|
||||
Do not open pull requests directly against this repository. They will be ignored. Instead, please open pull requests against [kubernetes/kubernetes](https://git.k8s.io/kubernetes/).
|
||||
The exception is changes to the `README.md` itself.
|
||||
Please follow the same [contributing guide](https://git.k8s.io/kubernetes/CONTRIBUTING.md) you would follow for any other pull request made to kubernetes/kubernetes.
|
||||
|
||||
This repository is published from [kubernetes/kubernetes/staging/src/k8s.io/client-go](https://git.k8s.io/kubernetes/staging/src/k8s.io/client-go) by the [kubernetes publishing-bot](https://git.k8s.io/publishing-bot).
|
||||
|
||||
|
||||
14
Godeps/Godeps.json
generated
14
Godeps/Godeps.json
generated
@@ -264,7 +264,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto",
|
||||
"Rev": "60c769a6c586"
|
||||
"Rev": "bac4c82f6975"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/exp",
|
||||
@@ -340,7 +340,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/yaml.v2",
|
||||
"Rev": "v2.2.4"
|
||||
"Rev": "v2.2.8"
|
||||
},
|
||||
{
|
||||
"ImportPath": "honnef.co/go/tools",
|
||||
@@ -348,11 +348,11 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api",
|
||||
"Rev": "v0.18.0-alpha.1"
|
||||
"Rev": "v0.17.6-beta.0"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery",
|
||||
"Rev": "v0.18.0-alpha.1"
|
||||
"Rev": "v0.17.6-beta.0"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/gengo",
|
||||
@@ -364,15 +364,15 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/kube-openapi",
|
||||
"Rev": "30be4d16710a"
|
||||
"Rev": "82d701f24f9d"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/utils",
|
||||
"Rev": "e782cd3c129f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "sigs.k8s.io/structured-merge-diff",
|
||||
"Rev": "15d366b2352e"
|
||||
"ImportPath": "sigs.k8s.io/structured-merge-diff/v2",
|
||||
"Rev": "v2.0.1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "sigs.k8s.io/yaml",
|
||||
|
||||
22
INSTALL.md
22
INSTALL.md
@@ -52,30 +52,14 @@ go mod init
|
||||
|
||||
### Add client-go as a dependency
|
||||
|
||||
Indicate which version of `client-go` your project requires:
|
||||
|
||||
- If you are using Kubernetes versions >= `v1.17.0`, use a corresponding
|
||||
`v0.x.y` tag. For example, `k8s.io/client-go@v0.17.0` corresponds to Kubernetes `v1.17.0`:
|
||||
Indicate which version of `client-go` your project requires (replace `kubernetes-1.15.0` with the desired version):
|
||||
|
||||
```sh
|
||||
go get k8s.io/client-go@v0.17.0
|
||||
```
|
||||
|
||||
You can also use a non-semver `kubernetes-1.x.y` tag to refer to a version
|
||||
of `client-go` corresponding to a given Kubernetes release. Prior to Kubernetes
|
||||
`v1.17.0` these were the only tags available for use with go modules.
|
||||
For example, `kubernetes-1.16.3` corresponds to Kubernetes `v1.16.3`.
|
||||
However, it is recommended to use semver-like `v0.x.y` tags over non-semver
|
||||
`kubernetes-1.x.y` tags to have a seamless experience with go modules.
|
||||
|
||||
- If you are using Kubernetes versions < `v1.17.0` (replace `kubernetes-1.16.3` with the desired version):
|
||||
|
||||
```sh
|
||||
go get k8s.io/client-go@kubernetes-1.16.3
|
||||
go get k8s.io/client-go@kubernetes-1.15.0
|
||||
```
|
||||
|
||||
You can now import and use the `k8s.io/client-go` APIs in your project.
|
||||
The next time you `go build`, `go test`, or `go run` your project,
|
||||
`k8s.io/client-go` and its dependencies will be downloaded (if needed),
|
||||
and detailed dependency version info will be added to your `go.mod` file
|
||||
(or you can also run `go mod tidy` to do this directly).
|
||||
(or you can also run `go mod tidy` to do this directly).
|
||||
105
README.md
105
README.md
@@ -2,8 +2,7 @@
|
||||
|
||||
Go clients for talking to a [kubernetes](http://kubernetes.io/) cluster.
|
||||
|
||||
We recommend using the `v0.x.y` tags for Kubernetes releases >= `v1.17.0` and
|
||||
`kubernetes-1.x.y` tags for Kubernetes releases < `v1.17.0`.
|
||||
We recommend using the `kubernetes-1.x.y` tag matching the current Kubernetes release (`kubernetes-1.15.3` at the time this was written).
|
||||
See [INSTALL.md](/INSTALL.md) for detailed installation instructions.
|
||||
`go get k8s.io/client-go@master` works, but will fetch `master`, which may be less stable than a tagged release.
|
||||
|
||||
@@ -45,31 +44,42 @@ See [INSTALL.md](/INSTALL.md) for detailed installation instructions.
|
||||
|
||||
### Versioning
|
||||
|
||||
- For each `v1.x.y` Kubernetes release, the major version (first digit)
|
||||
would remain `0`.
|
||||
`client-go` follows [semver](http://semver.org/). We will not make
|
||||
backwards-incompatible changes without incrementing the major version number. A
|
||||
change is backwards-incompatible either if it *i)* changes the public interfaces
|
||||
of `client-go`, or *ii)* makes `client-go` incompatible with otherwise supported
|
||||
versions of Kubernetes clusters.
|
||||
|
||||
- Bugfixes will result in the patch version (third digit) changing. PRs that are
|
||||
Changes that add features in a backwards-compatible way will result in bumping
|
||||
the minor version (second digit) number.
|
||||
|
||||
Bugfixes will result in the patch version (third digit) changing. PRs that are
|
||||
cherry-picked into an older Kubernetes release branch will result in an update
|
||||
to the corresponding branch in `client-go`, with a corresponding new tag
|
||||
changing the patch version.
|
||||
|
||||
A consequence of this is that `client-go` version numbers will be unrelated to
|
||||
Kubernetes version numbers.
|
||||
|
||||
#### Branches and tags.
|
||||
|
||||
We will create a new branch and tag for each increment in the minor version
|
||||
number. We will create only a new tag for each increment in the patch
|
||||
We will create a new branch and tag for each increment in the major version number or
|
||||
minor version number. We will create only a new tag for each increment in the patch
|
||||
version number. See [semver](http://semver.org/) for definitions of major,
|
||||
minor, and patch.
|
||||
|
||||
The HEAD of the master branch in client-go will track the HEAD of the master
|
||||
branch in the main Kubernetes repo.
|
||||
The master branch will track HEAD in the main Kubernetes repo and
|
||||
accumulate changes. Consider HEAD to have the version `x.(y+1).0-alpha` or
|
||||
`(x+1).0.0-alpha` (depending on whether it has accumulated a breaking change or
|
||||
not), where `x` and `y` are the current major and minor versions.
|
||||
|
||||
#### Compatibility: your code <-> client-go
|
||||
|
||||
The `v0.x.y` tags indicate that go APIs may change in incompatible ways in
|
||||
different versions.
|
||||
|
||||
See [INSTALL.md](INSTALL.md) for guidelines on requiring a specific
|
||||
version of client-go.
|
||||
`client-go` follows [semver](http://semver.org/), so until the major version of
|
||||
client-go gets increased, your code will compile and will continue to work with
|
||||
explicitly supported versions of Kubernetes clusters. You must use a dependency
|
||||
management system and pin a specific major version of `client-go` to get this
|
||||
benefit, as HEAD follows the upstream Kubernetes repo.
|
||||
|
||||
#### Compatibility: client-go <-> Kubernetes clusters
|
||||
|
||||
@@ -82,12 +92,16 @@ 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.15.0` | ✓ | +- | +- |
|
||||
| `kubernetes-1.16.0` | +- | ✓ | +- |
|
||||
| `kubernetes-1.17.0`/`v0.17.0` | +- | +- | ✓ |
|
||||
| `HEAD` | +- | +- | +- |
|
||||
| | Kubernetes 1.9 | Kubernetes 1.10 | Kubernetes 1.11 | Kubernetes 1.12 | Kubernetes 1.13 | Kubernetes 1.14 | Kubernetes 1.15 |
|
||||
|---------------------|----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|
|
||||
| client-go 6.0 | ✓ | +- | +- | +- | +- | +- | +- |
|
||||
| client-go 7.0 | +- | ✓ | +- | +- | +- | +- | +- |
|
||||
| client-go 8.0 | +- | +- | ✓ | +- | +- | +- | +- |
|
||||
| client-go 9.0 | +- | +- | +- | ✓ | +- | +- | +- |
|
||||
| client-go 10.0 | +- | +- | +- | +- | ✓ | +- | +- |
|
||||
| client-go 11.0 | +- | +- | +- | +- | +- | ✓ | +- |
|
||||
| client-go 12.0 | +- | +- | +- | +- | +- | +- | ✓ |
|
||||
| client-go HEAD | +- | +- | +- | +- | +- | +- | +- |
|
||||
|
||||
Key:
|
||||
|
||||
@@ -108,21 +122,19 @@ 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 | ✓ |
|
||||
| client-go 1.4 | Kubernetes main repo, 1.4 branch | = - |
|
||||
| client-go 1.5 | Kubernetes main repo, 1.5 branch | = - |
|
||||
| client-go 2.0 | Kubernetes main repo, 1.5 branch | = - |
|
||||
| client-go 3.0 | Kubernetes main repo, 1.6 branch | = - |
|
||||
| client-go 4.0 | Kubernetes main repo, 1.7 branch | = - |
|
||||
| client-go 5.0 | Kubernetes main repo, 1.8 branch | = - |
|
||||
| client-go 6.0 | Kubernetes main repo, 1.9 branch | = - |
|
||||
| client-go 7.0 | Kubernetes main repo, 1.10 branch | = - |
|
||||
| client-go 8.0 | Kubernetes main repo, 1.11 branch | =- |
|
||||
| client-go 9.0 | Kubernetes main repo, 1.12 branch | =- |
|
||||
| client-go 10.0 | Kubernetes main repo, 1.13 branch | ✓ |
|
||||
| client-go 11.0 | Kubernetes main repo, 1.14 branch | ✓ |
|
||||
| client-go 12.0 | Kubernetes main repo, 1.15 branch | ✓ |
|
||||
| client-go HEAD | Kubernetes main repo, master branch | ✓ |
|
||||
|
||||
Key:
|
||||
@@ -149,19 +161,14 @@ existing users won't be broken.
|
||||
|
||||
This repository is still a mirror of
|
||||
[k8s.io/kubernetes/staging/src/client-go](https://github.com/kubernetes/kubernetes/tree/master/staging/src/k8s.io/client-go),
|
||||
the code development is still done in the staging area.
|
||||
|
||||
Since Kubernetes `v1.8.0`, when syncing the code from the staging area,
|
||||
we also sync the Kubernetes version tags to client-go, prefixed with
|
||||
`kubernetes-`. From Kubernetes `v1.17.0`, we also create matching semver
|
||||
`v0.x.y` tags for each `v1.x.y` Kubernetes release.
|
||||
|
||||
For example, if you check out the `kubernetes-1.17.0` or the `v0.17.0` tag in
|
||||
client-go, the code you get is exactly the same as if you check out the `v1.17.0`
|
||||
tag in Kubernetes, and change directory to `staging/src/k8s.io/client-go`.
|
||||
|
||||
The purpose is to let users quickly find matching commits among published repos,
|
||||
like [sample-apiserver](https://github.com/kubernetes/sample-apiserver),
|
||||
the code development is still done in the staging area. Since Kubernetes 1.8
|
||||
release, when syncing the code from the staging area, we also sync the Kubernetes
|
||||
version tags to client-go, prefixed with "kubernetes-". For example, if you check
|
||||
out the `kubernetes-1.15.3` tag in client-go, the code you get is exactly the
|
||||
same as if you check out the `v1.15.3` tag in Kubernetes, and change directory to
|
||||
`staging/src/k8s.io/client-go`. The purpose is to let users quickly find matching
|
||||
commits among published repos, like
|
||||
[sample-apiserver](https://github.com/kubernetes/sample-apiserver),
|
||||
[apiextension-apiserver](https://github.com/kubernetes/apiextensions-apiserver),
|
||||
etc. The Kubernetes version tag does NOT claim any backwards compatibility
|
||||
guarantees for client-go. Please check the [semantic versions](#versioning) if
|
||||
@@ -172,7 +179,7 @@ you care about backwards compatibility.
|
||||
Use go1.11+ and fetch the desired version using the `go get` command. For example:
|
||||
|
||||
```
|
||||
go get k8s.io/client-go@v0.17.0
|
||||
go get k8s.io/client-go@kubernetes-1.15.3
|
||||
```
|
||||
|
||||
See [INSTALL.md](/INSTALL.md) for detailed instructions.
|
||||
|
||||
12
go.mod
12
go.mod
@@ -2,7 +2,7 @@
|
||||
|
||||
module k8s.io/client-go
|
||||
|
||||
go 1.13
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.38.0 // indirect
|
||||
@@ -23,13 +23,13 @@ require (
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.4.0
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
|
||||
google.golang.org/appengine v1.5.0 // indirect
|
||||
k8s.io/api v0.18.0-alpha.1
|
||||
k8s.io/apimachinery v0.18.0-alpha.1
|
||||
k8s.io/api v0.17.6-beta.0
|
||||
k8s.io/apimachinery v0.17.6-beta.0
|
||||
k8s.io/klog v1.0.0
|
||||
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f
|
||||
sigs.k8s.io/yaml v1.1.0
|
||||
@@ -38,6 +38,6 @@ require (
|
||||
replace (
|
||||
golang.org/x/sys => golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // pinned to release-branch.go1.13
|
||||
golang.org/x/tools => golang.org/x/tools v0.0.0-20190821162956-65e3620a7ae7 // pinned to release-branch.go1.13
|
||||
k8s.io/api => k8s.io/api v0.18.0-alpha.1
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.18.0-alpha.1
|
||||
k8s.io/api => k8s.io/api v0.17.6-beta.0
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.17.6-beta.0
|
||||
)
|
||||
|
||||
25
go.sum
25
go.sum
@@ -21,7 +21,6 @@ github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb0
|
||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -60,7 +59,6 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
@@ -82,7 +80,7 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
|
||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
@@ -97,7 +95,6 @@ github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
@@ -113,7 +110,6 @@ github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
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/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
@@ -121,7 +117,6 @@ github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzu
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
@@ -129,8 +124,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
@@ -189,21 +184,21 @@ gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.18.0-alpha.1/go.mod h1:X82bXHlVEfxpVA9rO5PMaSOdQ+VdlSjT9A2Tl/CWL4A=
|
||||
k8s.io/apimachinery v0.18.0-alpha.1/go.mod h1:Ng1IY8TS7sC44KJxT/WUR6qFRfWwahYYYpNXyYRKOCY=
|
||||
k8s.io/api v0.17.6-beta.0/go.mod h1:VidxcyvUtKF2+Hul10U4/nUiefO2ZPcMffUpPx0a6F4=
|
||||
k8s.io/apimachinery v0.17.6-beta.0/go.mod h1:ioIo1G/a+uONV7Tv+ZmCbMG1/a3kVw5YcDdncd8ugQ0=
|
||||
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
|
||||
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||
k8s.io/kube-openapi v0.0.0-20200316234421-82d701f24f9d h1:jocF7XFucw2pEiv2wS7wk2FRFCjDFGV1oa4TMs0SAT0=
|
||||
k8s.io/kube-openapi v0.0.0-20200316234421-82d701f24f9d/go.mod h1:F+5wygcW0wmRTnM3cOgIqGivxkwSWIWT5YdsDbeAOaU=
|
||||
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
|
||||
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/structured-merge-diff/v2 v2.0.1/go.mod h1:Wb7vfKAodbKgf6tn1Kl0VvGj7mRH6DGaRcixXEJXTsE=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
|
||||
70
listers/apps/v1/deployment_expansion.go
Normal file
70
listers/apps/v1/deployment_expansion.go
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
Copyright 2017 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 v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
apps "k8s.io/api/apps/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
// DeploymentListerExpansion allows custom methods to be added to
|
||||
// DeploymentLister.
|
||||
type DeploymentListerExpansion interface {
|
||||
GetDeploymentsForReplicaSet(rs *apps.ReplicaSet) ([]*apps.Deployment, error)
|
||||
}
|
||||
|
||||
// DeploymentNamespaceListerExpansion allows custom methods to be added to
|
||||
// DeploymentNamespaceLister.
|
||||
type DeploymentNamespaceListerExpansion interface{}
|
||||
|
||||
// GetDeploymentsForReplicaSet returns a list of Deployments that potentially
|
||||
// match a ReplicaSet. Only the one specified in the ReplicaSet's ControllerRef
|
||||
// will actually manage it.
|
||||
// Returns an error only if no matching Deployments are found.
|
||||
func (s *deploymentLister) GetDeploymentsForReplicaSet(rs *apps.ReplicaSet) ([]*apps.Deployment, error) {
|
||||
if len(rs.Labels) == 0 {
|
||||
return nil, fmt.Errorf("no deployments found for ReplicaSet %v because it has no labels", rs.Name)
|
||||
}
|
||||
|
||||
// TODO: MODIFY THIS METHOD so that it checks for the podTemplateSpecHash label
|
||||
dList, err := s.Deployments(rs.Namespace).List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var deployments []*apps.Deployment
|
||||
for _, d := range dList {
|
||||
selector, err := metav1.LabelSelectorAsSelector(d.Spec.Selector)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid label selector: %v", err)
|
||||
}
|
||||
// If a deployment with a nil or empty selector creeps in, it should match nothing, not everything.
|
||||
if selector.Empty() || !selector.Matches(labels.Set(rs.Labels)) {
|
||||
continue
|
||||
}
|
||||
deployments = append(deployments, d)
|
||||
}
|
||||
|
||||
if len(deployments) == 0 {
|
||||
return nil, fmt.Errorf("could not find deployments set for ReplicaSet %s in namespace %s with labels: %v", rs.Name, rs.Namespace, rs.Labels)
|
||||
}
|
||||
|
||||
return deployments, nil
|
||||
}
|
||||
@@ -25,11 +25,3 @@ type ControllerRevisionListerExpansion interface{}
|
||||
// ControllerRevisionNamespaceListerExpansion allows custom methods to be added to
|
||||
// ControllerRevisionNamespaceLister.
|
||||
type ControllerRevisionNamespaceListerExpansion interface{}
|
||||
|
||||
// DeploymentListerExpansion allows custom methods to be added to
|
||||
// DeploymentLister.
|
||||
type DeploymentListerExpansion interface{}
|
||||
|
||||
// DeploymentNamespaceListerExpansion allows custom methods to be added to
|
||||
// DeploymentNamespaceLister.
|
||||
type DeploymentNamespaceListerExpansion interface{}
|
||||
|
||||
70
listers/apps/v1beta2/deployment_expansion.go
Normal file
70
listers/apps/v1beta2/deployment_expansion.go
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
Copyright 2017 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 v1beta2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
apps "k8s.io/api/apps/v1beta2"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
// DeploymentListerExpansion allows custom methods to be added to
|
||||
// DeploymentLister.
|
||||
type DeploymentListerExpansion interface {
|
||||
GetDeploymentsForReplicaSet(rs *apps.ReplicaSet) ([]*apps.Deployment, error)
|
||||
}
|
||||
|
||||
// DeploymentNamespaceListerExpansion allows custom methods to be added to
|
||||
// DeploymentNamespaceLister.
|
||||
type DeploymentNamespaceListerExpansion interface{}
|
||||
|
||||
// GetDeploymentsForReplicaSet returns a list of Deployments that potentially
|
||||
// match a ReplicaSet. Only the one specified in the ReplicaSet's ControllerRef
|
||||
// will actually manage it.
|
||||
// Returns an error only if no matching Deployments are found.
|
||||
func (s *deploymentLister) GetDeploymentsForReplicaSet(rs *apps.ReplicaSet) ([]*apps.Deployment, error) {
|
||||
if len(rs.Labels) == 0 {
|
||||
return nil, fmt.Errorf("no deployments found for ReplicaSet %v because it has no labels", rs.Name)
|
||||
}
|
||||
|
||||
// TODO: MODIFY THIS METHOD so that it checks for the podTemplateSpecHash label
|
||||
dList, err := s.Deployments(rs.Namespace).List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var deployments []*apps.Deployment
|
||||
for _, d := range dList {
|
||||
selector, err := metav1.LabelSelectorAsSelector(d.Spec.Selector)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid label selector: %v", err)
|
||||
}
|
||||
// If a deployment with a nil or empty selector creeps in, it should match nothing, not everything.
|
||||
if selector.Empty() || !selector.Matches(labels.Set(rs.Labels)) {
|
||||
continue
|
||||
}
|
||||
deployments = append(deployments, d)
|
||||
}
|
||||
|
||||
if len(deployments) == 0 {
|
||||
return nil, fmt.Errorf("could not find deployments set for ReplicaSet %s in namespace %s with labels: %v", rs.Name, rs.Namespace, rs.Labels)
|
||||
}
|
||||
|
||||
return deployments, nil
|
||||
}
|
||||
@@ -25,11 +25,3 @@ type ControllerRevisionListerExpansion interface{}
|
||||
// ControllerRevisionNamespaceListerExpansion allows custom methods to be added to
|
||||
// ControllerRevisionNamespaceLister.
|
||||
type ControllerRevisionNamespaceListerExpansion interface{}
|
||||
|
||||
// DeploymentListerExpansion allows custom methods to be added to
|
||||
// DeploymentLister.
|
||||
type DeploymentListerExpansion interface{}
|
||||
|
||||
// DeploymentNamespaceListerExpansion allows custom methods to be added to
|
||||
// DeploymentNamespaceLister.
|
||||
type DeploymentNamespaceListerExpansion interface{}
|
||||
|
||||
@@ -58,10 +58,6 @@ type LimitRangeNamespaceListerExpansion interface{}
|
||||
// NamespaceLister.
|
||||
type NamespaceListerExpansion interface{}
|
||||
|
||||
// NodeListerExpansion allows custom methods to be added to
|
||||
// NodeLister.
|
||||
type NodeListerExpansion interface{}
|
||||
|
||||
// PersistentVolumeListerExpansion allows custom methods to be added to
|
||||
// PersistentVolumeLister.
|
||||
type PersistentVolumeListerExpansion interface{}
|
||||
@@ -106,14 +102,6 @@ type SecretListerExpansion interface{}
|
||||
// SecretNamespaceLister.
|
||||
type SecretNamespaceListerExpansion interface{}
|
||||
|
||||
// ServiceListerExpansion allows custom methods to be added to
|
||||
// ServiceLister.
|
||||
type ServiceListerExpansion interface{}
|
||||
|
||||
// ServiceNamespaceListerExpansion allows custom methods to be added to
|
||||
// ServiceNamespaceLister.
|
||||
type ServiceNamespaceListerExpansion interface{}
|
||||
|
||||
// ServiceAccountListerExpansion allows custom methods to be added to
|
||||
// ServiceAccountLister.
|
||||
type ServiceAccountListerExpansion interface{}
|
||||
|
||||
48
listers/core/v1/node_expansion.go
Normal file
48
listers/core/v1/node_expansion.go
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright 2017 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 v1
|
||||
|
||||
import (
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
// NodeConditionPredicate is a function that indicates whether the given node's conditions meet
|
||||
// some set of criteria defined by the function.
|
||||
type NodeConditionPredicate func(node *v1.Node) bool
|
||||
|
||||
// NodeListerExpansion allows custom methods to be added to
|
||||
// NodeLister.
|
||||
type NodeListerExpansion interface {
|
||||
ListWithPredicate(predicate NodeConditionPredicate) ([]*v1.Node, error)
|
||||
}
|
||||
|
||||
func (l *nodeLister) ListWithPredicate(predicate NodeConditionPredicate) ([]*v1.Node, error) {
|
||||
nodes, err := l.List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var filtered []*v1.Node
|
||||
for i := range nodes {
|
||||
if predicate(nodes[i]) {
|
||||
filtered = append(filtered, nodes[i])
|
||||
}
|
||||
}
|
||||
|
||||
return filtered, nil
|
||||
}
|
||||
56
listers/core/v1/service_expansion.go
Normal file
56
listers/core/v1/service_expansion.go
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
Copyright 2017 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 v1
|
||||
|
||||
import (
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
// ServiceListerExpansion allows custom methods to be added to
|
||||
// ServiceLister.
|
||||
type ServiceListerExpansion interface {
|
||||
GetPodServices(pod *v1.Pod) ([]*v1.Service, error)
|
||||
}
|
||||
|
||||
// ServiceNamespaceListerExpansion allows custom methods to be added to
|
||||
// ServiceNamespaceLister.
|
||||
type ServiceNamespaceListerExpansion interface{}
|
||||
|
||||
// TODO: Move this back to scheduler as a helper function that takes a Store,
|
||||
// rather than a method of ServiceLister.
|
||||
func (s *serviceLister) GetPodServices(pod *v1.Pod) ([]*v1.Service, error) {
|
||||
allServices, err := s.Services(pod.Namespace).List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var services []*v1.Service
|
||||
for i := range allServices {
|
||||
service := allServices[i]
|
||||
if service.Spec.Selector == nil {
|
||||
// services with nil selectors match nothing, not everything.
|
||||
continue
|
||||
}
|
||||
selector := labels.Set(service.Spec.Selector).AsSelectorPreValidated()
|
||||
if selector.Matches(labels.Set(pod.Labels)) {
|
||||
services = append(services, service)
|
||||
}
|
||||
}
|
||||
|
||||
return services, nil
|
||||
}
|
||||
70
listers/extensions/v1beta1/deployment_expansion.go
Normal file
70
listers/extensions/v1beta1/deployment_expansion.go
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
Copyright 2017 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 v1beta1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
extensions "k8s.io/api/extensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
// DeploymentListerExpansion allows custom methods to be added to
|
||||
// DeploymentLister.
|
||||
type DeploymentListerExpansion interface {
|
||||
GetDeploymentsForReplicaSet(rs *extensions.ReplicaSet) ([]*extensions.Deployment, error)
|
||||
}
|
||||
|
||||
// DeploymentNamespaceListerExpansion allows custom methods to be added to
|
||||
// DeploymentNamespaceLister.
|
||||
type DeploymentNamespaceListerExpansion interface{}
|
||||
|
||||
// GetDeploymentsForReplicaSet returns a list of Deployments that potentially
|
||||
// match a ReplicaSet. Only the one specified in the ReplicaSet's ControllerRef
|
||||
// will actually manage it.
|
||||
// Returns an error only if no matching Deployments are found.
|
||||
func (s *deploymentLister) GetDeploymentsForReplicaSet(rs *extensions.ReplicaSet) ([]*extensions.Deployment, error) {
|
||||
if len(rs.Labels) == 0 {
|
||||
return nil, fmt.Errorf("no deployments found for ReplicaSet %v because it has no labels", rs.Name)
|
||||
}
|
||||
|
||||
// TODO: MODIFY THIS METHOD so that it checks for the podTemplateSpecHash label
|
||||
dList, err := s.Deployments(rs.Namespace).List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var deployments []*extensions.Deployment
|
||||
for _, d := range dList {
|
||||
selector, err := metav1.LabelSelectorAsSelector(d.Spec.Selector)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid label selector: %v", err)
|
||||
}
|
||||
// If a deployment with a nil or empty selector creeps in, it should match nothing, not everything.
|
||||
if selector.Empty() || !selector.Matches(labels.Set(rs.Labels)) {
|
||||
continue
|
||||
}
|
||||
deployments = append(deployments, d)
|
||||
}
|
||||
|
||||
if len(deployments) == 0 {
|
||||
return nil, fmt.Errorf("could not find deployments set for ReplicaSet %s in namespace %s with labels: %v", rs.Name, rs.Namespace, rs.Labels)
|
||||
}
|
||||
|
||||
return deployments, nil
|
||||
}
|
||||
@@ -18,14 +18,6 @@ limitations under the License.
|
||||
|
||||
package v1beta1
|
||||
|
||||
// DeploymentListerExpansion allows custom methods to be added to
|
||||
// DeploymentLister.
|
||||
type DeploymentListerExpansion interface{}
|
||||
|
||||
// DeploymentNamespaceListerExpansion allows custom methods to be added to
|
||||
// DeploymentNamespaceLister.
|
||||
type DeploymentNamespaceListerExpansion interface{}
|
||||
|
||||
// IngressListerExpansion allows custom methods to be added to
|
||||
// IngressLister.
|
||||
type IngressListerExpansion interface{}
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -43,7 +42,6 @@ import (
|
||||
"k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1"
|
||||
"k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/client-go/tools/metrics"
|
||||
"k8s.io/client-go/transport"
|
||||
"k8s.io/client-go/util/connrotation"
|
||||
"k8s.io/klog"
|
||||
@@ -262,7 +260,6 @@ func (a *Authenticator) cert() (*tls.Certificate, error) {
|
||||
func (a *Authenticator) getCreds() (*credentials, error) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
if a.cachedCreds != nil && !a.credsExpired() {
|
||||
return a.cachedCreds, nil
|
||||
}
|
||||
@@ -270,7 +267,6 @@ func (a *Authenticator) getCreds() (*credentials, error) {
|
||||
if err := a.refreshCredsLocked(nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return a.cachedCreds, nil
|
||||
}
|
||||
|
||||
@@ -359,17 +355,6 @@ func (a *Authenticator) refreshCredsLocked(r *clientauthentication.Response) err
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed parsing client key/certificate: %v", err)
|
||||
}
|
||||
|
||||
// Leaf is initialized to be nil:
|
||||
// https://golang.org/pkg/crypto/tls/#X509KeyPair
|
||||
// Leaf certificate is the first certificate:
|
||||
// https://golang.org/pkg/crypto/tls/#Certificate
|
||||
// Populating leaf is useful for quickly accessing the underlying x509
|
||||
// certificate values.
|
||||
cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed parsing client leaf certificate: %v", err)
|
||||
}
|
||||
newCreds.cert = &cert
|
||||
}
|
||||
|
||||
@@ -377,20 +362,10 @@ func (a *Authenticator) refreshCredsLocked(r *clientauthentication.Response) err
|
||||
a.cachedCreds = newCreds
|
||||
// Only close all connections when TLS cert rotates. Token rotation doesn't
|
||||
// need the extra noise.
|
||||
if oldCreds != nil && !reflect.DeepEqual(oldCreds.cert, a.cachedCreds.cert) {
|
||||
// Can be nil if the exec auth plugin only returned token auth.
|
||||
if oldCreds.cert != nil && oldCreds.cert.Leaf != nil {
|
||||
metrics.ClientCertRotationAge.Observe(time.Now().Sub(oldCreds.cert.Leaf.NotBefore))
|
||||
}
|
||||
if len(a.onRotateList) > 0 && oldCreds != nil && !reflect.DeepEqual(oldCreds.cert, a.cachedCreds.cert) {
|
||||
for _, onRotate := range a.onRotateList {
|
||||
onRotate()
|
||||
}
|
||||
}
|
||||
|
||||
expiry := time.Time{}
|
||||
if a.cachedCreds.cert != nil && a.cachedCreds.cert.Leaf != nil {
|
||||
expiry = a.cachedCreds.cert.Leaf.NotAfter
|
||||
}
|
||||
expirationMetrics.set(a, expiry)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -97,10 +97,6 @@ func init() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
validCert = &cert
|
||||
}
|
||||
|
||||
@@ -764,7 +760,7 @@ func TestConcurrentUpdateTransportConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
// genClientCert generates an x509 certificate for testing. Certificate and key
|
||||
// are returned in PEM encoding. The generated cert expires in 24 hours.
|
||||
// are returned in PEM encoding.
|
||||
func genClientCert(t *testing.T) ([]byte, []byte) {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
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 exec
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/client-go/tools/metrics"
|
||||
)
|
||||
|
||||
type certificateExpirationTracker struct {
|
||||
mu sync.RWMutex
|
||||
m map[*Authenticator]time.Time
|
||||
metricSet func(*time.Time)
|
||||
}
|
||||
|
||||
var expirationMetrics = &certificateExpirationTracker{
|
||||
m: map[*Authenticator]time.Time{},
|
||||
metricSet: func(e *time.Time) {
|
||||
metrics.ClientCertExpiry.Set(e)
|
||||
},
|
||||
}
|
||||
|
||||
// set stores the given expiration time and updates the updates the certificate
|
||||
// expiry metric to the earliest expiration time.
|
||||
func (c *certificateExpirationTracker) set(a *Authenticator, t time.Time) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.m[a] = t
|
||||
|
||||
earliest := time.Time{}
|
||||
for _, t := range c.m {
|
||||
if t.IsZero() {
|
||||
continue
|
||||
}
|
||||
if earliest.IsZero() || earliest.After(t) {
|
||||
earliest = t
|
||||
}
|
||||
}
|
||||
if earliest.IsZero() {
|
||||
c.metricSet(nil)
|
||||
} else {
|
||||
c.metricSet(&earliest)
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
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 exec
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type mockExpiryGauge struct {
|
||||
v *time.Time
|
||||
}
|
||||
|
||||
func (m *mockExpiryGauge) Set(t *time.Time) {
|
||||
m.v = t
|
||||
}
|
||||
|
||||
func ptr(t time.Time) *time.Time {
|
||||
return &t
|
||||
}
|
||||
|
||||
func TestCertificateExpirationTracker(t *testing.T) {
|
||||
now := time.Now()
|
||||
mockMetric := &mockExpiryGauge{}
|
||||
|
||||
tracker := &certificateExpirationTracker{
|
||||
m: map[*Authenticator]time.Time{},
|
||||
metricSet: mockMetric.Set,
|
||||
}
|
||||
|
||||
firstAuthenticator := &Authenticator{}
|
||||
secondAuthenticator := &Authenticator{}
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
auth *Authenticator
|
||||
time time.Time
|
||||
want *time.Time
|
||||
}{
|
||||
{
|
||||
desc: "ttl for one authenticator",
|
||||
auth: firstAuthenticator,
|
||||
time: now.Add(time.Minute * 10),
|
||||
want: ptr(now.Add(time.Minute * 10)),
|
||||
},
|
||||
{
|
||||
desc: "second authenticator shorter ttl",
|
||||
auth: secondAuthenticator,
|
||||
time: now.Add(time.Minute * 5),
|
||||
want: ptr(now.Add(time.Minute * 5)),
|
||||
},
|
||||
{
|
||||
desc: "update shorter to be longer",
|
||||
auth: secondAuthenticator,
|
||||
time: now.Add(time.Minute * 15),
|
||||
want: ptr(now.Add(time.Minute * 10)),
|
||||
},
|
||||
{
|
||||
desc: "update shorter to be zero time",
|
||||
auth: firstAuthenticator,
|
||||
time: time.Time{},
|
||||
want: ptr(now.Add(time.Minute * 15)),
|
||||
},
|
||||
{
|
||||
desc: "update last to be zero time records nil",
|
||||
auth: secondAuthenticator,
|
||||
time: time.Time{},
|
||||
want: nil,
|
||||
},
|
||||
} {
|
||||
// Must run in series as the tests build off each other.
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
tracker.set(tc.auth, tc.time)
|
||||
if mockMetric.v != nil && tc.want != nil {
|
||||
if !mockMetric.v.Equal(*tc.want) {
|
||||
t.Errorf("got: %s; want: %s", mockMetric.v, tc.want)
|
||||
}
|
||||
} else if mockMetric.v != tc.want {
|
||||
t.Errorf("got: %s; want: %s", mockMetric.v, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -76,25 +76,24 @@ func newClientCache() *clientCache {
|
||||
}
|
||||
|
||||
type cacheKey struct {
|
||||
clusterAddress string
|
||||
// Canonical issuer URL string of the provider.
|
||||
issuerURL string
|
||||
clientID string
|
||||
}
|
||||
|
||||
func (c *clientCache) getClient(clusterAddress, issuer, clientID string) (*oidcAuthProvider, bool) {
|
||||
func (c *clientCache) getClient(issuer, clientID string) (*oidcAuthProvider, bool) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
client, ok := c.cache[cacheKey{clusterAddress: clusterAddress, issuerURL: issuer, clientID: clientID}]
|
||||
client, ok := c.cache[cacheKey{issuer, clientID}]
|
||||
return client, ok
|
||||
}
|
||||
|
||||
// setClient attempts to put the client in the cache but may return any clients
|
||||
// with the same keys set before. This is so there's only ever one client for a provider.
|
||||
func (c *clientCache) setClient(clusterAddress, issuer, clientID string, client *oidcAuthProvider) *oidcAuthProvider {
|
||||
func (c *clientCache) setClient(issuer, clientID string, client *oidcAuthProvider) *oidcAuthProvider {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
key := cacheKey{clusterAddress: clusterAddress, issuerURL: issuer, clientID: clientID}
|
||||
key := cacheKey{issuer, clientID}
|
||||
|
||||
// If another client has already initialized a client for the given provider we want
|
||||
// to use that client instead of the one we're trying to set. This is so all transports
|
||||
@@ -108,7 +107,7 @@ func (c *clientCache) setClient(clusterAddress, issuer, clientID string, client
|
||||
return client
|
||||
}
|
||||
|
||||
func newOIDCAuthProvider(clusterAddress string, cfg map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) {
|
||||
func newOIDCAuthProvider(_ string, cfg map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) {
|
||||
issuer := cfg[cfgIssuerUrl]
|
||||
if issuer == "" {
|
||||
return nil, fmt.Errorf("Must provide %s", cfgIssuerUrl)
|
||||
@@ -120,7 +119,7 @@ func newOIDCAuthProvider(clusterAddress string, cfg map[string]string, persister
|
||||
}
|
||||
|
||||
// Check cache for existing provider.
|
||||
if provider, ok := cache.getClient(clusterAddress, issuer, clientID); ok {
|
||||
if provider, ok := cache.getClient(issuer, clientID); ok {
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
@@ -158,7 +157,7 @@ func newOIDCAuthProvider(clusterAddress string, cfg map[string]string, persister
|
||||
persister: persister,
|
||||
}
|
||||
|
||||
return cache.setClient(clusterAddress, issuer, clientID, provider), nil
|
||||
return cache.setClient(issuer, clientID, provider), nil
|
||||
}
|
||||
|
||||
type oidcAuthProvider struct {
|
||||
|
||||
@@ -119,40 +119,20 @@ func TestExpired(t *testing.T) {
|
||||
func TestClientCache(t *testing.T) {
|
||||
cache := newClientCache()
|
||||
|
||||
if _, ok := cache.getClient("cluster1", "issuer1", "id1"); ok {
|
||||
if _, ok := cache.getClient("issuer1", "id1"); ok {
|
||||
t.Fatalf("got client before putting one in the cache")
|
||||
}
|
||||
assertCacheLen(t, cache, 0)
|
||||
|
||||
cli1 := new(oidcAuthProvider)
|
||||
cli2 := new(oidcAuthProvider)
|
||||
cli3 := new(oidcAuthProvider)
|
||||
|
||||
gotcli := cache.setClient("cluster1", "issuer1", "id1", cli1)
|
||||
gotcli := cache.setClient("issuer1", "id1", cli1)
|
||||
if cli1 != gotcli {
|
||||
t.Fatalf("set first client and got a different one")
|
||||
}
|
||||
assertCacheLen(t, cache, 1)
|
||||
|
||||
gotcli = cache.setClient("cluster1", "issuer1", "id1", cli2)
|
||||
gotcli = cache.setClient("issuer1", "id1", cli2)
|
||||
if cli1 != gotcli {
|
||||
t.Fatalf("set a second client and didn't get the first")
|
||||
}
|
||||
assertCacheLen(t, cache, 1)
|
||||
|
||||
gotcli = cache.setClient("cluster2", "issuer1", "id1", cli3)
|
||||
if cli1 == gotcli {
|
||||
t.Fatalf("set a third client and got the first")
|
||||
}
|
||||
if cli3 != gotcli {
|
||||
t.Fatalf("set third client and got a different one")
|
||||
}
|
||||
assertCacheLen(t, cache, 2)
|
||||
}
|
||||
|
||||
func assertCacheLen(t *testing.T, cache *clientCache, length int) {
|
||||
t.Helper()
|
||||
if len(cache.cache) != length {
|
||||
t.Errorf("expected cache length %d got %d", length, len(cache.cache))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,7 +562,7 @@ func (r *Request) tryThrottle() error {
|
||||
}
|
||||
|
||||
if latency := time.Since(now); latency > longThrottleLatency {
|
||||
klog.V(3).Infof("Throttling request took %v, request: %s:%s", latency, r.verb, r.URL().String())
|
||||
klog.V(4).Infof("Throttling request took %v, request: %s:%s", latency, r.verb, r.URL().String())
|
||||
}
|
||||
|
||||
return err
|
||||
@@ -603,7 +603,7 @@ func (r *Request) Watch() (watch.Interface, error) {
|
||||
if err != nil {
|
||||
// The watch stream mechanism handles many common partial data errors, so closed
|
||||
// connections can be retried in many cases.
|
||||
if net.IsProbableEOF(err) {
|
||||
if net.IsProbableEOF(err) || net.IsTimeout(err) {
|
||||
return watch.NewEmptyWatch(), nil
|
||||
}
|
||||
return nil, err
|
||||
@@ -806,24 +806,19 @@ func (r *Request) request(fn func(*http.Request, *http.Response)) error {
|
||||
r.backoff.UpdateBackoff(r.URL(), err, resp.StatusCode)
|
||||
}
|
||||
if err != nil {
|
||||
// "Connection reset by peer", "Connection refused" or "apiserver is shutting down" are usually a transient errors.
|
||||
// "Connection reset by peer" is usually a transient error.
|
||||
// Thus in case of "GET" operations, we simply retry it.
|
||||
// We are not automatically retrying "write" operations, as
|
||||
// they are not idempotent.
|
||||
if r.verb != "GET" {
|
||||
if !net.IsConnectionReset(err) || r.verb != "GET" {
|
||||
return err
|
||||
}
|
||||
// For connection errors and apiserver shutdown errors retry.
|
||||
if net.IsConnectionReset(err) || net.IsConnectionRefused(err) {
|
||||
// For the purpose of retry, we set the artificial "retry-after" response.
|
||||
// TODO: Should we clean the original response if it exists?
|
||||
resp = &http.Response{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
Header: http.Header{"Retry-After": []string{"1"}},
|
||||
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
// For the purpose of retry, we set the artificial "retry-after" response.
|
||||
// TODO: Should we clean the original response if it exists?
|
||||
resp = &http.Response{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
Header: http.Header{"Retry-After": []string{"1"}},
|
||||
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
78
tools/cache/reflector.go
vendored
78
tools/cache/reflector.go
vendored
@@ -74,9 +74,6 @@ type Reflector struct {
|
||||
// observed when doing a sync with the underlying store
|
||||
// it is thread safe, but not synchronized with the underlying store
|
||||
lastSyncResourceVersion string
|
||||
// isLastSyncResourceVersionGone is true if the previous list or watch request with lastSyncResourceVersion
|
||||
// failed with an HTTP 410 (Gone) status code.
|
||||
isLastSyncResourceVersionGone bool
|
||||
// lastSyncResourceVersionMutex guards read/write access to lastSyncResourceVersion
|
||||
lastSyncResourceVersionMutex sync.RWMutex
|
||||
// WatchListPageSize is the requested chunk size of initial and resync watch lists.
|
||||
@@ -188,7 +185,10 @@ func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
|
||||
klog.V(3).Infof("Listing and watching %v from %s", r.expectedTypeName, r.name)
|
||||
var resourceVersion string
|
||||
|
||||
options := metav1.ListOptions{ResourceVersion: r.relistResourceVersion()}
|
||||
// Explicitly set "0" as resource version - it's fine for the List()
|
||||
// to be served from cache and potentially be delayed relative to
|
||||
// etcd contents. Reflector framework will catch up via Watch() eventually.
|
||||
options := metav1.ListOptions{ResourceVersion: "0"}
|
||||
|
||||
if err := func() error {
|
||||
initTrace := trace.New("Reflector ListAndWatch", trace.Field{"name", r.name})
|
||||
@@ -211,17 +211,8 @@ func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
|
||||
if r.WatchListPageSize != 0 {
|
||||
pager.PageSize = r.WatchListPageSize
|
||||
}
|
||||
|
||||
// Pager falls back to full list if paginated list calls fail due to an "Expired" error.
|
||||
list, err = pager.List(context.Background(), options)
|
||||
if isExpiredError(err) {
|
||||
r.setIsLastSyncResourceVersionExpired(true)
|
||||
// Retry immediately if the resource version used to list is expired.
|
||||
// The pager already falls back to full list if paginated list calls fail due to an "Expired" error on
|
||||
// continuation pages, but the pager might not be enabled, or the full list might fail because the
|
||||
// resource version it is listing at is expired, so we need to fallback to resourceVersion="" in all
|
||||
// to recover and ensure the reflector makes forward progress.
|
||||
list, err = pager.List(context.Background(), metav1.ListOptions{ResourceVersion: r.relistResourceVersion()})
|
||||
}
|
||||
close(listCh)
|
||||
}()
|
||||
select {
|
||||
@@ -234,7 +225,6 @@ func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: Failed to list %v: %v", r.name, r.expectedTypeName, err)
|
||||
}
|
||||
r.setIsLastSyncResourceVersionExpired(false) // list was successful
|
||||
initTrace.Step("Objects listed")
|
||||
listMetaInterface, err := meta.ListAccessor(list)
|
||||
if err != nil {
|
||||
@@ -306,15 +296,14 @@ func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
|
||||
AllowWatchBookmarks: true,
|
||||
}
|
||||
|
||||
// start the clock before sending the request, since some proxies won't flush headers until after the first watch event is sent
|
||||
start := r.clock.Now()
|
||||
w, err := r.listerWatcher.Watch(options)
|
||||
if err != nil {
|
||||
switch {
|
||||
case isExpiredError(err):
|
||||
r.setIsLastSyncResourceVersionExpired(true)
|
||||
klog.V(4).Infof("%s: watch of %v closed with: %v", r.name, r.expectedTypeName, err)
|
||||
case err == io.EOF:
|
||||
switch err {
|
||||
case io.EOF:
|
||||
// watch closed normally
|
||||
case err == io.ErrUnexpectedEOF:
|
||||
case io.ErrUnexpectedEOF:
|
||||
klog.V(1).Infof("%s: Watch for %v closed with unexpected EOF: %v", r.name, r.expectedTypeName, err)
|
||||
default:
|
||||
utilruntime.HandleError(fmt.Errorf("%s: Failed to watch %v: %v", r.name, r.expectedTypeName, err))
|
||||
@@ -330,11 +319,10 @@ func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := r.watchHandler(w, &resourceVersion, resyncerrc, stopCh); err != nil {
|
||||
if err := r.watchHandler(start, w, &resourceVersion, resyncerrc, stopCh); err != nil {
|
||||
if err != errorStopRequested {
|
||||
switch {
|
||||
case isExpiredError(err):
|
||||
r.setIsLastSyncResourceVersionExpired(true)
|
||||
case apierrs.IsResourceExpired(err):
|
||||
klog.V(4).Infof("%s: watch of %v ended with: %v", r.name, r.expectedTypeName, err)
|
||||
default:
|
||||
klog.Warningf("%s: watch of %v ended with: %v", r.name, r.expectedTypeName, err)
|
||||
@@ -355,8 +343,7 @@ func (r *Reflector) syncWith(items []runtime.Object, resourceVersion string) err
|
||||
}
|
||||
|
||||
// watchHandler watches w and keeps *resourceVersion up to date.
|
||||
func (r *Reflector) watchHandler(w watch.Interface, resourceVersion *string, errc chan error, stopCh <-chan struct{}) error {
|
||||
start := r.clock.Now()
|
||||
func (r *Reflector) watchHandler(start time.Time, w watch.Interface, resourceVersion *string, errc chan error, stopCh <-chan struct{}) error {
|
||||
eventCount := 0
|
||||
|
||||
// Stopping the watcher should be idempotent and if we return from this function there's no way
|
||||
@@ -446,42 +433,3 @@ func (r *Reflector) setLastSyncResourceVersion(v string) {
|
||||
defer r.lastSyncResourceVersionMutex.Unlock()
|
||||
r.lastSyncResourceVersion = v
|
||||
}
|
||||
|
||||
// relistResourceVersion determines the resource version the reflector should list or relist from.
|
||||
// Returns either the lastSyncResourceVersion so that this reflector will relist with a resource
|
||||
// versions no older than has already been observed in relist results or watch events, or, if the last relist resulted
|
||||
// in an HTTP 410 (Gone) status code, returns "" so that the relist will use the latest resource version available in
|
||||
// etcd via a quorum read.
|
||||
func (r *Reflector) relistResourceVersion() string {
|
||||
r.lastSyncResourceVersionMutex.RLock()
|
||||
defer r.lastSyncResourceVersionMutex.RUnlock()
|
||||
|
||||
if r.isLastSyncResourceVersionGone {
|
||||
// Since this reflector makes paginated list requests, and all paginated list requests skip the watch cache
|
||||
// if the lastSyncResourceVersion is expired, we set ResourceVersion="" and list again to re-establish reflector
|
||||
// to the latest available ResourceVersion, using a consistent read from etcd.
|
||||
return ""
|
||||
}
|
||||
if r.lastSyncResourceVersion == "" {
|
||||
// For performance reasons, initial list performed by reflector uses "0" as resource version to allow it to
|
||||
// be served from the watch cache if it is enabled.
|
||||
return "0"
|
||||
}
|
||||
return r.lastSyncResourceVersion
|
||||
}
|
||||
|
||||
// setIsLastSyncResourceVersionExpired sets if the last list or watch request with lastSyncResourceVersion returned a
|
||||
// expired error: HTTP 410 (Gone) Status Code.
|
||||
func (r *Reflector) setIsLastSyncResourceVersionExpired(isExpired bool) {
|
||||
r.lastSyncResourceVersionMutex.Lock()
|
||||
defer r.lastSyncResourceVersionMutex.Unlock()
|
||||
r.isLastSyncResourceVersionGone = isExpired
|
||||
}
|
||||
|
||||
func isExpiredError(err error) bool {
|
||||
// In Kubernetes 1.17 and earlier, the api server returns both apierrs.StatusReasonExpired and
|
||||
// apierrs.StatusReasonGone for HTTP 410 (Gone) status code responses. In 1.18 the kube server is more consistent
|
||||
// and always returns apierrs.StatusReasonExpired. For backward compatibility we can only remove the apierrs.IsGone
|
||||
// check when we fully drop support for Kubernetes 1.17 servers from reflectors.
|
||||
return apierrs.IsResourceExpired(err) || apierrs.IsGone(err)
|
||||
}
|
||||
|
||||
198
tools/cache/reflector_test.go
vendored
198
tools/cache/reflector_test.go
vendored
@@ -26,7 +26,6 @@ import (
|
||||
"time"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -136,7 +135,7 @@ func TestReflectorWatchHandlerError(t *testing.T) {
|
||||
fw.Stop()
|
||||
}()
|
||||
var resumeRV string
|
||||
err := g.watchHandler(fw, &resumeRV, nevererrc, wait.NeverStop)
|
||||
err := g.watchHandler(time.Now(), fw, &resumeRV, nevererrc, wait.NeverStop)
|
||||
if err == nil {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
@@ -156,7 +155,7 @@ func TestReflectorWatchHandler(t *testing.T) {
|
||||
fw.Stop()
|
||||
}()
|
||||
var resumeRV string
|
||||
err := g.watchHandler(fw, &resumeRV, nevererrc, wait.NeverStop)
|
||||
err := g.watchHandler(time.Now(), fw, &resumeRV, nevererrc, wait.NeverStop)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
}
|
||||
@@ -205,7 +204,7 @@ func TestReflectorStopWatch(t *testing.T) {
|
||||
var resumeRV string
|
||||
stopWatch := make(chan struct{}, 1)
|
||||
stopWatch <- struct{}{}
|
||||
err := g.watchHandler(fw, &resumeRV, nevererrc, stopWatch)
|
||||
err := g.watchHandler(time.Now(), fw, &resumeRV, nevererrc, stopWatch)
|
||||
if err != errorStopRequested {
|
||||
t.Errorf("expected stop error, got %q", err)
|
||||
}
|
||||
@@ -435,197 +434,6 @@ func TestReflectorWatchListPageSize(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestReflectorResyncWithResourceVersion ensures that a reflector keeps track of the ResourceVersion and sends
|
||||
// it in relist requests to prevent the reflector from traveling back in time if the relist is to a api-server or
|
||||
// etcd that is partitioned and serving older data than the reflector has already processed.
|
||||
func TestReflectorResyncWithResourceVersion(t *testing.T) {
|
||||
stopCh := make(chan struct{})
|
||||
s := NewStore(MetaNamespaceKeyFunc)
|
||||
listCallRVs := []string{}
|
||||
|
||||
lw := &testLW{
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
// Stop once the reflector begins watching since we're only interested in the list.
|
||||
close(stopCh)
|
||||
fw := watch.NewFake()
|
||||
return fw, nil
|
||||
},
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
listCallRVs = append(listCallRVs, options.ResourceVersion)
|
||||
pods := make([]v1.Pod, 8)
|
||||
for i := 0; i < 8; i++ {
|
||||
pods[i] = v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), ResourceVersion: fmt.Sprintf("%d", i)}}
|
||||
}
|
||||
switch options.ResourceVersion {
|
||||
case "0":
|
||||
return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}, Items: pods[0:4]}, nil
|
||||
case "10":
|
||||
return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "11"}, Items: pods[0:8]}, nil
|
||||
default:
|
||||
t.Fatalf("Unrecognized ResourceVersion: %s", options.ResourceVersion)
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
r := NewReflector(lw, &v1.Pod{}, s, 0)
|
||||
|
||||
// Initial list should use RV=0
|
||||
r.ListAndWatch(stopCh)
|
||||
|
||||
results := s.List()
|
||||
if len(results) != 4 {
|
||||
t.Errorf("Expected 4 results, got %d", len(results))
|
||||
}
|
||||
|
||||
// relist should use lastSyncResourceVersions (RV=10)
|
||||
stopCh = make(chan struct{})
|
||||
r.ListAndWatch(stopCh)
|
||||
|
||||
results = s.List()
|
||||
if len(results) != 8 {
|
||||
t.Errorf("Expected 8 results, got %d", len(results))
|
||||
}
|
||||
|
||||
expectedRVs := []string{"0", "10"}
|
||||
if !reflect.DeepEqual(listCallRVs, expectedRVs) {
|
||||
t.Errorf("Expected series of list calls with resource versiosn of %v but got: %v", expectedRVs, listCallRVs)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReflectorExpiredExactResourceVersion tests that a reflector handles the behavior of kubernetes 1.16 an earlier
|
||||
// where if the exact ResourceVersion requested is not available for a List request for a non-zero ResourceVersion,
|
||||
// an "Expired" error is returned if the ResourceVersion has expired (etcd has compacted it).
|
||||
// (In kubernetes 1.17, or when the watch cache is enabled, the List will instead return the list that is no older than
|
||||
// the requested ResourceVersion).
|
||||
func TestReflectorExpiredExactResourceVersion(t *testing.T) {
|
||||
stopCh := make(chan struct{})
|
||||
s := NewStore(MetaNamespaceKeyFunc)
|
||||
listCallRVs := []string{}
|
||||
|
||||
lw := &testLW{
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
// Stop once the reflector begins watching since we're only interested in the list.
|
||||
close(stopCh)
|
||||
fw := watch.NewFake()
|
||||
return fw, nil
|
||||
},
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
listCallRVs = append(listCallRVs, options.ResourceVersion)
|
||||
pods := make([]v1.Pod, 8)
|
||||
for i := 0; i < 8; i++ {
|
||||
pods[i] = v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), ResourceVersion: fmt.Sprintf("%d", i)}}
|
||||
}
|
||||
switch options.ResourceVersion {
|
||||
case "0":
|
||||
return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}, Items: pods[0:4]}, nil
|
||||
case "10":
|
||||
// When watch cache is disabled, if the exact ResourceVersion requested is not available, a "Expired" error is returned.
|
||||
return nil, apierrs.NewResourceExpired("The resourceVersion for the provided watch is too old.")
|
||||
case "":
|
||||
return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "11"}, Items: pods[0:8]}, nil
|
||||
default:
|
||||
t.Fatalf("Unrecognized ResourceVersion: %s", options.ResourceVersion)
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
r := NewReflector(lw, &v1.Pod{}, s, 0)
|
||||
|
||||
// Initial list should use RV=0
|
||||
r.ListAndWatch(stopCh)
|
||||
|
||||
results := s.List()
|
||||
if len(results) != 4 {
|
||||
t.Errorf("Expected 4 results, got %d", len(results))
|
||||
}
|
||||
|
||||
// relist should use lastSyncResourceVersions (RV=10) and since RV=10 is expired, it should retry with RV="".
|
||||
stopCh = make(chan struct{})
|
||||
r.ListAndWatch(stopCh)
|
||||
|
||||
results = s.List()
|
||||
if len(results) != 8 {
|
||||
t.Errorf("Expected 8 results, got %d", len(results))
|
||||
}
|
||||
|
||||
expectedRVs := []string{"0", "10", ""}
|
||||
if !reflect.DeepEqual(listCallRVs, expectedRVs) {
|
||||
t.Errorf("Expected series of list calls with resource versiosn of %v but got: %v", expectedRVs, listCallRVs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReflectorFullListIfExpired(t *testing.T) {
|
||||
stopCh := make(chan struct{})
|
||||
s := NewStore(MetaNamespaceKeyFunc)
|
||||
listCallRVs := []string{}
|
||||
|
||||
lw := &testLW{
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
// Stop once the reflector begins watching since we're only interested in the list.
|
||||
close(stopCh)
|
||||
fw := watch.NewFake()
|
||||
return fw, nil
|
||||
},
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
listCallRVs = append(listCallRVs, options.ResourceVersion)
|
||||
pods := make([]v1.Pod, 8)
|
||||
for i := 0; i < 8; i++ {
|
||||
pods[i] = v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), ResourceVersion: fmt.Sprintf("%d", i)}}
|
||||
}
|
||||
rvContinueLimit := func(rv, c string, l int64) metav1.ListOptions {
|
||||
return metav1.ListOptions{ResourceVersion: rv, Continue: c, Limit: l}
|
||||
}
|
||||
switch rvContinueLimit(options.ResourceVersion, options.Continue, options.Limit) {
|
||||
// initial limited list
|
||||
case rvContinueLimit("0", "", 4):
|
||||
return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "10"}, Items: pods[0:4]}, nil
|
||||
// first page of the rv=10 list
|
||||
case rvContinueLimit("10", "", 4):
|
||||
return &v1.PodList{ListMeta: metav1.ListMeta{Continue: "C1", ResourceVersion: "11"}, Items: pods[0:4]}, nil
|
||||
// second page of the above list
|
||||
case rvContinueLimit("", "C1", 4):
|
||||
return nil, apierrs.NewResourceExpired("The resourceVersion for the provided watch is too old.")
|
||||
// rv=10 unlimited list
|
||||
case rvContinueLimit("10", "", 0):
|
||||
return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "11"}, Items: pods[0:8]}, nil
|
||||
default:
|
||||
err := fmt.Errorf("unexpected list options: %#v", options)
|
||||
t.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
r := NewReflector(lw, &v1.Pod{}, s, 0)
|
||||
r.WatchListPageSize = 4
|
||||
|
||||
// Initial list should use RV=0
|
||||
if err := r.ListAndWatch(stopCh); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
results := s.List()
|
||||
if len(results) != 4 {
|
||||
t.Errorf("Expected 4 results, got %d", len(results))
|
||||
}
|
||||
|
||||
// relist should use lastSyncResourceVersions (RV=10) and since second page of that expired, it should full list with RV=10
|
||||
stopCh = make(chan struct{})
|
||||
if err := r.ListAndWatch(stopCh); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
results = s.List()
|
||||
if len(results) != 8 {
|
||||
t.Errorf("Expected 8 results, got %d", len(results))
|
||||
}
|
||||
|
||||
expectedRVs := []string{"0", "10", "", "10"}
|
||||
if !reflect.DeepEqual(listCallRVs, expectedRVs) {
|
||||
t.Errorf("Expected series of list calls with resource versiosn of %#v but got: %#v", expectedRVs, listCallRVs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReflectorSetExpectedType(t *testing.T) {
|
||||
obj := &unstructured.Unstructured{}
|
||||
gvk := schema.GroupVersionKind{
|
||||
|
||||
@@ -31,6 +31,9 @@ func Convert_Slice_v1_NamedCluster_To_Map_string_To_Pointer_api_Cluster(in *[]Na
|
||||
if err := Convert_v1_Cluster_To_api_Cluster(&curr.Cluster, newCluster, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if *out == nil {
|
||||
*out = make(map[string]*api.Cluster)
|
||||
}
|
||||
if (*out)[curr.Name] == nil {
|
||||
(*out)[curr.Name] = newCluster
|
||||
} else {
|
||||
@@ -65,6 +68,9 @@ func Convert_Slice_v1_NamedAuthInfo_To_Map_string_To_Pointer_api_AuthInfo(in *[]
|
||||
if err := Convert_v1_AuthInfo_To_api_AuthInfo(&curr.AuthInfo, newAuthInfo, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if *out == nil {
|
||||
*out = make(map[string]*api.AuthInfo)
|
||||
}
|
||||
if (*out)[curr.Name] == nil {
|
||||
(*out)[curr.Name] = newAuthInfo
|
||||
} else {
|
||||
@@ -99,6 +105,9 @@ func Convert_Slice_v1_NamedContext_To_Map_string_To_Pointer_api_Context(in *[]Na
|
||||
if err := Convert_v1_Context_To_api_Context(&curr.Context, newContext, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if *out == nil {
|
||||
*out = make(map[string]*api.Context)
|
||||
}
|
||||
if (*out)[curr.Name] == nil {
|
||||
(*out)[curr.Name] = newContext
|
||||
} else {
|
||||
@@ -133,6 +142,9 @@ func Convert_Slice_v1_NamedExtension_To_Map_string_To_runtime_Object(in *[]Named
|
||||
if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&curr.Extension, &newExtension, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if *out == nil {
|
||||
*out = make(map[string]runtime.Object)
|
||||
}
|
||||
if (*out)[curr.Name] == nil {
|
||||
(*out)[curr.Name] = newExtension
|
||||
} else {
|
||||
|
||||
@@ -17,7 +17,6 @@ limitations under the License.
|
||||
package clientcmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -30,7 +29,6 @@ import (
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
clientcmdlatest "k8s.io/client-go/tools/clientcmd/api/latest"
|
||||
)
|
||||
@@ -80,6 +78,32 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func TestNilOutMap(t *testing.T) {
|
||||
var fakeKubeconfigData = `apiVersion: v1
|
||||
kind: Config
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority-data: UEhPTlkK
|
||||
server: https://1.1.1.1
|
||||
name: production
|
||||
contexts:
|
||||
- context:
|
||||
cluster: production
|
||||
user: production
|
||||
name: production
|
||||
current-context: production
|
||||
users:
|
||||
- name: production
|
||||
user:
|
||||
auth-provider:
|
||||
name: gcp`
|
||||
|
||||
_, _, err := clientcmdlatest.Codec.Decode([]byte(fakeKubeconfigData), nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonExistentCommandLineFile(t *testing.T) {
|
||||
loadingRules := ClientConfigLoadingRules{
|
||||
ExplicitPath: "bogus_file",
|
||||
@@ -177,56 +201,6 @@ func TestConflictingCurrentContext(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeYAML(t *testing.T) {
|
||||
config := clientcmdapi.Config{
|
||||
CurrentContext: "any-context-value",
|
||||
Contexts: map[string]*clientcmdapi.Context{
|
||||
"433e40": {
|
||||
Cluster: "433e40",
|
||||
},
|
||||
},
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
"0": {
|
||||
Server: "https://localhost:1234",
|
||||
},
|
||||
"1": {
|
||||
Server: "https://localhost:1234",
|
||||
},
|
||||
"433e40": {
|
||||
Server: "https://localhost:1234",
|
||||
},
|
||||
},
|
||||
}
|
||||
data, err := Write(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := []byte(`apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
server: https://localhost:1234
|
||||
name: "0"
|
||||
- cluster:
|
||||
server: https://localhost:1234
|
||||
name: "1"
|
||||
- cluster:
|
||||
server: https://localhost:1234
|
||||
name: "433e40"
|
||||
contexts:
|
||||
- context:
|
||||
cluster: "433e40"
|
||||
user: ""
|
||||
name: "433e40"
|
||||
current-context: any-context-value
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users: null
|
||||
`)
|
||||
if !bytes.Equal(expected, data) {
|
||||
t.Error(diff.ObjectReflectDiff(string(expected), string(data)))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadingEmptyMaps(t *testing.T) {
|
||||
configFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(configFile.Name())
|
||||
|
||||
@@ -26,16 +26,6 @@ import (
|
||||
|
||||
var registerMetrics sync.Once
|
||||
|
||||
// DurationMetric is a measurement of some amount of time.
|
||||
type DurationMetric interface {
|
||||
Observe(duration time.Duration)
|
||||
}
|
||||
|
||||
// ExpiryMetric sets some time of expiry. If nil, assume not relevant.
|
||||
type ExpiryMetric interface {
|
||||
Set(expiry *time.Time)
|
||||
}
|
||||
|
||||
// LatencyMetric observes client latency partitioned by verb and url.
|
||||
type LatencyMetric interface {
|
||||
Observe(verb string, u url.URL, latency time.Duration)
|
||||
@@ -47,51 +37,21 @@ type ResultMetric interface {
|
||||
}
|
||||
|
||||
var (
|
||||
// ClientCertExpiry is the expiry time of a client certificate
|
||||
ClientCertExpiry ExpiryMetric = noopExpiry{}
|
||||
// ClientCertRotationAge is the age of a certificate that has just been rotated.
|
||||
ClientCertRotationAge DurationMetric = noopDuration{}
|
||||
// RequestLatency is the latency metric that rest clients will update.
|
||||
RequestLatency LatencyMetric = noopLatency{}
|
||||
// RequestResult is the result metric that rest clients will update.
|
||||
RequestResult ResultMetric = noopResult{}
|
||||
)
|
||||
|
||||
// RegisterOpts contains all the metrics to register. Metrics may be nil.
|
||||
type RegisterOpts struct {
|
||||
ClientCertExpiry ExpiryMetric
|
||||
ClientCertRotationAge DurationMetric
|
||||
RequestLatency LatencyMetric
|
||||
RequestResult ResultMetric
|
||||
}
|
||||
|
||||
// Register registers metrics for the rest client to use. This can
|
||||
// only be called once.
|
||||
func Register(opts RegisterOpts) {
|
||||
func Register(lm LatencyMetric, rm ResultMetric) {
|
||||
registerMetrics.Do(func() {
|
||||
if opts.ClientCertExpiry != nil {
|
||||
ClientCertExpiry = opts.ClientCertExpiry
|
||||
}
|
||||
if opts.ClientCertRotationAge != nil {
|
||||
ClientCertRotationAge = opts.ClientCertRotationAge
|
||||
}
|
||||
if opts.RequestLatency != nil {
|
||||
RequestLatency = opts.RequestLatency
|
||||
}
|
||||
if opts.RequestResult != nil {
|
||||
RequestResult = opts.RequestResult
|
||||
}
|
||||
RequestLatency = lm
|
||||
RequestResult = rm
|
||||
})
|
||||
}
|
||||
|
||||
type noopDuration struct{}
|
||||
|
||||
func (noopDuration) Observe(time.Duration) {}
|
||||
|
||||
type noopExpiry struct{}
|
||||
|
||||
func (noopExpiry) Set(*time.Time) {}
|
||||
|
||||
type noopLatency struct{}
|
||||
|
||||
func (noopLatency) Observe(string, url.URL, time.Duration) {}
|
||||
|
||||
@@ -120,7 +120,7 @@ func (rw *RetryWatcher) doReceive() (bool, time.Duration) {
|
||||
|
||||
default:
|
||||
msg := "Watch failed: %v"
|
||||
if net.IsProbableEOF(err) {
|
||||
if net.IsProbableEOF(err) || net.IsTimeout(err) {
|
||||
klog.V(5).Infof(msg, err)
|
||||
// Retry
|
||||
return false, 0
|
||||
|
||||
@@ -112,15 +112,18 @@ type Config struct {
|
||||
// initialized using a generic, multi-use cert/key pair which will be
|
||||
// quickly replaced with a unique cert/key pair.
|
||||
BootstrapKeyPEM []byte
|
||||
// CertificateExpiration will record a metric that shows the remaining
|
||||
// lifetime of the certificate. This metric is a gauge because only the
|
||||
// current cert expiry time is really useful. Reading this metric at any
|
||||
// time simply gives the next expiration date, no need to keep some
|
||||
// history (histogram) of all previous expiry dates.
|
||||
CertificateExpiration Gauge
|
||||
// CertificateRotation will record a metric showing the time in seconds
|
||||
// that certificates lived before being rotated. This metric is a histogram
|
||||
// because there is value in keeping a history of rotation cadences. It
|
||||
// allows one to setup monitoring and alerting of unexpected rotation
|
||||
// behavior and track trends in rotation frequency.
|
||||
CertificateRotation Histogram
|
||||
// CertifcateRenewFailure will record a metric that keeps track of
|
||||
// certificate renewal failures.
|
||||
CertificateRenewFailure Counter
|
||||
}
|
||||
|
||||
// Store is responsible for getting and updating the current certificate.
|
||||
@@ -151,11 +154,6 @@ type Histogram interface {
|
||||
Observe(float64)
|
||||
}
|
||||
|
||||
// Counter will wrap a counter with labels
|
||||
type Counter interface {
|
||||
Inc()
|
||||
}
|
||||
|
||||
// NoCertKeyError indicates there is no cert/key currently available.
|
||||
type NoCertKeyError string
|
||||
|
||||
@@ -179,8 +177,8 @@ type manager struct {
|
||||
|
||||
certStore Store
|
||||
|
||||
certificateRotation Histogram
|
||||
certificateRenewFailure Counter
|
||||
certificateExpiration Gauge
|
||||
certificateRotation Histogram
|
||||
|
||||
// the following variables must only be accessed under certAccessLock
|
||||
certAccessLock sync.RWMutex
|
||||
@@ -215,17 +213,17 @@ func NewManager(config *Config) (Manager, error) {
|
||||
}
|
||||
|
||||
m := manager{
|
||||
stopCh: make(chan struct{}),
|
||||
clientFn: config.ClientFn,
|
||||
getTemplate: getTemplate,
|
||||
dynamicTemplate: config.GetTemplate != nil,
|
||||
usages: config.Usages,
|
||||
certStore: config.CertificateStore,
|
||||
cert: cert,
|
||||
forceRotation: forceRotation,
|
||||
certificateRotation: config.CertificateRotation,
|
||||
certificateRenewFailure: config.CertificateRenewFailure,
|
||||
now: time.Now,
|
||||
stopCh: make(chan struct{}),
|
||||
clientFn: config.ClientFn,
|
||||
getTemplate: getTemplate,
|
||||
dynamicTemplate: config.GetTemplate != nil,
|
||||
usages: config.Usages,
|
||||
certStore: config.CertificateStore,
|
||||
cert: cert,
|
||||
forceRotation: forceRotation,
|
||||
certificateExpiration: config.CertificateExpiration,
|
||||
certificateRotation: config.CertificateRotation,
|
||||
now: time.Now,
|
||||
}
|
||||
|
||||
return &m, nil
|
||||
@@ -406,9 +404,6 @@ func (m *manager) rotateCerts() (bool, error) {
|
||||
template, csrPEM, keyPEM, privateKey, err := m.generateCSR()
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("Unable to generate a certificate signing request: %v", err))
|
||||
if m.certificateRenewFailure != nil {
|
||||
m.certificateRenewFailure.Inc()
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -416,9 +411,6 @@ func (m *manager) rotateCerts() (bool, error) {
|
||||
client, err := m.getClient()
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("Unable to load a client to request certificates: %v", err))
|
||||
if m.certificateRenewFailure != nil {
|
||||
m.certificateRenewFailure.Inc()
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -427,9 +419,6 @@ func (m *manager) rotateCerts() (bool, error) {
|
||||
req, err := csr.RequestCertificate(client, csrPEM, "", m.usages, privateKey)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("Failed while requesting a signed certificate from the master: %v", err))
|
||||
if m.certificateRenewFailure != nil {
|
||||
m.certificateRenewFailure.Inc()
|
||||
}
|
||||
return false, m.updateServerError(err)
|
||||
}
|
||||
|
||||
@@ -444,18 +433,12 @@ func (m *manager) rotateCerts() (bool, error) {
|
||||
crtPEM, err := csr.WaitForCertificate(ctx, client, req)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("certificate request was not signed: %v", err))
|
||||
if m.certificateRenewFailure != nil {
|
||||
m.certificateRenewFailure.Inc()
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
cert, err := m.certStore.Update(crtPEM, keyPEM)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("Unable to store the new cert/key pair: %v", err))
|
||||
if m.certificateRenewFailure != nil {
|
||||
m.certificateRenewFailure.Inc()
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -546,6 +529,9 @@ func (m *manager) nextRotationDeadline() time.Time {
|
||||
deadline := m.cert.Leaf.NotBefore.Add(jitteryDuration(totalDuration))
|
||||
|
||||
klog.V(2).Infof("Certificate expiration is %v, rotation deadline is %v", notAfter, deadline)
|
||||
if m.certificateExpiration != nil {
|
||||
m.certificateExpiration.Set(float64(notAfter.Unix()))
|
||||
}
|
||||
return deadline
|
||||
}
|
||||
|
||||
|
||||
@@ -200,6 +200,7 @@ func TestSetRotationDeadline(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
g := metricMock{}
|
||||
m := manager{
|
||||
cert: &tls.Certificate{
|
||||
Leaf: &x509.Certificate{
|
||||
@@ -207,9 +208,10 @@ func TestSetRotationDeadline(t *testing.T) {
|
||||
NotAfter: tc.notAfter,
|
||||
},
|
||||
},
|
||||
getTemplate: func() *x509.CertificateRequest { return &x509.CertificateRequest{} },
|
||||
usages: []certificates.KeyUsage{},
|
||||
now: func() time.Time { return now },
|
||||
getTemplate: func() *x509.CertificateRequest { return &x509.CertificateRequest{} },
|
||||
usages: []certificates.KeyUsage{},
|
||||
certificateExpiration: &g,
|
||||
now: func() time.Time { return now },
|
||||
}
|
||||
jitteryDuration = func(float64) time.Duration { return time.Duration(float64(tc.notAfter.Sub(tc.notBefore)) * 0.7) }
|
||||
lowerBound := tc.notBefore.Add(time.Duration(float64(tc.notAfter.Sub(tc.notBefore)) * 0.7))
|
||||
@@ -223,6 +225,12 @@ func TestSetRotationDeadline(t *testing.T) {
|
||||
deadline,
|
||||
lowerBound)
|
||||
}
|
||||
if g.calls != 1 {
|
||||
t.Errorf("%d metrics were recorded, wanted %d", g.calls, 1)
|
||||
}
|
||||
if g.lastValue != float64(tc.notAfter.Unix()) {
|
||||
t.Errorf("%f value for metric was recorded, wanted %d", g.lastValue, tc.notAfter.Unix())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,11 +77,6 @@ func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.
|
||||
|
||||
closable := &closableConn{Conn: conn}
|
||||
|
||||
// Start tracking the connection
|
||||
d.mu.Lock()
|
||||
d.conns[closable] = struct{}{}
|
||||
d.mu.Unlock()
|
||||
|
||||
// When the connection is closed, remove it from the map. This will
|
||||
// be no-op if the connection isn't in the map, e.g. if CloseAll()
|
||||
// is called.
|
||||
@@ -91,6 +86,11 @@ func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.
|
||||
d.mu.Unlock()
|
||||
}
|
||||
|
||||
// Start tracking the connection
|
||||
d.mu.Lock()
|
||||
d.conns[closable] = struct{}{}
|
||||
d.mu.Unlock()
|
||||
|
||||
return closable, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ package connrotation
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -50,6 +52,73 @@ func TestCloseAll(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestCloseAllRace ensures CloseAll works with connections being simultaneously dialed
|
||||
func TestCloseAllRace(t *testing.T) {
|
||||
conns := int64(0)
|
||||
dialer := NewDialer(func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
return closeOnlyConn{onClose: func() { atomic.AddInt64(&conns, -1) }}, nil
|
||||
})
|
||||
|
||||
done := make(chan struct{})
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
// Close all as fast as we can
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
dialer.CloseAll()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Dial as fast as we can
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
if _, err := dialer.Dial("", ""); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
atomic.AddInt64(&conns, 1)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Soak to ensure no races
|
||||
time.Sleep(time.Second)
|
||||
|
||||
// Signal completion
|
||||
close(done)
|
||||
// Wait for goroutines
|
||||
wg.Wait()
|
||||
// Ensure CloseAll ran after all dials
|
||||
dialer.CloseAll()
|
||||
|
||||
// Expect all connections to close within 5 seconds
|
||||
for start := time.Now(); time.Now().Sub(start) < 5*time.Second; time.Sleep(10 * time.Millisecond) {
|
||||
// Ensure all connections were closed
|
||||
if c := atomic.LoadInt64(&conns); c == 0 {
|
||||
break
|
||||
} else {
|
||||
t.Logf("got %d open connections, want 0, will retry", c)
|
||||
}
|
||||
}
|
||||
// Ensure all connections were closed
|
||||
if c := atomic.LoadInt64(&conns); c != 0 {
|
||||
t.Fatalf("got %d open connections, want 0", c)
|
||||
}
|
||||
}
|
||||
|
||||
type closeOnlyConn struct {
|
||||
net.Conn
|
||||
onClose func()
|
||||
|
||||
Reference in New Issue
Block a user