From dcbe30ef087d69d187f5d0e0719c1fab8d116910 Mon Sep 17 00:00:00 2001 From: Seth Hoenig Date: Fri, 29 Jan 2021 10:42:19 -0600 Subject: [PATCH] api: enable query options on agent endpoints This PR adds support for setting QueryOptions on a few agent API endpoints. Nomad needs to be able to set the Namespace field on these endpoints to: - query for services / checks in a namespace - deregister services / checks in a namespace - update TTL status on checks in a namespace --- .changelog/9903.txt | 4 +++ api/agent.go | 39 ++++++++++++++++++++++++++ api/agent_test.go | 67 ++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 .changelog/9903.txt diff --git a/.changelog/9903.txt b/.changelog/9903.txt new file mode 100644 index 000000000..e341fcfcd --- /dev/null +++ b/.changelog/9903.txt @@ -0,0 +1,4 @@ +```release-note:improvement +api: Enable setting query options on agent endpoints. +``` + diff --git a/api/agent.go b/api/agent.go index 8c843ab4d..0ed6fc298 100644 --- a/api/agent.go +++ b/api/agent.go @@ -509,7 +509,14 @@ func (a *Agent) Checks() (map[string]*AgentCheck, error) { // ChecksWithFilter returns a subset of the locally registered checks that match // the given filter expression func (a *Agent) ChecksWithFilter(filter string) (map[string]*AgentCheck, error) { + return a.ChecksWithFilterOpts(filter, nil) +} + +// ChecksWithFilterOpts returns a subset of the locally registered checks that match +// the given filter expression and QueryOptions. +func (a *Agent) ChecksWithFilterOpts(filter string, q *QueryOptions) (map[string]*AgentCheck, error) { r := a.c.newRequest("GET", "/v1/agent/checks") + r.setQueryOptions(q) r.filterQuery(filter) _, resp, err := requireOK(a.c.doRequest(r)) if err != nil { @@ -532,7 +539,14 @@ func (a *Agent) Services() (map[string]*AgentService, error) { // ServicesWithFilter returns a subset of the locally registered services that match // the given filter expression func (a *Agent) ServicesWithFilter(filter string) (map[string]*AgentService, error) { + return a.ServicesWithFilterOpts(filter, nil) +} + +// ServicesWithFilterOpts returns a subset of the locally registered services that match +// the given filter expression and QueryOptions. +func (a *Agent) ServicesWithFilterOpts(filter string, q *QueryOptions) (map[string]*AgentService, error) { r := a.c.newRequest("GET", "/v1/agent/services") + r.setQueryOptions(q) r.filterQuery(filter) _, resp, err := requireOK(a.c.doRequest(r)) if err != nil { @@ -727,6 +741,19 @@ func (a *Agent) ServiceDeregister(serviceID string) error { return nil } +// ServiceDeregisterOpts is used to deregister a service with +// the local agent with QueryOptions. +func (a *Agent) ServiceDeregisterOpts(serviceID string, q *QueryOptions) error { + r := a.c.newRequest("PUT", "/v1/agent/service/deregister/"+serviceID) + r.setQueryOptions(q) + _, resp, err := requireOK(a.c.doRequest(r)) + if err != nil { + return err + } + resp.Body.Close() + return nil +} + // PassTTL is used to set a TTL check to the passing state. // // DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL(). @@ -801,6 +828,10 @@ type checkUpdate struct { // strings for compatibility (though a newer version of Consul will still be // required to use this API). func (a *Agent) UpdateTTL(checkID, output, status string) error { + return a.UpdateTTLOpts(checkID, output, status, nil) +} + +func (a *Agent) UpdateTTLOpts(checkID, output, status string, q *QueryOptions) error { switch status { case "pass", HealthPassing: status = HealthPassing @@ -814,6 +845,7 @@ func (a *Agent) UpdateTTL(checkID, output, status string) error { endpoint := fmt.Sprintf("/v1/agent/check/update/%s", checkID) r := a.c.newRequest("PUT", endpoint) + r.setQueryOptions(q) r.obj = &checkUpdate{ Status: status, Output: output, @@ -843,7 +875,14 @@ func (a *Agent) CheckRegister(check *AgentCheckRegistration) error { // CheckDeregister is used to deregister a check with // the local agent func (a *Agent) CheckDeregister(checkID string) error { + return a.CheckDeregisterOpts(checkID, nil) +} + +// CheckDeregisterOpts is used to deregister a check with +// the local agent using query options +func (a *Agent) CheckDeregisterOpts(checkID string, q *QueryOptions) error { r := a.c.newRequest("PUT", "/v1/agent/check/deregister/"+checkID) + r.setQueryOptions(q) _, resp, err := requireOK(a.c.doRequest(r)) if err != nil { return err diff --git a/api/agent_test.go b/api/agent_test.go index ef267bff1..12056f236 100644 --- a/api/agent_test.go +++ b/api/agent_test.go @@ -333,7 +333,7 @@ func TestAPI_AgentServices(t *testing.T) { } } -func TestAPI_AgentServicesWithFilter(t *testing.T) { +func TestAPI_AgentServicesWithFilterOpts(t *testing.T) { t.Parallel() c, s := makeClient(t) defer s.Stop() @@ -362,7 +362,8 @@ func TestAPI_AgentServicesWithFilter(t *testing.T) { } require.NoError(t, agent.ServiceRegister(reg)) - services, err := agent.ServicesWithFilter("foo in Tags") + opts := &QueryOptions{Namespace: defaultNamespace} + services, err := agent.ServicesWithFilterOpts("foo in Tags", opts) require.NoError(t, err) require.Len(t, services, 1) _, ok := services["foo2"] @@ -852,6 +853,63 @@ func TestAPI_AgentSetTTLStatus(t *testing.T) { } } +func TestAPI_AgentUpdateTTLOpts(t *testing.T) { + t.Parallel() + c, s := makeClient(t) + defer s.Stop() + + agent := c.Agent() + s.WaitForSerfCheck(t) + + reg := &AgentServiceRegistration{ + Name: "foo", + Check: &AgentServiceCheck{ + TTL: "15s", + }, + } + if err := agent.ServiceRegister(reg); err != nil { + t.Fatalf("err: %v", err) + } + + verify := func(status, output string) { + checks, err := agent.Checks() + if err != nil { + t.Fatalf("err: %v", err) + } + chk, ok := checks["service:foo"] + if !ok { + t.Fatalf("missing check: %v", checks) + } + if chk.Status != status { + t.Fatalf("Bad: %#v", chk) + } + if chk.Output != output { + t.Fatalf("Bad: %#v", chk) + } + } + + opts := &QueryOptions{Namespace: defaultNamespace} + + if err := agent.UpdateTTLOpts("service:foo", "foo", HealthWarning, opts); err != nil { + t.Fatalf("err: %v", err) + } + verify(HealthWarning, "foo") + + if err := agent.UpdateTTLOpts("service:foo", "bar", HealthPassing, opts); err != nil { + t.Fatalf("err: %v", err) + } + verify(HealthPassing, "bar") + + if err := agent.UpdateTTL("service:foo", "baz", HealthCritical); err != nil { + t.Fatalf("err: %v", err) + } + verify(HealthCritical, "baz") + + if err := agent.ServiceDeregister("foo"); err != nil { + t.Fatalf("err: %v", err) + } +} + func TestAPI_AgentChecks(t *testing.T) { t.Parallel() c, s := makeClient(t) @@ -887,7 +945,7 @@ func TestAPI_AgentChecks(t *testing.T) { } } -func TestAPI_AgentChecksWithFilter(t *testing.T) { +func TestAPI_AgentChecksWithFilterOpts(t *testing.T) { t.Parallel() c, s := makeClient(t) defer s.Stop() @@ -905,7 +963,8 @@ func TestAPI_AgentChecksWithFilter(t *testing.T) { reg.TTL = "15s" require.NoError(t, agent.CheckRegister(reg)) - checks, err := agent.ChecksWithFilter("Name == foo") + opts := &QueryOptions{Namespace: defaultNamespace} + checks, err := agent.ChecksWithFilterOpts("Name == foo", opts) require.NoError(t, err) require.Len(t, checks, 1) _, ok := checks["foo"]