mirror of
https://github.com/status-im/consul.git
synced 2025-01-13 07:14:37 +00:00
state: partition the nodes.uuid and nodes.meta indexes as well (#10882)
This commit is contained in:
parent
097e1645e3
commit
ac41e30614
@ -6,11 +6,13 @@ import (
|
||||
|
||||
"github.com/armon/go-metrics"
|
||||
"github.com/armon/go-metrics/prometheus"
|
||||
"github.com/hashicorp/consul/agent/metadata"
|
||||
"github.com/hashicorp/consul/types"
|
||||
"github.com/hashicorp/raft"
|
||||
autopilot "github.com/hashicorp/raft-autopilot"
|
||||
"github.com/hashicorp/serf/serf"
|
||||
|
||||
"github.com/hashicorp/consul/agent/metadata"
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/hashicorp/consul/types"
|
||||
)
|
||||
|
||||
var AutopilotGauges = []prometheus.GaugeDefinition{
|
||||
@ -127,7 +129,7 @@ func (s *Server) autopilotServerFromMetadata(srv *metadata.Server) (*autopilot.S
|
||||
// populate the node meta if there is any. When a node first joins or if
|
||||
// there are ACL issues then this could be empty if the server has not
|
||||
// yet been able to register itself in the catalog
|
||||
_, node, err := s.fsm.State().GetNodeID(types.NodeID(srv.ID))
|
||||
_, node, err := s.fsm.State().GetNodeID(types.NodeID(srv.ID), structs.NodeEnterpriseMetaInDefaultPartition())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving node from state store: %w", err)
|
||||
}
|
||||
|
@ -212,6 +212,19 @@ func indexFromUUIDQuery(raw interface{}) ([]byte, error) {
|
||||
return uuidStringToBytes(q.Value)
|
||||
}
|
||||
|
||||
func prefixIndexFromUUIDQuery(arg interface{}) ([]byte, error) {
|
||||
switch v := arg.(type) {
|
||||
case *structs.EnterpriseMeta:
|
||||
return nil, nil
|
||||
case structs.EnterpriseMeta:
|
||||
return nil, nil
|
||||
case Query:
|
||||
return variableLengthUUIDStringToBytes(v.Value)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unexpected type %T for Query prefix index", arg)
|
||||
}
|
||||
|
||||
func multiIndexPolicyFromACLRole(raw interface{}) ([][]byte, error) {
|
||||
role, ok := raw.(*structs.ACLRole)
|
||||
if !ok {
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"strings"
|
||||
|
||||
memdb "github.com/hashicorp/go-memdb"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/mitchellh/copystructure"
|
||||
|
||||
"github.com/hashicorp/consul/acl"
|
||||
@ -202,8 +201,6 @@ func (s *Store) EnsureNode(idx uint64, node *structs.Node) error {
|
||||
func ensureNoNodeWithSimilarNameTxn(tx ReadTxn, node *structs.Node, allowClashWithoutID bool) error {
|
||||
// Retrieve all of the nodes
|
||||
|
||||
// TODO(partitions): since the node UUID field is not partitioned, do we have to do something additional here?
|
||||
|
||||
enodes, err := tx.Get(tableNodes, indexID+"_prefix", node.GetEnterpriseMeta())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot lookup all nodes: %s", err)
|
||||
@ -277,8 +274,7 @@ func (s *Store) ensureNodeTxn(tx WriteTxn, idx uint64, preserveIndexes bool, nod
|
||||
// name is the same.
|
||||
var n *structs.Node
|
||||
if node.ID != "" {
|
||||
// TODO(partitions): should this take a node ent-meta?
|
||||
existing, err := getNodeIDTxn(tx, node.ID)
|
||||
existing, err := getNodeIDTxn(tx, node.ID, node.GetEnterpriseMeta())
|
||||
if err != nil {
|
||||
return fmt.Errorf("node lookup failed: %s", err)
|
||||
}
|
||||
@ -384,14 +380,11 @@ func getNodeTxn(tx ReadTxn, nodeNameOrID string, entMeta *structs.EnterpriseMeta
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func getNodeIDTxn(tx ReadTxn, id types.NodeID) (*structs.Node, error) {
|
||||
strnode := string(id)
|
||||
uuidValue, err := uuid.ParseUUID(strnode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("node lookup by ID failed, wrong UUID: %v for '%s'", err, strnode)
|
||||
}
|
||||
|
||||
node, err := tx.First(tableNodes, "uuid", uuidValue)
|
||||
func getNodeIDTxn(tx ReadTxn, id types.NodeID, entMeta *structs.EnterpriseMeta) (*structs.Node, error) {
|
||||
node, err := tx.First(tableNodes, indexUUID+"_prefix", Query{
|
||||
Value: string(id),
|
||||
EnterpriseMeta: *entMeta,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("node lookup by ID failed: %s", err)
|
||||
}
|
||||
@ -402,17 +395,20 @@ func getNodeIDTxn(tx ReadTxn, id types.NodeID) (*structs.Node, error) {
|
||||
}
|
||||
|
||||
// GetNodeID is used to retrieve a node registration by node ID.
|
||||
func (s *Store) GetNodeID(id types.NodeID) (uint64, *structs.Node, error) {
|
||||
func (s *Store) GetNodeID(id types.NodeID, entMeta *structs.EnterpriseMeta) (uint64, *structs.Node, error) {
|
||||
tx := s.db.Txn(false)
|
||||
defer tx.Abort()
|
||||
|
||||
// TODO: accept non-pointer value
|
||||
if entMeta == nil {
|
||||
entMeta = structs.NodeEnterpriseMetaInDefaultPartition()
|
||||
}
|
||||
|
||||
// Get the table index.
|
||||
///
|
||||
// NOTE: nodeIDs aren't partitioned so don't use the convenience function.
|
||||
idx := maxIndexTxn(tx, tableNodes)
|
||||
idx := catalogNodesMaxIndex(tx, entMeta)
|
||||
|
||||
// Retrieve the node from the state store
|
||||
node, err := getNodeIDTxn(tx, id)
|
||||
node, err := getNodeIDTxn(tx, id, entMeta)
|
||||
return idx, node, err
|
||||
}
|
||||
|
||||
@ -455,19 +451,25 @@ func (s *Store) NodesByMeta(ws memdb.WatchSet, filters map[string]string, entMet
|
||||
}
|
||||
|
||||
// Get the table index.
|
||||
idx := maxIndexTxn(tx, tableNodes)
|
||||
// TODO:(partitions) use the partitioned meta index
|
||||
// idx := catalogNodesMaxIndex(tx, entMeta)
|
||||
_ = entMeta
|
||||
idx := catalogNodesMaxIndex(tx, entMeta)
|
||||
|
||||
// Retrieve all of the nodes
|
||||
var args []interface{}
|
||||
for key, value := range filters {
|
||||
args = append(args, key, value)
|
||||
if len(filters) == 0 {
|
||||
return idx, nil, nil // NodesByMeta is never called with an empty map, but just in case make it return no results.
|
||||
}
|
||||
|
||||
// Retrieve all of the nodes. We'll do a lookup of just ONE KV pair, which
|
||||
// over-matches if multiple pairs are requested, but then in the loop below
|
||||
// we'll finish filtering.
|
||||
var firstKey, firstValue string
|
||||
for firstKey, firstValue = range filters {
|
||||
break
|
||||
}
|
||||
|
||||
nodes, err := tx.Get(tableNodes, "meta", args...)
|
||||
nodes, err := tx.Get(tableNodes, indexMeta, KeyValueQuery{
|
||||
Key: firstKey,
|
||||
Value: firstValue,
|
||||
EnterpriseMeta: *entMeta,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("failed nodes lookup: %s", err)
|
||||
}
|
||||
@ -831,20 +833,34 @@ func (s *Store) ServicesByNodeMeta(ws memdb.WatchSet, filters map[string]string,
|
||||
tx := s.db.Txn(false)
|
||||
defer tx.Abort()
|
||||
|
||||
// TODO: accept non-pointer value
|
||||
if entMeta == nil {
|
||||
entMeta = structs.NodeEnterpriseMetaInDefaultPartition()
|
||||
}
|
||||
|
||||
// Get the table index.
|
||||
idx := catalogServicesMaxIndex(tx, entMeta)
|
||||
if nodeIdx := catalogNodesMaxIndex(tx, entMeta); nodeIdx > idx {
|
||||
idx = nodeIdx
|
||||
}
|
||||
|
||||
// Retrieve all of the nodes with the meta k/v pair
|
||||
var args []interface{}
|
||||
for key, value := range filters {
|
||||
args = append(args, key, value)
|
||||
if len(filters) == 0 {
|
||||
return idx, nil, nil // ServicesByNodeMeta is never called with an empty map, but just in case make it return no results.
|
||||
}
|
||||
|
||||
// Retrieve all of the nodes. We'll do a lookup of just ONE KV pair, which
|
||||
// over-matches if multiple pairs are requested, but then in the loop below
|
||||
// we'll finish filtering.
|
||||
var firstKey, firstValue string
|
||||
for firstKey, firstValue = range filters {
|
||||
break
|
||||
}
|
||||
// TODO(partitions): scope the meta index to a partition
|
||||
nodes, err := tx.Get(tableNodes, "meta", args...)
|
||||
|
||||
nodes, err := tx.Get(tableNodes, indexMeta, KeyValueQuery{
|
||||
Key: firstKey,
|
||||
Value: firstValue,
|
||||
EnterpriseMeta: *entMeta,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("failed nodes lookup: %s", err)
|
||||
}
|
||||
@ -1276,7 +1292,10 @@ func (s *Store) nodeServices(ws memdb.WatchSet, nodeNameOrID string, entMeta *st
|
||||
}
|
||||
|
||||
// Attempt to lookup the node by its node ID
|
||||
iter, err := tx.Get(tableNodes, "uuid_prefix", resizeNodeLookupKey(nodeNameOrID))
|
||||
iter, err := tx.Get(tableNodes, indexUUID+"_prefix", Query{
|
||||
Value: resizeNodeLookupKey(nodeNameOrID),
|
||||
EnterpriseMeta: *entMeta,
|
||||
})
|
||||
if err != nil {
|
||||
ws.Add(watchCh)
|
||||
// TODO(sean@): We could/should log an error re: the uuid_prefix lookup
|
||||
|
@ -4,6 +4,7 @@ package state
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/hashicorp/consul/types"
|
||||
)
|
||||
|
||||
func testIndexerTableChecks() map[string]indexerTestCase {
|
||||
@ -175,6 +176,8 @@ func testIndexerTableGatewayServices() map[string]indexerTestCase {
|
||||
}
|
||||
|
||||
func testIndexerTableNodes() map[string]indexerTestCase {
|
||||
uuidBuf, uuid := generateUUID()
|
||||
|
||||
return map[string]indexerTestCase{
|
||||
indexID: {
|
||||
read: indexValue{
|
||||
@ -200,8 +203,59 @@ func testIndexerTableNodes() map[string]indexerTestCase {
|
||||
},
|
||||
},
|
||||
},
|
||||
// TODO: uuid
|
||||
// TODO: meta
|
||||
indexUUID: {
|
||||
read: indexValue{
|
||||
source: Query{Value: uuid},
|
||||
expected: uuidBuf,
|
||||
},
|
||||
write: indexValue{
|
||||
source: &structs.Node{
|
||||
ID: types.NodeID(uuid),
|
||||
Node: "NoDeId",
|
||||
},
|
||||
expected: uuidBuf,
|
||||
},
|
||||
prefix: []indexValue{
|
||||
{
|
||||
source: (*structs.EnterpriseMeta)(nil),
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
source: structs.EnterpriseMeta{},
|
||||
expected: nil,
|
||||
},
|
||||
{ // partial length
|
||||
source: Query{Value: uuid[:6]},
|
||||
expected: uuidBuf[:3],
|
||||
},
|
||||
{ // full length
|
||||
source: Query{Value: uuid},
|
||||
expected: uuidBuf,
|
||||
},
|
||||
},
|
||||
},
|
||||
indexMeta: {
|
||||
read: indexValue{
|
||||
source: KeyValueQuery{
|
||||
Key: "KeY",
|
||||
Value: "VaLuE",
|
||||
},
|
||||
expected: []byte("KeY\x00VaLuE\x00"),
|
||||
},
|
||||
writeMulti: indexValueMulti{
|
||||
source: &structs.Node{
|
||||
Node: "NoDeId",
|
||||
Meta: map[string]string{
|
||||
"MaP-kEy-1": "mAp-VaL-1",
|
||||
"mAp-KeY-2": "MaP-vAl-2",
|
||||
},
|
||||
},
|
||||
expected: [][]byte{
|
||||
[]byte("MaP-kEy-1\x00mAp-VaL-1\x00"),
|
||||
[]byte("mAp-KeY-2\x00MaP-vAl-2\x00"),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// TODO(partitions): fix schema tests for tables that reference nodes too
|
||||
}
|
||||
|
@ -27,6 +27,8 @@ const (
|
||||
indexUpstream = "upstream"
|
||||
indexDownstream = "downstream"
|
||||
indexGateway = "gateway"
|
||||
indexUUID = "uuid"
|
||||
indexMeta = "meta"
|
||||
)
|
||||
|
||||
// nodesTableSchema returns a new table schema used for storing struct.Node.
|
||||
@ -44,19 +46,23 @@ func nodesTableSchema() *memdb.TableSchema {
|
||||
prefixIndex: prefixIndexFromQueryNoNamespace,
|
||||
},
|
||||
},
|
||||
"uuid": {
|
||||
Name: "uuid",
|
||||
indexUUID: {
|
||||
Name: indexUUID,
|
||||
AllowMissing: true,
|
||||
Unique: true,
|
||||
Indexer: &memdb.UUIDFieldIndex{Field: "ID"},
|
||||
Indexer: indexerSingleWithPrefix{
|
||||
readIndex: indexFromUUIDQuery,
|
||||
writeIndex: indexIDFromNode,
|
||||
prefixIndex: prefixIndexFromUUIDQuery,
|
||||
},
|
||||
},
|
||||
"meta": {
|
||||
Name: "meta",
|
||||
indexMeta: {
|
||||
Name: indexMeta,
|
||||
AllowMissing: true,
|
||||
Unique: false,
|
||||
Indexer: &memdb.StringMapFieldIndex{
|
||||
Field: "Meta",
|
||||
Lowercase: false,
|
||||
Indexer: indexerMulti{
|
||||
readIndex: indexFromKeyValueQuery,
|
||||
writeIndexMulti: indexMetaFromNode,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -78,6 +84,50 @@ func indexFromNode(raw interface{}) ([]byte, error) {
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func indexIDFromNode(raw interface{}) ([]byte, error) {
|
||||
n, ok := raw.(*structs.Node)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected type %T for structs.Node index", raw)
|
||||
}
|
||||
|
||||
if n.ID == "" {
|
||||
return nil, errMissingValueForIndex
|
||||
}
|
||||
|
||||
v, err := uuidStringToBytes(string(n.ID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func indexMetaFromNode(raw interface{}) ([][]byte, error) {
|
||||
n, ok := raw.(*structs.Node)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected type %T for structs.Node index", raw)
|
||||
}
|
||||
|
||||
// NOTE: this is case-sensitive!
|
||||
|
||||
vals := make([][]byte, 0, len(n.Meta))
|
||||
for key, val := range n.Meta {
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var b indexBuilder
|
||||
b.String(key)
|
||||
b.String(val)
|
||||
vals = append(vals, b.Bytes())
|
||||
}
|
||||
if len(vals) == 0 {
|
||||
return nil, errMissingValueForIndex
|
||||
}
|
||||
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
// servicesTableSchema returns a new table schema used to store information
|
||||
// about services.
|
||||
func servicesTableSchema() *memdb.TableSchema {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
@ -29,23 +30,23 @@ func makeRandomNodeID(t *testing.T) types.NodeID {
|
||||
func TestStateStore_GetNodeID(t *testing.T) {
|
||||
s := testStateStore(t)
|
||||
|
||||
_, out, err := s.GetNodeID(types.NodeID("wrongId"))
|
||||
if err == nil || out != nil || !strings.Contains(err.Error(), "node lookup by ID failed, wrong UUID") {
|
||||
t.Fatalf("want an error, nil value, err:=%q ; out:=%q", err.Error(), out)
|
||||
_, out, err := s.GetNodeID(types.NodeID("wrongId"), nil)
|
||||
if err == nil || out != nil || !strings.Contains(err.Error(), "node lookup by ID failed: index error: UUID (without hyphens) must be") {
|
||||
t.Errorf("want an error, nil value, err:=%q ; out:=%q", err.Error(), out)
|
||||
}
|
||||
_, out, err = s.GetNodeID(types.NodeID("0123456789abcdefghijklmnopqrstuvwxyz"))
|
||||
if err == nil || out != nil || !strings.Contains(err.Error(), "node lookup by ID failed, wrong UUID") {
|
||||
t.Fatalf("want an error, nil value, err:=%q ; out:=%q", err, out)
|
||||
_, out, err = s.GetNodeID(types.NodeID("0123456789abcdefghijklmnopqrstuvwxyz"), nil)
|
||||
if err == nil || out != nil || !strings.Contains(err.Error(), "node lookup by ID failed: index error: invalid UUID") {
|
||||
t.Errorf("want an error, nil value, err:=%q ; out:=%q", err, out)
|
||||
}
|
||||
|
||||
_, out, err = s.GetNodeID(types.NodeID("00a916bc-a357-4a19-b886-59419fcee50Z"))
|
||||
if err == nil || out != nil || !strings.Contains(err.Error(), "node lookup by ID failed, wrong UUID") {
|
||||
t.Fatalf("want an error, nil value, err:=%q ; out:=%q", err, out)
|
||||
_, out, err = s.GetNodeID(types.NodeID("00a916bc-a357-4a19-b886-59419fcee50Z"), nil)
|
||||
if err == nil || out != nil || !strings.Contains(err.Error(), "node lookup by ID failed: index error: invalid UUID") {
|
||||
t.Errorf("want an error, nil value, err:=%q ; out:=%q", err, out)
|
||||
}
|
||||
|
||||
_, out, err = s.GetNodeID(types.NodeID("00a916bc-a357-4a19-b886-59419fcee506"))
|
||||
_, out, err = s.GetNodeID(types.NodeID("00a916bc-a357-4a19-b886-59419fcee506"), nil)
|
||||
if err != nil || out != nil {
|
||||
t.Fatalf("do not want any error nor returned value, err:=%q ; out:=%q", err, out)
|
||||
t.Errorf("do not want any error nor returned value, err:=%q ; out:=%q", err, out)
|
||||
}
|
||||
|
||||
nodeID := types.NodeID("00a916bc-a357-4a19-b886-59419fceeaaa")
|
||||
@ -56,14 +57,14 @@ func TestStateStore_GetNodeID(t *testing.T) {
|
||||
}
|
||||
require.NoError(t, s.EnsureRegistration(1, req))
|
||||
|
||||
_, out, err = s.GetNodeID(nodeID)
|
||||
_, out, err = s.GetNodeID(nodeID, nil)
|
||||
require.NoError(t, err)
|
||||
if out == nil || out.ID != nodeID {
|
||||
t.Fatalf("out should not be nil and contain nodeId, but was:=%#v", out)
|
||||
}
|
||||
|
||||
// Case insensitive lookup should work as well
|
||||
_, out, err = s.GetNodeID(types.NodeID("00a916bC-a357-4a19-b886-59419fceeAAA"))
|
||||
_, out, err = s.GetNodeID(types.NodeID("00a916bC-a357-4a19-b886-59419fceeAAA"), nil)
|
||||
require.NoError(t, err)
|
||||
if out == nil || out.ID != nodeID {
|
||||
t.Fatalf("out should not be nil and contain nodeId, but was:=%#v", out)
|
||||
@ -201,7 +202,7 @@ func TestStateStore_EnsureRegistration(t *testing.T) {
|
||||
}
|
||||
require.Equal(t, node, out)
|
||||
|
||||
_, out2, err := s.GetNodeID(nodeID)
|
||||
_, out2, err := s.GetNodeID(nodeID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("got err %s want nil", err)
|
||||
}
|
||||
@ -416,7 +417,7 @@ func TestStateStore_EnsureRegistration_Restore(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if out == nil {
|
||||
_, out, err = s.GetNodeID(types.NodeID(nodeLookup))
|
||||
_, out, err = s.GetNodeID(types.NodeID(nodeLookup), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
@ -695,11 +696,11 @@ func TestNodeRenamingNodes(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if _, node, err := s.GetNodeID(nodeID1); err != nil || node == nil || node.ID != nodeID1 {
|
||||
if _, node, err := s.GetNodeID(nodeID1, nil); err != nil || node == nil || node.ID != nodeID1 {
|
||||
t.Fatalf("err: %s, node:= %q", err, node)
|
||||
}
|
||||
|
||||
if _, node, err := s.GetNodeID(nodeID2); err != nil && node == nil || node.ID != nodeID2 {
|
||||
if _, node, err := s.GetNodeID(nodeID2, nil); err != nil && node == nil || node.ID != nodeID2 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
@ -750,7 +751,7 @@ func TestNodeRenamingNodes(t *testing.T) {
|
||||
}
|
||||
|
||||
// Retrieve the node again
|
||||
idx2, out2, err := s.GetNodeID(nodeID2)
|
||||
idx2, out2, err := s.GetNodeID(nodeID2, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
@ -1137,6 +1138,11 @@ func TestStateStore_GetNodesByMeta(t *testing.T) {
|
||||
filters map[string]string
|
||||
nodes []string
|
||||
}{
|
||||
// Empty meta filter
|
||||
{
|
||||
filters: map[string]string{},
|
||||
nodes: []string{},
|
||||
},
|
||||
// Simple meta filter
|
||||
{
|
||||
filters: map[string]string{"role": "server"},
|
||||
@ -1206,9 +1212,7 @@ func TestStateStore_NodeServices(t *testing.T) {
|
||||
Node: "node1",
|
||||
Address: "1.2.3.4",
|
||||
}
|
||||
if err := s.EnsureRegistration(1, req); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
require.NoError(t, s.EnsureRegistration(1, req))
|
||||
}
|
||||
{
|
||||
req := &structs.RegisterRequest{
|
||||
@ -1216,83 +1220,59 @@ func TestStateStore_NodeServices(t *testing.T) {
|
||||
Node: "node2",
|
||||
Address: "5.6.7.8",
|
||||
}
|
||||
if err := s.EnsureRegistration(2, req); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
require.NoError(t, s.EnsureRegistration(2, req))
|
||||
}
|
||||
|
||||
// Look up by name.
|
||||
{
|
||||
_, ns, err := s.NodeServices(nil, "node1", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
t.Run("Look up by name", func(t *testing.T) {
|
||||
{
|
||||
_, ns, err := s.NodeServices(nil, "node1", nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ns)
|
||||
require.Equal(t, "node1", ns.Node.Node)
|
||||
}
|
||||
if ns == nil || ns.Node.Node != "node1" {
|
||||
t.Fatalf("bad: %#v", *ns)
|
||||
{
|
||||
_, ns, err := s.NodeServices(nil, "node2", nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ns)
|
||||
require.Equal(t, "node2", ns.Node.Node)
|
||||
}
|
||||
}
|
||||
{
|
||||
_, ns, err := s.NodeServices(nil, "node2", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if ns == nil || ns.Node.Node != "node2" {
|
||||
t.Fatalf("bad: %#v", *ns)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Look up by UUID.
|
||||
{
|
||||
_, ns, err := s.NodeServices(nil, "40e4a748-2192-161a-0510-aaaaaaaaaaaa", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
t.Run("Look up by UUID", func(t *testing.T) {
|
||||
{
|
||||
_, ns, err := s.NodeServices(nil, "40e4a748-2192-161a-0510-aaaaaaaaaaaa", nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ns)
|
||||
require.Equal(t, "node1", ns.Node.Node)
|
||||
}
|
||||
if ns == nil || ns.Node.Node != "node1" {
|
||||
t.Fatalf("bad: %#v", ns)
|
||||
{
|
||||
_, ns, err := s.NodeServices(nil, "40e4a748-2192-161a-0510-bbbbbbbbbbbb", nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ns)
|
||||
require.Equal(t, "node2", ns.Node.Node)
|
||||
}
|
||||
}
|
||||
{
|
||||
_, ns, err := s.NodeServices(nil, "40e4a748-2192-161a-0510-bbbbbbbbbbbb", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if ns == nil || ns.Node.Node != "node2" {
|
||||
t.Fatalf("bad: %#v", ns)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Ambiguous prefix.
|
||||
{
|
||||
t.Run("Ambiguous prefix", func(t *testing.T) {
|
||||
_, ns, err := s.NodeServices(nil, "40e4a748-2192-161a-0510", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if ns != nil {
|
||||
t.Fatalf("bad: %#v", ns)
|
||||
}
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, ns)
|
||||
})
|
||||
|
||||
// Bad node, and not a UUID (should not get a UUID error).
|
||||
{
|
||||
t.Run("Bad node", func(t *testing.T) {
|
||||
// Bad node, and not a UUID (should not get a UUID error).
|
||||
_, ns, err := s.NodeServices(nil, "nope", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if ns != nil {
|
||||
t.Fatalf("bad: %#v", ns)
|
||||
}
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, ns)
|
||||
})
|
||||
|
||||
// Specific prefix.
|
||||
{
|
||||
t.Run("Specific prefix", func(t *testing.T) {
|
||||
_, ns, err := s.NodeServices(nil, "40e4a748-2192-161a-0510-bb", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if ns == nil || ns.Node.Node != "node2" {
|
||||
t.Fatalf("bad: %#v", ns)
|
||||
}
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ns)
|
||||
require.Equal(t, "node2", ns.Node.Node)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStateStore_DeleteNode(t *testing.T) {
|
||||
@ -7582,3 +7562,17 @@ func dumpMaxIndexes(t *testing.T, tx ReadTxn) map[string]uint64 {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func generateUUID() ([]byte, string) {
|
||||
buf := make([]byte, 16)
|
||||
if _, err := crand.Read(buf); err != nil {
|
||||
panic(fmt.Errorf("failed to read random bytes: %v", err))
|
||||
}
|
||||
uuid := fmt.Sprintf("%08x-%04x-%04x-%04x-%12x",
|
||||
buf[0:4],
|
||||
buf[4:6],
|
||||
buf[6:8],
|
||||
buf[8:10],
|
||||
buf[10:16])
|
||||
return buf, uuid
|
||||
}
|
||||
|
@ -51,13 +51,25 @@ func indexFromServiceNameAsString(arg interface{}) ([]byte, error) {
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
// uuidStringToBytes is a modified version of memdb.UUIDFieldIndex.parseString
|
||||
func uuidStringToBytes(uuid string) ([]byte, error) {
|
||||
l := len(uuid)
|
||||
if l != 36 {
|
||||
// Verify the length
|
||||
if l := len(uuid); l != 36 {
|
||||
return nil, fmt.Errorf("UUID must be 36 characters")
|
||||
}
|
||||
return parseUUIDString(uuid)
|
||||
}
|
||||
|
||||
func variableLengthUUIDStringToBytes(uuid string) ([]byte, error) {
|
||||
// Verify the length
|
||||
if l := len(uuid); l > 36 {
|
||||
return nil, fmt.Errorf("Invalid UUID length. UUID have 36 characters; got %d", l)
|
||||
}
|
||||
return parseUUIDString(uuid)
|
||||
}
|
||||
|
||||
// parseUUIDString is a modified version of memdb.UUIDFieldIndex.parseString.
|
||||
// Callers should verify the length.
|
||||
func parseUUIDString(uuid string) ([]byte, error) {
|
||||
hyphens := strings.Count(uuid, "-")
|
||||
if hyphens > 4 {
|
||||
return nil, fmt.Errorf(`UUID should have maximum of 4 "-"; got %d`, hyphens)
|
||||
@ -83,3 +95,36 @@ type BoolQuery struct {
|
||||
Value bool
|
||||
structs.EnterpriseMeta
|
||||
}
|
||||
|
||||
// KeyValueQuery is a type used to query for both a key and a value that may
|
||||
// include an enterprise identifier.
|
||||
type KeyValueQuery struct {
|
||||
Key string
|
||||
Value string
|
||||
structs.EnterpriseMeta
|
||||
}
|
||||
|
||||
// NamespaceOrDefault exists because structs.EnterpriseMeta uses a pointer
|
||||
// receiver for this method. Remove once that is fixed.
|
||||
func (q KeyValueQuery) NamespaceOrDefault() string {
|
||||
return q.EnterpriseMeta.NamespaceOrDefault()
|
||||
}
|
||||
|
||||
// PartitionOrDefault exists because structs.EnterpriseMeta uses a pointer
|
||||
// receiver for this method. Remove once that is fixed.
|
||||
func (q KeyValueQuery) PartitionOrDefault() string {
|
||||
return q.EnterpriseMeta.PartitionOrDefault()
|
||||
}
|
||||
|
||||
func indexFromKeyValueQuery(arg interface{}) ([]byte, error) {
|
||||
// NOTE: this is case-sensitive!
|
||||
q, ok := arg.(KeyValueQuery)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected type %T for Query index", arg)
|
||||
}
|
||||
|
||||
var b indexBuilder
|
||||
b.String(q.Key)
|
||||
b.String(q.Value)
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/go-memdb"
|
||||
@ -110,12 +111,20 @@ func (tc indexerTestCase) run(t *testing.T, indexer memdb.Indexer) {
|
||||
}
|
||||
}
|
||||
|
||||
sortMultiByteSlice := func(v [][]byte) {
|
||||
sort.Slice(v, func(i, j int) bool {
|
||||
return string(v[i]) < string(v[j])
|
||||
})
|
||||
}
|
||||
|
||||
if i, ok := indexer.(memdb.MultiIndexer); ok {
|
||||
t.Run("writeIndexMulti", func(t *testing.T) {
|
||||
valid, actual, err := i.FromObject(tc.writeMulti.source)
|
||||
require.NoError(t, err)
|
||||
require.True(t, valid)
|
||||
require.Equal(t, tc.writeMulti.expected, actual)
|
||||
sortMultiByteSlice(actual)
|
||||
sortMultiByteSlice(tc.writeMulti.expected)
|
||||
require.ElementsMatch(t, tc.writeMulti.expected, actual)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -153,7 +153,7 @@ func (s *Store) txnNode(tx WriteTxn, idx uint64, op *structs.TxnNodeOp) (structs
|
||||
|
||||
getNode := func() (*structs.Node, error) {
|
||||
if op.Node.ID != "" {
|
||||
return getNodeIDTxn(tx, op.Node.ID)
|
||||
return getNodeIDTxn(tx, op.Node.ID, op.Node.GetEnterpriseMeta())
|
||||
} else {
|
||||
return getNodeTxn(tx, op.Node.Node, op.Node.GetEnterpriseMeta())
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user