Replace godep with dep
This commit is contained in:
parent
1e7489927c
commit
bf5616c65b
14883 changed files with 3937406 additions and 361781 deletions
84
vendor/k8s.io/apiserver/pkg/storage/etcd/BUILD
generated
vendored
Normal file
84
vendor/k8s.io/apiserver/pkg/storage/etcd/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"api_object_versioner_test.go",
|
||||
"etcd_helper_test.go",
|
||||
"etcd_watcher_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//vendor/github.com/coreos/etcd/client:go_default_library",
|
||||
"//vendor/golang.org/x/net/context:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/testing:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/example:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/example/v1:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/storage:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/storage/etcd/etcdtest:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/storage/testing:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/storage/tests:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"api_object_versioner.go",
|
||||
"doc.go",
|
||||
"etcd_helper.go",
|
||||
"etcd_watcher.go",
|
||||
],
|
||||
deps = [
|
||||
"//vendor/github.com/coreos/etcd/client:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/golang.org/x/net/context:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/cache:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/storage:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/storage/etcd/metrics:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/storage/etcd/util:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/trace:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage/etcd/etcdtest:all-srcs",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage/etcd/metrics:all-srcs",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage/etcd/testing:all-srcs",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage/etcd/util:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
25
vendor/k8s.io/apiserver/pkg/storage/etcd/OWNERS
generated
vendored
Executable file
25
vendor/k8s.io/apiserver/pkg/storage/etcd/OWNERS
generated
vendored
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
reviewers:
|
||||
- lavalamp
|
||||
- smarterclayton
|
||||
- wojtek-t
|
||||
- deads2k
|
||||
- derekwaynecarr
|
||||
- caesarxuchao
|
||||
- mikedanese
|
||||
- liggitt
|
||||
- davidopp
|
||||
- pmorie
|
||||
- luxas
|
||||
- janetkuo
|
||||
- roberthbailey
|
||||
- tallclair
|
||||
- timothysc
|
||||
- dims
|
||||
- hongchaodeng
|
||||
- krousey
|
||||
- fgrzadkowski
|
||||
- resouer
|
||||
- pweil-
|
||||
- mqliang
|
||||
- feihujiang
|
||||
- enj
|
||||
109
vendor/k8s.io/apiserver/pkg/storage/etcd/api_object_versioner.go
generated
vendored
Normal file
109
vendor/k8s.io/apiserver/pkg/storage/etcd/api_object_versioner.go
generated
vendored
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
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 etcd
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"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) error {
|
||||
listAccessor, err := meta.ListAccessor(obj)
|
||||
if err != nil || listAccessor == nil {
|
||||
return err
|
||||
}
|
||||
versionString := ""
|
||||
if resourceVersion != 0 {
|
||||
versionString = strconv.FormatUint(resourceVersion, 10)
|
||||
}
|
||||
listAccessor.SetResourceVersion(versionString)
|
||||
listAccessor.SetContinue(nextKey)
|
||||
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)
|
||||
}
|
||||
|
||||
// APIObjectVersioner 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
|
||||
}
|
||||
58
vendor/k8s.io/apiserver/pkg/storage/etcd/api_object_versioner_test.go
generated
vendored
Normal file
58
vendor/k8s.io/apiserver/pkg/storage/etcd/api_object_versioner_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
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 etcd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
storagetesting "k8s.io/apiserver/pkg/storage/testing"
|
||||
)
|
||||
|
||||
func TestObjectVersioner(t *testing.T) {
|
||||
v := APIObjectVersioner{}
|
||||
if ver, err := v.ObjectResourceVersion(&storagetesting.TestResource{ObjectMeta: metav1.ObjectMeta{ResourceVersion: "5"}}); err != nil || ver != 5 {
|
||||
t.Errorf("unexpected version: %d %v", ver, err)
|
||||
}
|
||||
if ver, err := v.ObjectResourceVersion(&storagetesting.TestResource{ObjectMeta: metav1.ObjectMeta{ResourceVersion: "a"}}); err == nil || ver != 0 {
|
||||
t.Errorf("unexpected version: %d %v", ver, err)
|
||||
}
|
||||
obj := &storagetesting.TestResource{ObjectMeta: metav1.ObjectMeta{ResourceVersion: "a"}}
|
||||
if err := v.UpdateObject(obj, 5); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if obj.ResourceVersion != "5" || obj.DeletionTimestamp != nil {
|
||||
t.Errorf("unexpected resource version: %#v", obj)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareResourceVersion(t *testing.T) {
|
||||
five := &storagetesting.TestResource{ObjectMeta: metav1.ObjectMeta{ResourceVersion: "5"}}
|
||||
six := &storagetesting.TestResource{ObjectMeta: metav1.ObjectMeta{ResourceVersion: "6"}}
|
||||
|
||||
versioner := APIObjectVersioner{}
|
||||
|
||||
if e, a := -1, versioner.CompareResourceVersion(five, six); e != a {
|
||||
t.Errorf("expected %v got %v", e, a)
|
||||
}
|
||||
if e, a := 1, versioner.CompareResourceVersion(six, five); e != a {
|
||||
t.Errorf("expected %v got %v", e, a)
|
||||
}
|
||||
if e, a := 0, versioner.CompareResourceVersion(six, six); e != a {
|
||||
t.Errorf("expected %v got %v", e, a)
|
||||
}
|
||||
}
|
||||
17
vendor/k8s.io/apiserver/pkg/storage/etcd/doc.go
generated
vendored
Normal file
17
vendor/k8s.io/apiserver/pkg/storage/etcd/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
Copyright 2015 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 etcd // import "k8s.io/apiserver/pkg/storage/etcd"
|
||||
651
vendor/k8s.io/apiserver/pkg/storage/etcd/etcd_helper.go
generated
vendored
Normal file
651
vendor/k8s.io/apiserver/pkg/storage/etcd/etcd_helper.go
generated
vendored
Normal file
|
|
@ -0,0 +1,651 @@
|
|||
/*
|
||||
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 etcd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
etcd "github.com/coreos/etcd/client"
|
||||
"github.com/golang/glog"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/conversion"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilcache "k8s.io/apimachinery/pkg/util/cache"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/apiserver/pkg/storage/etcd/metrics"
|
||||
etcdutil "k8s.io/apiserver/pkg/storage/etcd/util"
|
||||
utiltrace "k8s.io/apiserver/pkg/util/trace"
|
||||
)
|
||||
|
||||
// ValueTransformer allows a string value to be transformed before being read from or written to the underlying store. The methods
|
||||
// must be able to undo the transformation caused by the other.
|
||||
type ValueTransformer interface {
|
||||
// TransformStringFromStorage may transform the provided string from its underlying storage representation or return an error.
|
||||
// Stale is true if the object on disk is stale and a write to etcd should be issued, even if the contents of the object
|
||||
// have not changed.
|
||||
TransformStringFromStorage(string) (value string, stale bool, err error)
|
||||
// TransformStringToStorage may transform the provided string into the appropriate form in storage or return an error.
|
||||
TransformStringToStorage(string) (value string, err error)
|
||||
}
|
||||
|
||||
type identityTransformer struct{}
|
||||
|
||||
func (identityTransformer) TransformStringFromStorage(s string) (string, bool, error) {
|
||||
return s, false, nil
|
||||
}
|
||||
func (identityTransformer) TransformStringToStorage(s string) (string, error) { return s, nil }
|
||||
|
||||
// IdentityTransformer performs no transformation on the provided values.
|
||||
var IdentityTransformer ValueTransformer = identityTransformer{}
|
||||
|
||||
// Creates a new storage interface from the client
|
||||
// TODO: deprecate in favor of storage.Config abstraction over time
|
||||
func NewEtcdStorage(client etcd.Client, codec runtime.Codec, prefix string, quorum bool, cacheSize int, copier runtime.ObjectCopier, transformer ValueTransformer) storage.Interface {
|
||||
return &etcdHelper{
|
||||
etcdMembersAPI: etcd.NewMembersAPI(client),
|
||||
etcdKeysAPI: etcd.NewKeysAPI(client),
|
||||
codec: codec,
|
||||
versioner: APIObjectVersioner{},
|
||||
copier: copier,
|
||||
transformer: transformer,
|
||||
pathPrefix: path.Join("/", prefix),
|
||||
quorum: quorum,
|
||||
cache: utilcache.NewCache(cacheSize),
|
||||
}
|
||||
}
|
||||
|
||||
// etcdHelper is the reference implementation of storage.Interface.
|
||||
type etcdHelper struct {
|
||||
etcdMembersAPI etcd.MembersAPI
|
||||
etcdKeysAPI etcd.KeysAPI
|
||||
codec runtime.Codec
|
||||
copier runtime.ObjectCopier
|
||||
transformer ValueTransformer
|
||||
// Note that versioner is required for etcdHelper to work correctly.
|
||||
// The public constructors (NewStorage & NewEtcdStorage) are setting it
|
||||
// correctly, so be careful when manipulating with it manually.
|
||||
// optional, has to be set to perform any atomic operations
|
||||
versioner storage.Versioner
|
||||
// prefix for all etcd keys
|
||||
pathPrefix string
|
||||
// if true, perform quorum read
|
||||
quorum bool
|
||||
|
||||
// We cache objects stored in etcd. For keys we use Node.ModifiedIndex which is equivalent
|
||||
// to resourceVersion.
|
||||
// This depends on etcd's indexes being globally unique across all objects/types. This will
|
||||
// have to revisited if we decide to do things like multiple etcd clusters, or etcd will
|
||||
// support multi-object transaction that will result in many objects with the same index.
|
||||
// Number of entries stored in the cache is controlled by maxEtcdCacheEntries constant.
|
||||
// TODO: Measure how much this cache helps after the conversion code is optimized.
|
||||
cache utilcache.Cache
|
||||
}
|
||||
|
||||
func init() {
|
||||
metrics.Register()
|
||||
}
|
||||
|
||||
// Implements storage.Interface.
|
||||
func (h *etcdHelper) Versioner() storage.Versioner {
|
||||
return h.versioner
|
||||
}
|
||||
|
||||
// Implements storage.Interface.
|
||||
func (h *etcdHelper) Create(ctx context.Context, key string, obj, out runtime.Object, ttl uint64) error {
|
||||
trace := utiltrace.New("etcdHelper::Create " + getTypeName(obj))
|
||||
defer trace.LogIfLong(250 * time.Millisecond)
|
||||
if ctx == nil {
|
||||
glog.Errorf("Context is nil")
|
||||
}
|
||||
key = path.Join(h.pathPrefix, key)
|
||||
data, err := runtime.Encode(h.codec, obj)
|
||||
trace.Step("Object encoded")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if version, err := h.versioner.ObjectResourceVersion(obj); err == nil && version != 0 {
|
||||
return errors.New("resourceVersion may not be set on objects to be created")
|
||||
}
|
||||
if err := h.versioner.PrepareObjectForStorage(obj); err != nil {
|
||||
return fmt.Errorf("PrepareObjectForStorage returned an error: %v", err)
|
||||
}
|
||||
trace.Step("Version checked")
|
||||
|
||||
startTime := time.Now()
|
||||
opts := etcd.SetOptions{
|
||||
TTL: time.Duration(ttl) * time.Second,
|
||||
PrevExist: etcd.PrevNoExist,
|
||||
}
|
||||
|
||||
newBody, err := h.transformer.TransformStringToStorage(string(data))
|
||||
if err != nil {
|
||||
return storage.NewInternalError(err.Error())
|
||||
}
|
||||
|
||||
response, err := h.etcdKeysAPI.Set(ctx, key, newBody, &opts)
|
||||
trace.Step("Object created")
|
||||
metrics.RecordEtcdRequestLatency("create", getTypeName(obj), startTime)
|
||||
if err != nil {
|
||||
return toStorageErr(err, key, 0)
|
||||
}
|
||||
if out != nil {
|
||||
if _, err := conversion.EnforcePtr(out); err != nil {
|
||||
panic("unable to convert output object to pointer")
|
||||
}
|
||||
_, _, _, err = h.extractObj(response, err, out, false, false)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func checkPreconditions(key string, preconditions *storage.Preconditions, out runtime.Object) error {
|
||||
if preconditions == nil {
|
||||
return nil
|
||||
}
|
||||
objMeta, err := meta.Accessor(out)
|
||||
if err != nil {
|
||||
return storage.NewInternalErrorf("can't enforce preconditions %v on un-introspectable object %v, got error: %v", *preconditions, out, err)
|
||||
}
|
||||
if preconditions.UID != nil && *preconditions.UID != objMeta.GetUID() {
|
||||
errMsg := fmt.Sprintf("Precondition failed: UID in precondition: %v, UID in object meta: %v", preconditions.UID, objMeta.GetUID())
|
||||
return storage.NewInvalidObjError(key, errMsg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements storage.Interface.
|
||||
func (h *etcdHelper) Delete(ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions) error {
|
||||
if ctx == nil {
|
||||
glog.Errorf("Context is nil")
|
||||
}
|
||||
key = path.Join(h.pathPrefix, key)
|
||||
v, err := conversion.EnforcePtr(out)
|
||||
if err != nil {
|
||||
panic("unable to convert output object to pointer")
|
||||
}
|
||||
|
||||
if preconditions == nil {
|
||||
startTime := time.Now()
|
||||
response, err := h.etcdKeysAPI.Delete(ctx, key, nil)
|
||||
metrics.RecordEtcdRequestLatency("delete", getTypeName(out), startTime)
|
||||
if !etcdutil.IsEtcdNotFound(err) {
|
||||
// if the object that existed prior to the delete is returned by etcd, update the out object.
|
||||
if err != nil || response.PrevNode != nil {
|
||||
_, _, _, err = h.extractObj(response, err, out, false, true)
|
||||
}
|
||||
}
|
||||
return toStorageErr(err, key, 0)
|
||||
}
|
||||
|
||||
// Check the preconditions match.
|
||||
obj := reflect.New(v.Type()).Interface().(runtime.Object)
|
||||
for {
|
||||
_, node, res, _, err := h.bodyAndExtractObj(ctx, key, obj, false)
|
||||
if err != nil {
|
||||
return toStorageErr(err, key, 0)
|
||||
}
|
||||
if err := checkPreconditions(key, preconditions, obj); err != nil {
|
||||
return toStorageErr(err, key, 0)
|
||||
}
|
||||
index := uint64(0)
|
||||
if node != nil {
|
||||
index = node.ModifiedIndex
|
||||
} else if res != nil {
|
||||
index = res.Index
|
||||
}
|
||||
opt := etcd.DeleteOptions{PrevIndex: index}
|
||||
startTime := time.Now()
|
||||
response, err := h.etcdKeysAPI.Delete(ctx, key, &opt)
|
||||
metrics.RecordEtcdRequestLatency("delete", getTypeName(out), startTime)
|
||||
if !etcdutil.IsEtcdTestFailed(err) {
|
||||
if !etcdutil.IsEtcdNotFound(err) {
|
||||
// if the object that existed prior to the delete is returned by etcd, update the out object.
|
||||
if err != nil || response.PrevNode != nil {
|
||||
_, _, _, err = h.extractObj(response, err, out, false, true)
|
||||
}
|
||||
}
|
||||
return toStorageErr(err, key, 0)
|
||||
}
|
||||
|
||||
glog.V(4).Infof("deletion of %s failed because of a conflict, going to retry", key)
|
||||
}
|
||||
}
|
||||
|
||||
// Implements storage.Interface.
|
||||
func (h *etcdHelper) Watch(ctx context.Context, key string, resourceVersion string, pred storage.SelectionPredicate) (watch.Interface, error) {
|
||||
if ctx == nil {
|
||||
glog.Errorf("Context is nil")
|
||||
}
|
||||
watchRV, err := storage.ParseWatchResourceVersion(resourceVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key = path.Join(h.pathPrefix, key)
|
||||
w := newEtcdWatcher(false, h.quorum, nil, storage.SimpleFilter(pred), h.codec, h.versioner, nil, h.transformer, h)
|
||||
go w.etcdWatch(ctx, h.etcdKeysAPI, key, watchRV)
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// Implements storage.Interface.
|
||||
func (h *etcdHelper) WatchList(ctx context.Context, key string, resourceVersion string, pred storage.SelectionPredicate) (watch.Interface, error) {
|
||||
if ctx == nil {
|
||||
glog.Errorf("Context is nil")
|
||||
}
|
||||
watchRV, err := storage.ParseWatchResourceVersion(resourceVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key = path.Join(h.pathPrefix, key)
|
||||
w := newEtcdWatcher(true, h.quorum, exceptKey(key), storage.SimpleFilter(pred), h.codec, h.versioner, nil, h.transformer, h)
|
||||
go w.etcdWatch(ctx, h.etcdKeysAPI, key, watchRV)
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// Implements storage.Interface.
|
||||
func (h *etcdHelper) Get(ctx context.Context, key string, resourceVersion string, objPtr runtime.Object, ignoreNotFound bool) error {
|
||||
if ctx == nil {
|
||||
glog.Errorf("Context is nil")
|
||||
}
|
||||
key = path.Join(h.pathPrefix, key)
|
||||
_, _, _, _, err := h.bodyAndExtractObj(ctx, key, objPtr, ignoreNotFound)
|
||||
return err
|
||||
}
|
||||
|
||||
// bodyAndExtractObj performs the normal Get path to etcd, returning the parsed node and response for additional information
|
||||
// about the response, like the current etcd index and the ttl.
|
||||
func (h *etcdHelper) bodyAndExtractObj(ctx context.Context, key string, objPtr runtime.Object, ignoreNotFound bool) (body string, node *etcd.Node, res *etcd.Response, stale bool, err error) {
|
||||
if ctx == nil {
|
||||
glog.Errorf("Context is nil")
|
||||
}
|
||||
startTime := time.Now()
|
||||
|
||||
opts := &etcd.GetOptions{
|
||||
Quorum: h.quorum,
|
||||
}
|
||||
|
||||
response, err := h.etcdKeysAPI.Get(ctx, key, opts)
|
||||
metrics.RecordEtcdRequestLatency("get", getTypeName(objPtr), startTime)
|
||||
if err != nil && !etcdutil.IsEtcdNotFound(err) {
|
||||
return "", nil, nil, false, toStorageErr(err, key, 0)
|
||||
}
|
||||
body, node, stale, err = h.extractObj(response, err, objPtr, ignoreNotFound, false)
|
||||
return body, node, response, stale, toStorageErr(err, key, 0)
|
||||
}
|
||||
|
||||
func (h *etcdHelper) extractObj(response *etcd.Response, inErr error, objPtr runtime.Object, ignoreNotFound, prevNode bool) (body string, node *etcd.Node, stale bool, err error) {
|
||||
if response != nil {
|
||||
if prevNode {
|
||||
node = response.PrevNode
|
||||
} else {
|
||||
node = response.Node
|
||||
}
|
||||
}
|
||||
if inErr != nil || node == nil || len(node.Value) == 0 {
|
||||
if ignoreNotFound {
|
||||
v, err := conversion.EnforcePtr(objPtr)
|
||||
if err != nil {
|
||||
return "", nil, false, err
|
||||
}
|
||||
v.Set(reflect.Zero(v.Type()))
|
||||
return "", nil, false, nil
|
||||
} else if inErr != nil {
|
||||
return "", nil, false, inErr
|
||||
}
|
||||
return "", nil, false, fmt.Errorf("unable to locate a value on the response: %#v", response)
|
||||
}
|
||||
|
||||
body, stale, err = h.transformer.TransformStringFromStorage(node.Value)
|
||||
if err != nil {
|
||||
return body, nil, stale, storage.NewInternalError(err.Error())
|
||||
}
|
||||
out, gvk, err := h.codec.Decode([]byte(body), nil, objPtr)
|
||||
if err != nil {
|
||||
return body, nil, stale, err
|
||||
}
|
||||
if out != objPtr {
|
||||
return body, nil, stale, fmt.Errorf("unable to decode object %s into %v", gvk.String(), reflect.TypeOf(objPtr))
|
||||
}
|
||||
// being unable to set the version does not prevent the object from being extracted
|
||||
_ = h.versioner.UpdateObject(objPtr, node.ModifiedIndex)
|
||||
return body, node, stale, err
|
||||
}
|
||||
|
||||
// Implements storage.Interface.
|
||||
func (h *etcdHelper) GetToList(ctx context.Context, key string, resourceVersion string, pred storage.SelectionPredicate, listObj runtime.Object) error {
|
||||
if ctx == nil {
|
||||
glog.Errorf("Context is nil")
|
||||
}
|
||||
trace := utiltrace.New("GetToList " + getTypeName(listObj))
|
||||
listPtr, err := meta.GetItemsPtr(listObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key = path.Join(h.pathPrefix, key)
|
||||
startTime := time.Now()
|
||||
trace.Step("About to read etcd node")
|
||||
|
||||
opts := &etcd.GetOptions{
|
||||
Quorum: h.quorum,
|
||||
}
|
||||
response, err := h.etcdKeysAPI.Get(ctx, key, opts)
|
||||
trace.Step("Etcd node read")
|
||||
metrics.RecordEtcdRequestLatency("get", getTypeName(listPtr), startTime)
|
||||
if err != nil {
|
||||
if etcdutil.IsEtcdNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
return toStorageErr(err, key, 0)
|
||||
}
|
||||
|
||||
nodes := make([]*etcd.Node, 0)
|
||||
nodes = append(nodes, response.Node)
|
||||
|
||||
if err := h.decodeNodeList(nodes, storage.SimpleFilter(pred), listPtr); err != nil {
|
||||
return err
|
||||
}
|
||||
trace.Step("Object decoded")
|
||||
if err := h.versioner.UpdateList(listObj, response.Index, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeNodeList walks the tree of each node in the list and decodes into the specified object
|
||||
func (h *etcdHelper) decodeNodeList(nodes []*etcd.Node, filter storage.FilterFunc, slicePtr interface{}) error {
|
||||
trace := utiltrace.New("decodeNodeList " + getTypeName(slicePtr))
|
||||
defer trace.LogIfLong(400 * time.Millisecond)
|
||||
v, err := conversion.EnforcePtr(slicePtr)
|
||||
if err != nil || v.Kind() != reflect.Slice {
|
||||
// This should not happen at runtime.
|
||||
panic("need ptr to slice")
|
||||
}
|
||||
for _, node := range nodes {
|
||||
if node.Dir {
|
||||
// IMPORTANT: do not log each key as a discrete step in the trace log
|
||||
// as it produces an immense amount of log spam when there is a large
|
||||
// amount of content in the list.
|
||||
if err := h.decodeNodeList(node.Nodes, filter, slicePtr); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if obj, found := h.getFromCache(node.ModifiedIndex, filter); found {
|
||||
// obj != nil iff it matches the filter function.
|
||||
if obj != nil {
|
||||
v.Set(reflect.Append(v, reflect.ValueOf(obj).Elem()))
|
||||
}
|
||||
} else {
|
||||
body, _, err := h.transformer.TransformStringFromStorage(node.Value)
|
||||
if err != nil {
|
||||
// omit items from lists and watches that cannot be transformed, but log the error
|
||||
utilruntime.HandleError(fmt.Errorf("unable to transform key %q: %v", node.Key, err))
|
||||
continue
|
||||
}
|
||||
|
||||
obj, _, err := h.codec.Decode([]byte(body), nil, reflect.New(v.Type().Elem()).Interface().(runtime.Object))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// being unable to set the version does not prevent the object from being extracted
|
||||
_ = h.versioner.UpdateObject(obj, node.ModifiedIndex)
|
||||
if filter(obj) {
|
||||
v.Set(reflect.Append(v, reflect.ValueOf(obj).Elem()))
|
||||
}
|
||||
if node.ModifiedIndex != 0 {
|
||||
h.addToCache(node.ModifiedIndex, obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
trace.Step(fmt.Sprintf("Decoded %v nodes", len(nodes)))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements storage.Interface.
|
||||
func (h *etcdHelper) List(ctx context.Context, key string, resourceVersion string, pred storage.SelectionPredicate, listObj runtime.Object) error {
|
||||
if ctx == nil {
|
||||
glog.Errorf("Context is nil")
|
||||
}
|
||||
trace := utiltrace.New("List " + getTypeName(listObj))
|
||||
defer trace.LogIfLong(400 * time.Millisecond)
|
||||
listPtr, err := meta.GetItemsPtr(listObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key = path.Join(h.pathPrefix, key)
|
||||
startTime := time.Now()
|
||||
trace.Step("About to list etcd node")
|
||||
nodes, index, err := h.listEtcdNode(ctx, key)
|
||||
trace.Step("Etcd node listed")
|
||||
metrics.RecordEtcdRequestLatency("list", getTypeName(listPtr), startTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := h.decodeNodeList(nodes, storage.SimpleFilter(pred), listPtr); err != nil {
|
||||
return err
|
||||
}
|
||||
trace.Step("Node list decoded")
|
||||
if err := h.versioner.UpdateList(listObj, index, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *etcdHelper) listEtcdNode(ctx context.Context, key string) ([]*etcd.Node, uint64, error) {
|
||||
if ctx == nil {
|
||||
glog.Errorf("Context is nil")
|
||||
}
|
||||
opts := etcd.GetOptions{
|
||||
Recursive: true,
|
||||
Sort: true,
|
||||
Quorum: h.quorum,
|
||||
}
|
||||
result, err := h.etcdKeysAPI.Get(ctx, key, &opts)
|
||||
if err != nil {
|
||||
var index uint64
|
||||
if etcdError, ok := err.(etcd.Error); ok {
|
||||
index = etcdError.Index
|
||||
}
|
||||
nodes := make([]*etcd.Node, 0)
|
||||
if etcdutil.IsEtcdNotFound(err) {
|
||||
return nodes, index, nil
|
||||
} else {
|
||||
return nodes, index, toStorageErr(err, key, 0)
|
||||
}
|
||||
}
|
||||
return result.Node.Nodes, result.Index, nil
|
||||
}
|
||||
|
||||
// Implements storage.Interface.
|
||||
func (h *etcdHelper) GuaranteedUpdate(
|
||||
ctx context.Context, key string, ptrToType runtime.Object, ignoreNotFound bool,
|
||||
preconditions *storage.Preconditions, tryUpdate storage.UpdateFunc, _ ...runtime.Object) error {
|
||||
// Ignore the suggestion about current object.
|
||||
if ctx == nil {
|
||||
glog.Errorf("Context is nil")
|
||||
}
|
||||
v, err := conversion.EnforcePtr(ptrToType)
|
||||
if err != nil {
|
||||
// Panic is appropriate, because this is a programming error.
|
||||
panic("need ptr to type")
|
||||
}
|
||||
key = path.Join(h.pathPrefix, key)
|
||||
for {
|
||||
obj := reflect.New(v.Type()).Interface().(runtime.Object)
|
||||
origBody, node, res, stale, err := h.bodyAndExtractObj(ctx, key, obj, ignoreNotFound)
|
||||
if err != nil {
|
||||
return toStorageErr(err, key, 0)
|
||||
}
|
||||
if err := checkPreconditions(key, preconditions, obj); err != nil {
|
||||
return toStorageErr(err, key, 0)
|
||||
}
|
||||
meta := storage.ResponseMeta{}
|
||||
if node != nil {
|
||||
meta.TTL = node.TTL
|
||||
meta.ResourceVersion = node.ModifiedIndex
|
||||
}
|
||||
// Get the object to be written by calling tryUpdate.
|
||||
ret, newTTL, err := tryUpdate(obj, meta)
|
||||
if err != nil {
|
||||
return toStorageErr(err, key, 0)
|
||||
}
|
||||
|
||||
index := uint64(0)
|
||||
ttl := uint64(0)
|
||||
if node != nil {
|
||||
index = node.ModifiedIndex
|
||||
if node.TTL != 0 {
|
||||
ttl = uint64(node.TTL)
|
||||
}
|
||||
if node.Expiration != nil && ttl == 0 {
|
||||
ttl = 1
|
||||
}
|
||||
} else if res != nil {
|
||||
index = res.Index
|
||||
}
|
||||
|
||||
if newTTL != nil {
|
||||
if ttl != 0 && *newTTL == 0 {
|
||||
// TODO: remove this after we have verified this is no longer an issue
|
||||
glog.V(4).Infof("GuaranteedUpdate is clearing TTL for %q, may not be intentional", key)
|
||||
}
|
||||
ttl = *newTTL
|
||||
}
|
||||
|
||||
// Since update object may have a resourceVersion set, we need to clear it here.
|
||||
if err := h.versioner.PrepareObjectForStorage(ret); err != nil {
|
||||
return errors.New("resourceVersion cannot be set on objects store in etcd")
|
||||
}
|
||||
|
||||
newBodyData, err := runtime.Encode(h.codec, ret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newBody := string(newBodyData)
|
||||
data, err := h.transformer.TransformStringToStorage(newBody)
|
||||
if err != nil {
|
||||
return storage.NewInternalError(err.Error())
|
||||
}
|
||||
|
||||
// First time this key has been used, try creating new value.
|
||||
if index == 0 {
|
||||
startTime := time.Now()
|
||||
opts := etcd.SetOptions{
|
||||
TTL: time.Duration(ttl) * time.Second,
|
||||
PrevExist: etcd.PrevNoExist,
|
||||
}
|
||||
response, err := h.etcdKeysAPI.Set(ctx, key, data, &opts)
|
||||
metrics.RecordEtcdRequestLatency("create", getTypeName(ptrToType), startTime)
|
||||
if etcdutil.IsEtcdNodeExist(err) {
|
||||
continue
|
||||
}
|
||||
_, _, _, err = h.extractObj(response, err, ptrToType, false, false)
|
||||
return toStorageErr(err, key, 0)
|
||||
}
|
||||
|
||||
// If we don't send an update, we simply return the currently existing
|
||||
// version of the object. However, the value transformer may indicate that
|
||||
// the on disk representation has changed and that we must commit an update.
|
||||
if newBody == origBody && !stale {
|
||||
_, _, _, err := h.extractObj(res, nil, ptrToType, ignoreNotFound, false)
|
||||
return err
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
// Swap origBody with data, if origBody is the latest etcd data.
|
||||
opts := etcd.SetOptions{
|
||||
PrevIndex: index,
|
||||
TTL: time.Duration(ttl) * time.Second,
|
||||
}
|
||||
response, err := h.etcdKeysAPI.Set(ctx, key, data, &opts)
|
||||
metrics.RecordEtcdRequestLatency("compareAndSwap", getTypeName(ptrToType), startTime)
|
||||
if etcdutil.IsEtcdTestFailed(err) {
|
||||
// Try again.
|
||||
continue
|
||||
}
|
||||
_, _, _, err = h.extractObj(response, err, ptrToType, false, false)
|
||||
return toStorageErr(err, key, int64(index))
|
||||
}
|
||||
}
|
||||
|
||||
// etcdCache defines interface used for caching objects stored in etcd. Objects are keyed by
|
||||
// their Node.ModifiedIndex, which is unique across all types.
|
||||
// All implementations must be thread-safe.
|
||||
type etcdCache interface {
|
||||
getFromCache(index uint64, filter storage.FilterFunc) (runtime.Object, bool)
|
||||
addToCache(index uint64, obj runtime.Object)
|
||||
}
|
||||
|
||||
func getTypeName(obj interface{}) string {
|
||||
return reflect.TypeOf(obj).String()
|
||||
}
|
||||
|
||||
func (h *etcdHelper) getFromCache(index uint64, filter storage.FilterFunc) (runtime.Object, bool) {
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
metrics.ObserveGetCache(startTime)
|
||||
}()
|
||||
obj, found := h.cache.Get(index)
|
||||
if found {
|
||||
if !filter(obj.(runtime.Object)) {
|
||||
return nil, true
|
||||
}
|
||||
// We should not return the object itself to avoid polluting the cache if someone
|
||||
// modifies returned values.
|
||||
objCopy := obj.(runtime.Object).DeepCopyObject()
|
||||
metrics.ObserveCacheHit()
|
||||
return objCopy.(runtime.Object), true
|
||||
}
|
||||
metrics.ObserveCacheMiss()
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (h *etcdHelper) addToCache(index uint64, obj runtime.Object) {
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
metrics.ObserveAddCache(startTime)
|
||||
}()
|
||||
objCopy := obj.DeepCopyObject()
|
||||
isOverwrite := h.cache.Add(index, objCopy)
|
||||
if !isOverwrite {
|
||||
metrics.ObserveNewEntry()
|
||||
}
|
||||
}
|
||||
|
||||
func toStorageErr(err error, key string, rv int64) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
switch {
|
||||
case etcdutil.IsEtcdNotFound(err):
|
||||
return storage.NewKeyNotFoundError(key, rv)
|
||||
case etcdutil.IsEtcdNodeExist(err):
|
||||
return storage.NewKeyExistsError(key, rv)
|
||||
case etcdutil.IsEtcdTestFailed(err):
|
||||
return storage.NewResourceVersionConflictsError(key, rv)
|
||||
case etcdutil.IsEtcdUnreachable(err):
|
||||
return storage.NewUnreachableError(key, rv)
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
695
vendor/k8s.io/apiserver/pkg/storage/etcd/etcd_helper_test.go
generated
vendored
Normal file
695
vendor/k8s.io/apiserver/pkg/storage/etcd/etcd_helper_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,695 @@
|
|||
/*
|
||||
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 etcd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
etcd "github.com/coreos/etcd/client"
|
||||
"golang.org/x/net/context"
|
||||
apitesting "k8s.io/apimachinery/pkg/api/testing"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/conversion"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apiserver/pkg/apis/example"
|
||||
examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/apiserver/pkg/storage/etcd/etcdtest"
|
||||
etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing"
|
||||
storagetesting "k8s.io/apiserver/pkg/storage/testing"
|
||||
storagetests "k8s.io/apiserver/pkg/storage/tests"
|
||||
)
|
||||
|
||||
// prefixTransformer adds and verifies that all data has the correct prefix on its way in and out.
|
||||
type prefixTransformer struct {
|
||||
prefix string
|
||||
stale bool
|
||||
err error
|
||||
}
|
||||
|
||||
func (p prefixTransformer) TransformStringFromStorage(s string) (string, bool, error) {
|
||||
if !strings.HasPrefix(s, p.prefix) {
|
||||
return "", false, fmt.Errorf("value does not have expected prefix: %s", s)
|
||||
}
|
||||
return strings.TrimPrefix(s, p.prefix), p.stale, p.err
|
||||
}
|
||||
func (p prefixTransformer) TransformStringToStorage(s string) (string, error) {
|
||||
if len(s) > 0 {
|
||||
return p.prefix + s, p.err
|
||||
}
|
||||
return s, p.err
|
||||
}
|
||||
|
||||
func defaultPrefix(s string) string {
|
||||
return "test!" + s
|
||||
}
|
||||
|
||||
func defaultPrefixValue(value []byte) string {
|
||||
return defaultPrefix(string(value))
|
||||
}
|
||||
|
||||
func testScheme(t *testing.T) (*runtime.Scheme, serializer.CodecFactory) {
|
||||
scheme := runtime.NewScheme()
|
||||
scheme.Log(t)
|
||||
scheme.AddKnownTypes(schema.GroupVersion{Version: runtime.APIVersionInternal}, &storagetesting.TestResource{})
|
||||
example.AddToScheme(scheme)
|
||||
examplev1.AddToScheme(scheme)
|
||||
if err := scheme.AddConversionFuncs(
|
||||
func(in *storagetesting.TestResource, out *storagetesting.TestResource, s conversion.Scope) error {
|
||||
*out = *in
|
||||
return nil
|
||||
},
|
||||
func(in, out *time.Time, s conversion.Scope) error {
|
||||
*out = *in
|
||||
return nil
|
||||
},
|
||||
); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
codecs := serializer.NewCodecFactory(scheme)
|
||||
return scheme, codecs
|
||||
}
|
||||
|
||||
func newEtcdHelper(client etcd.Client, scheme *runtime.Scheme, codec runtime.Codec, prefix string) etcdHelper {
|
||||
return *NewEtcdStorage(client, codec, prefix, false, etcdtest.DeserializationCacheSize, scheme, prefixTransformer{prefix: "test!"}).(*etcdHelper)
|
||||
}
|
||||
|
||||
// Returns an encoded version of example.Pod with the given name.
|
||||
func getEncodedPod(name string, codec runtime.Codec) string {
|
||||
pod, _ := runtime.Encode(codec, &examplev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name},
|
||||
})
|
||||
return string(pod)
|
||||
}
|
||||
|
||||
func createObj(t *testing.T, helper etcdHelper, name string, obj, out runtime.Object, ttl uint64) error {
|
||||
err := helper.Create(context.TODO(), name, obj, out, ttl)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func createPodList(t *testing.T, helper etcdHelper, list *example.PodList) error {
|
||||
for i := range list.Items {
|
||||
returnedObj := &example.Pod{}
|
||||
err := createObj(t, helper, list.Items[i].Name, &list.Items[i], returnedObj, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
list.Items[i] = *returnedObj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion)
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
defer server.Terminate(t)
|
||||
helper := newEtcdHelper(server.Client, scheme, codec, etcdtest.PathPrefix())
|
||||
|
||||
list := example.PodList{
|
||||
Items: []example.Pod{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
|
||||
Spec: storagetests.DeepEqualSafePodSpec(),
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "baz"},
|
||||
Spec: storagetests.DeepEqualSafePodSpec(),
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: storagetests.DeepEqualSafePodSpec(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
createPodList(t, helper, &list)
|
||||
var got example.PodList
|
||||
// TODO: a sorted filter function could be applied such implied
|
||||
// ordering on the returned list doesn't matter.
|
||||
err := helper.List(context.TODO(), "/", "", storage.Everything, &got)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %v", err)
|
||||
}
|
||||
|
||||
if e, a := list.Items, got.Items; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %#v, got %#v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransformationFailure(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion)
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
defer server.Terminate(t)
|
||||
helper := newEtcdHelper(server.Client, scheme, codec, etcdtest.PathPrefix())
|
||||
|
||||
pods := []example.Pod{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
|
||||
Spec: storagetests.DeepEqualSafePodSpec(),
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "baz"},
|
||||
Spec: storagetests.DeepEqualSafePodSpec(),
|
||||
},
|
||||
}
|
||||
createPodList(t, helper, &example.PodList{Items: pods[:1]})
|
||||
|
||||
// create a second resource with an invalid prefix
|
||||
oldTransformer := helper.transformer
|
||||
helper.transformer = prefixTransformer{prefix: "otherprefix!"}
|
||||
createPodList(t, helper, &example.PodList{Items: pods[1:]})
|
||||
helper.transformer = oldTransformer
|
||||
|
||||
// only the first item is returned, and no error
|
||||
var got example.PodList
|
||||
if err := helper.List(context.TODO(), "/", "", storage.Everything, &got); err != nil {
|
||||
t.Errorf("Unexpected error %v", err)
|
||||
}
|
||||
if e, a := pods[:1], got.Items; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Unexpected: %s", diff.ObjectReflectDiff(e, a))
|
||||
}
|
||||
|
||||
// Get should fail
|
||||
if err := helper.Get(context.TODO(), "/baz", "", &example.Pod{}, false); !storage.IsInternalError(err) {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
// GuaranteedUpdate should return an error
|
||||
if err := helper.GuaranteedUpdate(context.TODO(), "/baz", &example.Pod{}, false, nil, func(input runtime.Object, res storage.ResponseMeta) (output runtime.Object, ttl *uint64, err error) {
|
||||
return input, nil, nil
|
||||
}, &pods[1]); !storage.IsInternalError(err) {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Delete succeeds but reports an error because we cannot access the body
|
||||
if err := helper.Delete(context.TODO(), "/baz", &example.Pod{}, nil); !storage.IsInternalError(err) {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err := helper.Get(context.TODO(), "/baz", "", &example.Pod{}, false); !storage.IsNotFound(err) {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListFiltered(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion)
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
defer server.Terminate(t)
|
||||
helper := newEtcdHelper(server.Client, scheme, codec, etcdtest.PathPrefix())
|
||||
|
||||
list := example.PodList{
|
||||
Items: []example.Pod{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
|
||||
Spec: storagetests.DeepEqualSafePodSpec(),
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "baz"},
|
||||
Spec: storagetests.DeepEqualSafePodSpec(),
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: storagetests.DeepEqualSafePodSpec(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
createPodList(t, helper, &list)
|
||||
// List only "bar" pod
|
||||
p := storage.SelectionPredicate{
|
||||
Label: labels.Everything(),
|
||||
Field: fields.SelectorFromSet(fields.Set{"metadata.name": "bar"}),
|
||||
GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, bool, error) {
|
||||
pod := obj.(*example.Pod)
|
||||
return labels.Set(pod.Labels), fields.Set{"metadata.name": pod.Name}, pod.Initializers != nil, nil
|
||||
},
|
||||
}
|
||||
var got example.PodList
|
||||
err := helper.List(context.TODO(), "/", "", p, &got)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %v", err)
|
||||
}
|
||||
// Check to make certain that the filter function only returns "bar"
|
||||
if e, a := list.Items[0], got.Items[0]; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %#v, got %#v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
// TestListAcrossDirectories ensures that the client excludes directories and flattens tree-response - simulates cross-namespace query
|
||||
func TestListAcrossDirectories(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
codec := apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion)
|
||||
defer server.Terminate(t)
|
||||
|
||||
roothelper := newEtcdHelper(server.Client, scheme, codec, etcdtest.PathPrefix())
|
||||
helper1 := newEtcdHelper(server.Client, scheme, codec, etcdtest.PathPrefix()+"/dir1")
|
||||
helper2 := newEtcdHelper(server.Client, scheme, codec, etcdtest.PathPrefix()+"/dir2")
|
||||
|
||||
list := example.PodList{
|
||||
Items: []example.Pod{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "baz"},
|
||||
Spec: storagetests.DeepEqualSafePodSpec(),
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: storagetests.DeepEqualSafePodSpec(),
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
|
||||
Spec: storagetests.DeepEqualSafePodSpec(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
returnedObj := &example.Pod{}
|
||||
// create the 1st 2 elements in one directory
|
||||
createObj(t, helper1, list.Items[0].Name, &list.Items[0], returnedObj, 0)
|
||||
list.Items[0] = *returnedObj
|
||||
createObj(t, helper1, list.Items[1].Name, &list.Items[1], returnedObj, 0)
|
||||
list.Items[1] = *returnedObj
|
||||
// create the last element in the other directory
|
||||
createObj(t, helper2, list.Items[2].Name, &list.Items[2], returnedObj, 0)
|
||||
list.Items[2] = *returnedObj
|
||||
|
||||
var got example.PodList
|
||||
err := roothelper.List(context.TODO(), "/", "", storage.Everything, &got)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %v", err)
|
||||
}
|
||||
if e, a := list.Items, got.Items; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %#v, got %#v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion)
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
defer server.Terminate(t)
|
||||
key := "/some/key"
|
||||
helper := newEtcdHelper(server.Client, scheme, codec, etcdtest.PathPrefix())
|
||||
expect := example.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: storagetests.DeepEqualSafePodSpec(),
|
||||
}
|
||||
var got example.Pod
|
||||
if err := helper.Create(context.TODO(), key, &expect, &got, 0); err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
}
|
||||
expect = got
|
||||
if err := helper.Get(context.TODO(), key, "", &got, false); err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, expect) {
|
||||
t.Errorf("Wanted %#v, got %#v", expect, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNotFoundErr(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, schema.GroupVersion{Version: "v1"})
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
defer server.Terminate(t)
|
||||
boguskey := "/some/boguskey"
|
||||
helper := newEtcdHelper(server.Client, scheme, codec, etcdtest.PathPrefix())
|
||||
|
||||
var got example.Pod
|
||||
err := helper.Get(context.TODO(), boguskey, "", &got, false)
|
||||
if !storage.IsNotFound(err) {
|
||||
t.Errorf("Unexpected reponse on key=%v, err=%v", boguskey, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion)
|
||||
obj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
defer server.Terminate(t)
|
||||
helper := newEtcdHelper(server.Client, scheme, codec, etcdtest.PathPrefix())
|
||||
returnedObj := &example.Pod{}
|
||||
err := helper.Create(context.TODO(), "/some/key", obj, returnedObj, 5)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
}
|
||||
_, err = runtime.Encode(codec, obj)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
}
|
||||
err = helper.Get(context.TODO(), "/some/key", "", returnedObj, false)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
}
|
||||
_, err = runtime.Encode(codec, returnedObj)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
}
|
||||
if obj.Name != returnedObj.Name {
|
||||
t.Errorf("Wanted %v, got %v", obj.Name, returnedObj.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateNilOutParam(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion)
|
||||
obj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
defer server.Terminate(t)
|
||||
helper := newEtcdHelper(server.Client, scheme, codec, etcdtest.PathPrefix())
|
||||
err := helper.Create(context.TODO(), "/some/key", obj, nil, 5)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuaranteedUpdate(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, schema.GroupVersion{Version: runtime.APIVersionInternal})
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
defer server.Terminate(t)
|
||||
key := "/some/key"
|
||||
helper := newEtcdHelper(server.Client, scheme, codec, etcdtest.PathPrefix())
|
||||
|
||||
obj := &storagetesting.TestResource{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Value: 1}
|
||||
err := helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, nil, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) {
|
||||
return obj, nil
|
||||
}))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
}
|
||||
|
||||
// Update an existing node.
|
||||
callbackCalled := false
|
||||
objUpdate := &storagetesting.TestResource{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Value: 2}
|
||||
err = helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, nil, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) {
|
||||
callbackCalled = true
|
||||
|
||||
if in.(*storagetesting.TestResource).Value != 1 {
|
||||
t.Errorf("Callback input was not current set value")
|
||||
}
|
||||
|
||||
return objUpdate, nil
|
||||
}))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
objCheck := &storagetesting.TestResource{}
|
||||
err = helper.Get(context.TODO(), key, "", objCheck, false)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
}
|
||||
if objCheck.Value != 2 {
|
||||
t.Errorf("Value should have been 2 but got %v", objCheck.Value)
|
||||
}
|
||||
|
||||
if !callbackCalled {
|
||||
t.Errorf("tryUpdate callback should have been called.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuaranteedUpdateNoChange(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, schema.GroupVersion{Version: runtime.APIVersionInternal})
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
defer server.Terminate(t)
|
||||
key := "/some/key"
|
||||
helper := newEtcdHelper(server.Client, scheme, codec, etcdtest.PathPrefix())
|
||||
|
||||
obj := &storagetesting.TestResource{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Value: 1}
|
||||
original := &storagetesting.TestResource{}
|
||||
err := helper.GuaranteedUpdate(context.TODO(), key, original, true, nil, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) {
|
||||
return obj, nil
|
||||
}))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
}
|
||||
|
||||
// Update an existing node with the same data
|
||||
callbackCalled := false
|
||||
objUpdate := &storagetesting.TestResource{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: original.ResourceVersion}, Value: 1}
|
||||
result := &storagetesting.TestResource{}
|
||||
err = helper.GuaranteedUpdate(context.TODO(), key, result, true, nil, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) {
|
||||
callbackCalled = true
|
||||
return objUpdate, nil
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %#v", err)
|
||||
}
|
||||
if !callbackCalled {
|
||||
t.Errorf("tryUpdate callback should have been called.")
|
||||
}
|
||||
if result.ResourceVersion != original.ResourceVersion {
|
||||
t.Fatalf("updated the object resource version")
|
||||
}
|
||||
|
||||
// Update an existing node with the same data but return stale
|
||||
helper.transformer = prefixTransformer{prefix: "test!", stale: true}
|
||||
callbackCalled = false
|
||||
result = &storagetesting.TestResource{}
|
||||
objUpdate = &storagetesting.TestResource{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Value: 1}
|
||||
err = helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, nil, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) {
|
||||
callbackCalled = true
|
||||
return objUpdate, nil
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %#v", err)
|
||||
}
|
||||
if !callbackCalled {
|
||||
t.Errorf("tryUpdate callback should have been called.")
|
||||
}
|
||||
if result.ResourceVersion == original.ResourceVersion {
|
||||
t.Errorf("did not update the object resource version")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuaranteedUpdateKeyNotFound(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, schema.GroupVersion{Version: runtime.APIVersionInternal})
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
defer server.Terminate(t)
|
||||
key := "/some/key"
|
||||
helper := newEtcdHelper(server.Client, scheme, codec, etcdtest.PathPrefix())
|
||||
|
||||
// Create a new node.
|
||||
obj := &storagetesting.TestResource{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Value: 1}
|
||||
|
||||
f := storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) {
|
||||
return obj, nil
|
||||
})
|
||||
|
||||
ignoreNotFound := false
|
||||
err := helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, ignoreNotFound, nil, f)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error for key not found.")
|
||||
}
|
||||
|
||||
ignoreNotFound = true
|
||||
err = helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, ignoreNotFound, nil, f)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %v.", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuaranteedUpdate_CreateCollision(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, schema.GroupVersion{Version: runtime.APIVersionInternal})
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
defer server.Terminate(t)
|
||||
key := "/some/key"
|
||||
helper := newEtcdHelper(server.Client, scheme, codec, etcdtest.PathPrefix())
|
||||
|
||||
const concurrency = 10
|
||||
var wgDone sync.WaitGroup
|
||||
var wgForceCollision sync.WaitGroup
|
||||
wgDone.Add(concurrency)
|
||||
wgForceCollision.Add(concurrency)
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
// Increment storagetesting.TestResource.Value by 1
|
||||
go func() {
|
||||
defer wgDone.Done()
|
||||
|
||||
firstCall := true
|
||||
err := helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, nil, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) {
|
||||
defer func() { firstCall = false }()
|
||||
|
||||
if firstCall {
|
||||
// Force collision by joining all concurrent GuaranteedUpdate operations here.
|
||||
wgForceCollision.Done()
|
||||
wgForceCollision.Wait()
|
||||
}
|
||||
|
||||
currValue := in.(*storagetesting.TestResource).Value
|
||||
obj := &storagetesting.TestResource{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Value: currValue + 1}
|
||||
return obj, nil
|
||||
}))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wgDone.Wait()
|
||||
|
||||
stored := &storagetesting.TestResource{}
|
||||
err := helper.Get(context.TODO(), key, "", stored, false)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %#v", stored)
|
||||
}
|
||||
if stored.Value != concurrency {
|
||||
t.Errorf("Some of the writes were lost. Stored value: %d", stored.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuaranteedUpdateUIDMismatch(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion)
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
defer server.Terminate(t)
|
||||
prefix := path.Join("/", etcdtest.PathPrefix())
|
||||
helper := newEtcdHelper(server.Client, scheme, codec, prefix)
|
||||
|
||||
obj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "A"}}
|
||||
podPtr := &example.Pod{}
|
||||
err := helper.Create(context.TODO(), "/some/key", obj, podPtr, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %#v", err)
|
||||
}
|
||||
err = helper.GuaranteedUpdate(context.TODO(), "/some/key", podPtr, true, storage.NewUIDPreconditions("B"), storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) {
|
||||
return obj, nil
|
||||
}))
|
||||
if !storage.IsInvalidObj(err) {
|
||||
t.Fatalf("Expect a Test Failed (write conflict) error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteUIDMismatch(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion)
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
defer server.Terminate(t)
|
||||
prefix := path.Join("/", etcdtest.PathPrefix())
|
||||
helper := newEtcdHelper(server.Client, scheme, codec, prefix)
|
||||
|
||||
obj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "A"}}
|
||||
podPtr := &example.Pod{}
|
||||
err := helper.Create(context.TODO(), "/some/key", obj, podPtr, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %#v", err)
|
||||
}
|
||||
err = helper.Delete(context.TODO(), "/some/key", obj, storage.NewUIDPreconditions("B"))
|
||||
if !storage.IsInvalidObj(err) {
|
||||
t.Fatalf("Expect a Test Failed (write conflict) error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type getFunc func(ctx context.Context, key string, opts *etcd.GetOptions) (*etcd.Response, error)
|
||||
|
||||
type fakeDeleteKeysAPI struct {
|
||||
etcd.KeysAPI
|
||||
fakeGetFunc getFunc
|
||||
getCount int
|
||||
// The fakeGetFunc will be called fakeGetCap times before the KeysAPI's Get will be called.
|
||||
fakeGetCap int
|
||||
}
|
||||
|
||||
func (f *fakeDeleteKeysAPI) Get(ctx context.Context, key string, opts *etcd.GetOptions) (*etcd.Response, error) {
|
||||
f.getCount++
|
||||
if f.getCount < f.fakeGetCap {
|
||||
return f.fakeGetFunc(ctx, key, opts)
|
||||
}
|
||||
return f.KeysAPI.Get(ctx, key, opts)
|
||||
}
|
||||
|
||||
// This is to emulate the case where another party updates the object when
|
||||
// etcdHelper.Delete has verified the preconditions, but hasn't carried out the
|
||||
// deletion yet. Etcd will fail the deletion and report the conflict. etcdHelper
|
||||
// should retry until there is no conflict.
|
||||
func TestDeleteWithRetry(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion)
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
defer server.Terminate(t)
|
||||
prefix := path.Join("/", etcdtest.PathPrefix())
|
||||
|
||||
obj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "A"}}
|
||||
// fakeGet returns a large ModifiedIndex to emulate the case that another
|
||||
// party has updated the object.
|
||||
fakeGet := func(ctx context.Context, key string, opts *etcd.GetOptions) (*etcd.Response, error) {
|
||||
data, _ := runtime.Encode(codec, obj)
|
||||
return &etcd.Response{Node: &etcd.Node{Value: defaultPrefixValue(data), ModifiedIndex: 99}}, nil
|
||||
}
|
||||
expectedRetries := 3
|
||||
helper := newEtcdHelper(server.Client, scheme, codec, prefix)
|
||||
fake := &fakeDeleteKeysAPI{KeysAPI: helper.etcdKeysAPI, fakeGetCap: expectedRetries, fakeGetFunc: fakeGet}
|
||||
helper.etcdKeysAPI = fake
|
||||
|
||||
returnedObj := &example.Pod{}
|
||||
err := helper.Create(context.TODO(), "/some/key", obj, returnedObj, 0)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
}
|
||||
|
||||
err = helper.Delete(context.TODO(), "/some/key", obj, storage.NewUIDPreconditions("A"))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
}
|
||||
if fake.getCount != expectedRetries {
|
||||
t.Errorf("Expect %d retries, got %d", expectedRetries, fake.getCount)
|
||||
}
|
||||
err = helper.Get(context.TODO(), "/some/key", "", obj, false)
|
||||
if !storage.IsNotFound(err) {
|
||||
t.Errorf("Expect an NotFound error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefix(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion)
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
defer server.Terminate(t)
|
||||
|
||||
testcases := map[string]string{
|
||||
"custom/prefix": "/custom/prefix",
|
||||
"/custom//prefix//": "/custom/prefix",
|
||||
"/registry": "/registry",
|
||||
}
|
||||
for configuredPrefix, effectivePrefix := range testcases {
|
||||
helper := newEtcdHelper(server.Client, scheme, codec, configuredPrefix)
|
||||
if helper.pathPrefix != effectivePrefix {
|
||||
t.Errorf("configured prefix of %s, expected effective prefix of %s, got %s", configuredPrefix, effectivePrefix, helper.pathPrefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
497
vendor/k8s.io/apiserver/pkg/storage/etcd/etcd_watcher.go
generated
vendored
Normal file
497
vendor/k8s.io/apiserver/pkg/storage/etcd/etcd_watcher.go
generated
vendored
Normal file
|
|
@ -0,0 +1,497 @@
|
|||
/*
|
||||
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 etcd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
etcdutil "k8s.io/apiserver/pkg/storage/etcd/util"
|
||||
|
||||
etcd "github.com/coreos/etcd/client"
|
||||
"github.com/golang/glog"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Etcd watch event actions
|
||||
const (
|
||||
EtcdCreate = "create"
|
||||
EtcdGet = "get"
|
||||
EtcdSet = "set"
|
||||
EtcdCAS = "compareAndSwap"
|
||||
EtcdDelete = "delete"
|
||||
EtcdCAD = "compareAndDelete"
|
||||
EtcdExpire = "expire"
|
||||
)
|
||||
|
||||
// TransformFunc attempts to convert an object to another object for use with a watcher.
|
||||
type TransformFunc func(runtime.Object) (runtime.Object, error)
|
||||
|
||||
// includeFunc returns true if the given key should be considered part of a watch
|
||||
type includeFunc func(key string) bool
|
||||
|
||||
// exceptKey is an includeFunc that returns false when the provided key matches the watched key
|
||||
func exceptKey(except string) includeFunc {
|
||||
return func(key string) bool {
|
||||
return key != except
|
||||
}
|
||||
}
|
||||
|
||||
// etcdWatcher converts a native etcd watch to a watch.Interface.
|
||||
type etcdWatcher struct {
|
||||
// HighWaterMarks for performance debugging.
|
||||
// Important: Since HighWaterMark is using sync/atomic, it has to be at the top of the struct due to a bug on 32-bit platforms
|
||||
// See: https://golang.org/pkg/sync/atomic/ for more information
|
||||
incomingHWM storage.HighWaterMark
|
||||
outgoingHWM storage.HighWaterMark
|
||||
|
||||
encoding runtime.Codec
|
||||
// Note that versioner is required for etcdWatcher to work correctly.
|
||||
// There is no public constructor of it, so be careful when manipulating
|
||||
// with it manually.
|
||||
versioner storage.Versioner
|
||||
transform TransformFunc
|
||||
valueTransformer ValueTransformer
|
||||
|
||||
list bool // If we're doing a recursive watch, should be true.
|
||||
quorum bool // If we enable quorum, shoule be true
|
||||
include includeFunc
|
||||
filter storage.FilterFunc
|
||||
|
||||
etcdIncoming chan *etcd.Response
|
||||
etcdError chan error
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
etcdCallEnded chan struct{}
|
||||
|
||||
outgoing chan watch.Event
|
||||
userStop chan struct{}
|
||||
stopped bool
|
||||
stopLock sync.Mutex
|
||||
// wg is used to avoid calls to etcd after Stop(), and to make sure
|
||||
// that the translate goroutine is not leaked.
|
||||
wg sync.WaitGroup
|
||||
|
||||
// Injectable for testing. Send the event down the outgoing channel.
|
||||
emit func(watch.Event)
|
||||
|
||||
cache etcdCache
|
||||
}
|
||||
|
||||
// watchWaitDuration is the amount of time to wait for an error from watch.
|
||||
const watchWaitDuration = 100 * time.Millisecond
|
||||
|
||||
// newEtcdWatcher returns a new etcdWatcher; if list is true, watch sub-nodes.
|
||||
// The versioner must be able to handle the objects that transform creates.
|
||||
func newEtcdWatcher(
|
||||
list bool, quorum bool, include includeFunc, filter storage.FilterFunc,
|
||||
encoding runtime.Codec, versioner storage.Versioner, transform TransformFunc,
|
||||
valueTransformer ValueTransformer,
|
||||
cache etcdCache) *etcdWatcher {
|
||||
w := &etcdWatcher{
|
||||
encoding: encoding,
|
||||
versioner: versioner,
|
||||
transform: transform,
|
||||
valueTransformer: valueTransformer,
|
||||
|
||||
list: list,
|
||||
quorum: quorum,
|
||||
include: include,
|
||||
filter: filter,
|
||||
// Buffer this channel, so that the etcd client is not forced
|
||||
// to context switch with every object it gets, and so that a
|
||||
// long time spent decoding an object won't block the *next*
|
||||
// object. Basically, we see a lot of "401 window exceeded"
|
||||
// errors from etcd, and that's due to the client not streaming
|
||||
// results but rather getting them one at a time. So we really
|
||||
// want to never block the etcd client, if possible. The 100 is
|
||||
// mostly arbitrary--we know it goes as high as 50, though.
|
||||
// There's a V(2) log message that prints the length so we can
|
||||
// monitor how much of this buffer is actually used.
|
||||
etcdIncoming: make(chan *etcd.Response, 100),
|
||||
etcdError: make(chan error, 1),
|
||||
// Similarly to etcdIncomming, we don't want to force context
|
||||
// switch on every new incoming object.
|
||||
outgoing: make(chan watch.Event, 100),
|
||||
userStop: make(chan struct{}),
|
||||
stopped: false,
|
||||
wg: sync.WaitGroup{},
|
||||
cache: cache,
|
||||
ctx: nil,
|
||||
cancel: nil,
|
||||
}
|
||||
w.emit = func(e watch.Event) {
|
||||
if curLen := int64(len(w.outgoing)); w.outgoingHWM.Update(curLen) {
|
||||
// Monitor if this gets backed up, and how much.
|
||||
glog.V(1).Infof("watch (%v): %v objects queued in outgoing channel.", reflect.TypeOf(e.Object).String(), curLen)
|
||||
}
|
||||
// Give up on user stop, without this we leak a lot of goroutines in tests.
|
||||
select {
|
||||
case w.outgoing <- e:
|
||||
case <-w.userStop:
|
||||
}
|
||||
}
|
||||
// translate will call done. We need to Add() here because otherwise,
|
||||
// if Stop() gets called before translate gets started, there'd be a
|
||||
// problem.
|
||||
w.wg.Add(1)
|
||||
go w.translate()
|
||||
return w
|
||||
}
|
||||
|
||||
// etcdWatch calls etcd's Watch function, and handles any errors. Meant to be called
|
||||
// as a goroutine.
|
||||
func (w *etcdWatcher) etcdWatch(ctx context.Context, client etcd.KeysAPI, key string, resourceVersion uint64) {
|
||||
defer utilruntime.HandleCrash()
|
||||
defer close(w.etcdError)
|
||||
defer close(w.etcdIncoming)
|
||||
|
||||
// All calls to etcd are coming from this function - once it is finished
|
||||
// no other call to etcd should be generated by this watcher.
|
||||
done := func() {}
|
||||
|
||||
// We need to be prepared, that Stop() can be called at any time.
|
||||
// It can potentially also be called, even before this function is called.
|
||||
// If that is the case, we simply skip all the code here.
|
||||
// See #18928 for more details.
|
||||
var watcher etcd.Watcher
|
||||
returned := func() bool {
|
||||
w.stopLock.Lock()
|
||||
defer w.stopLock.Unlock()
|
||||
if w.stopped {
|
||||
// Watcher has already been stopped - don't event initiate it here.
|
||||
return true
|
||||
}
|
||||
w.wg.Add(1)
|
||||
done = w.wg.Done
|
||||
// Perform initialization of watcher under lock - we want to avoid situation when
|
||||
// Stop() is called in the meantime (which in tests can cause etcd termination and
|
||||
// strange behavior here).
|
||||
if resourceVersion == 0 {
|
||||
latest, err := etcdGetInitialWatchState(ctx, client, key, w.list, w.quorum, w.etcdIncoming)
|
||||
if err != nil {
|
||||
w.etcdError <- err
|
||||
return true
|
||||
}
|
||||
resourceVersion = latest
|
||||
}
|
||||
|
||||
opts := etcd.WatcherOptions{
|
||||
Recursive: w.list,
|
||||
AfterIndex: resourceVersion,
|
||||
}
|
||||
watcher = client.Watcher(key, &opts)
|
||||
w.ctx, w.cancel = context.WithCancel(ctx)
|
||||
return false
|
||||
}()
|
||||
defer done()
|
||||
if returned {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
resp, err := watcher.Next(w.ctx)
|
||||
if err != nil {
|
||||
w.etcdError <- err
|
||||
return
|
||||
}
|
||||
w.etcdIncoming <- resp
|
||||
}
|
||||
}
|
||||
|
||||
// etcdGetInitialWatchState turns an etcd Get request into a watch equivalent
|
||||
func etcdGetInitialWatchState(ctx context.Context, client etcd.KeysAPI, key string, recursive bool, quorum bool, incoming chan<- *etcd.Response) (resourceVersion uint64, err error) {
|
||||
opts := etcd.GetOptions{
|
||||
Recursive: recursive,
|
||||
Sort: false,
|
||||
Quorum: quorum,
|
||||
}
|
||||
resp, err := client.Get(ctx, key, &opts)
|
||||
if err != nil {
|
||||
if !etcdutil.IsEtcdNotFound(err) {
|
||||
utilruntime.HandleError(fmt.Errorf("watch was unable to retrieve the current index for the provided key (%q): %v", key, err))
|
||||
return resourceVersion, toStorageErr(err, key, 0)
|
||||
}
|
||||
if etcdError, ok := err.(etcd.Error); ok {
|
||||
resourceVersion = etcdError.Index
|
||||
}
|
||||
return resourceVersion, nil
|
||||
}
|
||||
resourceVersion = resp.Index
|
||||
convertRecursiveResponse(resp.Node, resp, incoming)
|
||||
return
|
||||
}
|
||||
|
||||
// convertRecursiveResponse turns a recursive get response from etcd into individual response objects
|
||||
// by copying the original response. This emulates the behavior of a recursive watch.
|
||||
func convertRecursiveResponse(node *etcd.Node, response *etcd.Response, incoming chan<- *etcd.Response) {
|
||||
if node.Dir {
|
||||
for i := range node.Nodes {
|
||||
convertRecursiveResponse(node.Nodes[i], response, incoming)
|
||||
}
|
||||
return
|
||||
}
|
||||
copied := *response
|
||||
copied.Action = "get"
|
||||
copied.Node = node
|
||||
incoming <- &copied
|
||||
}
|
||||
|
||||
// translate pulls stuff from etcd, converts, and pushes out the outgoing channel. Meant to be
|
||||
// called as a goroutine.
|
||||
func (w *etcdWatcher) translate() {
|
||||
defer w.wg.Done()
|
||||
defer close(w.outgoing)
|
||||
defer utilruntime.HandleCrash()
|
||||
|
||||
for {
|
||||
select {
|
||||
case err := <-w.etcdError:
|
||||
if err != nil {
|
||||
var status *metav1.Status
|
||||
switch {
|
||||
case etcdutil.IsEtcdWatchExpired(err):
|
||||
status = &metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Message: err.Error(),
|
||||
Code: http.StatusGone, // Gone
|
||||
Reason: metav1.StatusReasonExpired,
|
||||
}
|
||||
// TODO: need to generate errors using api/errors which has a circular dependency on this package
|
||||
// no other way to inject errors
|
||||
// case etcdutil.IsEtcdUnreachable(err):
|
||||
// status = errors.NewServerTimeout(...)
|
||||
default:
|
||||
status = &metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Message: err.Error(),
|
||||
Code: http.StatusInternalServerError,
|
||||
Reason: metav1.StatusReasonInternalError,
|
||||
}
|
||||
}
|
||||
w.emit(watch.Event{
|
||||
Type: watch.Error,
|
||||
Object: status,
|
||||
})
|
||||
}
|
||||
return
|
||||
case <-w.userStop:
|
||||
return
|
||||
case res, ok := <-w.etcdIncoming:
|
||||
if ok {
|
||||
if curLen := int64(len(w.etcdIncoming)); w.incomingHWM.Update(curLen) {
|
||||
// Monitor if this gets backed up, and how much.
|
||||
glog.V(1).Infof("watch: %v objects queued in incoming channel.", curLen)
|
||||
}
|
||||
w.sendResult(res)
|
||||
}
|
||||
// If !ok, don't return here-- must wait for etcdError channel
|
||||
// to give an error or be closed.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// decodeObject extracts an object from the provided etcd node or returns an error.
|
||||
func (w *etcdWatcher) decodeObject(node *etcd.Node) (runtime.Object, error) {
|
||||
if obj, found := w.cache.getFromCache(node.ModifiedIndex, storage.SimpleFilter(storage.Everything)); found {
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
body, _, err := w.valueTransformer.TransformStringFromStorage(node.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
obj, err := runtime.Decode(w.encoding, []byte(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ensure resource version is set on the object we load from etcd
|
||||
if err := w.versioner.UpdateObject(obj, node.ModifiedIndex); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failure to version api object (%d) %#v: %v", node.ModifiedIndex, obj, err))
|
||||
}
|
||||
|
||||
// perform any necessary transformation
|
||||
if w.transform != nil {
|
||||
obj, err = w.transform(obj)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failure to transform api object %#v: %v", obj, err))
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if node.ModifiedIndex != 0 {
|
||||
w.cache.addToCache(node.ModifiedIndex, obj)
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func (w *etcdWatcher) sendAdd(res *etcd.Response) {
|
||||
if res.Node == nil {
|
||||
utilruntime.HandleError(fmt.Errorf("unexpected nil node: %#v", res))
|
||||
return
|
||||
}
|
||||
if w.include != nil && !w.include(res.Node.Key) {
|
||||
return
|
||||
}
|
||||
obj, err := w.decodeObject(res.Node)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failure to decode api object: %v\n'%v' from %#v %#v", err, string(res.Node.Value), res, res.Node))
|
||||
// TODO: expose an error through watch.Interface?
|
||||
// Ignore this value. If we stop the watch on a bad value, a client that uses
|
||||
// the resourceVersion to resume will never be able to get past a bad value.
|
||||
return
|
||||
}
|
||||
if !w.filter(obj) {
|
||||
return
|
||||
}
|
||||
action := watch.Added
|
||||
w.emit(watch.Event{
|
||||
Type: action,
|
||||
Object: obj,
|
||||
})
|
||||
}
|
||||
|
||||
func (w *etcdWatcher) sendModify(res *etcd.Response) {
|
||||
if res.Node == nil {
|
||||
glog.Errorf("unexpected nil node: %#v", res)
|
||||
return
|
||||
}
|
||||
if w.include != nil && !w.include(res.Node.Key) {
|
||||
return
|
||||
}
|
||||
curObj, err := w.decodeObject(res.Node)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failure to decode api object: %v\n'%v' from %#v %#v", err, string(res.Node.Value), res, res.Node))
|
||||
// TODO: expose an error through watch.Interface?
|
||||
// Ignore this value. If we stop the watch on a bad value, a client that uses
|
||||
// the resourceVersion to resume will never be able to get past a bad value.
|
||||
return
|
||||
}
|
||||
curObjPasses := w.filter(curObj)
|
||||
oldObjPasses := false
|
||||
var oldObj runtime.Object
|
||||
if res.PrevNode != nil && res.PrevNode.Value != "" {
|
||||
// Ignore problems reading the old object.
|
||||
if oldObj, err = w.decodeObject(res.PrevNode); err == nil {
|
||||
if err := w.versioner.UpdateObject(oldObj, res.Node.ModifiedIndex); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failure to version api object (%d) %#v: %v", res.Node.ModifiedIndex, oldObj, err))
|
||||
}
|
||||
oldObjPasses = w.filter(oldObj)
|
||||
}
|
||||
}
|
||||
// Some changes to an object may cause it to start or stop matching a filter.
|
||||
// We need to report those as adds/deletes. So we have to check both the previous
|
||||
// and current value of the object.
|
||||
switch {
|
||||
case curObjPasses && oldObjPasses:
|
||||
w.emit(watch.Event{
|
||||
Type: watch.Modified,
|
||||
Object: curObj,
|
||||
})
|
||||
case curObjPasses && !oldObjPasses:
|
||||
w.emit(watch.Event{
|
||||
Type: watch.Added,
|
||||
Object: curObj,
|
||||
})
|
||||
case !curObjPasses && oldObjPasses:
|
||||
w.emit(watch.Event{
|
||||
Type: watch.Deleted,
|
||||
Object: oldObj,
|
||||
})
|
||||
}
|
||||
// Do nothing if neither new nor old object passed the filter.
|
||||
}
|
||||
|
||||
func (w *etcdWatcher) sendDelete(res *etcd.Response) {
|
||||
if res.PrevNode == nil {
|
||||
utilruntime.HandleError(fmt.Errorf("unexpected nil prev node: %#v", res))
|
||||
return
|
||||
}
|
||||
if w.include != nil && !w.include(res.PrevNode.Key) {
|
||||
return
|
||||
}
|
||||
node := *res.PrevNode
|
||||
if res.Node != nil {
|
||||
// Note that this sends the *old* object with the etcd index for the time at
|
||||
// which it gets deleted. This will allow users to restart the watch at the right
|
||||
// index.
|
||||
node.ModifiedIndex = res.Node.ModifiedIndex
|
||||
}
|
||||
obj, err := w.decodeObject(&node)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failure to decode api object: %v\nfrom %#v %#v", err, res, res.Node))
|
||||
// TODO: expose an error through watch.Interface?
|
||||
// Ignore this value. If we stop the watch on a bad value, a client that uses
|
||||
// the resourceVersion to resume will never be able to get past a bad value.
|
||||
return
|
||||
}
|
||||
if !w.filter(obj) {
|
||||
return
|
||||
}
|
||||
w.emit(watch.Event{
|
||||
Type: watch.Deleted,
|
||||
Object: obj,
|
||||
})
|
||||
}
|
||||
|
||||
func (w *etcdWatcher) sendResult(res *etcd.Response) {
|
||||
switch res.Action {
|
||||
case EtcdCreate, EtcdGet:
|
||||
// "Get" will only happen in watch 0 case, where we explicitly want ADDED event
|
||||
// for initial state.
|
||||
w.sendAdd(res)
|
||||
case EtcdSet, EtcdCAS:
|
||||
w.sendModify(res)
|
||||
case EtcdDelete, EtcdExpire, EtcdCAD:
|
||||
w.sendDelete(res)
|
||||
default:
|
||||
utilruntime.HandleError(fmt.Errorf("unknown action: %v", res.Action))
|
||||
}
|
||||
}
|
||||
|
||||
// ResultChan implements watch.Interface.
|
||||
func (w *etcdWatcher) ResultChan() <-chan watch.Event {
|
||||
return w.outgoing
|
||||
}
|
||||
|
||||
// Stop implements watch.Interface.
|
||||
func (w *etcdWatcher) Stop() {
|
||||
w.stopLock.Lock()
|
||||
if w.cancel != nil {
|
||||
w.cancel()
|
||||
w.cancel = nil
|
||||
}
|
||||
if !w.stopped {
|
||||
w.stopped = true
|
||||
close(w.userStop)
|
||||
}
|
||||
w.stopLock.Unlock()
|
||||
|
||||
// Wait until all calls to etcd are finished and no other
|
||||
// will be issued.
|
||||
w.wg.Wait()
|
||||
}
|
||||
557
vendor/k8s.io/apiserver/pkg/storage/etcd/etcd_watcher_test.go
generated
vendored
Normal file
557
vendor/k8s.io/apiserver/pkg/storage/etcd/etcd_watcher_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,557 @@
|
|||
/*
|
||||
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 etcd
|
||||
|
||||
import (
|
||||
rt "runtime"
|
||||
"testing"
|
||||
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
apitesting "k8s.io/apimachinery/pkg/api/testing"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/apiserver/pkg/apis/example"
|
||||
examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/apiserver/pkg/storage/etcd/etcdtest"
|
||||
etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing"
|
||||
|
||||
etcd "github.com/coreos/etcd/client"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var versioner = APIObjectVersioner{}
|
||||
|
||||
// Implements etcdCache interface as empty methods (i.e. does not cache any objects)
|
||||
type fakeEtcdCache struct{}
|
||||
|
||||
func (f *fakeEtcdCache) getFromCache(index uint64, filter storage.FilterFunc) (runtime.Object, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (f *fakeEtcdCache) addToCache(index uint64, obj runtime.Object) {
|
||||
}
|
||||
|
||||
var _ etcdCache = &fakeEtcdCache{}
|
||||
|
||||
func TestWatchInterpretations(t *testing.T) {
|
||||
_, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion)
|
||||
// Declare some pods to make the test cases compact.
|
||||
podFoo := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
|
||||
podBar := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}}
|
||||
podBaz := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "baz"}}
|
||||
|
||||
// All of these test cases will be run with the firstLetterIsB Filter.
|
||||
table := map[string]struct {
|
||||
actions []string // Run this test item for every action here.
|
||||
prevNodeValue string
|
||||
nodeValue string
|
||||
expectEmit bool
|
||||
expectType watch.EventType
|
||||
expectObject runtime.Object
|
||||
}{
|
||||
"create": {
|
||||
actions: []string{"create", "get"},
|
||||
nodeValue: runtime.EncodeOrDie(codec, podBar),
|
||||
expectEmit: true,
|
||||
expectType: watch.Added,
|
||||
expectObject: podBar,
|
||||
},
|
||||
"create but filter blocks": {
|
||||
actions: []string{"create", "get"},
|
||||
nodeValue: runtime.EncodeOrDie(codec, podFoo),
|
||||
expectEmit: false,
|
||||
},
|
||||
"delete": {
|
||||
actions: []string{"delete"},
|
||||
prevNodeValue: runtime.EncodeOrDie(codec, podBar),
|
||||
expectEmit: true,
|
||||
expectType: watch.Deleted,
|
||||
expectObject: podBar,
|
||||
},
|
||||
"delete but filter blocks": {
|
||||
actions: []string{"delete"},
|
||||
nodeValue: runtime.EncodeOrDie(codec, podFoo),
|
||||
expectEmit: false,
|
||||
},
|
||||
"modify appears to create 1": {
|
||||
actions: []string{"set", "compareAndSwap"},
|
||||
nodeValue: runtime.EncodeOrDie(codec, podBar),
|
||||
expectEmit: true,
|
||||
expectType: watch.Added,
|
||||
expectObject: podBar,
|
||||
},
|
||||
"modify appears to create 2": {
|
||||
actions: []string{"set", "compareAndSwap"},
|
||||
prevNodeValue: runtime.EncodeOrDie(codec, podFoo),
|
||||
nodeValue: runtime.EncodeOrDie(codec, podBar),
|
||||
expectEmit: true,
|
||||
expectType: watch.Added,
|
||||
expectObject: podBar,
|
||||
},
|
||||
"modify appears to delete": {
|
||||
actions: []string{"set", "compareAndSwap"},
|
||||
prevNodeValue: runtime.EncodeOrDie(codec, podBar),
|
||||
nodeValue: runtime.EncodeOrDie(codec, podFoo),
|
||||
expectEmit: true,
|
||||
expectType: watch.Deleted,
|
||||
expectObject: podBar, // Should return last state that passed the filter!
|
||||
},
|
||||
"modify modifies": {
|
||||
actions: []string{"set", "compareAndSwap"},
|
||||
prevNodeValue: runtime.EncodeOrDie(codec, podBar),
|
||||
nodeValue: runtime.EncodeOrDie(codec, podBaz),
|
||||
expectEmit: true,
|
||||
expectType: watch.Modified,
|
||||
expectObject: podBaz,
|
||||
},
|
||||
"modify ignores": {
|
||||
actions: []string{"set", "compareAndSwap"},
|
||||
nodeValue: runtime.EncodeOrDie(codec, podFoo),
|
||||
expectEmit: false,
|
||||
},
|
||||
}
|
||||
firstLetterIsB := func(obj runtime.Object) bool {
|
||||
return obj.(*example.Pod).Name[0] == 'b'
|
||||
}
|
||||
for name, item := range table {
|
||||
for _, action := range item.actions {
|
||||
w := newEtcdWatcher(true, false, nil, firstLetterIsB, codec, versioner, nil, prefixTransformer{prefix: "test!"}, &fakeEtcdCache{})
|
||||
emitCalled := false
|
||||
w.emit = func(event watch.Event) {
|
||||
emitCalled = true
|
||||
if !item.expectEmit {
|
||||
return
|
||||
}
|
||||
if e, a := item.expectType, event.Type; e != a {
|
||||
t.Errorf("'%v - %v': expected %v, got %v", name, action, e, a)
|
||||
}
|
||||
if e, a := item.expectObject, event.Object; !apiequality.Semantic.DeepDerivative(e, a) {
|
||||
t.Errorf("'%v - %v': expected %v, got %v", name, action, e, a)
|
||||
}
|
||||
}
|
||||
|
||||
var n, pn *etcd.Node
|
||||
if item.nodeValue != "" {
|
||||
n = &etcd.Node{Value: defaultPrefix(item.nodeValue)}
|
||||
}
|
||||
if item.prevNodeValue != "" {
|
||||
pn = &etcd.Node{Value: defaultPrefix(item.prevNodeValue)}
|
||||
}
|
||||
|
||||
w.sendResult(&etcd.Response{
|
||||
Action: action,
|
||||
Node: n,
|
||||
PrevNode: pn,
|
||||
})
|
||||
|
||||
if e, a := item.expectEmit, emitCalled; e != a {
|
||||
t.Errorf("'%v - %v': expected %v, got %v", name, action, e, a)
|
||||
}
|
||||
w.Stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchInterpretation_ResponseNotSet(t *testing.T) {
|
||||
_, codecs := testScheme(t)
|
||||
codec := codecs.LegacyCodec(schema.GroupVersion{Version: "v1"})
|
||||
w := newEtcdWatcher(false, false, nil, storage.SimpleFilter(storage.Everything), codec, versioner, nil, prefixTransformer{prefix: "test!"}, &fakeEtcdCache{})
|
||||
w.emit = func(e watch.Event) {
|
||||
t.Errorf("Unexpected emit: %v", e)
|
||||
}
|
||||
|
||||
w.sendResult(&etcd.Response{
|
||||
Action: "update",
|
||||
})
|
||||
w.Stop()
|
||||
}
|
||||
|
||||
func TestWatchInterpretation_ResponseNoNode(t *testing.T) {
|
||||
_, codecs := testScheme(t)
|
||||
codec := codecs.LegacyCodec(schema.GroupVersion{Version: "v1"})
|
||||
actions := []string{"create", "set", "compareAndSwap", "delete"}
|
||||
for _, action := range actions {
|
||||
w := newEtcdWatcher(false, false, nil, storage.SimpleFilter(storage.Everything), codec, versioner, nil, prefixTransformer{prefix: "test!"}, &fakeEtcdCache{})
|
||||
w.emit = func(e watch.Event) {
|
||||
t.Errorf("Unexpected emit: %v", e)
|
||||
}
|
||||
w.sendResult(&etcd.Response{
|
||||
Action: action,
|
||||
})
|
||||
w.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchInterpretation_ResponseBadData(t *testing.T) {
|
||||
_, codecs := testScheme(t)
|
||||
codec := codecs.LegacyCodec(schema.GroupVersion{Version: "v1"})
|
||||
actions := []string{"create", "set", "compareAndSwap", "delete"}
|
||||
for _, action := range actions {
|
||||
w := newEtcdWatcher(false, false, nil, storage.SimpleFilter(storage.Everything), codec, versioner, nil, prefixTransformer{prefix: "test!"}, &fakeEtcdCache{})
|
||||
w.emit = func(e watch.Event) {
|
||||
t.Errorf("Unexpected emit: %v", e)
|
||||
}
|
||||
w.sendResult(&etcd.Response{
|
||||
Action: action,
|
||||
Node: &etcd.Node{
|
||||
Value: defaultPrefix("foobar"),
|
||||
},
|
||||
})
|
||||
w.sendResult(&etcd.Response{
|
||||
Action: action,
|
||||
PrevNode: &etcd.Node{
|
||||
Value: defaultPrefix("foobar"),
|
||||
},
|
||||
})
|
||||
w.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendResultDeleteEventHaveLatestIndex(t *testing.T) {
|
||||
_, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion)
|
||||
filter := func(obj runtime.Object) bool {
|
||||
return obj.(*example.Pod).Name != "bar"
|
||||
}
|
||||
w := newEtcdWatcher(false, false, nil, filter, codec, versioner, nil, prefixTransformer{prefix: "test!"}, &fakeEtcdCache{})
|
||||
|
||||
eventChan := make(chan watch.Event, 1)
|
||||
w.emit = func(e watch.Event) {
|
||||
eventChan <- e
|
||||
}
|
||||
|
||||
fooPod := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
|
||||
barPod := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}}
|
||||
fooBytes, err := runtime.Encode(codec, fooPod)
|
||||
if err != nil {
|
||||
t.Fatalf("Encode failed: %v", err)
|
||||
}
|
||||
barBytes, err := runtime.Encode(codec, barPod)
|
||||
if err != nil {
|
||||
t.Fatalf("Encode failed: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
response *etcd.Response
|
||||
expRV string
|
||||
}{{ // Delete event
|
||||
response: &etcd.Response{
|
||||
Action: EtcdDelete,
|
||||
Node: &etcd.Node{
|
||||
ModifiedIndex: 2,
|
||||
},
|
||||
PrevNode: &etcd.Node{
|
||||
Value: defaultPrefixValue(fooBytes),
|
||||
ModifiedIndex: 1,
|
||||
},
|
||||
},
|
||||
expRV: "2",
|
||||
}, { // Modify event with uninterested data
|
||||
response: &etcd.Response{
|
||||
Action: EtcdSet,
|
||||
Node: &etcd.Node{
|
||||
Value: defaultPrefixValue(barBytes),
|
||||
ModifiedIndex: 2,
|
||||
},
|
||||
PrevNode: &etcd.Node{
|
||||
Value: defaultPrefixValue(fooBytes),
|
||||
ModifiedIndex: 1,
|
||||
},
|
||||
},
|
||||
expRV: "2",
|
||||
}}
|
||||
|
||||
for i, tt := range tests {
|
||||
w.sendResult(tt.response)
|
||||
ev := <-eventChan
|
||||
if ev.Type != watch.Deleted {
|
||||
t.Errorf("#%d: event type want=Deleted, get=%s", i, ev.Type)
|
||||
return
|
||||
}
|
||||
rv := ev.Object.(*example.Pod).ResourceVersion
|
||||
if rv != tt.expRV {
|
||||
t.Errorf("#%d: resource version want=%s, get=%s", i, tt.expRV, rv)
|
||||
}
|
||||
}
|
||||
w.Stop()
|
||||
}
|
||||
|
||||
func TestWatch(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion)
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
defer server.Terminate(t)
|
||||
key := "/some/key"
|
||||
h := newEtcdHelper(server.Client, scheme, codec, etcdtest.PathPrefix())
|
||||
|
||||
watching, err := h.Watch(context.TODO(), key, "0", storage.Everything)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
// watching is explicitly closed below.
|
||||
|
||||
// Test normal case
|
||||
pod := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
|
||||
returnObj := &example.Pod{}
|
||||
err = h.Create(context.TODO(), key, pod, returnObj, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
event := <-watching.ResultChan()
|
||||
if e, a := watch.Added, event.Type; e != a {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
}
|
||||
if e, a := pod, event.Object; !apiequality.Semantic.DeepDerivative(e, a) {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
}
|
||||
|
||||
watching.Stop()
|
||||
|
||||
// There is a race in etcdWatcher so that after calling Stop() one of
|
||||
// two things can happen:
|
||||
// - ResultChan() may be closed (triggered by closing userStop channel)
|
||||
// - an Error "context cancelled" may be emitted (triggered by cancelling request
|
||||
// to etcd and putting that error to etcdError channel)
|
||||
// We need to be prepared for both here.
|
||||
event, open := <-watching.ResultChan()
|
||||
if open && event.Type != watch.Error {
|
||||
t.Errorf("Unexpected event from stopped watcher: %#v", event)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchEtcdState(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion)
|
||||
key := "/somekey/foo"
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
defer server.Terminate(t)
|
||||
|
||||
h := newEtcdHelper(server.Client, scheme, codec, etcdtest.PathPrefix())
|
||||
watching, err := h.Watch(context.TODO(), key, "0", storage.Everything)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
defer watching.Stop()
|
||||
|
||||
pod := &example.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
}
|
||||
|
||||
err = h.Create(context.TODO(), key, pod, pod, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
event := <-watching.ResultChan()
|
||||
if event.Type != watch.Added {
|
||||
t.Errorf("Unexpected event %#v", event)
|
||||
}
|
||||
|
||||
pod.ResourceVersion = ""
|
||||
pod.Status = example.PodStatus{
|
||||
Phase: example.PodPhase("Running"),
|
||||
}
|
||||
|
||||
// CAS the previous value
|
||||
updateFn := func(input runtime.Object, res storage.ResponseMeta) (runtime.Object, *uint64, error) {
|
||||
return pod.DeepCopyObject(), nil, nil
|
||||
}
|
||||
err = h.GuaranteedUpdate(context.TODO(), key, &example.Pod{}, false, nil, updateFn)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
event = <-watching.ResultChan()
|
||||
if event.Type != watch.Modified {
|
||||
t.Errorf("Unexpected event %#v", event)
|
||||
}
|
||||
|
||||
if e, a := pod, event.Object; !apiequality.Semantic.DeepDerivative(e, a) {
|
||||
t.Errorf("Unexpected error: expected %#v, got %#v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchFromZeroIndex(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion)
|
||||
pod := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
|
||||
|
||||
key := "/somekey/foo"
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
defer server.Terminate(t)
|
||||
|
||||
h := newEtcdHelper(server.Client, scheme, codec, etcdtest.PathPrefix())
|
||||
|
||||
// set before the watch and verify events
|
||||
err := h.Create(context.TODO(), key, pod, pod, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
pod.ResourceVersion = ""
|
||||
|
||||
watching, err := h.Watch(context.TODO(), key, "0", storage.Everything)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// The create trigger ADDED event when watching from 0
|
||||
event := <-watching.ResultChan()
|
||||
watching.Stop()
|
||||
if event.Type != watch.Added {
|
||||
t.Errorf("Unexpected event %#v", event)
|
||||
}
|
||||
|
||||
// check for concatenation on watch event with CAS
|
||||
updateFn := func(input runtime.Object, res storage.ResponseMeta) (runtime.Object, *uint64, error) {
|
||||
pod := input.(*example.Pod)
|
||||
pod.Name = "bar"
|
||||
return pod, nil, nil
|
||||
}
|
||||
err = h.GuaranteedUpdate(context.TODO(), key, &example.Pod{}, false, nil, updateFn)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
watching, err = h.Watch(context.TODO(), key, "0", storage.Everything)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
defer watching.Stop()
|
||||
|
||||
// because we watch from 0, first event that we receive will always be ADDED
|
||||
event = <-watching.ResultChan()
|
||||
if event.Type != watch.Added {
|
||||
t.Errorf("Unexpected event %#v", event)
|
||||
}
|
||||
|
||||
pod.Name = "baz"
|
||||
updateFn = func(input runtime.Object, res storage.ResponseMeta) (runtime.Object, *uint64, error) {
|
||||
pod := input.(*example.Pod)
|
||||
pod.Name = "baz"
|
||||
return pod, nil, nil
|
||||
}
|
||||
err = h.GuaranteedUpdate(context.TODO(), key, &example.Pod{}, false, nil, updateFn)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
event = <-watching.ResultChan()
|
||||
if event.Type != watch.Modified {
|
||||
t.Errorf("Unexpected event %#v", event)
|
||||
}
|
||||
|
||||
if e, a := pod, event.Object; a == nil || !apiequality.Semantic.DeepDerivative(e, a) {
|
||||
t.Errorf("Unexpected error: expected %#v, got %#v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchListFromZeroIndex(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion)
|
||||
prefix := "/some/key"
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
defer server.Terminate(t)
|
||||
h := newEtcdHelper(server.Client, scheme, codec, prefix)
|
||||
|
||||
watching, err := h.WatchList(context.TODO(), "/", "0", storage.Everything)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
defer watching.Stop()
|
||||
|
||||
// creates foo which should trigger the WatchList for "/"
|
||||
pod := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
|
||||
err = h.Create(context.TODO(), pod.Name, pod, pod, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
event, _ := <-watching.ResultChan()
|
||||
if event.Type != watch.Added {
|
||||
t.Errorf("Unexpected event %#v", event)
|
||||
}
|
||||
|
||||
if e, a := pod, event.Object; !apiequality.Semantic.DeepDerivative(e, a) {
|
||||
t.Errorf("Unexpected error: expected %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchListIgnoresRootKey(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
codec := apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion)
|
||||
pod := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
|
||||
key := "/some/key"
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
defer server.Terminate(t)
|
||||
h := newEtcdHelper(server.Client, scheme, codec, key)
|
||||
|
||||
watching, err := h.WatchList(context.TODO(), key, "0", storage.Everything)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
defer watching.Stop()
|
||||
|
||||
// creates key/foo which should trigger the WatchList for "key"
|
||||
err = h.Create(context.TODO(), key, pod, pod, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// force context switch to ensure watches would catch and notify.
|
||||
rt.Gosched()
|
||||
|
||||
select {
|
||||
case event, _ := <-watching.ResultChan():
|
||||
t.Fatalf("Unexpected event: %#v", event)
|
||||
default:
|
||||
// fall through, expected behavior
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchPurposefulShutdown(t *testing.T) {
|
||||
scheme, codecs := testScheme(t)
|
||||
codec := codecs.LegacyCodec(schema.GroupVersion{Version: "v1"})
|
||||
server := etcdtesting.NewEtcdTestClientServer(t)
|
||||
defer server.Terminate(t)
|
||||
key := "/some/key"
|
||||
h := newEtcdHelper(server.Client, scheme, codec, etcdtest.PathPrefix())
|
||||
|
||||
// Test purposeful shutdown
|
||||
watching, err := h.Watch(context.TODO(), key, "0", storage.Everything)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
watching.Stop()
|
||||
rt.Gosched()
|
||||
|
||||
// There is a race in etcdWatcher so that after calling Stop() one of
|
||||
// two things can happen:
|
||||
// - ResultChan() may be closed (triggered by closing userStop channel)
|
||||
// - an Error "context cancelled" may be emitted (triggered by cancelling request
|
||||
// to etcd and putting that error to etcdError channel)
|
||||
// We need to be prepared for both here.
|
||||
event, open := <-watching.ResultChan()
|
||||
if open && event.Type != watch.Error {
|
||||
t.Errorf("Unexpected event from stopped watcher: %#v", event)
|
||||
}
|
||||
}
|
||||
27
vendor/k8s.io/apiserver/pkg/storage/etcd/etcdtest/BUILD
generated
vendored
Normal file
27
vendor/k8s.io/apiserver/pkg/storage/etcd/etcdtest/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"etcdtest.go",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
17
vendor/k8s.io/apiserver/pkg/storage/etcd/etcdtest/doc.go
generated
vendored
Normal file
17
vendor/k8s.io/apiserver/pkg/storage/etcd/etcdtest/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
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 etcdtest // import "k8s.io/apiserver/pkg/storage/etcd/etcdtest"
|
||||
39
vendor/k8s.io/apiserver/pkg/storage/etcd/etcdtest/etcdtest.go
generated
vendored
Normal file
39
vendor/k8s.io/apiserver/pkg/storage/etcd/etcdtest/etcdtest.go
generated
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
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 etcdtest
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
// Cache size to use for tests.
|
||||
const DeserializationCacheSize = 150
|
||||
|
||||
// Returns the prefix set via the ETCD_PREFIX environment variable (if any).
|
||||
func PathPrefix() string {
|
||||
pref := os.Getenv("ETCD_PREFIX")
|
||||
if pref == "" {
|
||||
pref = "registry"
|
||||
}
|
||||
return path.Join("/", pref)
|
||||
}
|
||||
|
||||
// Adds the ETCD_PREFIX to the provided key
|
||||
func AddPrefix(in string) string {
|
||||
return path.Join(PathPrefix(), in)
|
||||
}
|
||||
25
vendor/k8s.io/apiserver/pkg/storage/etcd/metrics/BUILD
generated
vendored
Normal file
25
vendor/k8s.io/apiserver/pkg/storage/etcd/metrics/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["metrics.go"],
|
||||
deps = ["//vendor/github.com/prometheus/client_golang/prometheus:go_default_library"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
110
vendor/k8s.io/apiserver/pkg/storage/etcd/metrics/metrics.go
generated
vendored
Normal file
110
vendor/k8s.io/apiserver/pkg/storage/etcd/metrics/metrics.go
generated
vendored
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
Copyright 2015 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 metrics
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
cacheHitCounterOpts = prometheus.CounterOpts{
|
||||
Name: "etcd_helper_cache_hit_count",
|
||||
Help: "Counter of etcd helper cache hits.",
|
||||
}
|
||||
cacheHitCounter = prometheus.NewCounter(cacheHitCounterOpts)
|
||||
cacheMissCounterOpts = prometheus.CounterOpts{
|
||||
Name: "etcd_helper_cache_miss_count",
|
||||
Help: "Counter of etcd helper cache miss.",
|
||||
}
|
||||
cacheMissCounter = prometheus.NewCounter(cacheMissCounterOpts)
|
||||
cacheEntryCounterOpts = prometheus.CounterOpts{
|
||||
Name: "etcd_helper_cache_entry_count",
|
||||
Help: "Counter of etcd helper cache entries. This can be different from etcd_helper_cache_miss_count " +
|
||||
"because two concurrent threads can miss the cache and generate the same entry twice.",
|
||||
}
|
||||
cacheEntryCounter = prometheus.NewCounter(cacheEntryCounterOpts)
|
||||
cacheGetLatency = prometheus.NewSummary(
|
||||
prometheus.SummaryOpts{
|
||||
Name: "etcd_request_cache_get_latencies_summary",
|
||||
Help: "Latency in microseconds of getting an object from etcd cache",
|
||||
},
|
||||
)
|
||||
cacheAddLatency = prometheus.NewSummary(
|
||||
prometheus.SummaryOpts{
|
||||
Name: "etcd_request_cache_add_latencies_summary",
|
||||
Help: "Latency in microseconds of adding an object to etcd cache",
|
||||
},
|
||||
)
|
||||
etcdRequestLatenciesSummary = prometheus.NewSummaryVec(
|
||||
prometheus.SummaryOpts{
|
||||
Name: "etcd_request_latencies_summary",
|
||||
Help: "Etcd request latency summary in microseconds for each operation and object type.",
|
||||
},
|
||||
[]string{"operation", "type"},
|
||||
)
|
||||
)
|
||||
|
||||
var registerMetrics sync.Once
|
||||
|
||||
// Register all metrics.
|
||||
func Register() {
|
||||
// Register the metrics.
|
||||
registerMetrics.Do(func() {
|
||||
prometheus.MustRegister(cacheHitCounter)
|
||||
prometheus.MustRegister(cacheMissCounter)
|
||||
prometheus.MustRegister(cacheEntryCounter)
|
||||
prometheus.MustRegister(cacheAddLatency)
|
||||
prometheus.MustRegister(cacheGetLatency)
|
||||
prometheus.MustRegister(etcdRequestLatenciesSummary)
|
||||
})
|
||||
}
|
||||
|
||||
func RecordEtcdRequestLatency(verb, resource string, startTime time.Time) {
|
||||
etcdRequestLatenciesSummary.WithLabelValues(verb, resource).Observe(float64(time.Since(startTime) / time.Microsecond))
|
||||
}
|
||||
|
||||
func ObserveGetCache(startTime time.Time) {
|
||||
cacheGetLatency.Observe(float64(time.Since(startTime) / time.Microsecond))
|
||||
}
|
||||
|
||||
func ObserveAddCache(startTime time.Time) {
|
||||
cacheAddLatency.Observe(float64(time.Since(startTime) / time.Microsecond))
|
||||
}
|
||||
|
||||
func ObserveCacheHit() {
|
||||
cacheHitCounter.Inc()
|
||||
}
|
||||
|
||||
func ObserveCacheMiss() {
|
||||
cacheMissCounter.Inc()
|
||||
}
|
||||
|
||||
func ObserveNewEntry() {
|
||||
cacheEntryCounter.Inc()
|
||||
}
|
||||
|
||||
func Reset() {
|
||||
cacheHitCounter = prometheus.NewCounter(cacheHitCounterOpts)
|
||||
cacheMissCounter = prometheus.NewCounter(cacheMissCounterOpts)
|
||||
cacheEntryCounter = prometheus.NewCounter(cacheEntryCounterOpts)
|
||||
// TODO: Reset cacheAddLatency.
|
||||
// TODO: Reset cacheGetLatency.
|
||||
etcdRequestLatenciesSummary.Reset()
|
||||
}
|
||||
44
vendor/k8s.io/apiserver/pkg/storage/etcd/testing/BUILD
generated
vendored
Normal file
44
vendor/k8s.io/apiserver/pkg/storage/etcd/testing/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["utils.go"],
|
||||
deps = [
|
||||
"//vendor/github.com/coreos/etcd/client:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/clientv3:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/etcdserver:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/etcdserver/api/v2http:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/integration:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/pkg/testutil:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/pkg/transport:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/pkg/types:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/golang.org/x/net/context:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/storage/etcd/etcdtest:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/storage/etcd/testing/testingcert:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage/etcd/testing/testingcert:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
24
vendor/k8s.io/apiserver/pkg/storage/etcd/testing/testingcert/BUILD
generated
vendored
Normal file
24
vendor/k8s.io/apiserver/pkg/storage/etcd/testing/testingcert/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["certificates.go"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
113
vendor/k8s.io/apiserver/pkg/storage/etcd/testing/testingcert/certificates.go
generated
vendored
Normal file
113
vendor/k8s.io/apiserver/pkg/storage/etcd/testing/testingcert/certificates.go
generated
vendored
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
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 testingcert
|
||||
|
||||
// You can use cfssl tool to generate certificates, please refer
|
||||
// https://github.com/coreos/etcd/tree/master/hack/tls-setup for more details.
|
||||
//
|
||||
// ca-config.json:
|
||||
// expiry was changed from 1 year to 100 years (876000h)
|
||||
// ca-csr.json:
|
||||
// ca expiry was set to 100 years (876000h) ("ca":{"expiry":"876000h"})
|
||||
// key was changed from ecdsa,384 to rsa,2048
|
||||
// req-csr.json:
|
||||
// key was changed from ecdsa,384 to rsa,2048
|
||||
// hosts were changed to "localhost","127.0.0.1"
|
||||
const CAFileContent = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEUDCCAzigAwIBAgIUKfV5+qwlw3JneAPdJS7JCO8xIlYwDQYJKoZIhvcNAQEL
|
||||
BQAwgawxCzAJBgNVBAYTAlVTMSowKAYDVQQKEyFIb25lc3QgQWNobWVkJ3MgVXNl
|
||||
ZCBDZXJ0aWZpY2F0ZXMxKTAnBgNVBAsTIEhhc3RpbHktR2VuZXJhdGVkIFZhbHVl
|
||||
cyBEaXZpc29uMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRMwEQYDVQQIEwpDYWxp
|
||||
Zm9ybmlhMRkwFwYDVQQDExBBdXRvZ2VuZXJhdGVkIENBMCAXDTE2MDMxMjIzMTQw
|
||||
MFoYDzIxMTYwMjE3MjMxNDAwWjCBrDELMAkGA1UEBhMCVVMxKjAoBgNVBAoTIUhv
|
||||
bmVzdCBBY2htZWQncyBVc2VkIENlcnRpZmljYXRlczEpMCcGA1UECxMgSGFzdGls
|
||||
eS1HZW5lcmF0ZWQgVmFsdWVzIERpdmlzb24xFjAUBgNVBAcTDVNhbiBGcmFuY2lz
|
||||
Y28xEzARBgNVBAgTCkNhbGlmb3JuaWExGTAXBgNVBAMTEEF1dG9nZW5lcmF0ZWQg
|
||||
Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDP+acpr1USrObZFu+6
|
||||
v+Bk6rYw+sWynP373cNUUiHfnZ3D7f9yJsDscV0Mo4R8DddqkxawrA5fK2Fm2Z9G
|
||||
vvY5par4/JbwRIEkXmeM4e52Mqv0Yuoz62O+0jQvRawnCCJMcKuo+ijHMjmm0AF1
|
||||
JdhTpTgvUwEP9WtY9JVTkfMCnDqZiqOU5D+d4YWUtkKqgQNvbZRs6wGubhMCZe8X
|
||||
m+3bK8YAsWWtoFgr7plxXk4D8MLh+PqJ3oJjfxfW5A9dHbnSEmdZ3vrYwrKgyfNf
|
||||
bvHE5qQmiSZUbUaCw3mKfaEMCNesPT46nBHxhAWc5aiL1tOXzvV5Uze7A7huPoI9
|
||||
a3etAgMBAAGjZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEC
|
||||
MB0GA1UdDgQWBBQYc0xXQ6VNjFvIOqWfXorxx9rKRzAfBgNVHSMEGDAWgBQYc0xX
|
||||
Q6VNjFvIOqWfXorxx9rKRzANBgkqhkiG9w0BAQsFAAOCAQEAaKyHDWYVjEyEKTXJ
|
||||
qS9r46ehL5FZlWD2ZytBP8aHE307l9AfQ+DFWldCNaqMXLZozsresVaSzSOI6UUD
|
||||
lCIQLDpPyxbpR320u8mC08+lhhwR/YRkrEqKHk56Wl4OaqoyWmguqYU9p0DiQeTU
|
||||
sZsxOwG7cyEEvvs+XmZ/vBLBOr59xyjwn4seQqzwZj3VYeiKLw40iQt1yT442rcP
|
||||
CfdlE9wTEONvWT+kBGMt0JlalXH3jFvlfcGQdDfRmDeTJtA+uIbvJhwJuGCNHHAc
|
||||
xqC+4mAGBPN/dMPXpjayHD5dOXIKLfrNpqse6jImYlY9zduvwIHRDK/zvqTyPlNZ
|
||||
uR84Nw==
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
const CertFileContent = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIELzCCAxegAwIBAgIUcjkJA3cmHeoBQggaKZmfKebFL9cwDQYJKoZIhvcNAQEL
|
||||
BQAwgawxCzAJBgNVBAYTAlVTMSowKAYDVQQKEyFIb25lc3QgQWNobWVkJ3MgVXNl
|
||||
ZCBDZXJ0aWZpY2F0ZXMxKTAnBgNVBAsTIEhhc3RpbHktR2VuZXJhdGVkIFZhbHVl
|
||||
cyBEaXZpc29uMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRMwEQYDVQQIEwpDYWxp
|
||||
Zm9ybmlhMRkwFwYDVQQDExBBdXRvZ2VuZXJhdGVkIENBMCAXDTE2MDMxMjIzMTQw
|
||||
MFoYDzIxMTYwMjE3MjMxNDAwWjBVMRYwFAYDVQQKEw1hdXRvZ2VuZXJhdGVkMRUw
|
||||
EwYDVQQLEwxldGNkIGNsdXN0ZXIxFTATBgNVBAcTDHRoZSBpbnRlcm5ldDENMAsG
|
||||
A1UEAxMEZXRjZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOiW5A65
|
||||
hWGbnwceoZHM0+OexU4cPF/FpP+7BOK5i7ymSWAqfKfNuio2TB1lAErC1oX7bgTX
|
||||
ieP10uz3FYWQNrlDn0I4KSA888rFPtx8GwoxH/52fGlE80BUV9PNeOVP+mYza0ih
|
||||
oFj2+PhXVL/JZbx9P/2RLSNbEnq+OPk8AN82SkNtpFzanwtpb3f+kt73878KNoQu
|
||||
xYZaCF1sK45Kn7mjKSDu/b3xUbTrNwnyVAGOdLzI7CCWOu+ECoZYAH4ZNHHakbyY
|
||||
eWQ7U9leocEOPlqxsQAKodaCYjuAaOFIcz8/W81q+3qNw/6GbZ4znjRKQ3OtIPZ4
|
||||
JH1iNofCudWDp+0CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYw
|
||||
FAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFMJE
|
||||
43qLCWhyZAE/wxNneSJw7aUVMB8GA1UdIwQYMBaAFBhzTFdDpU2MW8g6pZ9eivHH
|
||||
2spHMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsFAAOC
|
||||
AQEAuELC8tbmpyKlA4HLSDHOUquypNyiE6ftBIifJtp8bvBd+jiv4Pr8oVGxHoqq
|
||||
48X7lamvDirLV5gmK0CxO+EXkIUHhULzPyYPynqsR7KZlk1PWghqsF65nwqcjS3b
|
||||
tykLttD1AUDIozYvujVYBKXGxb6jcGM1rBF1XtslciFZ5qQnj6dTUujo9/xBA2ql
|
||||
kOKiVXBNU8KFzq4c20RzHFLfWkbc30Q4XG4dTDVBeGupnFQRkZ0y2dSSU82QcLA/
|
||||
HgAyQSO7+csN13r84zbmDuRpUgo6eTXzJ+77G19KDkEL7XEtlw2jB2L6/o+3RGtw
|
||||
JLOpEsgi7hsvOYCuTA3Krw52Mw==
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
const KeyFileContent = `
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA6JbkDrmFYZufBx6hkczT457FThw8X8Wk/7sE4rmLvKZJYCp8
|
||||
p826KjZMHWUASsLWhftuBNeJ4/XS7PcVhZA2uUOfQjgpIDzzysU+3HwbCjEf/nZ8
|
||||
aUTzQFRX08145U/6ZjNrSKGgWPb4+FdUv8llvH0//ZEtI1sSer44+TwA3zZKQ22k
|
||||
XNqfC2lvd/6S3vfzvwo2hC7FhloIXWwrjkqfuaMpIO79vfFRtOs3CfJUAY50vMjs
|
||||
IJY674QKhlgAfhk0cdqRvJh5ZDtT2V6hwQ4+WrGxAAqh1oJiO4Bo4UhzPz9bzWr7
|
||||
eo3D/oZtnjOeNEpDc60g9ngkfWI2h8K51YOn7QIDAQABAoIBAQCj88Fc08++x0kp
|
||||
ZqEzunPebsvcTLEOPa8aiUVfYLWszHbKsAhg7Pb+zHmI+upiyMcZeOvLw/eyVlVR
|
||||
rrZgCRFaNN2texMaY3zigXnXSDBzVb+cyv7V4cGqpgmnBp7i3ia/Jh3I/A2gyK8l
|
||||
t8HI03nAjXWvE0gDNS5okXBt16sxq6ZWyzHHVbN3UYtCDxnyh2Ibck4b+K8I8Bn1
|
||||
mwMsSqPXJS1UQ3U5UqcaMs7WOEGx+xmaPJTWm5Lb//BkakGuBTQj+7wotyXQYG5U
|
||||
uZdPPcFRk6cqgjzUeKVUtGkdmfgHSTdIwZowkKibB4rdrudsRnSwfeB+83Jp9JwG
|
||||
JPrGvsbNAoGBAPULIO+vVBZXVpUEAhvNSXtmOi/hAbQhOuix8iwHbJ5EbrWaDn4B
|
||||
Reb2cw/fZGgGG4jtAOXdiY8R1XGGP8+RPZ5El10ZWnNrKQfpZ27gK/5yeq5dfGBG
|
||||
4JLUpcrT180FJo00rgiQYJnHCk1fWrnzXNV6K08ZZHGr6yv4S/jbq/7vAoGBAPL9
|
||||
NTN/UWXWFlSHVcb2dFHcvIiPwRj9KwhuMu90b/CilBbSJ1av13xtf2ar5zkrEtWH
|
||||
CB3q3wBaklQP9MfOqEWGZeOUcd9AbYWtxHjHmP5fJA9RjErjlTtqGkusNtZJbchU
|
||||
UWfT/Tl9pREpCvJ/8iawc1hx7sHHKzYwnDnMaQbjAoGAfJdd9cBltr5NjZLuJ4in
|
||||
dhCyQSncnePPegUQJwbXWVleGQPtnm+zRQ3Fzyo8eQ+x7Frk+/s6N/5PUlt6EmW8
|
||||
uL4TYAjGDq1LvXQVXTCp7cPzULjDxogDI2Tvr0MrFFksEtvYKQ6Pr2CeglybWrS8
|
||||
XOazIpK8mXdaKY8jwbKfrw0CgYAFnfrb3OaZzxAnFhXSiqH3vn2RPpl9JWUYRcvh
|
||||
ozRvQKLhwCvuohP+KV3XlsO6m5dM3lk+r85F6NIXJWNINyvGp6u1ThovygJ+I502
|
||||
GY8c2kAwJndyx74MaJCBDVMbMwlZpzFWkBz7dj8ZnXRGVNTZNh0Ef2XAjwUdtJP3
|
||||
9hS7dwKBgQDCzq0RIxFyy3F5baGHWLVICxmhNExQ2+Vebh+DvsPKtnz6OrWdRbGX
|
||||
wgGVLrn53s6eCblnXLtKr/Li+t7fS8IkQkvu5guOvI9VeVUmZhFET3GVmUxu+JTb
|
||||
iQY4uBgaf8Fgay4dkOfjvlOpFDR4E7UbJpg8/cFKTrpwgOiUVyFVdQ==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`
|
||||
328
vendor/k8s.io/apiserver/pkg/storage/etcd/testing/utils.go
generated
vendored
Normal file
328
vendor/k8s.io/apiserver/pkg/storage/etcd/testing/utils.go
generated
vendored
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package testing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/storage/etcd/etcdtest"
|
||||
"k8s.io/apiserver/pkg/storage/etcd/testing/testingcert"
|
||||
"k8s.io/apiserver/pkg/storage/storagebackend"
|
||||
|
||||
etcd "github.com/coreos/etcd/client"
|
||||
"github.com/coreos/etcd/clientv3"
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/api/v2http"
|
||||
"github.com/coreos/etcd/integration"
|
||||
"github.com/coreos/etcd/pkg/testutil"
|
||||
"github.com/coreos/etcd/pkg/transport"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/golang/glog"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// EtcdTestServer encapsulates the datastructures needed to start local instance for testing
|
||||
type EtcdTestServer struct {
|
||||
// The following are lumped etcd2 test server params
|
||||
// TODO: Deprecate in a post 1.5 release
|
||||
etcdserver.ServerConfig
|
||||
PeerListeners, ClientListeners []net.Listener
|
||||
Client etcd.Client
|
||||
|
||||
CertificatesDir string
|
||||
CertFile string
|
||||
KeyFile string
|
||||
CAFile string
|
||||
|
||||
raftHandler http.Handler
|
||||
s *etcdserver.EtcdServer
|
||||
hss []*httptest.Server
|
||||
|
||||
// The following are lumped etcd3 test server params
|
||||
v3Cluster *integration.ClusterV3
|
||||
V3Client *clientv3.Client
|
||||
}
|
||||
|
||||
// newLocalListener opens a port localhost using any port
|
||||
func newLocalListener(t *testing.T) net.Listener {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// newSecuredLocalListener opens a port localhost using any port
|
||||
// with SSL enable
|
||||
func newSecuredLocalListener(t *testing.T, certFile, keyFile, caFile string) net.Listener {
|
||||
var l net.Listener
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tlsInfo := transport.TLSInfo{
|
||||
CertFile: certFile,
|
||||
KeyFile: keyFile,
|
||||
CAFile: caFile,
|
||||
}
|
||||
tlscfg, err := tlsInfo.ServerConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected serverConfig error: %v", err)
|
||||
}
|
||||
l, err = transport.NewKeepAliveListener(l, "https", tlscfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func newHttpTransport(t *testing.T, certFile, keyFile, caFile string) etcd.CancelableTransport {
|
||||
tlsInfo := transport.TLSInfo{
|
||||
CertFile: certFile,
|
||||
KeyFile: keyFile,
|
||||
CAFile: caFile,
|
||||
}
|
||||
tr, err := transport.NewTransport(tlsInfo, time.Second)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
// configureTestCluster will set the params to start an etcd server
|
||||
func configureTestCluster(t *testing.T, name string, https bool) *EtcdTestServer {
|
||||
var err error
|
||||
m := &EtcdTestServer{}
|
||||
|
||||
pln := newLocalListener(t)
|
||||
m.PeerListeners = []net.Listener{pln}
|
||||
m.PeerURLs, err = types.NewURLs([]string{"http://" + pln.Addr().String()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Allow test launches to control where etcd data goes, for space or performance reasons
|
||||
baseDir := os.Getenv("TEST_ETCD_DIR")
|
||||
if len(baseDir) == 0 {
|
||||
baseDir = os.TempDir()
|
||||
}
|
||||
|
||||
if https {
|
||||
m.CertificatesDir, err = ioutil.TempDir(baseDir, "etcd_certificates")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m.CertFile = path.Join(m.CertificatesDir, "etcdcert.pem")
|
||||
if err = ioutil.WriteFile(m.CertFile, []byte(testingcert.CertFileContent), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m.KeyFile = path.Join(m.CertificatesDir, "etcdkey.pem")
|
||||
if err = ioutil.WriteFile(m.KeyFile, []byte(testingcert.KeyFileContent), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m.CAFile = path.Join(m.CertificatesDir, "ca.pem")
|
||||
if err = ioutil.WriteFile(m.CAFile, []byte(testingcert.CAFileContent), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cln := newSecuredLocalListener(t, m.CertFile, m.KeyFile, m.CAFile)
|
||||
m.ClientListeners = []net.Listener{cln}
|
||||
m.ClientURLs, err = types.NewURLs([]string{"https://" + cln.Addr().String()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
cln := newLocalListener(t)
|
||||
m.ClientListeners = []net.Listener{cln}
|
||||
m.ClientURLs, err = types.NewURLs([]string{"http://" + cln.Addr().String()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
m.Name = name
|
||||
m.DataDir, err = ioutil.TempDir(baseDir, "etcd")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
clusterStr := fmt.Sprintf("%s=http://%s", name, pln.Addr().String())
|
||||
m.InitialPeerURLsMap, err = types.NewURLsMap(clusterStr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m.InitialClusterToken = "TestEtcd"
|
||||
m.NewCluster = true
|
||||
m.ForceNewCluster = false
|
||||
m.ElectionTicks = 10
|
||||
m.TickMs = uint(10)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// launch will attempt to start the etcd server
|
||||
func (m *EtcdTestServer) launch(t *testing.T) error {
|
||||
var err error
|
||||
if m.s, err = etcdserver.NewServer(&m.ServerConfig); err != nil {
|
||||
return fmt.Errorf("failed to initialize the etcd server: %v", err)
|
||||
}
|
||||
m.s.SyncTicker = time.Tick(500 * time.Millisecond)
|
||||
m.s.Start()
|
||||
m.raftHandler = &testutil.PauseableHandler{Next: v2http.NewPeerHandler(m.s)}
|
||||
for _, ln := range m.PeerListeners {
|
||||
hs := &httptest.Server{
|
||||
Listener: ln,
|
||||
Config: &http.Server{Handler: m.raftHandler},
|
||||
}
|
||||
hs.Start()
|
||||
m.hss = append(m.hss, hs)
|
||||
}
|
||||
for _, ln := range m.ClientListeners {
|
||||
hs := &httptest.Server{
|
||||
Listener: ln,
|
||||
Config: &http.Server{Handler: v2http.NewClientHandler(m.s, m.ServerConfig.ReqTimeout())},
|
||||
}
|
||||
hs.Start()
|
||||
m.hss = append(m.hss, hs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// waitForEtcd wait until etcd is propagated correctly
|
||||
func (m *EtcdTestServer) waitUntilUp() error {
|
||||
membersAPI := etcd.NewMembersAPI(m.Client)
|
||||
for start := time.Now(); time.Since(start) < wait.ForeverTestTimeout; time.Sleep(10 * time.Millisecond) {
|
||||
members, err := membersAPI.List(context.TODO())
|
||||
if err != nil {
|
||||
glog.Errorf("Error when getting etcd cluster members")
|
||||
continue
|
||||
}
|
||||
if len(members) == 1 && len(members[0].ClientURLs) > 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("timeout on waiting for etcd cluster")
|
||||
}
|
||||
|
||||
// Terminate will shutdown the running etcd server
|
||||
func (m *EtcdTestServer) Terminate(t *testing.T) {
|
||||
if m.v3Cluster != nil {
|
||||
m.v3Cluster.Terminate(t)
|
||||
} else {
|
||||
m.Client = nil
|
||||
m.s.Stop()
|
||||
// TODO: This is a pretty ugly hack to workaround races during closing
|
||||
// in-memory etcd server in unit tests - see #18928 for more details.
|
||||
// We should get rid of it as soon as we have a proper fix - etcd clients
|
||||
// have overwritten transport counting opened connections (probably by
|
||||
// overwriting Dial function) and termination function waiting for all
|
||||
// connections to be closed and stopping accepting new ones.
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
for _, hs := range m.hss {
|
||||
hs.CloseClientConnections()
|
||||
hs.Close()
|
||||
}
|
||||
if err := os.RemoveAll(m.ServerConfig.DataDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(m.CertificatesDir) > 0 {
|
||||
if err := os.RemoveAll(m.CertificatesDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewEtcdTestClientServer DEPRECATED creates a new client and server for testing
|
||||
func NewEtcdTestClientServer(t *testing.T) *EtcdTestServer {
|
||||
server := configureTestCluster(t, "foo", true)
|
||||
err := server.launch(t)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start etcd server error=%v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := etcd.Config{
|
||||
Endpoints: server.ClientURLs.StringSlice(),
|
||||
Transport: newHttpTransport(t, server.CertFile, server.KeyFile, server.CAFile),
|
||||
}
|
||||
server.Client, err = etcd.New(cfg)
|
||||
if err != nil {
|
||||
server.Terminate(t)
|
||||
t.Fatalf("Unexpected error in NewEtcdTestClientServer (%v)", err)
|
||||
return nil
|
||||
}
|
||||
if err := server.waitUntilUp(); err != nil {
|
||||
server.Terminate(t)
|
||||
t.Fatalf("Unexpected error in waitUntilUp (%v)", err)
|
||||
return nil
|
||||
}
|
||||
return server
|
||||
}
|
||||
|
||||
// NewUnsecuredEtcdTestClientServer DEPRECATED creates a new client and server for testing
|
||||
func NewUnsecuredEtcdTestClientServer(t *testing.T) *EtcdTestServer {
|
||||
server := configureTestCluster(t, "foo", false)
|
||||
err := server.launch(t)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start etcd server error=%v", err)
|
||||
return nil
|
||||
}
|
||||
cfg := etcd.Config{
|
||||
Endpoints: server.ClientURLs.StringSlice(),
|
||||
Transport: newHttpTransport(t, server.CertFile, server.KeyFile, server.CAFile),
|
||||
}
|
||||
server.Client, err = etcd.New(cfg)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error in NewUnsecuredEtcdTestClientServer (%v)", err)
|
||||
server.Terminate(t)
|
||||
return nil
|
||||
}
|
||||
if err := server.waitUntilUp(); err != nil {
|
||||
t.Errorf("Unexpected error in waitUntilUp (%v)", err)
|
||||
server.Terminate(t)
|
||||
return nil
|
||||
}
|
||||
return server
|
||||
}
|
||||
|
||||
// NewEtcd3TestClientServer creates a new client and server for testing
|
||||
func NewUnsecuredEtcd3TestClientServer(t *testing.T, scheme *runtime.Scheme) (*EtcdTestServer, *storagebackend.Config) {
|
||||
server := &EtcdTestServer{
|
||||
v3Cluster: integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1}),
|
||||
}
|
||||
server.V3Client = server.v3Cluster.RandClient()
|
||||
config := &storagebackend.Config{
|
||||
Type: "etcd3",
|
||||
Prefix: etcdtest.PathPrefix(),
|
||||
ServerList: server.V3Client.Endpoints(),
|
||||
DeserializationCacheSize: etcdtest.DeserializationCacheSize,
|
||||
Copier: scheme,
|
||||
Paging: true,
|
||||
}
|
||||
return server, config
|
||||
}
|
||||
39
vendor/k8s.io/apiserver/pkg/storage/etcd/util/BUILD
generated
vendored
Normal file
39
vendor/k8s.io/apiserver/pkg/storage/etcd/util/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["etcd_util_test.go"],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//vendor/github.com/coreos/etcd/client:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"etcd_util.go",
|
||||
],
|
||||
deps = ["//vendor/github.com/coreos/etcd/client:go_default_library"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
19
vendor/k8s.io/apiserver/pkg/storage/etcd/util/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/apiserver/pkg/storage/etcd/util/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
Copyright 2015 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 util holds generic etcd-related utility functions that any user of ectd might want to
|
||||
// use, without pulling in kubernetes-specific code.
|
||||
package util // import "k8s.io/apiserver/pkg/storage/etcd/util"
|
||||
99
vendor/k8s.io/apiserver/pkg/storage/etcd/util/etcd_util.go
generated
vendored
Normal file
99
vendor/k8s.io/apiserver/pkg/storage/etcd/util/etcd_util.go
generated
vendored
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
Copyright 2015 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 util
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
etcd "github.com/coreos/etcd/client"
|
||||
)
|
||||
|
||||
// IsEtcdNotFound returns true if and only if err is an etcd not found error.
|
||||
func IsEtcdNotFound(err error) bool {
|
||||
return isEtcdErrorNum(err, etcd.ErrorCodeKeyNotFound)
|
||||
}
|
||||
|
||||
// IsEtcdNodeExist returns true if and only if err is an etcd node already exist error.
|
||||
func IsEtcdNodeExist(err error) bool {
|
||||
return isEtcdErrorNum(err, etcd.ErrorCodeNodeExist)
|
||||
}
|
||||
|
||||
// IsEtcdTestFailed returns true if and only if err is an etcd write conflict.
|
||||
func IsEtcdTestFailed(err error) bool {
|
||||
return isEtcdErrorNum(err, etcd.ErrorCodeTestFailed)
|
||||
}
|
||||
|
||||
// IsEtcdWatchExpired returns true if and only if err indicates the watch has expired.
|
||||
func IsEtcdWatchExpired(err error) bool {
|
||||
// NOTE: This seems weird why it wouldn't be etcd.ErrorCodeWatcherCleared
|
||||
// I'm using the previous matching value
|
||||
return isEtcdErrorNum(err, etcd.ErrorCodeEventIndexCleared)
|
||||
}
|
||||
|
||||
// IsEtcdUnreachable returns true if and only if err indicates the server could not be reached.
|
||||
func IsEtcdUnreachable(err error) bool {
|
||||
// NOTE: The logic has changed previous error code no longer applies
|
||||
return err == etcd.ErrClusterUnavailable
|
||||
}
|
||||
|
||||
// isEtcdErrorNum returns true if and only if err is an etcd error, whose errorCode matches errorCode
|
||||
func isEtcdErrorNum(err error, errorCode int) bool {
|
||||
if err != nil {
|
||||
if etcdError, ok := err.(etcd.Error); ok {
|
||||
return etcdError.Code == errorCode
|
||||
}
|
||||
// NOTE: There are other error types returned
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetEtcdVersion performs a version check against the provided Etcd server,
|
||||
// returning the string response, and error (if any).
|
||||
func GetEtcdVersion(host string) (string, error) {
|
||||
response, err := http.Get(host + "/version")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("unsuccessful response from etcd server %q: %v", host, err)
|
||||
}
|
||||
versionBytes, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(versionBytes), nil
|
||||
}
|
||||
|
||||
type etcdHealth struct {
|
||||
// Note this has to be public so the json library can modify it.
|
||||
Health string `json:"health"`
|
||||
}
|
||||
|
||||
func EtcdHealthCheck(data []byte) error {
|
||||
obj := etcdHealth{}
|
||||
if err := json.Unmarshal(data, &obj); err != nil {
|
||||
return err
|
||||
}
|
||||
if obj.Health != "true" {
|
||||
return fmt.Errorf("Unhealthy status: %s", obj.Health)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
120
vendor/k8s.io/apiserver/pkg/storage/etcd/util/etcd_util_test.go
generated
vendored
Normal file
120
vendor/k8s.io/apiserver/pkg/storage/etcd/util/etcd_util_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
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 util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
etcd "github.com/coreos/etcd/client"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const validEtcdVersion = "etcd 2.0.9"
|
||||
|
||||
func TestIsEtcdNotFound(t *testing.T) {
|
||||
try := func(err error, isNotFound bool) {
|
||||
if IsEtcdNotFound(err) != isNotFound {
|
||||
t.Errorf("Expected %#v to return %v, but it did not", err, isNotFound)
|
||||
}
|
||||
}
|
||||
try(&etcd.Error{Code: 101}, false)
|
||||
try(nil, false)
|
||||
try(fmt.Errorf("some other kind of error"), false)
|
||||
}
|
||||
|
||||
func TestGetEtcdVersion_ValidVersion(t *testing.T) {
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, validEtcdVersion)
|
||||
}))
|
||||
defer testServer.Close()
|
||||
|
||||
var version string
|
||||
var err error
|
||||
if version, err = GetEtcdVersion(testServer.URL); err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
assert.Equal(t, validEtcdVersion, version, "Unexpected version")
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestGetEtcdVersion_ErrorStatus(t *testing.T) {
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
}))
|
||||
defer testServer.Close()
|
||||
|
||||
_, err := GetEtcdVersion(testServer.URL)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestGetEtcdVersion_NotListening(t *testing.T) {
|
||||
portIsOpen := func(port int) bool {
|
||||
conn, err := net.DialTimeout("tcp", "127.0.0.1:"+strconv.Itoa(port), 1*time.Second)
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
port := rand.Intn((1 << 16) - 1)
|
||||
for tried := 0; portIsOpen(port); tried++ {
|
||||
if tried >= 10 {
|
||||
t.Fatal("Couldn't find a closed TCP port to continue testing")
|
||||
}
|
||||
port++
|
||||
}
|
||||
|
||||
_, err := GetEtcdVersion("http://127.0.0.1:" + strconv.Itoa(port))
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestEtcdHealthCheck(t *testing.T) {
|
||||
tests := []struct {
|
||||
data string
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
data: "{\"health\": \"true\"}",
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
data: "{\"health\": \"false\"}",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
data: "invalid json",
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
err := EtcdHealthCheck([]byte(test.data))
|
||||
if err != nil && !test.expectErr {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if err == nil && test.expectErr {
|
||||
t.Error("unexpected non-error")
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue