Merge pull request #2280 from hashicorp/f-lan-tag

Adds missing TaggedAddress structures to Go API client, adapts for address translation.
This commit is contained in:
James Phillips 2016-08-16 11:35:00 -07:00 committed by GitHub
commit 59fa4b3ae3
15 changed files with 134 additions and 13 deletions

View File

@ -80,6 +80,9 @@ type QueryMeta struct {
// How long did the request take // How long did the request take
RequestTime time.Duration RequestTime time.Duration
// Is address translation enabled for HTTP responses on this agent
AddressTranslationEnabled bool
} }
// WriteMeta is used to return meta data about a write // WriteMeta is used to return meta data about a write
@ -542,6 +545,15 @@ func parseQueryMeta(resp *http.Response, q *QueryMeta) error {
default: default:
q.KnownLeader = false q.KnownLeader = false
} }
// Parse X-Consul-Translate-Addresses
switch header.Get("X-Consul-Translate-Addresses") {
case "true":
q.AddressTranslationEnabled = true
default:
q.AddressTranslationEnabled = false
}
return nil return nil
} }

View File

@ -306,6 +306,7 @@ func TestParseQueryMeta(t *testing.T) {
resp.Header.Set("X-Consul-Index", "12345") resp.Header.Set("X-Consul-Index", "12345")
resp.Header.Set("X-Consul-LastContact", "80") resp.Header.Set("X-Consul-LastContact", "80")
resp.Header.Set("X-Consul-KnownLeader", "true") resp.Header.Set("X-Consul-KnownLeader", "true")
resp.Header.Set("X-Consul-Translate-Addresses", "true")
qm := &QueryMeta{} qm := &QueryMeta{}
if err := parseQueryMeta(resp, qm); err != nil { if err := parseQueryMeta(resp, qm); err != nil {
@ -321,6 +322,9 @@ func TestParseQueryMeta(t *testing.T) {
if !qm.KnownLeader { if !qm.KnownLeader {
t.Fatalf("Bad: %v", qm) t.Fatalf("Bad: %v", qm)
} }
if !qm.AddressTranslationEnabled {
t.Fatalf("Bad: %v", qm)
}
} }
func TestAPI_UnixSocket(t *testing.T) { func TestAPI_UnixSocket(t *testing.T) {

View File

@ -1,13 +1,15 @@
package api package api
type Node struct { type Node struct {
Node string Node string
Address string Address string
TaggedAddresses map[string]string
} }
type CatalogService struct { type CatalogService struct {
Node string Node string
Address string Address string
TaggedAddresses map[string]string
ServiceID string ServiceID string
ServiceName string ServiceName string
ServiceAddress string ServiceAddress string
@ -22,11 +24,12 @@ type CatalogNode struct {
} }
type CatalogRegistration struct { type CatalogRegistration struct {
Node string Node string
Address string Address string
Datacenter string TaggedAddresses map[string]string
Service *AgentService Datacenter string
Check *AgentCheck Service *AgentService
Check *AgentCheck
} }
type CatalogDeregistration struct { type CatalogDeregistration struct {

View File

@ -51,6 +51,10 @@ func TestCatalog_Nodes(t *testing.T) {
return false, fmt.Errorf("Bad: %v", nodes) return false, fmt.Errorf("Bad: %v", nodes)
} }
if _, ok := nodes[0].TaggedAddresses["wan"]; !ok {
return false, fmt.Errorf("Bad: %v", nodes)
}
return true, nil return true, nil
}, func(err error) { }, func(err error) {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
@ -128,10 +132,15 @@ func TestCatalog_Node(t *testing.T) {
if meta.LastIndex == 0 { if meta.LastIndex == 0 {
return false, fmt.Errorf("Bad: %v", meta) return false, fmt.Errorf("Bad: %v", meta)
} }
if len(info.Services) == 0 { if len(info.Services) == 0 {
return false, fmt.Errorf("Bad: %v", info) return false, fmt.Errorf("Bad: %v", info)
} }
if _, ok := info.Node.TaggedAddresses["wan"]; !ok {
return false, fmt.Errorf("Bad: %v", info)
}
return true, nil return true, nil
}, func(err error) { }, func(err error) {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)

View File

@ -94,6 +94,9 @@ func TestHealth_Service(t *testing.T) {
if len(checks) == 0 { if len(checks) == 0 {
return false, fmt.Errorf("Bad: %v", checks) return false, fmt.Errorf("Bad: %v", checks)
} }
if _, ok := checks[0].Node.TaggedAddresses["wan"]; !ok {
return false, fmt.Errorf("Bad: %v", checks)
}
return true, nil return true, nil
}, func(err error) { }, func(err error) {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)

View File

@ -17,6 +17,9 @@ func TestPreparedQuery(t *testing.T) {
Datacenter: "dc1", Datacenter: "dc1",
Node: "foobar", Node: "foobar",
Address: "192.168.10.10", Address: "192.168.10.10",
TaggedAddresses: map[string]string{
"wan": "127.0.0.1",
},
Service: &AgentService{ Service: &AgentService{
ID: "redis1", ID: "redis1",
Service: "redis", Service: "redis",
@ -96,6 +99,9 @@ func TestPreparedQuery(t *testing.T) {
if len(results.Nodes) != 1 || results.Nodes[0].Node.Node != "foobar" { if len(results.Nodes) != 1 || results.Nodes[0].Node.Node != "foobar" {
t.Fatalf("bad: %v", results) t.Fatalf("bad: %v", results)
} }
if wan, ok := results.Nodes[0].Node.TaggedAddresses["wan"]; !ok || wan != "127.0.0.1" {
t.Fatalf("bad: %v", results)
}
// Execute by name. // Execute by name.
results, _, err = query.Execute("my-query", nil) results, _, err = query.Execute("my-query", nil)
@ -105,6 +111,9 @@ func TestPreparedQuery(t *testing.T) {
if len(results.Nodes) != 1 || results.Nodes[0].Node.Node != "foobar" { if len(results.Nodes) != 1 || results.Nodes[0].Node.Node != "foobar" {
t.Fatalf("bad: %v", results) t.Fatalf("bad: %v", results)
} }
if wan, ok := results.Nodes[0].Node.TaggedAddresses["wan"]; !ok || wan != "127.0.0.1" {
t.Fatalf("bad: %v", results)
}
// Delete it. // Delete it.
_, err = query.Delete(def.ID, nil) _, err = query.Delete(def.ID, nil)

View File

@ -170,6 +170,7 @@ func Create(config *Config, logOutput io.Writer) (*Agent, error) {
// Create the default set of tagged addresses. // Create the default set of tagged addresses.
config.TaggedAddresses = map[string]string{ config.TaggedAddresses = map[string]string{
"lan": config.AdvertiseAddr,
"wan": config.AdvertiseAddrWan, "wan": config.AdvertiseAddrWan,
} }

View File

@ -183,6 +183,7 @@ func TestAgent_CheckAdvertiseAddrsSettings(t *testing.T) {
t.Fatalf("RPC is not properly set to %v: %s", c.AdvertiseAddrs.RPC, rpc) t.Fatalf("RPC is not properly set to %v: %s", c.AdvertiseAddrs.RPC, rpc)
} }
expected := map[string]string{ expected := map[string]string{
"lan": agent.config.AdvertiseAddr,
"wan": agent.config.AdvertiseAddrWan, "wan": agent.config.AdvertiseAddrWan,
} }
if !reflect.DeepEqual(agent.config.TaggedAddresses, expected) { if !reflect.DeepEqual(agent.config.TaggedAddresses, expected) {

View File

@ -329,6 +329,7 @@ func (s *HTTPServer) registerHandlers(enableDebug bool) {
func (s *HTTPServer) wrap(handler func(resp http.ResponseWriter, req *http.Request) (interface{}, error)) func(resp http.ResponseWriter, req *http.Request) { func (s *HTTPServer) wrap(handler func(resp http.ResponseWriter, req *http.Request) (interface{}, error)) func(resp http.ResponseWriter, req *http.Request) {
f := func(resp http.ResponseWriter, req *http.Request) { f := func(resp http.ResponseWriter, req *http.Request) {
setHeaders(resp, s.agent.config.HTTPAPIResponseHeaders) setHeaders(resp, s.agent.config.HTTPAPIResponseHeaders)
setTranslateAddr(resp, s.agent.config.TranslateWanAddrs)
// Obfuscate any tokens from appearing in the logs // Obfuscate any tokens from appearing in the logs
formVals, err := url.ParseQuery(req.URL.RawQuery) formVals, err := url.ParseQuery(req.URL.RawQuery)
@ -373,6 +374,7 @@ func (s *HTTPServer) wrap(handler func(resp http.ResponseWriter, req *http.Reque
if strings.Contains(errMsg, "Permission denied") || strings.Contains(errMsg, "ACL not found") { if strings.Contains(errMsg, "Permission denied") || strings.Contains(errMsg, "ACL not found") {
code = http.StatusForbidden // 403 code = http.StatusForbidden // 403
} }
resp.WriteHeader(code) resp.WriteHeader(code)
resp.Write([]byte(err.Error())) resp.Write([]byte(err.Error()))
return return
@ -452,6 +454,14 @@ func decodeBody(req *http.Request, out interface{}, cb func(interface{}) error)
return mapstructure.Decode(raw, out) return mapstructure.Decode(raw, out)
} }
// setTranslateAddr is used to set the address translation header. This is only
// present if the feature is active.
func setTranslateAddr(resp http.ResponseWriter, active bool) {
if active {
resp.Header().Set("X-Consul-Translate-Addresses", "true")
}
}
// setIndex is used to set the index response header // setIndex is used to set the index response header
func setIndex(resp http.ResponseWriter, index uint64) { func setIndex(resp http.ResponseWriter, index uint64) {
resp.Header().Set("X-Consul-Index", strconv.FormatUint(index, 10)) resp.Header().Set("X-Consul-Index", strconv.FormatUint(index, 10))

View File

@ -223,6 +223,51 @@ func TestSetMeta(t *testing.T) {
} }
} }
func TestHTTPAPI_TranslateAddrHeader(t *testing.T) {
// Header should not be present if address translation is off.
{
dir, srv := makeHTTPServer(t)
defer os.RemoveAll(dir)
defer srv.Shutdown()
defer srv.agent.Shutdown()
resp := httptest.NewRecorder()
handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
return nil, nil
}
req, _ := http.NewRequest("GET", "/v1/agent/self", nil)
srv.wrap(handler)(resp, req)
translate := resp.Header().Get("X-Consul-Translate-Addresses")
if translate != "" {
t.Fatalf("bad: expected %q, got %q", "", translate)
}
}
// Header should be set to true if it's turned on.
{
dir, srv := makeHTTPServer(t)
srv.agent.config.TranslateWanAddrs = true
defer os.RemoveAll(dir)
defer srv.Shutdown()
defer srv.agent.Shutdown()
resp := httptest.NewRecorder()
handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
return nil, nil
}
req, _ := http.NewRequest("GET", "/v1/agent/self", nil)
srv.wrap(handler)(resp, req)
translate := resp.Header().Get("X-Consul-Translate-Addresses")
if translate != "true" {
t.Fatalf("bad: expected %q, got %q", "true", translate)
}
}
}
func TestHTTPAPIResponseHeaders(t *testing.T) { func TestHTTPAPIResponseHeaders(t *testing.T) {
dir, srv := makeHTTPServer(t) dir, srv := makeHTTPServer(t)
srv.agent.config.HTTPAPIResponseHeaders = map[string]string{ srv.agent.config.HTTPAPIResponseHeaders = map[string]string{

View File

@ -96,3 +96,12 @@ configuration option. However, the token can also be specified per-request
by using the `X-Consul-Token` request header or the `token` querystring by using the `X-Consul-Token` request header or the `token` querystring
parameter. The request header takes precedence over the default token, and parameter. The request header takes precedence over the default token, and
the querystring parameter takes precedence over everything. the querystring parameter takes precedence over everything.
## <a id="translate_header"></a>Translated Addresses
Consul 0.7 added the ability to translate addresses in HTTP response based on the configuration
setting for [`translate_wan_addrs`](/docs/agent/options.html#translate_wan_addrs). In order to
allow clients to know if address translation is in effect, the `X-Consul-Translate-Addresses`
header will be added if translation is enabled, and will have a value of `true`. If translation
is not enabled then this header will not be present.

View File

@ -41,7 +41,8 @@ body must look something like:
"Node": "foobar", "Node": "foobar",
"Address": "192.168.10.10", "Address": "192.168.10.10",
"TaggedAddresses": { "TaggedAddresses": {
"wan": "127.0.0.1" "lan": "192.168.10.10",
"wan": "10.0.10.10"
}, },
"Service": { "Service": {
"ID": "redis1", "ID": "redis1",
@ -69,7 +70,8 @@ requires `Node` and `Address` to be provided while `Datacenter` will be defaulte
to match that of the agent. If only those are provided, the endpoint will register to match that of the agent. If only those are provided, the endpoint will register
the node with the catalog. `TaggedAddresses` can be used in conjunction with the the node with the catalog. `TaggedAddresses` can be used in conjunction with the
[`translate_wan_addrs`](/docs/agent/options.html#translate_wan_addrs) configuration [`translate_wan_addrs`](/docs/agent/options.html#translate_wan_addrs) configuration
option. Currently only the "wan" tag is supported. 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.
If the `Service` key is provided, the service will also be registered. If 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. `ID` is not provided, it will be defaulted to the value of the `Service.Service` property.
@ -200,6 +202,7 @@ It returns a JSON body like this:
"Node": "baz", "Node": "baz",
"Address": "10.1.10.11", "Address": "10.1.10.11",
"TaggedAddresses": { "TaggedAddresses": {
"lan": "10.1.10.11",
"wan": "10.1.10.11" "wan": "10.1.10.11"
} }
}, },
@ -207,6 +210,7 @@ It returns a JSON body like this:
"Node": "foobar", "Node": "foobar",
"Address": "10.1.10.12", "Address": "10.1.10.12",
"TaggedAddresses": { "TaggedAddresses": {
"lan": "10.1.10.11",
"wan": "10.1.10.12" "wan": "10.1.10.12"
} }
} }
@ -287,6 +291,7 @@ It returns a JSON body like this:
"Node": "foobar", "Node": "foobar",
"Address": "10.1.10.12", "Address": "10.1.10.12",
"TaggedAddresses": { "TaggedAddresses": {
"lan": "10.1.10.12",
"wan": "10.1.10.12" "wan": "10.1.10.12"
} }
}, },

View File

@ -129,6 +129,7 @@ It returns a JSON body like this:
"Node": "foobar", "Node": "foobar",
"Address": "10.1.10.12", "Address": "10.1.10.12",
"TaggedAddresses": { "TaggedAddresses": {
"lan": "10.1.10.12",
"wan": "10.1.10.12" "wan": "10.1.10.12"
} }
}, },

View File

@ -399,7 +399,11 @@ a JSON body will be returned like this:
{ {
"Node": { "Node": {
"Node": "foobar", "Node": "foobar",
"Address": "10.1.10.12" "Address": "10.1.10.12",
"TaggedAddresses": {
"lan": "10.1.10.12",
"wan": "10.1.10.12"
}
}, },
"Service": { "Service": {
"ID": "redis", "ID": "redis",

View File

@ -756,9 +756,14 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
default. default.
<br> <br>
<br> <br>
Starting in Consul 0.7 and later, node addresses in responses to the following HTTP endpoints will Starting in Consul 0.7 and later, node addresses in responses to HTTP requests will also prefer a
prefer a node's configured <a href="#_advertise-wan">WAN address</a> when querying for a node in a node's configured <a href="#_advertise-wan">WAN address</a> when querying for a node in a remote
remote datacenter: datacenter. An [`X-Consul-Translate-Addresses`](/docs/agent/http.html#translate_header) header
will be present on all responses when translation is enabled to help clients know that the addresses
may be translated. The `TaggedAddresses` field in responses also have a `lan` address for clients that
need knowledge of that address, regardless of translation.
<br>
<br>The following endpoints translate addresses:
<br> <br>
* [`/v1/catalog/nodes`](/docs/agent/http/catalog.html#catalog_nodes) * [`/v1/catalog/nodes`](/docs/agent/http/catalog.html#catalog_nodes)
* [`/v1/catalog/node/<node>`](/docs/agent/http/catalog.html#catalog_node) * [`/v1/catalog/node/<node>`](/docs/agent/http/catalog.html#catalog_node)