Adds basic support for node IDs.
This commit is contained in:
parent
17c4754eac
commit
96bff003b7
|
@ -657,7 +657,7 @@ func TestAgent_Monitor(t *testing.T) {
|
|||
// Wait for the first log message and validate it
|
||||
select {
|
||||
case log := <-logCh:
|
||||
if !strings.Contains(log, "[INFO] raft: Initial configuration") {
|
||||
if !strings.Contains(log, "[INFO]") {
|
||||
t.Fatalf("bad: %q", log)
|
||||
}
|
||||
case <-time.After(10 * time.Second):
|
||||
|
|
|
@ -224,7 +224,6 @@ func Create(config *Config, logOutput io.Writer, logWriter *logger.LogWriter,
|
|||
shutdownCh: make(chan struct{}),
|
||||
endpoints: make(map[string]string),
|
||||
}
|
||||
|
||||
if err := agent.resolveTmplAddrs(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -236,6 +235,12 @@ func Create(config *Config, logOutput io.Writer, logWriter *logger.LogWriter,
|
|||
}
|
||||
agent.acls = acls
|
||||
|
||||
// Retrieve or generate the node ID before setting up the rest of the
|
||||
// agent, which depends on it.
|
||||
if err := agent.setupNodeID(config); err != nil {
|
||||
return nil, fmt.Errorf("Failed to setup node ID: %v", err)
|
||||
}
|
||||
|
||||
// Initialize the local state.
|
||||
agent.state.Init(config, agent.logger)
|
||||
|
||||
|
@ -303,6 +308,9 @@ func (a *Agent) consulConfig() *consul.Config {
|
|||
base = consul.DefaultConfig()
|
||||
}
|
||||
|
||||
// This is set when the agent starts up
|
||||
base.NodeID = a.config.NodeID
|
||||
|
||||
// Apply dev mode
|
||||
base.DevMode = a.config.DevMode
|
||||
|
||||
|
@ -600,6 +608,67 @@ func (a *Agent) setupClient() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// setupNodeID will pull the persisted node ID, if any, or create a random one
|
||||
// and persist it.
|
||||
func (a *Agent) setupNodeID(config *Config) error {
|
||||
// If they've configured a node ID manually then just use that, as
|
||||
// long as it's valid.
|
||||
if config.NodeID != "" {
|
||||
if _, err := uuid.ParseUUID(string(config.NodeID)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// For dev mode we have no filesystem access so just make a GUID.
|
||||
if a.config.DevMode {
|
||||
id, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.NodeID = types.NodeID(id)
|
||||
a.logger.Printf("[INFO] agent: Generated unique node ID %q for this agent (will not be persisted in dev mode)", config.NodeID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load saved state, if any. Since a user could edit this, we also
|
||||
// validate it.
|
||||
fileID := filepath.Join(config.DataDir, "node-id")
|
||||
if _, err := os.Stat(fileID); err == nil {
|
||||
rawID, err := ioutil.ReadFile(fileID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nodeID := strings.TrimSpace(string(rawID))
|
||||
if _, err := uuid.ParseUUID(nodeID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.NodeID = types.NodeID(nodeID)
|
||||
}
|
||||
|
||||
// If we still don't have a valid node ID, make one.
|
||||
if config.NodeID == "" {
|
||||
id, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := lib.EnsurePath(fileID, false); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(fileID, []byte(id), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.NodeID = types.NodeID(id)
|
||||
a.logger.Printf("[INFO] agent: Generated unique node ID %q for this agent (persisted)", config.NodeID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setupKeyrings is used to initialize and load keyrings during agent startup
|
||||
func (a *Agent) setupKeyrings(config *consul.Config) error {
|
||||
fileLAN := filepath.Join(a.config.DataDir, serfLANKeyring)
|
||||
|
|
|
@ -18,6 +18,8 @@ import (
|
|||
"github.com/hashicorp/consul/consul/structs"
|
||||
"github.com/hashicorp/consul/logger"
|
||||
"github.com/hashicorp/consul/testutil"
|
||||
"github.com/hashicorp/consul/types"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/raft"
|
||||
"strings"
|
||||
)
|
||||
|
@ -308,6 +310,62 @@ func TestAgent_ReconnectConfigSettings(t *testing.T) {
|
|||
}()
|
||||
}
|
||||
|
||||
func TestAgent_NodeID(t *testing.T) {
|
||||
c := nextConfig()
|
||||
dir, agent := makeAgent(t, c)
|
||||
defer os.RemoveAll(dir)
|
||||
defer agent.Shutdown()
|
||||
|
||||
// The auto-assigned ID should be valid.
|
||||
id := agent.consulConfig().NodeID
|
||||
if _, err := uuid.ParseUUID(string(id)); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Set an invalid ID via config.
|
||||
c.NodeID = types.NodeID("nope")
|
||||
err := agent.setupNodeID(c)
|
||||
if err == nil || !strings.Contains(err.Error(), "uuid string is wrong length") {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Set a valid ID via config.
|
||||
newID, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
c.NodeID = types.NodeID(newID)
|
||||
if err := agent.setupNodeID(c); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if id := agent.consulConfig().NodeID; string(id) != newID {
|
||||
t.Fatalf("bad: %q vs. %q", id, newID)
|
||||
}
|
||||
|
||||
// Set an invalid ID via the file.
|
||||
fileID := filepath.Join(c.DataDir, "node-id")
|
||||
if err := ioutil.WriteFile(fileID, []byte("adf4238a!882b!9ddc!4a9d!5b6758e4159e"), 0600); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
c.NodeID = ""
|
||||
err = agent.setupNodeID(c)
|
||||
if err == nil || !strings.Contains(err.Error(), "uuid is improperly formatted") {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Set a valid ID via the file.
|
||||
if err := ioutil.WriteFile(fileID, []byte("adf4238a-882b-9ddc-4a9d-5b6758e4159e"), 0600); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
c.NodeID = ""
|
||||
if err := agent.setupNodeID(c); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if id := agent.consulConfig().NodeID; string(id) != "adf4238a-882b-9ddc-4a9d-5b6758e4159e" {
|
||||
t.Fatalf("bad: %q vs. %q", id, newID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_AddService(t *testing.T) {
|
||||
dir, agent := makeAgent(t, nextConfig())
|
||||
defer os.RemoveAll(dir)
|
||||
|
|
|
@ -92,6 +92,7 @@ func (c *Command) readConfig() *Config {
|
|||
|
||||
cmdFlags.StringVar(&cmdConfig.LogLevel, "log-level", "", "log level")
|
||||
cmdFlags.StringVar(&cmdConfig.NodeName, "node", "", "node name")
|
||||
cmdFlags.StringVar((*string)(&cmdConfig.NodeID), "node-id", "", "node ID")
|
||||
cmdFlags.StringVar(&dcDeprecated, "dc", "", "node datacenter (deprecated: use 'datacenter' instead)")
|
||||
cmdFlags.StringVar(&cmdConfig.Datacenter, "datacenter", "", "node datacenter")
|
||||
cmdFlags.StringVar(&cmdConfig.DataDir, "data-dir", "", "path to the data directory")
|
||||
|
@ -1115,6 +1116,7 @@ func (c *Command) Run(args []string) int {
|
|||
|
||||
c.Ui.Output("Consul agent running!")
|
||||
c.Ui.Info(fmt.Sprintf(" Version: '%s'", c.HumanVersion))
|
||||
c.Ui.Info(fmt.Sprintf(" Node ID: '%s'", config.NodeID))
|
||||
c.Ui.Info(fmt.Sprintf(" Node name: '%s'", config.NodeName))
|
||||
c.Ui.Info(fmt.Sprintf(" Datacenter: '%s'", config.Datacenter))
|
||||
c.Ui.Info(fmt.Sprintf(" Server: %v (bootstrap: %v)", config.Server, config.Bootstrap))
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
|
||||
"github.com/hashicorp/consul/consul"
|
||||
"github.com/hashicorp/consul/lib"
|
||||
"github.com/hashicorp/consul/types"
|
||||
"github.com/hashicorp/consul/watch"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
@ -312,6 +313,10 @@ type Config struct {
|
|||
// LogLevel is the level of the logs to putout
|
||||
LogLevel string `mapstructure:"log_level"`
|
||||
|
||||
// Node ID is a unique ID for this node across space and time. Defaults
|
||||
// to a randomly-generated ID that persists in the data-dir.
|
||||
NodeID types.NodeID `mapstructure:"node_id"`
|
||||
|
||||
// Node name is the name we use to advertise. Defaults to hostname.
|
||||
NodeName string `mapstructure:"node_name"`
|
||||
|
||||
|
@ -1273,6 +1278,9 @@ func MergeConfig(a, b *Config) *Config {
|
|||
if b.Protocol > 0 {
|
||||
result.Protocol = b.Protocol
|
||||
}
|
||||
if b.NodeID != "" {
|
||||
result.NodeID = b.NodeID
|
||||
}
|
||||
if b.NodeName != "" {
|
||||
result.NodeName = b.NodeName
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ func TestDecodeConfig(t *testing.T) {
|
|||
}
|
||||
|
||||
// Without a protocol
|
||||
input = `{"node_name": "foo", "datacenter": "dc2"}`
|
||||
input = `{"node_id": "bar", "node_name": "foo", "datacenter": "dc2"}`
|
||||
config, err = DecodeConfig(bytes.NewReader([]byte(input)))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
@ -70,6 +70,10 @@ func TestDecodeConfig(t *testing.T) {
|
|||
t.Fatalf("bad: %#v", config)
|
||||
}
|
||||
|
||||
if config.NodeID != "bar" {
|
||||
t.Fatalf("bad: %#v", config)
|
||||
}
|
||||
|
||||
if config.Datacenter != "dc2" {
|
||||
t.Fatalf("bad: %#v", config)
|
||||
}
|
||||
|
@ -1532,6 +1536,7 @@ func TestMergeConfig(t *testing.T) {
|
|||
DataDir: "/tmp/foo",
|
||||
Domain: "basic",
|
||||
LogLevel: "debug",
|
||||
NodeID: "bar",
|
||||
NodeName: "foo",
|
||||
ClientAddr: "127.0.0.1",
|
||||
BindAddr: "127.0.0.1",
|
||||
|
@ -1586,6 +1591,7 @@ func TestMergeConfig(t *testing.T) {
|
|||
},
|
||||
Domain: "other",
|
||||
LogLevel: "info",
|
||||
NodeID: "bar",
|
||||
NodeName: "baz",
|
||||
ClientAddr: "127.0.0.2",
|
||||
BindAddr: "127.0.0.2",
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/hashicorp/consul/consul/agent"
|
||||
"github.com/hashicorp/consul/consul/servers"
|
||||
"github.com/hashicorp/consul/consul/structs"
|
||||
"github.com/hashicorp/consul/lib"
|
||||
"github.com/hashicorp/serf/coordinate"
|
||||
"github.com/hashicorp/serf/serf"
|
||||
)
|
||||
|
@ -144,6 +145,7 @@ func (c *Client) setupSerf(conf *serf.Config, ch chan serf.Event, path string) (
|
|||
conf.NodeName = c.config.NodeName
|
||||
conf.Tags["role"] = "node"
|
||||
conf.Tags["dc"] = c.config.Datacenter
|
||||
conf.Tags["id"] = string(c.config.NodeID)
|
||||
conf.Tags["vsn"] = fmt.Sprintf("%d", c.config.ProtocolVersion)
|
||||
conf.Tags["vsn_min"] = fmt.Sprintf("%d", ProtocolVersionMin)
|
||||
conf.Tags["vsn_max"] = fmt.Sprintf("%d", ProtocolVersionMax)
|
||||
|
@ -156,7 +158,7 @@ func (c *Client) setupSerf(conf *serf.Config, ch chan serf.Event, path string) (
|
|||
conf.RejoinAfterLeave = c.config.RejoinAfterLeave
|
||||
conf.Merge = &lanMergeDelegate{dc: c.config.Datacenter}
|
||||
conf.DisableCoordinates = c.config.DisableCoordinates
|
||||
if err := ensurePath(conf.SnapshotPath, false); err != nil {
|
||||
if err := lib.EnsurePath(conf.SnapshotPath, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return serf.Create(conf)
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/hashicorp/consul/tlsutil"
|
||||
"github.com/hashicorp/consul/types"
|
||||
"github.com/hashicorp/memberlist"
|
||||
"github.com/hashicorp/raft"
|
||||
"github.com/hashicorp/serf/serf"
|
||||
|
@ -66,6 +67,9 @@ type Config struct {
|
|||
// DevMode is used to enable a development server mode.
|
||||
DevMode bool
|
||||
|
||||
// NodeID is a unique identifier for this node across space and time.
|
||||
NodeID types.NodeID
|
||||
|
||||
// Node name is the name we use to advertise. Defaults to hostname.
|
||||
NodeName string
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/hashicorp/consul/consul/agent"
|
||||
"github.com/hashicorp/consul/consul/state"
|
||||
"github.com/hashicorp/consul/consul/structs"
|
||||
"github.com/hashicorp/consul/lib"
|
||||
"github.com/hashicorp/consul/tlsutil"
|
||||
"github.com/hashicorp/raft"
|
||||
"github.com/hashicorp/raft-boltdb"
|
||||
|
@ -308,6 +309,7 @@ func (s *Server) setupSerf(conf *serf.Config, ch chan serf.Event, path string, w
|
|||
}
|
||||
conf.Tags["role"] = "consul"
|
||||
conf.Tags["dc"] = s.config.Datacenter
|
||||
conf.Tags["id"] = string(s.config.NodeID)
|
||||
conf.Tags["vsn"] = fmt.Sprintf("%d", s.config.ProtocolVersion)
|
||||
conf.Tags["vsn_min"] = fmt.Sprintf("%d", ProtocolVersionMin)
|
||||
conf.Tags["vsn_max"] = fmt.Sprintf("%d", ProtocolVersionMax)
|
||||
|
@ -337,7 +339,7 @@ func (s *Server) setupSerf(conf *serf.Config, ch chan serf.Event, path string, w
|
|||
// When enabled, the Serf gossip may just turn off if we are the minority
|
||||
// node which is rather unexpected.
|
||||
conf.EnableNameConflictResolution = false
|
||||
if err := ensurePath(conf.SnapshotPath, false); err != nil {
|
||||
if err := lib.EnsurePath(conf.SnapshotPath, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -390,7 +392,7 @@ func (s *Server) setupRaft() error {
|
|||
} else {
|
||||
// Create the base raft path.
|
||||
path := filepath.Join(s.config.DataDir, raftState)
|
||||
if err := ensurePath(path, true); err != nil {
|
||||
if err := lib.EnsurePath(path, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,6 @@ import (
|
|||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
|
@ -64,14 +62,6 @@ func init() {
|
|||
privateBlocks[5] = block
|
||||
}
|
||||
|
||||
// ensurePath is used to make sure a path exists
|
||||
func ensurePath(path string, dir bool) error {
|
||||
if !dir {
|
||||
path = filepath.Dir(path)
|
||||
}
|
||||
return os.MkdirAll(path, 0755)
|
||||
}
|
||||
|
||||
// CanServersUnderstandProtocol checks to see if all the servers in the given
|
||||
// list understand the given protocol version. If there are no servers in the
|
||||
// list then this will return false.
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package lib
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// EnsurePath is used to make sure a path exists
|
||||
func EnsurePath(path string, dir bool) error {
|
||||
if !dir {
|
||||
path = filepath.Dir(path)
|
||||
}
|
||||
return os.MkdirAll(path, 0755)
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package types
|
||||
|
||||
// NodeID is a unique identifier for a node across space and time.
|
||||
type NodeID string
|
|
@ -143,6 +143,7 @@ It returns a JSON body like this:
|
|||
"DNSRecursors": [],
|
||||
"Domain": "consul.",
|
||||
"LogLevel": "INFO",
|
||||
"NodeID": "40e4a748-2192-161a-0510-9bf59fe950b5",
|
||||
"NodeName": "foobar",
|
||||
"ClientAddr": "127.0.0.1",
|
||||
"BindAddr": "0.0.0.0",
|
||||
|
@ -183,6 +184,7 @@ It returns a JSON body like this:
|
|||
"Tags": {
|
||||
"bootstrap": "1",
|
||||
"dc": "dc1",
|
||||
"id": "40e4a748-2192-161a-0510-9bf59fe950b5",
|
||||
"port": "8300",
|
||||
"role": "consul",
|
||||
"vsn": "1",
|
||||
|
|
|
@ -282,6 +282,15 @@ will exit with an error at startup.
|
|||
* <a name="_node"></a><a href="#_node">`-node`</a> - The name of this node in the cluster.
|
||||
This must be unique within the cluster. By default this is the hostname of the machine.
|
||||
|
||||
* <a name="_node_id"></a><a href="#_node_id">`-node-id`</a> - Available in Consul 0.7.3 and later, this
|
||||
is a unique identifier for this node across all time, even if the name of the node or address
|
||||
changes. This must be in the form of a hex string, 36 characters long, such as
|
||||
`adf4238a-882b-9ddc-4a9d-5b6758e4159e`. If this isn't supplied, which is the most common case, then
|
||||
the agent will generate an identifier at startup and persist it in the <a href="#_data_dir">data directory</a>
|
||||
so that it will remain the same across agent restarts. This is currently only exposed via the agent's
|
||||
<a href="/docs/agent/http/agent.html#agent_self">/v1/agent/self</a> endpoint, but future versions of
|
||||
Consul will use this to better manage cluster changes, especially for Consul servers.
|
||||
|
||||
* <a name="_node_meta"></a><a href="#_node_meta">`-node-meta`</a> - 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:
|
||||
|
@ -695,6 +704,9 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
|
|||
* <a name="log_level"></a><a href="#log_level">`log_level`</a> Equivalent to the
|
||||
[`-log-level` command-line flag](#_log_level).
|
||||
|
||||
* <a name="node_id"></a><a href="#node_id">`node_id`</a> Equivalent to the
|
||||
[`-node-id` command-line flag](#_node_id).
|
||||
|
||||
* <a name="node_name"></a><a href="#node_name">`node_name`</a> Equivalent to the
|
||||
[`-node` command-line flag](#_node).
|
||||
|
||||
|
|
Loading…
Reference in New Issue