From 55cd1d975c9179bca3d276f77ba211fbf3fe81a8 Mon Sep 17 00:00:00 2001 From: James Phillips Date: Wed, 18 Jan 2017 14:26:42 -0800 Subject: [PATCH] Adds catalog support for node IDs. --- api/catalog.go | 5 +- command/agent/local.go | 6 +- command/agent/local_test.go | 38 ++++++++---- consul/catalog_endpoint.go | 6 ++ consul/catalog_endpoint_test.go | 38 ++++++++++-- consul/leader.go | 1 + consul/state/catalog.go | 62 ++++++++++--------- consul/state/catalog_test.go | 4 +- consul/structs/structs.go | 19 ++++-- consul/structs/structs_test.go | 11 +++- .../docs/agent/http/catalog.html.markdown | 12 +++- .../docs/agent/http/health.html.markdown | 4 ++ .../docs/agent/http/query.html.markdown | 1 + .../source/docs/agent/options.html.markdown | 6 +- 14 files changed, 152 insertions(+), 61 deletions(-) diff --git a/api/catalog.go b/api/catalog.go index 10e93b42d..96226f11f 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 853cdd5ad..5ce92b48f 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 79e8b41b2..53fc726a3 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 55c28c004..8ee16c2a4 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 6749d5b20..aa25388a2 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 43cde0666..4efededfe 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 1f790ab69..a6c4448e3 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 e7614098f..71d1e4514 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 bf09ca377..644fb97c0 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 c1a39c4f9..30213f472 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 6d84eea78..bffc4b888 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 69bfc332e..84fdfe10e 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 014eac852..83a535873 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 55bc0dbeb..cf475c830 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,