func isKeyCurrent(k string, r *v3.GetResponse) v3.Cmp { rev := r.Header.Revision + 1 if len(r.Kvs) != 0 { rev = r.Kvs[0].ModRevision + 1 } return v3.Compare(v3.ModRevision(k), "<", rev) }
func (s *store) conditionalDelete(ctx context.Context, key string, out runtime.Object, v reflect.Value, precondtions *storage.Preconditions) error { getResp, err := s.client.KV.Get(ctx, key) if err != nil { return err } for { origState, err := s.getState(getResp, key, v, false) if err != nil { return err } if err := checkPreconditions(key, precondtions, origState.obj); err != nil { return err } txnResp, err := s.client.KV.Txn(ctx).If( clientv3.Compare(clientv3.ModRevision(key), "=", origState.rev), ).Then( clientv3.OpDelete(key), ).Else( clientv3.OpGet(key), ).Commit() if err != nil { return err } if !txnResp.Succeeded { getResp = (*clientv3.GetResponse)(txnResp.Responses[0].GetResponseRange()) glog.V(4).Infof("deletion of %s failed because of a conflict, going to retry", key) continue } return decode(s.codec, s.versioner, origState.data, out, origState.rev) } }
// GuaranteedUpdate implements storage.Interface.GuaranteedUpdate. func (s *store) GuaranteedUpdate(ctx context.Context, key string, out runtime.Object, ignoreNotFound bool, precondtions *storage.Preconditions, tryUpdate storage.UpdateFunc) error { v, err := conversion.EnforcePtr(out) if err != nil { panic("unable to convert output object to pointer") } key = keyWithPrefix(s.pathPrefix, key) getResp, err := s.client.KV.Get(ctx, key) if err != nil { return err } for { origState, err := s.getState(getResp, key, v, ignoreNotFound) if err != nil { return err } if err := checkPreconditions(key, precondtions, origState.obj); err != nil { return err } ret, ttl, err := s.updateState(origState, tryUpdate) if err != nil { return err } data, err := runtime.Encode(s.codec, ret) if err != nil { return err } if bytes.Equal(data, origState.data) { return decode(s.codec, s.versioner, origState.data, out, origState.rev) } opts, err := s.ttlOpts(ctx, int64(ttl)) if err != nil { return err } txnResp, err := s.client.KV.Txn(ctx).If( clientv3.Compare(clientv3.ModRevision(key), "=", origState.rev), ).Then( clientv3.OpPut(key, string(data), opts...), ).Else( clientv3.OpGet(key), ).Commit() if err != nil { return err } if !txnResp.Succeeded { getResp = (*clientv3.GetResponse)(txnResp.Responses[0].GetResponseRange()) glog.V(4).Infof("GuaranteedUpdate of %s failed because of a conflict, going to retry", key) continue } putResp := txnResp.Responses[0].GetResponsePut() return decode(s.codec, s.versioner, data, out, putResp.Header.Revision) } }
// deleteRevKey deletes a key by revision, returning false if key is missing func deleteRevKey(kv v3.KV, key string, rev int64) (bool, error) { cmp := v3.Compare(v3.ModRevision(key), "=", rev) req := v3.OpDelete(key) txnresp, err := kv.Txn(context.TODO()).If(cmp).Then(req).Commit() if err != nil { return false, err } else if !txnresp.Succeeded { return false, nil } return true, nil }
func parseCompare(line string) (*clientv3.Cmp, error) { var ( key string op string val string ) lparenSplit := strings.SplitN(line, "(", 2) if len(lparenSplit) != 2 { return nil, fmt.Errorf("malformed comparison: %s", line) } target := lparenSplit[0] n, serr := fmt.Sscanf(lparenSplit[1], "%q) %s %q", &key, &op, &val) if n != 3 { return nil, fmt.Errorf("malformed comparison: %s; got %s(%q) %s %q", line, target, key, op, val) } if serr != nil { return nil, fmt.Errorf("malformed comparison: %s (%v)", line, serr) } var ( v int64 err error cmp clientv3.Cmp ) switch target { case "ver", "version": if v, err = strconv.ParseInt(val, 10, 64); err == nil { cmp = clientv3.Compare(clientv3.Version(key), op, v) } case "c", "create": if v, err = strconv.ParseInt(val, 10, 64); err == nil { cmp = clientv3.Compare(clientv3.CreateRevision(key), op, v) } case "m", "mod": if v, err = strconv.ParseInt(val, 10, 64); err == nil { cmp = clientv3.Compare(clientv3.ModRevision(key), op, v) } case "val", "value": cmp = clientv3.Compare(clientv3.Value(key), op, val) default: return nil, fmt.Errorf("malformed comparison: %s (unknown target %s)", line, target) } if err != nil { return nil, fmt.Errorf("invalid txn compare request: %s", line) } return &cmp, nil }
func NewUniqueKV(ctx context.Context, kv v3.KV, pfx, val string, opts ...v3.OpOption) (string, int64, error) { for { newKey := fmt.Sprintf("%s/%v", pfx, time.Now().UnixNano()) put := v3.OpPut(newKey, val, opts...) cmp := v3.Compare(v3.ModRevision(newKey), "=", 0) resp, err := kv.Txn(ctx).If(cmp).Then(put).Commit() if err != nil { return "", 0, err } if !resp.Succeeded { continue } return newKey, resp.Header.Revision, nil } }
func putKeyAtMostOnce(ctx context.Context, client *clientv3.Client, key string) error { gr, err := getKey(ctx, client, key) if err != nil { return err } var modrev int64 if len(gr.Kvs) > 0 { modrev = gr.Kvs[0].ModRevision } for ctx.Err() == nil { _, err := client.Txn(ctx).If(clientv3.Compare(clientv3.ModRevision(key), "=", modrev)).Then(clientv3.OpPut(key, key)).Commit() if err == nil { return nil } } return ctx.Err() }
// newSequentialKV allocates a new sequential key <prefix>/nnnnn with a given // value and lease. Note: a bookkeeping node __<prefix> is also allocated. func newSequentialKV(kv v3.KV, prefix, val string, leaseID v3.LeaseID) (*RemoteKV, error) { resp, err := kv.Get(context.TODO(), prefix, v3.WithLastKey()...) if err != nil { return nil, err } // add 1 to last key, if any newSeqNum := 0 if len(resp.Kvs) != 0 { fields := strings.Split(string(resp.Kvs[0].Key), "/") _, serr := fmt.Sscanf(fields[len(fields)-1], "%d", &newSeqNum) if serr != nil { return nil, serr } newSeqNum++ } newKey := fmt.Sprintf("%s/%016d", prefix, newSeqNum) // base prefix key must be current (i.e., <=) with the server update; // the base key is important to avoid the following: // N1: LastKey() == 1, start txn. // N2: New Key 2, New Key 3, Delete Key 2 // N1: txn succeeds allocating key 2 when it shouldn't baseKey := "__" + prefix // current revision might contain modification so +1 cmp := v3.Compare(v3.ModRevision(baseKey), "<", resp.Header.Revision+1) reqPrefix := v3.OpPut(baseKey, "", v3.WithLease(leaseID)) reqNewKey := v3.OpPut(newKey, val, v3.WithLease(leaseID)) txn := kv.Txn(context.TODO()) txnresp, err := txn.If(cmp).Then(reqPrefix, reqNewKey).Commit() if err != nil { return nil, err } if !txnresp.Succeeded { return newSequentialKV(kv, prefix, val, leaseID) } return &RemoteKV{kv, newKey, txnresp.Header.Revision, val}, nil }
func notFound(key string) clientv3.Cmp { return clientv3.Compare(clientv3.ModRevision(key), "=", 0) }
// GuaranteedUpdate implements storage.Interface.GuaranteedUpdate. func (s *store) GuaranteedUpdate( ctx context.Context, key string, out runtime.Object, ignoreNotFound bool, precondtions *storage.Preconditions, tryUpdate storage.UpdateFunc, suggestion ...runtime.Object) error { trace := util.NewTrace(fmt.Sprintf("GuaranteedUpdate etcd3: %s", reflect.TypeOf(out).String())) defer trace.LogIfLong(500 * time.Millisecond) v, err := conversion.EnforcePtr(out) if err != nil { panic("unable to convert output object to pointer") } key = keyWithPrefix(s.pathPrefix, key) var origState *objState if len(suggestion) == 1 && suggestion[0] != nil { origState, err = s.getStateFromObject(suggestion[0]) if err != nil { return err } } else { getResp, err := s.client.KV.Get(ctx, key, s.getOps...) if err != nil { return err } origState, err = s.getState(getResp, key, v, ignoreNotFound) if err != nil { return err } } trace.Step("initial value restored") for { if err := checkPreconditions(key, precondtions, origState.obj); err != nil { return err } ret, ttl, err := s.updateState(origState, tryUpdate) if err != nil { return err } data, err := runtime.Encode(s.codec, ret) if err != nil { return err } if bytes.Equal(data, origState.data) { return decode(s.codec, s.versioner, origState.data, out, origState.rev) } opts, err := s.ttlOpts(ctx, int64(ttl)) if err != nil { return err } trace.Step("Transaction prepared") txnResp, err := s.client.KV.Txn(ctx).If( clientv3.Compare(clientv3.ModRevision(key), "=", origState.rev), ).Then( clientv3.OpPut(key, string(data), opts...), ).Else( clientv3.OpGet(key), ).Commit() if err != nil { return err } trace.Step("Transaction committed") if !txnResp.Succeeded { getResp := (*clientv3.GetResponse)(txnResp.Responses[0].GetResponseRange()) glog.V(4).Infof("GuaranteedUpdate of %s failed because of a conflict, going to retry", key) origState, err = s.getState(getResp, key, v, ignoreNotFound) if err != nil { return err } trace.Step("Retry value restored") continue } putResp := txnResp.Responses[0].GetResponsePut() return decode(s.codec, s.versioner, data, out, putResp.Header.Revision) } }