From 55d927d1ae7704f2b3ab9598cd1bb71620a1a24a Mon Sep 17 00:00:00 2001 From: cskh Date: Thu, 15 Jun 2023 23:53:02 -0400 Subject: [PATCH] tag support for -type=checks -service=foo --- agent/health_endpoint.go | 12 +++++ api/health.go | 10 ++++ api/watch/funcs.go | 2 +- api/watch/funcs_test.go | 106 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 128 insertions(+), 2 deletions(-) diff --git a/agent/health_endpoint.go b/agent/health_endpoint.go index 9663ed5769ee..2ba9bea4dbb4 100644 --- a/agent/health_endpoint.go +++ b/agent/health_endpoint.go @@ -140,6 +140,18 @@ func (s *HTTPHandlers) HealthServiceChecks(resp http.ResponseWriter, req *http.R return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing service name"} } + // build tag filter + params := req.URL.Query() + if tags, ok := params["tag"]; ok { + for i, tag := range tags { + expr := fmt.Sprintf(`%s in ServiceTags`, tag) + if i < len(tags)-1 { + expr += " and " + } + args.Filter += expr + } + } + // Make the RPC request var out structs.IndexedHealthChecks defer setMeta(resp, &out.QueryMeta) diff --git a/api/health.go b/api/health.go index 76619bbe1901..480f492f494b 100644 --- a/api/health.go +++ b/api/health.go @@ -261,7 +261,17 @@ func (h *Health) Node(node string, q *QueryOptions) (HealthChecks, *QueryMeta, e // Checks is used to return the checks associated with a service func (h *Health) Checks(service string, q *QueryOptions) (HealthChecks, *QueryMeta, error) { + return h.ChecksTags(service, nil, q) +} + +// ChecksTags is used to return the checks associated with a service filtered by tags +func (h *Health) ChecksTags(service string, tags []string, q *QueryOptions) (HealthChecks, *QueryMeta, error) { r := h.c.newRequest("GET", "/v1/health/checks/"+service) + if len(tags) > 0 { + for _, tag := range tags { + r.params.Add("tag", tag) + } + } r.setQueryOptions(q) rtt, resp, err := h.c.doRequest(r) if err != nil { diff --git a/api/watch/funcs.go b/api/watch/funcs.go index 5cf15b562ec1..33e3223d5f55 100644 --- a/api/watch/funcs.go +++ b/api/watch/funcs.go @@ -206,7 +206,7 @@ func checksWatch(params map[string]interface{}) (WatcherFunc, error) { if state != "" { checks, meta, err = health.StateTags(state, tags, &opts) } else { - checks, meta, err = health.Checks(service, &opts) + checks, meta, err = health.ChecksTags(service, tags, &opts) } if err != nil { return nil, nil, err diff --git a/api/watch/funcs_test.go b/api/watch/funcs_test.go index 25e20c8d0721..d3562397c79b 100644 --- a/api/watch/funcs_test.go +++ b/api/watch/funcs_test.go @@ -772,7 +772,111 @@ func TestChecksWatch_Service(t *testing.T) { } } -func TestChecksWatch_Service_Tags(t *testing.T) { +func TestChecksWatch_Service_Tag(t *testing.T) { + t.Parallel() + c, s := makeClient(t) + defer s.Stop() + + s.WaitForSerfCheck(t) + + var ( + wakeups [][]*api.HealthCheck + notifyCh = make(chan struct{}) + ) + + plan := mustParse(t, `{"type":"checks", "service":"foobar", "tag":["b", "a"]}`) + plan.Handler = func(idx uint64, raw interface{}) { + if raw == nil { + return // ignore + } + v, ok := raw.([]*api.HealthCheck) + if !ok { + return // ignore + } + wakeups = append(wakeups, v) + notifyCh <- struct{}{} + } + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + if err := plan.Run(s.HTTPAddr); err != nil { + t.Errorf("err: %v", err) + } + }() + defer plan.Stop() + + // Wait for first wakeup. + <-notifyCh + { + catalog := c.Catalog() + + // we want to find this one + reg := &api.CatalogRegistration{ + Node: "foobar", + Address: "1.1.1.1", + Datacenter: "dc1", + Service: &api.AgentService{ + ID: "foobar", + Service: "foobar", + Tags: []string{"a", "b"}, + }, + Check: &api.AgentCheck{ + Node: "foobar", + CheckID: "foobar", + Name: "foobar", + Status: api.HealthPassing, + ServiceID: "foobar", + }, + } + if _, err := catalog.Register(reg, nil); err != nil { + t.Fatalf("err: %v", err) + } + + // we don't want to find this one + reg = &api.CatalogRegistration{ + Node: "bar", + Address: "2.2.2.2", + Datacenter: "dc1", + Service: &api.AgentService{ + ID: "foobar", + Service: "foobar", + Tags: []string{"a"}, + }, + Check: &api.AgentCheck{ + Node: "bar", + CheckID: "foobar", + Name: "foobar", + Status: api.HealthPassing, + ServiceID: "foobar", + }, + } + if _, err := catalog.Register(reg, nil); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Wait for second wakeup. + <-notifyCh + + plan.Stop() + wg.Wait() + + require.Len(t, wakeups, 2) + + { + v := wakeups[0] + require.Len(t, v, 0) + } + { + v := wakeups[1] + require.Len(t, v, 1) + require.Equal(t, "foobar", v[0].CheckID) + } +} + +func TestChecksWatch_Tag(t *testing.T) { t.Parallel() c, s := makeClient(t) defer s.Stop()