Skip to content

Commit

Permalink
Renames "debug" endpoint and structures to "explain".
Browse files Browse the repository at this point in the history
  • Loading branch information
James Phillips committed Mar 4, 2016
1 parent 4ec826d commit 0a59971
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 58 deletions.
29 changes: 17 additions & 12 deletions command/agent/prepared_query_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
const (
preparedQueryEndpoint = "PreparedQuery"
preparedQueryExecuteSuffix = "/execute"
preparedQueryDebugSuffix = "/debug"
preparedQueryExplainSuffix = "/explain"
)

// preparedQueryCreateResponse is used to wrap the query ID.
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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)
}
Expand Down
27 changes: 16 additions & 11 deletions command/agent/prepared_query_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand Down
15 changes: 8 additions & 7 deletions consul/prepared_query_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}

Expand Down
42 changes: 21 additions & 21 deletions consul/prepared_query_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
{
Expand All @@ -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)
}
Expand All @@ -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"
{
Expand All @@ -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)
}
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
{
Expand All @@ -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)
}
Expand All @@ -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"
{
Expand All @@ -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)
}
Expand All @@ -1319,29 +1319,29 @@ 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",
QueryIDOrName: generateUUID(),
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)
}
Expand Down
4 changes: 2 additions & 2 deletions consul/structs/prepared_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 5 additions & 5 deletions website/source/docs/agent/http/query.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ The following endpoints are supported:
a prepared query
* [`/v1/query/<query or name>/execute`](#execute): Executes a
prepared query by its ID or optional name
* [`/v1/query/<query or name>/debug`](#debug): Debugs a
prepared query by its ID or optional name
* [`/v1/query/<query or name>/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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.

### <a name="debug"></a> /v1/query/\<query or name\>/debug
### <a name="explain"></a> /v1/query/\<query or name\>/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.
Expand Down

0 comments on commit 0a59971

Please sign in to comment.