diff --git a/api/api.go b/api/api.go index 9587043a4..9a59b724c 100644 --- a/api/api.go +++ b/api/api.go @@ -74,6 +74,11 @@ type QueryOptions struct { // that node. Setting this to "_agent" will use the agent's node // for the sort. Near string + + // NodeMeta is used to filter results by nodes with the given + // metadata key/value pairs. Currently, only one key/value pair can + // be provided for filtering. + NodeMeta map[string]string } // WriteOptions are used to parameterize a write @@ -386,6 +391,11 @@ func (r *request) setQueryOptions(q *QueryOptions) { if q.Near != "" { r.params.Set("near", q.Near) } + if len(q.NodeMeta) > 0 { + for key, value := range q.NodeMeta { + r.params.Add("node-meta", key+":"+value) + } + } } // durToMsec converts a duration to a millisecond specified string. If the diff --git a/api/catalog.go b/api/catalog.go index 56f0dbf69..10e93b42d 100644 --- a/api/catalog.go +++ b/api/catalog.go @@ -4,12 +4,14 @@ type Node struct { Node string Address string TaggedAddresses map[string]string + Meta map[string]string } type CatalogService struct { Node string Address string TaggedAddresses map[string]string + NodeMeta map[string]string ServiceID string ServiceName string ServiceAddress string @@ -29,6 +31,7 @@ type CatalogRegistration struct { Node string Address string TaggedAddresses map[string]string + NodeMeta map[string]string Datacenter string Service *AgentService Check *AgentCheck diff --git a/api/catalog_test.go b/api/catalog_test.go index e37d3dd50..527153b32 100644 --- a/api/catalog_test.go +++ b/api/catalog_test.go @@ -60,6 +60,64 @@ func TestCatalog_Nodes(t *testing.T) { }) } +func TestCatalog_Nodes_MetaFilter(t *testing.T) { + meta := map[string]string{"somekey": "somevalue"} + c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) { + conf.NodeMeta = meta + }) + defer s.Stop() + + catalog := c.Catalog() + + // Make sure we get the node back when filtering by its metadata + testutil.WaitForResult(func() (bool, error) { + nodes, meta, err := catalog.Nodes(&QueryOptions{NodeMeta: meta}) + if err != nil { + return false, err + } + + if meta.LastIndex == 0 { + return false, fmt.Errorf("Bad: %v", meta) + } + + if len(nodes) == 0 { + return false, fmt.Errorf("Bad: %v", nodes) + } + + if _, ok := nodes[0].TaggedAddresses["wan"]; !ok { + return false, fmt.Errorf("Bad: %v", nodes[0]) + } + + if v, ok := nodes[0].Meta["somekey"]; !ok || v != "somevalue" { + return false, fmt.Errorf("Bad: %v", nodes[0].Meta) + } + + return true, nil + }, func(err error) { + t.Fatalf("err: %s", err) + }) + + // Get nothing back when we use an invalid filter + testutil.WaitForResult(func() (bool, error) { + nodes, meta, err := catalog.Nodes(&QueryOptions{NodeMeta: map[string]string{"nope":"nope"}}) + if err != nil { + return false, err + } + + if meta.LastIndex == 0 { + return false, fmt.Errorf("Bad: %v", meta) + } + + if len(nodes) != 0 { + return false, fmt.Errorf("Bad: %v", nodes) + } + + return true, nil + }, func(err error) { + t.Fatalf("err: %s", err) + }) +} + func TestCatalog_Services(t *testing.T) { t.Parallel() c, s := makeClient(t) @@ -87,6 +145,56 @@ func TestCatalog_Services(t *testing.T) { }) } +func TestCatalog_Services_NodeMetaFilter(t *testing.T) { + meta := map[string]string{"somekey": "somevalue"} + c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) { + conf.NodeMeta = meta + }) + defer s.Stop() + + catalog := c.Catalog() + + // Make sure we get the service back when filtering by the node's metadata + testutil.WaitForResult(func() (bool, error) { + services, meta, err := catalog.Services(&QueryOptions{NodeMeta: meta}) + if err != nil { + return false, err + } + + if meta.LastIndex == 0 { + return false, fmt.Errorf("Bad: %v", meta) + } + + if len(services) == 0 { + return false, fmt.Errorf("Bad: %v", services) + } + + return true, nil + }, func(err error) { + t.Fatalf("err: %s", err) + }) + + // Get nothing back when using an invalid filter + testutil.WaitForResult(func() (bool, error) { + services, meta, err := catalog.Services(&QueryOptions{NodeMeta: map[string]string{"nope":"nope"}}) + if err != nil { + return false, err + } + + if meta.LastIndex == 0 { + return false, fmt.Errorf("Bad: %v", meta) + } + + if len(services) != 0 { + return false, fmt.Errorf("Bad: %v", services) + } + + return true, nil + }, func(err error) { + t.Fatalf("err: %s", err) + }) +} + func TestCatalog_Service(t *testing.T) { t.Parallel() c, s := makeClient(t) @@ -173,6 +281,7 @@ func TestCatalog_Registration(t *testing.T) { Datacenter: "dc1", Node: "foobar", Address: "192.168.10.10", + NodeMeta: map[string]string{"somekey": "somevalue"}, Service: service, Check: check, } @@ -200,6 +309,10 @@ func TestCatalog_Registration(t *testing.T) { return false, fmt.Errorf("missing checkid service:redis1") } + if v, ok := node.Node.Meta["somekey"]; !ok || v != "somevalue" { + return false, fmt.Errorf("missing node meta pair somekey:somevalue") + } + return true, nil }, func(err error) { t.Fatalf("err: %s", err) diff --git a/testutil/server.go b/testutil/server.go index 8ab196eca..e90121781 100644 --- a/testutil/server.go +++ b/testutil/server.go @@ -53,6 +53,7 @@ type TestAddressConfig struct { // TestServerConfig is the main server configuration struct. type TestServerConfig struct { NodeName string `json:"node_name"` + NodeMeta map[string]string `json:"node_meta"` Performance *TestPerformanceConfig `json:"performance,omitempty"` Bootstrap bool `json:"bootstrap,omitempty"` Server bool `json:"server,omitempty"` diff --git a/website/source/docs/agent/http/agent.html.markdown b/website/source/docs/agent/http/agent.html.markdown index c1f6e4775..b01a30033 100644 --- a/website/source/docs/agent/http/agent.html.markdown +++ b/website/source/docs/agent/http/agent.html.markdown @@ -128,6 +128,8 @@ This endpoint is used to return the configuration and member information of the Consul 0.7.0 and later also includes a snapshot of various operating statistics under the `Stats` key. These statistics are intended to help human operators for debugging and may change over time, so this part of the interface should not be consumed programmatically. +Consul 0.7.3 and later also includes a block of user-defined node metadata values under the `Meta` key. These are arbitrary key/value pairs defined in the [node meta](/docs/agent/options.html#_node_meta) section of the agent configuration. + It returns a JSON body like this: ```javascript @@ -194,6 +196,10 @@ It returns a JSON body like this: "DelegateMin": 2, "DelegateMax": 4, "DelegateCur": 4 + }, + "Meta": { + "instance_type": "i2.xlarge", + "os_version": "ubuntu_16.04", } } ``` diff --git a/website/source/docs/agent/http/catalog.html.markdown b/website/source/docs/agent/http/catalog.html.markdown index e5dcaab16..0a7a06629 100644 --- a/website/source/docs/agent/http/catalog.html.markdown +++ b/website/source/docs/agent/http/catalog.html.markdown @@ -44,6 +44,9 @@ body must look something like: "lan": "192.168.10.10", "wan": "10.0.10.10" }, + "NodeMeta": { + "somekey": "somevalue" + }, "Service": { "ID": "redis1", "Service": "redis", @@ -73,6 +76,10 @@ the node with the catalog. `TaggedAddresses` can be used in conjunction with the option and the `wan` address. The `lan` address was added in Consul 0.7 to help find the LAN address if address translation is enabled. +The `Meta` block was added in Consul 0.7.3 to enable associating arbitrary metadata +key/value pairs with a node for filtering purposes. For more information on node metadata, +see the [node meta](/docs/agent/options.html#_node_meta) section of the configuration page. + If the `Service` key is provided, the service will also be registered. If `ID` is not provided, it will be defaulted to the value of the `Service.Service` property. Only one service with a given `ID` may be present per node. The service `Tags`, `Address`, @@ -191,9 +198,9 @@ the node list in ascending order based on the estimated round trip time from that node. Passing `?near=_agent` will use the agent's node for the sort. -Adding the optional `?node-meta=` parameter with a desired node -metadata key/value pair of the form `key:value` will filter the -results to nodes with that pair present. +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. It returns a JSON body like this: @@ -205,6 +212,9 @@ It returns a JSON body like this: "TaggedAddresses": { "lan": "10.1.10.11", "wan": "10.1.10.11" + }, + "Meta": { + "instance_type": "t2.medium" } }, { @@ -213,6 +223,9 @@ It returns a JSON body like this: "TaggedAddresses": { "lan": "10.1.10.11", "wan": "10.1.10.12" + }, + "Meta": { + "instance_type": "t2.large" } } ] @@ -226,9 +239,9 @@ This endpoint is hit with a `GET` and returns the services registered in a given DC. By default, the datacenter of the agent is queried; however, the `dc` can be provided using the `?dc=` query parameter. -Adding the optional `?node-meta=` parameter with a desired node -metadata key/value pair of the form `key:value` will filter the -results to services with that pair present. +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. It returns a JSON body like this: @@ -273,6 +286,9 @@ It returns a JSON body like this: "lan": "192.168.10.10", "wan": "10.0.10.10" }, + "Meta": { + "instance_type": "t2.medium" + } "CreateIndex": 51, "ModifyIndex": 51, "Node": "foobar", @@ -294,6 +310,7 @@ The returned fields are as follows: - `Address`: IP address of the Consul node on which the service is registered - `TaggedAddresses`: List of explicit LAN and WAN IP addresses for the agent +- `Meta`: Added in Consul 0.7.3, a list of user-defined metadata key/value pairs for the node - `CreateIndex`: Internal index value representing when the service was created - `ModifyIndex`: Last index that modified the service - `Node`: Node name of the Consul node on which the service is registered @@ -321,6 +338,9 @@ It returns a JSON body like this: "TaggedAddresses": { "lan": "10.1.10.12", "wan": "10.1.10.12" + }, + "Meta": { + "instance_type": "t2.medium" } }, "Services": { diff --git a/website/source/docs/agent/http/health.html.markdown b/website/source/docs/agent/http/health.html.markdown index 9c8767ef7..e9f18c641 100644 --- a/website/source/docs/agent/http/health.html.markdown +++ b/website/source/docs/agent/http/health.html.markdown @@ -131,6 +131,9 @@ It returns a JSON body like this: "TaggedAddresses": { "lan": "10.1.10.12", "wan": "10.1.10.12" + }, + "Meta": { + "instance_type": "t2.medium" } }, "Service": { diff --git a/website/source/docs/agent/options.html.markdown b/website/source/docs/agent/options.html.markdown index 3a7d6f8c0..0f9196359 100644 --- a/website/source/docs/agent/options.html.markdown +++ b/website/source/docs/agent/options.html.markdown @@ -251,9 +251,9 @@ will exit with an error at startup. * `-node` - The name of this node in the cluster. This must be unique within the cluster. By default this is the hostname of the machine. -* `-node-meta` - An arbitrary metadata key/value pair - to associate with the node, of the form `key:value`. This can be specified multiple times. Node metadata - pairs have the following restrictions: +* `-node-meta` - Available in Consul 0.7.3 and later, + this specifies an arbitrary metadata key/value pair to associate with the node, of the form `key:value`. + This can be specified multiple times. Node metadata pairs have the following restrictions: - A maximum of 64 key/value pairs can be registered per node. - Metadata keys must be between 1 and 128 characters (inclusive) in length - Metadata keys must contain only alphanumeric, `-`, and `_` characters. @@ -667,9 +667,18 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass * `node_name` Equivalent to the [`-node` command-line flag](#_node). -* `node_meta` This object allows associating arbitrary - metadata key/value pairs with the local node, which can then be used for filtering results from certain - catalog endpoints. See the [`-node-meta` command-line flag](#_node_meta) for more information. +* `node_meta` Available in Consul 0.7.3 and later, + This object allows associating arbitrary metadata key/value pairs with the local node, which can + then be used for filtering results from certain catalog endpoints. See the + [`-node-meta` command-line flag](#_node_meta) for more information. + + ```javascript + { + "node_meta": { + "instance_type": "t2.medium" + } + } + ``` * `performance` Available in Consul 0.7 and later, this is a nested object that allows tuning the performance of different subsystems in