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 }