// 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 = h.prefixEtcdKey(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) { glog.Infof("deletion of %s failed because of a conflict, going to retry", key) } else { 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) } } }
// Implements storage.Interface. func (h *etcdHelper) Set(ctx context.Context, key string, obj, out runtime.Object, ttl uint64) error { if ctx == nil { glog.Errorf("Context is nil") } var response *etcd.Response data, err := runtime.Encode(h.codec, obj) if err != nil { return err } key = h.prefixEtcdKey(key) create := true if h.versioner != nil { if version, err := h.versioner.ObjectResourceVersion(obj); err == nil && version != 0 { create = false startTime := time.Now() opts := etcd.SetOptions{ TTL: time.Duration(ttl) * time.Second, PrevIndex: version, } response, err = h.client.Set(ctx, key, string(data), &opts) metrics.RecordEtcdRequestLatency("compareAndSwap", getTypeName(obj), startTime) if err != nil { return err } } } if create { // Create will fail if a key already exists. startTime := time.Now() opts := etcd.SetOptions{ TTL: time.Duration(ttl) * time.Second, PrevExist: etcd.PrevNoExist, } response, err = h.client.Set(ctx, key, string(data), &opts) if err != nil { return err } metrics.RecordEtcdRequestLatency("create", getTypeName(obj), startTime) } if err != nil { return err } 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 }
// Implements storage.Interface. func (h *etcdHelper) List(ctx context.Context, key string, resourceVersion string, filter storage.FilterFunc, listObj runtime.Object) error { if ctx == nil { glog.Errorf("Context is nil") } trace := util.NewTrace("List " + getTypeName(listObj)) defer trace.LogIfLong(time.Second) listPtr, err := meta.GetItemsPtr(listObj) if err != nil { return err } key = h.prefixEtcdKey(key) startTime := time.Now() trace.Step("About to list etcd node") nodes, index, err := h.listEtcdNode(ctx, key) metrics.RecordEtcdRequestLatency("list", getTypeName(listPtr), startTime) trace.Step("Etcd node listed") if err != nil { return err } if err := h.decodeNodeList(nodes, filter, listPtr); err != nil { return err } trace.Step("Node list decoded") if err := h.versioner.UpdateList(listObj, index); err != nil { return err } return nil }
// Implements storage.Interface. func (h *etcdHelper) Create(ctx context.Context, key string, obj, out runtime.Object, ttl uint64) error { if ctx == nil { glog.Errorf("Context is nil") } key = h.prefixEtcdKey(key) data, err := runtime.Encode(h.codec, obj) if err != nil { return err } if h.versioner != nil { if version, err := h.versioner.ObjectResourceVersion(obj); err == nil && version != 0 { return errors.New("resourceVersion may not be set on objects to be created") } } startTime := time.Now() opts := etcd.SetOptions{ TTL: time.Duration(ttl) * time.Second, PrevExist: etcd.PrevNoExist, } response, err := h.client.Set(ctx, key, string(data), &opts) metrics.RecordEtcdRequestLatency("create", getTypeName(obj), startTime) if err != nil { return err } 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 }
// 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, err error) { if ctx == nil { glog.Errorf("Context is nil") } startTime := time.Now() response, err := h.client.Get(ctx, key, nil) metrics.RecordEtcdRequestLatency("get", getTypeName(objPtr), startTime) if err != nil && !etcdutil.IsEtcdNotFound(err) { return "", nil, nil, err } body, node, err = h.extractObj(response, err, objPtr, ignoreNotFound, false) return body, node, response, 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, 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, toStorageErr(err, key, 0) } body, node, err = h.extractObj(response, err, objPtr, ignoreNotFound, false) return body, node, response, toStorageErr(err, key, 0) }
// Implements storage.Interface. func (h *etcdHelper) Delete(ctx context.Context, key string, out runtime.Object) error { if ctx == nil { glog.Errorf("Context is nil") } key = h.prefixEtcdKey(key) if _, err := conversion.EnforcePtr(out); err != nil { panic("unable to convert output object to pointer") } 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 out. if err != nil || response.PrevNode != nil { _, _, err = h.extractObj(response, err, out, false, true) } } return err }
// Implements storage.Interface. func (h *etcdHelper) GetToList(ctx context.Context, key string, filter storage.FilterFunc, listObj runtime.Object) error { if ctx == nil { glog.Errorf("Context is nil") } trace := util.NewTrace("GetToList " + getTypeName(listObj)) listPtr, err := meta.GetItemsPtr(listObj) if err != nil { return err } key = h.prefixEtcdKey(key) startTime := time.Now() trace.Step("About to read etcd node") opts := &etcd.GetOptions{ Quorum: h.quorum, } response, err := h.client.Get(ctx, key, opts) metrics.RecordEtcdRequestLatency("get", getTypeName(listPtr), startTime) trace.Step("Etcd node read") if err != nil { if etcdutil.IsEtcdNotFound(err) { return nil } return err } nodes := make([]*etcd.Node, 0) nodes = append(nodes, response.Node) if err := h.decodeNodeList(nodes, filter, listPtr); err != nil { return err } trace.Step("Object decoded") if h.versioner != nil { if err := h.versioner.UpdateList(listObj, response.Index); err != nil { return err } } return nil }
// Implements storage.Interface. func (h *etcdHelper) Create(ctx context.Context, key string, obj, out runtime.Object, ttl uint64) error { trace := util.NewTrace("etcdHelper::Create " + getTypeName(obj)) defer trace.LogIfLong(250 * time.Millisecond) if ctx == nil { glog.Errorf("Context is nil") } key = h.prefixEtcdKey(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") } trace.Step("Version checked") startTime := time.Now() opts := etcd.SetOptions{ TTL: time.Duration(ttl) * time.Second, PrevExist: etcd.PrevNoExist, } response, err := h.etcdKeysAPI.Set(ctx, key, string(data), &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 }
// Implements storage.Interface. func (h *etcdHelper) GuaranteedUpdate(ctx context.Context, key string, ptrToType runtime.Object, ignoreNotFound bool, tryUpdate storage.UpdateFunc) error { 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 = h.prefixEtcdKey(key) for { obj := reflect.New(v.Type()).Interface().(runtime.Object) origBody, node, res, err := h.bodyAndExtractObj(ctx, key, obj, ignoreNotFound) if err != nil { return err } meta := storage.ResponseMeta{} if node != nil { meta.TTL = node.TTL if node.Expiration != nil { meta.Expiration = node.Expiration } meta.ResourceVersion = node.ModifiedIndex } // Get the object to be written by calling tryUpdate. ret, newTTL, err := tryUpdate(obj, meta) if err != nil { return err } 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.UpdateObject(ret, meta.Expiration, 0); err != nil { return errors.New("resourceVersion cannot be set on objects store in etcd") } data, err := runtime.Encode(h.codec, ret) if err != nil { return err } // 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, string(data), &opts) metrics.RecordEtcdRequestLatency("create", getTypeName(ptrToType), startTime) if etcdutil.IsEtcdNodeExist(err) { continue } _, _, err = h.extractObj(response, err, ptrToType, false, false) return err } if string(data) == origBody { // If we don't send an update, we simply return the currently existing // version of the object. _, _, 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{ PrevValue: origBody, PrevIndex: index, TTL: time.Duration(ttl) * time.Second, } response, err := h.etcdKeysAPI.Set(ctx, key, string(data), &opts) metrics.RecordEtcdRequestLatency("compareAndSwap", getTypeName(ptrToType), startTime) if etcdutil.IsEtcdTestFailed(err) { // Try again. continue } _, _, err = h.extractObj(response, err, ptrToType, false, false) return err } }
// Implements storage.Interface. func (h *etcdHelper) Set(ctx context.Context, key string, obj, out runtime.Object, ttl uint64) error { if ctx == nil { glog.Errorf("Context is nil") } version := uint64(0) var err error if version, err = h.versioner.ObjectResourceVersion(obj); err != nil { return errors.New("couldn't get resourceVersion from object") } if version != 0 { // We cannot store object with resourceVersion in etcd, we need to clear it here. if err := h.versioner.UpdateObject(obj, nil, 0); err != nil { return errors.New("resourceVersion cannot be set on objects store in etcd") } } var response *etcd.Response data, err := runtime.Encode(h.codec, obj) if err != nil { return err } key = h.prefixEtcdKey(key) create := true if version != 0 { create = false startTime := time.Now() opts := etcd.SetOptions{ TTL: time.Duration(ttl) * time.Second, PrevIndex: version, } response, err = h.etcdKeysAPI.Set(ctx, key, string(data), &opts) metrics.RecordEtcdRequestLatency("compareAndSwap", getTypeName(obj), startTime) if err != nil { return err } } if create { // Create will fail if a key already exists. startTime := time.Now() opts := etcd.SetOptions{ TTL: time.Duration(ttl) * time.Second, PrevExist: etcd.PrevNoExist, } response, err = h.etcdKeysAPI.Set(ctx, key, string(data), &opts) if err != nil { return err } metrics.RecordEtcdRequestLatency("create", getTypeName(obj), startTime) } 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 }
// Implements storage.Interface. func (h *etcdHelper) GuaranteedUpdate(ctx context.Context, key string, ptrToType runtime.Object, ignoreNotFound bool, tryUpdate storage.UpdateFunc) error { 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 = h.prefixEtcdKey(key) for { obj := reflect.New(v.Type()).Interface().(runtime.Object) origBody, node, res, err := h.bodyAndExtractObj(ctx, key, obj, ignoreNotFound) if err != nil { return err } meta := storage.ResponseMeta{} if node != nil { meta.TTL = node.TTL if node.Expiration != nil { meta.Expiration = node.Expiration } meta.ResourceVersion = node.ModifiedIndex } // Get the object to be written by calling tryUpdate. ret, newTTL, err := tryUpdate(obj, meta) if err != nil { return err } 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.EtcdIndex } 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 } data, err := h.codec.Encode(ret) if err != nil { return err } // First time this key has been used, try creating new value. if index == 0 { startTime := time.Now() response, err := h.client.Create(key, string(data), ttl) metrics.RecordEtcdRequestLatency("create", getTypeName(ptrToType), startTime) if IsEtcdNodeExist(err) { continue } _, _, err = h.extractObj(response, err, ptrToType, false, false) return err } if string(data) == origBody { return nil } startTime := time.Now() // Swap origBody with data, if origBody is the latest etcd data. response, err := h.client.CompareAndSwap(key, string(data), ttl, origBody, index) metrics.RecordEtcdRequestLatency("compareAndSwap", getTypeName(ptrToType), startTime) if IsEtcdTestFailed(err) { // Try again. continue } _, _, err = h.extractObj(response, err, ptrToType, false, false) return err } }