Skip to content

Commit

Permalink
Fix catalog tag filter backward compat (#4944)
Browse files Browse the repository at this point in the history
Fix catalog service node filtering (ex /v1/catalog/service/srv?tag=tag1)
between agent version <=v1.2.3 and server >=v1.3.0.
New server version did not account for the old field when filtering
hence request made from old agent were not tag-filtered.
  • Loading branch information
Aestek authored and banks committed Nov 13, 2018
1 parent e875783 commit c512d2b
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 2 deletions.
10 changes: 9 additions & 1 deletion agent/consul/catalog_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,15 @@ func (c *Catalog) ServiceNodes(args *structs.ServiceSpecificRequest, reply *stru
}

if args.TagFilter {
return s.ServiceTagNodes(ws, args.ServiceName, args.ServiceTags)
tags := args.ServiceTags

// Agents < v1.3.0 and DNS service lookups populate the ServiceTag field. In this case,
// use ServiceTag instead of the ServiceTags field.
if args.ServiceTag != "" {
tags = []string{args.ServiceTag}
}

return s.ServiceTagNodes(ws, args.ServiceName, tags)
}

return s.ServiceNodes(ws, args.ServiceName)
Expand Down
73 changes: 73 additions & 0 deletions agent/consul/catalog_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1628,6 +1628,79 @@ func TestCatalog_ListServiceNodes(t *testing.T) {
}
}

// TestCatalog_ListServiceNodes_ServiceTags_V1_2_3Compat asserts the compatibility between <=v1.2.3 agents and >=v1.3.0 servers
// see https://github.com/hashicorp/consul/issues/4922
func TestCatalog_ListServiceNodes_ServiceTags_V1_2_3Compat(t *testing.T) {
t.Parallel()
dir1, s1 := testServer(t)
defer os.RemoveAll(dir1)
defer s1.Shutdown()
codec := rpcClient(t, s1)
defer codec.Close()

testrpc.WaitForTestAgent(t, s1.RPC, "dc1")

err := s1.fsm.State().EnsureNode(1, &structs.Node{Node: "foo", Address: "127.0.0.1"})
require.NoError(t, err)

// register two service instances with different tags
err = s1.fsm.State().EnsureService(2, "foo", &structs.NodeService{ID: "db1", Service: "db", Tags: []string{"primary"}, Address: "127.0.0.1", Port: 5000})
require.NoError(t, err)
err = s1.fsm.State().EnsureService(2, "foo", &structs.NodeService{ID: "db2", Service: "db", Tags: []string{"secondary"}, Address: "127.0.0.1", Port: 5001})
require.NoError(t, err)

// make a request with the <=1.2.3 ServiceTag tag field (vs ServiceTags)
args := structs.ServiceSpecificRequest{
Datacenter: "dc1",
ServiceName: "db",
ServiceTag: "primary",
TagFilter: true,
}
var out structs.IndexedServiceNodes
err = msgpackrpc.CallWithCodec(codec, "Catalog.ServiceNodes", &args, &out)
require.NoError(t, err)

// nodes should be filtered, even when using the old ServiceTag field
require.Equal(t, 1, len(out.ServiceNodes))
require.Equal(t, "db1", out.ServiceNodes[0].ServiceID)

// test with the other tag
args = structs.ServiceSpecificRequest{
Datacenter: "dc1",
ServiceName: "db",
ServiceTag: "secondary",
TagFilter: true,
}
out = structs.IndexedServiceNodes{}
err = msgpackrpc.CallWithCodec(codec, "Catalog.ServiceNodes", &args, &out)
require.NoError(t, err)
require.Equal(t, 1, len(out.ServiceNodes))
require.Equal(t, "db2", out.ServiceNodes[0].ServiceID)

// no tag, both instances
args = structs.ServiceSpecificRequest{
Datacenter: "dc1",
ServiceName: "db",
}
out = structs.IndexedServiceNodes{}
err = msgpackrpc.CallWithCodec(codec, "Catalog.ServiceNodes", &args, &out)
require.NoError(t, err)
require.Equal(t, 2, len(out.ServiceNodes))

// when both ServiceTag and ServiceTags fields are populated, use ServiceTag
args = structs.ServiceSpecificRequest{
Datacenter: "dc1",
ServiceName: "db",
ServiceTag: "primary",
ServiceTags: []string{"secondary"},
TagFilter: true,
}
out = structs.IndexedServiceNodes{}
err = msgpackrpc.CallWithCodec(codec, "Catalog.ServiceNodes", &args, &out)
require.NoError(t, err)
require.Equal(t, "db1", out.ServiceNodes[0].ServiceID)
}

func TestCatalog_ListServiceNodes_NodeMetaFilter(t *testing.T) {
t.Parallel()
dir1, s1 := testServer(t)
Expand Down
2 changes: 1 addition & 1 deletion agent/consul/health_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ func (h *Health) serviceNodesConnect(ws memdb.WatchSet, s *state.Store, args *st
}

func (h *Health) serviceNodesTagFilter(ws memdb.WatchSet, s *state.Store, args *structs.ServiceSpecificRequest) (uint64, structs.CheckServiceNodes, error) {
// DNS service lookups populate the ServiceTag field. In this case,
// Agents < v1.3.0 and DNS service lookups populate the ServiceTag field. In this case,
// use ServiceTag instead of the ServiceTags field.
if args.ServiceTag != "" {
return s.CheckServiceTagNodes(ws, args.ServiceName, []string{args.ServiceTag})
Expand Down

0 comments on commit c512d2b

Please sign in to comment.