diff --git a/command/agent/prepared_query_endpoint.go b/command/agent/prepared_query_endpoint.go index 3e44cfa70afc..bf643f7c26af 100644 --- a/command/agent/prepared_query_endpoint.go +++ b/command/agent/prepared_query_endpoint.go @@ -13,7 +13,7 @@ import ( const ( preparedQueryEndpoint = "PreparedQuery" preparedQueryExecuteSuffix = "/execute" - preparedQueryDebugSuffix = "/debug" + preparedQueryExplainSuffix = "/explain" ) // preparedQueryCreateResponse is used to wrap the query ID. @@ -125,19 +125,24 @@ func (s *HTTPServer) preparedQueryExecute(id string, resp http.ResponseWriter, r return reply, nil } -// preparedQueryDebug shows what a given name resolves to, which is useful for -// operators in a world with templates. -func (s *HTTPServer) preparedQueryDebug(id string, resp http.ResponseWriter, req *http.Request) (interface{}, error) { +// preparedQueryExplain shows which query a name resolves to, the fully +// interpolated template (if it's a template), as well as additional info +// about the execution of a query. +func (s *HTTPServer) preparedQueryExplain(id string, resp http.ResponseWriter, req *http.Request) (interface{}, error) { args := structs.PreparedQueryExecuteRequest{ QueryIDOrName: id, } + s.parseSource(req, &args.Source) if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done { return nil, nil } + if err := parseLimit(req, &args.Limit); err != nil { + return nil, fmt.Errorf("Bad limit: %s", err) + } - var reply structs.PreparedQueryDebugResponse + var reply structs.PreparedQueryExplainResponse endpoint := s.agent.getEndpoint(preparedQueryEndpoint) - if err := s.agent.RPC(endpoint+".Debug", &args, &reply); err != nil { + if err := s.agent.RPC(endpoint+".Explain", &args, &reply); err != nil { // We have to check the string since the RPC sheds // the specific error type. if err.Error() == consul.ErrQueryNotFound.Error() { @@ -224,21 +229,21 @@ func (s *HTTPServer) preparedQueryDelete(id string, resp http.ResponseWriter, re func (s *HTTPServer) PreparedQuerySpecific(resp http.ResponseWriter, req *http.Request) (interface{}, error) { id := strings.TrimPrefix(req.URL.Path, "/v1/query/") - execute, debug := false, false + execute, explain := false, false if strings.HasSuffix(id, preparedQueryExecuteSuffix) { execute = true id = strings.TrimSuffix(id, preparedQueryExecuteSuffix) - } else if strings.HasSuffix(id, preparedQueryDebugSuffix) { - debug = true - id = strings.TrimSuffix(id, preparedQueryDebugSuffix) + } else if strings.HasSuffix(id, preparedQueryExplainSuffix) { + explain = true + id = strings.TrimSuffix(id, preparedQueryExplainSuffix) } switch req.Method { case "GET": if execute { return s.preparedQueryExecute(id, resp, req) - } else if debug { - return s.preparedQueryDebug(id, resp, req) + } else if explain { + return s.preparedQueryExplain(id, resp, req) } else { return s.preparedQueryGet(id, resp, req) } diff --git a/command/agent/prepared_query_endpoint_test.go b/command/agent/prepared_query_endpoint_test.go index 487f2b6c46d2..8997de05f321 100644 --- a/command/agent/prepared_query_endpoint_test.go +++ b/command/agent/prepared_query_endpoint_test.go @@ -25,7 +25,7 @@ type MockPreparedQuery struct { getFn func(*structs.PreparedQuerySpecificRequest, *structs.IndexedPreparedQueries) error listFn func(*structs.DCSpecificRequest, *structs.IndexedPreparedQueries) error executeFn func(*structs.PreparedQueryExecuteRequest, *structs.PreparedQueryExecuteResponse) error - debugFn func(*structs.PreparedQueryExecuteRequest, *structs.PreparedQueryDebugResponse) error + explainFn func(*structs.PreparedQueryExecuteRequest, *structs.PreparedQueryExplainResponse) error } func (m *MockPreparedQuery) Apply(args *structs.PreparedQueryRequest, @@ -60,12 +60,12 @@ func (m *MockPreparedQuery) Execute(args *structs.PreparedQueryExecuteRequest, return fmt.Errorf("should not have called Execute") } -func (m *MockPreparedQuery) Debug(args *structs.PreparedQueryExecuteRequest, - reply *structs.PreparedQueryDebugResponse) error { - if m.debugFn != nil { - return m.debugFn(args, reply) +func (m *MockPreparedQuery) Explain(args *structs.PreparedQueryExecuteRequest, + reply *structs.PreparedQueryExplainResponse) error { + if m.explainFn != nil { + return m.explainFn(args, reply) } - return fmt.Errorf("should not have called Debug") + return fmt.Errorf("should not have called Explain") } func TestPreparedQuery_Create(t *testing.T) { @@ -341,17 +341,22 @@ func TestPreparedQuery_Execute(t *testing.T) { }) } -func TestPreparedQuery_Debug(t *testing.T) { +func TestPreparedQuery_Explain(t *testing.T) { httpTest(t, func(srv *HTTPServer) { m := MockPreparedQuery{} if err := srv.agent.InjectEndpoint("PreparedQuery", &m); err != nil { t.Fatalf("err: %v", err) } - m.debugFn = func(args *structs.PreparedQueryExecuteRequest, reply *structs.PreparedQueryDebugResponse) error { + m.explainFn = func(args *structs.PreparedQueryExecuteRequest, reply *structs.PreparedQueryExplainResponse) error { expected := &structs.PreparedQueryExecuteRequest{ Datacenter: "dc1", QueryIDOrName: "my-id", + Limit: 5, + Source: structs.QuerySource{ + Datacenter: "dc1", + Node: "my-node", + }, QueryOptions: structs.QueryOptions{ Token: "my-token", RequireConsistent: true, @@ -367,7 +372,7 @@ func TestPreparedQuery_Debug(t *testing.T) { } body := bytes.NewBuffer(nil) - req, err := http.NewRequest("GET", "/v1/query/my-id/debug?token=my-token&consistent=true", body) + req, err := http.NewRequest("GET", "/v1/query/my-id/explain?token=my-token&consistent=true&near=my-node&limit=5", body) if err != nil { t.Fatalf("err: %v", err) } @@ -380,7 +385,7 @@ func TestPreparedQuery_Debug(t *testing.T) { if resp.Code != 200 { t.Fatalf("bad code: %d", resp.Code) } - r, ok := obj.(structs.PreparedQueryDebugResponse) + r, ok := obj.(structs.PreparedQueryExplainResponse) if !ok { t.Fatalf("unexpected: %T", obj) } @@ -391,7 +396,7 @@ func TestPreparedQuery_Debug(t *testing.T) { httpTest(t, func(srv *HTTPServer) { body := bytes.NewBuffer(nil) - req, err := http.NewRequest("GET", "/v1/query/not-there/debug", body) + req, err := http.NewRequest("GET", "/v1/query/not-there/explain", body) if err != nil { t.Fatalf("err: %v", err) } diff --git a/consul/prepared_query_endpoint.go b/consul/prepared_query_endpoint.go index 5fcbdbbf7ef5..d30b6c10dd22 100644 --- a/consul/prepared_query_endpoint.go +++ b/consul/prepared_query_endpoint.go @@ -269,15 +269,16 @@ func (p *PreparedQuery) List(args *structs.DCSpecificRequest, reply *structs.Ind }) } -// Debug resolves a prepared query and returns the (possibly rendered template) +// Explain resolves a prepared query and returns the (possibly rendered template) // to the caller. This is useful for letting operators figure out which query is -// picking up a given name. -func (p *PreparedQuery) Debug(args *structs.PreparedQueryExecuteRequest, - reply *structs.PreparedQueryDebugResponse) error { - if done, err := p.srv.forward("PreparedQuery.Debug", args, args, reply); done { +// picking up a given name. We can also add additional info about how the query +// will be executed here. +func (p *PreparedQuery) Explain(args *structs.PreparedQueryExecuteRequest, + reply *structs.PreparedQueryExplainResponse) error { + if done, err := p.srv.forward("PreparedQuery.Explain", args, args, reply); done { return err } - defer metrics.MeasureSince([]string{"consul", "prepared-query", "debug"}, time.Now()) + defer metrics.MeasureSince([]string{"consul", "prepared-query", "explain"}, time.Now()) // We have to do this ourselves since we are not doing a blocking RPC. p.srv.setQueryMeta(&reply.QueryMeta) @@ -308,7 +309,7 @@ func (p *PreparedQuery) Debug(args *structs.PreparedQueryExecuteRequest, // If the query was filtered out, return an error. if len(queries.Queries) == 0 { - p.srv.logger.Printf("[WARN] consul.prepared_query: Debug on prepared query '%s' denied due to ACLs", query.ID) + p.srv.logger.Printf("[WARN] consul.prepared_query: Explain on prepared query '%s' denied due to ACLs", query.ID) return permissionDeniedErr } diff --git a/consul/prepared_query_endpoint_test.go b/consul/prepared_query_endpoint_test.go index 1ad05fb634c1..6569442558c1 100644 --- a/consul/prepared_query_endpoint_test.go +++ b/consul/prepared_query_endpoint_test.go @@ -734,20 +734,20 @@ func TestPreparedQuery_ACLDeny_Catchall_Template(t *testing.T) { } } - // Debugging should also be denied without a token. + // Explaining should also be denied without a token. { req := &structs.PreparedQueryExecuteRequest{ Datacenter: "dc1", QueryIDOrName: "anything", } - var resp structs.PreparedQueryDebugResponse - err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Debug", req, &resp) + var resp structs.PreparedQueryExplainResponse + err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Explain", req, &resp) if err == nil || !strings.Contains(err.Error(), permissionDenied) { t.Fatalf("bad: %v", err) } } - // The user can debug and see the redacted token. + // The user can explain and see the redacted token. query.Query.Token = redactedToken query.Query.Service.Service = "anything" { @@ -756,8 +756,8 @@ func TestPreparedQuery_ACLDeny_Catchall_Template(t *testing.T) { QueryIDOrName: "anything", QueryOptions: structs.QueryOptions{Token: token}, } - var resp structs.PreparedQueryDebugResponse - err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Debug", req, &resp) + var resp structs.PreparedQueryExplainResponse + err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Explain", req, &resp) if err != nil { t.Fatalf("err: %v", err) } @@ -769,7 +769,7 @@ func TestPreparedQuery_ACLDeny_Catchall_Template(t *testing.T) { } } - // Make sure the management token can also debug and see the token. + // Make sure the management token can also explain and see the token. query.Query.Token = "5e1e24e5-1329-f86f-18c6-3d3734edb2cd" query.Query.Service.Service = "anything" { @@ -778,8 +778,8 @@ func TestPreparedQuery_ACLDeny_Catchall_Template(t *testing.T) { QueryIDOrName: "anything", QueryOptions: structs.QueryOptions{Token: "root"}, } - var resp structs.PreparedQueryDebugResponse - err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Debug", req, &resp) + var resp structs.PreparedQueryExplainResponse + err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Explain", req, &resp) if err != nil { t.Fatalf("err: %v", err) } @@ -1217,7 +1217,7 @@ func TestPreparedQuery_List(t *testing.T) { } } -func TestPreparedQuery_Debug(t *testing.T) { +func TestPreparedQuery_Explain(t *testing.T) { dir1, s1 := testServerWithConfig(t, func(c *Config) { c.ACLDatacenter = "dc1" c.ACLMasterToken = "root" @@ -1275,7 +1275,7 @@ func TestPreparedQuery_Debug(t *testing.T) { t.Fatalf("err: %v", err) } - // Debug via the management token. + // Explain via the management token. query.Query.ID = reply query.Query.Service.Service = "prod-redis" { @@ -1284,8 +1284,8 @@ func TestPreparedQuery_Debug(t *testing.T) { QueryIDOrName: "prod-redis", QueryOptions: structs.QueryOptions{Token: "root"}, } - var resp structs.PreparedQueryDebugResponse - err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Debug", req, &resp) + var resp structs.PreparedQueryExplainResponse + err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Explain", req, &resp) if err != nil { t.Fatalf("err: %v", err) } @@ -1297,7 +1297,7 @@ func TestPreparedQuery_Debug(t *testing.T) { } } - // Debug via the user token, which will redact the captured token. + // Explain via the user token, which will redact the captured token. query.Query.Token = redactedToken query.Query.Service.Service = "prod-redis" { @@ -1306,8 +1306,8 @@ func TestPreparedQuery_Debug(t *testing.T) { QueryIDOrName: "prod-redis", QueryOptions: structs.QueryOptions{Token: token}, } - var resp structs.PreparedQueryDebugResponse - err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Debug", req, &resp) + var resp structs.PreparedQueryExplainResponse + err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Explain", req, &resp) if err != nil { t.Fatalf("err: %v", err) } @@ -1319,21 +1319,21 @@ func TestPreparedQuery_Debug(t *testing.T) { } } - // Debugging should be denied without a token, since the user isn't + // Explaining should be denied without a token, since the user isn't // allowed to see the query. { req := &structs.PreparedQueryExecuteRequest{ Datacenter: "dc1", QueryIDOrName: "prod-redis", } - var resp structs.PreparedQueryDebugResponse - err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Debug", req, &resp) + var resp structs.PreparedQueryExplainResponse + err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Explain", req, &resp) if err == nil || !strings.Contains(err.Error(), permissionDenied) { t.Fatalf("bad: %v", err) } } - // Try to debug a bogus ID. + // Try to explain a bogus ID. { req := &structs.PreparedQueryExecuteRequest{ Datacenter: "dc1", @@ -1341,7 +1341,7 @@ func TestPreparedQuery_Debug(t *testing.T) { QueryOptions: structs.QueryOptions{Token: "root"}, } var resp structs.IndexedPreparedQueries - if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Debug", req, &resp); err != nil { + if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Explain", req, &resp); err != nil { if err.Error() != ErrQueryNotFound.Error() { t.Fatalf("err: %v", err) } diff --git a/consul/structs/prepared_query.go b/consul/structs/prepared_query.go index dad94c5f0a06..b1b20c9ed361 100644 --- a/consul/structs/prepared_query.go +++ b/consul/structs/prepared_query.go @@ -232,8 +232,8 @@ type PreparedQueryExecuteResponse struct { QueryMeta } -// PreparedQueryDebugResponse has the results when debugging a query. -type PreparedQueryDebugResponse struct { +// PreparedQueryExplainResponse has the results when explaining a query/ +type PreparedQueryExplainResponse struct { // Query has the fully-rendered query. Query PreparedQuery diff --git a/website/source/docs/agent/http/query.html.markdown b/website/source/docs/agent/http/query.html.markdown index 21336afee6eb..f4f66a139562 100644 --- a/website/source/docs/agent/http/query.html.markdown +++ b/website/source/docs/agent/http/query.html.markdown @@ -34,8 +34,8 @@ The following endpoints are supported: a prepared query * [`/v1/query//execute`](#execute): Executes a prepared query by its ID or optional name -* [`/v1/query//debug`](#debug): Debugs a - prepared query by its ID or optional name +* [`/v1/query//explain`](#explain): Provides information about + how a prepared query will be executed by its ID or optional name Not all endpoints support blocking queries and all consistency modes, see details in the sections below. @@ -231,7 +231,7 @@ above with a `Regexp` field set to `^geo-db-(.*?)-([^\-]+?)$` would return "master" for `${match(2)}`. If the regular expression doesn't match, or an invalid index is given, then `${match(N)}` will return an empty string. -See the [query debug](#debug) endpoint which is useful for testing interpolations +See the [query explain](#explain) endpoint which is useful for testing interpolations and determining which query is handling a given name. Using templates it's possible to apply prepared query behaviors to many services @@ -439,9 +439,9 @@ while executing the query. This provides some insight into where the data came from. This will be zero during non-failover operations where there were healthy nodes found in the local datacenter. -### /v1/query/\/debug +### /v1/query/\/explain -The query debug endpoint supports only the `GET` method and is used to see +The query explain endpoint supports only the `GET` method and is used to see a fully-rendered query for a given name. This is especially useful for finding which [prepared query template](#templates) matches a given name, and what the final query looks like after interpolation.