// Copyright 2016 The etcd Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package clientv3 import ( "fmt" "sync" "time" pb "github.com/coreos/etcd/etcdserver/etcdserverpb" "golang.org/x/net/context" utiltrace "k8s.io/utils/trace" ) type Txn interface { // If takes a list of comparison. If all comparisons passed in succeed, // the operations passed into Then() will be executed. Or the operations // passed into Else() will be executed. If(cs ...Cmp) Txn // Then takes a list of operations. The Ops list will be executed, if the // comparisons passed in If() succeed. Then(ops ...Op) Txn // Else takes a list of operations. The Ops list will be executed, if the // comparisons passed in If() fail. Else(ops ...Op) Txn // Commit tries to commit the transaction. Commit() (*TxnResponse, error) } type txn struct { kv *kv ctx context.Context mu sync.Mutex cif bool cthen bool celse bool cmps []*pb.Compare sus []Op fas []Op } func (txn *txn) If(cs ...Cmp) Txn { txn.mu.Lock() defer txn.mu.Unlock() if txn.cif { panic("cannot call If twice!") } if txn.cthen { panic("cannot call If after Then!") } if txn.celse { panic("cannot call If after Else!") } txn.cif = true for i := range cs { txn.cmps = append(txn.cmps, (*pb.Compare)(&cs[i])) } return txn } func (txn *txn) Then(ops ...Op) Txn { txn.mu.Lock() defer txn.mu.Unlock() if txn.cthen { panic("cannot call Then twice!") } if txn.celse { panic("cannot call Then after Else!") } txn.cthen = true for _, op := range ops { txn.sus = append(txn.sus, op) } return txn } func (txn *txn) Else(ops ...Op) Txn { txn.mu.Lock() defer txn.mu.Unlock() if txn.celse { panic("cannot call Else twice!") } txn.celse = true for _, op := range ops { txn.fas = append(txn.fas, op) } return txn } func (txn *txn) do(op Op) (*pb.ResponseOp, error) { switch op.t { case tRange: r, err := txn.kv.opGet(txn.ctx, op) if err != nil { return nil, err } return &pb.ResponseOp{ Response: &pb.ResponseOp_ResponseRange{ ResponseRange: (*pb.RangeResponse)(r), }, }, nil case tPut: r, err := txn.kv.opPut(txn.ctx, op) if err != nil { return nil, err } return &pb.ResponseOp{ Response: &pb.ResponseOp_ResponsePut{ ResponsePut: (*pb.PutResponse)(r), }, }, nil case tDeleteRange: r, err := txn.kv.opDelete(txn.ctx, op) if err != nil { return nil, err } return &pb.ResponseOp{ Response: &pb.ResponseOp_ResponseDeleteRange{ ResponseDeleteRange: (*pb.DeleteRangeResponse)(r), }, }, nil default: return nil, fmt.Errorf("unknown op in txn: %#v", op) } } func (txn *txn) Commit() (*TxnResponse, error) { trace := utiltrace.New("SQL Commit") defer trace.LogIfLong(500 * time.Millisecond) locks := map[string]bool{} resp := &TxnResponse{ Header: &pb.ResponseHeader{}, } good := true for _, c := range txn.cmps { k := string(c.Key) if !locks[k] { txn.kv.l.Lock(k) trace.Step(fmt.Sprintf("lock acquired: %s", k)) locks[k] = true defer txn.kv.l.Unlock(k) } gr, err := txn.kv.Get(txn.ctx, k) if err != nil { return nil, err } switch c.Target { case pb.Compare_VERSION: ver := int64(0) if len(gr.Kvs) > 0 { ver = gr.Kvs[0].Version } cv, _ := c.TargetUnion.(*pb.Compare_Version) if ver != cv.Version { good = false } case pb.Compare_MOD: mod := int64(0) if len(gr.Kvs) > 0 { mod = gr.Kvs[0].ModRevision } cv, _ := c.TargetUnion.(*pb.Compare_ModRevision) if mod != cv.ModRevision { good = false } default: return nil, fmt.Errorf("unknown txn target %v", c.Target) } trace.Step(fmt.Sprintf("condition key %s good %v", k, good)) } resp.Succeeded = good ops := txn.sus if !good { ops = txn.fas } for _, op := range ops { r, err := txn.do(op) if err != nil { return nil, err } resp.Responses = append(resp.Responses, r) trace.Step(fmt.Sprintf("op key %s op %v", op.key, op.t)) } return resp, nil }