Enable looking up consul nodes by their node ID.

Assuming the following output from a consul agent:

```
==> Consul agent running!
           Version: 'v0.7.3-43-gc5e140c-dev (c5e140c+CHANGES)'
           Node ID: '40e4a748-2192-161a-0510-9bf59fe950b5'
         Node name: 'myhost'
```

it is now possible to lookup nodes by their Node Name or Node ID, or a
prefix match of the Node ID, with the following caveats re: the prefix
match:

1) first eight digits of the Node ID are a required minimum (eight was
   chosen as an arbitrary number)
2) the length of the Node ID must be an even number or no result will be
   returned.

```
% dig @127.0.0.1 -p 8600 myhost.node.dc1.consul.
myhost.node.dc1.consul.	0	IN	A	127.0.0.1
% dig @127.0.0.1 -p 8600 40e4a748-2192-161a-0510-9bf59fe950b5.node.dc1.consul.
40e4a748-2192-161a-0510-9bf59fe950b5.node.dc1.consul. 0	IN A 127.0.0.1
% dig @127.0.0.1 -p 8600 40e4a748.node.dc1.consul.
40e4a748.node.dc1.consul. 0	IN	A	127.0.0.1
% dig @127.0.0.1 -p 8600 40e4a74821.node.dc1.consul.
40e4a74821.node.dc1.consul. 0	IN	A	127.0.0.1
% dig @127.0.0.1 -p 8600 40e4a748-21.node.dc1.consul.
40e4a748-21.node.dc1.consul. 0	IN	A	127.0.0.1
```
This commit is contained in:
Sean Chittenden 2017-02-01 14:20:25 -08:00
parent c5e140c79d
commit f3f3f73e6d
No known key found for this signature in database
GPG Key ID: 4EBC9DC16C2E5E16
3 changed files with 78 additions and 7 deletions

View File

