state: partition the nodes.uuid and nodes.meta indexes as well (#10882)

This commit is contained in:
R.B. Boyer 2021-08-19 16:17:59 -05:00 committed by GitHub
parent 097e1645e3
commit ac41e30614
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 322 additions and 136 deletions

View File

@ -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)
}

View File

@ -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 {

View File

@ -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

View File

@ -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
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -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())
}