Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds basic support for node IDs. #2661

Merged
merged 5 commits into from
Jan 19, 2017
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be the place where we could hook into github.com/shirou/gopsutil:

import "github.com/shirou/gopsutil"
info, err := host.Info()
if info != nil && info.HostID != "" {
  config.NodeID = types.NodeID(info.HostID)
}

If the ID came from gopsutil then I wouldn't necessarily want to persist it in the data directory, but there shouldn't be any harm in that either since the ID coming from gopsutil and the OS should outlive the contents of whatever is in the data directory.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah - it might not be a bad idea to save it even from gopsutil in case we improve the algorithm over there and people upgrade Consul in-place, but that's probably not a huge issue either way.

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