@ -9,6 +9,13 @@ import (
"github.com/hashicorp/go-memdb" "github.com/hashicorp/go-memdb"
) )
const (
// minUUIDLookupLen is used as a minimum length of a node name required before
// we test to see if the name is actually a UUID and perform an ID-based node
// lookup.
minUUIDLookupLen = 8
)
// Nodes is used to pull the full list of nodes for use during snapshots. // Nodes is used to pull the full list of nodes for use during snapshots.
func (s *StateSnapshot) Nodes() (memdb.ResultIterator, error) { func (s *StateSnapshot) Nodes() (memdb.ResultIterator, error) {
iter, err := s.tx.Get("nodes", "id") iter, err := s.tx.Get("nodes", "id")
@ -151,7 +158,7 @@ func (s *StateStore) ensureNodeTxn(tx *memdb.Txn, idx uint64, node *structs.Node
// Check for an existing node // Check for an existing node
existing, err := tx.First("nodes", "id", node.Node) existing, err := tx.First("nodes", "id", node.Node)
if err != nil { if err != nil {
return fmt.Errorf("node lookup failed: %s", err) return fmt.Errorf("node name lookup failed: %s", err)
} }
// Get the indexes // Get the indexes
@ -174,7 +181,7 @@ func (s *StateStore) ensureNodeTxn(tx *memdb.Txn, idx uint64, node *structs.Node
return nil return nil
} }
// GetNode is used to retrieve a node registration by node ID. // GetNode is used to retrieve a node registration by node name ID.
func (s *StateStore) GetNode(id string) (uint64, *structs.Node, error) { func (s *StateStore) GetNode(id string) (uint64, *structs.Node, error) {
tx := s.db.Txn(false) tx := s.db.Txn(false)
defer tx.Abort() defer tx.Abort()
@ -193,6 +200,25 @@ func (s *StateStore) GetNode(id string) (uint64, *structs.Node, error) {
return idx, nil, nil return idx, nil, nil
} }
// GetNodeID is used to retrieve a node registration by node ID.
func (s *StateStore) GetNodeID(id types.NodeID) (uint64, *structs.Node, error) {
tx := s.db.Txn(false)
defer tx.Abort()
// Get the table index.
idx := maxIndexTxn(tx, "nodes")
// Retrieve the node from the state store
node, err := tx.First("nodes", "uuid", string(id))
if err != nil {
return 0, nil, fmt.Errorf("node lookup failed: %s", err)
}
if node != nil {
return idx, node.(*structs.Node), nil
}
return idx, nil, nil
}
// Nodes is used to return all of the known nodes. // Nodes is used to return all of the known nodes.
func (s *StateStore) Nodes(ws memdb.WatchSet) (uint64, structs.Nodes, error) { func (s *StateStore) Nodes(ws memdb.WatchSet) (uint64, structs.Nodes, error) {
tx := s.db.Txn(false) tx := s.db.Txn(false)
@ -662,15 +688,34 @@ func (s *StateStore) NodeServices(ws memdb.WatchSet, nodeName string) (uint64, *
// Get the table index. // Get the table index.
idx := maxIndexTxn(tx, "nodes", "services") idx := maxIndexTxn(tx, "nodes", "services")
// Query the node // Query the node by node name
watchCh, n, err := tx.FirstWatch("nodes", "id", nodeName) watchCh, n, err := tx.FirstWatch("nodes", "id", nodeName)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("node lookup failed: %s", err) return 0, nil, fmt.Errorf("node lookup failed: %s", err)
} }
ws.Add(watchCh)
if n == nil { if n == nil {
if len(nodeName) >= minUUIDLookupLen {
// Attempt to lookup the node by it's node ID
var idWatchCh <-chan struct{}
idWatchCh, n, err = tx.FirstWatch("nodes", "uuid_prefix", nodeName)
if err != nil {
return 0, nil, fmt.Errorf("node ID lookup failed: %s", err)
}
if n == nil {
ws.Add(watchCh)
return 0, nil, nil
} else {
ws.Add(idWatchCh)
}
} else {
ws.Add(watchCh)
return 0, nil, nil return 0, nil, nil
} }
} else {
ws.Add(watchCh)
}
node := n.(*structs.Node) node := n.(*structs.Node)
// Read all of the services // Read all of the services

View File

@ -10,14 +10,23 @@ import (
"github.com/hashicorp/consul/lib" "github.com/hashicorp/consul/lib"
"github.com/hashicorp/consul/types" "github.com/hashicorp/consul/types"
"github.com/hashicorp/go-memdb" "github.com/hashicorp/go-memdb"
uuid "github.com/hashicorp/go-uuid"
) )
func makeRandomNodeID(t *testing.T) types.NodeID {
id, err := uuid.GenerateUUID()
if err != nil {
t.Fatalf("err: %v", err)
}
return types.NodeID(id)
}
func TestStateStore_EnsureRegistration(t *testing.T) { func TestStateStore_EnsureRegistration(t *testing.T) {
s := testStateStore(t) s := testStateStore(t)
// Start with just a node. // Start with just a node.
req := &structs.RegisterRequest{ req := &structs.RegisterRequest{
ID: types.NodeID("40e4a748-2192-161a-0510-9bf59fe950b5"), ID: makeRandomNodeID(t),
Node: "node1", Node: "node1",
Address: "1.2.3.4", Address: "1.2.3.4",
TaggedAddresses: map[string]string{ TaggedAddresses: map[string]string{
@ -27,6 +36,7 @@ func TestStateStore_EnsureRegistration(t *testing.T) {
"somekey": "somevalue", "somekey": "somevalue",
}, },
} }
nodeID := req.ID
if err := s.EnsureRegistration(1, req); err != nil { if err := s.EnsureRegistration(1, req); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
@ -37,7 +47,7 @@ func TestStateStore_EnsureRegistration(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if out.ID != types.NodeID("40e4a748-2192-161a-0510-9bf59fe950b5") || if out.ID != nodeID ||
out.Node != "node1" || out.Address != "1.2.3.4" || out.Node != "node1" || out.Address != "1.2.3.4" ||
len(out.TaggedAddresses) != 1 || len(out.TaggedAddresses) != 1 ||
out.TaggedAddresses["hello"] != "world" || out.TaggedAddresses["hello"] != "world" ||
@ -45,6 +55,13 @@ func TestStateStore_EnsureRegistration(t *testing.T) {
out.CreateIndex != 1 || out.ModifyIndex != 1 { out.CreateIndex != 1 || out.ModifyIndex != 1 {
t.Fatalf("bad node returned: %#v", out) t.Fatalf("bad node returned: %#v", out)
} }
_, out2, err := s.GetNodeID(nodeID)
if err != nil {
t.Fatalf("err: %s", err)
}
if !reflect.DeepEqual(out, out2) {
t.Fatalf("bad node returned: %#v -- %#v", out, out2)
}
} }
verifyNode() verifyNode()
@ -183,6 +200,7 @@ func TestStateStore_EnsureRegistration_Restore(t *testing.T) {
// Start with just a node. // Start with just a node.
req := &structs.RegisterRequest{ req := &structs.RegisterRequest{
ID: makeRandomNodeID(t),
Node: "node1", Node: "node1",
Address: "1.2.3.4", Address: "1.2.3.4",
} }

View File

@ -78,6 +78,14 @@ func nodesTableSchema() *memdb.TableSchema {
Lowercase: true, Lowercase: true,
}, },
}, },
"uuid": &memdb.IndexSchema{
Name: "uuid",
AllowMissing: false,
Unique: true,
Indexer: &memdb.UUIDFieldIndex{
Field: "ID",
},
},
"meta": &memdb.IndexSchema{ "meta": &memdb.IndexSchema{
Name: "meta", Name: "meta",
AllowMissing: true, AllowMissing: true,