github.com/outbrain/consul@v1.4.5/agent/txn_endpoint_test.go (about)

     1  package agent
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"reflect"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/hashicorp/consul/testrpc"
    13  
    14  	"github.com/hashicorp/consul/agent/structs"
    15  )
    16  
    17  func TestTxnEndpoint_Bad_JSON(t *testing.T) {
    18  	t.Parallel()
    19  	a := NewTestAgent(t, t.Name(), "")
    20  	defer a.Shutdown()
    21  
    22  	buf := bytes.NewBuffer([]byte("{"))
    23  	req, _ := http.NewRequest("PUT", "/v1/txn", buf)
    24  	resp := httptest.NewRecorder()
    25  	if _, err := a.srv.Txn(resp, req); err != nil {
    26  		t.Fatalf("err: %v", err)
    27  	}
    28  	if resp.Code != 400 {
    29  		t.Fatalf("expected 400, got %d", resp.Code)
    30  	}
    31  	if !bytes.Contains(resp.Body.Bytes(), []byte("Failed to parse")) {
    32  		t.Fatalf("expected conflicting args error")
    33  	}
    34  }
    35  
    36  func TestTxnEndpoint_Bad_Size_Item(t *testing.T) {
    37  	t.Parallel()
    38  	a := NewTestAgent(t, t.Name(), "")
    39  	defer a.Shutdown()
    40  
    41  	buf := bytes.NewBuffer([]byte(fmt.Sprintf(`
    42   [
    43       {
    44           "KV": {
    45               "Verb": "set",
    46               "Key": "key",
    47               "Value": %q
    48           }
    49       }
    50   ]
    51   `, strings.Repeat("bad", 2*maxKVSize))))
    52  	req, _ := http.NewRequest("PUT", "/v1/txn", buf)
    53  	resp := httptest.NewRecorder()
    54  	if _, err := a.srv.Txn(resp, req); err != nil {
    55  		t.Fatalf("err: %v", err)
    56  	}
    57  	if resp.Code != 413 {
    58  		t.Fatalf("expected 413, got %d", resp.Code)
    59  	}
    60  }
    61  
    62  func TestTxnEndpoint_Bad_Size_Net(t *testing.T) {
    63  	t.Parallel()
    64  	a := NewTestAgent(t, t.Name(), "")
    65  	defer a.Shutdown()
    66  
    67  	value := strings.Repeat("X", maxKVSize/2)
    68  	buf := bytes.NewBuffer([]byte(fmt.Sprintf(`
    69   [
    70       {
    71           "KV": {
    72               "Verb": "set",
    73               "Key": "key1",
    74               "Value": %q
    75           }
    76       },
    77       {
    78           "KV": {
    79               "Verb": "set",
    80               "Key": "key1",
    81               "Value": %q
    82           }
    83       },
    84       {
    85           "KV": {
    86               "Verb": "set",
    87               "Key": "key1",
    88               "Value": %q
    89           }
    90       }
    91   ]
    92   `, value, value, value)))
    93  	req, _ := http.NewRequest("PUT", "/v1/txn", buf)
    94  	resp := httptest.NewRecorder()
    95  	if _, err := a.srv.Txn(resp, req); err != nil {
    96  		t.Fatalf("err: %v", err)
    97  	}
    98  	if resp.Code != 413 {
    99  		t.Fatalf("expected 413, got %d", resp.Code)
   100  	}
   101  }
   102  
   103  func TestTxnEndpoint_Bad_Size_Ops(t *testing.T) {
   104  	t.Parallel()
   105  	a := NewTestAgent(t, t.Name(), "")
   106  	defer a.Shutdown()
   107  
   108  	buf := bytes.NewBuffer([]byte(fmt.Sprintf(`
   109   [
   110       %s
   111       {
   112           "KV": {
   113               "Verb": "set",
   114               "Key": "key",
   115               "Value": ""
   116           }
   117       }
   118   ]
   119   `, strings.Repeat(`{ "KV": { "Verb": "get", "Key": "key" } },`, 2*maxTxnOps))))
   120  	req, _ := http.NewRequest("PUT", "/v1/txn", buf)
   121  	resp := httptest.NewRecorder()
   122  	if _, err := a.srv.Txn(resp, req); err != nil {
   123  		t.Fatalf("err: %v", err)
   124  	}
   125  	if resp.Code != 413 {
   126  		t.Fatalf("expected 413, got %d", resp.Code)
   127  	}
   128  }
   129  
   130  func TestTxnEndpoint_KV_Actions(t *testing.T) {
   131  	t.Parallel()
   132  	t.Run("", func(t *testing.T) {
   133  		a := NewTestAgent(t, t.Name(), "")
   134  		defer a.Shutdown()
   135  		testrpc.WaitForTestAgent(t, a.RPC, "dc1")
   136  
   137  		// Make sure all incoming fields get converted properly to the internal
   138  		// RPC format.
   139  		var index uint64
   140  		id := makeTestSession(t, a.srv)
   141  		{
   142  			buf := bytes.NewBuffer([]byte(fmt.Sprintf(`
   143   [
   144       {
   145           "KV": {
   146               "Verb": "lock",
   147               "Key": "key",
   148               "Value": "aGVsbG8gd29ybGQ=",
   149               "Flags": 23,
   150               "Session": %q
   151           }
   152       },
   153       {
   154           "KV": {
   155               "Verb": "get",
   156               "Key": "key"
   157           }
   158       }
   159   ]
   160   `, id)))
   161  			req, _ := http.NewRequest("PUT", "/v1/txn", buf)
   162  			resp := httptest.NewRecorder()
   163  			obj, err := a.srv.Txn(resp, req)
   164  			if err != nil {
   165  				t.Fatalf("err: %v", err)
   166  			}
   167  			if resp.Code != 200 {
   168  				t.Fatalf("expected 200, got %d", resp.Code)
   169  			}
   170  
   171  			txnResp, ok := obj.(structs.TxnResponse)
   172  			if !ok {
   173  				t.Fatalf("bad type: %T", obj)
   174  			}
   175  			if len(txnResp.Results) != 2 {
   176  				t.Fatalf("bad: %v", txnResp)
   177  			}
   178  			index = txnResp.Results[0].KV.ModifyIndex
   179  			expected := structs.TxnResponse{
   180  				Results: structs.TxnResults{
   181  					&structs.TxnResult{
   182  						KV: &structs.DirEntry{
   183  							Key:       "key",
   184  							Value:     nil,
   185  							Flags:     23,
   186  							Session:   id,
   187  							LockIndex: 1,
   188  							RaftIndex: structs.RaftIndex{
   189  								CreateIndex: index,
   190  								ModifyIndex: index,
   191  							},
   192  						},
   193  					},
   194  					&structs.TxnResult{
   195  						KV: &structs.DirEntry{
   196  							Key:       "key",
   197  							Value:     []byte("hello world"),
   198  							Flags:     23,
   199  							Session:   id,
   200  							LockIndex: 1,
   201  							RaftIndex: structs.RaftIndex{
   202  								CreateIndex: index,
   203  								ModifyIndex: index,
   204  							},
   205  						},
   206  					},
   207  				},
   208  			}
   209  			if !reflect.DeepEqual(txnResp, expected) {
   210  				t.Fatalf("bad: %v", txnResp)
   211  			}
   212  		}
   213  
   214  		// Do a read-only transaction that should get routed to the
   215  		// fast-path endpoint.
   216  		{
   217  			buf := bytes.NewBuffer([]byte(`
   218   [
   219       {
   220           "KV": {
   221               "Verb": "get",
   222               "Key": "key"
   223           }
   224       },
   225       {
   226           "KV": {
   227               "Verb": "get-tree",
   228               "Key": "key"
   229           }
   230       }
   231   ]
   232   `))
   233  			req, _ := http.NewRequest("PUT", "/v1/txn", buf)
   234  			resp := httptest.NewRecorder()
   235  			obj, err := a.srv.Txn(resp, req)
   236  			if err != nil {
   237  				t.Fatalf("err: %v", err)
   238  			}
   239  			if resp.Code != 200 {
   240  				t.Fatalf("expected 200, got %d", resp.Code)
   241  			}
   242  
   243  			header := resp.Header().Get("X-Consul-KnownLeader")
   244  			if header != "true" {
   245  				t.Fatalf("bad: %v", header)
   246  			}
   247  			header = resp.Header().Get("X-Consul-LastContact")
   248  			if header != "0" {
   249  				t.Fatalf("bad: %v", header)
   250  			}
   251  
   252  			txnResp, ok := obj.(structs.TxnReadResponse)
   253  			if !ok {
   254  				t.Fatalf("bad type: %T", obj)
   255  			}
   256  			expected := structs.TxnReadResponse{
   257  				TxnResponse: structs.TxnResponse{
   258  					Results: structs.TxnResults{
   259  						&structs.TxnResult{
   260  							KV: &structs.DirEntry{
   261  								Key:       "key",
   262  								Value:     []byte("hello world"),
   263  								Flags:     23,
   264  								Session:   id,
   265  								LockIndex: 1,
   266  								RaftIndex: structs.RaftIndex{
   267  									CreateIndex: index,
   268  									ModifyIndex: index,
   269  								},
   270  							},
   271  						},
   272  						&structs.TxnResult{
   273  							KV: &structs.DirEntry{
   274  								Key:       "key",
   275  								Value:     []byte("hello world"),
   276  								Flags:     23,
   277  								Session:   id,
   278  								LockIndex: 1,
   279  								RaftIndex: structs.RaftIndex{
   280  									CreateIndex: index,
   281  									ModifyIndex: index,
   282  								},
   283  							},
   284  						},
   285  					},
   286  				},
   287  				QueryMeta: structs.QueryMeta{
   288  					KnownLeader: true,
   289  				},
   290  			}
   291  			if !reflect.DeepEqual(txnResp, expected) {
   292  				t.Fatalf("bad: %v", txnResp)
   293  			}
   294  		}
   295  
   296  		// Now that we have an index we can do a CAS to make sure the
   297  		// index field gets translated to the RPC format.
   298  		{
   299  			buf := bytes.NewBuffer([]byte(fmt.Sprintf(`
   300   [
   301       {
   302           "KV": {
   303               "Verb": "cas",
   304               "Key": "key",
   305               "Value": "Z29vZGJ5ZSB3b3JsZA==",
   306               "Index": %d
   307           }
   308       },
   309       {
   310           "KV": {
   311               "Verb": "get",
   312               "Key": "key"
   313           }
   314       }
   315   ]
   316   `, index)))
   317  			req, _ := http.NewRequest("PUT", "/v1/txn", buf)
   318  			resp := httptest.NewRecorder()
   319  			obj, err := a.srv.Txn(resp, req)
   320  			if err != nil {
   321  				t.Fatalf("err: %v", err)
   322  			}
   323  			if resp.Code != 200 {
   324  				t.Fatalf("expected 200, got %d", resp.Code)
   325  			}
   326  
   327  			txnResp, ok := obj.(structs.TxnResponse)
   328  			if !ok {
   329  				t.Fatalf("bad type: %T", obj)
   330  			}
   331  			if len(txnResp.Results) != 2 {
   332  				t.Fatalf("bad: %v", txnResp)
   333  			}
   334  			modIndex := txnResp.Results[0].KV.ModifyIndex
   335  			expected := structs.TxnResponse{
   336  				Results: structs.TxnResults{
   337  					&structs.TxnResult{
   338  						KV: &structs.DirEntry{
   339  							Key:     "key",
   340  							Value:   nil,
   341  							Session: id,
   342  							RaftIndex: structs.RaftIndex{
   343  								CreateIndex: index,
   344  								ModifyIndex: modIndex,
   345  							},
   346  						},
   347  					},
   348  					&structs.TxnResult{
   349  						KV: &structs.DirEntry{
   350  							Key:     "key",
   351  							Value:   []byte("goodbye world"),
   352  							Session: id,
   353  							RaftIndex: structs.RaftIndex{
   354  								CreateIndex: index,
   355  								ModifyIndex: modIndex,
   356  							},
   357  						},
   358  					},
   359  				},
   360  			}
   361  			if !reflect.DeepEqual(txnResp, expected) {
   362  				t.Fatalf("bad: %v", txnResp)
   363  			}
   364  		}
   365  	})
   366  
   367  	// Verify an error inside a transaction.
   368  	t.Run("", func(t *testing.T) {
   369  		a := NewTestAgent(t, t.Name(), "")
   370  		defer a.Shutdown()
   371  
   372  		buf := bytes.NewBuffer([]byte(`
   373   [
   374       {
   375           "KV": {
   376               "Verb": "lock",
   377               "Key": "key",
   378               "Value": "aGVsbG8gd29ybGQ=",
   379               "Session": "nope"
   380           }
   381       },
   382       {
   383           "KV": {
   384               "Verb": "get",
   385               "Key": "key"
   386           }
   387       }
   388   ]
   389   `))
   390  		req, _ := http.NewRequest("PUT", "/v1/txn", buf)
   391  		resp := httptest.NewRecorder()
   392  		if _, err := a.srv.Txn(resp, req); err != nil {
   393  			t.Fatalf("err: %v", err)
   394  		}
   395  		if resp.Code != 409 {
   396  			t.Fatalf("expected 409, got %d", resp.Code)
   397  		}
   398  		if !bytes.Contains(resp.Body.Bytes(), []byte("failed session lookup")) {
   399  			t.Fatalf("bad: %s", resp.Body.String())
   400  		}
   401  	})
   402  }