diff --git a/command/agent/agent.go b/command/agent/agent.go index fd712f6a69..99b4d25b5d 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -545,6 +545,22 @@ func (a *Agent) WANMembers() []serf.Member { } } +// CanServersUnderstandProtocol checks to see if all the servers understand the +// given protocol version. +func (a *Agent) CanServersUnderstandProtocol(version uint8) bool { + numServers, numWhoGrok := 0, 0 + members := a.LANMembers() + for _, member := range members { + if member.Tags["role"] == "consul" { + numServers++ + if member.ProtocolMax >= version { + numWhoGrok++ + } + } + } + return (numServers > 0) && (numWhoGrok == numServers) +} + // StartSync is called once Services and Checks are registered. // This is called to prevent a race between clients and the anti-entropy routines func (a *Agent) StartSync() { @@ -562,6 +578,16 @@ func (a *Agent) ResumeSync() { a.state.Resume() } +// Returns the coordinate of this node in the local pool (assumes coordinates +// are enabled, so check that before calling). +func (a *Agent) GetCoordinate() (*coordinate.Coordinate, error) { + if a.config.Server { + return a.server.GetLANCoordinate() + } else { + return a.client.GetCoordinate() + } +} + // sendCoordinate is a long-running loop that periodically sends our coordinate // to the server. Closing the agent's shutdownChannel will cause this to exit. func (a *Agent) sendCoordinate() { @@ -573,14 +599,13 @@ func (a *Agent) sendCoordinate() { select { case <-time.After(intv): + if !a.CanServersUnderstandProtocol(3) { + continue + } + var c *coordinate.Coordinate var err error - if a.config.Server { - c, err = a.server.GetLANCoordinate() - } else { - c, err = a.client.GetCoordinate() - } - if err != nil { + if c, err = a.GetCoordinate(); err != nil { a.logger.Printf("[ERR] agent: failed to get coordinate: %s", err) continue } diff --git a/command/agent/agent_endpoint.go b/command/agent/agent_endpoint.go index 53c1c34b99..a816df8e9f 100644 --- a/command/agent/agent_endpoint.go +++ b/command/agent/agent_endpoint.go @@ -17,18 +17,17 @@ type AgentSelf struct { } func (s *HTTPServer) AgentSelf(resp http.ResponseWriter, req *http.Request) (interface{}, error) { - var coord *coordinate.Coordinate + var c *coordinate.Coordinate if !s.agent.config.DisableCoordinates { var err error - coord, err = s.agent.server.GetLANCoordinate() - if err != nil { + if c, err = s.agent.GetCoordinate(); err != nil { return nil, err } } return AgentSelf{ Config: s.agent.config, - Coord: coord, + Coord: c, Member: s.agent.LocalMember(), }, nil } diff --git a/command/agent/agent_test.go b/command/agent/agent_test.go index b6237bac66..cf1f6c0b68 100644 --- a/command/agent/agent_test.go +++ b/command/agent/agent_test.go @@ -1581,3 +1581,51 @@ func TestAgent_purgeCheckState(t *testing.T) { t.Fatalf("should have removed file") } } + +func TestAgent_GetCoordinate(t *testing.T) { + check := func(server bool) { + config := nextConfig() + config.Server = server + dir, agent := makeAgent(t, config) + defer os.RemoveAll(dir) + defer agent.Shutdown() + + // This doesn't verify the returned coordinate, but it makes + // sure that the agent chooses the correct Serf instance, + // depending on how it's configured as a client or a server. + // If it chooses the wrong one, this will crash. + if _, err := agent.GetCoordinate(); err != nil { + t.Fatalf("err: %s", err) + } + } + + check(true) + check(false) +} + +func TestAgent_CanServersUnderstandProtocol(t *testing.T) { + config := nextConfig() + dir, agent := makeAgent(t, config) + defer os.RemoveAll(dir) + defer agent.Shutdown() + + min := uint8(consul.ProtocolVersionMin) + if !agent.CanServersUnderstandProtocol(min) { + t.Fatalf("should grok %d", min) + } + + max := uint8(consul.ProtocolVersionMax) + if !agent.CanServersUnderstandProtocol(max) { + t.Fatalf("should grok %d", max) + } + + current := uint8(config.Protocol) + if !agent.CanServersUnderstandProtocol(current) { + t.Fatalf("should grok %d", current) + } + + future := max + 1 + if agent.CanServersUnderstandProtocol(future) { + t.Fatalf("should not grok %d", future) + } +} diff --git a/command/agent/config.go b/command/agent/config.go index f59bbc9b2c..e7b3322bde 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -478,7 +478,7 @@ func DefaultConfig() *Config { }, StatsitePrefix: "consul", SyslogFacility: "LOCAL0", - Protocol: consul.ProtocolVersionMax, + Protocol: consul.ProtocolVersion2Compatible, CheckUpdateInterval: 5 * time.Minute, AEInterval: time.Minute, DisableCoordinates: false, diff --git a/consul/config.go b/consul/config.go index e5b9dd36fa..a1d39a1b71 100644 --- a/consul/config.go +++ b/consul/config.go @@ -32,6 +32,7 @@ func init() { protocolVersionMap = map[uint8]uint8{ 1: 4, 2: 4, + 3: 4, } } @@ -267,7 +268,7 @@ func DefaultConfig() *Config { SerfLANConfig: serf.DefaultConfig(), SerfWANConfig: serf.DefaultConfig(), ReconcileInterval: 60 * time.Second, - ProtocolVersion: ProtocolVersionMax, + ProtocolVersion: ProtocolVersion2Compatible, ACLTTL: 30 * time.Second, ACLDefaultPolicy: "allow", ACLDownPolicy: "extend-cache", diff --git a/consul/server.go b/consul/server.go index cf53b00aa2..35da2c403f 100644 --- a/consul/server.go +++ b/consul/server.go @@ -28,7 +28,15 @@ import ( // protocol versions. const ( ProtocolVersionMin uint8 = 1 - ProtocolVersionMax = 2 + + // Version 3 added support for network coordinates but we kept the + // default protocol version at 2 to ease the transition to this new + // feature. A Consul agent speaking version 2 of the protocol will + // attempt to send its coordinates to a server who understands version + // 3 or greater. + ProtocolVersion2Compatible = 2 + + ProtocolVersionMax = 3 ) const (