Upgrade k8s package verison (#5358)
* upgrade k8s package version Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io> * Script upgrade and code formatting. Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io> Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>
This commit is contained in:
4
vendor/k8s.io/apiserver/pkg/storage/etcd3/OWNERS
generated
vendored
4
vendor/k8s.io/apiserver/pkg/storage/etcd3/OWNERS
generated
vendored
@@ -1,6 +1,4 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
reviewers:
|
||||
- wojtek-t
|
||||
- timothysc
|
||||
- hongchaodeng
|
||||
- wojtek-t
|
||||
|
||||
131
vendor/k8s.io/apiserver/pkg/storage/etcd3/api_object_versioner.go
generated
vendored
131
vendor/k8s.io/apiserver/pkg/storage/etcd3/api_object_versioner.go
generated
vendored
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
Copyright 2014 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 etcd3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
)
|
||||
|
||||
// APIObjectVersioner implements versioning and extracting etcd node information
|
||||
// for objects that have an embedded ObjectMeta or ListMeta field.
|
||||
type APIObjectVersioner struct{}
|
||||
|
||||
// UpdateObject implements Versioner
|
||||
func (a APIObjectVersioner) UpdateObject(obj runtime.Object, resourceVersion uint64) error {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
versionString := ""
|
||||
if resourceVersion != 0 {
|
||||
versionString = strconv.FormatUint(resourceVersion, 10)
|
||||
}
|
||||
accessor.SetResourceVersion(versionString)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateList implements Versioner
|
||||
func (a APIObjectVersioner) UpdateList(obj runtime.Object, resourceVersion uint64, nextKey string, count *int64) error {
|
||||
if resourceVersion == 0 {
|
||||
return fmt.Errorf("illegal resource version from storage: %d", resourceVersion)
|
||||
}
|
||||
listAccessor, err := meta.ListAccessor(obj)
|
||||
if err != nil || listAccessor == nil {
|
||||
return err
|
||||
}
|
||||
versionString := strconv.FormatUint(resourceVersion, 10)
|
||||
listAccessor.SetResourceVersion(versionString)
|
||||
listAccessor.SetContinue(nextKey)
|
||||
listAccessor.SetRemainingItemCount(count)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrepareObjectForStorage clears resource version and self link prior to writing to etcd.
|
||||
func (a APIObjectVersioner) PrepareObjectForStorage(obj runtime.Object) error {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
accessor.SetResourceVersion("")
|
||||
accessor.SetSelfLink("")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ObjectResourceVersion implements Versioner
|
||||
func (a APIObjectVersioner) ObjectResourceVersion(obj runtime.Object) (uint64, error) {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
version := accessor.GetResourceVersion()
|
||||
if len(version) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
return strconv.ParseUint(version, 10, 64)
|
||||
}
|
||||
|
||||
// ParseResourceVersion takes a resource version argument and converts it to
|
||||
// the etcd version. For watch we should pass to helper.Watch(). Because resourceVersion is
|
||||
// an opaque value, the default watch behavior for non-zero watch is to watch
|
||||
// the next value (if you pass "1", you will see updates from "2" onwards).
|
||||
func (a APIObjectVersioner) ParseResourceVersion(resourceVersion string) (uint64, error) {
|
||||
if resourceVersion == "" || resourceVersion == "0" {
|
||||
return 0, nil
|
||||
}
|
||||
version, err := strconv.ParseUint(resourceVersion, 10, 64)
|
||||
if err != nil {
|
||||
return 0, storage.NewInvalidError(field.ErrorList{
|
||||
// Validation errors are supposed to return version-specific field
|
||||
// paths, but this is probably close enough.
|
||||
field.Invalid(field.NewPath("resourceVersion"), resourceVersion, err.Error()),
|
||||
})
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
// Versioner implements Versioner
|
||||
var Versioner storage.Versioner = APIObjectVersioner{}
|
||||
|
||||
// CompareResourceVersion compares etcd resource versions. Outside this API they are all strings,
|
||||
// but etcd resource versions are special, they're actually ints, so we can easily compare them.
|
||||
func (a APIObjectVersioner) CompareResourceVersion(lhs, rhs runtime.Object) int {
|
||||
lhsVersion, err := Versioner.ObjectResourceVersion(lhs)
|
||||
if err != nil {
|
||||
// coder error
|
||||
panic(err)
|
||||
}
|
||||
rhsVersion, err := Versioner.ObjectResourceVersion(rhs)
|
||||
if err != nil {
|
||||
// coder error
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if lhsVersion == rhsVersion {
|
||||
return 0
|
||||
}
|
||||
if lhsVersion < rhsVersion {
|
||||
return -1
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
2
vendor/k8s.io/apiserver/pkg/storage/etcd3/compact.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/storage/etcd3/compact.go
generated
vendored
@@ -22,7 +22,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.etcd.io/etcd/clientv3"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
|
||||
5
vendor/k8s.io/apiserver/pkg/storage/etcd3/errors.go
generated
vendored
5
vendor/k8s.io/apiserver/pkg/storage/etcd3/errors.go
generated
vendored
@@ -18,8 +18,9 @@ package etcd3
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
|
||||
etcdrpc "go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes"
|
||||
etcdrpc "go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
)
|
||||
|
||||
@@ -60,7 +61,7 @@ func handleCompactedErrorForPaging(continueKey, keyPrefix string) error {
|
||||
// continueToken.ResoureVersion=-1 means that the apiserver can
|
||||
// continue the list at the latest resource version. We don't use rv=0
|
||||
// for this purpose to distinguish from a bad token that has empty rv.
|
||||
newToken, err := encodeContinue(continueKey, keyPrefix, -1)
|
||||
newToken, err := storage.EncodeContinue(continueKey, keyPrefix, -1)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
return errors.NewResourceExpired(continueExpired)
|
||||
|
||||
4
vendor/k8s.io/apiserver/pkg/storage/etcd3/event.go
generated
vendored
4
vendor/k8s.io/apiserver/pkg/storage/etcd3/event.go
generated
vendored
@@ -18,8 +18,8 @@ package etcd3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go.etcd.io/etcd/clientv3"
|
||||
"go.etcd.io/etcd/mvcc/mvccpb"
|
||||
"go.etcd.io/etcd/api/v3/mvccpb"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
)
|
||||
|
||||
type event struct {
|
||||
|
||||
108
vendor/k8s.io/apiserver/pkg/storage/etcd3/latency_tracker.go
generated
vendored
Normal file
108
vendor/k8s.io/apiserver/pkg/storage/etcd3/latency_tracker.go
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
Copyright 2022 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 etcd3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
endpointsrequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
)
|
||||
|
||||
// NewETCDLatencyTracker returns an implementation of
|
||||
// clientv3.KV that times the calls from the specified
|
||||
// 'delegate' KV instance in order to track latency incurred.
|
||||
func NewETCDLatencyTracker(delegate clientv3.KV) clientv3.KV {
|
||||
return &clientV3KVLatencyTracker{KV: delegate}
|
||||
}
|
||||
|
||||
// clientV3KVLatencyTracker decorates a clientv3.KV instance and times
|
||||
// each call so we can track the latency an API request incurs in etcd
|
||||
// round trips (the time it takes to send data to etcd and get the
|
||||
// complete response back)
|
||||
//
|
||||
// If an API request involves N (N>=1) round trips to etcd, then we will sum
|
||||
// up the latenciy incurred in each roundtrip.
|
||||
|
||||
// It uses the context associated with the request in flight, so there
|
||||
// are no states shared among the requests in flight, and so there is no
|
||||
// concurrency overhead.
|
||||
// If the goroutine executing the request handler makes concurrent calls
|
||||
// to the underlying storage layer, that is protected since the latency
|
||||
// tracking function TrackStorageLatency is thread safe.
|
||||
//
|
||||
// NOTE: Compact is an asynchronous process and is not associated with
|
||||
//
|
||||
// any request, so we will not be tracking its latency.
|
||||
type clientV3KVLatencyTracker struct {
|
||||
clientv3.KV
|
||||
}
|
||||
|
||||
func (c *clientV3KVLatencyTracker) Put(ctx context.Context, key, val string, opts ...clientv3.OpOption) (*clientv3.PutResponse, error) {
|
||||
startedAt := time.Now()
|
||||
defer func() {
|
||||
endpointsrequest.TrackStorageLatency(ctx, time.Since(startedAt))
|
||||
}()
|
||||
|
||||
return c.KV.Put(ctx, key, val, opts...)
|
||||
}
|
||||
|
||||
func (c *clientV3KVLatencyTracker) Get(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error) {
|
||||
startedAt := time.Now()
|
||||
defer func() {
|
||||
endpointsrequest.TrackStorageLatency(ctx, time.Since(startedAt))
|
||||
}()
|
||||
|
||||
return c.KV.Get(ctx, key, opts...)
|
||||
}
|
||||
|
||||
func (c *clientV3KVLatencyTracker) Delete(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.DeleteResponse, error) {
|
||||
startedAt := time.Now()
|
||||
defer func() {
|
||||
endpointsrequest.TrackStorageLatency(ctx, time.Since(startedAt))
|
||||
}()
|
||||
|
||||
return c.KV.Delete(ctx, key, opts...)
|
||||
}
|
||||
|
||||
func (c *clientV3KVLatencyTracker) Do(ctx context.Context, op clientv3.Op) (clientv3.OpResponse, error) {
|
||||
startedAt := time.Now()
|
||||
defer func() {
|
||||
endpointsrequest.TrackStorageLatency(ctx, time.Since(startedAt))
|
||||
}()
|
||||
|
||||
return c.KV.Do(ctx, op)
|
||||
}
|
||||
|
||||
func (c *clientV3KVLatencyTracker) Txn(ctx context.Context) clientv3.Txn {
|
||||
return &clientV3TxnTracker{ctx: ctx, Txn: c.KV.Txn(ctx)}
|
||||
}
|
||||
|
||||
type clientV3TxnTracker struct {
|
||||
ctx context.Context
|
||||
clientv3.Txn
|
||||
}
|
||||
|
||||
func (t *clientV3TxnTracker) Commit() (*clientv3.TxnResponse, error) {
|
||||
startedAt := time.Now()
|
||||
defer func() {
|
||||
endpointsrequest.TrackStorageLatency(t.ctx, time.Since(startedAt))
|
||||
}()
|
||||
|
||||
return t.Txn.Commit()
|
||||
}
|
||||
2
vendor/k8s.io/apiserver/pkg/storage/etcd3/lease_manager.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/storage/etcd3/lease_manager.go
generated
vendored
@@ -21,7 +21,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.etcd.io/etcd/clientv3"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
"k8s.io/apiserver/pkg/storage/etcd3/metrics"
|
||||
)
|
||||
|
||||
|
||||
16
vendor/k8s.io/apiserver/pkg/storage/etcd3/logger.go
generated
vendored
16
vendor/k8s.io/apiserver/pkg/storage/etcd3/logger.go
generated
vendored
@@ -19,12 +19,12 @@ package etcd3
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"go.etcd.io/etcd/clientv3"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
clientv3.SetLogger(klogWrapper{})
|
||||
grpclog.SetLoggerV2(klogWrapper{})
|
||||
}
|
||||
|
||||
type klogWrapper struct{}
|
||||
@@ -32,15 +32,21 @@ type klogWrapper struct{}
|
||||
const klogWrapperDepth = 4
|
||||
|
||||
func (klogWrapper) Info(args ...interface{}) {
|
||||
klog.InfoDepth(klogWrapperDepth, args...)
|
||||
if klogV := klog.V(5); klogV.Enabled() {
|
||||
klogV.InfoSDepth(klogWrapperDepth, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (klogWrapper) Infoln(args ...interface{}) {
|
||||
klog.InfoDepth(klogWrapperDepth, fmt.Sprintln(args...))
|
||||
if klogV := klog.V(5); klogV.Enabled() {
|
||||
klogV.InfoSDepth(klogWrapperDepth, fmt.Sprintln(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (klogWrapper) Infof(format string, args ...interface{}) {
|
||||
klog.InfoDepth(klogWrapperDepth, fmt.Sprintf(format, args...))
|
||||
if klogV := klog.V(5); klogV.Enabled() {
|
||||
klog.V(5).InfoSDepth(klogWrapperDepth, fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (klogWrapper) Warning(args ...interface{}) {
|
||||
|
||||
65
vendor/k8s.io/apiserver/pkg/storage/etcd3/metrics/metrics.go
generated
vendored
65
vendor/k8s.io/apiserver/pkg/storage/etcd3/metrics/metrics.go
generated
vendored
@@ -26,7 +26,7 @@ import (
|
||||
|
||||
/*
|
||||
* By default, all the following metrics are defined as falling under
|
||||
* ALPHA stability level https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/1209-metrics-stability/20190404-kubernetes-control-plane-metrics-stability.md#stability-classes)
|
||||
* ALPHA stability level https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/1209-metrics-stability/kubernetes-control-plane-metrics-stability.md#stability-classes)
|
||||
*
|
||||
* Promoting the stability level of the metric is a responsibility of the component owner, since it
|
||||
* involves explicitly acknowledging support for the metric across multiple releases, in accordance with
|
||||
@@ -38,20 +38,15 @@ var (
|
||||
Name: "etcd_request_duration_seconds",
|
||||
Help: "Etcd request latency in seconds for each operation and object type.",
|
||||
// Etcd request latency in seconds for each operation and object type.
|
||||
Buckets: []float64{0.005, 0.025, 0.1, 0.25, 0.5, 1.0, 2.0, 4.0, 15.0, 30.0, 60.0},
|
||||
// This metric is used for verifying etcd api call latencies SLO
|
||||
// keep consistent with apiserver metric 'requestLatencies' in
|
||||
// staging/src/k8s.io/apiserver/pkg/endpoints/metrics/metrics.go
|
||||
Buckets: []float64{0.005, 0.025, 0.05, 0.1, 0.2, 0.4, 0.6, 0.8, 1.0, 1.25, 1.5, 2, 3,
|
||||
4, 5, 6, 8, 10, 15, 20, 30, 45, 60},
|
||||
StabilityLevel: compbasemetrics.ALPHA,
|
||||
},
|
||||
[]string{"operation", "type"},
|
||||
)
|
||||
etcdObjectCounts = compbasemetrics.NewGaugeVec(
|
||||
&compbasemetrics.GaugeOpts{
|
||||
Name: "etcd_object_counts",
|
||||
DeprecatedVersion: "1.22.0",
|
||||
Help: "Number of stored objects at the time of last check split by kind. This metric is replaced by apiserver_storage_object_counts.",
|
||||
StabilityLevel: compbasemetrics.ALPHA,
|
||||
},
|
||||
[]string{"resource"},
|
||||
)
|
||||
objectCounts = compbasemetrics.NewGaugeVec(
|
||||
&compbasemetrics.GaugeOpts{
|
||||
Name: "apiserver_storage_objects",
|
||||
@@ -85,6 +80,38 @@ var (
|
||||
},
|
||||
[]string{},
|
||||
)
|
||||
listStorageCount = compbasemetrics.NewCounterVec(
|
||||
&compbasemetrics.CounterOpts{
|
||||
Name: "apiserver_storage_list_total",
|
||||
Help: "Number of LIST requests served from storage",
|
||||
StabilityLevel: compbasemetrics.ALPHA,
|
||||
},
|
||||
[]string{"resource"},
|
||||
)
|
||||
listStorageNumFetched = compbasemetrics.NewCounterVec(
|
||||
&compbasemetrics.CounterOpts{
|
||||
Name: "apiserver_storage_list_fetched_objects_total",
|
||||
Help: "Number of objects read from storage in the course of serving a LIST request",
|
||||
StabilityLevel: compbasemetrics.ALPHA,
|
||||
},
|
||||
[]string{"resource"},
|
||||
)
|
||||
listStorageNumSelectorEvals = compbasemetrics.NewCounterVec(
|
||||
&compbasemetrics.CounterOpts{
|
||||
Name: "apiserver_storage_list_evaluated_objects_total",
|
||||
Help: "Number of objects tested in the course of serving a LIST request from storage",
|
||||
StabilityLevel: compbasemetrics.ALPHA,
|
||||
},
|
||||
[]string{"resource"},
|
||||
)
|
||||
listStorageNumReturned = compbasemetrics.NewCounterVec(
|
||||
&compbasemetrics.CounterOpts{
|
||||
Name: "apiserver_storage_list_returned_objects_total",
|
||||
Help: "Number of objects returned for a LIST request from storage",
|
||||
StabilityLevel: compbasemetrics.ALPHA,
|
||||
},
|
||||
[]string{"resource"},
|
||||
)
|
||||
)
|
||||
|
||||
var registerMetrics sync.Once
|
||||
@@ -95,17 +122,19 @@ func Register() {
|
||||
registerMetrics.Do(func() {
|
||||
legacyregistry.MustRegister(etcdRequestLatency)
|
||||
legacyregistry.MustRegister(objectCounts)
|
||||
legacyregistry.MustRegister(etcdObjectCounts)
|
||||
legacyregistry.MustRegister(dbTotalSize)
|
||||
legacyregistry.MustRegister(etcdBookmarkCounts)
|
||||
legacyregistry.MustRegister(etcdLeaseObjectCounts)
|
||||
legacyregistry.MustRegister(listStorageCount)
|
||||
legacyregistry.MustRegister(listStorageNumFetched)
|
||||
legacyregistry.MustRegister(listStorageNumSelectorEvals)
|
||||
legacyregistry.MustRegister(listStorageNumReturned)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateObjectCount sets the apiserver_storage_object_counts and etcd_object_counts (deprecated) metric.
|
||||
// UpdateObjectCount sets the apiserver_storage_object_counts metric.
|
||||
func UpdateObjectCount(resourcePrefix string, count int64) {
|
||||
objectCounts.WithLabelValues(resourcePrefix).Set(float64(count))
|
||||
etcdObjectCounts.WithLabelValues(resourcePrefix).Set(float64(count))
|
||||
}
|
||||
|
||||
// RecordEtcdRequestLatency sets the etcd_request_duration_seconds metrics.
|
||||
@@ -139,3 +168,11 @@ func UpdateLeaseObjectCount(count int64) {
|
||||
// See pkg/storage/etcd3/lease_manager.go
|
||||
etcdLeaseObjectCounts.WithLabelValues().Observe(float64(count))
|
||||
}
|
||||
|
||||
// RecordListEtcd3Metrics notes various metrics of the cost to serve a LIST request
|
||||
func RecordStorageListMetrics(resource string, numFetched, numEvald, numReturned int) {
|
||||
listStorageCount.WithLabelValues(resource).Inc()
|
||||
listStorageNumFetched.WithLabelValues(resource).Add(float64(numFetched))
|
||||
listStorageNumSelectorEvals.WithLabelValues(resource).Add(float64(numEvald))
|
||||
listStorageNumReturned.WithLabelValues(resource).Add(float64(numReturned))
|
||||
}
|
||||
|
||||
331
vendor/k8s.io/apiserver/pkg/storage/etcd3/store.go
generated
vendored
331
vendor/k8s.io/apiserver/pkg/storage/etcd3/store.go
generated
vendored
@@ -19,8 +19,6 @@ package etcd3
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
@@ -28,16 +26,18 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.etcd.io/etcd/clientv3"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/conversion"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
endpointsrequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/apiserver/pkg/storage/etcd3/metrics"
|
||||
@@ -47,6 +47,13 @@ import (
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
// maxLimit is a maximum page limit increase used when fetching objects from etcd.
|
||||
// This limit is used only for increasing page size by kube-apiserver. If request
|
||||
// specifies larger limit initially, it won't be changed.
|
||||
maxLimit = 10000
|
||||
)
|
||||
|
||||
// authenticatedDataString satisfies the value.Context interface. It uses the key to
|
||||
// authenticate the stored data. This does not defend against reuse of previously
|
||||
// encrypted values under the same key, but will prevent an attacker from using an
|
||||
@@ -64,14 +71,16 @@ func (d authenticatedDataString) AuthenticatedData() []byte {
|
||||
var _ value.Context = authenticatedDataString("")
|
||||
|
||||
type store struct {
|
||||
client *clientv3.Client
|
||||
codec runtime.Codec
|
||||
versioner storage.Versioner
|
||||
transformer value.Transformer
|
||||
pathPrefix string
|
||||
watcher *watcher
|
||||
pagingEnabled bool
|
||||
leaseManager *leaseManager
|
||||
client *clientv3.Client
|
||||
codec runtime.Codec
|
||||
versioner storage.Versioner
|
||||
transformer value.Transformer
|
||||
pathPrefix string
|
||||
groupResource schema.GroupResource
|
||||
groupResourceString string
|
||||
watcher *watcher
|
||||
pagingEnabled bool
|
||||
leaseManager *leaseManager
|
||||
}
|
||||
|
||||
type objState struct {
|
||||
@@ -83,12 +92,12 @@ type objState struct {
|
||||
}
|
||||
|
||||
// New returns an etcd3 implementation of storage.Interface.
|
||||
func New(c *clientv3.Client, codec runtime.Codec, newFunc func() runtime.Object, prefix string, transformer value.Transformer, pagingEnabled bool, leaseManagerConfig LeaseManagerConfig) storage.Interface {
|
||||
return newStore(c, codec, newFunc, prefix, transformer, pagingEnabled, leaseManagerConfig)
|
||||
func New(c *clientv3.Client, codec runtime.Codec, newFunc func() runtime.Object, prefix string, groupResource schema.GroupResource, transformer value.Transformer, pagingEnabled bool, leaseManagerConfig LeaseManagerConfig) storage.Interface {
|
||||
return newStore(c, codec, newFunc, prefix, groupResource, transformer, pagingEnabled, leaseManagerConfig)
|
||||
}
|
||||
|
||||
func newStore(c *clientv3.Client, codec runtime.Codec, newFunc func() runtime.Object, prefix string, transformer value.Transformer, pagingEnabled bool, leaseManagerConfig LeaseManagerConfig) *store {
|
||||
versioner := APIObjectVersioner{}
|
||||
func newStore(c *clientv3.Client, codec runtime.Codec, newFunc func() runtime.Object, prefix string, groupResource schema.GroupResource, transformer value.Transformer, pagingEnabled bool, leaseManagerConfig LeaseManagerConfig) *store {
|
||||
versioner := storage.APIObjectVersioner{}
|
||||
result := &store{
|
||||
client: c,
|
||||
codec: codec,
|
||||
@@ -98,9 +107,11 @@ func newStore(c *clientv3.Client, codec runtime.Codec, newFunc func() runtime.Ob
|
||||
// for compatibility with etcd2 impl.
|
||||
// no-op for default prefix of '/registry'.
|
||||
// keeps compatibility with etcd2 impl for custom prefixes that don't start with '/'
|
||||
pathPrefix: path.Join("/", prefix),
|
||||
watcher: newWatcher(c, codec, newFunc, versioner, transformer),
|
||||
leaseManager: newDefaultLeaseManager(c, leaseManagerConfig),
|
||||
pathPrefix: path.Join("/", prefix),
|
||||
groupResource: groupResource,
|
||||
groupResourceString: groupResource.String(),
|
||||
watcher: newWatcher(c, codec, newFunc, versioner, transformer),
|
||||
leaseManager: newDefaultLeaseManager(c, leaseManagerConfig),
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -131,7 +142,7 @@ func (s *store) Get(ctx context.Context, key string, opts storage.GetOptions, ou
|
||||
}
|
||||
kv := getResp.Kvs[0]
|
||||
|
||||
data, _, err := s.transformer.TransformFromStorage(kv.Value, authenticatedDataString(key))
|
||||
data, _, err := s.transformer.TransformFromStorage(ctx, kv.Value, authenticatedDataString(key))
|
||||
if err != nil {
|
||||
return storage.NewInternalError(err.Error())
|
||||
}
|
||||
@@ -141,13 +152,21 @@ func (s *store) Get(ctx context.Context, key string, opts storage.GetOptions, ou
|
||||
|
||||
// Create implements storage.Interface.Create.
|
||||
func (s *store) Create(ctx context.Context, key string, obj, out runtime.Object, ttl uint64) error {
|
||||
trace := utiltrace.New("Create etcd3",
|
||||
utiltrace.Field{"audit-id", endpointsrequest.GetAuditIDTruncated(ctx)},
|
||||
utiltrace.Field{"key", key},
|
||||
utiltrace.Field{"type", getTypeName(obj)},
|
||||
)
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
if version, err := s.versioner.ObjectResourceVersion(obj); err == nil && version != 0 {
|
||||
return errors.New("resourceVersion should not be set on objects to be created")
|
||||
}
|
||||
if err := s.versioner.PrepareObjectForStorage(obj); err != nil {
|
||||
return fmt.Errorf("PrepareObjectForStorage failed: %v", err)
|
||||
}
|
||||
trace.Step("About to Encode")
|
||||
data, err := runtime.Encode(s.codec, obj)
|
||||
trace.Step("Encode finished", utiltrace.Field{"len", len(data)}, utiltrace.Field{"err", err})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -158,7 +177,8 @@ func (s *store) Create(ctx context.Context, key string, obj, out runtime.Object,
|
||||
return err
|
||||
}
|
||||
|
||||
newData, err := s.transformer.TransformToStorage(data, authenticatedDataString(key))
|
||||
newData, err := s.transformer.TransformToStorage(ctx, data, authenticatedDataString(key))
|
||||
trace.Step("TransformToStorage finished", utiltrace.Field{"err", err})
|
||||
if err != nil {
|
||||
return storage.NewInternalError(err.Error())
|
||||
}
|
||||
@@ -170,16 +190,20 @@ func (s *store) Create(ctx context.Context, key string, obj, out runtime.Object,
|
||||
clientv3.OpPut(key, string(newData), opts...),
|
||||
).Commit()
|
||||
metrics.RecordEtcdRequestLatency("create", getTypeName(obj), startTime)
|
||||
trace.Step("Txn call finished", utiltrace.Field{"err", err})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !txnResp.Succeeded {
|
||||
return storage.NewKeyExistsError(key, 0)
|
||||
}
|
||||
|
||||
if out != nil {
|
||||
putResp := txnResp.Responses[0].GetResponsePut()
|
||||
return decode(s.codec, s.versioner, data, out, putResp.Header.Revision)
|
||||
err = decode(s.codec, s.versioner, data, out, putResp.Header.Revision)
|
||||
trace.Step("decode finished", utiltrace.Field{"len", len(data)}, utiltrace.Field{"err", err})
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -206,7 +230,7 @@ func (s *store) conditionalDelete(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.getState(getResp, key, v, false)
|
||||
return s.getState(ctx, getResp, key, v, false)
|
||||
}
|
||||
|
||||
var origState *objState
|
||||
@@ -230,12 +254,22 @@ func (s *store) conditionalDelete(
|
||||
}
|
||||
|
||||
// It's possible we're working with stale data.
|
||||
// Remember the revision of the potentially stale data and the resulting update error
|
||||
cachedRev := origState.rev
|
||||
cachedUpdateErr := err
|
||||
|
||||
// Actually fetch
|
||||
origState, err = getCurrentState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
origStateIsCurrent = true
|
||||
|
||||
// it turns out our cached data was not stale, return the error
|
||||
if cachedRev == origState.rev {
|
||||
return cachedUpdateErr
|
||||
}
|
||||
|
||||
// Retry
|
||||
continue
|
||||
}
|
||||
@@ -246,12 +280,22 @@ func (s *store) conditionalDelete(
|
||||
}
|
||||
|
||||
// It's possible we're working with stale data.
|
||||
// Remember the revision of the potentially stale data and the resulting update error
|
||||
cachedRev := origState.rev
|
||||
cachedUpdateErr := err
|
||||
|
||||
// Actually fetch
|
||||
origState, err = getCurrentState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
origStateIsCurrent = true
|
||||
|
||||
// it turns out our cached data was not stale, return the error
|
||||
if cachedRev == origState.rev {
|
||||
return cachedUpdateErr
|
||||
}
|
||||
|
||||
// Retry
|
||||
continue
|
||||
}
|
||||
@@ -271,7 +315,7 @@ func (s *store) conditionalDelete(
|
||||
if !txnResp.Succeeded {
|
||||
getResp := (*clientv3.GetResponse)(txnResp.Responses[0].GetResponseRange())
|
||||
klog.V(4).Infof("deletion of %s failed because of a conflict, going to retry", key)
|
||||
origState, err = s.getState(getResp, key, v, false)
|
||||
origState, err = s.getState(ctx, getResp, key, v, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -284,12 +328,15 @@ func (s *store) conditionalDelete(
|
||||
|
||||
// GuaranteedUpdate implements storage.Interface.GuaranteedUpdate.
|
||||
func (s *store) GuaranteedUpdate(
|
||||
ctx context.Context, key string, out runtime.Object, ignoreNotFound bool,
|
||||
ctx context.Context, key string, destination runtime.Object, ignoreNotFound bool,
|
||||
preconditions *storage.Preconditions, tryUpdate storage.UpdateFunc, cachedExistingObject runtime.Object) error {
|
||||
trace := utiltrace.New("GuaranteedUpdate etcd3", utiltrace.Field{"type", getTypeName(out)})
|
||||
trace := utiltrace.New("GuaranteedUpdate etcd3",
|
||||
utiltrace.Field{"audit-id", endpointsrequest.GetAuditIDTruncated(ctx)},
|
||||
utiltrace.Field{"key", key},
|
||||
utiltrace.Field{"type", getTypeName(destination)})
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
v, err := conversion.EnforcePtr(out)
|
||||
v, err := conversion.EnforcePtr(destination)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to convert output object to pointer: %v", err)
|
||||
}
|
||||
@@ -298,11 +345,11 @@ func (s *store) GuaranteedUpdate(
|
||||
getCurrentState := func() (*objState, error) {
|
||||
startTime := time.Now()
|
||||
getResp, err := s.client.KV.Get(ctx, key)
|
||||
metrics.RecordEtcdRequestLatency("get", getTypeName(out), startTime)
|
||||
metrics.RecordEtcdRequestLatency("get", getTypeName(destination), startTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.getState(getResp, key, v, ignoreNotFound)
|
||||
return s.getState(ctx, getResp, key, v, ignoreNotFound)
|
||||
}
|
||||
|
||||
var origState *objState
|
||||
@@ -345,17 +392,29 @@ func (s *store) GuaranteedUpdate(
|
||||
}
|
||||
|
||||
// It's possible we were working with stale data
|
||||
// Remember the revision of the potentially stale data and the resulting update error
|
||||
cachedRev := origState.rev
|
||||
cachedUpdateErr := err
|
||||
|
||||
// Actually fetch
|
||||
origState, err = getCurrentState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
origStateIsCurrent = true
|
||||
|
||||
// it turns out our cached data was not stale, return the error
|
||||
if cachedRev == origState.rev {
|
||||
return cachedUpdateErr
|
||||
}
|
||||
|
||||
// Retry
|
||||
continue
|
||||
}
|
||||
|
||||
trace.Step("About to Encode")
|
||||
data, err := runtime.Encode(s.codec, ret)
|
||||
trace.Step("Encode finished", utiltrace.Field{"len", len(data)}, utiltrace.Field{"err", err})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -376,11 +435,12 @@ func (s *store) GuaranteedUpdate(
|
||||
}
|
||||
// recheck that the data from etcd is not stale before short-circuiting a write
|
||||
if !origState.stale {
|
||||
return decode(s.codec, s.versioner, origState.data, out, origState.rev)
|
||||
return decode(s.codec, s.versioner, origState.data, destination, origState.rev)
|
||||
}
|
||||
}
|
||||
|
||||
newData, err := s.transformer.TransformToStorage(data, transformContext)
|
||||
newData, err := s.transformer.TransformToStorage(ctx, data, transformContext)
|
||||
trace.Step("TransformToStorage finished", utiltrace.Field{"err", err})
|
||||
if err != nil {
|
||||
return storage.NewInternalError(err.Error())
|
||||
}
|
||||
@@ -399,7 +459,8 @@ func (s *store) GuaranteedUpdate(
|
||||
).Else(
|
||||
clientv3.OpGet(key),
|
||||
).Commit()
|
||||
metrics.RecordEtcdRequestLatency("update", getTypeName(out), startTime)
|
||||
metrics.RecordEtcdRequestLatency("update", getTypeName(destination), startTime)
|
||||
trace.Step("Txn call finished", utiltrace.Field{"err", err})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -407,7 +468,7 @@ func (s *store) GuaranteedUpdate(
|
||||
if !txnResp.Succeeded {
|
||||
getResp := (*clientv3.GetResponse)(txnResp.Responses[0].GetResponseRange())
|
||||
klog.V(4).Infof("GuaranteedUpdate of %s failed because of a conflict, going to retry", key)
|
||||
origState, err = s.getState(getResp, key, v, ignoreNotFound)
|
||||
origState, err = s.getState(ctx, getResp, key, v, ignoreNotFound)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -417,64 +478,10 @@ func (s *store) GuaranteedUpdate(
|
||||
}
|
||||
putResp := txnResp.Responses[0].GetResponsePut()
|
||||
|
||||
return decode(s.codec, s.versioner, data, out, putResp.Header.Revision)
|
||||
}
|
||||
}
|
||||
|
||||
// GetToList implements storage.Interface.GetToList.
|
||||
func (s *store) GetToList(ctx context.Context, key string, listOpts storage.ListOptions, listObj runtime.Object) error {
|
||||
resourceVersion := listOpts.ResourceVersion
|
||||
match := listOpts.ResourceVersionMatch
|
||||
pred := listOpts.Predicate
|
||||
trace := utiltrace.New("GetToList etcd3",
|
||||
utiltrace.Field{"key", key},
|
||||
utiltrace.Field{"resourceVersion", resourceVersion},
|
||||
utiltrace.Field{"resourceVersionMatch", match},
|
||||
utiltrace.Field{"limit", pred.Limit},
|
||||
utiltrace.Field{"continue", pred.Continue})
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
listPtr, err := meta.GetItemsPtr(listObj)
|
||||
if err != nil {
|
||||
err = decode(s.codec, s.versioner, data, destination, putResp.Header.Revision)
|
||||
trace.Step("decode finished", utiltrace.Field{"len", len(data)}, utiltrace.Field{"err", err})
|
||||
return err
|
||||
}
|
||||
v, err := conversion.EnforcePtr(listPtr)
|
||||
if err != nil || v.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("need ptr to slice: %v", err)
|
||||
}
|
||||
|
||||
newItemFunc := getNewItemFunc(listObj, v)
|
||||
|
||||
key = path.Join(s.pathPrefix, key)
|
||||
startTime := time.Now()
|
||||
var opts []clientv3.OpOption
|
||||
if len(resourceVersion) > 0 && match == metav1.ResourceVersionMatchExact {
|
||||
rv, err := s.versioner.ParseResourceVersion(resourceVersion)
|
||||
if err != nil {
|
||||
return apierrors.NewBadRequest(fmt.Sprintf("invalid resource version: %v", err))
|
||||
}
|
||||
opts = append(opts, clientv3.WithRev(int64(rv)))
|
||||
}
|
||||
|
||||
getResp, err := s.client.KV.Get(ctx, key, opts...)
|
||||
metrics.RecordEtcdRequestLatency("get", getTypeName(listPtr), startTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = s.validateMinimumResourceVersion(resourceVersion, uint64(getResp.Header.Revision)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(getResp.Kvs) > 0 {
|
||||
data, _, err := s.transformer.TransformFromStorage(getResp.Kvs[0].Value, authenticatedDataString(key))
|
||||
if err != nil {
|
||||
return storage.NewInternalError(err.Error())
|
||||
}
|
||||
if err := appendListItem(v, data, uint64(getResp.Kvs[0].ModRevision), pred, s.codec, s.versioner, newItemFunc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// update version with cluster level revision
|
||||
return s.versioner.UpdateList(listObj, uint64(getResp.Header.Revision), "", nil)
|
||||
}
|
||||
|
||||
func getNewItemFunc(listObj runtime.Object, v reflect.Value) func() runtime.Object {
|
||||
@@ -513,72 +520,14 @@ func (s *store) Count(key string) (int64, error) {
|
||||
return getResp.Count, nil
|
||||
}
|
||||
|
||||
// continueToken is a simple structured object for encoding the state of a continue token.
|
||||
// TODO: if we change the version of the encoded from, we can't start encoding the new version
|
||||
// until all other servers are upgraded (i.e. we need to support rolling schema)
|
||||
// This is a public API struct and cannot change.
|
||||
type continueToken struct {
|
||||
APIVersion string `json:"v"`
|
||||
ResourceVersion int64 `json:"rv"`
|
||||
StartKey string `json:"start"`
|
||||
}
|
||||
|
||||
// parseFrom transforms an encoded predicate from into a versioned struct.
|
||||
// TODO: return a typed error that instructs clients that they must relist
|
||||
func decodeContinue(continueValue, keyPrefix string) (fromKey string, rv int64, err error) {
|
||||
data, err := base64.RawURLEncoding.DecodeString(continueValue)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("continue key is not valid: %v", err)
|
||||
}
|
||||
var c continueToken
|
||||
if err := json.Unmarshal(data, &c); err != nil {
|
||||
return "", 0, fmt.Errorf("continue key is not valid: %v", err)
|
||||
}
|
||||
switch c.APIVersion {
|
||||
case "meta.k8s.io/v1":
|
||||
if c.ResourceVersion == 0 {
|
||||
return "", 0, fmt.Errorf("continue key is not valid: incorrect encoded start resourceVersion (version meta.k8s.io/v1)")
|
||||
}
|
||||
if len(c.StartKey) == 0 {
|
||||
return "", 0, fmt.Errorf("continue key is not valid: encoded start key empty (version meta.k8s.io/v1)")
|
||||
}
|
||||
// defend against path traversal attacks by clients - path.Clean will ensure that startKey cannot
|
||||
// be at a higher level of the hierarchy, and so when we append the key prefix we will end up with
|
||||
// continue start key that is fully qualified and cannot range over anything less specific than
|
||||
// keyPrefix.
|
||||
key := c.StartKey
|
||||
if !strings.HasPrefix(key, "/") {
|
||||
key = "/" + key
|
||||
}
|
||||
cleaned := path.Clean(key)
|
||||
if cleaned != key {
|
||||
return "", 0, fmt.Errorf("continue key is not valid: %s", c.StartKey)
|
||||
}
|
||||
return keyPrefix + cleaned[1:], c.ResourceVersion, nil
|
||||
default:
|
||||
return "", 0, fmt.Errorf("continue key is not valid: server does not recognize this encoded version %q", c.APIVersion)
|
||||
}
|
||||
}
|
||||
|
||||
// encodeContinue returns a string representing the encoded continuation of the current query.
|
||||
func encodeContinue(key, keyPrefix string, resourceVersion int64) (string, error) {
|
||||
nextKey := strings.TrimPrefix(key, keyPrefix)
|
||||
if nextKey == key {
|
||||
return "", fmt.Errorf("unable to encode next field: the key and key prefix do not match")
|
||||
}
|
||||
out, err := json.Marshal(&continueToken{APIVersion: "meta.k8s.io/v1", ResourceVersion: resourceVersion, StartKey: nextKey})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(out), nil
|
||||
}
|
||||
|
||||
// List implements storage.Interface.List.
|
||||
func (s *store) List(ctx context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error {
|
||||
// GetList implements storage.Interface.
|
||||
func (s *store) GetList(ctx context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error {
|
||||
recursive := opts.Recursive
|
||||
resourceVersion := opts.ResourceVersion
|
||||
match := opts.ResourceVersionMatch
|
||||
pred := opts.Predicate
|
||||
trace := utiltrace.New("List etcd3",
|
||||
trace := utiltrace.New(fmt.Sprintf("List(recursive=%v) etcd3", recursive),
|
||||
utiltrace.Field{"audit-id", endpointsrequest.GetAuditIDTruncated(ctx)},
|
||||
utiltrace.Field{"key", key},
|
||||
utiltrace.Field{"resourceVersion", resourceVersion},
|
||||
utiltrace.Field{"resourceVersionMatch", match},
|
||||
@@ -593,24 +542,26 @@ func (s *store) List(ctx context.Context, key string, opts storage.ListOptions,
|
||||
if err != nil || v.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("need ptr to slice: %v", err)
|
||||
}
|
||||
key = path.Join(s.pathPrefix, key)
|
||||
|
||||
if s.pathPrefix != "" {
|
||||
key = path.Join(s.pathPrefix, key)
|
||||
}
|
||||
// We need to make sure the key ended with "/" so that we only get children "directories".
|
||||
// e.g. if we have key "/a", "/a/b", "/ab", getting keys with prefix "/a" will return all three,
|
||||
// while with prefix "/a/" will return only "/a/b" which is the correct answer.
|
||||
if !strings.HasSuffix(key, "/") {
|
||||
// For recursive lists, we need to make sure the key ended with "/" so that we only
|
||||
// get children "directories". e.g. if we have key "/a", "/a/b", "/ab", getting keys
|
||||
// with prefix "/a" will return all three, while with prefix "/a/" will return only
|
||||
// "/a/b" which is the correct answer.
|
||||
if recursive && !strings.HasSuffix(key, "/") {
|
||||
key += "/"
|
||||
}
|
||||
keyPrefix := key
|
||||
|
||||
// set the appropriate clientv3 options to filter the returned data set
|
||||
var limitOption *clientv3.OpOption
|
||||
limit := pred.Limit
|
||||
var paging bool
|
||||
options := make([]clientv3.OpOption, 0, 4)
|
||||
if s.pagingEnabled && pred.Limit > 0 {
|
||||
paging = true
|
||||
options = append(options, clientv3.WithLimit(pred.Limit))
|
||||
options = append(options, clientv3.WithLimit(limit))
|
||||
limitOption = &options[len(options)-1]
|
||||
}
|
||||
|
||||
newItemFunc := getNewItemFunc(listObj, v)
|
||||
@@ -627,8 +578,8 @@ func (s *store) List(ctx context.Context, key string, opts storage.ListOptions,
|
||||
var returnedRV, continueRV, withRev int64
|
||||
var continueKey string
|
||||
switch {
|
||||
case s.pagingEnabled && len(pred.Continue) > 0:
|
||||
continueKey, continueRV, err = decodeContinue(pred.Continue, keyPrefix)
|
||||
case recursive && s.pagingEnabled && len(pred.Continue) > 0:
|
||||
continueKey, continueRV, err = storage.DecodeContinue(pred.Continue, keyPrefix)
|
||||
if err != nil {
|
||||
return apierrors.NewBadRequest(fmt.Sprintf("invalid continue token: %v", err))
|
||||
}
|
||||
@@ -648,7 +599,7 @@ func (s *store) List(ctx context.Context, key string, opts storage.ListOptions,
|
||||
withRev = continueRV
|
||||
returnedRV = continueRV
|
||||
}
|
||||
case s.pagingEnabled && pred.Limit > 0:
|
||||
case recursive && s.pagingEnabled && pred.Limit > 0:
|
||||
if fromRV != nil {
|
||||
switch match {
|
||||
case metav1.ResourceVersionMatchNotOlderThan:
|
||||
@@ -684,7 +635,9 @@ func (s *store) List(ctx context.Context, key string, opts storage.ListOptions,
|
||||
}
|
||||
}
|
||||
|
||||
options = append(options, clientv3.WithPrefix())
|
||||
if recursive {
|
||||
options = append(options, clientv3.WithPrefix())
|
||||
}
|
||||
}
|
||||
if withRev != 0 {
|
||||
options = append(options, clientv3.WithRev(withRev))
|
||||
@@ -694,13 +647,26 @@ func (s *store) List(ctx context.Context, key string, opts storage.ListOptions,
|
||||
var lastKey []byte
|
||||
var hasMore bool
|
||||
var getResp *clientv3.GetResponse
|
||||
var numFetched int
|
||||
var numEvald int
|
||||
// Because these metrics are for understanding the costs of handling LIST requests,
|
||||
// get them recorded even in error cases.
|
||||
defer func() {
|
||||
numReturn := v.Len()
|
||||
metrics.RecordStorageListMetrics(s.groupResourceString, numFetched, numEvald, numReturn)
|
||||
}()
|
||||
for {
|
||||
startTime := time.Now()
|
||||
getResp, err = s.client.KV.Get(ctx, key, options...)
|
||||
metrics.RecordEtcdRequestLatency("list", getTypeName(listPtr), startTime)
|
||||
if recursive {
|
||||
metrics.RecordEtcdRequestLatency("list", getTypeName(listPtr), startTime)
|
||||
} else {
|
||||
metrics.RecordEtcdRequestLatency("get", getTypeName(listPtr), startTime)
|
||||
}
|
||||
if err != nil {
|
||||
return interpretListError(err, len(pred.Continue) > 0, continueKey, keyPrefix)
|
||||
}
|
||||
numFetched += len(getResp.Kvs)
|
||||
if err = s.validateMinimumResourceVersion(resourceVersion, uint64(getResp.Header.Revision)); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -719,14 +685,14 @@ func (s *store) List(ctx context.Context, key string, opts storage.ListOptions,
|
||||
}
|
||||
|
||||
// take items from the response until the bucket is full, filtering as we go
|
||||
for _, kv := range getResp.Kvs {
|
||||
for i, kv := range getResp.Kvs {
|
||||
if paging && int64(v.Len()) >= pred.Limit {
|
||||
hasMore = true
|
||||
break
|
||||
}
|
||||
lastKey = kv.Key
|
||||
|
||||
data, _, err := s.transformer.TransformFromStorage(kv.Value, authenticatedDataString(kv.Key))
|
||||
data, _, err := s.transformer.TransformFromStorage(ctx, kv.Value, authenticatedDataString(kv.Key))
|
||||
if err != nil {
|
||||
return storage.NewInternalErrorf("unable to transform key %q: %v", kv.Key, err)
|
||||
}
|
||||
@@ -734,6 +700,10 @@ func (s *store) List(ctx context.Context, key string, opts storage.ListOptions,
|
||||
if err := appendListItem(v, data, uint64(kv.ModRevision), pred, s.codec, s.versioner, newItemFunc); err != nil {
|
||||
return err
|
||||
}
|
||||
numEvald++
|
||||
|
||||
// free kv early. Long lists can take O(seconds) to decode.
|
||||
getResp.Kvs[i] = nil
|
||||
}
|
||||
|
||||
// indicate to the client which resource version was returned
|
||||
@@ -749,6 +719,16 @@ func (s *store) List(ctx context.Context, key string, opts storage.ListOptions,
|
||||
if int64(v.Len()) >= pred.Limit {
|
||||
break
|
||||
}
|
||||
|
||||
if limit < maxLimit {
|
||||
// We got incomplete result due to field/label selector dropping the object.
|
||||
// Double page size to reduce total number of calls to etcd.
|
||||
limit *= 2
|
||||
if limit > maxLimit {
|
||||
limit = maxLimit
|
||||
}
|
||||
*limitOption = clientv3.WithLimit(limit)
|
||||
}
|
||||
key = string(lastKey) + "\x00"
|
||||
if withRev == 0 {
|
||||
withRev = returnedRV
|
||||
@@ -760,7 +740,7 @@ func (s *store) List(ctx context.Context, key string, opts storage.ListOptions,
|
||||
// we never return a key that the client wouldn't be allowed to see
|
||||
if hasMore {
|
||||
// we want to start immediately after the last key
|
||||
next, err := encodeContinue(string(lastKey)+"\x00", keyPrefix, returnedRV)
|
||||
next, err := storage.EncodeContinue(string(lastKey)+"\x00", keyPrefix, returnedRV)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -803,7 +783,7 @@ func growSlice(v reflect.Value, maxCapacity int, sizes ...int) {
|
||||
return
|
||||
}
|
||||
if v.Len() > 0 {
|
||||
extra := reflect.MakeSlice(v.Type(), 0, max)
|
||||
extra := reflect.MakeSlice(v.Type(), v.Len(), max)
|
||||
reflect.Copy(extra, v)
|
||||
v.Set(extra)
|
||||
} else {
|
||||
@@ -814,24 +794,15 @@ func growSlice(v reflect.Value, maxCapacity int, sizes ...int) {
|
||||
|
||||
// Watch implements storage.Interface.Watch.
|
||||
func (s *store) Watch(ctx context.Context, key string, opts storage.ListOptions) (watch.Interface, error) {
|
||||
return s.watch(ctx, key, opts, false)
|
||||
}
|
||||
|
||||
// WatchList implements storage.Interface.WatchList.
|
||||
func (s *store) WatchList(ctx context.Context, key string, opts storage.ListOptions) (watch.Interface, error) {
|
||||
return s.watch(ctx, key, opts, true)
|
||||
}
|
||||
|
||||
func (s *store) watch(ctx context.Context, key string, opts storage.ListOptions, recursive bool) (watch.Interface, error) {
|
||||
rev, err := s.versioner.ParseResourceVersion(opts.ResourceVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key = path.Join(s.pathPrefix, key)
|
||||
return s.watcher.Watch(ctx, key, int64(rev), recursive, opts.ProgressNotify, opts.Predicate)
|
||||
return s.watcher.Watch(ctx, key, int64(rev), opts.Recursive, opts.ProgressNotify, opts.Predicate)
|
||||
}
|
||||
|
||||
func (s *store) getState(getResp *clientv3.GetResponse, key string, v reflect.Value, ignoreNotFound bool) (*objState, error) {
|
||||
func (s *store) getState(ctx context.Context, getResp *clientv3.GetResponse, key string, v reflect.Value, ignoreNotFound bool) (*objState, error) {
|
||||
state := &objState{
|
||||
meta: &storage.ResponseMeta{},
|
||||
}
|
||||
@@ -850,7 +821,7 @@ func (s *store) getState(getResp *clientv3.GetResponse, key string, v reflect.Va
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
data, stale, err := s.transformer.TransformFromStorage(getResp.Kvs[0].Value, authenticatedDataString(key))
|
||||
data, stale, err := s.transformer.TransformFromStorage(ctx, getResp.Kvs[0].Value, authenticatedDataString(key))
|
||||
if err != nil {
|
||||
return nil, storage.NewInternalError(err.Error())
|
||||
}
|
||||
|
||||
19
vendor/k8s.io/apiserver/pkg/storage/etcd3/watcher.go
generated
vendored
19
vendor/k8s.io/apiserver/pkg/storage/etcd3/watcher.go
generated
vendored
@@ -32,8 +32,9 @@ import (
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/apiserver/pkg/storage/etcd3/metrics"
|
||||
"k8s.io/apiserver/pkg/storage/value"
|
||||
utilflowcontrol "k8s.io/apiserver/pkg/util/flowcontrol"
|
||||
|
||||
"go.etcd.io/etcd/clientv3"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
@@ -120,6 +121,14 @@ func (w *watcher) Watch(ctx context.Context, key string, rev int64, recursive, p
|
||||
}
|
||||
wc := w.createWatchChan(ctx, key, rev, recursive, progressNotify, pred)
|
||||
go wc.run()
|
||||
|
||||
// For etcd watch we don't have an easy way to answer whether the watch
|
||||
// has already caught up. So in the initial version (given that watchcache
|
||||
// is by default enabled for all resources but Events), we just deliver
|
||||
// the initialization signal immediately. Improving this will be explored
|
||||
// in the future.
|
||||
utilflowcontrol.WatchInitialized(ctx)
|
||||
|
||||
return wc, nil
|
||||
}
|
||||
|
||||
@@ -283,7 +292,7 @@ func (wc *watchChan) processEvent(wg *sync.WaitGroup) {
|
||||
continue
|
||||
}
|
||||
if len(wc.resultChan) == outgoingBufSize {
|
||||
klog.V(3).InfoS("Fast watcher, slow processing. Probably caused by slow dispatching events to watchers", "outgoingEvents", outgoingBufSize)
|
||||
klog.V(3).InfoS("Fast watcher, slow processing. Probably caused by slow dispatching events to watchers", "outgoingEvents", outgoingBufSize, "objectType", wc.watcher.objectType)
|
||||
}
|
||||
// If user couldn't receive results fast enough, we also block incoming events from watcher.
|
||||
// Because storing events in local will cause more memory usage.
|
||||
@@ -402,7 +411,7 @@ func (wc *watchChan) sendError(err error) {
|
||||
|
||||
func (wc *watchChan) sendEvent(e *event) {
|
||||
if len(wc.incomingEventChan) == incomingBufSize {
|
||||
klog.V(3).InfoS("Fast watcher, slow processing. Probably caused by slow decoding, user not receiving fast, or other processing logic", "incomingEvents", incomingBufSize)
|
||||
klog.V(3).InfoS("Fast watcher, slow processing. Probably caused by slow decoding, user not receiving fast, or other processing logic", "incomingEvents", incomingBufSize, "objectType", wc.watcher.objectType)
|
||||
}
|
||||
select {
|
||||
case wc.incomingEventChan <- e:
|
||||
@@ -417,7 +426,7 @@ func (wc *watchChan) prepareObjs(e *event) (curObj runtime.Object, oldObj runtim
|
||||
}
|
||||
|
||||
if !e.isDeleted {
|
||||
data, _, err := wc.watcher.transformer.TransformFromStorage(e.value, authenticatedDataString(e.key))
|
||||
data, _, err := wc.watcher.transformer.TransformFromStorage(wc.ctx, e.value, authenticatedDataString(e.key))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -432,7 +441,7 @@ func (wc *watchChan) prepareObjs(e *event) (curObj runtime.Object, oldObj runtim
|
||||
// we need the object only to compute whether it was filtered out
|
||||
// before).
|
||||
if len(e.prevValue) > 0 && (e.isDeleted || !wc.acceptAll()) {
|
||||
data, _, err := wc.watcher.transformer.TransformFromStorage(e.prevValue, authenticatedDataString(e.key))
|
||||
data, _, err := wc.watcher.transformer.TransformFromStorage(wc.ctx, e.prevValue, authenticatedDataString(e.key))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user