Add support for multiple metadata filters to remaining endpoints

Enabled multiple meta filters for /v1/catalog/nodes and /v1/catalog/services
This commit is contained in:
Kyle Havlovitz 2017-01-13 20:45:34 -05:00
parent 5acd69b4fc
commit 9e696220a8
No known key found for this signature in database
GPG Key ID: 8A5E6B173056AD6C
3 changed files with 84 additions and 78 deletions

View File

@ -198,11 +198,8 @@ func (s *StateStore) Nodes() (uint64, structs.Nodes, error) {
return idx, results, nil
}
// NodesByMeta is used to return all nodes with the given meta key/value pair.
// NodesByMeta is used to return all nodes with the given metadata key/value pairs.
func (s *StateStore) NodesByMeta(filters map[string]string) (uint64, structs.Nodes, error) {
if len(filters) > 1 {
return 0, nil, fmt.Errorf("multiple meta filters not supported")
}
tx := s.db.Txn(false)
defer tx.Abort()
@ -213,6 +210,7 @@ func (s *StateStore) NodesByMeta(filters map[string]string) (uint64, structs.Nod
var args []interface{}
for key, value := range filters {
args = append(args, key, value)
break
}
nodes, err := tx.Get("nodes", "meta", args...)
if err != nil {
@ -222,7 +220,10 @@ func (s *StateStore) NodesByMeta(filters map[string]string) (uint64, structs.Nod
// Create and return the nodes list.
var results structs.Nodes
for node := nodes.Next(); node != nil; node = nodes.Next() {
results = append(results, node.(*structs.Node))
n := node.(*structs.Node)
if len(filters) <= 1 || structs.SatisfiesMetaFilters(n.Meta, filters) {
results = append(results, n)
}
}
return idx, results, nil
}
@ -437,11 +438,8 @@ func (s *StateStore) Services() (uint64, structs.Services, error) {
return idx, results, nil
}
// Services returns all services, filtered by the given node metadata.
// ServicesByNodeMeta returns all services, filtered by the given node metadata.
func (s *StateStore) ServicesByNodeMeta(filters map[string]string) (uint64, structs.Services, error) {
if len(filters) > 1 {
return 0, nil, fmt.Errorf("multiple meta filters not supported")
}
tx := s.db.Txn(false)
defer tx.Abort()
@ -452,6 +450,7 @@ func (s *StateStore) ServicesByNodeMeta(filters map[string]string) (uint64, stru
var args []interface{}
for key, value := range filters {
args = append(args, key, value)
break
}
nodes, err := tx.Get("nodes", "meta", args...)
if err != nil {
@ -462,6 +461,9 @@ func (s *StateStore) ServicesByNodeMeta(filters map[string]string) (uint64, stru
unique := make(map[string]map[string]struct{})
for node := nodes.Next(); node != nil; node = nodes.Next() {
n := node.(*structs.Node)
if len(filters) > 1 && !structs.SatisfiesMetaFilters(n.Meta, filters) {
continue
}
// List all the services on the node
services, err := tx.Get("services", "node", n.Node)
if err != nil {
@ -878,8 +880,8 @@ func (s *StateStore) ServiceChecks(serviceName string) (uint64, structs.HealthCh
}
// ServiceChecksByNodeMeta is used to get all checks associated with a
// given service ID. The query is performed against a service
// _name_ instead of a service ID.
// given service ID, filtered by the given node metadata values. The query
// is performed against a service _name_ instead of a service ID.
func (s *StateStore) ServiceChecksByNodeMeta(serviceName string, filters map[string]string) (uint64, structs.HealthChecks, error) {
tx := s.db.Txn(false)
defer tx.Abort()
@ -921,8 +923,8 @@ func (s *StateStore) ChecksInState(state string) (uint64, structs.HealthChecks,
return s.parseChecks(idx, checks)
}
// ChecksInState is used to query the state store for all checks
// which are in the provided state.
// ChecksInStateByNodeMeta is used to query the state store for all checks
// which are in the provided state, filtered by the given node metadata values.
func (s *StateStore) ChecksInStateByNodeMeta(state string, filters map[string]string) (uint64, structs.HealthChecks, error) {
tx := s.db.Txn(false)
defer tx.Abort()

View File

@ -545,65 +545,50 @@ func TestStateStore_GetNodesByMeta(t *testing.T) {
}
// Create some nodes in the state store
node0 := &structs.Node{Node: "node0", Address: "127.0.0.1", Meta: map[string]string{"role": "client", "common": "1"}}
if err := s.EnsureNode(0, node0); err != nil {
t.Fatalf("err: %v", err)
}
node1 := &structs.Node{Node: "node1", Address: "127.0.0.1", Meta: map[string]string{"role": "server", "common": "1"}}
if err := s.EnsureNode(1, node1); err != nil {
t.Fatalf("err: %v", err)
testRegisterNodeWithMeta(t, s, 0, "node0", map[string]string{"role": "client"})
testRegisterNodeWithMeta(t, s, 1, "node1", map[string]string{"role": "client", "common": "1"})
testRegisterNodeWithMeta(t, s, 2, "node2", map[string]string{"role": "server", "common": "1"})
cases := []struct {
filters map[string]string
nodes []string
}{
// Simple meta filter
{
filters: map[string]string{"role": "server"},
nodes: []string{"node2"},
},
// Common meta filter
{
filters: map[string]string{"common": "1"},
nodes: []string{"node1", "node2"},
},
// Invalid meta filter
{
filters: map[string]string{"invalid": "nope"},
nodes: []string{},
},
// Multiple meta filters
{
filters: map[string]string{"role": "client", "common": "1"},
nodes: []string{"node1"},
},
}
// Retrieve the node with role=client
idx, nodes, err := s.NodesByMeta(map[string]string{"role": "client"})
for _, tc := range cases {
_, result, err := s.NodesByMeta(tc.filters)
if err != nil {
t.Fatalf("err: %s", err)
}
if idx != 1 {
t.Fatalf("bad index: %d", idx)
t.Fatalf("bad: %v", err)
}
// Only one node was returned
if n := len(nodes); n != 1 {
t.Fatalf("bad node count: %d", n)
if len(result) != len(tc.nodes) {
t.Fatalf("bad: %v %v", result, tc.nodes)
}
// Make sure the node is correct
if nodes[0].CreateIndex != 0 || nodes[0].ModifyIndex != 0 {
t.Fatalf("bad node index: %d, %d", nodes[0].CreateIndex, nodes[0].ModifyIndex)
for i, node := range result {
if node.Node != tc.nodes[i] {
t.Fatalf("bad: %v %v", node.Node, tc.nodes[i])
}
if nodes[0].Node != "node0" {
t.Fatalf("bad: %#v", nodes[0])
}
if !reflect.DeepEqual(nodes[0].Meta, node0.Meta) {
t.Fatalf("bad: %v != %v", nodes[0].Meta, node0.Meta)
}
// Retrieve both nodes via their common meta field
idx, nodes, err = s.NodesByMeta(map[string]string{"common": "1"})
if err != nil {
t.Fatalf("err: %s", err)
}
if idx != 1 {
t.Fatalf("bad index: %d", idx)
}
// All nodes were returned
if n := len(nodes); n != 2 {
t.Fatalf("bad node count: %d", n)
}
// Make sure the nodes match
for i, node := range nodes {
if node.CreateIndex != uint64(i) || node.ModifyIndex != uint64(i) {
t.Fatalf("bad node index: %d, %d", node.CreateIndex, node.ModifyIndex)
}
name := fmt.Sprintf("node%d", i)
if node.Node != name {
t.Fatalf("bad: %#v", node)
}
if v, ok := node.Meta["common"]; !ok || v != "1" {
t.Fatalf("bad: %v", node.Meta)
}
}
}
@ -976,13 +961,10 @@ func TestStateStore_ServicesByNodeMeta(t *testing.T) {
}
// Filter the services by the first node's meta value
idx, res, err = s.ServicesByNodeMeta(map[string]string{"role": "client"})
_, res, err = s.ServicesByNodeMeta(map[string]string{"role": "client"})
if err != nil {
t.Fatalf("err: %s", err)
}
if idx != 3 {
t.Fatalf("bad index: %d", idx)
}
expected := structs.Services{
"redis": []string{"master", "prod"},
}
@ -992,13 +974,10 @@ func TestStateStore_ServicesByNodeMeta(t *testing.T) {
}
// Get all services using the common meta value
idx, res, err = s.ServicesByNodeMeta(map[string]string{"common": "1"})
_, res, err = s.ServicesByNodeMeta(map[string]string{"common": "1"})
if err != nil {
t.Fatalf("err: %s", err)
}
if idx != 3 {
t.Fatalf("bad index: %d", idx)
}
expected = structs.Services{
"redis": []string{"master", "prod", "slave"},
}
@ -1006,6 +985,29 @@ func TestStateStore_ServicesByNodeMeta(t *testing.T) {
if !reflect.DeepEqual(res, expected) {
t.Fatalf("bad: %v %v", res, expected)
}
// Get an empty list for an invalid meta value
_, res, err = s.ServicesByNodeMeta(map[string]string{"invalid": "nope"})
if err != nil {
t.Fatalf("err: %s", err)
}
expected = structs.Services{}
if !reflect.DeepEqual(res, expected) {
t.Fatalf("bad: %v %v", res, expected)
}
// Get the first node's service instance using multiple meta filters
_, res, err = s.ServicesByNodeMeta(map[string]string{"role": "client", "common": "1"})
if err != nil {
t.Fatalf("err: %s", err)
}
expected = structs.Services{
"redis": []string{"master", "prod"},
}
sort.Strings(res["redis"])
if !reflect.DeepEqual(res, expected) {
t.Fatalf("bad: %v %v", res, expected)
}
}
func TestStateStore_ServiceNodes(t *testing.T) {

View File

@ -200,7 +200,8 @@ node for the sort.
In Consul 0.7.3 and later, the optional `?node-meta=` parameter can be
provided with a desired node metadata key/value pair of the form `key:value`.
This will filter the results to nodes with that pair present.
This parameter can be specified multiple times, and will filter the results to
nodes with the specified key/value pair(s).
It returns a JSON body like this:
@ -241,7 +242,8 @@ however, the `dc` can be provided using the `?dc=` query parameter.
In Consul 0.7.3 and later, the optional `?node-meta=` parameter can be
provided with a desired node metadata key/value pair of the form `key:value`.
This will filter the results to services with that pair present.
This parameter can be specified multiple times, and will filter the results to
services on nodes with the specified key/value pair(s).
It returns a JSON body like this: