github.com/outbrain/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  }