mirror of https://github.com/hashicorp/consul
James Phillips
9 years ago
2 changed files with 342 additions and 0 deletions
@ -0,0 +1,219 @@
|
||||
package api |
||||
|
||||
import () |
||||
|
||||
// QueryDatacenterOptions sets options about how we fail over if there are no
|
||||
// healthy nodes in the local datacenter.
|
||||
type QueryDatacenterOptions struct { |
||||
// NearestN is set to the number of remote datacenters to try, based on
|
||||
// network coordinates.
|
||||
NearestN int |
||||
|
||||
// Datacenters is a fixed list of datacenters to try after NearestN. We
|
||||
// never try a datacenter multiple times, so those are subtracted from
|
||||
// this list before proceeding.
|
||||
Datacenters []string |
||||
} |
||||
|
||||
// QueryDNSOptions controls settings when query results are served over DNS.
|
||||
type QueryDNSOptions struct { |
||||
// TTL is the time to live for the served DNS results.
|
||||
TTL string |
||||
} |
||||
|
||||
// ServiceQuery is used to query for a set of healthy nodes offering a specific
|
||||
// service.
|
||||
type ServiceQuery struct { |
||||
// Service is the service to query.
|
||||
Service string |
||||
|
||||
// Failover controls what we do if there are no healthy nodes in the
|
||||
// local datacenter.
|
||||
Failover QueryDatacenterOptions |
||||
|
||||
// If OnlyPassing is true then we will only include nodes with passing
|
||||
// health checks (critical AND warning checks will cause a node to be
|
||||
// discarded)
|
||||
OnlyPassing bool |
||||
|
||||
// Tags are a set of required and/or disallowed tags. If a tag is in
|
||||
// this list it must be present. If the tag is preceded with "~" then
|
||||
// it is disallowed.
|
||||
Tags []string |
||||
} |
||||
|
||||
// PrepatedQueryDefinition defines a complete prepared query.
|
||||
type PreparedQueryDefinition struct { |
||||
// ID is this UUID-based ID for the query, always generated by Consul.
|
||||
ID string |
||||
|
||||
// Name is an optional friendly name for the query supplied by the
|
||||
// user. NOTE - if this feature is used then it will reduce the security
|
||||
// of any read ACL associated with this query/service since this name
|
||||
// can be used to locate nodes with supplying any ACL.
|
||||
Name string |
||||
|
||||
// Session is an optional session to tie this query's lifetime to. If
|
||||
// this is omitted then the query will not expire.
|
||||
Session string |
||||
|
||||
// Token is the ACL token used when the query was created, and it is
|
||||
// used when a query is subsequently executed. This token, or a token
|
||||
// with management privileges, must be used to change the query later.
|
||||
Token string |
||||
|
||||
// Service defines a service query (leaving things open for other types
|
||||
// later).
|
||||
Service ServiceQuery |
||||
|
||||
// DNS has options that control how the results of this query are
|
||||
// served over DNS.
|
||||
DNS QueryDNSOptions |
||||
} |
||||
|
||||
// PreparedQueryExecuteResponse has the results of executing a query.
|
||||
type PreparedQueryExecuteResponse struct { |
||||
// Service is the service that was queried.
|
||||
Service string |
||||
|
||||
// Nodes has the nodes that were output by the query.
|
||||
Nodes []ServiceEntry |
||||
|
||||
// DNS has the options for serving these results over DNS.
|
||||
DNS QueryDNSOptions |
||||
|
||||
// Datacenter is the datacenter that these results came from.
|
||||
Datacenter string |
||||
|
||||
// Failovers is a count of how many times we had to query a remote
|
||||
// datacenter.
|
||||
Failovers int |
||||
} |
||||
|
||||
// PreparedQuery can be used to query the prepared query endpoints.
|
||||
type PreparedQuery struct { |
||||
c *Client |
||||
} |
||||
|
||||
// PreparedQuery returns a handle to the prepared query endpoints.
|
||||
func (c *Client) PreparedQuery() *PreparedQuery { |
||||
return &PreparedQuery{c} |
||||
} |
||||
|
||||
// Create makes a new prepared query. The ID of the new query is returned.
|
||||
func (c *PreparedQuery) Create(query *PreparedQueryDefinition, q *WriteOptions) (string, *WriteMeta, error) { |
||||
r := c.c.newRequest("POST", "/v1/query") |
||||
r.setWriteOptions(q) |
||||
r.obj = query |
||||
rtt, resp, err := requireOK(c.c.doRequest(r)) |
||||
if err != nil { |
||||
return "", nil, err |
||||
} |
||||
defer resp.Body.Close() |
||||
|
||||
wm := &WriteMeta{} |
||||
wm.RequestTime = rtt |
||||
|
||||
var out struct{ ID string } |
||||
if err := decodeBody(resp, &out); err != nil { |
||||
return "", nil, err |
||||
} |
||||
return out.ID, wm, nil |
||||
} |
||||
|
||||
// Update makes updates to an existing prepared query.
|
||||
func (c *PreparedQuery) Update(query *PreparedQueryDefinition, q *WriteOptions) (*WriteMeta, error) { |
||||
r := c.c.newRequest("PUT", "/v1/query/"+query.ID) |
||||
r.setWriteOptions(q) |
||||
r.obj = query |
||||
rtt, resp, err := requireOK(c.c.doRequest(r)) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
resp.Body.Close() |
||||
|
||||
wm := &WriteMeta{} |
||||
wm.RequestTime = rtt |
||||
return wm, nil |
||||
} |
||||
|
||||
// List is used to fetch all the prepared queries (always requires a management
|
||||
// token).
|
||||
func (c *PreparedQuery) List(q *QueryOptions) ([]*PreparedQueryDefinition, *QueryMeta, error) { |
||||
r := c.c.newRequest("GET", "/v1/query") |
||||
r.setQueryOptions(q) |
||||
rtt, resp, err := requireOK(c.c.doRequest(r)) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
defer resp.Body.Close() |
||||
|
||||
qm := &QueryMeta{} |
||||
parseQueryMeta(resp, qm) |
||||
qm.RequestTime = rtt |
||||
|
||||
var out []*PreparedQueryDefinition |
||||
if err := decodeBody(resp, &out); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
return out, qm, nil |
||||
} |
||||
|
||||
// Get is used to fetch a specific prepared query.
|
||||
func (c *PreparedQuery) Get(queryID string, q *QueryOptions) ([]*PreparedQueryDefinition, *QueryMeta, error) { |
||||
r := c.c.newRequest("GET", "/v1/query/"+queryID) |
||||
r.setQueryOptions(q) |
||||
rtt, resp, err := requireOK(c.c.doRequest(r)) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
defer resp.Body.Close() |
||||
|
||||
qm := &QueryMeta{} |
||||
parseQueryMeta(resp, qm) |
||||
qm.RequestTime = rtt |
||||
|
||||
var out []*PreparedQueryDefinition |
||||
if err := decodeBody(resp, &out); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
return out, qm, nil |
||||
} |
||||
|
||||
// Delete is used to delete a specific prepared query.
|
||||
func (c *PreparedQuery) Delete(queryID string, q *QueryOptions) (*QueryMeta, error) { |
||||
r := c.c.newRequest("DELETE", "/v1/query/"+queryID) |
||||
r.setQueryOptions(q) |
||||
rtt, resp, err := requireOK(c.c.doRequest(r)) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer resp.Body.Close() |
||||
|
||||
qm := &QueryMeta{} |
||||
parseQueryMeta(resp, qm) |
||||
qm.RequestTime = rtt |
||||
return qm, nil |
||||
} |
||||
|
||||
// Execute is used to execute a specific prepared query. You can execute using
|
||||
// a query ID or name.
|
||||
func (c *PreparedQuery) Execute(queryIDOrName string, q *QueryOptions) (*PreparedQueryExecuteResponse, *QueryMeta, error) { |
||||
r := c.c.newRequest("GET", "/v1/query/"+queryIDOrName+"/execute") |
||||
r.setQueryOptions(q) |
||||
rtt, resp, err := requireOK(c.c.doRequest(r)) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
defer resp.Body.Close() |
||||
|
||||
qm := &QueryMeta{} |
||||
parseQueryMeta(resp, qm) |
||||
qm.RequestTime = rtt |
||||
|
||||
var out *PreparedQueryExecuteResponse |
||||
if err := decodeBody(resp, &out); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
return out, qm, nil |
||||
} |
@ -0,0 +1,123 @@
|
||||
package api |
||||
|
||||
import ( |
||||
"reflect" |
||||
"testing" |
||||
|
||||
"github.com/hashicorp/consul/testutil" |
||||
) |
||||
|
||||
func TestPreparedQuery(t *testing.T) { |
||||
t.Parallel() |
||||
c, s := makeClient(t) |
||||
defer s.Stop() |
||||
|
||||
// Set up a node and a service.
|
||||
reg := &CatalogRegistration{ |
||||
Datacenter: "dc1", |
||||
Node: "foobar", |
||||
Address: "192.168.10.10", |
||||
Service: &AgentService{ |
||||
ID: "redis1", |
||||
Service: "redis", |
||||
Tags: []string{"master", "v1"}, |
||||
Port: 8000, |
||||
}, |
||||
} |
||||
|
||||
catalog := c.Catalog() |
||||
testutil.WaitForResult(func() (bool, error) { |
||||
if _, err := catalog.Register(reg, nil); err != nil { |
||||
return false, err |
||||
} |
||||
|
||||
if _, _, err := catalog.Node("foobar", nil); err != nil { |
||||
return false, err |
||||
} |
||||
|
||||
return true, nil |
||||
}, func(err error) { |
||||
t.Fatalf("err: %s", err) |
||||
}) |
||||
|
||||
// Create a simple prepared query.
|
||||
def := &PreparedQueryDefinition{ |
||||
Service: ServiceQuery{ |
||||
Service: "redis", |
||||
}, |
||||
} |
||||
|
||||
query := c.PreparedQuery() |
||||
var err error |
||||
def.ID, _, err = query.Create(def, nil) |
||||
if err != nil { |
||||
t.Fatalf("err: %s", err) |
||||
} |
||||
|
||||
// Read it back.
|
||||
defs, _, err := query.Get(def.ID, nil) |
||||
if err != nil { |
||||
t.Fatalf("err: %s", err) |
||||
} |
||||
if len(defs) != 1 || !reflect.DeepEqual(defs[0], def) { |
||||
t.Fatalf("bad: %v", defs) |
||||
} |
||||
|
||||
// List them all.
|
||||
defs, _, err = query.List(nil) |
||||
if err != nil { |
||||
t.Fatalf("err: %s", err) |
||||
} |
||||
if len(defs) != 1 || !reflect.DeepEqual(defs[0], def) { |
||||
t.Fatalf("bad: %v", defs) |
||||
} |
||||
|
||||
// Make an update.
|
||||
def.Name = "my-query" |
||||
_, err = query.Update(def, nil) |
||||
if err != nil { |
||||
t.Fatalf("err: %s", err) |
||||
} |
||||
|
||||
// Read it back again to verify the update worked.
|
||||
defs, _, err = query.Get(def.ID, nil) |
||||
if err != nil { |
||||
t.Fatalf("err: %s", err) |
||||
} |
||||
if len(defs) != 1 || !reflect.DeepEqual(defs[0], def) { |
||||
t.Fatalf("bad: %v", defs) |
||||
} |
||||
|
||||
// Execute by ID.
|
||||
results, _, err := query.Execute(def.ID, nil) |
||||
if err != nil { |
||||
t.Fatalf("err: %s", err) |
||||
} |
||||
if len(results.Nodes) != 1 || results.Nodes[0].Node.Node != "foobar" { |
||||
t.Fatalf("bad: %v", results) |
||||
} |
||||
|
||||
// Execute by name.
|
||||
results, _, err = query.Execute("my-query", nil) |
||||
if err != nil { |
||||
t.Fatalf("err: %s", err) |
||||
} |
||||
if len(results.Nodes) != 1 || results.Nodes[0].Node.Node != "foobar" { |
||||
t.Fatalf("bad: %v", results) |
||||
} |
||||
|
||||
// Delete it.
|
||||
_, err = query.Delete(def.ID, nil) |
||||
if err != nil { |
||||
t.Fatalf("err: %s", err) |
||||
} |
||||
|
||||
// Make sure there are no longer any queries.
|
||||
defs, _, err = query.List(nil) |
||||
if err != nil { |
||||
t.Fatalf("err: %s", err) |
||||
} |
||||
if len(defs) != 0 { |
||||
t.Fatalf("bad: %v", defs) |
||||
} |
||||
} |
Loading…
Reference in new issue