Skip to content

Commit

Permalink
Merge pull request #2661 from hashicorp/f-node-id
Browse files Browse the repository at this point in the history
Adds basic support for node IDs.
  • Loading branch information
slackpad committed Jan 19, 2017
2 parents 61a20c5 + 432540f commit acbc228
Show file tree
Hide file tree
Showing 27 changed files with 351 additions and 78 deletions.
2 changes: 1 addition & 1 deletion api/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
5 changes: 4 additions & 1 deletion api/catalog.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package api

type Node struct {
ID string
Node string
Address string
TaggedAddresses map[string]string
Meta map[string]string
}

type CatalogService struct {
ID string
Node string
Address string
TaggedAddresses map[string]string
Expand All @@ -28,6 +30,7 @@ type CatalogNode struct {
}

type CatalogRegistration struct {
ID string
Node string
Address string
TaggedAddresses map[string]string
Expand All @@ -39,7 +42,7 @@ type CatalogRegistration struct {

type CatalogDeregistration struct {
Node string
Address string
Address string // Obsolete.
Datacenter string
ServiceID string
CheckID string
Expand Down
71 changes: 70 additions & 1 deletion command/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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)

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down
67 changes: 67 additions & 0 deletions command/agent/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -308,6 +310,71 @@ 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)
}

// Running again should get the same ID (persisted in the file).
c.NodeID = ""
if err := agent.setupNodeID(c); err != nil {
t.Fatalf("err: %v", err)
}
if newID := agent.consulConfig().NodeID; id != newID {
t.Fatalf("bad: %q vs %q", id, newID)
}

// 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)
Expand Down
2 changes: 2 additions & 0 deletions command/agent/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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))
Expand Down
8 changes: 8 additions & 0 deletions command/agent/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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"`

Expand Down Expand Up @@ -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
}
Expand Down
8 changes: 7 additions & 1 deletion command/agent/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
6 changes: 5 additions & 1 deletion command/agent/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ type localState struct {
iface consul.Interface

// nodeInfoInSync tracks whether the server has our correct top-level
// node information in sync (currently only used for tagged addresses)
// node information in sync
nodeInfoInSync bool

// Services tracks the local services
Expand Down Expand Up @@ -431,6 +431,7 @@ func (l *localState) setSyncState() error {

// Check the node info
if out1.NodeServices == nil || out1.NodeServices.Node == nil ||
out1.NodeServices.Node.ID != l.config.NodeID ||
!reflect.DeepEqual(out1.NodeServices.Node.TaggedAddresses, l.config.TaggedAddresses) ||
!reflect.DeepEqual(out1.NodeServices.Node.Meta, l.metadata) {
l.nodeInfoInSync = false
Expand Down Expand Up @@ -633,6 +634,7 @@ func (l *localState) deleteCheck(id types.CheckID) error {
func (l *localState) syncService(id string) error {
req := structs.RegisterRequest{
Datacenter: l.config.Datacenter,
ID: l.config.NodeID,
Node: l.config.NodeName,
Address: l.config.AdvertiseAddr,
TaggedAddresses: l.config.TaggedAddresses,
Expand Down Expand Up @@ -695,6 +697,7 @@ func (l *localState) syncCheck(id types.CheckID) error {

req := structs.RegisterRequest{
Datacenter: l.config.Datacenter,
ID: l.config.NodeID,
Node: l.config.NodeName,
Address: l.config.AdvertiseAddr,
TaggedAddresses: l.config.TaggedAddresses,
Expand Down Expand Up @@ -722,6 +725,7 @@ func (l *localState) syncCheck(id types.CheckID) error {
func (l *localState) syncNodeInfo() error {
req := structs.RegisterRequest{
Datacenter: l.config.Datacenter,
ID: l.config.NodeID,
Node: l.config.NodeName,
Address: l.config.AdvertiseAddr,
TaggedAddresses: l.config.TaggedAddresses,
Expand Down
Loading

0 comments on commit acbc228

Please sign in to comment.