Enabling "service" watch handler to accept a slice of tags

Originally from PR #5347
This commit is contained in:
Matt Keeler 2019-04-29 15:28:01 -04:00 committed by GitHub
parent 93f579dc8b
commit f67e12eb6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 166 additions and 16 deletions

View File

@ -134,15 +134,17 @@ func serviceWatch(params map[string]interface{}) (WatcherFunc, error) {
return nil, err
}
var service, tag string
var (
service string
tags []string
)
if err := assignValue(params, "service", &service); err != nil {
return nil, err
}
if service == "" {
return nil, fmt.Errorf("Must specify a single service to watch")
}
if err := assignValue(params, "tag", &tag); err != nil {
if err := assignValueStringSlice(params, "tag", &tags); err != nil {
return nil, err
}
@ -155,7 +157,7 @@ func serviceWatch(params map[string]interface{}) (WatcherFunc, error) {
health := p.client.Health()
opts := makeQueryOptionsWithContext(p, stale)
defer p.cancelFunc()
nodes, meta, err := health.Service(service, tag, passingOnly, &opts)
nodes, meta, err := health.ServiceMultipleTags(service, tags, passingOnly, &opts)
if err != nil {
return nil, nil, err
}

View File

@ -428,6 +428,101 @@ func TestServiceWatch(t *testing.T) {
wg.Wait()
}
func TestServiceMultipleTagsWatch(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
invoke := makeInvokeCh()
plan := mustParse(t, `{"type":"service", "service":"foo", "tag":["bar","buzz"], "passingonly":true}`)
plan.Handler = func(idx uint64, raw interface{}) {
if raw == nil {
return // ignore
}
v, ok := raw.([]*api.ServiceEntry)
if !ok || len(v) == 0 {
return // ignore
}
if v[0].Service.ID != "foobarbuzzbiff" {
invoke <- errBadContent
return
}
if len(v[0].Service.Tags) == 0 {
invoke <- errBadContent
return
}
// test for our tags
barFound := false
buzzFound := false
for _, t := range v[0].Service.Tags {
if t == "bar" {
barFound = true
} else if t == "buzz" {
buzzFound = true
}
}
if !barFound || !buzzFound {
invoke <- errBadContent
return
}
invoke <- nil
}
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
agent := c.Agent()
// we do not want to find this one.
time.Sleep(20 * time.Millisecond)
reg := &api.AgentServiceRegistration{
ID: "foobarbiff",
Name: "foo",
Tags: []string{"bar", "biff"},
}
if err := agent.ServiceRegister(reg); err != nil {
t.Fatalf("err: %v", err)
}
// we do not want to find this one.
reg = &api.AgentServiceRegistration{
ID: "foobuzzbiff",
Name: "foo",
Tags: []string{"buzz", "biff"},
}
if err := agent.ServiceRegister(reg); err != nil {
t.Fatalf("err: %v", err)
}
// we want to find this one
reg = &api.AgentServiceRegistration{
ID: "foobarbuzzbiff",
Name: "foo",
Tags: []string{"bar", "buzz", "biff"},
}
if err := agent.ServiceRegister(reg); err != nil {
t.Fatalf("err: %v", err)
}
}()
wg.Add(1)
go func() {
defer wg.Done()
if err := plan.Run(s.HTTPAddr); err != nil {
t.Fatalf("err: %v", err)
}
}()
if err := <-invoke; err != nil {
t.Fatalf("err: %v", err)
}
plan.Stop()
wg.Wait()
}
func TestChecksWatch_State(t *testing.T) {
t.Parallel()
c, s := makeClient(t)

View File

@ -233,6 +233,37 @@ func assignValueBool(params map[string]interface{}, name string, out *bool) erro
return nil
}
// assignValueStringSlice is used to extract a value ensuring it is either a string or a slice of strings
func assignValueStringSlice(params map[string]interface{}, name string, out *[]string) error {
if raw, ok := params[name]; ok {
var tmp []string
switch raw.(type) {
case string:
tmp = make([]string, 1, 1)
tmp[0] = raw.(string)
case []string:
l := len(raw.([]string))
tmp = make([]string, l, l)
copy(tmp, raw.([]string))
case []interface{}:
l := len(raw.([]interface{}))
tmp = make([]string, l, l)
for i, v := range raw.([]interface{}) {
if s, ok := v.(string); ok {
tmp[i] = s
} else {
return fmt.Errorf("Index %d of %s expected to be string", i, name)
}
}
default:
return fmt.Errorf("Expecting %s to be a string or []string", name)
}
*out = tmp
delete(params, name)
}
return nil
}
// Parse the 'http_handler_config' parameters
func parseHttpHandlerConfig(configParams interface{}) (*HttpHandlerConfig, error) {
var config HttpHandlerConfig

View File

@ -36,7 +36,7 @@ type cmd struct {
key string
prefix string
service string
tag string
tag []string
passingOnly string
state string
name string
@ -55,8 +55,8 @@ func (c *cmd) init() {
c.flags.StringVar(&c.service, "service", "",
"Specifies the service to watch. Required for 'service' type, "+
"optional for 'checks' type.")
c.flags.StringVar(&c.tag, "tag", "",
"Specifies the service tag to filter on. Optional for 'service' type.")
c.flags.Var((*flags.AppendSliceValue)(&c.tag), "tag", "Specifies the service tag(s) to filter on. "+
"Optional for 'service' type. May be specified multiple times")
c.flags.StringVar(&c.passingOnly, "passingonly", "",
"Specifies if only hosts passing all checks are displayed. "+
"Optional for 'service' type, must be one of `[true|false]`. Defaults false.")
@ -115,7 +115,7 @@ func (c *cmd) Run(args []string) int {
if c.service != "" {
params["service"] = c.service
}
if c.tag != "" {
if len(c.tag) > 0 {
params["tag"] = c.tag
}
if c.http.Stale() {

View File

@ -266,26 +266,45 @@ An example of the output of this command:
The "service" watch type is used to monitor the providers
of a single service. It requires the "service" parameter
and optionally takes the parameters "tag" and "passingonly".
The "tag" parameter will filter by tag, and "passingonly" is
a boolean that will filter to only the instances passing all
health checks.
and optionally takes the parameters "tag" and
"passingonly". The "tag" parameter will filter by one or more tags.
It may be either a single string value or a slice of strings.
The "passingonly" is a boolean that will filter to only the
instances passing all health checks.
This maps to the `/v1/health/service` API internally.
Here is an example configuration:
Here is an example configuration with a single tag:
```javascript
{
"type": "service",
"service": "redis",
"args": ["/usr/bin/my-service-handler.sh", "-redis"]
"args": ["/usr/bin/my-service-handler.sh", "-redis"],
"tag": "bar"
}
```
Here is an example configuration with multiple tags:
```javascript
{
"type": "service",
"service": "redis",
"args": ["/usr/bin/my-service-handler.sh", "-redis"],
"tag": ["bar", "foo"]
}
```
Or, using the watch command:
$ consul watch -type=service -service=redis /usr/bin/my-service-handler.sh
Single tag:
$ consul watch -type=service -service=redis -tag=bar /usr/bin/my-service-handler.sh
Multiple tag:
$ consul watch -type=service -service=redis -tag=bar -tag=foo /usr/bin/my-service-handler.sh
An example of the output of this command:
@ -299,7 +318,10 @@ An example of the output of this command:
"Service": {
"ID": "redis",
"Service": "redis",
"Tags": null,
"Tags": [
"bar",
"foo"
],
"Port": 8000
},
"Checks": [