// 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) 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 } }