Merge pull request #3332 from hashicorp/issue_3322

This fixes #3322
This commit is contained in:
preetapan 2017-07-28 17:54:30 -05:00 committed by GitHub
commit 0f494d8b86
7 changed files with 212 additions and 181 deletions

View File

@ -5,7 +5,6 @@ import (
"crypto/sha512"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
@ -30,7 +29,6 @@ import (
"github.com/hashicorp/consul/logger"
"github.com/hashicorp/consul/types"
"github.com/hashicorp/consul/watch"
"github.com/hashicorp/go-sockaddr/template"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/raft"
"github.com/hashicorp/serf/coordinate"
@ -228,55 +226,6 @@ func New(c *Config) (*Agent, error) {
httpAddrs: httpAddrs,
tokens: new(token.Store),
}
if err := a.resolveTmplAddrs(); err != nil {
return nil, err
}
// Try to get an advertise address
switch {
case a.config.AdvertiseAddr != "":
ipStr, err := parseSingleIPTemplate(a.config.AdvertiseAddr)
if err != nil {
return nil, fmt.Errorf("Advertise address resolution failed: %v", err)
}
if net.ParseIP(ipStr) == nil {
return nil, fmt.Errorf("Failed to parse advertise address: %v", ipStr)
}
a.config.AdvertiseAddr = ipStr
case a.config.BindAddr != "" && !ipaddr.IsAny(a.config.BindAddr):
a.config.AdvertiseAddr = a.config.BindAddr
default:
ip, err := consul.GetPrivateIP()
if ipaddr.IsAnyV6(a.config.BindAddr) {
ip, err = consul.GetPublicIPv6()
}
if err != nil {
return nil, fmt.Errorf("Failed to get advertise address: %v", err)
}
a.config.AdvertiseAddr = ip.String()
}
// Try to get an advertise address for the wan
if a.config.AdvertiseAddrWan != "" {
ipStr, err := parseSingleIPTemplate(a.config.AdvertiseAddrWan)
if err != nil {
return nil, fmt.Errorf("Advertise WAN address resolution failed: %v", err)
}
if net.ParseIP(ipStr) == nil {
return nil, fmt.Errorf("Failed to parse advertise address for WAN: %v", ipStr)
}
a.config.AdvertiseAddrWan = ipStr
} else {
a.config.AdvertiseAddrWan = a.config.AdvertiseAddr
}
// Create the default set of tagged addresses.
a.config.TaggedAddresses = map[string]string{
"lan": a.config.AdvertiseAddr,
"wan": a.config.AdvertiseAddrWan,
}
// Set up the initial state of the token store based on the config.
a.tokens.UpdateUserToken(a.config.ACLToken)
@ -820,113 +769,6 @@ func (a *Agent) consulConfig() (*consul.Config, error) {
return base, nil
}
// parseSingleIPTemplate is used as a helper function to parse out a single IP
// address from a config parameter.
func parseSingleIPTemplate(ipTmpl string) (string, error) {
out, err := template.Parse(ipTmpl)
if err != nil {
return "", fmt.Errorf("Unable to parse address template %q: %v", ipTmpl, err)
}
ips := strings.Split(out, " ")
switch len(ips) {
case 0:
return "", errors.New("No addresses found, please configure one.")
case 1:
return ips[0], nil
default:
return "", fmt.Errorf("Multiple addresses found (%q), please configure one.", out)
}
}
// resolveTmplAddrs iterates over the myriad of addresses in the agent's config
// and performs go-sockaddr/template Parse on each known address in case the
// user specified a template config for any of their values.
func (a *Agent) resolveTmplAddrs() error {
if a.config.AdvertiseAddr != "" {
ipStr, err := parseSingleIPTemplate(a.config.AdvertiseAddr)
if err != nil {
return fmt.Errorf("Advertise address resolution failed: %v", err)
}
a.config.AdvertiseAddr = ipStr
}
if a.config.Addresses.DNS != "" {
ipStr, err := parseSingleIPTemplate(a.config.Addresses.DNS)
if err != nil {
return fmt.Errorf("DNS address resolution failed: %v", err)
}
a.config.Addresses.DNS = ipStr
}
if a.config.Addresses.HTTP != "" {
ipStr, err := parseSingleIPTemplate(a.config.Addresses.HTTP)
if err != nil {
return fmt.Errorf("HTTP address resolution failed: %v", err)
}
a.config.Addresses.HTTP = ipStr
}
if a.config.Addresses.HTTPS != "" {
ipStr, err := parseSingleIPTemplate(a.config.Addresses.HTTPS)
if err != nil {
return fmt.Errorf("HTTPS address resolution failed: %v", err)
}
a.config.Addresses.HTTPS = ipStr
}
if a.config.AdvertiseAddrWan != "" {
ipStr, err := parseSingleIPTemplate(a.config.AdvertiseAddrWan)
if err != nil {
return fmt.Errorf("Advertise WAN address resolution failed: %v", err)
}
a.config.AdvertiseAddrWan = ipStr
}
if a.config.BindAddr != "" {
ipStr, err := parseSingleIPTemplate(a.config.BindAddr)
if err != nil {
return fmt.Errorf("Bind address resolution failed: %v", err)
}
a.config.BindAddr = ipStr
}
if a.config.ClientAddr != "" {
ipStr, err := parseSingleIPTemplate(a.config.ClientAddr)
if err != nil {
return fmt.Errorf("Client address resolution failed: %v", err)
}
a.config.ClientAddr = ipStr
}
if a.config.SerfLanBindAddr != "" {
ipStr, err := parseSingleIPTemplate(a.config.SerfLanBindAddr)
if err != nil {
return fmt.Errorf("Serf LAN Address resolution failed: %v", err)
}
a.config.SerfLanBindAddr = ipStr
}
if a.config.SerfWanBindAddr != "" {
ipStr, err := parseSingleIPTemplate(a.config.SerfWanBindAddr)
if err != nil {
return fmt.Errorf("Serf WAN Address resolution failed: %v", err)
}
a.config.SerfWanBindAddr = ipStr
}
// Parse all tagged addresses
for k, v := range a.config.TaggedAddresses {
ipStr, err := parseSingleIPTemplate(v)
if err != nil {
return fmt.Errorf("%s address resolution failed: %v", k, err)
}
a.config.TaggedAddresses[k] = ipStr
}
return nil
}
// makeRandomID will generate a random UUID for a node.
func (a *Agent) makeRandomID() (string, error) {
id, err := uuid.GenerateUUID()

View File

@ -117,6 +117,7 @@ func TestAgent_CheckAdvertiseAddrsSettings(t *testing.T) {
cfg.AdvertiseAddrs.SerfLan, _ = net.ResolveTCPAddr("tcp", "127.0.0.42:1233")
cfg.AdvertiseAddrs.SerfWan, _ = net.ResolveTCPAddr("tcp", "127.0.0.43:1234")
cfg.AdvertiseAddrs.RPC, _ = net.ResolveTCPAddr("tcp", "127.0.0.44:1235")
cfg.SetupTaggedAndAdvertiseAddrs()
a := NewTestAgent(t.Name(), cfg)
defer a.Shutdown()

View File

@ -16,10 +16,12 @@ import (
"github.com/hashicorp/consul/agent/consul"
"github.com/hashicorp/consul/agent/consul/structs"
"github.com/hashicorp/consul/ipaddr"
"github.com/hashicorp/consul/lib"
"github.com/hashicorp/consul/tlsutil"
"github.com/hashicorp/consul/types"
"github.com/hashicorp/consul/watch"
"github.com/hashicorp/go-sockaddr/template"
"github.com/mitchellh/mapstructure"
)
@ -2121,6 +2123,101 @@ func ReadConfigPaths(paths []string) (*Config, error) {
return result, nil
}
// ResolveTmplAddrs iterates over the myriad of addresses in the agent's config
// and performs go-sockaddr/template Parse on each known address in case the
// user specified a template config for any of their values.
func (c *Config) ResolveTmplAddrs() (err error) {
parse := func(addr *string, socketAllowed bool, name string) {
if *addr == "" || err != nil {
return
}
var ip string
ip, err = parseSingleIPTemplate(*addr)
if err != nil {
err = fmt.Errorf("Resolution of %s failed: %v", name, err)
return
}
ipAddr := net.ParseIP(ip)
if !socketAllowed && ipAddr == nil {
err = fmt.Errorf("Failed to parse %s: %v", name, ip)
return
}
if socketAllowed && socketPath(ip) == "" && ipAddr == nil {
err = fmt.Errorf("Failed to parse %s, %q is not a valid IP address or socket", name, ip)
return
}
*addr = ip
}
if c == nil {
return
}
parse(&c.Addresses.DNS, true, "DNS address")
parse(&c.Addresses.HTTP, true, "HTTP address")
parse(&c.Addresses.HTTPS, true, "HTTPS address")
parse(&c.AdvertiseAddr, false, "Advertise address")
parse(&c.AdvertiseAddrWan, false, "Advertise WAN address")
parse(&c.BindAddr, true, "Bind address")
parse(&c.ClientAddr, true, "Client address")
parse(&c.SerfLanBindAddr, false, "Serf LAN address")
parse(&c.SerfWanBindAddr, false, "Serf WAN address")
return
}
// SetupTaggedAndAdvertiseAddrs configures advertise addresses and sets up a map of tagged addresses
func (cfg *Config) SetupTaggedAndAdvertiseAddrs() error {
if cfg.AdvertiseAddr == "" {
switch {
case cfg.BindAddr != "" && !ipaddr.IsAny(cfg.BindAddr):
cfg.AdvertiseAddr = cfg.BindAddr
default:
ip, err := consul.GetPrivateIP()
if ipaddr.IsAnyV6(cfg.BindAddr) {
ip, err = consul.GetPublicIPv6()
}
if err != nil {
return fmt.Errorf("Failed to get advertise address: %v", err)
}
cfg.AdvertiseAddr = ip.String()
}
}
// Try to get an advertise address for the wan
if cfg.AdvertiseAddrWan == "" {
cfg.AdvertiseAddrWan = cfg.AdvertiseAddr
}
// Create the default set of tagged addresses.
cfg.TaggedAddresses = map[string]string{
"lan": cfg.AdvertiseAddr,
"wan": cfg.AdvertiseAddrWan,
}
return nil
}
// parseSingleIPTemplate is used as a helper function to parse out a single IP
// address from a config parameter.
func parseSingleIPTemplate(ipTmpl string) (string, error) {
out, err := template.Parse(ipTmpl)
if err != nil {
return "", fmt.Errorf("Unable to parse address template %q: %v", ipTmpl, err)
}
ips := strings.Split(out, " ")
switch len(ips) {
case 0:
return "", errors.New("No addresses found, please configure one.")
case 1:
return ips[0], nil
default:
return "", fmt.Errorf("Multiple addresses found (%q), please configure one.", out)
}
}
// Implement the sort interface for dirEnts
func (d dirEnts) Len() int {
return len(d)

View File

@ -50,18 +50,34 @@ func TestConfigEncryptBytes(t *testing.T) {
func TestDecodeConfig(t *testing.T) {
tests := []struct {
desc string
in string
c *Config
err error
desc string
in string
c *Config
err error
parseTemplateErr error
}{
// special flows
{
in: `{"bad": "no way jose"}`,
err: errors.New("Config has invalid keys: bad"),
},
{
in: `{"advertise_addr":"unix:///path/to/file"}`,
parseTemplateErr: errors.New("Failed to parse Advertise address: unix:///path/to/file"),
c: &Config{AdvertiseAddr: "unix:///path/to/file"},
},
{
in: `{"advertise_addr_wan":"unix:///path/to/file"}`,
parseTemplateErr: errors.New("Failed to parse Advertise WAN address: unix:///path/to/file"),
c: &Config{AdvertiseAddrWan: "unix:///path/to/file"},
},
{
in: `{"addresses":{"http":"notunix://blah"}}`,
parseTemplateErr: errors.New("Failed to parse HTTP address, \"notunix://blah\" is not a valid IP address or socket"),
c: &Config{Addresses: AddressConfig{HTTP: "notunix://blah"}},
},
// happy flows in alphabeical order
// happy flows in alphabetical order
{
in: `{"acl_agent_master_token":"a"}`,
c: &Config{ACLAgentMasterToken: "a"},
@ -103,28 +119,56 @@ func TestDecodeConfig(t *testing.T) {
c: &Config{ACLTTL: 2 * time.Second, ACLTTLRaw: "2s"},
},
{
in: `{"addresses":{"dns":"a"}}`,
c: &Config{Addresses: AddressConfig{DNS: "a"}},
in: `{"addresses":{"dns":"1.2.3.4"}}`,
c: &Config{Addresses: AddressConfig{DNS: "1.2.3.4"}},
},
{
in: `{"addresses":{"http":"a"}}`,
c: &Config{Addresses: AddressConfig{HTTP: "a"}},
in: `{"addresses":{"dns":"{{\"1.2.3.4\"}}"}}`,
c: &Config{Addresses: AddressConfig{DNS: "1.2.3.4"}},
},
{
in: `{"addresses":{"https":"a"}}`,
c: &Config{Addresses: AddressConfig{HTTPS: "a"}},
in: `{"addresses":{"http":"1.2.3.4"}}`,
c: &Config{Addresses: AddressConfig{HTTP: "1.2.3.4"}},
},
{
in: `{"addresses":{"http":"unix:///var/foo/bar"}}`,
c: &Config{Addresses: AddressConfig{HTTP: "unix:///var/foo/bar"}},
},
{
in: `{"addresses":{"http":"{{\"1.2.3.4\"}}"}}`,
c: &Config{Addresses: AddressConfig{HTTP: "1.2.3.4"}},
},
{
in: `{"addresses":{"https":"1.2.3.4"}}`,
c: &Config{Addresses: AddressConfig{HTTPS: "1.2.3.4"}},
},
{
in: `{"addresses":{"https":"unix:///var/foo/bar"}}`,
c: &Config{Addresses: AddressConfig{HTTPS: "unix:///var/foo/bar"}},
},
{
in: `{"addresses":{"https":"{{\"1.2.3.4\"}}"}}`,
c: &Config{Addresses: AddressConfig{HTTPS: "1.2.3.4"}},
},
{
in: `{"addresses":{"rpc":"a"}}`,
c: &Config{Addresses: AddressConfig{RPC: "a"}},
},
{
in: `{"advertise_addr":"a"}`,
c: &Config{AdvertiseAddr: "a"},
in: `{"advertise_addr":"1.2.3.4"}`,
c: &Config{AdvertiseAddr: "1.2.3.4"},
},
{
in: `{"advertise_addr_wan":"a"}`,
c: &Config{AdvertiseAddrWan: "a"},
in: `{"advertise_addr":"{{\"1.2.3.4\"}}"}`,
c: &Config{AdvertiseAddr: "1.2.3.4"},
},
{
in: `{"advertise_addr_wan":"1.2.3.4"}`,
c: &Config{AdvertiseAddrWan: "1.2.3.4"},
},
{
in: `{"advertise_addr_wan":"{{\"1.2.3.4\"}}"}`,
c: &Config{AdvertiseAddrWan: "1.2.3.4"},
},
{
in: `{"advertise_addrs":{"rpc":"1.2.3.4:5678"}}`,
@ -202,8 +246,12 @@ func TestDecodeConfig(t *testing.T) {
c: &Config{Autopilot: Autopilot{CleanupDeadServers: Bool(true)}},
},
{
in: `{"bind_addr":"a"}`,
c: &Config{BindAddr: "a"},
in: `{"bind_addr":"1.2.3.4"}`,
c: &Config{BindAddr: "1.2.3.4"},
},
{
in: `{"bind_addr":"{{\"1.2.3.4\"}}"}`,
c: &Config{BindAddr: "1.2.3.4"},
},
{
in: `{"bootstrap":true}`,
@ -230,8 +278,12 @@ func TestDecodeConfig(t *testing.T) {
c: &Config{CertFile: "a"},
},
{
in: `{"client_addr":"a"}`,
c: &Config{ClientAddr: "a"},
in: `{"client_addr":"1.2.3.4"}`,
c: &Config{ClientAddr: "1.2.3.4"},
},
{
in: `{"client_addr":"{{\"1.2.3.4\"}}"}`,
c: &Config{ClientAddr: "1.2.3.4"},
},
{
in: `{"data_dir":"a"}`,
@ -531,12 +583,30 @@ func TestDecodeConfig(t *testing.T) {
c: &Config{RetryMaxAttemptsWan: 123},
},
{
in: `{"serf_lan_bind":"a"}`,
c: &Config{SerfLanBindAddr: "a"},
in: `{"serf_lan_bind":"1.2.3.4"}`,
c: &Config{SerfLanBindAddr: "1.2.3.4"},
},
{
in: `{"serf_wan_bind":"a"}`,
c: &Config{SerfWanBindAddr: "a"},
in: `{"serf_lan_bind":"unix:///var/foo/bar"}`,
c: &Config{SerfLanBindAddr: "unix:///var/foo/bar"},
parseTemplateErr: errors.New("Failed to parse Serf LAN address: unix:///var/foo/bar"),
},
{
in: `{"serf_lan_bind":"{{\"1.2.3.4\"}}"}`,
c: &Config{SerfLanBindAddr: "1.2.3.4"},
},
{
in: `{"serf_wan_bind":"1.2.3.4"}`,
c: &Config{SerfWanBindAddr: "1.2.3.4"},
},
{
in: `{"serf_wan_bind":"unix:///var/foo/bar"}`,
c: &Config{SerfWanBindAddr: "unix:///var/foo/bar"},
parseTemplateErr: errors.New("Failed to parse Serf WAN address: unix:///var/foo/bar"),
},
{
in: `{"serf_wan_bind":"{{\"1.2.3.4\"}}"}`,
c: &Config{SerfWanBindAddr: "1.2.3.4"},
},
{
in: `{"server":true}`,
@ -1165,6 +1235,10 @@ func TestDecodeConfig(t *testing.T) {
if got, want := err, tt.err; !reflect.DeepEqual(got, want) {
t.Fatalf("got error %v want %v", got, want)
}
err = c.ResolveTmplAddrs()
if got, want := err, tt.parseTemplateErr; !reflect.DeepEqual(got, want) {
t.Fatalf("got error %v on ResolveTmplAddrs, expected %v", err, want)
}
got, want := c, tt.c
verify.Values(t, "", got, want)
})

View File

@ -334,6 +334,7 @@ func TestConfig() *Config {
ccfg.CoordinateUpdatePeriod = 100 * time.Millisecond
ccfg.ServerHealthInterval = 10 * time.Millisecond
cfg.SetupTaggedAndAdvertiseAddrs()
return cfg
}

View File

@ -462,6 +462,16 @@ func (cmd *AgentCommand) readConfig() *agent.Config {
cfg.Version = cmd.Version
cfg.VersionPrerelease = cmd.VersionPrerelease
if err := cfg.ResolveTmplAddrs(); err != nil {
cmd.UI.Error(fmt.Sprintf("Failed to parse config: %v", err))
return nil
}
if err := cfg.SetupTaggedAndAdvertiseAddrs(); err != nil {
cmd.UI.Error(fmt.Sprintf("Failed to set up tagged and advertise addresses: %v", err))
return nil
}
return cfg
}

View File

@ -176,6 +176,7 @@ func TestReadCliConfig(t *testing.T) {
args: []string{
"-data-dir", tmpDir,
"-node", `"a"`,
"-bind", "1.2.3.4",
"-advertise-wan", "1.2.3.4",
"-serf-wan-bind", "4.3.2.1",
"-serf-lan-bind", "4.3.2.2",
@ -207,6 +208,7 @@ func TestReadCliConfig(t *testing.T) {
"-data-dir", tmpDir,
"-node-meta", "somekey:somevalue",
"-node-meta", "otherkey:othervalue",
"-bind", "1.2.3.4",
},
ShutdownCh: shutdownCh,
BaseCommand: baseCommand(cli.NewMockUi()),
@ -229,6 +231,7 @@ func TestReadCliConfig(t *testing.T) {
"-node", `"server1"`,
"-server",
"-data-dir", tmpDir,
"-bind", "1.2.3.4",
},
ShutdownCh: shutdownCh,
BaseCommand: baseCommand(ui),
@ -256,6 +259,7 @@ func TestReadCliConfig(t *testing.T) {
args: []string{
"-data-dir", tmpDir,
"-node", `"client"`,
"-bind", "1.2.3.4",
},
ShutdownCh: shutdownCh,
BaseCommand: baseCommand(ui),
@ -301,6 +305,7 @@ func TestAgent_HostBasedIDs(t *testing.T) {
cmd := &AgentCommand{
args: []string{
"-data-dir", tmpDir,
"-bind", "127.0.0.1",
},
BaseCommand: baseCommand(cli.NewMockUi()),
}
@ -317,6 +322,7 @@ func TestAgent_HostBasedIDs(t *testing.T) {
args: []string{
"-data-dir", tmpDir,
"-disable-host-node-id=false",
"-bind", "127.0.0.1",
},
BaseCommand: baseCommand(cli.NewMockUi()),
}