Renames "debug" endpoint and structures to "explain".

This commit is contained in:
James Phillips 2016-03-03 17:30:36 -08:00
parent 8493640b09
commit 275c84a0cc
6 changed files with 69 additions and 58 deletions

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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

View File

@ -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.
@ -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.
### <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.