mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-04 18:00:08 +00:00
Merge pull request #717 from nyaxt/atomicupdate_test
Add test for AtomicUpdate
This commit is contained in:
commit
e1b0000404
@ -17,7 +17,6 @@ limitations under the License.
|
|||||||
package tools
|
package tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
@ -30,11 +29,15 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
EtcdErrorCodeNotFound = 100
|
EtcdErrorCodeNotFound = 100
|
||||||
|
EtcdErrorCodeTestFailed = 101
|
||||||
|
EtcdErrorCodeNodeExist = 105
|
||||||
EtcdErrorCodeValueRequired = 200
|
EtcdErrorCodeValueRequired = 200
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
EtcdErrorNotFound = &etcd.EtcdError{ErrorCode: EtcdErrorCodeNotFound}
|
EtcdErrorNotFound = &etcd.EtcdError{ErrorCode: EtcdErrorCodeNotFound}
|
||||||
|
EtcdErrorTestFailed = &etcd.EtcdError{ErrorCode: EtcdErrorCodeTestFailed}
|
||||||
|
EtcdErrorNodeExist = &etcd.EtcdError{ErrorCode: EtcdErrorCodeNodeExist}
|
||||||
EtcdErrorValueRequired = &etcd.EtcdError{ErrorCode: EtcdErrorCodeValueRequired}
|
EtcdErrorValueRequired = &etcd.EtcdError{ErrorCode: EtcdErrorCodeValueRequired}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -221,7 +224,7 @@ func (h *EtcdHelper) AtomicUpdate(key string, ptrToType interface{}, tryUpdate E
|
|||||||
return h.SetObj(key, ret)
|
return h.SetObj(key, ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(ret)
|
data, err := api.Encode(ret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,16 @@ type fakeEtcdGetSet struct {
|
|||||||
set func(key, value string, ttl uint64) (*etcd.Response, error)
|
set func(key, value string, ttl uint64) (*etcd.Response, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TestResource struct {
|
||||||
|
api.JSONBase `json:",inline" yaml:",inline"`
|
||||||
|
Value int `json:"value" yaml:"value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
api.AddKnownTypes("", TestResource{})
|
||||||
|
api.AddKnownTypes("v1beta1", TestResource{})
|
||||||
|
}
|
||||||
|
|
||||||
func TestIsNotFoundErr(t *testing.T) {
|
func TestIsNotFoundErr(t *testing.T) {
|
||||||
try := func(err error, isNotFound bool) {
|
try := func(err error, isNotFound bool) {
|
||||||
if IsEtcdNotFound(err) != isNotFound {
|
if IsEtcdNotFound(err) != isNotFound {
|
||||||
@ -154,6 +164,61 @@ func TestSetObj(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAtomicUpdate(t *testing.T) {
|
||||||
|
fakeClient := MakeFakeEtcdClient(t)
|
||||||
|
fakeClient.TestIndex = true
|
||||||
|
helper := EtcdHelper{fakeClient}
|
||||||
|
|
||||||
|
// Create a new node.
|
||||||
|
fakeClient.ExpectNotFoundGet("/some/key")
|
||||||
|
obj := TestResource{JSONBase: api.JSONBase{ID: "foo"}, Value: 1}
|
||||||
|
err := helper.AtomicUpdate("/some/key", &TestResource{}, func(in interface{}) (interface{}, error) {
|
||||||
|
return obj, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error %#v", err)
|
||||||
|
}
|
||||||
|
data, err := api.Encode(obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error %#v", err)
|
||||||
|
}
|
||||||
|
expect := string(data)
|
||||||
|
got := fakeClient.Data["/some/key"].R.Node.Value
|
||||||
|
if expect != got {
|
||||||
|
t.Errorf("Wanted %v, got %v", expect, got)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
// Update an existing node.
|
||||||
|
callbackCalled := false
|
||||||
|
objUpdate := &TestResource{JSONBase: api.JSONBase{ID: "foo"}, Value: 2}
|
||||||
|
err = helper.AtomicUpdate("/some/key", &TestResource{}, func(in interface{}) (interface{}, error) {
|
||||||
|
callbackCalled = true
|
||||||
|
|
||||||
|
if in.(*TestResource).Value != 1 {
|
||||||
|
t.Errorf("Callback input was not current set value")
|
||||||
|
}
|
||||||
|
|
||||||
|
return objUpdate, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error %#v", err)
|
||||||
|
}
|
||||||
|
data, err = api.Encode(objUpdate)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error %#v", err)
|
||||||
|
}
|
||||||
|
expect = string(data)
|
||||||
|
got = fakeClient.Data["/some/key"].R.Node.Value
|
||||||
|
if expect != got {
|
||||||
|
t.Errorf("Wanted %v, got %v", expect, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !callbackCalled {
|
||||||
|
t.Errorf("tryUpdate callback should have been called.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestWatchInterpretation_ListAdd(t *testing.T) {
|
func TestWatchInterpretation_ListAdd(t *testing.T) {
|
||||||
called := false
|
called := false
|
||||||
w := newEtcdWatcher(true, func(interface{}) bool {
|
w := newEtcdWatcher(true, func(interface{}) bool {
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package tools
|
package tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/coreos/go-etcd/etcd"
|
"github.com/coreos/go-etcd/etcd"
|
||||||
@ -38,9 +39,12 @@ type FakeEtcdClient struct {
|
|||||||
|
|
||||||
Data map[string]EtcdResponseWithError
|
Data map[string]EtcdResponseWithError
|
||||||
DeletedKeys []string
|
DeletedKeys []string
|
||||||
|
expectNotFoundGetSet map[string]struct{}
|
||||||
Err error
|
Err error
|
||||||
t TestLogger
|
t TestLogger
|
||||||
Ix int
|
Ix int
|
||||||
|
TestIndex bool
|
||||||
|
ChangeIndex uint64
|
||||||
|
|
||||||
// Will become valid after Watch is called; tester may write to it. Tester may
|
// Will become valid after Watch is called; tester may write to it. Tester may
|
||||||
// also read from it to verify that it's closed after injecting an error.
|
// also read from it to verify that it's closed after injecting an error.
|
||||||
@ -53,6 +57,7 @@ type FakeEtcdClient struct {
|
|||||||
func MakeFakeEtcdClient(t TestLogger) *FakeEtcdClient {
|
func MakeFakeEtcdClient(t TestLogger) *FakeEtcdClient {
|
||||||
ret := &FakeEtcdClient{
|
ret := &FakeEtcdClient{
|
||||||
t: t,
|
t: t,
|
||||||
|
expectNotFoundGetSet: map[string]struct{}{},
|
||||||
Data: map[string]EtcdResponseWithError{},
|
Data: map[string]EtcdResponseWithError{},
|
||||||
}
|
}
|
||||||
// There are three publicly accessible channels in FakeEtcdClient:
|
// There are three publicly accessible channels in FakeEtcdClient:
|
||||||
@ -70,6 +75,19 @@ func MakeFakeEtcdClient(t TestLogger) *FakeEtcdClient {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FakeEtcdClient) ExpectNotFoundGet(key string) {
|
||||||
|
f.expectNotFoundGetSet[key] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeEtcdClient) generateIndex() uint64 {
|
||||||
|
if !f.TestIndex {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
f.ChangeIndex++
|
||||||
|
return f.ChangeIndex
|
||||||
|
}
|
||||||
|
|
||||||
func (f *FakeEtcdClient) AddChild(key, data string, ttl uint64) (*etcd.Response, error) {
|
func (f *FakeEtcdClient) AddChild(key, data string, ttl uint64) (*etcd.Response, error) {
|
||||||
f.Ix = f.Ix + 1
|
f.Ix = f.Ix + 1
|
||||||
return f.Set(fmt.Sprintf("%s/%d", key, f.Ix), data, ttl)
|
return f.Set(fmt.Sprintf("%s/%d", key, f.Ix), data, ttl)
|
||||||
@ -78,34 +96,100 @@ func (f *FakeEtcdClient) AddChild(key, data string, ttl uint64) (*etcd.Response,
|
|||||||
func (f *FakeEtcdClient) Get(key string, sort, recursive bool) (*etcd.Response, error) {
|
func (f *FakeEtcdClient) Get(key string, sort, recursive bool) (*etcd.Response, error) {
|
||||||
result := f.Data[key]
|
result := f.Data[key]
|
||||||
if result.R == nil {
|
if result.R == nil {
|
||||||
|
if _, ok := f.expectNotFoundGetSet[key]; !ok {
|
||||||
f.t.Errorf("Unexpected get for %s", key)
|
f.t.Errorf("Unexpected get for %s", key)
|
||||||
|
}
|
||||||
return &etcd.Response{}, EtcdErrorNotFound
|
return &etcd.Response{}, EtcdErrorNotFound
|
||||||
}
|
}
|
||||||
f.t.Logf("returning %v: %v %#v", key, result.R, result.E)
|
f.t.Logf("returning %v: %v %#v", key, result.R, result.E)
|
||||||
return result.R, result.E
|
return result.R, result.E
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FakeEtcdClient) nodeExists(key string) bool {
|
||||||
|
result, ok := f.Data[key]
|
||||||
|
return ok && result.R != nil && result.R.Node != nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f *FakeEtcdClient) Set(key, value string, ttl uint64) (*etcd.Response, error) {
|
func (f *FakeEtcdClient) Set(key, value string, ttl uint64) (*etcd.Response, error) {
|
||||||
|
if f.Err != nil {
|
||||||
|
return nil, f.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
i := f.generateIndex()
|
||||||
|
|
||||||
|
if f.nodeExists(key) {
|
||||||
|
prevResult := f.Data[key]
|
||||||
|
createdIndex := prevResult.R.Node.CreatedIndex
|
||||||
result := EtcdResponseWithError{
|
result := EtcdResponseWithError{
|
||||||
R: &etcd.Response{
|
R: &etcd.Response{
|
||||||
Node: &etcd.Node{
|
Node: &etcd.Node{
|
||||||
Value: value,
|
Value: value,
|
||||||
|
CreatedIndex: createdIndex,
|
||||||
|
ModifiedIndex: i,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
f.Data[key] = result
|
f.Data[key] = result
|
||||||
return result.R, f.Err
|
return result.R, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := EtcdResponseWithError{
|
||||||
|
R: &etcd.Response{
|
||||||
|
Node: &etcd.Node{
|
||||||
|
Value: value,
|
||||||
|
CreatedIndex: i,
|
||||||
|
ModifiedIndex: i,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
f.Data[key] = result
|
||||||
|
return result.R, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeEtcdClient) CompareAndSwap(key, value string, ttl uint64, prevValue string, prevIndex uint64) (*etcd.Response, error) {
|
func (f *FakeEtcdClient) CompareAndSwap(key, value string, ttl uint64, prevValue string, prevIndex uint64) (*etcd.Response, error) {
|
||||||
// TODO: Maybe actually implement compare and swap here?
|
if f.Err != nil {
|
||||||
|
return nil, f.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !f.TestIndex {
|
||||||
|
f.t.Errorf("Enable TestIndex for test involving CompareAndSwap")
|
||||||
|
return nil, errors.New("Enable TestIndex for test involving CompareAndSwap")
|
||||||
|
}
|
||||||
|
|
||||||
|
if prevValue == "" && prevIndex == 0 {
|
||||||
|
return nil, errors.New("Either prevValue or prevIndex must be specified.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !f.nodeExists(key) {
|
||||||
|
return nil, EtcdErrorNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
prevNode := f.Data[key].R.Node
|
||||||
|
|
||||||
|
if prevValue != "" && prevValue != prevNode.Value {
|
||||||
|
return nil, EtcdErrorTestFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
if prevIndex != 0 && prevIndex != prevNode.ModifiedIndex {
|
||||||
|
return nil, EtcdErrorTestFailed
|
||||||
|
}
|
||||||
|
|
||||||
return f.Set(key, value, ttl)
|
return f.Set(key, value, ttl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeEtcdClient) Create(key, value string, ttl uint64) (*etcd.Response, error) {
|
func (f *FakeEtcdClient) Create(key, value string, ttl uint64) (*etcd.Response, error) {
|
||||||
|
if f.nodeExists(key) {
|
||||||
|
return nil, EtcdErrorNodeExist
|
||||||
|
}
|
||||||
|
|
||||||
return f.Set(key, value, ttl)
|
return f.Set(key, value, ttl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeEtcdClient) Delete(key string, recursive bool) (*etcd.Response, error) {
|
func (f *FakeEtcdClient) Delete(key string, recursive bool) (*etcd.Response, error) {
|
||||||
|
if f.Err != nil {
|
||||||
|
return nil, f.Err
|
||||||
|
}
|
||||||
|
|
||||||
f.Data[key] = EtcdResponseWithError{
|
f.Data[key] = EtcdResponseWithError{
|
||||||
R: &etcd.Response{
|
R: &etcd.Response{
|
||||||
Node: nil,
|
Node: nil,
|
||||||
@ -114,7 +198,7 @@ func (f *FakeEtcdClient) Delete(key string, recursive bool) (*etcd.Response, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
f.DeletedKeys = append(f.DeletedKeys, key)
|
f.DeletedKeys = append(f.DeletedKeys, key)
|
||||||
return &etcd.Response{}, f.Err
|
return &etcd.Response{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeEtcdClient) WaitForWatchCompletion() {
|
func (f *FakeEtcdClient) WaitForWatchCompletion() {
|
||||||
|
Loading…
Reference in New Issue
Block a user