diff --git a/api/catalog.go b/api/catalog.go
index 10e93b42d9..96226f11f8 100644
--- a/api/catalog.go
+++ b/api/catalog.go
@@ -1,6 +1,7 @@
package api
type Node struct {
+ ID string
Node string
Address string
TaggedAddresses map[string]string
@@ -8,6 +9,7 @@ type Node struct {
}
type CatalogService struct {
+ ID string
Node string
Address string
TaggedAddresses map[string]string
@@ -28,6 +30,7 @@ type CatalogNode struct {
}
type CatalogRegistration struct {
+ ID string
Node string
Address string
TaggedAddresses map[string]string
@@ -39,7 +42,7 @@ type CatalogRegistration struct {
type CatalogDeregistration struct {
Node string
- Address string
+ Address string // Obsolete.
Datacenter string
ServiceID string
CheckID string
diff --git a/command/agent/local.go b/command/agent/local.go
index 853cdd5ad4..5ce92b48ff 100644
--- a/command/agent/local.go
+++ b/command/agent/local.go
@@ -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
@@ -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
@@ -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,
@@ -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,
@@ -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,
diff --git a/command/agent/local_test.go b/command/agent/local_test.go
index 79e8b41b2a..53fc726a36 100644
--- a/command/agent/local_test.go
+++ b/command/agent/local_test.go
@@ -121,10 +121,14 @@ func TestAgentAntiEntropy_Services(t *testing.T) {
return false, fmt.Errorf("err: %v", err)
}
- // Make sure we sent along our tagged addresses when we synced.
+ // Make sure we sent along our node info when we synced.
+ id := services.NodeServices.Node.ID
addrs := services.NodeServices.Node.TaggedAddresses
- if len(addrs) == 0 || !reflect.DeepEqual(addrs, conf.TaggedAddresses) {
- return false, fmt.Errorf("bad: %v", addrs)
+ meta := services.NodeServices.Node.Meta
+ if id != conf.NodeID ||
+ !reflect.DeepEqual(addrs, conf.TaggedAddresses) ||
+ !reflect.DeepEqual(meta, conf.Meta) {
+ return false, fmt.Errorf("bad: %v", services.NodeServices.Node)
}
// We should have 6 services (consul included)
@@ -717,7 +721,7 @@ func TestAgentAntiEntropy_Checks(t *testing.T) {
}
}
- // Make sure we sent along our tagged addresses when we synced.
+ // Make sure we sent along our node info addresses when we synced.
{
req := structs.NodeSpecificRequest{
Datacenter: "dc1",
@@ -728,9 +732,13 @@ func TestAgentAntiEntropy_Checks(t *testing.T) {
t.Fatalf("err: %v", err)
}
+ id := services.NodeServices.Node.ID
addrs := services.NodeServices.Node.TaggedAddresses
- if len(addrs) == 0 || !reflect.DeepEqual(addrs, conf.TaggedAddresses) {
- t.Fatalf("bad: %v", addrs)
+ meta := services.NodeServices.Node.Meta
+ if id != conf.NodeID ||
+ !reflect.DeepEqual(addrs, conf.TaggedAddresses) ||
+ !reflect.DeepEqual(meta, conf.Meta) {
+ t.Fatalf("bad: %v", services.NodeServices.Node)
}
}
@@ -985,6 +993,7 @@ func TestAgentAntiEntropy_Check_DeferSync(t *testing.T) {
func TestAgentAntiEntropy_NodeInfo(t *testing.T) {
conf := nextConfig()
+ conf.NodeID = types.NodeID("40e4a748-2192-161a-0510-9bf59fe950b5")
conf.Meta["somekey"] = "somevalue"
dir, agent := makeAgent(t, conf)
defer os.RemoveAll(dir)
@@ -1020,12 +1029,14 @@ func TestAgentAntiEntropy_NodeInfo(t *testing.T) {
// Make sure we synced our node info - this should have ridden on the
// "consul" service sync
+ id := services.NodeServices.Node.ID
addrs := services.NodeServices.Node.TaggedAddresses
meta := services.NodeServices.Node.Meta
- if len(addrs) == 0 || !reflect.DeepEqual(addrs, conf.TaggedAddresses) || !reflect.DeepEqual(meta, conf.Meta) {
- return false, fmt.Errorf("bad: %v", addrs)
+ if id != conf.NodeID ||
+ !reflect.DeepEqual(addrs, conf.TaggedAddresses) ||
+ !reflect.DeepEqual(meta, conf.Meta) {
+ return false, fmt.Errorf("bad: %v", services.NodeServices.Node)
}
-
return true, nil
}, func(err error) {
t.Fatalf("err: %s", err)
@@ -1045,12 +1056,15 @@ func TestAgentAntiEntropy_NodeInfo(t *testing.T) {
if err := agent.RPC("Catalog.NodeServices", &req, &services); err != nil {
return false, fmt.Errorf("err: %v", err)
}
+
+ id := services.NodeServices.Node.ID
addrs := services.NodeServices.Node.TaggedAddresses
meta := services.NodeServices.Node.Meta
- if len(addrs) == 0 || !reflect.DeepEqual(addrs, conf.TaggedAddresses) || !reflect.DeepEqual(meta, conf.Meta) {
- return false, fmt.Errorf("bad: %v", addrs)
+ if id != conf.NodeID ||
+ !reflect.DeepEqual(addrs, conf.TaggedAddresses) ||
+ !reflect.DeepEqual(meta, conf.Meta) {
+ return false, fmt.Errorf("bad: %v", services.NodeServices.Node)
}
-
return true, nil
}, func(err error) {
t.Fatalf("err: %s", err)
diff --git a/consul/catalog_endpoint.go b/consul/catalog_endpoint.go
index 55c28c0045..8ee16c2a48 100644
--- a/consul/catalog_endpoint.go
+++ b/consul/catalog_endpoint.go
@@ -7,6 +7,7 @@ import (
"github.com/armon/go-metrics"
"github.com/hashicorp/consul/consul/structs"
"github.com/hashicorp/consul/types"
+ "github.com/hashicorp/go-uuid"
)
// Catalog endpoint is used to manipulate the service catalog
@@ -25,6 +26,11 @@ func (c *Catalog) Register(args *structs.RegisterRequest, reply *struct{}) error
if args.Node == "" || args.Address == "" {
return fmt.Errorf("Must provide node and address")
}
+ if args.ID != "" {
+ if _, err := uuid.ParseUUID(string(args.ID)); err != nil {
+ return fmt.Errorf("Bad node ID: %v", err)
+ }
+ }
// Fetch the ACL token, if any.
acl, err := c.srv.resolveToken(args.Token)
diff --git a/consul/catalog_endpoint_test.go b/consul/catalog_endpoint_test.go
index 6749d5b203..aa25388a27 100644
--- a/consul/catalog_endpoint_test.go
+++ b/consul/catalog_endpoint_test.go
@@ -11,6 +11,7 @@ import (
"github.com/hashicorp/consul/consul/structs"
"github.com/hashicorp/consul/lib"
"github.com/hashicorp/consul/testutil"
+ "github.com/hashicorp/consul/types"
"github.com/hashicorp/net-rpc-msgpackrpc"
)
@@ -40,13 +41,40 @@ func TestCatalog_Register(t *testing.T) {
if err != nil {
t.Fatalf("err: %v", err)
}
+}
- testutil.WaitForResult(func() (bool, error) {
- err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out)
- return err == nil, err
- }, func(err error) {
+func TestCatalog_Register_NodeID(t *testing.T) {
+ dir1, s1 := testServer(t)
+ defer os.RemoveAll(dir1)
+ defer s1.Shutdown()
+ codec := rpcClient(t, s1)
+ defer codec.Close()
+
+ arg := structs.RegisterRequest{
+ Datacenter: "dc1",
+ ID: "nope",
+ Node: "foo",
+ Address: "127.0.0.1",
+ Service: &structs.NodeService{
+ Service: "db",
+ Tags: []string{"master"},
+ Port: 8000,
+ },
+ Check: &structs.HealthCheck{
+ ServiceID: "db",
+ },
+ }
+ var out struct{}
+
+ err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out)
+ if err == nil || !strings.Contains(err.Error(), "Bad node ID") {
t.Fatalf("err: %v", err)
- })
+ }
+
+ arg.ID = types.NodeID("adf4238a-882b-9ddc-4a9d-5b6758e4159e")
+ if err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out); err != nil {
+ t.Fatalf("err: %v", err)
+ }
}
func TestCatalog_Register_ACLDeny(t *testing.T) {
diff --git a/consul/leader.go b/consul/leader.go
index 43cde0666a..4efededfee 100644
--- a/consul/leader.go
+++ b/consul/leader.go
@@ -418,6 +418,7 @@ AFTER_CHECK:
// Register with the catalog
req := structs.RegisterRequest{
Datacenter: s.config.Datacenter,
+ ID: types.NodeID(member.Tags["id"]),
Node: member.Name,
Address: member.Addr.String(),
Service: service,
diff --git a/consul/state/catalog.go b/consul/state/catalog.go
index 1f790ab692..a6c4448e33 100644
--- a/consul/state/catalog.go
+++ b/consul/state/catalog.go
@@ -72,6 +72,7 @@ func (s *StateStore) ensureRegistrationTxn(tx *memdb.Txn, idx uint64, watches *D
req *structs.RegisterRequest) error {
// Add the node.
node := &structs.Node{
+ ID: req.ID,
Node: req.Node,
Address: req.Address,
TaggedAddresses: req.TaggedAddresses,
@@ -229,12 +230,12 @@ func (s *StateStore) NodesByMeta(filters map[string]string) (uint64, structs.Nod
}
// DeleteNode is used to delete a given node by its ID.
-func (s *StateStore) DeleteNode(idx uint64, nodeID string) error {
+func (s *StateStore) DeleteNode(idx uint64, nodeName string) error {
tx := s.db.Txn(true)
defer tx.Abort()
// Call the node deletion.
- if err := s.deleteNodeTxn(tx, idx, nodeID); err != nil {
+ if err := s.deleteNodeTxn(tx, idx, nodeName); err != nil {
return err
}
@@ -244,9 +245,9 @@ func (s *StateStore) DeleteNode(idx uint64, nodeID string) error {
// deleteNodeTxn is the inner method used for removing a node from
// the store within a given transaction.
-func (s *StateStore) deleteNodeTxn(tx *memdb.Txn, idx uint64, nodeID string) error {
+func (s *StateStore) deleteNodeTxn(tx *memdb.Txn, idx uint64, nodeName string) error {
// Look up the node.
- node, err := tx.First("nodes", "id", nodeID)
+ node, err := tx.First("nodes", "id", nodeName)
if err != nil {
return fmt.Errorf("node lookup failed: %s", err)
}
@@ -259,7 +260,7 @@ func (s *StateStore) deleteNodeTxn(tx *memdb.Txn, idx uint64, nodeID string) err
watches := NewDumbWatchManager(s.tableWatches)
// Delete all services associated with the node and update the service index.
- services, err := tx.Get("services", "node", nodeID)
+ services, err := tx.Get("services", "node", nodeName)
if err != nil {
return fmt.Errorf("failed service lookup: %s", err)
}
@@ -270,14 +271,14 @@ func (s *StateStore) deleteNodeTxn(tx *memdb.Txn, idx uint64, nodeID string) err
// Do the delete in a separate loop so we don't trash the iterator.
for _, sid := range sids {
- if err := s.deleteServiceTxn(tx, idx, watches, nodeID, sid); err != nil {
+ if err := s.deleteServiceTxn(tx, idx, watches, nodeName, sid); err != nil {
return err
}
}
// Delete all checks associated with the node. This will invalidate
// sessions as necessary.
- checks, err := tx.Get("checks", "node", nodeID)
+ checks, err := tx.Get("checks", "node", nodeName)
if err != nil {
return fmt.Errorf("failed check lookup: %s", err)
}
@@ -288,13 +289,13 @@ func (s *StateStore) deleteNodeTxn(tx *memdb.Txn, idx uint64, nodeID string) err
// Do the delete in a separate loop so we don't trash the iterator.
for _, cid := range cids {
- if err := s.deleteCheckTxn(tx, idx, watches, nodeID, cid); err != nil {
+ if err := s.deleteCheckTxn(tx, idx, watches, nodeName, cid); err != nil {
return err
}
}
// Delete any coordinate associated with this node.
- coord, err := tx.First("coordinates", "id", nodeID)
+ coord, err := tx.First("coordinates", "id", nodeName)
if err != nil {
return fmt.Errorf("failed coordinate lookup: %s", err)
}
@@ -317,7 +318,7 @@ func (s *StateStore) deleteNodeTxn(tx *memdb.Txn, idx uint64, nodeID string) err
}
// Invalidate any sessions for this node.
- sessions, err := tx.Get("sessions", "node", nodeID)
+ sessions, err := tx.Get("sessions", "node", nodeName)
if err != nil {
return fmt.Errorf("failed session lookup: %s", err)
}
@@ -365,9 +366,8 @@ func (s *StateStore) ensureServiceTxn(tx *memdb.Txn, idx uint64, watches *DumbWa
}
// Create the service node entry and populate the indexes. Note that
- // conversion doesn't populate any of the node-specific information
- // (Address and TaggedAddresses). That's always populated when we read
- // from the state store.
+ // conversion doesn't populate any of the node-specific information.
+ // That's always populated when we read from the state store.
entry := svc.ToServiceNode(node)
if existing != nil {
entry.CreateIndex = existing.(*structs.ServiceNode).CreateIndex
@@ -590,6 +590,7 @@ func (s *StateStore) parseServiceNodes(tx *memdb.Txn, services structs.ServiceNo
// used by agents to perform address translation if they are
// configured to do that.
node := n.(*structs.Node)
+ s.ID = node.ID
s.Address = node.Address
s.TaggedAddresses = node.TaggedAddresses
s.NodeMeta = node.Meta
@@ -601,7 +602,7 @@ func (s *StateStore) parseServiceNodes(tx *memdb.Txn, services structs.ServiceNo
// NodeService is used to retrieve a specific service associated with the given
// node.
-func (s *StateStore) NodeService(nodeID string, serviceID string) (uint64, *structs.NodeService, error) {
+func (s *StateStore) NodeService(nodeName string, serviceID string) (uint64, *structs.NodeService, error) {
tx := s.db.Txn(false)
defer tx.Abort()
@@ -609,9 +610,9 @@ func (s *StateStore) NodeService(nodeID string, serviceID string) (uint64, *stru
idx := maxIndexTxn(tx, s.getWatchTables("NodeService")...)
// Query the service
- service, err := tx.First("services", "id", nodeID, serviceID)
+ service, err := tx.First("services", "id", nodeName, serviceID)
if err != nil {
- return 0, nil, fmt.Errorf("failed querying service for node %q: %s", nodeID, err)
+ return 0, nil, fmt.Errorf("failed querying service for node %q: %s", nodeName, err)
}
if service != nil {
@@ -622,7 +623,7 @@ func (s *StateStore) NodeService(nodeID string, serviceID string) (uint64, *stru
}
// NodeServices is used to query service registrations by node ID.
-func (s *StateStore) NodeServices(nodeID string) (uint64, *structs.NodeServices, error) {
+func (s *StateStore) NodeServices(nodeName string) (uint64, *structs.NodeServices, error) {
tx := s.db.Txn(false)
defer tx.Abort()
@@ -630,7 +631,7 @@ func (s *StateStore) NodeServices(nodeID string) (uint64, *structs.NodeServices,
idx := maxIndexTxn(tx, s.getWatchTables("NodeServices")...)
// Query the node
- n, err := tx.First("nodes", "id", nodeID)
+ n, err := tx.First("nodes", "id", nodeName)
if err != nil {
return 0, nil, fmt.Errorf("node lookup failed: %s", err)
}
@@ -640,9 +641,9 @@ func (s *StateStore) NodeServices(nodeID string) (uint64, *structs.NodeServices,
node := n.(*structs.Node)
// Read all of the services
- services, err := tx.Get("services", "node", nodeID)
+ services, err := tx.Get("services", "node", nodeName)
if err != nil {
- return 0, nil, fmt.Errorf("failed querying services for node %q: %s", nodeID, err)
+ return 0, nil, fmt.Errorf("failed querying services for node %q: %s", nodeName, err)
}
// Initialize the node services struct
@@ -661,13 +662,13 @@ func (s *StateStore) NodeServices(nodeID string) (uint64, *structs.NodeServices,
}
// DeleteService is used to delete a given service associated with a node.
-func (s *StateStore) DeleteService(idx uint64, nodeID, serviceID string) error {
+func (s *StateStore) DeleteService(idx uint64, nodeName, serviceID string) error {
tx := s.db.Txn(true)
defer tx.Abort()
// Call the service deletion
watches := NewDumbWatchManager(s.tableWatches)
- if err := s.deleteServiceTxn(tx, idx, watches, nodeID, serviceID); err != nil {
+ if err := s.deleteServiceTxn(tx, idx, watches, nodeName, serviceID); err != nil {
return err
}
@@ -678,9 +679,9 @@ func (s *StateStore) DeleteService(idx uint64, nodeID, serviceID string) error {
// deleteServiceTxn is the inner method called to remove a service
// registration within an existing transaction.
-func (s *StateStore) deleteServiceTxn(tx *memdb.Txn, idx uint64, watches *DumbWatchManager, nodeID, serviceID string) error {
+func (s *StateStore) deleteServiceTxn(tx *memdb.Txn, idx uint64, watches *DumbWatchManager, nodeName, serviceID string) error {
// Look up the service.
- service, err := tx.First("services", "id", nodeID, serviceID)
+ service, err := tx.First("services", "id", nodeName, serviceID)
if err != nil {
return fmt.Errorf("failed service lookup: %s", err)
}
@@ -690,7 +691,7 @@ func (s *StateStore) deleteServiceTxn(tx *memdb.Txn, idx uint64, watches *DumbWa
// Delete any checks associated with the service. This will invalidate
// sessions as necessary.
- checks, err := tx.Get("checks", "node_service", nodeID, serviceID)
+ checks, err := tx.Get("checks", "node_service", nodeName, serviceID)
if err != nil {
return fmt.Errorf("failed service check lookup: %s", err)
}
@@ -701,7 +702,7 @@ func (s *StateStore) deleteServiceTxn(tx *memdb.Txn, idx uint64, watches *DumbWa
// Do the delete in a separate loop so we don't trash the iterator.
for _, cid := range cids {
- if err := s.deleteCheckTxn(tx, idx, watches, nodeID, cid); err != nil {
+ if err := s.deleteCheckTxn(tx, idx, watches, nodeName, cid); err != nil {
return err
}
}
@@ -825,7 +826,7 @@ func (s *StateStore) ensureCheckTxn(tx *memdb.Txn, idx uint64, watches *DumbWatc
// NodeCheck is used to retrieve a specific check associated with the given
// node.
-func (s *StateStore) NodeCheck(nodeID string, checkID types.CheckID) (uint64, *structs.HealthCheck, error) {
+func (s *StateStore) NodeCheck(nodeName string, checkID types.CheckID) (uint64, *structs.HealthCheck, error) {
tx := s.db.Txn(false)
defer tx.Abort()
@@ -833,7 +834,7 @@ func (s *StateStore) NodeCheck(nodeID string, checkID types.CheckID) (uint64, *s
idx := maxIndexTxn(tx, s.getWatchTables("NodeCheck")...)
// Return the check.
- check, err := tx.First("checks", "id", nodeID, string(checkID))
+ check, err := tx.First("checks", "id", nodeName, string(checkID))
if err != nil {
return 0, nil, fmt.Errorf("failed check lookup: %s", err)
}
@@ -846,7 +847,7 @@ func (s *StateStore) NodeCheck(nodeID string, checkID types.CheckID) (uint64, *s
// NodeChecks is used to retrieve checks associated with the
// given node from the state store.
-func (s *StateStore) NodeChecks(nodeID string) (uint64, structs.HealthChecks, error) {
+func (s *StateStore) NodeChecks(nodeName string) (uint64, structs.HealthChecks, error) {
tx := s.db.Txn(false)
defer tx.Abort()
@@ -854,7 +855,7 @@ func (s *StateStore) NodeChecks(nodeID string) (uint64, structs.HealthChecks, er
idx := maxIndexTxn(tx, s.getWatchTables("NodeChecks")...)
// Return the checks.
- checks, err := tx.Get("checks", "node", nodeID)
+ checks, err := tx.Get("checks", "node", nodeName)
if err != nil {
return 0, nil, fmt.Errorf("failed check lookup: %s", err)
}
@@ -1195,6 +1196,7 @@ func (s *StateStore) parseNodes(tx *memdb.Txn, idx uint64,
// Create the wrapped node
dump := &structs.NodeInfo{
+ ID: node.ID,
Node: node.Node,
Address: node.Address,
TaggedAddresses: node.TaggedAddresses,
diff --git a/consul/state/catalog_test.go b/consul/state/catalog_test.go
index e7614098f4..71d1e45143 100644
--- a/consul/state/catalog_test.go
+++ b/consul/state/catalog_test.go
@@ -16,6 +16,7 @@ func TestStateStore_EnsureRegistration(t *testing.T) {
// Start with just a node.
req := &structs.RegisterRequest{
+ ID: types.NodeID("40e4a748-2192-161a-0510-9bf59fe950b5"),
Node: "node1",
Address: "1.2.3.4",
TaggedAddresses: map[string]string{
@@ -35,7 +36,8 @@ func TestStateStore_EnsureRegistration(t *testing.T) {
if err != nil {
t.Fatalf("err: %s", err)
}
- if out.Node != "node1" || out.Address != "1.2.3.4" ||
+ if out.ID != types.NodeID("40e4a748-2192-161a-0510-9bf59fe950b5") ||
+ out.Node != "node1" || out.Address != "1.2.3.4" ||
len(out.TaggedAddresses) != 1 ||
out.TaggedAddresses["hello"] != "world" ||
out.Meta["somekey"] != "somevalue" ||
diff --git a/consul/structs/structs.go b/consul/structs/structs.go
index bf09ca3770..644fb97c07 100644
--- a/consul/structs/structs.go
+++ b/consul/structs/structs.go
@@ -170,6 +170,7 @@ type QueryMeta struct {
// is provided, the node is registered.
type RegisterRequest struct {
Datacenter string
+ ID types.NodeID
Node string
Address string
TaggedAddresses map[string]string
@@ -194,7 +195,8 @@ func (r *RegisterRequest) ChangesNode(node *Node) bool {
}
// Check if any of the node-level fields are being changed.
- if r.Node != node.Node ||
+ if r.ID != node.ID ||
+ r.Node != node.Node ||
r.Address != node.Address ||
!reflect.DeepEqual(r.TaggedAddresses, node.TaggedAddresses) ||
!reflect.DeepEqual(r.NodeMeta, node.Meta) {
@@ -280,6 +282,7 @@ func (r *ChecksInStateRequest) RequestDatacenter() string {
// Used to return information about a node
type Node struct {
+ ID types.NodeID
Node string
Address string
TaggedAddresses map[string]string
@@ -302,12 +305,13 @@ func SatisfiesMetaFilters(meta map[string]string, filters map[string]string) boo
// Maps service name to available tags
type Services map[string][]string
-// ServiceNode represents a node that is part of a service. Address, TaggedAddresses,
-// and NodeMeta are node-related fields that are always empty in the state
-// store and are filled in on the way out by parseServiceNodes(). This is also
-// why PartialClone() skips them, because we know they are blank already so it
-// would be a waste of time to copy them.
+// ServiceNode represents a node that is part of a service. ID, Address,
+// TaggedAddresses, and NodeMeta are node-related fields that are always empty
+// in the state store and are filled in on the way out by parseServiceNodes().
+// This is also why PartialClone() skips them, because we know they are blank
+// already so it would be a waste of time to copy them.
type ServiceNode struct {
+ ID types.NodeID
Node string
Address string
TaggedAddresses map[string]string
@@ -329,6 +333,7 @@ func (s *ServiceNode) PartialClone() *ServiceNode {
copy(tags, s.ServiceTags)
return &ServiceNode{
+ // Skip ID, see above.
Node: s.Node,
// Skip Address, see above.
// Skip TaggedAddresses, see above.
@@ -395,6 +400,7 @@ func (s *NodeService) IsSame(other *NodeService) bool {
// ToServiceNode converts the given node service to a service node.
func (s *NodeService) ToServiceNode(node string) *ServiceNode {
return &ServiceNode{
+ // Skip ID, see ServiceNode definition.
Node: node,
// Skip Address, see ServiceNode definition.
// Skip TaggedAddresses, see ServiceNode definition.
@@ -501,6 +507,7 @@ OUTER:
// a node. This is currently used for the UI only, as it is
// rather expensive to generate.
type NodeInfo struct {
+ ID types.NodeID
Node string
Address string
TaggedAddresses map[string]string
diff --git a/consul/structs/structs_test.go b/consul/structs/structs_test.go
index c1a39c4f99..30213f4729 100644
--- a/consul/structs/structs_test.go
+++ b/consul/structs/structs_test.go
@@ -107,6 +107,7 @@ func TestStructs_ACL_IsSame(t *testing.T) {
func TestStructs_RegisterRequest_ChangesNode(t *testing.T) {
req := &RegisterRequest{
+ ID: types.NodeID("40e4a748-2192-161a-0510-9bf59fe950b5"),
Node: "test",
Address: "127.0.0.1",
TaggedAddresses: make(map[string]string),
@@ -116,6 +117,7 @@ func TestStructs_RegisterRequest_ChangesNode(t *testing.T) {
}
node := &Node{
+ ID: types.NodeID("40e4a748-2192-161a-0510-9bf59fe950b5"),
Node: "test",
Address: "127.0.0.1",
TaggedAddresses: make(map[string]string),
@@ -140,6 +142,7 @@ func TestStructs_RegisterRequest_ChangesNode(t *testing.T) {
}
}
+ check(func() { req.ID = "nope" }, func() { req.ID = types.NodeID("40e4a748-2192-161a-0510-9bf59fe950b5") })
check(func() { req.Node = "nope" }, func() { req.Node = "test" })
check(func() { req.Address = "127.0.0.2" }, func() { req.Address = "127.0.0.1" })
check(func() { req.TaggedAddresses["wan"] = "nope" }, func() { delete(req.TaggedAddresses, "wan") })
@@ -153,6 +156,7 @@ func TestStructs_RegisterRequest_ChangesNode(t *testing.T) {
// testServiceNode gives a fully filled out ServiceNode instance.
func testServiceNode() *ServiceNode {
return &ServiceNode{
+ ID: types.NodeID("40e4a748-2192-161a-0510-9bf59fe950b5"),
Node: "node1",
Address: "127.0.0.1",
TaggedAddresses: map[string]string{
@@ -182,10 +186,14 @@ func TestStructs_ServiceNode_PartialClone(t *testing.T) {
// Make sure the parts that weren't supposed to be cloned didn't get
// copied over, then zero-value them out so we can do a DeepEqual() on
// the rest of the contents.
- if clone.Address != "" || len(clone.TaggedAddresses) != 0 || len(clone.NodeMeta) != 0 {
+ if clone.ID != "" ||
+ clone.Address != "" ||
+ len(clone.TaggedAddresses) != 0 ||
+ len(clone.NodeMeta) != 0 {
t.Fatalf("bad: %v", clone)
}
+ sn.ID = ""
sn.Address = ""
sn.TaggedAddresses = nil
sn.NodeMeta = nil
@@ -206,6 +214,7 @@ func TestStructs_ServiceNode_Conversions(t *testing.T) {
// These two fields get lost in the conversion, so we have to zero-value
// them out before we do the compare.
+ sn.ID = ""
sn.Address = ""
sn.TaggedAddresses = nil
sn.NodeMeta = nil
diff --git a/website/source/docs/agent/http/catalog.html.markdown b/website/source/docs/agent/http/catalog.html.markdown
index 6d84eea786..bffc4b8884 100644
--- a/website/source/docs/agent/http/catalog.html.markdown
+++ b/website/source/docs/agent/http/catalog.html.markdown
@@ -38,6 +38,7 @@ body must look something like:
```javascript
{
"Datacenter": "dc1",
+ "ID": "40e4a748-2192-161a-0510-9bf59fe950b5",
"Node": "foobar",
"Address": "192.168.10.10",
"TaggedAddresses": {
@@ -74,7 +75,10 @@ to match that of the agent. If only those are provided, the endpoint will regist
the node with the catalog. `TaggedAddresses` can be used in conjunction with the
[`translate_wan_addrs`](/docs/agent/options.html#translate_wan_addrs) configuration
option and the `wan` address. The `lan` address was added in Consul 0.7 to help find
-the LAN address if address translation is enabled.
+the LAN address if address translation is enabled. The `ID` field was added in Consul
+0.7.3 and is optional, but if supplied must be in the form of a hex string, 36
+characters long. This is a unique identifier for this node across all time, even if
+the node name or address changes.
The `Meta` block was added in Consul 0.7.3 to enable associating arbitrary metadata
key/value pairs with a node for filtering purposes. For more information on node metadata,
@@ -208,6 +212,7 @@ It returns a JSON body like this:
```javascript
[
{
+ "ID": "40e4a748-2192-161a-0510-9bf59fe950b5",
"Node": "baz",
"Address": "10.1.10.11",
"TaggedAddresses": {
@@ -219,6 +224,7 @@ It returns a JSON body like this:
}
},
{
+ "ID": "8f246b77-f3e1-ff88-5b48-8ec93abf3e05",
"Node": "foobar",
"Address": "10.1.10.12",
"TaggedAddresses": {
@@ -288,6 +294,8 @@ It returns a JSON body like this:
```javascript
[
{
+ "ID": "40e4a748-2192-161a-0510-9bf59fe950b5",
+ "Node": "foobar",
"Address": "192.168.10.10",
"TaggedAddresses": {
"lan": "192.168.10.10",
@@ -298,7 +306,6 @@ It returns a JSON body like this:
}
"CreateIndex": 51,
"ModifyIndex": 51,
- "Node": "foobar",
"ServiceAddress": "172.17.0.3",
"ServiceEnableTagOverride": false,
"ServiceID": "32a2a47f7992:nodea:5000",
@@ -340,6 +347,7 @@ It returns a JSON body like this:
```javascript
{
"Node": {
+ "ID": "40e4a748-2192-161a-0510-9bf59fe950b5",
"Node": "foobar",
"Address": "10.1.10.12",
"TaggedAddresses": {
diff --git a/website/source/docs/agent/http/health.html.markdown b/website/source/docs/agent/http/health.html.markdown
index 69bfc332ee..84fdfe10ee 100644
--- a/website/source/docs/agent/http/health.html.markdown
+++ b/website/source/docs/agent/http/health.html.markdown
@@ -33,6 +33,8 @@ It returns a JSON body like this:
```javascript
[
{
+ "ID": "40e4a748-2192-161a-0510-9bf59fe950b5",
+ "Node": "foobar",
"Node": "foobar",
"CheckID": "serfHealth",
"Name": "Serf Health Status",
@@ -43,6 +45,7 @@ It returns a JSON body like this:
"ServiceName": ""
},
{
+ "ID": "40e4a748-2192-161a-0510-9bf59fe950b5",
"Node": "foobar",
"CheckID": "service:redis",
"Name": "Service 'redis' check",
@@ -136,6 +139,7 @@ It returns a JSON body like this:
[
{
"Node": {
+ "ID": "40e4a748-2192-161a-0510-9bf59fe950b5",
"Node": "foobar",
"Address": "10.1.10.12",
"TaggedAddresses": {
diff --git a/website/source/docs/agent/http/query.html.markdown b/website/source/docs/agent/http/query.html.markdown
index 014eac8524..83a5358739 100644
--- a/website/source/docs/agent/http/query.html.markdown
+++ b/website/source/docs/agent/http/query.html.markdown
@@ -402,6 +402,7 @@ a JSON body will be returned like this:
"Nodes": [
{
"Node": {
+ "ID": "40e4a748-2192-161a-0510-9bf59fe950b5",
"Node": "foobar",
"Address": "10.1.10.12",
"TaggedAddresses": {
diff --git a/website/source/docs/agent/options.html.markdown b/website/source/docs/agent/options.html.markdown
index 55bc0dbeb3..cf475c8301 100644
--- a/website/source/docs/agent/options.html.markdown
+++ b/website/source/docs/agent/options.html.markdown
@@ -287,8 +287,10 @@ will exit with an error at startup.
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 data directory
- so that it will remain the same across agent restarts. This is currently only exposed via the agent's
- /v1/agent/self endpoint, but future versions of
+ so that it will remain the same across agent restarts. This is currently only exposed via
+ /v1/agent/self,
+ /v1/catalog, and
+ /v1/health endpoints, but future versions of
Consul will use this to better manage cluster changes, especially for Consul servers.
* `-node-meta` - Available in Consul 0.7.3 and later,