github.com/kjdelisle/consul@v1.4.5/agent/prepared_query_endpoint_test.go (about) 1 package agent 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 "net/http/httptest" 9 "reflect" 10 "sync/atomic" 11 "testing" 12 13 "github.com/hashicorp/consul/testrpc" 14 15 "github.com/hashicorp/consul/agent/structs" 16 "github.com/hashicorp/consul/types" 17 "github.com/stretchr/testify/require" 18 ) 19 20 // MockPreparedQuery is a fake endpoint that we inject into the Consul server 21 // in order to observe the RPC calls made by these HTTP endpoints. This lets 22 // us make sure that the request is being formed properly without having to 23 // set up a realistic environment for prepared queries, which is a huge task and 24 // already done in detail inside the prepared query endpoint's unit tests. If we 25 // can prove this formats proper requests into that then we should be good to 26 // go. We will do a single set of end-to-end tests in here to make sure that the 27 // server is wired up to the right endpoint when not "injected". 28 type MockPreparedQuery struct { 29 applyFn func(*structs.PreparedQueryRequest, *string) error 30 getFn func(*structs.PreparedQuerySpecificRequest, *structs.IndexedPreparedQueries) error 31 listFn func(*structs.DCSpecificRequest, *structs.IndexedPreparedQueries) error 32 executeFn func(*structs.PreparedQueryExecuteRequest, *structs.PreparedQueryExecuteResponse) error 33 explainFn func(*structs.PreparedQueryExecuteRequest, *structs.PreparedQueryExplainResponse) error 34 } 35 36 func (m *MockPreparedQuery) Apply(args *structs.PreparedQueryRequest, 37 reply *string) (err error) { 38 if m.applyFn != nil { 39 return m.applyFn(args, reply) 40 } 41 return fmt.Errorf("should not have called Apply") 42 } 43 44 func (m *MockPreparedQuery) Get(args *structs.PreparedQuerySpecificRequest, 45 reply *structs.IndexedPreparedQueries) error { 46 if m.getFn != nil { 47 return m.getFn(args, reply) 48 } 49 return fmt.Errorf("should not have called Get") 50 } 51 52 func (m *MockPreparedQuery) List(args *structs.DCSpecificRequest, 53 reply *structs.IndexedPreparedQueries) error { 54 if m.listFn != nil { 55 return m.listFn(args, reply) 56 } 57 return fmt.Errorf("should not have called List") 58 } 59 60 func (m *MockPreparedQuery) Execute(args *structs.PreparedQueryExecuteRequest, 61 reply *structs.PreparedQueryExecuteResponse) error { 62 if m.executeFn != nil { 63 return m.executeFn(args, reply) 64 } 65 return fmt.Errorf("should not have called Execute") 66 } 67 68 func (m *MockPreparedQuery) Explain(args *structs.PreparedQueryExecuteRequest, 69 reply *structs.PreparedQueryExplainResponse) error { 70 if m.explainFn != nil { 71 return m.explainFn(args, reply) 72 } 73 return fmt.Errorf("should not have called Explain") 74 } 75 76 func TestPreparedQuery_Create(t *testing.T) { 77 t.Parallel() 78 a := NewTestAgent(t, t.Name(), "") 79 defer a.Shutdown() 80 81 m := MockPreparedQuery{ 82 applyFn: func(args *structs.PreparedQueryRequest, reply *string) error { 83 expected := &structs.PreparedQueryRequest{ 84 Datacenter: "dc1", 85 Op: structs.PreparedQueryCreate, 86 Query: &structs.PreparedQuery{ 87 Name: "my-query", 88 Session: "my-session", 89 Service: structs.ServiceQuery{ 90 Service: "my-service", 91 Failover: structs.QueryDatacenterOptions{ 92 NearestN: 4, 93 Datacenters: []string{"dc1", "dc2"}, 94 }, 95 IgnoreCheckIDs: []types.CheckID{"broken_check"}, 96 OnlyPassing: true, 97 Tags: []string{"foo", "bar"}, 98 NodeMeta: map[string]string{"somekey": "somevalue"}, 99 ServiceMeta: map[string]string{"env": "prod"}, 100 }, 101 DNS: structs.QueryDNSOptions{ 102 TTL: "10s", 103 }, 104 }, 105 WriteRequest: structs.WriteRequest{ 106 Token: "my-token", 107 }, 108 } 109 if !reflect.DeepEqual(args, expected) { 110 t.Fatalf("bad: %v", args) 111 } 112 113 *reply = "my-id" 114 return nil 115 }, 116 } 117 if err := a.registerEndpoint("PreparedQuery", &m); err != nil { 118 t.Fatalf("err: %v", err) 119 } 120 121 body := bytes.NewBuffer(nil) 122 enc := json.NewEncoder(body) 123 raw := map[string]interface{}{ 124 "Name": "my-query", 125 "Session": "my-session", 126 "Service": map[string]interface{}{ 127 "Service": "my-service", 128 "Failover": map[string]interface{}{ 129 "NearestN": 4, 130 "Datacenters": []string{"dc1", "dc2"}, 131 }, 132 "IgnoreCheckIDs": []string{"broken_check"}, 133 "OnlyPassing": true, 134 "Tags": []string{"foo", "bar"}, 135 "NodeMeta": map[string]string{"somekey": "somevalue"}, 136 "ServiceMeta": map[string]string{"env": "prod"}, 137 }, 138 "DNS": map[string]interface{}{ 139 "TTL": "10s", 140 }, 141 } 142 if err := enc.Encode(raw); err != nil { 143 t.Fatalf("err: %v", err) 144 } 145 146 req, _ := http.NewRequest("POST", "/v1/query?token=my-token", body) 147 resp := httptest.NewRecorder() 148 obj, err := a.srv.PreparedQueryGeneral(resp, req) 149 if err != nil { 150 t.Fatalf("err: %v", err) 151 } 152 if resp.Code != 200 { 153 t.Fatalf("bad code: %d", resp.Code) 154 } 155 r, ok := obj.(preparedQueryCreateResponse) 156 if !ok { 157 t.Fatalf("unexpected: %T", obj) 158 } 159 if r.ID != "my-id" { 160 t.Fatalf("bad ID: %s", r.ID) 161 } 162 } 163 164 func TestPreparedQuery_List(t *testing.T) { 165 t.Parallel() 166 t.Run("", func(t *testing.T) { 167 a := NewTestAgent(t, t.Name(), "") 168 defer a.Shutdown() 169 170 m := MockPreparedQuery{ 171 listFn: func(args *structs.DCSpecificRequest, reply *structs.IndexedPreparedQueries) error { 172 // Return an empty response. 173 return nil 174 }, 175 } 176 if err := a.registerEndpoint("PreparedQuery", &m); err != nil { 177 t.Fatalf("err: %v", err) 178 } 179 180 body := bytes.NewBuffer(nil) 181 req, _ := http.NewRequest("GET", "/v1/query", body) 182 resp := httptest.NewRecorder() 183 obj, err := a.srv.PreparedQueryGeneral(resp, req) 184 if err != nil { 185 t.Fatalf("err: %v", err) 186 } 187 if resp.Code != 200 { 188 t.Fatalf("bad code: %d", resp.Code) 189 } 190 r, ok := obj.(structs.PreparedQueries) 191 if !ok { 192 t.Fatalf("unexpected: %T", obj) 193 } 194 if r == nil || len(r) != 0 { 195 t.Fatalf("bad: %v", r) 196 } 197 }) 198 199 t.Run("", func(t *testing.T) { 200 a := NewTestAgent(t, t.Name(), "") 201 defer a.Shutdown() 202 203 m := MockPreparedQuery{ 204 listFn: func(args *structs.DCSpecificRequest, reply *structs.IndexedPreparedQueries) error { 205 expected := &structs.DCSpecificRequest{ 206 Datacenter: "dc1", 207 QueryOptions: structs.QueryOptions{ 208 Token: "my-token", 209 RequireConsistent: true, 210 }, 211 } 212 if !reflect.DeepEqual(args, expected) { 213 t.Fatalf("bad: %v", args) 214 } 215 216 query := &structs.PreparedQuery{ 217 ID: "my-id", 218 } 219 reply.Queries = append(reply.Queries, query) 220 return nil 221 }, 222 } 223 if err := a.registerEndpoint("PreparedQuery", &m); err != nil { 224 t.Fatalf("err: %v", err) 225 } 226 227 body := bytes.NewBuffer(nil) 228 req, _ := http.NewRequest("GET", "/v1/query?token=my-token&consistent=true", body) 229 resp := httptest.NewRecorder() 230 obj, err := a.srv.PreparedQueryGeneral(resp, req) 231 if err != nil { 232 t.Fatalf("err: %v", err) 233 } 234 if resp.Code != 200 { 235 t.Fatalf("bad code: %d", resp.Code) 236 } 237 r, ok := obj.(structs.PreparedQueries) 238 if !ok { 239 t.Fatalf("unexpected: %T", obj) 240 } 241 if len(r) != 1 || r[0].ID != "my-id" { 242 t.Fatalf("bad: %v", r) 243 } 244 }) 245 } 246 247 func TestPreparedQuery_Execute(t *testing.T) { 248 t.Parallel() 249 t.Run("", func(t *testing.T) { 250 a := NewTestAgent(t, t.Name(), "") 251 defer a.Shutdown() 252 253 m := MockPreparedQuery{ 254 executeFn: func(args *structs.PreparedQueryExecuteRequest, reply *structs.PreparedQueryExecuteResponse) error { 255 // Just return an empty response. 256 return nil 257 }, 258 } 259 if err := a.registerEndpoint("PreparedQuery", &m); err != nil { 260 t.Fatalf("err: %v", err) 261 } 262 263 body := bytes.NewBuffer(nil) 264 req, _ := http.NewRequest("GET", "/v1/query/my-id/execute", body) 265 resp := httptest.NewRecorder() 266 obj, err := a.srv.PreparedQuerySpecific(resp, req) 267 if err != nil { 268 t.Fatalf("err: %v", err) 269 } 270 if resp.Code != 200 { 271 t.Fatalf("bad code: %d", resp.Code) 272 } 273 r, ok := obj.(structs.PreparedQueryExecuteResponse) 274 if !ok { 275 t.Fatalf("unexpected: %T", obj) 276 } 277 if r.Nodes == nil || len(r.Nodes) != 0 { 278 t.Fatalf("bad: %v", r) 279 } 280 }) 281 282 t.Run("", func(t *testing.T) { 283 a := NewTestAgent(t, t.Name(), "") 284 defer a.Shutdown() 285 286 m := MockPreparedQuery{ 287 executeFn: func(args *structs.PreparedQueryExecuteRequest, reply *structs.PreparedQueryExecuteResponse) error { 288 expected := &structs.PreparedQueryExecuteRequest{ 289 Datacenter: "dc1", 290 QueryIDOrName: "my-id", 291 Limit: 5, 292 Source: structs.QuerySource{ 293 Datacenter: "dc1", 294 Node: "my-node", 295 }, 296 Agent: structs.QuerySource{ 297 Datacenter: a.Config.Datacenter, 298 Node: a.Config.NodeName, 299 }, 300 QueryOptions: structs.QueryOptions{ 301 Token: "my-token", 302 RequireConsistent: true, 303 }, 304 } 305 if !reflect.DeepEqual(args, expected) { 306 t.Fatalf("bad: %v", args) 307 } 308 309 // Just set something so we can tell this is returned. 310 reply.Failovers = 99 311 return nil 312 }, 313 } 314 if err := a.registerEndpoint("PreparedQuery", &m); err != nil { 315 t.Fatalf("err: %v", err) 316 } 317 318 body := bytes.NewBuffer(nil) 319 req, _ := http.NewRequest("GET", "/v1/query/my-id/execute?token=my-token&consistent=true&near=my-node&limit=5", body) 320 resp := httptest.NewRecorder() 321 obj, err := a.srv.PreparedQuerySpecific(resp, req) 322 if err != nil { 323 t.Fatalf("err: %v", err) 324 } 325 if resp.Code != 200 { 326 t.Fatalf("bad code: %d", resp.Code) 327 } 328 r, ok := obj.(structs.PreparedQueryExecuteResponse) 329 if !ok { 330 t.Fatalf("unexpected: %T", obj) 331 } 332 if r.Failovers != 99 { 333 t.Fatalf("bad: %v", r) 334 } 335 }) 336 337 t.Run("", func(t *testing.T) { 338 a := NewTestAgent(t, t.Name(), "") 339 defer a.Shutdown() 340 341 m := MockPreparedQuery{ 342 executeFn: func(args *structs.PreparedQueryExecuteRequest, reply *structs.PreparedQueryExecuteResponse) error { 343 expected := &structs.PreparedQueryExecuteRequest{ 344 Datacenter: "dc1", 345 QueryIDOrName: "my-id", 346 Limit: 5, 347 Source: structs.QuerySource{ 348 Datacenter: "dc1", 349 Node: "_ip", 350 Ip: "127.0.0.1", 351 }, 352 Agent: structs.QuerySource{ 353 Datacenter: a.Config.Datacenter, 354 Node: a.Config.NodeName, 355 }, 356 QueryOptions: structs.QueryOptions{ 357 Token: "my-token", 358 RequireConsistent: true, 359 }, 360 } 361 if !reflect.DeepEqual(args, expected) { 362 t.Fatalf("bad: %v", args) 363 } 364 365 // Just set something so we can tell this is returned. 366 reply.Failovers = 99 367 return nil 368 }, 369 } 370 if err := a.registerEndpoint("PreparedQuery", &m); err != nil { 371 t.Fatalf("err: %v", err) 372 } 373 374 body := bytes.NewBuffer(nil) 375 req, _ := http.NewRequest("GET", "/v1/query/my-id/execute?token=my-token&consistent=true&near=_ip&limit=5", body) 376 req.Header.Add("X-Forwarded-For", "127.0.0.1") 377 resp := httptest.NewRecorder() 378 obj, err := a.srv.PreparedQuerySpecific(resp, req) 379 if err != nil { 380 t.Fatalf("err: %v", err) 381 } 382 if resp.Code != 200 { 383 t.Fatalf("bad code: %d", resp.Code) 384 } 385 r, ok := obj.(structs.PreparedQueryExecuteResponse) 386 if !ok { 387 t.Fatalf("unexpected: %T", obj) 388 } 389 if r.Failovers != 99 { 390 t.Fatalf("bad: %v", r) 391 } 392 }) 393 394 t.Run("", func(t *testing.T) { 395 a := NewTestAgent(t, t.Name(), "") 396 defer a.Shutdown() 397 398 m := MockPreparedQuery{ 399 executeFn: func(args *structs.PreparedQueryExecuteRequest, reply *structs.PreparedQueryExecuteResponse) error { 400 expected := &structs.PreparedQueryExecuteRequest{ 401 Datacenter: "dc1", 402 QueryIDOrName: "my-id", 403 Limit: 5, 404 Source: structs.QuerySource{ 405 Datacenter: "dc1", 406 Node: "_ip", 407 Ip: "198.18.0.1", 408 }, 409 Agent: structs.QuerySource{ 410 Datacenter: a.Config.Datacenter, 411 Node: a.Config.NodeName, 412 }, 413 QueryOptions: structs.QueryOptions{ 414 Token: "my-token", 415 RequireConsistent: true, 416 }, 417 } 418 if !reflect.DeepEqual(args, expected) { 419 t.Fatalf("bad: %v", args) 420 } 421 422 // Just set something so we can tell this is returned. 423 reply.Failovers = 99 424 return nil 425 }, 426 } 427 if err := a.registerEndpoint("PreparedQuery", &m); err != nil { 428 t.Fatalf("err: %v", err) 429 } 430 431 body := bytes.NewBuffer(nil) 432 req, _ := http.NewRequest("GET", "/v1/query/my-id/execute?token=my-token&consistent=true&near=_ip&limit=5", body) 433 req.Header.Add("X-Forwarded-For", "198.18.0.1") 434 resp := httptest.NewRecorder() 435 obj, err := a.srv.PreparedQuerySpecific(resp, req) 436 if err != nil { 437 t.Fatalf("err: %v", err) 438 } 439 if resp.Code != 200 { 440 t.Fatalf("bad code: %d", resp.Code) 441 } 442 r, ok := obj.(structs.PreparedQueryExecuteResponse) 443 if !ok { 444 t.Fatalf("unexpected: %T", obj) 445 } 446 if r.Failovers != 99 { 447 t.Fatalf("bad: %v", r) 448 } 449 450 req, _ = http.NewRequest("GET", "/v1/query/my-id/execute?token=my-token&consistent=true&near=_ip&limit=5", body) 451 req.Header.Add("X-Forwarded-For", "198.18.0.1, 198.19.0.1") 452 resp = httptest.NewRecorder() 453 obj, err = a.srv.PreparedQuerySpecific(resp, req) 454 if err != nil { 455 t.Fatalf("err: %v", err) 456 } 457 if resp.Code != 200 { 458 t.Fatalf("bad code: %d", resp.Code) 459 } 460 r, ok = obj.(structs.PreparedQueryExecuteResponse) 461 if !ok { 462 t.Fatalf("unexpected: %T", obj) 463 } 464 if r.Failovers != 99 { 465 t.Fatalf("bad: %v", r) 466 } 467 }) 468 469 // Ensure the proper params are set when no special args are passed 470 t.Run("", func(t *testing.T) { 471 a := NewTestAgent(t, t.Name(), "") 472 defer a.Shutdown() 473 474 m := MockPreparedQuery{ 475 executeFn: func(args *structs.PreparedQueryExecuteRequest, reply *structs.PreparedQueryExecuteResponse) error { 476 if args.Source.Node != "" { 477 t.Fatalf("expect node to be empty, got %q", args.Source.Node) 478 } 479 expect := structs.QuerySource{ 480 Datacenter: a.Config.Datacenter, 481 Node: a.Config.NodeName, 482 } 483 if !reflect.DeepEqual(args.Agent, expect) { 484 t.Fatalf("expect: %#v\nactual: %#v", expect, args.Agent) 485 } 486 return nil 487 }, 488 } 489 if err := a.registerEndpoint("PreparedQuery", &m); err != nil { 490 t.Fatalf("err: %v", err) 491 } 492 493 req, _ := http.NewRequest("GET", "/v1/query/my-id/execute", nil) 494 resp := httptest.NewRecorder() 495 if _, err := a.srv.PreparedQuerySpecific(resp, req); err != nil { 496 t.Fatalf("err: %v", err) 497 } 498 }) 499 500 // Ensure WAN translation occurs for a response outside of the local DC. 501 t.Run("", func(t *testing.T) { 502 a := NewTestAgent(t, t.Name(), ` 503 datacenter = "dc1" 504 translate_wan_addrs = true 505 `) 506 defer a.Shutdown() 507 508 m := MockPreparedQuery{ 509 executeFn: func(args *structs.PreparedQueryExecuteRequest, reply *structs.PreparedQueryExecuteResponse) error { 510 nodesResponse := make(structs.CheckServiceNodes, 1) 511 nodesResponse[0].Node = &structs.Node{ 512 Node: "foo", Address: "127.0.0.1", 513 TaggedAddresses: map[string]string{ 514 "wan": "127.0.0.2", 515 }, 516 } 517 reply.Nodes = nodesResponse 518 reply.Datacenter = "dc2" 519 return nil 520 }, 521 } 522 if err := a.registerEndpoint("PreparedQuery", &m); err != nil { 523 t.Fatalf("err: %v", err) 524 } 525 526 body := bytes.NewBuffer(nil) 527 req, _ := http.NewRequest("GET", "/v1/query/my-id/execute?dc=dc2", body) 528 resp := httptest.NewRecorder() 529 obj, err := a.srv.PreparedQuerySpecific(resp, req) 530 if err != nil { 531 t.Fatalf("err: %v", err) 532 } 533 if resp.Code != 200 { 534 t.Fatalf("bad code: %d", resp.Code) 535 } 536 r, ok := obj.(structs.PreparedQueryExecuteResponse) 537 if !ok { 538 t.Fatalf("unexpected: %T", obj) 539 } 540 if r.Nodes == nil || len(r.Nodes) != 1 { 541 t.Fatalf("bad: %v", r) 542 } 543 544 node := r.Nodes[0] 545 if node.Node.Address != "127.0.0.2" { 546 t.Fatalf("bad: %v", node.Node) 547 } 548 }) 549 550 // Ensure WAN translation doesn't occur for the local DC. 551 t.Run("", func(t *testing.T) { 552 a := NewTestAgent(t, t.Name(), ` 553 datacenter = "dc1" 554 translate_wan_addrs = true 555 `) 556 defer a.Shutdown() 557 558 m := MockPreparedQuery{ 559 executeFn: func(args *structs.PreparedQueryExecuteRequest, reply *structs.PreparedQueryExecuteResponse) error { 560 nodesResponse := make(structs.CheckServiceNodes, 1) 561 nodesResponse[0].Node = &structs.Node{ 562 Node: "foo", Address: "127.0.0.1", 563 TaggedAddresses: map[string]string{ 564 "wan": "127.0.0.2", 565 }, 566 } 567 reply.Nodes = nodesResponse 568 reply.Datacenter = "dc1" 569 return nil 570 }, 571 } 572 if err := a.registerEndpoint("PreparedQuery", &m); err != nil { 573 t.Fatalf("err: %v", err) 574 } 575 576 body := bytes.NewBuffer(nil) 577 req, _ := http.NewRequest("GET", "/v1/query/my-id/execute?dc=dc2", body) 578 resp := httptest.NewRecorder() 579 obj, err := a.srv.PreparedQuerySpecific(resp, req) 580 if err != nil { 581 t.Fatalf("err: %v", err) 582 } 583 if resp.Code != 200 { 584 t.Fatalf("bad code: %d", resp.Code) 585 } 586 r, ok := obj.(structs.PreparedQueryExecuteResponse) 587 if !ok { 588 t.Fatalf("unexpected: %T", obj) 589 } 590 if r.Nodes == nil || len(r.Nodes) != 1 { 591 t.Fatalf("bad: %v", r) 592 } 593 594 node := r.Nodes[0] 595 if node.Node.Address != "127.0.0.1" { 596 t.Fatalf("bad: %v", node.Node) 597 } 598 }) 599 600 t.Run("", func(t *testing.T) { 601 a := NewTestAgent(t, t.Name(), "") 602 defer a.Shutdown() 603 604 body := bytes.NewBuffer(nil) 605 req, _ := http.NewRequest("GET", "/v1/query/not-there/execute", body) 606 resp := httptest.NewRecorder() 607 if _, err := a.srv.PreparedQuerySpecific(resp, req); err != nil { 608 t.Fatalf("err: %v", err) 609 } 610 if resp.Code != 404 { 611 t.Fatalf("bad code: %d", resp.Code) 612 } 613 }) 614 } 615 616 func TestPreparedQuery_ExecuteCached(t *testing.T) { 617 t.Parallel() 618 619 a := NewTestAgent(t, t.Name(), "") 620 defer a.Shutdown() 621 622 failovers := int32(99) 623 624 m := MockPreparedQuery{ 625 executeFn: func(args *structs.PreparedQueryExecuteRequest, reply *structs.PreparedQueryExecuteResponse) error { 626 // Just set something so we can tell this is returned. 627 reply.Failovers = int(atomic.LoadInt32(&failovers)) 628 return nil 629 }, 630 } 631 if err := a.registerEndpoint("PreparedQuery", &m); err != nil { 632 t.Fatalf("err: %v", err) 633 } 634 635 doRequest := func(expectFailovers int, expectCache string, revalidate bool) { 636 body := bytes.NewBuffer(nil) 637 req, _ := http.NewRequest("GET", "/v1/query/my-id/execute?cached", body) 638 639 if revalidate { 640 req.Header.Set("Cache-Control", "must-revalidate") 641 } 642 643 resp := httptest.NewRecorder() 644 obj, err := a.srv.PreparedQuerySpecific(resp, req) 645 646 require := require.New(t) 647 require.NoError(err) 648 require.Equal(200, resp.Code) 649 650 r, ok := obj.(structs.PreparedQueryExecuteResponse) 651 require.True(ok) 652 require.Equal(expectFailovers, r.Failovers) 653 654 require.Equal(expectCache, resp.Header().Get("X-Cache")) 655 } 656 657 // Should be a miss at first 658 doRequest(99, "MISS", false) 659 660 // Change the actual response 661 atomic.StoreInt32(&failovers, 66) 662 663 // Request again, should be a cache hit and have the cached (not current) 664 // value. 665 doRequest(99, "HIT", false) 666 667 // Request with max age that should invalidate cache. note that this will be 668 // sent as max-age=0 as that uses seconds but that should cause immediate 669 // invalidation rather than being ignored as an unset value. 670 doRequest(66, "MISS", true) 671 } 672 673 func TestPreparedQuery_Explain(t *testing.T) { 674 t.Parallel() 675 t.Run("", func(t *testing.T) { 676 a := NewTestAgent(t, t.Name(), "") 677 defer a.Shutdown() 678 679 m := MockPreparedQuery{ 680 explainFn: func(args *structs.PreparedQueryExecuteRequest, reply *structs.PreparedQueryExplainResponse) error { 681 expected := &structs.PreparedQueryExecuteRequest{ 682 Datacenter: "dc1", 683 QueryIDOrName: "my-id", 684 Limit: 5, 685 Source: structs.QuerySource{ 686 Datacenter: "dc1", 687 Node: "my-node", 688 }, 689 Agent: structs.QuerySource{ 690 Datacenter: a.Config.Datacenter, 691 Node: a.Config.NodeName, 692 }, 693 QueryOptions: structs.QueryOptions{ 694 Token: "my-token", 695 RequireConsistent: true, 696 }, 697 } 698 if !reflect.DeepEqual(args, expected) { 699 t.Fatalf("bad: %v", args) 700 } 701 702 // Just set something so we can tell this is returned. 703 reply.Query.Name = "hello" 704 return nil 705 }, 706 } 707 if err := a.registerEndpoint("PreparedQuery", &m); err != nil { 708 t.Fatalf("err: %v", err) 709 } 710 711 body := bytes.NewBuffer(nil) 712 req, _ := http.NewRequest("GET", "/v1/query/my-id/explain?token=my-token&consistent=true&near=my-node&limit=5", body) 713 resp := httptest.NewRecorder() 714 obj, err := a.srv.PreparedQuerySpecific(resp, req) 715 if err != nil { 716 t.Fatalf("err: %v", err) 717 } 718 if resp.Code != 200 { 719 t.Fatalf("bad code: %d", resp.Code) 720 } 721 r, ok := obj.(structs.PreparedQueryExplainResponse) 722 if !ok { 723 t.Fatalf("unexpected: %T", obj) 724 } 725 if r.Query.Name != "hello" { 726 t.Fatalf("bad: %v", r) 727 } 728 }) 729 730 t.Run("", func(t *testing.T) { 731 a := NewTestAgent(t, t.Name(), "") 732 defer a.Shutdown() 733 734 body := bytes.NewBuffer(nil) 735 req, _ := http.NewRequest("GET", "/v1/query/not-there/explain", body) 736 resp := httptest.NewRecorder() 737 if _, err := a.srv.PreparedQuerySpecific(resp, req); err != nil { 738 t.Fatalf("err: %v", err) 739 } 740 if resp.Code != 404 { 741 t.Fatalf("bad code: %d", resp.Code) 742 } 743 }) 744 745 // Ensure that Connect is passed through 746 t.Run("", func(t *testing.T) { 747 a := NewTestAgent(t, t.Name(), "") 748 defer a.Shutdown() 749 require := require.New(t) 750 751 m := MockPreparedQuery{ 752 executeFn: func(args *structs.PreparedQueryExecuteRequest, reply *structs.PreparedQueryExecuteResponse) error { 753 require.True(args.Connect) 754 return nil 755 }, 756 } 757 require.NoError(a.registerEndpoint("PreparedQuery", &m)) 758 759 body := bytes.NewBuffer(nil) 760 req, _ := http.NewRequest("GET", "/v1/query/my-id/execute?connect=true", body) 761 resp := httptest.NewRecorder() 762 _, err := a.srv.PreparedQuerySpecific(resp, req) 763 require.NoError(err) 764 require.Equal(200, resp.Code) 765 }) 766 } 767 768 func TestPreparedQuery_Get(t *testing.T) { 769 t.Parallel() 770 t.Run("", func(t *testing.T) { 771 a := NewTestAgent(t, t.Name(), "") 772 defer a.Shutdown() 773 774 m := MockPreparedQuery{ 775 getFn: func(args *structs.PreparedQuerySpecificRequest, reply *structs.IndexedPreparedQueries) error { 776 expected := &structs.PreparedQuerySpecificRequest{ 777 Datacenter: "dc1", 778 QueryID: "my-id", 779 QueryOptions: structs.QueryOptions{ 780 Token: "my-token", 781 RequireConsistent: true, 782 }, 783 } 784 if !reflect.DeepEqual(args, expected) { 785 t.Fatalf("bad: %v", args) 786 } 787 788 query := &structs.PreparedQuery{ 789 ID: "my-id", 790 } 791 reply.Queries = append(reply.Queries, query) 792 return nil 793 }, 794 } 795 if err := a.registerEndpoint("PreparedQuery", &m); err != nil { 796 t.Fatalf("err: %v", err) 797 } 798 799 body := bytes.NewBuffer(nil) 800 req, _ := http.NewRequest("GET", "/v1/query/my-id?token=my-token&consistent=true", body) 801 resp := httptest.NewRecorder() 802 obj, err := a.srv.PreparedQuerySpecific(resp, req) 803 if err != nil { 804 t.Fatalf("err: %v", err) 805 } 806 if resp.Code != 200 { 807 t.Fatalf("bad code: %d", resp.Code) 808 } 809 r, ok := obj.(structs.PreparedQueries) 810 if !ok { 811 t.Fatalf("unexpected: %T", obj) 812 } 813 if len(r) != 1 || r[0].ID != "my-id" { 814 t.Fatalf("bad: %v", r) 815 } 816 }) 817 818 t.Run("", func(t *testing.T) { 819 a := NewTestAgent(t, t.Name(), "") 820 defer a.Shutdown() 821 822 body := bytes.NewBuffer(nil) 823 req, _ := http.NewRequest("GET", "/v1/query/f004177f-2c28-83b7-4229-eacc25fe55d1", body) 824 resp := httptest.NewRecorder() 825 if _, err := a.srv.PreparedQuerySpecific(resp, req); err != nil { 826 t.Fatalf("err: %v", err) 827 } 828 if resp.Code != 404 { 829 t.Fatalf("bad code: %d", resp.Code) 830 } 831 }) 832 } 833 834 func TestPreparedQuery_Update(t *testing.T) { 835 t.Parallel() 836 a := NewTestAgent(t, t.Name(), "") 837 defer a.Shutdown() 838 839 m := MockPreparedQuery{ 840 applyFn: func(args *structs.PreparedQueryRequest, reply *string) error { 841 expected := &structs.PreparedQueryRequest{ 842 Datacenter: "dc1", 843 Op: structs.PreparedQueryUpdate, 844 Query: &structs.PreparedQuery{ 845 ID: "my-id", 846 Name: "my-query", 847 Session: "my-session", 848 Service: structs.ServiceQuery{ 849 Service: "my-service", 850 Failover: structs.QueryDatacenterOptions{ 851 NearestN: 4, 852 Datacenters: []string{"dc1", "dc2"}, 853 }, 854 OnlyPassing: true, 855 Tags: []string{"foo", "bar"}, 856 NodeMeta: map[string]string{"somekey": "somevalue"}, 857 }, 858 DNS: structs.QueryDNSOptions{ 859 TTL: "10s", 860 }, 861 }, 862 WriteRequest: structs.WriteRequest{ 863 Token: "my-token", 864 }, 865 } 866 if !reflect.DeepEqual(args, expected) { 867 t.Fatalf("bad: %v", args) 868 } 869 870 *reply = "don't care" 871 return nil 872 }, 873 } 874 if err := a.registerEndpoint("PreparedQuery", &m); err != nil { 875 t.Fatalf("err: %v", err) 876 } 877 878 body := bytes.NewBuffer(nil) 879 enc := json.NewEncoder(body) 880 raw := map[string]interface{}{ 881 "ID": "this should get ignored", 882 "Name": "my-query", 883 "Session": "my-session", 884 "Service": map[string]interface{}{ 885 "Service": "my-service", 886 "Failover": map[string]interface{}{ 887 "NearestN": 4, 888 "Datacenters": []string{"dc1", "dc2"}, 889 }, 890 "OnlyPassing": true, 891 "Tags": []string{"foo", "bar"}, 892 "NodeMeta": map[string]string{"somekey": "somevalue"}, 893 }, 894 "DNS": map[string]interface{}{ 895 "TTL": "10s", 896 }, 897 } 898 if err := enc.Encode(raw); err != nil { 899 t.Fatalf("err: %v", err) 900 } 901 902 req, _ := http.NewRequest("PUT", "/v1/query/my-id?token=my-token", body) 903 resp := httptest.NewRecorder() 904 if _, err := a.srv.PreparedQuerySpecific(resp, req); err != nil { 905 t.Fatalf("err: %v", err) 906 } 907 if resp.Code != 200 { 908 t.Fatalf("bad code: %d", resp.Code) 909 } 910 } 911 912 func TestPreparedQuery_Delete(t *testing.T) { 913 t.Parallel() 914 a := NewTestAgent(t, t.Name(), "") 915 defer a.Shutdown() 916 917 m := MockPreparedQuery{ 918 applyFn: func(args *structs.PreparedQueryRequest, reply *string) error { 919 expected := &structs.PreparedQueryRequest{ 920 Datacenter: "dc1", 921 Op: structs.PreparedQueryDelete, 922 Query: &structs.PreparedQuery{ 923 ID: "my-id", 924 }, 925 WriteRequest: structs.WriteRequest{ 926 Token: "my-token", 927 }, 928 } 929 if !reflect.DeepEqual(args, expected) { 930 t.Fatalf("bad: %v", args) 931 } 932 933 *reply = "don't care" 934 return nil 935 }, 936 } 937 if err := a.registerEndpoint("PreparedQuery", &m); err != nil { 938 t.Fatalf("err: %v", err) 939 } 940 941 body := bytes.NewBuffer(nil) 942 enc := json.NewEncoder(body) 943 raw := map[string]interface{}{ 944 "ID": "this should get ignored", 945 } 946 if err := enc.Encode(raw); err != nil { 947 t.Fatalf("err: %v", err) 948 } 949 950 req, _ := http.NewRequest("DELETE", "/v1/query/my-id?token=my-token", body) 951 resp := httptest.NewRecorder() 952 if _, err := a.srv.PreparedQuerySpecific(resp, req); err != nil { 953 t.Fatalf("err: %v", err) 954 } 955 if resp.Code != 200 { 956 t.Fatalf("bad code: %d", resp.Code) 957 } 958 } 959 960 func TestPreparedQuery_parseLimit(t *testing.T) { 961 t.Parallel() 962 body := bytes.NewBuffer(nil) 963 req, _ := http.NewRequest("GET", "/v1/query", body) 964 limit := 99 965 if err := parseLimit(req, &limit); err != nil { 966 t.Fatalf("err: %v", err) 967 } 968 if limit != 0 { 969 t.Fatalf("bad limit: %d", limit) 970 } 971 972 req, _ = http.NewRequest("GET", "/v1/query?limit=11", body) 973 if err := parseLimit(req, &limit); err != nil { 974 t.Fatalf("err: %v", err) 975 } 976 if limit != 11 { 977 t.Fatalf("bad limit: %d", limit) 978 } 979 980 req, _ = http.NewRequest("GET", "/v1/query?limit=bob", body) 981 if err := parseLimit(req, &limit); err == nil { 982 t.Fatalf("bad: %v", err) 983 } 984 } 985 986 // Since we've done exhaustive testing of the calls into the endpoints above 987 // this is just a basic end-to-end sanity check to make sure things are wired 988 // correctly when calling through to the real endpoints. 989 func TestPreparedQuery_Integration(t *testing.T) { 990 t.Parallel() 991 a := NewTestAgent(t, t.Name(), "") 992 defer a.Shutdown() 993 testrpc.WaitForTestAgent(t, a.RPC, "dc1") 994 995 // Register a node and a service. 996 { 997 args := &structs.RegisterRequest{ 998 Datacenter: "dc1", 999 Node: a.Config.NodeName, 1000 Address: "127.0.0.1", 1001 Service: &structs.NodeService{ 1002 Service: "my-service", 1003 }, 1004 } 1005 var out struct{} 1006 if err := a.RPC("Catalog.Register", args, &out); err != nil { 1007 t.Fatalf("err: %v", err) 1008 } 1009 } 1010 1011 // Create a query. 1012 var id string 1013 { 1014 body := bytes.NewBuffer(nil) 1015 enc := json.NewEncoder(body) 1016 raw := map[string]interface{}{ 1017 "Name": "my-query", 1018 "Service": map[string]interface{}{ 1019 "Service": "my-service", 1020 }, 1021 } 1022 if err := enc.Encode(raw); err != nil { 1023 t.Fatalf("err: %v", err) 1024 } 1025 1026 req, _ := http.NewRequest("POST", "/v1/query", body) 1027 resp := httptest.NewRecorder() 1028 obj, err := a.srv.PreparedQueryGeneral(resp, req) 1029 if err != nil { 1030 t.Fatalf("err: %v", err) 1031 } 1032 if resp.Code != 200 { 1033 t.Fatalf("bad code: %d", resp.Code) 1034 } 1035 r, ok := obj.(preparedQueryCreateResponse) 1036 if !ok { 1037 t.Fatalf("unexpected: %T", obj) 1038 } 1039 id = r.ID 1040 } 1041 1042 // List them all. 1043 { 1044 body := bytes.NewBuffer(nil) 1045 req, _ := http.NewRequest("GET", "/v1/query?token=root", body) 1046 resp := httptest.NewRecorder() 1047 obj, err := a.srv.PreparedQueryGeneral(resp, req) 1048 if err != nil { 1049 t.Fatalf("err: %v", err) 1050 } 1051 if resp.Code != 200 { 1052 t.Fatalf("bad code: %d", resp.Code) 1053 } 1054 r, ok := obj.(structs.PreparedQueries) 1055 if !ok { 1056 t.Fatalf("unexpected: %T", obj) 1057 } 1058 if len(r) != 1 { 1059 t.Fatalf("bad: %v", r) 1060 } 1061 } 1062 1063 // Execute it. 1064 { 1065 body := bytes.NewBuffer(nil) 1066 req, _ := http.NewRequest("GET", "/v1/query/"+id+"/execute", body) 1067 resp := httptest.NewRecorder() 1068 obj, err := a.srv.PreparedQuerySpecific(resp, req) 1069 if err != nil { 1070 t.Fatalf("err: %v", err) 1071 } 1072 if resp.Code != 200 { 1073 t.Fatalf("bad code: %d", resp.Code) 1074 } 1075 r, ok := obj.(structs.PreparedQueryExecuteResponse) 1076 if !ok { 1077 t.Fatalf("unexpected: %T", obj) 1078 } 1079 if len(r.Nodes) != 1 { 1080 t.Fatalf("bad: %v", r) 1081 } 1082 } 1083 1084 // Read it back. 1085 { 1086 body := bytes.NewBuffer(nil) 1087 req, _ := http.NewRequest("GET", "/v1/query/"+id, body) 1088 resp := httptest.NewRecorder() 1089 obj, err := a.srv.PreparedQuerySpecific(resp, req) 1090 if err != nil { 1091 t.Fatalf("err: %v", err) 1092 } 1093 if resp.Code != 200 { 1094 t.Fatalf("bad code: %d", resp.Code) 1095 } 1096 r, ok := obj.(structs.PreparedQueries) 1097 if !ok { 1098 t.Fatalf("unexpected: %T", obj) 1099 } 1100 if len(r) != 1 { 1101 t.Fatalf("bad: %v", r) 1102 } 1103 } 1104 1105 // Make an update to it. 1106 { 1107 body := bytes.NewBuffer(nil) 1108 enc := json.NewEncoder(body) 1109 raw := map[string]interface{}{ 1110 "Name": "my-query", 1111 "Service": map[string]interface{}{ 1112 "Service": "my-service", 1113 "OnlyPassing": true, 1114 }, 1115 } 1116 if err := enc.Encode(raw); err != nil { 1117 t.Fatalf("err: %v", err) 1118 } 1119 1120 req, _ := http.NewRequest("PUT", "/v1/query/"+id, body) 1121 resp := httptest.NewRecorder() 1122 if _, err := a.srv.PreparedQuerySpecific(resp, req); err != nil { 1123 t.Fatalf("err: %v", err) 1124 } 1125 if resp.Code != 200 { 1126 t.Fatalf("bad code: %d", resp.Code) 1127 } 1128 } 1129 1130 // Delete it. 1131 { 1132 body := bytes.NewBuffer(nil) 1133 req, _ := http.NewRequest("DELETE", "/v1/query/"+id, body) 1134 resp := httptest.NewRecorder() 1135 if _, err := a.srv.PreparedQuerySpecific(resp, req); err != nil { 1136 t.Fatalf("err: %v", err) 1137 } 1138 if resp.Code != 200 { 1139 t.Fatalf("bad code: %d", resp.Code) 1140 } 1141 } 1142 }