go.etcd.io/etcd@v3.3.27+incompatible/clientv3/integration/txn_test.go (about)

     1  // Copyright 2016 The etcd Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package integration
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/coreos/etcd/clientv3"
    24  	"github.com/coreos/etcd/embed"
    25  	"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
    26  	"github.com/coreos/etcd/integration"
    27  	"github.com/coreos/etcd/pkg/testutil"
    28  )
    29  
    30  func TestTxnError(t *testing.T) {
    31  	defer testutil.AfterTest(t)
    32  
    33  	clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1})
    34  	defer clus.Terminate(t)
    35  
    36  	kv := clus.RandClient()
    37  	ctx := context.TODO()
    38  
    39  	_, err := kv.Txn(ctx).Then(clientv3.OpPut("foo", "bar1"), clientv3.OpPut("foo", "bar2")).Commit()
    40  	if err != rpctypes.ErrDuplicateKey {
    41  		t.Fatalf("expected %v, got %v", rpctypes.ErrDuplicateKey, err)
    42  	}
    43  
    44  	ops := make([]clientv3.Op, int(embed.DefaultMaxTxnOps+10))
    45  	for i := range ops {
    46  		ops[i] = clientv3.OpPut(fmt.Sprintf("foo%d", i), "")
    47  	}
    48  	_, err = kv.Txn(ctx).Then(ops...).Commit()
    49  	if err != rpctypes.ErrTooManyOps {
    50  		t.Fatalf("expected %v, got %v", rpctypes.ErrTooManyOps, err)
    51  	}
    52  }
    53  
    54  func TestTxnWriteFail(t *testing.T) {
    55  	defer testutil.AfterTest(t)
    56  
    57  	clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3})
    58  	defer clus.Terminate(t)
    59  
    60  	kv := clus.Client(0)
    61  
    62  	clus.Members[0].Stop(t)
    63  
    64  	txnc, getc := make(chan struct{}), make(chan struct{})
    65  	go func() {
    66  		ctx, cancel := context.WithTimeout(context.TODO(), time.Second)
    67  		defer cancel()
    68  		resp, err := kv.Txn(ctx).Then(clientv3.OpPut("foo", "bar")).Commit()
    69  		if err == nil {
    70  			t.Fatalf("expected error, got response %v", resp)
    71  		}
    72  		close(txnc)
    73  	}()
    74  
    75  	go func() {
    76  		defer close(getc)
    77  		select {
    78  		case <-time.After(5 * time.Second):
    79  			t.Fatalf("timed out waiting for txn fail")
    80  		case <-txnc:
    81  		}
    82  		// and ensure the put didn't take
    83  		gresp, gerr := clus.Client(1).Get(context.TODO(), "foo")
    84  		if gerr != nil {
    85  			t.Fatal(gerr)
    86  		}
    87  		if len(gresp.Kvs) != 0 {
    88  			t.Fatalf("expected no keys, got %v", gresp.Kvs)
    89  		}
    90  	}()
    91  
    92  	select {
    93  	case <-time.After(2 * clus.Members[1].ServerConfig.ReqTimeout()):
    94  		t.Fatalf("timed out waiting for get")
    95  	case <-getc:
    96  	}
    97  
    98  	// reconnect so terminate doesn't complain about double-close
    99  	clus.Members[0].Restart(t)
   100  }
   101  
   102  func TestTxnReadRetry(t *testing.T) {
   103  	t.Skipf("skipping txn read retry test: re-enable after we do retry on txn read request")
   104  
   105  	defer testutil.AfterTest(t)
   106  
   107  	clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3})
   108  	defer clus.Terminate(t)
   109  
   110  	kv := clus.Client(0)
   111  
   112  	thenOps := [][]clientv3.Op{
   113  		{clientv3.OpGet("foo")},
   114  		{clientv3.OpTxn(nil, []clientv3.Op{clientv3.OpGet("foo")}, nil)},
   115  		{clientv3.OpTxn(nil, nil, nil)},
   116  		{},
   117  	}
   118  	for i := range thenOps {
   119  		clus.Members[0].Stop(t)
   120  		<-clus.Members[0].StopNotify()
   121  
   122  		donec := make(chan struct{})
   123  		go func() {
   124  			_, err := kv.Txn(context.TODO()).Then(thenOps[i]...).Commit()
   125  			if err != nil {
   126  				t.Fatalf("expected response, got error %v", err)
   127  			}
   128  			donec <- struct{}{}
   129  		}()
   130  		// wait for txn to fail on disconnect
   131  		time.Sleep(100 * time.Millisecond)
   132  
   133  		// restart node; client should resume
   134  		clus.Members[0].Restart(t)
   135  		select {
   136  		case <-donec:
   137  		case <-time.After(2 * clus.Members[1].ServerConfig.ReqTimeout()):
   138  			t.Fatalf("waited too long")
   139  		}
   140  	}
   141  }
   142  
   143  func TestTxnSuccess(t *testing.T) {
   144  	defer testutil.AfterTest(t)
   145  
   146  	clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3})
   147  	defer clus.Terminate(t)
   148  
   149  	kv := clus.Client(0)
   150  	ctx := context.TODO()
   151  
   152  	_, err := kv.Txn(ctx).Then(clientv3.OpPut("foo", "bar")).Commit()
   153  	if err != nil {
   154  		t.Fatal(err)
   155  	}
   156  
   157  	resp, err := kv.Get(ctx, "foo")
   158  	if err != nil {
   159  		t.Fatal(err)
   160  	}
   161  	if len(resp.Kvs) != 1 || string(resp.Kvs[0].Key) != "foo" {
   162  		t.Fatalf("unexpected Get response %v", resp)
   163  	}
   164  }
   165  
   166  func TestTxnCompareRange(t *testing.T) {
   167  	defer testutil.AfterTest(t)
   168  
   169  	clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1})
   170  	defer clus.Terminate(t)
   171  
   172  	kv := clus.Client(0)
   173  	fooResp, err := kv.Put(context.TODO(), "foo/", "bar")
   174  	if err != nil {
   175  		t.Fatal(err)
   176  	}
   177  	if _, err = kv.Put(context.TODO(), "foo/a", "baz"); err != nil {
   178  		t.Fatal(err)
   179  	}
   180  	tresp, terr := kv.Txn(context.TODO()).If(
   181  		clientv3.Compare(
   182  			clientv3.CreateRevision("foo/"), "=", fooResp.Header.Revision).
   183  			WithPrefix(),
   184  	).Commit()
   185  	if terr != nil {
   186  		t.Fatal(terr)
   187  	}
   188  	if tresp.Succeeded {
   189  		t.Fatal("expected prefix compare to false, got compares as true")
   190  	}
   191  }
   192  
   193  func TestTxnNested(t *testing.T) {
   194  	defer testutil.AfterTest(t)
   195  
   196  	clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3})
   197  	defer clus.Terminate(t)
   198  
   199  	kv := clus.Client(0)
   200  
   201  	tresp, err := kv.Txn(context.TODO()).
   202  		If(clientv3.Compare(clientv3.Version("foo"), "=", 0)).
   203  		Then(
   204  			clientv3.OpPut("foo", "bar"),
   205  			clientv3.OpTxn(nil, []clientv3.Op{clientv3.OpPut("abc", "123")}, nil)).
   206  		Else(clientv3.OpPut("foo", "baz")).Commit()
   207  	if err != nil {
   208  		t.Fatal(err)
   209  	}
   210  	if len(tresp.Responses) != 2 {
   211  		t.Errorf("expected 2 top-level txn responses, got %+v", tresp.Responses)
   212  	}
   213  
   214  	// check txn writes were applied
   215  	resp, err := kv.Get(context.TODO(), "foo")
   216  	if err != nil {
   217  		t.Fatal(err)
   218  	}
   219  	if len(resp.Kvs) != 1 || string(resp.Kvs[0].Value) != "bar" {
   220  		t.Errorf("unexpected Get response %+v", resp)
   221  	}
   222  	resp, err = kv.Get(context.TODO(), "abc")
   223  	if err != nil {
   224  		t.Fatal(err)
   225  	}
   226  	if len(resp.Kvs) != 1 || string(resp.Kvs[0].Value) != "123" {
   227  		t.Errorf("unexpected Get response %+v", resp)
   228  	}
   229  }