Use etcd compare and swap to update the list of pods, to remove a race.
This commit is contained in:
@@ -24,10 +24,24 @@ import (
|
||||
"github.com/coreos/go-etcd/etcd"
|
||||
)
|
||||
|
||||
// EtcdClient is an injectable interface for testing.
|
||||
type EtcdClient interface {
|
||||
AddChild(key, data string, ttl uint64) (*etcd.Response, error)
|
||||
Get(key string, sort, recursive bool) (*etcd.Response, error)
|
||||
Set(key, value string, ttl uint64) (*etcd.Response, error)
|
||||
Create(key, value string, ttl uint64) (*etcd.Response, error)
|
||||
CompareAndSwap(key, value string, ttl uint64, prevValue string, prevIndex uint64) (*etcd.Response, error)
|
||||
Delete(key string, recursive bool) (*etcd.Response, error)
|
||||
// I'd like to use directional channels here (e.g. <-chan) but this interface mimics
|
||||
// the etcd client interface which doesn't, and it doesn't seem worth it to wrap the api.
|
||||
Watch(prefix string, waitIndex uint64, recursive bool, receiver chan *etcd.Response, stop chan bool) (*etcd.Response, error)
|
||||
}
|
||||
|
||||
// Interface exposing only the etcd operations needed by EtcdHelper.
|
||||
type EtcdGetSet interface {
|
||||
Get(key string, sort, recursive bool) (*etcd.Response, error)
|
||||
Set(key, value string, ttl uint64) (*etcd.Response, error)
|
||||
CompareAndSwap(key, value string, ttl uint64, prevValue string, prevIndex uint64) (*etcd.Response, error)
|
||||
}
|
||||
|
||||
// EtcdHelper offers common object marshalling/unmarshalling operations on an etcd client.
|
||||
@@ -37,6 +51,16 @@ type EtcdHelper struct {
|
||||
|
||||
// Returns true iff err is an etcd not found error.
|
||||
func IsEtcdNotFound(err error) bool {
|
||||
return isEtcdErrorNum(err, 100)
|
||||
}
|
||||
|
||||
// Returns true iff err is an etcd write conflict.
|
||||
func IsEtcdConflict(err error) bool {
|
||||
return isEtcdErrorNum(err, 101)
|
||||
}
|
||||
|
||||
// Returns true iff err is an etcd error, whose errorCode matches errorCode
|
||||
func isEtcdErrorNum(err error, errorCode int) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
@@ -46,7 +70,7 @@ func IsEtcdNotFound(err error) bool {
|
||||
if etcdError == nil {
|
||||
return false
|
||||
}
|
||||
if etcdError.ErrorCode == 100 {
|
||||
if etcdError.ErrorCode == errorCode {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -92,23 +116,33 @@ func (h *EtcdHelper) ExtractList(key string, slicePtr interface{}) error {
|
||||
// Unmarshals json found at key into objPtr. On a not found error, will either return
|
||||
// a zero object of the requested type, or an error, depending on ignoreNotFound. Treats
|
||||
// empty responses and nil response nodes exactly like a not found error.
|
||||
func (h *EtcdHelper) ExtractObj(key string, objPtr interface{}, ignoreNotFound bool) error {
|
||||
func (h *EtcdHelper) ExtractObj(key string, objPtr interface{}, ignoreNotFound bool) (error, uint64) {
|
||||
response, err := h.Client.Get(key, false, false)
|
||||
|
||||
if err != nil && !IsEtcdNotFound(err) {
|
||||
return err
|
||||
return err, 0
|
||||
}
|
||||
if err != nil || response.Node == nil || len(response.Node.Value) == 0 {
|
||||
if ignoreNotFound {
|
||||
pv := reflect.ValueOf(objPtr)
|
||||
pv.Elem().Set(reflect.Zero(pv.Type().Elem()))
|
||||
return nil
|
||||
return nil, 0
|
||||
} else if err != nil {
|
||||
return err
|
||||
return err, 0
|
||||
}
|
||||
return fmt.Errorf("key '%v' found no nodes field: %#v", key, response)
|
||||
return fmt.Errorf("key '%v' found no nodes field: %#v", key, response), 0
|
||||
}
|
||||
return json.Unmarshal([]byte(response.Node.Value), objPtr)
|
||||
return json.Unmarshal([]byte(response.Node.Value), objPtr), response.Node.ModifiedIndex
|
||||
}
|
||||
|
||||
// CompareAndSwapObj marshals obj via json, and stores under key so long as index matches the previous modified index
|
||||
func (h *EtcdHelper) CompareAndSwapObj(key string, obj interface{}, index uint64) error {
|
||||
data, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = h.Client.CompareAndSwap(key, string(data), 0, "", index)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetObj marshals obj via json, and stores under key.
|
||||
|
@@ -86,7 +86,7 @@ func TestExtractObj(t *testing.T) {
|
||||
fakeClient.Set("/some/key", MakeJSONString(expect), 0)
|
||||
helper := EtcdHelper{fakeClient}
|
||||
var got testMarshalType
|
||||
err := helper.ExtractObj("/some/key", &got, false)
|
||||
err, _ := helper.ExtractObj("/some/key", &got, false)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
}
|
||||
@@ -120,11 +120,11 @@ func TestExtractObjNotFoundErr(t *testing.T) {
|
||||
helper := EtcdHelper{fakeClient}
|
||||
try := func(key string) {
|
||||
var got testMarshalType
|
||||
err := helper.ExtractObj(key, &got, false)
|
||||
err, _ := helper.ExtractObj(key, &got, false)
|
||||
if err == nil {
|
||||
t.Errorf("%s: wanted error but didn't get one", key)
|
||||
}
|
||||
err = helper.ExtractObj(key, &got, true)
|
||||
err, _ = helper.ExtractObj(key, &got, true)
|
||||
if err != nil {
|
||||
t.Errorf("%s: didn't want error but got %#v", key, err)
|
||||
}
|
||||
|
@@ -23,18 +23,6 @@ import (
|
||||
"github.com/coreos/go-etcd/etcd"
|
||||
)
|
||||
|
||||
// EtcdClient is an injectable interface for testing.
|
||||
type EtcdClient interface {
|
||||
AddChild(key, data string, ttl uint64) (*etcd.Response, error)
|
||||
Get(key string, sort, recursive bool) (*etcd.Response, error)
|
||||
Set(key, value string, ttl uint64) (*etcd.Response, error)
|
||||
Create(key, value string, ttl uint64) (*etcd.Response, error)
|
||||
Delete(key string, recursive bool) (*etcd.Response, error)
|
||||
// I'd like to use directional channels here (e.g. <-chan) but this interface mimics
|
||||
// the etcd client interface which doesn't, and it doesn't seem worth it to wrap the api.
|
||||
Watch(prefix string, waitIndex uint64, recursive bool, receiver chan *etcd.Response, stop chan bool) (*etcd.Response, error)
|
||||
}
|
||||
|
||||
type EtcdResponseWithError struct {
|
||||
R *etcd.Response
|
||||
E error
|
||||
@@ -87,6 +75,12 @@ func (f *FakeEtcdClient) Set(key, value string, ttl uint64) (*etcd.Response, err
|
||||
f.Data[key] = result
|
||||
return result.R, f.Err
|
||||
}
|
||||
|
||||
func (f *FakeEtcdClient) CompareAndSwap(key, value string, ttl uint64, prevValue string, prevIndex uint64) (*etcd.Response, error) {
|
||||
// TODO: Maybe actually implement compare and swap here?
|
||||
return f.Set(key, value, ttl)
|
||||
}
|
||||
|
||||
func (f *FakeEtcdClient) Create(key, value string, ttl uint64) (*etcd.Response, error) {
|
||||
return f.Set(key, value, ttl)
|
||||
}
|
||||
|
Reference in New Issue
Block a user