state: partition nodes and coordinates in the state store (#10859)

Additionally:

- partitioned the catalog indexes appropriately for partitioning
- removed a stray reference to a non-existent index named "node.checks"
This commit is contained in:
R.B. Boyer 2021-08-17 13:29:39 -05:00 committed by GitHub
parent 540f88d622
commit 310e775a8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 723 additions and 324 deletions

View File

@ -53,9 +53,10 @@ func TestFSM_RegisterNode(t *testing.T) {
} }
req := structs.RegisterRequest{ req := structs.RegisterRequest{
Datacenter: "dc1", Datacenter: "dc1",
Node: "foo", Node: "foo",
Address: "127.0.0.1", Address: "127.0.0.1",
EnterpriseMeta: *structs.NodeEnterpriseMetaInDefaultPartition(),
} }
buf, err := structs.Encode(structs.RegisterRequestType, req) buf, err := structs.Encode(structs.RegisterRequestType, req)
if err != nil { if err != nil {
@ -114,6 +115,7 @@ func TestFSM_RegisterNode_Service(t *testing.T) {
Status: api.HealthPassing, Status: api.HealthPassing,
ServiceID: "db", ServiceID: "db",
}, },
EnterpriseMeta: *structs.NodeEnterpriseMetaInDefaultPartition(),
} }
buf, err := structs.Encode(structs.RegisterRequestType, req) buf, err := structs.Encode(structs.RegisterRequestType, req)
if err != nil { if err != nil {
@ -712,12 +714,14 @@ func TestFSM_CoordinateUpdate(t *testing.T) {
// Write a batch of two coordinates. // Write a batch of two coordinates.
updates := structs.Coordinates{ updates := structs.Coordinates{
&structs.Coordinate{ &structs.Coordinate{
Node: "node1", Node: "node1",
Coord: generateRandomCoordinate(), Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
Coord: generateRandomCoordinate(),
}, },
&structs.Coordinate{ &structs.Coordinate{
Node: "node2", Node: "node2",
Coord: generateRandomCoordinate(), Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
Coord: generateRandomCoordinate(),
}, },
} }
buf, err := structs.Encode(structs.CoordinateBatchUpdateType, updates) buf, err := structs.Encode(structs.CoordinateBatchUpdateType, updates)
@ -734,9 +738,7 @@ func TestFSM_CoordinateUpdate(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if !reflect.DeepEqual(coords, updates) { require.Equal(t, updates, coords)
t.Fatalf("bad: %#v", coords)
}
} }
func TestFSM_SessionCreate_Destroy(t *testing.T) { func TestFSM_SessionCreate_Destroy(t *testing.T) {

View File

@ -96,6 +96,8 @@ func (s *snapshot) persistNodes(sink raft.SnapshotSink,
// Register each node // Register each node
for node := nodes.Next(); node != nil; node = nodes.Next() { for node := nodes.Next(); node != nil; node = nodes.Next() {
n := node.(*structs.Node) n := node.(*structs.Node)
nodeEntMeta := n.GetEnterpriseMeta()
req := structs.RegisterRequest{ req := structs.RegisterRequest{
ID: n.ID, ID: n.ID,
Node: n.Node, Node: n.Node,
@ -104,6 +106,7 @@ func (s *snapshot) persistNodes(sink raft.SnapshotSink,
TaggedAddresses: n.TaggedAddresses, TaggedAddresses: n.TaggedAddresses,
NodeMeta: n.Meta, NodeMeta: n.Meta,
RaftIndex: n.RaftIndex, RaftIndex: n.RaftIndex,
EnterpriseMeta: *nodeEntMeta,
} }
// Register the node itself // Register the node itself
@ -115,8 +118,7 @@ func (s *snapshot) persistNodes(sink raft.SnapshotSink,
} }
// Register each service this node has // Register each service this node has
// TODO(partitions) services, err := s.state.Services(n.Node, nodeEntMeta)
services, err := s.state.Services(n.Node, nil)
if err != nil { if err != nil {
return err return err
} }
@ -132,8 +134,7 @@ func (s *snapshot) persistNodes(sink raft.SnapshotSink,
// Register each check this node has // Register each check this node has
req.Service = nil req.Service = nil
// TODO(partitions) checks, err := s.state.Checks(n.Node, nodeEntMeta)
checks, err := s.state.Checks(n.Node, nil)
if err != nil { if err != nil {
return err return err
} }

View File

@ -876,6 +876,7 @@ func TestInternal_GatewayServiceDump_Terminating(t *testing.T) {
{ {
Node: &structs.Node{ Node: &structs.Node{
Node: "baz", Node: "baz",
Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
Address: "127.0.0.3", Address: "127.0.0.3",
Datacenter: "dc1", Datacenter: "dc1",
}, },
@ -908,6 +909,7 @@ func TestInternal_GatewayServiceDump_Terminating(t *testing.T) {
{ {
Node: &structs.Node{ Node: &structs.Node{
Node: "bar", Node: "bar",
Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
Address: "127.0.0.2", Address: "127.0.0.2",
Datacenter: "dc1", Datacenter: "dc1",
}, },
@ -1215,6 +1217,7 @@ func TestInternal_GatewayServiceDump_Ingress(t *testing.T) {
{ {
Node: &structs.Node{ Node: &structs.Node{
Node: "bar", Node: "bar",
Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
Address: "127.0.0.2", Address: "127.0.0.2",
Datacenter: "dc1", Datacenter: "dc1",
}, },
@ -1250,6 +1253,7 @@ func TestInternal_GatewayServiceDump_Ingress(t *testing.T) {
{ {
Node: &structs.Node{ Node: &structs.Node{
Node: "baz", Node: "baz",
Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
Address: "127.0.0.3", Address: "127.0.0.3",
Datacenter: "dc1", Datacenter: "dc1",
}, },

View File

@ -49,16 +49,28 @@ func (s *Snapshot) Nodes() (memdb.ResultIterator, error) {
// Services is used to pull the full list of services for a given node for use // Services is used to pull the full list of services for a given node for use
// during snapshots. // during snapshots.
func (s *Snapshot) Services(node string, _ *structs.EnterpriseMeta) (memdb.ResultIterator, error) { func (s *Snapshot) Services(node string, entMeta *structs.EnterpriseMeta) (memdb.ResultIterator, error) {
// TODO(partitions): use the provided entmeta // TODO: accept non-pointer value
return s.tx.Get(tableServices, indexNode, Query{Value: node}) if entMeta == nil {
entMeta = structs.NodeEnterpriseMetaInDefaultPartition()
}
return s.tx.Get(tableServices, indexNode, Query{
Value: node,
EnterpriseMeta: *entMeta,
})
} }
// Checks is used to pull the full list of checks for a given node for use // Checks is used to pull the full list of checks for a given node for use
// during snapshots. // during snapshots.
func (s *Snapshot) Checks(node string, _ *structs.EnterpriseMeta) (memdb.ResultIterator, error) { func (s *Snapshot) Checks(node string, entMeta *structs.EnterpriseMeta) (memdb.ResultIterator, error) {
// TODO(partitions): use the provided entmeta // TODO: accept non-pointer value
return s.tx.Get(tableChecks, indexNode, Query{Value: node}) if entMeta == nil {
entMeta = structs.NodeEnterpriseMetaInDefaultPartition()
}
return s.tx.Get(tableChecks, indexNode, Query{
Value: node,
EnterpriseMeta: *entMeta,
})
} }
// Registration is used to make sure a node, service, and check registration is // Registration is used to make sure a node, service, and check registration is
@ -83,6 +95,7 @@ func (s *Store) EnsureRegistration(idx uint64, req *structs.RegisterRequest) err
} }
func (s *Store) ensureCheckIfNodeMatches(tx WriteTxn, idx uint64, preserveIndexes bool, node string, check *structs.HealthCheck) error { func (s *Store) ensureCheckIfNodeMatches(tx WriteTxn, idx uint64, preserveIndexes bool, node string, check *structs.HealthCheck) error {
// TODO(partitions): do we have to check partition here? probably not
if check.Node != node { if check.Node != node {
return fmt.Errorf("check node %q does not match node %q", return fmt.Errorf("check node %q does not match node %q",
check.Node, node) check.Node, node)
@ -107,6 +120,7 @@ func (s *Store) ensureRegistrationTxn(tx WriteTxn, idx uint64, preserveIndexes b
Node: req.Node, Node: req.Node,
Address: req.Address, Address: req.Address,
Datacenter: req.Datacenter, Datacenter: req.Datacenter,
Partition: req.PartitionOrDefault(),
TaggedAddresses: req.TaggedAddresses, TaggedAddresses: req.TaggedAddresses,
Meta: req.NodeMeta, Meta: req.NodeMeta,
} }
@ -121,7 +135,10 @@ func (s *Store) ensureRegistrationTxn(tx WriteTxn, idx uint64, preserveIndexes b
// modify the node at all so we prevent watch churn and useless writes // modify the node at all so we prevent watch churn and useless writes
// and modify index bumps on the node. // and modify index bumps on the node.
{ {
existing, err := tx.First(tableNodes, indexID, Query{Value: node.Node}) existing, err := tx.First(tableNodes, indexID, Query{
Value: node.Node,
EnterpriseMeta: *node.GetEnterpriseMeta(),
})
if err != nil { if err != nil {
return fmt.Errorf("node lookup failed: %s", err) return fmt.Errorf("node lookup failed: %s", err)
} }
@ -136,7 +153,11 @@ func (s *Store) ensureRegistrationTxn(tx WriteTxn, idx uint64, preserveIndexes b
// node info above to make sure we actually need to update the service // node info above to make sure we actually need to update the service
// definition in order to prevent useless churn if nothing has changed. // definition in order to prevent useless churn if nothing has changed.
if req.Service != nil { if req.Service != nil {
existing, err := tx.First(tableServices, indexID, NodeServiceQuery{EnterpriseMeta: req.Service.EnterpriseMeta, Node: req.Node, Service: req.Service.ID}) existing, err := tx.First(tableServices, indexID, NodeServiceQuery{
EnterpriseMeta: req.Service.EnterpriseMeta,
Node: req.Node,
Service: req.Service.ID,
})
if err != nil { if err != nil {
return fmt.Errorf("failed service lookup: %s", err) return fmt.Errorf("failed service lookup: %s", err)
} }
@ -180,7 +201,8 @@ func (s *Store) EnsureNode(idx uint64, node *structs.Node) error {
// If allowClashWithoutID then, getting a conflict on another node without ID will be allowed // If allowClashWithoutID then, getting a conflict on another node without ID will be allowed
func ensureNoNodeWithSimilarNameTxn(tx ReadTxn, node *structs.Node, allowClashWithoutID bool) error { func ensureNoNodeWithSimilarNameTxn(tx ReadTxn, node *structs.Node, allowClashWithoutID bool) error {
// Retrieve all of the nodes // Retrieve all of the nodes
enodes, err := tx.Get(tableNodes, indexID)
enodes, err := tx.Get(tableNodes, indexID+"_prefix", node.GetEnterpriseMeta())
if err != nil { if err != nil {
return fmt.Errorf("Cannot lookup all nodes: %s", err) return fmt.Errorf("Cannot lookup all nodes: %s", err)
} }
@ -189,7 +211,11 @@ func ensureNoNodeWithSimilarNameTxn(tx ReadTxn, node *structs.Node, allowClashWi
if strings.EqualFold(node.Node, enode.Node) && node.ID != enode.ID { if strings.EqualFold(node.Node, enode.Node) && node.ID != enode.ID {
// Look up the existing node's Serf health check to see if it's failed. // Look up the existing node's Serf health check to see if it's failed.
// If it is, the node can be renamed. // If it is, the node can be renamed.
enodeCheck, err := tx.First(tableChecks, indexID, NodeCheckQuery{EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(), Node: enode.Node, CheckID: string(structs.SerfCheckID)}) enodeCheck, err := tx.First(tableChecks, indexID, NodeCheckQuery{
EnterpriseMeta: *node.GetEnterpriseMeta(),
Node: enode.Node,
CheckID: string(structs.SerfCheckID),
})
if err != nil { if err != nil {
return fmt.Errorf("Cannot get status of node %s: %s", enode.Node, err) return fmt.Errorf("Cannot get status of node %s: %s", enode.Node, err)
} }
@ -216,7 +242,7 @@ func ensureNoNodeWithSimilarNameTxn(tx ReadTxn, node *structs.Node, allowClashWi
// Returns a bool indicating if a write happened and any error. // Returns a bool indicating if a write happened and any error.
func (s *Store) ensureNodeCASTxn(tx WriteTxn, idx uint64, node *structs.Node) (bool, error) { func (s *Store) ensureNodeCASTxn(tx WriteTxn, idx uint64, node *structs.Node) (bool, error) {
// Retrieve the existing entry. // Retrieve the existing entry.
existing, err := getNodeTxn(tx, node.Node) existing, err := getNodeTxn(tx, node.Node, node.GetEnterpriseMeta())
if err != nil { if err != nil {
return false, err return false, err
} }
@ -249,6 +275,7 @@ func (s *Store) ensureNodeTxn(tx WriteTxn, idx uint64, preserveIndexes bool, nod
// name is the same. // name is the same.
var n *structs.Node var n *structs.Node
if node.ID != "" { if node.ID != "" {
// TODO(partitions): should this take a node ent-meta?
existing, err := getNodeIDTxn(tx, node.ID) existing, err := getNodeIDTxn(tx, node.ID)
if err != nil { if err != nil {
return fmt.Errorf("node lookup failed: %s", err) return fmt.Errorf("node lookup failed: %s", err)
@ -262,7 +289,7 @@ func (s *Store) ensureNodeTxn(tx WriteTxn, idx uint64, preserveIndexes bool, nod
return fmt.Errorf("Error while renaming Node ID: %q (%s): %s", node.ID, node.Address, dupNameError) return fmt.Errorf("Error while renaming Node ID: %q (%s): %s", node.ID, node.Address, dupNameError)
} }
// We are actually renaming a node, remove its reference first // We are actually renaming a node, remove its reference first
err := s.deleteNodeTxn(tx, idx, n.Node) err := s.deleteNodeTxn(tx, idx, n.Node, n.GetEnterpriseMeta())
if err != nil { if err != nil {
return fmt.Errorf("Error while renaming Node ID: %q (%s) from %s to %s", return fmt.Errorf("Error while renaming Node ID: %q (%s) from %s to %s",
node.ID, node.Address, n.Node, node.Node) node.ID, node.Address, n.Node, node.Node)
@ -282,7 +309,10 @@ func (s *Store) ensureNodeTxn(tx WriteTxn, idx uint64, preserveIndexes bool, nod
// Check for an existing node by name to support nodes with no IDs. // Check for an existing node by name to support nodes with no IDs.
if n == nil { if n == nil {
existing, err := tx.First(tableNodes, indexID, Query{Value: node.Node}) existing, err := tx.First(tableNodes, indexID, Query{
Value: node.Node,
EnterpriseMeta: *node.GetEnterpriseMeta(),
})
if err != nil { if err != nil {
return fmt.Errorf("node name lookup failed: %s", err) return fmt.Errorf("node name lookup failed: %s", err)
} }
@ -314,41 +344,35 @@ func (s *Store) ensureNodeTxn(tx WriteTxn, idx uint64, preserveIndexes bool, nod
} }
// Insert the node and update the index. // Insert the node and update the index.
if err := tx.Insert("nodes", node); err != nil { return catalogInsertNode(tx, node)
return fmt.Errorf("failed inserting node: %s", err)
}
if err := tx.Insert(tableIndex, &IndexEntry{"nodes", idx}); err != nil {
return fmt.Errorf("failed updating index: %s", err)
}
// Update the node's service indexes as the node information is included
// in health queries and we would otherwise miss node updates in some cases
// for those queries.
if err := updateAllServiceIndexesOfNode(tx, idx, node.Node); err != nil {
return fmt.Errorf("failed updating index: %s", err)
}
return nil
} }
// GetNode is used to retrieve a node registration by node name ID. // GetNode is used to retrieve a node registration by node name ID.
func (s *Store) GetNode(nodeNameOrID string, _ *structs.EnterpriseMeta) (uint64, *structs.Node, error) { func (s *Store) GetNode(nodeNameOrID string, entMeta *structs.EnterpriseMeta) (uint64, *structs.Node, error) {
// TODO(partitions): use the provided entmeta
tx := s.db.Txn(false) tx := s.db.Txn(false)
defer tx.Abort() defer tx.Abort()
// TODO: accept non-pointer value
if entMeta == nil {
entMeta = structs.NodeEnterpriseMetaInDefaultPartition()
}
// Get the table index. // Get the table index.
idx := maxIndexTxn(tx, "nodes") idx := catalogNodesMaxIndex(tx, entMeta)
// Retrieve the node from the state store // Retrieve the node from the state store
node, err := getNodeTxn(tx, nodeNameOrID) node, err := getNodeTxn(tx, nodeNameOrID, entMeta)
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)
} }
return idx, node, nil return idx, node, nil
} }
func getNodeTxn(tx ReadTxn, nodeName string) (*structs.Node, error) { func getNodeTxn(tx ReadTxn, nodeNameOrID string, entMeta *structs.EnterpriseMeta) (*structs.Node, error) {
node, err := tx.First(tableNodes, indexID, Query{Value: nodeName}) node, err := tx.First(tableNodes, indexID, Query{
Value: nodeNameOrID,
EnterpriseMeta: *entMeta,
})
if err != nil { if err != nil {
return nil, fmt.Errorf("node lookup failed: %s", err) return nil, fmt.Errorf("node lookup failed: %s", err)
} }
@ -365,7 +389,7 @@ func getNodeIDTxn(tx ReadTxn, id types.NodeID) (*structs.Node, error) {
return nil, fmt.Errorf("node lookup by ID failed, wrong UUID: %v for '%s'", err, strnode) return nil, fmt.Errorf("node lookup by ID failed, wrong UUID: %v for '%s'", err, strnode)
} }
node, err := tx.First("nodes", "uuid", uuidValue) node, err := tx.First(tableNodes, "uuid", uuidValue)
if err != nil { if err != nil {
return nil, fmt.Errorf("node lookup by ID failed: %s", err) return nil, fmt.Errorf("node lookup by ID failed: %s", err)
} }
@ -381,7 +405,9 @@ func (s *Store) GetNodeID(id types.NodeID) (uint64, *structs.Node, error) {
defer tx.Abort() defer tx.Abort()
// Get the table index. // Get the table index.
idx := maxIndexTxn(tx, "nodes") ///
// NOTE: nodeIDs aren't partitioned so don't use the convenience function.
idx := maxIndexTxn(tx, tableNodes)
// Retrieve the node from the state store // Retrieve the node from the state store
node, err := getNodeIDTxn(tx, id) node, err := getNodeIDTxn(tx, id)
@ -389,16 +415,20 @@ func (s *Store) GetNodeID(id types.NodeID) (uint64, *structs.Node, error) {
} }
// Nodes is used to return all of the known nodes. // Nodes is used to return all of the known nodes.
func (s *Store) Nodes(ws memdb.WatchSet, _ *structs.EnterpriseMeta) (uint64, structs.Nodes, error) { func (s *Store) Nodes(ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (uint64, structs.Nodes, error) {
// TODO(partitions): use the provided entmeta
tx := s.db.Txn(false) tx := s.db.Txn(false)
defer tx.Abort() defer tx.Abort()
// TODO: accept non-pointer value
if entMeta == nil {
entMeta = structs.NodeEnterpriseMetaInDefaultPartition()
}
// Get the table index. // Get the table index.
idx := maxIndexTxn(tx, "nodes") idx := catalogNodesMaxIndex(tx, entMeta)
// Retrieve all of the nodes // Retrieve all of the nodes
nodes, err := tx.Get(tableNodes, indexID) nodes, err := tx.Get(tableNodes, indexID+"_prefix", entMeta)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed nodes lookup: %s", err) return 0, nil, fmt.Errorf("failed nodes lookup: %s", err)
} }
@ -413,13 +443,20 @@ func (s *Store) Nodes(ws memdb.WatchSet, _ *structs.EnterpriseMeta) (uint64, str
} }
// NodesByMeta is used to return all nodes with the given metadata key/value pairs. // NodesByMeta is used to return all nodes with the given metadata key/value pairs.
func (s *Store) NodesByMeta(ws memdb.WatchSet, filters map[string]string, _ *structs.EnterpriseMeta) (uint64, structs.Nodes, error) { func (s *Store) NodesByMeta(ws memdb.WatchSet, filters map[string]string, entMeta *structs.EnterpriseMeta) (uint64, structs.Nodes, error) {
// TODO(partitions): use the provided entmeta
tx := s.db.Txn(false) tx := s.db.Txn(false)
defer tx.Abort() defer tx.Abort()
// TODO: accept non-pointer value
if entMeta == nil {
entMeta = structs.NodeEnterpriseMetaInDefaultPartition()
}
// Get the table index. // Get the table index.
idx := maxIndexTxn(tx, "nodes") idx := maxIndexTxn(tx, tableNodes)
// TODO:(partitions) use the partitioned meta index
// idx := catalogNodesMaxIndex(tx, entMeta)
_ = entMeta
// Retrieve all of the nodes // Retrieve all of the nodes
var args []interface{} var args []interface{}
@ -427,7 +464,8 @@ func (s *Store) NodesByMeta(ws memdb.WatchSet, filters map[string]string, _ *str
args = append(args, key, value) args = append(args, key, value)
break break
} }
nodes, err := tx.Get("nodes", "meta", args...)
nodes, err := tx.Get(tableNodes, "meta", args...)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed nodes lookup: %s", err) return 0, nil, fmt.Errorf("failed nodes lookup: %s", err)
} }
@ -445,13 +483,20 @@ func (s *Store) NodesByMeta(ws memdb.WatchSet, filters map[string]string, _ *str
} }
// DeleteNode is used to delete a given node by its ID. // DeleteNode is used to delete a given node by its ID.
func (s *Store) DeleteNode(idx uint64, nodeName string, _ *structs.EnterpriseMeta) error { func (s *Store) DeleteNode(idx uint64, nodeName string, entMeta *structs.EnterpriseMeta) error {
// TODO(partitions): use the provided entmeta
tx := s.db.WriteTxn(idx) tx := s.db.WriteTxn(idx)
defer tx.Abort() defer tx.Abort()
// TODO(partition): double check all freshly modified state store functions
// that take an ent meta do this trick
// TODO: accept non-pointer value
if entMeta == nil {
entMeta = structs.NodeEnterpriseMetaInDefaultPartition()
}
// Call the node deletion. // Call the node deletion.
if err := s.deleteNodeTxn(tx, idx, nodeName); err != nil { if err := s.deleteNodeTxn(tx, idx, nodeName, entMeta); err != nil {
return err return err
} }
@ -461,9 +506,9 @@ func (s *Store) DeleteNode(idx uint64, nodeName string, _ *structs.EnterpriseMet
// deleteNodeCASTxn is used to try doing a node delete operation with a given // deleteNodeCASTxn is used to try doing a node delete operation with a given
// raft index. If the CAS index specified is not equal to the last observed index for // raft index. If the CAS index specified is not equal to the last observed index for
// the given check, then the call is a noop, otherwise a normal check delete is invoked. // the given check, then the call is a noop, otherwise a normal check delete is invoked.
func (s *Store) deleteNodeCASTxn(tx WriteTxn, idx, cidx uint64, nodeName string) (bool, error) { func (s *Store) deleteNodeCASTxn(tx WriteTxn, idx, cidx uint64, nodeName string, entMeta *structs.EnterpriseMeta) (bool, error) {
// Look up the node. // Look up the node.
node, err := getNodeTxn(tx, nodeName) node, err := getNodeTxn(tx, nodeName, entMeta)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -479,7 +524,7 @@ func (s *Store) deleteNodeCASTxn(tx WriteTxn, idx, cidx uint64, nodeName string)
} }
// Call the actual deletion if the above passed. // Call the actual deletion if the above passed.
if err := s.deleteNodeTxn(tx, idx, nodeName); err != nil { if err := s.deleteNodeTxn(tx, idx, nodeName, entMeta); err != nil {
return false, err return false, err
} }
@ -488,9 +533,17 @@ func (s *Store) deleteNodeCASTxn(tx WriteTxn, idx, cidx uint64, nodeName string)
// deleteNodeTxn is the inner method used for removing a node from // deleteNodeTxn is the inner method used for removing a node from
// the store within a given transaction. // the store within a given transaction.
func (s *Store) deleteNodeTxn(tx WriteTxn, idx uint64, nodeName string) error { func (s *Store) deleteNodeTxn(tx WriteTxn, idx uint64, nodeName string, entMeta *structs.EnterpriseMeta) error {
// TODO: accept non-pointer value
if entMeta == nil {
entMeta = structs.DefaultEnterpriseMetaInDefaultPartition()
}
// Look up the node. // Look up the node.
node, err := tx.First(tableNodes, indexID, Query{Value: nodeName}) node, err := tx.First(tableNodes, indexID, Query{
Value: nodeName,
EnterpriseMeta: *entMeta,
})
if err != nil { if err != nil {
return fmt.Errorf("node lookup failed: %s", err) return fmt.Errorf("node lookup failed: %s", err)
} }
@ -499,7 +552,10 @@ func (s *Store) deleteNodeTxn(tx WriteTxn, idx uint64, nodeName string) error {
} }
// Delete all services associated with the node and update the service index. // Delete all services associated with the node and update the service index.
services, err := tx.Get(tableServices, indexNode, Query{Value: nodeName}) services, err := tx.Get(tableServices, indexNode, Query{
Value: nodeName,
EnterpriseMeta: *entMeta,
})
if err != nil { if err != nil {
return fmt.Errorf("failed service lookup: %s", err) return fmt.Errorf("failed service lookup: %s", err)
} }
@ -525,7 +581,10 @@ func (s *Store) deleteNodeTxn(tx WriteTxn, idx uint64, nodeName string) error {
// Delete all checks associated with the node. This will invalidate // Delete all checks associated with the node. This will invalidate
// sessions as necessary. // sessions as necessary.
checks, err := tx.Get(tableChecks, indexNode, Query{Value: nodeName}) checks, err := tx.Get(tableChecks, indexNode, Query{
Value: nodeName,
EnterpriseMeta: *entMeta,
})
if err != nil { if err != nil {
return fmt.Errorf("failed check lookup: %s", err) return fmt.Errorf("failed check lookup: %s", err)
} }
@ -542,28 +601,28 @@ func (s *Store) deleteNodeTxn(tx WriteTxn, idx uint64, nodeName string) error {
} }
// Delete any coordinates associated with this node. // Delete any coordinates associated with this node.
coords, err := tx.Get("coordinates", "node", nodeName) coords, err := tx.Get(tableCoordinates, indexNode, Query{
Value: nodeName,
EnterpriseMeta: *entMeta,
})
if err != nil { if err != nil {
return fmt.Errorf("failed coordinate lookup: %s", err) return fmt.Errorf("failed coordinate lookup: %s", err)
} }
var coordsToDelete []interface{} var coordsToDelete []*structs.Coordinate
for coord := coords.Next(); coord != nil; coord = coords.Next() { for coord := coords.Next(); coord != nil; coord = coords.Next() {
coordsToDelete = append(coordsToDelete, coord) coordsToDelete = append(coordsToDelete, coord.(*structs.Coordinate))
} }
for _, coord := range coordsToDelete { for _, coord := range coordsToDelete {
if err := tx.Delete("coordinates", coord); err != nil { if err := deleteCoordinateTxn(tx, idx, coord); err != nil {
return fmt.Errorf("failed deleting coordinate: %s", err) return fmt.Errorf("failed deleting coordinate: %s", err)
} }
if err := tx.Insert(tableIndex, &IndexEntry{"coordinates", idx}); err != nil {
return fmt.Errorf("failed updating index: %s", err)
}
} }
// Delete the node and update the index. // Delete the node and update the index.
if err := tx.Delete("nodes", node); err != nil { if err := tx.Delete(tableNodes, node); err != nil {
return fmt.Errorf("failed deleting node: %s", err) return fmt.Errorf("failed deleting node: %s", err)
} }
if err := tx.Insert(tableIndex, &IndexEntry{"nodes", idx}); err != nil { if err := catalogUpdateNodesIndexes(tx, idx, entMeta); err != nil {
return fmt.Errorf("failed updating index: %s", err) return fmt.Errorf("failed updating index: %s", err)
} }
@ -626,7 +685,11 @@ func ensureServiceCASTxn(tx WriteTxn, idx uint64, node string, svc *structs.Node
// existing memdb transaction. // existing memdb transaction.
func ensureServiceTxn(tx WriteTxn, idx uint64, node string, preserveIndexes bool, svc *structs.NodeService) error { func ensureServiceTxn(tx WriteTxn, idx uint64, node string, preserveIndexes bool, svc *structs.NodeService) error {
// Check for existing service // Check for existing service
existing, err := tx.First(tableServices, indexID, NodeServiceQuery{EnterpriseMeta: svc.EnterpriseMeta, Node: node, Service: svc.ID}) existing, err := tx.First(tableServices, indexID, NodeServiceQuery{
EnterpriseMeta: svc.EnterpriseMeta,
Node: node,
Service: svc.ID,
})
if err != nil { if err != nil {
return fmt.Errorf("failed service lookup: %s", err) return fmt.Errorf("failed service lookup: %s", err)
} }
@ -651,7 +714,10 @@ func ensureServiceTxn(tx WriteTxn, idx uint64, node string, preserveIndexes bool
// That's always populated when we read from the state store. // That's always populated when we read from the state store.
entry := svc.ToServiceNode(node) entry := svc.ToServiceNode(node)
// Get the node // Get the node
n, err := tx.First(tableNodes, indexID, Query{Value: node}) n, err := tx.First(tableNodes, indexID, Query{
Value: node,
EnterpriseMeta: svc.EnterpriseMeta,
})
if err != nil { if err != nil {
return fmt.Errorf("failed node lookup: %s", err) return fmt.Errorf("failed node lookup: %s", err)
} }
@ -765,7 +831,7 @@ func (s *Store) ServicesByNodeMeta(ws memdb.WatchSet, filters map[string]string,
// Get the table index. // Get the table index.
idx := catalogServicesMaxIndex(tx, entMeta) idx := catalogServicesMaxIndex(tx, entMeta)
if nodeIdx := maxIndexTxn(tx, "nodes"); nodeIdx > idx { if nodeIdx := catalogNodesMaxIndex(tx, entMeta); nodeIdx > idx {
idx = nodeIdx idx = nodeIdx
} }
@ -775,7 +841,8 @@ func (s *Store) ServicesByNodeMeta(ws memdb.WatchSet, filters map[string]string,
args = append(args, key, value) args = append(args, key, value)
break break
} }
nodes, err := tx.Get("nodes", "meta", args...) // TODO(partitions): scope the meta index to a partition
nodes, err := tx.Get(tableNodes, "meta", args...)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed nodes lookup: %s", err) return 0, nil, fmt.Errorf("failed nodes lookup: %s", err)
} }
@ -971,7 +1038,7 @@ func serviceNodesTxn(tx ReadTxn, ws memdb.WatchSet, index string, q Query) (uint
} }
// Fill in the node details. // Fill in the node details.
results, err = parseServiceNodes(tx, ws, results) results, err = parseServiceNodes(tx, ws, results, &q.EnterpriseMeta)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed parsing service nodes: %s", err) return 0, nil, fmt.Errorf("failed parsing service nodes: %s", err)
} }
@ -1017,7 +1084,7 @@ func (s *Store) ServiceTagNodes(ws memdb.WatchSet, service string, tags []string
} }
// Fill in the node details. // Fill in the node details.
results, err = parseServiceNodes(tx, ws, results) results, err = parseServiceNodes(tx, ws, results, entMeta)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed parsing service nodes: %s", err) return 0, nil, fmt.Errorf("failed parsing service nodes: %s", err)
} }
@ -1087,7 +1154,7 @@ func (s *Store) ServiceAddressNodes(ws memdb.WatchSet, address string, entMeta *
} }
// Fill in the node details. // Fill in the node details.
results, err = parseServiceNodes(tx, ws, results) results, err = parseServiceNodes(tx, ws, results, entMeta)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed parsing service nodes: %s", err) return 0, nil, fmt.Errorf("failed parsing service nodes: %s", err)
} }
@ -1096,10 +1163,10 @@ func (s *Store) ServiceAddressNodes(ws memdb.WatchSet, address string, entMeta *
// parseServiceNodes iterates over a services query and fills in the node details, // parseServiceNodes iterates over a services query and fills in the node details,
// returning a ServiceNodes slice. // returning a ServiceNodes slice.
func parseServiceNodes(tx ReadTxn, ws memdb.WatchSet, services structs.ServiceNodes) (structs.ServiceNodes, error) { func parseServiceNodes(tx ReadTxn, ws memdb.WatchSet, services structs.ServiceNodes, entMeta *structs.EnterpriseMeta) (structs.ServiceNodes, error) {
// We don't want to track an unlimited number of nodes, so we pull a // We don't want to track an unlimited number of nodes, so we pull a
// top-level watch to use as a fallback. // top-level watch to use as a fallback.
allNodes, err := tx.Get(tableNodes, indexID) allNodes, err := tx.Get(tableNodes, indexID+"_prefix", entMeta)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed nodes lookup: %s", err) return nil, fmt.Errorf("failed nodes lookup: %s", err)
} }
@ -1114,7 +1181,10 @@ func parseServiceNodes(tx ReadTxn, ws memdb.WatchSet, services structs.ServiceNo
s := sn.PartialClone() s := sn.PartialClone()
// Grab the corresponding node record. // Grab the corresponding node record.
watchCh, n, err := tx.FirstWatch(tableNodes, indexID, Query{Value: sn.Node}) watchCh, n, err := tx.FirstWatch(tableNodes, indexID, Query{
Value: sn.Node,
EnterpriseMeta: sn.EnterpriseMeta,
})
if err != nil { if err != nil {
return nil, fmt.Errorf("failed node lookup: %s", err) return nil, fmt.Errorf("failed node lookup: %s", err)
} }
@ -1128,6 +1198,7 @@ func parseServiceNodes(tx ReadTxn, ws memdb.WatchSet, services structs.ServiceNo
s.Address = node.Address s.Address = node.Address
s.Datacenter = node.Datacenter s.Datacenter = node.Datacenter
s.TaggedAddresses = node.TaggedAddresses s.TaggedAddresses = node.TaggedAddresses
s.EnterpriseMeta.Merge(node.GetEnterpriseMeta())
s.NodeMeta = node.Meta s.NodeMeta = node.Meta
results = append(results, s) results = append(results, s)
@ -1160,7 +1231,11 @@ func getNodeServiceTxn(tx ReadTxn, nodeName, serviceID string, entMeta *structs.
} }
// Query the service // Query the service
service, err := tx.First(tableServices, indexID, NodeServiceQuery{EnterpriseMeta: *entMeta, Node: nodeName, Service: serviceID}) service, err := tx.First(tableServices, indexID, NodeServiceQuery{
EnterpriseMeta: *entMeta,
Node: nodeName,
Service: serviceID,
})
if err != nil { if err != nil {
return nil, fmt.Errorf("failed querying service for node %q: %s", nodeName, err) return nil, fmt.Errorf("failed querying service for node %q: %s", nodeName, err)
} }
@ -1176,11 +1251,16 @@ func (s *Store) nodeServices(ws memdb.WatchSet, nodeNameOrID string, entMeta *st
tx := s.db.Txn(false) tx := s.db.Txn(false)
defer tx.Abort() defer tx.Abort()
// TODO: accept non-pointer value for entMeta
if entMeta == nil {
entMeta = structs.DefaultEnterpriseMetaInDefaultPartition()
}
// Get the table index. // Get the table index.
idx := catalogMaxIndex(tx, entMeta, false) idx := catalogMaxIndex(tx, entMeta, false)
// Query the node by node name // Query the node by node name
watchCh, n, err := tx.FirstWatch(tableNodes, indexID, Query{Value: nodeNameOrID}) watchCh, n, err := tx.FirstWatch(tableNodes, indexID, Query{Value: nodeNameOrID, EnterpriseMeta: *entMeta})
if err != nil { if err != nil {
return true, 0, nil, nil, fmt.Errorf("node lookup failed: %s", err) return true, 0, nil, nil, fmt.Errorf("node lookup failed: %s", err)
} }
@ -1194,7 +1274,7 @@ func (s *Store) nodeServices(ws memdb.WatchSet, nodeNameOrID string, entMeta *st
} }
// Attempt to lookup the node by its node ID // Attempt to lookup the node by its node ID
iter, err := tx.Get("nodes", "uuid_prefix", resizeNodeLookupKey(nodeNameOrID)) iter, err := tx.Get(tableNodes, "uuid_prefix", resizeNodeLookupKey(nodeNameOrID))
if err != nil { if err != nil {
ws.Add(watchCh) ws.Add(watchCh)
// TODO(sean@): We could/should log an error re: the uuid_prefix lookup // TODO(sean@): We could/should log an error re: the uuid_prefix lookup
@ -1347,7 +1427,11 @@ func (s *Store) deleteServiceTxn(tx WriteTxn, idx uint64, nodeName, serviceID st
} }
// Delete any checks associated with the service. This will invalidate // Delete any checks associated with the service. This will invalidate
// sessions as necessary. // sessions as necessary.
nsq := NodeServiceQuery{Node: nodeName, Service: serviceID, EnterpriseMeta: *entMeta} nsq := NodeServiceQuery{
Node: nodeName,
Service: serviceID,
EnterpriseMeta: *entMeta,
}
checks, err := tx.Get(tableChecks, indexNodeService, nsq) checks, err := tx.Get(tableChecks, indexNodeService, nsq)
if err != nil { if err != nil {
return fmt.Errorf("failed service check lookup: %s", err) return fmt.Errorf("failed service check lookup: %s", err)
@ -1432,8 +1516,11 @@ func (s *Store) EnsureCheck(idx uint64, hc *structs.HealthCheck) error {
} }
// updateAllServiceIndexesOfNode updates the Raft index of all the services associated with this node // updateAllServiceIndexesOfNode updates the Raft index of all the services associated with this node
func updateAllServiceIndexesOfNode(tx WriteTxn, idx uint64, nodeID string) error { func updateAllServiceIndexesOfNode(tx WriteTxn, idx uint64, nodeID string, entMeta *structs.EnterpriseMeta) error {
services, err := tx.Get(tableServices, indexNode, Query{Value: nodeID}) services, err := tx.Get(tableServices, indexNode, Query{
Value: nodeID,
EnterpriseMeta: *entMeta.WildcardEnterpriseMetaForPartition(),
})
if err != nil { if err != nil {
return fmt.Errorf("failed updating services for node %s: %s", nodeID, err) return fmt.Errorf("failed updating services for node %s: %s", nodeID, err)
} }
@ -1483,7 +1570,11 @@ func (s *Store) ensureCheckCASTxn(tx WriteTxn, idx uint64, hc *structs.HealthChe
// checks with no matching node or service. // checks with no matching node or service.
func (s *Store) ensureCheckTxn(tx WriteTxn, idx uint64, preserveIndexes bool, hc *structs.HealthCheck) error { func (s *Store) ensureCheckTxn(tx WriteTxn, idx uint64, preserveIndexes bool, hc *structs.HealthCheck) error {
// Check if we have an existing health check // Check if we have an existing health check
existing, err := tx.First(tableChecks, indexID, NodeCheckQuery{EnterpriseMeta: hc.EnterpriseMeta, Node: hc.Node, CheckID: string(hc.CheckID)}) existing, err := tx.First(tableChecks, indexID, NodeCheckQuery{
EnterpriseMeta: hc.EnterpriseMeta,
Node: hc.Node,
CheckID: string(hc.CheckID),
})
if err != nil { if err != nil {
return fmt.Errorf("failed health check lookup: %s", err) return fmt.Errorf("failed health check lookup: %s", err)
} }
@ -1503,7 +1594,10 @@ func (s *Store) ensureCheckTxn(tx WriteTxn, idx uint64, preserveIndexes bool, hc
} }
// Get the node // Get the node
node, err := tx.First(tableNodes, indexID, Query{Value: hc.Node}) node, err := tx.First(tableNodes, indexID, Query{
Value: hc.Node,
EnterpriseMeta: hc.EnterpriseMeta,
})
if err != nil { if err != nil {
return fmt.Errorf("failed node lookup: %s", err) return fmt.Errorf("failed node lookup: %s", err)
} }
@ -1515,7 +1609,11 @@ func (s *Store) ensureCheckTxn(tx WriteTxn, idx uint64, preserveIndexes bool, hc
// If the check is associated with a service, check that we have // If the check is associated with a service, check that we have
// a registration for the service. // a registration for the service.
if hc.ServiceID != "" { if hc.ServiceID != "" {
service, err := tx.First(tableServices, indexID, NodeServiceQuery{EnterpriseMeta: hc.EnterpriseMeta, Node: hc.Node, Service: hc.ServiceID}) service, err := tx.First(tableServices, indexID, NodeServiceQuery{
EnterpriseMeta: hc.EnterpriseMeta,
Node: hc.Node,
Service: hc.ServiceID,
})
if err != nil { if err != nil {
return fmt.Errorf("failed service lookup: %s", err) return fmt.Errorf("failed service lookup: %s", err)
} }
@ -1543,7 +1641,7 @@ func (s *Store) ensureCheckTxn(tx WriteTxn, idx uint64, preserveIndexes bool, hc
} else { } else {
// Since the check has been modified, it impacts all services of node // Since the check has been modified, it impacts all services of node
// Update the status for all the services associated with this node // Update the status for all the services associated with this node
err = updateAllServiceIndexesOfNode(tx, idx, hc.Node) err = updateAllServiceIndexesOfNode(tx, idx, hc.Node, &hc.EnterpriseMeta)
if err != nil { if err != nil {
return err return err
} }
@ -1683,7 +1781,7 @@ func (s *Store) ServiceChecksByNodeMeta(ws memdb.WatchSet, serviceName string,
} }
ws.Add(iter.WatchCh()) ws.Add(iter.WatchCh())
return parseChecksByNodeMeta(tx, ws, idx, iter, filters) return parseChecksByNodeMeta(tx, ws, idx, iter, filters, entMeta)
} }
// ChecksInState is used to query the state store for all checks // ChecksInState is used to query the state store for all checks
@ -1715,7 +1813,7 @@ func (s *Store) ChecksInStateByNodeMeta(ws memdb.WatchSet, state string, filters
return 0, nil, err return 0, nil, err
} }
return parseChecksByNodeMeta(tx, ws, idx, iter, filters) return parseChecksByNodeMeta(tx, ws, idx, iter, filters, entMeta)
} }
func checksInStateTxn(tx ReadTxn, ws memdb.WatchSet, state string, entMeta *structs.EnterpriseMeta) (uint64, memdb.ResultIterator, error) { func checksInStateTxn(tx ReadTxn, ws memdb.WatchSet, state string, entMeta *structs.EnterpriseMeta) (uint64, memdb.ResultIterator, error) {
@ -1746,11 +1844,12 @@ func checksInStateTxn(tx ReadTxn, ws memdb.WatchSet, state string, entMeta *stru
// parseChecksByNodeMeta is a helper function used to deduplicate some // parseChecksByNodeMeta is a helper function used to deduplicate some
// repetitive code for returning health checks filtered by node metadata fields. // repetitive code for returning health checks filtered by node metadata fields.
func parseChecksByNodeMeta(tx ReadTxn, ws memdb.WatchSet, func parseChecksByNodeMeta(tx ReadTxn, ws memdb.WatchSet,
idx uint64, iter memdb.ResultIterator, filters map[string]string) (uint64, structs.HealthChecks, error) { idx uint64, iter memdb.ResultIterator, filters map[string]string,
entMeta *structs.EnterpriseMeta) (uint64, structs.HealthChecks, error) {
// We don't want to track an unlimited number of nodes, so we pull a // We don't want to track an unlimited number of nodes, so we pull a
// top-level watch to use as a fallback. // top-level watch to use as a fallback.
allNodes, err := tx.Get(tableNodes, indexID) allNodes, err := tx.Get(tableNodes, indexID+"_prefix", entMeta)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed nodes lookup: %s", err) return 0, nil, fmt.Errorf("failed nodes lookup: %s", err)
} }
@ -1760,7 +1859,10 @@ func parseChecksByNodeMeta(tx ReadTxn, ws memdb.WatchSet,
var results structs.HealthChecks var results structs.HealthChecks
for check := iter.Next(); check != nil; check = iter.Next() { for check := iter.Next(); check != nil; check = iter.Next() {
healthCheck := check.(*structs.HealthCheck) healthCheck := check.(*structs.HealthCheck)
watchCh, node, err := tx.FirstWatch(tableNodes, indexID, Query{Value: healthCheck.Node}) watchCh, node, err := tx.FirstWatch(tableNodes, indexID, Query{
Value: healthCheck.Node,
EnterpriseMeta: healthCheck.EnterpriseMeta,
})
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed node lookup: %s", err) return 0, nil, fmt.Errorf("failed node lookup: %s", err)
} }
@ -1871,7 +1973,7 @@ func (s *Store) deleteCheckTxn(tx WriteTxn, idx uint64, node string, checkID typ
return err return err
} }
} else { } else {
if err := updateAllServiceIndexesOfNode(tx, idx, existing.Node); err != nil { if err := updateAllServiceIndexesOfNode(tx, idx, existing.Node, &existing.EnterpriseMeta); err != nil {
return fmt.Errorf("Failed to update services linked to deleted healthcheck: %s", err) return fmt.Errorf("Failed to update services linked to deleted healthcheck: %s", err)
} }
if err := catalogUpdateServicesIndexes(tx, idx, entMeta); err != nil { if err := catalogUpdateServicesIndexes(tx, idx, entMeta); err != nil {
@ -1999,7 +2101,10 @@ func checkServiceNodesTxn(tx ReadTxn, ws memdb.WatchSet, serviceName string, con
entMeta = structs.DefaultEnterpriseMetaInDefaultPartition() entMeta = structs.DefaultEnterpriseMetaInDefaultPartition()
} }
q := Query{Value: serviceName, EnterpriseMeta: *entMeta} q := Query{
Value: serviceName,
EnterpriseMeta: *entMeta,
}
iter, err := tx.Get(tableServices, index, q) iter, err := tx.Get(tableServices, index, q)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed service lookup: %s", err) return 0, nil, fmt.Errorf("failed service lookup: %s", err)
@ -2114,7 +2219,7 @@ func checkServiceNodesTxn(tx ReadTxn, ws memdb.WatchSet, serviceName string, con
ws.Add(iter.WatchCh()) ws.Add(iter.WatchCh())
} }
return parseCheckServiceNodes(tx, fallbackWS, idx, results, err) return parseCheckServiceNodes(tx, fallbackWS, idx, results, entMeta, err)
} }
// CheckServiceTagNodes is used to query all nodes and checks for a given // CheckServiceTagNodes is used to query all nodes and checks for a given
@ -2148,7 +2253,7 @@ func (s *Store) CheckServiceTagNodes(ws memdb.WatchSet, serviceName string, tags
// Get the table index. // Get the table index.
idx := maxIndexForService(tx, serviceName, serviceExists, true, entMeta) idx := maxIndexForService(tx, serviceName, serviceExists, true, entMeta)
return parseCheckServiceNodes(tx, ws, idx, results, err) return parseCheckServiceNodes(tx, ws, idx, results, entMeta, err)
} }
// GatewayServices is used to query all services associated with a gateway // GatewayServices is used to query all services associated with a gateway
@ -2181,6 +2286,7 @@ func (s *Store) GatewayServices(ws memdb.WatchSet, gateway string, entMeta *stru
func parseCheckServiceNodes( func parseCheckServiceNodes(
tx ReadTxn, ws memdb.WatchSet, idx uint64, tx ReadTxn, ws memdb.WatchSet, idx uint64,
services structs.ServiceNodes, services structs.ServiceNodes,
entMeta *structs.EnterpriseMeta,
err error) (uint64, structs.CheckServiceNodes, error) { err error) (uint64, structs.CheckServiceNodes, error) {
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
@ -2194,7 +2300,7 @@ func parseCheckServiceNodes(
// We don't want to track an unlimited number of nodes, so we pull a // We don't want to track an unlimited number of nodes, so we pull a
// top-level watch to use as a fallback. // top-level watch to use as a fallback.
allNodes, err := tx.Get(tableNodes, indexID) allNodes, err := tx.Get(tableNodes, indexID+"_prefix", entMeta)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed nodes lookup: %s", err) return 0, nil, fmt.Errorf("failed nodes lookup: %s", err)
} }
@ -2203,7 +2309,7 @@ func parseCheckServiceNodes(
// We need a similar fallback for checks. Since services need the // We need a similar fallback for checks. Since services need the
// status of node + service-specific checks, we pull in a top-level // status of node + service-specific checks, we pull in a top-level
// watch over all checks. // watch over all checks.
allChecks, err := tx.Get(tableChecks, indexID) allChecks, err := tx.Get(tableChecks, indexID+"_prefix", entMeta)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed checks lookup: %s", err) return 0, nil, fmt.Errorf("failed checks lookup: %s", err)
} }
@ -2212,7 +2318,10 @@ func parseCheckServiceNodes(
results := make(structs.CheckServiceNodes, 0, len(services)) results := make(structs.CheckServiceNodes, 0, len(services))
for _, sn := range services { for _, sn := range services {
// Retrieve the node. // Retrieve the node.
watchCh, n, err := tx.FirstWatch(tableNodes, indexID, Query{Value: sn.Node}) watchCh, n, err := tx.FirstWatch(tableNodes, indexID, Query{
Value: sn.Node,
EnterpriseMeta: sn.EnterpriseMeta,
})
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed node lookup: %s", err) return 0, nil, fmt.Errorf("failed node lookup: %s", err)
} }
@ -2226,7 +2335,11 @@ func parseCheckServiceNodes(
// First add the node-level checks. These always apply to any // First add the node-level checks. These always apply to any
// service on the node. // service on the node.
var checks structs.HealthChecks var checks structs.HealthChecks
q := NodeServiceQuery{Node: sn.Node, EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition()} q := NodeServiceQuery{
Node: sn.Node,
Service: "", // node checks have no service
EnterpriseMeta: *sn.EnterpriseMeta.WildcardEnterpriseMetaForPartition(),
}
iter, err := tx.Get(tableChecks, indexNodeService, q) iter, err := tx.Get(tableChecks, indexNodeService, q)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
@ -2237,7 +2350,11 @@ func parseCheckServiceNodes(
} }
// Now add the service-specific checks. // Now add the service-specific checks.
q = NodeServiceQuery{Node: sn.Node, Service: sn.ServiceID, EnterpriseMeta: sn.EnterpriseMeta} q = NodeServiceQuery{
Node: sn.Node,
Service: sn.ServiceID,
EnterpriseMeta: sn.EnterpriseMeta,
}
iter, err = tx.Get(tableChecks, indexNodeService, q) iter, err = tx.Get(tableChecks, indexNodeService, q)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
@ -2264,11 +2381,18 @@ func (s *Store) NodeInfo(ws memdb.WatchSet, node string, entMeta *structs.Enterp
tx := s.db.Txn(false) tx := s.db.Txn(false)
defer tx.Abort() defer tx.Abort()
if entMeta == nil {
entMeta = structs.NodeEnterpriseMetaInDefaultPartition()
}
// Get the table index. // Get the table index.
idx := catalogMaxIndex(tx, entMeta, true) idx := catalogMaxIndex(tx, entMeta, true)
// Query the node by the passed node // Query the node by the passed node
nodes, err := tx.Get(tableNodes, indexID, Query{Value: node}) nodes, err := tx.Get(tableNodes, indexID, Query{
Value: node,
EnterpriseMeta: *entMeta,
})
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed node lookup: %s", err) return 0, nil, fmt.Errorf("failed node lookup: %s", err)
} }
@ -2287,7 +2411,7 @@ func (s *Store) NodeDump(ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (ui
idx := catalogMaxIndex(tx, entMeta, true) idx := catalogMaxIndex(tx, entMeta, true)
// Fetch all of the registered nodes // Fetch all of the registered nodes
nodes, err := tx.Get(tableNodes, indexID) nodes, err := tx.Get(tableNodes, indexID+"_prefix", entMeta)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed node lookup: %s", err) return 0, nil, fmt.Errorf("failed node lookup: %s", err)
} }
@ -2321,7 +2445,7 @@ func serviceDumpAllTxn(tx ReadTxn, ws memdb.WatchSet, entMeta *structs.Enterpris
results = append(results, sn) results = append(results, sn)
} }
return parseCheckServiceNodes(tx, nil, idx, results, err) return parseCheckServiceNodes(tx, nil, idx, results, entMeta, err)
} }
func serviceDumpKindTxn(tx ReadTxn, ws memdb.WatchSet, kind structs.ServiceKind, entMeta *structs.EnterpriseMeta) (uint64, structs.CheckServiceNodes, error) { func serviceDumpKindTxn(tx ReadTxn, ws memdb.WatchSet, kind structs.ServiceKind, entMeta *structs.EnterpriseMeta) (uint64, structs.CheckServiceNodes, error) {
@ -2345,7 +2469,7 @@ func serviceDumpKindTxn(tx ReadTxn, ws memdb.WatchSet, kind structs.ServiceKind,
results = append(results, sn) results = append(results, sn)
} }
return parseCheckServiceNodes(tx, nil, idx, results, err) return parseCheckServiceNodes(tx, nil, idx, results, entMeta, err)
} }
// parseNodes takes an iterator over a set of nodes and returns a struct // parseNodes takes an iterator over a set of nodes and returns a struct
@ -2360,14 +2484,14 @@ func parseNodes(tx ReadTxn, ws memdb.WatchSet, idx uint64,
// We don't want to track an unlimited number of services, so we pull a // We don't want to track an unlimited number of services, so we pull a
// top-level watch to use as a fallback. // top-level watch to use as a fallback.
allServices, err := tx.Get(tableServices, indexID) allServices, err := tx.Get(tableServices, indexID+"_prefix", entMeta)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed services lookup: %s", err) return 0, nil, fmt.Errorf("failed services lookup: %s", err)
} }
allServicesCh := allServices.WatchCh() allServicesCh := allServices.WatchCh()
// We need a similar fallback for checks. // We need a similar fallback for checks.
allChecks, err := tx.Get(tableChecks, indexID) allChecks, err := tx.Get(tableChecks, indexID+"_prefix", entMeta)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed checks lookup: %s", err) return 0, nil, fmt.Errorf("failed checks lookup: %s", err)
} }
@ -2381,6 +2505,7 @@ func parseNodes(tx ReadTxn, ws memdb.WatchSet, idx uint64,
dump := &structs.NodeInfo{ dump := &structs.NodeInfo{
ID: node.ID, ID: node.ID,
Node: node.Node, Node: node.Node,
Partition: node.Partition,
Address: node.Address, Address: node.Address,
TaggedAddresses: node.TaggedAddresses, TaggedAddresses: node.TaggedAddresses,
Meta: node.Meta, Meta: node.Meta,
@ -2793,7 +2918,10 @@ func serviceGatewayNodes(tx ReadTxn, ws memdb.WatchSet, service string, kind str
maxIdx = lib.MaxUint64(maxIdx, mapping.ModifyIndex) maxIdx = lib.MaxUint64(maxIdx, mapping.ModifyIndex)
// Look up nodes for gateway // Look up nodes for gateway
q := Query{Value: mapping.Gateway.Name, EnterpriseMeta: mapping.Gateway.EnterpriseMeta} q := Query{
Value: mapping.Gateway.Name,
EnterpriseMeta: mapping.Gateway.EnterpriseMeta,
}
gwServices, err := tx.Get(tableServices, indexService, q) gwServices, err := tx.Get(tableServices, indexService, q)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed service lookup: %s", err) return 0, nil, fmt.Errorf("failed service lookup: %s", err)
@ -3262,8 +3390,8 @@ func updateMeshTopology(tx WriteTxn, idx uint64, node string, svc *structs.NodeS
oldUpstreams := make(map[structs.ServiceName]bool) oldUpstreams := make(map[structs.ServiceName]bool)
if e, ok := existing.(*structs.ServiceNode); ok { if e, ok := existing.(*structs.ServiceNode); ok {
for _, u := range e.ServiceProxy.Upstreams { for _, u := range e.ServiceProxy.Upstreams {
upstreamMeta := structs.NewEnterpriseMetaInDefaultPartition(u.DestinationNamespace) upstreamMeta := e.NewEnterpriseMetaInPartition(u.DestinationNamespace)
sn := structs.NewServiceName(u.DestinationName, &upstreamMeta) sn := structs.NewServiceName(u.DestinationName, upstreamMeta)
oldUpstreams[sn] = true oldUpstreams[sn] = true
} }
@ -3278,8 +3406,8 @@ func updateMeshTopology(tx WriteTxn, idx uint64, node string, svc *structs.NodeS
} }
// TODO (freddy): Account for upstream datacenter // TODO (freddy): Account for upstream datacenter
upstreamMeta := structs.NewEnterpriseMetaInDefaultPartition(u.DestinationNamespace) upstreamMeta := svc.NewEnterpriseMetaInPartition(u.DestinationNamespace)
upstream := structs.NewServiceName(u.DestinationName, &upstreamMeta) upstream := structs.NewServiceName(u.DestinationName, upstreamMeta)
obj, err := tx.First(tableMeshTopology, indexID, upstream, downstream) obj, err := tx.First(tableMeshTopology, indexID, upstream, downstream)
if err != nil { if err != nil {

View File

@ -284,7 +284,7 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
return nil return nil
}, },
Mutate: func(s *Store, tx *txn) error { Mutate: func(s *Store, tx *txn) error {
return s.deleteNodeTxn(tx, tx.Index, "node1") return s.deleteNodeTxn(tx, tx.Index, "node1", nil)
}, },
WantEvents: []stream.Event{ WantEvents: []stream.Event{
// Should publish deregistration events for all services // Should publish deregistration events for all services
@ -1695,10 +1695,11 @@ type regOption func(req *structs.RegisterRequest) error
func testNodeRegistration(t *testing.T, opts ...regOption) *structs.RegisterRequest { func testNodeRegistration(t *testing.T, opts ...regOption) *structs.RegisterRequest {
r := &structs.RegisterRequest{ r := &structs.RegisterRequest{
Datacenter: "dc1", Datacenter: "dc1",
ID: "11111111-2222-3333-4444-555555555555", ID: "11111111-2222-3333-4444-555555555555",
Node: "node1", Node: "node1",
Address: "10.10.10.10", Address: "10.10.10.10",
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
Checks: structs.HealthChecks{ Checks: structs.HealthChecks{
&structs.HealthCheck{ &structs.HealthCheck{
CheckID: "serf-health", CheckID: "serf-health",
@ -1719,19 +1720,21 @@ func testServiceRegistration(t *testing.T, svc string, opts ...regOption) *struc
// note: don't pass opts or they might get applied twice! // note: don't pass opts or they might get applied twice!
r := testNodeRegistration(t) r := testNodeRegistration(t)
r.Service = &structs.NodeService{ r.Service = &structs.NodeService{
ID: svc, ID: svc,
Service: svc, Service: svc,
Port: 8080, Port: 8080,
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
} }
r.Checks = append(r.Checks, r.Checks = append(r.Checks,
&structs.HealthCheck{ &structs.HealthCheck{
CheckID: types.CheckID("service:" + svc), CheckID: types.CheckID("service:" + svc),
Name: "service:" + svc, Name: "service:" + svc,
Node: "node1", Node: "node1",
ServiceID: svc, ServiceID: svc,
ServiceName: svc, ServiceName: svc,
Type: "ttl", Type: "ttl",
Status: api.HealthPassing, Status: api.HealthPassing,
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
}) })
for _, opt := range opts { for _, opt := range opts {
err := opt(r) err := opt(r)
@ -1753,6 +1756,7 @@ func testServiceHealthEvent(t *testing.T, svc string, opts ...eventOption) strea
csn := getPayloadCheckServiceNode(e.Payload) csn := getPayloadCheckServiceNode(e.Payload)
csn.Node.ID = "11111111-2222-3333-4444-555555555555" csn.Node.ID = "11111111-2222-3333-4444-555555555555"
csn.Node.Address = "10.10.10.10" csn.Node.Address = "10.10.10.10"
csn.Node.Partition = structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty()
for _, opt := range opts { for _, opt := range opts {
if err := opt(&e); err != nil { if err := opt(&e); err != nil {

View File

@ -26,6 +26,15 @@ func serviceKindIndexName(kind structs.ServiceKind, _ *structs.EnterpriseMeta) s
} }
} }
func catalogUpdateNodesIndexes(tx WriteTxn, idx uint64, entMeta *structs.EnterpriseMeta) error {
// overall nodes index
if err := indexUpdateMaxTxn(tx, idx, tableNodes); err != nil {
return fmt.Errorf("failed updating index: %s", err)
}
return nil
}
func catalogUpdateServicesIndexes(tx WriteTxn, idx uint64, _ *structs.EnterpriseMeta) error { func catalogUpdateServicesIndexes(tx WriteTxn, idx uint64, _ *structs.EnterpriseMeta) error {
// overall services index // overall services index
if err := indexUpdateMaxTxn(tx, idx, tableServices); err != nil { if err := indexUpdateMaxTxn(tx, idx, tableServices); err != nil {
@ -60,6 +69,29 @@ func catalogUpdateServiceExtinctionIndex(tx WriteTxn, idx uint64, _ *structs.Ent
return nil return nil
} }
func catalogInsertNode(tx WriteTxn, node *structs.Node) error {
// ensure that the Partition is always clear within the state store in OSS
node.Partition = ""
// Insert the node and update the index.
if err := tx.Insert(tableNodes, node); err != nil {
return fmt.Errorf("failed inserting node: %s", err)
}
if err := catalogUpdateNodesIndexes(tx, node.ModifyIndex, node.GetEnterpriseMeta()); err != nil {
return err
}
// Update the node's service indexes as the node information is included
// in health queries and we would otherwise miss node updates in some cases
// for those queries.
if err := updateAllServiceIndexesOfNode(tx, node.ModifyIndex, node.Node, node.GetEnterpriseMeta()); err != nil {
return fmt.Errorf("failed updating index: %s", err)
}
return nil
}
func catalogInsertService(tx WriteTxn, svc *structs.ServiceNode) error { func catalogInsertService(tx WriteTxn, svc *structs.ServiceNode) error {
// Insert the service and update the index // Insert the service and update the index
if err := tx.Insert(tableServices, svc); err != nil { if err := tx.Insert(tableServices, svc); err != nil {
@ -81,6 +113,10 @@ func catalogInsertService(tx WriteTxn, svc *structs.ServiceNode) error {
return nil return nil
} }
func catalogNodesMaxIndex(tx ReadTxn, entMeta *structs.EnterpriseMeta) uint64 {
return maxIndexTxn(tx, tableNodes)
}
func catalogServicesMaxIndex(tx ReadTxn, _ *structs.EnterpriseMeta) uint64 { func catalogServicesMaxIndex(tx ReadTxn, _ *structs.EnterpriseMeta) uint64 {
return maxIndexTxn(tx, tableServices) return maxIndexTxn(tx, tableServices)
} }
@ -107,16 +143,16 @@ func catalogServiceLastExtinctionIndex(tx ReadTxn, _ *structs.EnterpriseMeta) (i
func catalogMaxIndex(tx ReadTxn, _ *structs.EnterpriseMeta, checks bool) uint64 { func catalogMaxIndex(tx ReadTxn, _ *structs.EnterpriseMeta, checks bool) uint64 {
if checks { if checks {
return maxIndexTxn(tx, "nodes", tableServices, tableChecks) return maxIndexTxn(tx, tableNodes, tableServices, tableChecks)
} }
return maxIndexTxn(tx, "nodes", tableServices) return maxIndexTxn(tx, tableNodes, tableServices)
} }
func catalogMaxIndexWatch(tx ReadTxn, ws memdb.WatchSet, _ *structs.EnterpriseMeta, checks bool) uint64 { func catalogMaxIndexWatch(tx ReadTxn, ws memdb.WatchSet, _ *structs.EnterpriseMeta, checks bool) uint64 {
if checks { if checks {
return maxIndexWatchTxn(tx, ws, "nodes", tableServices, tableChecks) return maxIndexWatchTxn(tx, ws, tableNodes, tableServices, tableChecks)
} }
return maxIndexWatchTxn(tx, ws, "nodes", tableServices) return maxIndexWatchTxn(tx, ws, tableNodes, tableServices)
} }
func catalogUpdateCheckIndexes(tx WriteTxn, idx uint64, _ *structs.EnterpriseMeta) error { func catalogUpdateCheckIndexes(tx WriteTxn, idx uint64, _ *structs.EnterpriseMeta) error {

View File

@ -185,7 +185,25 @@ func testIndexerTableNodes() map[string]indexerTestCase {
source: &structs.Node{Node: "NoDeId"}, source: &structs.Node{Node: "NoDeId"},
expected: []byte("nodeid\x00"), expected: []byte("nodeid\x00"),
}, },
prefix: []indexValue{
{
source: (*structs.EnterpriseMeta)(nil),
expected: nil,
},
{
source: structs.EnterpriseMeta{},
expected: nil,
},
{
source: Query{Value: "NoDeId"},
expected: []byte("nodeid\x00"),
},
},
}, },
// TODO: uuid
// TODO: meta
// TODO(partitions): fix schema tests for tables that reference nodes too
} }
} }

View File

@ -38,9 +38,10 @@ func nodesTableSchema() *memdb.TableSchema {
Name: indexID, Name: indexID,
AllowMissing: false, AllowMissing: false,
Unique: true, Unique: true,
Indexer: indexerSingle{ Indexer: indexerSingleWithPrefix{
readIndex: indexFromQuery, readIndex: indexFromQuery,
writeIndex: indexFromNode, writeIndex: indexFromNode,
prefixIndex: prefixIndexFromQueryNoNamespace,
}, },
}, },
"uuid": { "uuid": {

View File

@ -171,6 +171,7 @@ func TestStateStore_EnsureRegistration(t *testing.T) {
ID: nodeID, ID: nodeID,
Node: "node1", Node: "node1",
Address: "1.2.3.4", Address: "1.2.3.4",
Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
TaggedAddresses: map[string]string{"hello": "world"}, TaggedAddresses: map[string]string{"hello": "world"},
Meta: map[string]string{"somekey": "somevalue"}, Meta: map[string]string{"somekey": "somevalue"},
RaftIndex: structs.RaftIndex{CreateIndex: 1, ModifyIndex: 1}, RaftIndex: structs.RaftIndex{CreateIndex: 1, ModifyIndex: 1},
@ -1316,7 +1317,7 @@ func TestStateStore_DeleteNode(t *testing.T) {
} }
// Indexes were updated. // Indexes were updated.
for _, tbl := range []string{"nodes", tableServices, tableChecks} { for _, tbl := range []string{tableNodes, tableServices, tableChecks} {
if idx := s.maxIndex(tbl); idx != 3 { if idx := s.maxIndex(tbl); idx != 3 {
t.Fatalf("bad index: %d (%s)", idx, tbl) t.Fatalf("bad index: %d (%s)", idx, tbl)
} }
@ -1327,7 +1328,7 @@ func TestStateStore_DeleteNode(t *testing.T) {
if err := s.DeleteNode(4, "node1", nil); err != nil { if err := s.DeleteNode(4, "node1", nil); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if idx := s.maxIndex("nodes"); idx != 3 { if idx := s.maxIndex(tableNodes); idx != 3 {
t.Fatalf("bad index: %d", idx) t.Fatalf("bad index: %d", idx)
} }
} }
@ -4236,7 +4237,8 @@ func TestStateStore_NodeInfo_NodeDump(t *testing.T) {
// Check that our result matches what we expect. // Check that our result matches what we expect.
expect := structs.NodeDump{ expect := structs.NodeDump{
&structs.NodeInfo{ &structs.NodeInfo{
Node: "node1", Node: "node1",
Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
Checks: structs.HealthChecks{ Checks: structs.HealthChecks{
&structs.HealthCheck{ &structs.HealthCheck{
Node: "node1", Node: "node1",
@ -4293,7 +4295,8 @@ func TestStateStore_NodeInfo_NodeDump(t *testing.T) {
}, },
}, },
&structs.NodeInfo{ &structs.NodeInfo{
Node: "node2", Node: "node2",
Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
Checks: structs.HealthChecks{ Checks: structs.HealthChecks{
&structs.HealthCheck{ &structs.HealthCheck{
Node: "node2", Node: "node2",
@ -7518,3 +7521,46 @@ func TestProtocolForIngressGateway(t *testing.T) {
}) })
} }
} }
func runStep(t *testing.T, name string, fn func(t *testing.T)) {
t.Helper()
if !t.Run(name, fn) {
t.FailNow()
}
}
func assertMaxIndexes(t *testing.T, tx ReadTxn, expect map[string]uint64, skip ...string) {
t.Helper()
all := dumpMaxIndexes(t, tx)
for _, index := range skip {
if _, ok := all[index]; ok {
delete(all, index)
} else {
t.Logf("index %q isn't even set; probably test assertion isn't relevant anymore", index)
}
}
require.Equal(t, expect, all)
// TODO
// for _, index := range indexes {
// require.Equal(t, expectIndex, maxIndexTxn(tx, index),
// "index %s has the wrong value", index)
// }
}
func dumpMaxIndexes(t *testing.T, tx ReadTxn) map[string]uint64 {
out := make(map[string]uint64)
iter, err := tx.Get(tableIndex, "id")
require.NoError(t, err)
for entry := iter.Next(); entry != nil; entry = iter.Next() {
if idx, ok := entry.(*IndexEntry); ok {
out[idx.Key] = idx.Value
}
}
return out
}

View File

@ -2,6 +2,7 @@ package state
import ( import (
"fmt" "fmt"
"strings"
"github.com/hashicorp/go-memdb" "github.com/hashicorp/go-memdb"
@ -9,39 +10,88 @@ import (
"github.com/hashicorp/consul/lib" "github.com/hashicorp/consul/lib"
) )
const tableCoordinates = "coordinates"
func indexFromCoordinate(raw interface{}) ([]byte, error) {
c, ok := raw.(*structs.Coordinate)
if !ok {
return nil, fmt.Errorf("unexpected type %T for structs.Coordinate index", raw)
}
if c.Node == "" {
return nil, errMissingValueForIndex
}
var b indexBuilder
b.String(strings.ToLower(c.Node))
b.String(strings.ToLower(c.Segment))
return b.Bytes(), nil
}
func indexNodeFromCoordinate(raw interface{}) ([]byte, error) {
c, ok := raw.(*structs.Coordinate)
if !ok {
return nil, fmt.Errorf("unexpected type %T for structs.Coordinate index", raw)
}
if c.Node == "" {
return nil, errMissingValueForIndex
}
var b indexBuilder
b.String(strings.ToLower(c.Node))
return b.Bytes(), nil
}
func indexFromCoordinateQuery(raw interface{}) ([]byte, error) {
q, ok := raw.(CoordinateQuery)
if !ok {
return nil, fmt.Errorf("unexpected type %T for CoordinateQuery index", raw)
}
if q.Node == "" {
return nil, errMissingValueForIndex
}
var b indexBuilder
b.String(strings.ToLower(q.Node))
b.String(strings.ToLower(q.Segment))
return b.Bytes(), nil
}
type CoordinateQuery struct {
Node string
Segment string
Partition string
}
func (c CoordinateQuery) PartitionOrDefault() string {
return structs.PartitionOrDefault(c.Partition)
}
// coordinatesTableSchema returns a new table schema used for storing // coordinatesTableSchema returns a new table schema used for storing
// network coordinates. // network coordinates.
func coordinatesTableSchema() *memdb.TableSchema { func coordinatesTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{ return &memdb.TableSchema{
Name: "coordinates", Name: tableCoordinates,
Indexes: map[string]*memdb.IndexSchema{ Indexes: map[string]*memdb.IndexSchema{
"id": { indexID: {
Name: "id", Name: indexID,
AllowMissing: false, AllowMissing: false,
Unique: true, Unique: true,
Indexer: &memdb.CompoundIndex{ Indexer: indexerSingleWithPrefix{
// AllowMissing is required since we allow readIndex: indexFromCoordinateQuery,
// Segment to be an empty string. writeIndex: indexFromCoordinate,
AllowMissing: true, prefixIndex: prefixIndexFromQueryNoNamespace,
Indexes: []memdb.Indexer{
&memdb.StringFieldIndex{
Field: "Node",
Lowercase: true,
},
&memdb.StringFieldIndex{
Field: "Segment",
Lowercase: true,
},
},
}, },
}, },
"node": { indexNode: {
Name: "node", Name: indexNode,
AllowMissing: false, AllowMissing: false,
Unique: false, Unique: false,
Indexer: &memdb.StringFieldIndex{ Indexer: indexerSingle{
Field: "Node", readIndex: indexFromQuery,
Lowercase: true, writeIndex: indexNodeFromCoordinate,
}, },
}, },
}, },
@ -50,7 +100,7 @@ func coordinatesTableSchema() *memdb.TableSchema {
// Coordinates is used to pull all the coordinates from the snapshot. // Coordinates is used to pull all the coordinates from the snapshot.
func (s *Snapshot) Coordinates() (memdb.ResultIterator, error) { func (s *Snapshot) Coordinates() (memdb.ResultIterator, error) {
iter, err := s.tx.Get("coordinates", "id") iter, err := s.tx.Get(tableCoordinates, indexID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -68,28 +118,31 @@ func (s *Restore) Coordinates(idx uint64, updates structs.Coordinates) error {
continue continue
} }
if err := s.tx.Insert("coordinates", update); err != nil { if err := ensureCoordinateTxn(s.tx, idx, update); err != nil {
return fmt.Errorf("failed restoring coordinate: %s", err) return fmt.Errorf("failed restoring coordinate: %s", err)
} }
} }
if err := indexUpdateMaxTxn(s.tx, idx, "coordinates"); err != nil {
return fmt.Errorf("failed updating index: %s", err)
}
return nil return nil
} }
// Coordinate returns a map of coordinates for the given node, indexed by // Coordinate returns a map of coordinates for the given node, indexed by
// network segment. // network segment.
func (s *Store) Coordinate(ws memdb.WatchSet, node string, _ *structs.EnterpriseMeta) (uint64, lib.CoordinateSet, error) { func (s *Store) Coordinate(ws memdb.WatchSet, node string, entMeta *structs.EnterpriseMeta) (uint64, lib.CoordinateSet, error) {
// TODO(partitions): use the provided entmeta
tx := s.db.Txn(false) tx := s.db.Txn(false)
defer tx.Abort() defer tx.Abort()
tableIdx := maxIndexTxn(tx, "coordinates") // TODO: accept non-pointer value
if entMeta == nil {
entMeta = structs.NodeEnterpriseMetaInDefaultPartition()
}
iter, err := tx.Get("coordinates", "node", node) tableIdx := coordinatesMaxIndex(tx, entMeta)
iter, err := tx.Get(tableCoordinates, indexNode, Query{
Value: node,
EnterpriseMeta: *entMeta,
})
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed coordinate lookup: %s", err) return 0, nil, fmt.Errorf("failed coordinate lookup: %s", err)
} }
@ -104,16 +157,20 @@ func (s *Store) Coordinate(ws memdb.WatchSet, node string, _ *structs.Enterprise
} }
// Coordinates queries for all nodes with coordinates. // Coordinates queries for all nodes with coordinates.
func (s *Store) Coordinates(ws memdb.WatchSet, _ *structs.EnterpriseMeta) (uint64, structs.Coordinates, error) { func (s *Store) Coordinates(ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (uint64, structs.Coordinates, error) {
// TODO(partitions): use the provided entmeta
tx := s.db.Txn(false) tx := s.db.Txn(false)
defer tx.Abort() defer tx.Abort()
// TODO: accept non-pointer value
if entMeta == nil {
entMeta = structs.NodeEnterpriseMetaInDefaultPartition()
}
// Get the table index. // Get the table index.
idx := maxIndexTxn(tx, "coordinates") idx := coordinatesMaxIndex(tx, entMeta)
// Pull all the coordinates. // Pull all the coordinates.
iter, err := tx.Get("coordinates", "id") iter, err := tx.Get(tableCoordinates, indexID+"_prefix", entMeta)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed coordinate lookup: %s", err) return 0, nil, fmt.Errorf("failed coordinate lookup: %s", err)
} }
@ -140,6 +197,8 @@ func (s *Store) CoordinateBatchUpdate(idx uint64, updates structs.Coordinates) e
continue continue
} }
entMeta := update.GetEnterpriseMeta()
// Since the cleanup of coordinates is tied to deletion of // Since the cleanup of coordinates is tied to deletion of
// nodes, we silently drop any updates for nodes that we don't // nodes, we silently drop any updates for nodes that we don't
// know about. This might be possible during normal operation // know about. This might be possible during normal operation
@ -148,7 +207,10 @@ func (s *Store) CoordinateBatchUpdate(idx uint64, updates structs.Coordinates) e
// don't carefully sequence this, and since it will fix itself // don't carefully sequence this, and since it will fix itself
// on the next coordinate update from that node, we don't return // on the next coordinate update from that node, we don't return
// an error or log anything. // an error or log anything.
node, err := tx.First(tableNodes, indexID, Query{Value: update.Node}) node, err := tx.First(tableNodes, indexID, Query{
Value: update.Node,
EnterpriseMeta: *entMeta,
})
if err != nil { if err != nil {
return fmt.Errorf("failed node lookup: %s", err) return fmt.Errorf("failed node lookup: %s", err)
} }
@ -156,15 +218,22 @@ func (s *Store) CoordinateBatchUpdate(idx uint64, updates structs.Coordinates) e
continue continue
} }
if err := tx.Insert("coordinates", update); err != nil { if err := ensureCoordinateTxn(tx, idx, update); err != nil {
return fmt.Errorf("failed inserting coordinate: %s", err) return fmt.Errorf("failed inserting coordinate: %s", err)
} }
} }
// Update the index.
if err := tx.Insert(tableIndex, &IndexEntry{"coordinates", idx}); err != nil {
return fmt.Errorf("failed updating index: %s", err)
}
return tx.Commit() return tx.Commit()
} }
func deleteCoordinateTxn(tx WriteTxn, idx uint64, coord *structs.Coordinate) error {
if err := tx.Delete(tableCoordinates, coord); err != nil {
return fmt.Errorf("failed deleting coordinate: %s", err)
}
if err := updateCoordinatesIndexes(tx, idx, coord.GetEnterpriseMeta()); err != nil {
return fmt.Errorf("failed updating coordinate index: %s", err)
}
return nil
}

View File

@ -0,0 +1,36 @@
// +build !consulent
package state
import (
"fmt"
"github.com/hashicorp/consul/agent/structs"
)
func coordinatesMaxIndex(tx ReadTxn, entMeta *structs.EnterpriseMeta) uint64 {
return maxIndexTxn(tx, tableCoordinates)
}
func updateCoordinatesIndexes(tx WriteTxn, idx uint64, entMeta *structs.EnterpriseMeta) error {
// Update the index.
if err := indexUpdateMaxTxn(tx, idx, tableCoordinates); err != nil {
return fmt.Errorf("failed updating index: %s", err)
}
return nil
}
func ensureCoordinateTxn(tx WriteTxn, idx uint64, coord *structs.Coordinate) error {
// ensure that the Partition is always empty within the state store
coord.Partition = ""
if err := tx.Insert(tableCoordinates, coord); err != nil {
return fmt.Errorf("failed inserting coordinate: %s", err)
}
if err := updateCoordinatesIndexes(tx, idx, coord.GetEnterpriseMeta()); err != nil {
return fmt.Errorf("failed updating coordinate index: %s", err)
}
return nil
}

View File

@ -0,0 +1,54 @@
// +build !consulent
package state
import "github.com/hashicorp/consul/agent/structs"
func testIndexerTableCoordinates() map[string]indexerTestCase {
return map[string]indexerTestCase{
indexID: {
read: indexValue{
source: CoordinateQuery{
Node: "NoDeId",
Segment: "SeGmEnT",
},
expected: []byte("nodeid\x00segment\x00"),
},
write: indexValue{
source: &structs.Coordinate{
Node: "NoDeId",
Segment: "SeGmEnT",
},
expected: []byte("nodeid\x00segment\x00"),
},
prefix: []indexValue{
{
source: (*structs.EnterpriseMeta)(nil),
expected: nil,
},
{
source: structs.EnterpriseMeta{},
expected: nil,
},
{
source: Query{Value: "NoDeId"},
expected: []byte("nodeid\x00"),
},
},
},
indexNode: {
read: indexValue{
source: Query{
Value: "NoDeId",
},
expected: []byte("nodeid\x00"),
},
write: indexValue{
source: &structs.Coordinate{
Node: "NoDeId",
},
expected: []byte("nodeid\x00"),
},
},
}
}

View File

@ -41,19 +41,13 @@ func TestStateStore_Coordinate_Updates(t *testing.T) {
// a per-node coordinate for a nonexistent node doesn't do anything bad. // a per-node coordinate for a nonexistent node doesn't do anything bad.
ws := memdb.NewWatchSet() ws := memdb.NewWatchSet()
idx, all, err := s.Coordinates(ws, nil) idx, all, err := s.Coordinates(ws, nil)
if err != nil { require.NoError(t, err)
t.Fatalf("err: %s", err) require.Equal(t, uint64(0), idx, "bad index")
}
if idx != 0 {
t.Fatalf("bad index: %d", idx)
}
require.Nil(t, all) require.Nil(t, all)
coordinateWs := memdb.NewWatchSet() coordinateWs := memdb.NewWatchSet()
_, coords, err := s.Coordinate(coordinateWs, "nope", nil) _, coords, err := s.Coordinate(coordinateWs, "nope", nil)
if err != nil { require.NoError(t, err)
t.Fatalf("err: %s", err)
}
require.Equal(t, lib.CoordinateSet{}, coords) require.Equal(t, lib.CoordinateSet{}, coords)
// Make an update for nodes that don't exist and make sure they get // Make an update for nodes that don't exist and make sure they get
@ -68,40 +62,26 @@ func TestStateStore_Coordinate_Updates(t *testing.T) {
Coord: generateRandomCoordinate(), Coord: generateRandomCoordinate(),
}, },
} }
if err := s.CoordinateBatchUpdate(1, updates); err != nil { require.NoError(t, s.CoordinateBatchUpdate(1, updates))
t.Fatalf("err: %s", err) require.False(t, watchFired(ws) || watchFired(coordinateWs))
}
if watchFired(ws) || watchFired(coordinateWs) {
t.Fatalf("bad")
}
// Should still be empty, though applying an empty batch does bump // Should still be empty, though applying an empty batch does NOT bump
// the table index. // the table index.
ws = memdb.NewWatchSet() ws = memdb.NewWatchSet()
idx, all, err = s.Coordinates(ws, nil) idx, all, err = s.Coordinates(ws, nil)
if err != nil { require.NoError(t, err)
t.Fatalf("err: %s", err) require.Equal(t, uint64(0), idx, "bad index")
}
if idx != 1 {
t.Fatalf("bad index: %d", idx)
}
require.Nil(t, all) require.Nil(t, all)
coordinateWs = memdb.NewWatchSet() coordinateWs = memdb.NewWatchSet()
idx, _, err = s.Coordinate(coordinateWs, "node1", nil) idx, _, err = s.Coordinate(coordinateWs, "node1", nil)
if err != nil { require.NoError(t, err)
t.Fatalf("err: %s", err) require.Equal(t, uint64(0), idx, "bad index")
}
if idx != 1 {
t.Fatalf("bad index: %d", idx)
}
// Register the nodes then do the update again. // Register the nodes then do the update again.
testRegisterNode(t, s, 1, "node1") testRegisterNode(t, s, 1, "node1")
testRegisterNode(t, s, 2, "node2") testRegisterNode(t, s, 2, "node2")
if err := s.CoordinateBatchUpdate(3, updates); err != nil { require.NoError(t, s.CoordinateBatchUpdate(3, updates))
t.Fatalf("err: %s", err)
}
if !watchFired(ws) || !watchFired(coordinateWs) { if !watchFired(ws) || !watchFired(coordinateWs) {
t.Fatalf("bad") t.Fatalf("bad")
} }
@ -109,12 +89,8 @@ func TestStateStore_Coordinate_Updates(t *testing.T) {
// Should go through now. // Should go through now.
ws = memdb.NewWatchSet() ws = memdb.NewWatchSet()
idx, all, err = s.Coordinates(ws, nil) idx, all, err = s.Coordinates(ws, nil)
if err != nil { require.NoError(t, err)
t.Fatalf("err: %s", err) require.Equal(t, uint64(3), idx, "bad index")
}
if idx != 3 {
t.Fatalf("bad index: %d", idx)
}
require.Equal(t, updates, all) require.Equal(t, updates, all)
// Also verify the per-node coordinate interface. // Also verify the per-node coordinate interface.
@ -122,12 +98,8 @@ func TestStateStore_Coordinate_Updates(t *testing.T) {
for i, update := range updates { for i, update := range updates {
nodeWs[i] = memdb.NewWatchSet() nodeWs[i] = memdb.NewWatchSet()
idx, coords, err := s.Coordinate(nodeWs[i], update.Node, nil) idx, coords, err := s.Coordinate(nodeWs[i], update.Node, nil)
if err != nil { require.NoError(t, err)
t.Fatalf("err: %s", err) require.Equal(t, uint64(3), idx, "bad index")
}
if idx != 3 {
t.Fatalf("bad index: %d", idx)
}
expected := lib.CoordinateSet{ expected := lib.CoordinateSet{
"": update.Coord, "": update.Coord,
} }
@ -136,9 +108,7 @@ func TestStateStore_Coordinate_Updates(t *testing.T) {
// Update the coordinate for one of the nodes. // Update the coordinate for one of the nodes.
updates[1].Coord = generateRandomCoordinate() updates[1].Coord = generateRandomCoordinate()
if err := s.CoordinateBatchUpdate(4, updates); err != nil { require.NoError(t, s.CoordinateBatchUpdate(4, updates))
t.Fatalf("err: %s", err)
}
if !watchFired(ws) { if !watchFired(ws) {
t.Fatalf("bad") t.Fatalf("bad")
} }
@ -150,23 +120,15 @@ func TestStateStore_Coordinate_Updates(t *testing.T) {
// Verify it got applied. // Verify it got applied.
idx, all, err = s.Coordinates(nil, nil) idx, all, err = s.Coordinates(nil, nil)
if err != nil { require.NoError(t, err)
t.Fatalf("err: %s", err) require.Equal(t, uint64(4), idx, "bad index")
}
if idx != 4 {
t.Fatalf("bad index: %d", idx)
}
require.Equal(t, updates, all) require.Equal(t, updates, all)
// And check the per-node coordinate version of the same thing. // And check the per-node coordinate version of the same thing.
for _, update := range updates { for _, update := range updates {
idx, coords, err := s.Coordinate(nil, update.Node, nil) idx, coords, err := s.Coordinate(nil, update.Node, nil)
if err != nil { require.NoError(t, err)
t.Fatalf("err: %s", err) require.Equal(t, uint64(4), idx, "bad index")
}
if idx != 4 {
t.Fatalf("bad index: %d", idx)
}
expected := lib.CoordinateSet{ expected := lib.CoordinateSet{
"": update.Coord, "": update.Coord,
} }
@ -180,19 +142,13 @@ func TestStateStore_Coordinate_Updates(t *testing.T) {
Coord: &coordinate.Coordinate{Height: math.NaN()}, Coord: &coordinate.Coordinate{Height: math.NaN()},
}, },
} }
if err := s.CoordinateBatchUpdate(5, badUpdates); err != nil { require.NoError(t, s.CoordinateBatchUpdate(5, badUpdates))
t.Fatalf("err: %s", err)
}
// Verify we are at the previous state, though the empty batch does bump // Verify we are at the previous state, and verify that the empty batch
// the table index. // does NOT bump the table index.
idx, all, err = s.Coordinates(nil, nil) idx, all, err = s.Coordinates(nil, nil)
if err != nil { require.NoError(t, err)
t.Fatalf("err: %s", err) require.Equal(t, uint64(4), idx, "bad index")
}
if idx != 5 {
t.Fatalf("bad index: %d", idx)
}
require.Equal(t, updates, all) require.Equal(t, updates, all)
} }
@ -213,15 +169,11 @@ func TestStateStore_Coordinate_Cleanup(t *testing.T) {
Coord: generateRandomCoordinate(), Coord: generateRandomCoordinate(),
}, },
} }
if err := s.CoordinateBatchUpdate(2, updates); err != nil { require.NoError(t, s.CoordinateBatchUpdate(2, updates))
t.Fatalf("err: %s", err)
}
// Make sure it's in there. // Make sure it's in there.
_, coords, err := s.Coordinate(nil, "node1", nil) _, coords, err := s.Coordinate(nil, "node1", nil)
if err != nil { require.NoError(t, err)
t.Fatalf("err: %s", err)
}
expected := lib.CoordinateSet{ expected := lib.CoordinateSet{
"alpha": updates[0].Coord, "alpha": updates[0].Coord,
"beta": updates[1].Coord, "beta": updates[1].Coord,
@ -229,25 +181,17 @@ func TestStateStore_Coordinate_Cleanup(t *testing.T) {
require.Equal(t, expected, coords) require.Equal(t, expected, coords)
// Now delete the node. // Now delete the node.
if err := s.DeleteNode(3, "node1", nil); err != nil { require.NoError(t, s.DeleteNode(3, "node1", nil))
t.Fatalf("err: %s", err)
}
// Make sure the coordinate is gone. // Make sure the coordinate is gone.
_, coords, err = s.Coordinate(nil, "node1", nil) _, coords, err = s.Coordinate(nil, "node1", nil)
if err != nil { require.NoError(t, err)
t.Fatalf("err: %s", err)
}
require.Equal(t, lib.CoordinateSet{}, coords) require.Equal(t, lib.CoordinateSet{}, coords)
// Make sure the index got updated. // Make sure the index got updated.
idx, all, err := s.Coordinates(nil, nil) idx, all, err := s.Coordinates(nil, nil)
if err != nil { require.NoError(t, err)
t.Fatalf("err: %s", err) require.Equal(t, uint64(3), idx, "bad index")
}
if idx != 3 {
t.Fatalf("bad index: %d", idx)
}
require.Nil(t, all) require.Nil(t, all)
} }
@ -267,9 +211,7 @@ func TestStateStore_Coordinate_Snapshot_Restore(t *testing.T) {
Coord: generateRandomCoordinate(), Coord: generateRandomCoordinate(),
}, },
} }
if err := s.CoordinateBatchUpdate(3, updates); err != nil { require.NoError(t, s.CoordinateBatchUpdate(3, updates))
t.Fatalf("err: %s", err)
}
// Manually put a bad coordinate in for node3. // Manually put a bad coordinate in for node3.
testRegisterNode(t, s, 4, "node3") testRegisterNode(t, s, 4, "node3")
@ -278,9 +220,7 @@ func TestStateStore_Coordinate_Snapshot_Restore(t *testing.T) {
Coord: &coordinate.Coordinate{Height: math.NaN()}, Coord: &coordinate.Coordinate{Height: math.NaN()},
} }
tx := s.db.WriteTxn(5) tx := s.db.WriteTxn(5)
if err := tx.Insert("coordinates", badUpdate); err != nil { require.NoError(t, tx.Insert("coordinates", badUpdate))
t.Fatalf("err: %v", err)
}
require.NoError(t, tx.Commit()) require.NoError(t, tx.Commit())
// Snapshot the coordinates. // Snapshot the coordinates.
@ -298,18 +238,13 @@ func TestStateStore_Coordinate_Snapshot_Restore(t *testing.T) {
Coord: generateRandomCoordinate(), Coord: generateRandomCoordinate(),
}, },
} }
if err := s.CoordinateBatchUpdate(5, trash); err != nil { require.NoError(t, s.CoordinateBatchUpdate(5, trash))
t.Fatalf("err: %s", err)
}
// Verify the snapshot. // Verify the snapshot.
if idx := snap.LastIndex(); idx != 4 { require.Equal(t, uint64(4), snap.LastIndex(), "bad index")
t.Fatalf("bad index: %d", idx)
}
iter, err := snap.Coordinates() iter, err := snap.Coordinates()
if err != nil { require.NoError(t, err)
t.Fatalf("err: %s", err)
}
var dump structs.Coordinates var dump structs.Coordinates
for coord := iter.Next(); coord != nil; coord = iter.Next() { for coord := iter.Next(); coord != nil; coord = iter.Next() {
dump = append(dump, coord.(*structs.Coordinate)) dump = append(dump, coord.(*structs.Coordinate))
@ -319,30 +254,20 @@ func TestStateStore_Coordinate_Snapshot_Restore(t *testing.T) {
// the read side. // the read side.
require.Equal(t, append(updates, badUpdate), dump) require.Equal(t, append(updates, badUpdate), dump)
// Restore the values into a new state store. runStep(t, "restore the values into a new state store", func(t *testing.T) {
func() {
s := testStateStore(t) s := testStateStore(t)
restore := s.Restore() restore := s.Restore()
if err := restore.Coordinates(6, dump); err != nil { require.NoError(t, restore.Coordinates(6, dump))
t.Fatalf("err: %s", err)
}
restore.Commit() restore.Commit()
// Read the restored coordinates back out and verify that they match. // Read the restored coordinates back out and verify that they match.
idx, res, err := s.Coordinates(nil, nil) idx, res, err := s.Coordinates(nil, nil)
if err != nil { require.NoError(t, err)
t.Fatalf("err: %s", err) require.Equal(t, uint64(6), idx, "bad index")
}
if idx != 6 {
t.Fatalf("bad index: %d", idx)
}
require.Equal(t, updates, res) require.Equal(t, updates, res)
// Check that the index was updated (note that it got passed // Check that the index was updated (note that it got passed
// in during the restore). // in during the restore).
if idx := s.maxIndex("coordinates"); idx != 6 { require.Equal(t, uint64(6), s.maxIndex("coordinates"), "bad index")
t.Fatalf("bad index: %d", idx) })
}
}()
} }

View File

@ -2,6 +2,7 @@ package state
import ( import (
"bytes" "bytes"
"encoding/binary"
"errors" "errors"
"fmt" "fmt"
) )
@ -113,6 +114,15 @@ func (b *indexBuilder) String(v string) {
(*bytes.Buffer)(b).WriteString(null) (*bytes.Buffer)(b).WriteString(null)
} }
func (b *indexBuilder) Int64(v int64) {
const size = binary.MaxVarintLen64
// Get the value and encode it
buf := make([]byte, size)
binary.PutVarint(buf, v)
b.Raw(buf)
}
// Raw appends the bytes without a null terminator to the buffer. Raw should // Raw appends the bytes without a null terminator to the buffer. Raw should
// only be used when v has a fixed length, or when building the last segment of // only be used when v has a fixed length, or when building the last segment of
// a prefix index. // a prefix index.

View File

@ -40,6 +40,17 @@ func indexFromQuery(arg interface{}) ([]byte, error) {
return b.Bytes(), nil return b.Bytes(), nil
} }
func indexFromServiceNameAsString(arg interface{}) ([]byte, error) {
sn, ok := arg.(structs.ServiceName)
if !ok {
return nil, fmt.Errorf("unexpected type %T for ServiceName index", arg)
}
var b indexBuilder
b.String(strings.ToLower(sn.String()))
return b.Bytes(), nil
}
// uuidStringToBytes is a modified version of memdb.UUIDFieldIndex.parseString // uuidStringToBytes is a modified version of memdb.UUIDFieldIndex.parseString
func uuidStringToBytes(uuid string) ([]byte, error) { func uuidStringToBytes(uuid string) ([]byte, error) {
l := len(uuid) l := len(uuid)

View File

@ -23,3 +23,22 @@ func prefixIndexFromQuery(arg interface{}) ([]byte, error) {
return nil, fmt.Errorf("unexpected type %T for Query prefix index", arg) return nil, fmt.Errorf("unexpected type %T for Query prefix index", arg)
} }
func prefixIndexFromQueryNoNamespace(arg interface{}) ([]byte, error) {
return prefixIndexFromQuery(arg)
}
func prefixIndexFromServiceNameAsString(arg interface{}) ([]byte, error) {
var b indexBuilder
switch v := arg.(type) {
case *structs.EnterpriseMeta:
return nil, nil
case structs.EnterpriseMeta:
return nil, nil
case structs.ServiceName:
b.String(strings.ToLower(v.String()))
return b.Bytes(), nil
}
return nil, fmt.Errorf("unexpected type %T for Query prefix index", arg)
}

View File

@ -36,14 +36,18 @@ func TestNewDBSchema_Indexers(t *testing.T) {
require.NoError(t, schema.Validate()) require.NoError(t, schema.Validate())
var testcases = map[string]func() map[string]indexerTestCase{ var testcases = map[string]func() map[string]indexerTestCase{
tableACLPolicies: testIndexerTableACLPolicies, // acl
tableACLRoles: testIndexerTableACLRoles, tableACLPolicies: testIndexerTableACLPolicies,
tableACLRoles: testIndexerTableACLRoles,
// catalog
tableChecks: testIndexerTableChecks, tableChecks: testIndexerTableChecks,
tableServices: testIndexerTableServices, tableServices: testIndexerTableServices,
tableNodes: testIndexerTableNodes, tableNodes: testIndexerTableNodes,
tableConfigEntries: testIndexerTableConfigEntries, tableCoordinates: testIndexerTableCoordinates,
tableMeshTopology: testIndexerTableMeshTopology, tableMeshTopology: testIndexerTableMeshTopology,
tableGatewayServices: testIndexerTableGatewayServices, tableGatewayServices: testIndexerTableGatewayServices,
// config
tableConfigEntries: testIndexerTableConfigEntries,
} }
addEnterpriseIndexerTestCases(testcases) addEnterpriseIndexerTestCases(testcases)

View File

@ -57,18 +57,37 @@ func testStateStore(t *testing.T) *Store {
} }
func testRegisterNode(t *testing.T, s *Store, idx uint64, nodeID string) { func testRegisterNode(t *testing.T, s *Store, idx uint64, nodeID string) {
testRegisterNodeWithMeta(t, s, idx, nodeID, nil) testRegisterNodeOpts(t, s, idx, nodeID)
} }
// testRegisterNodeWithChange registers a node and ensures it gets different from previous registration // testRegisterNodeWithChange registers a node and ensures it gets different from previous registration
func testRegisterNodeWithChange(t *testing.T, s *Store, idx uint64, nodeID string) { func testRegisterNodeWithChange(t *testing.T, s *Store, idx uint64, nodeID string) {
testRegisterNodeWithMeta(t, s, idx, nodeID, map[string]string{ testRegisterNodeOpts(t, s, idx, nodeID, regNodeWithMeta(map[string]string{
"version": fmt.Sprint(idx), "version": fmt.Sprint(idx),
}) }))
} }
func testRegisterNodeWithMeta(t *testing.T, s *Store, idx uint64, nodeID string, meta map[string]string) { func testRegisterNodeWithMeta(t *testing.T, s *Store, idx uint64, nodeID string, meta map[string]string) {
node := &structs.Node{Node: nodeID, Meta: meta} testRegisterNodeOpts(t, s, idx, nodeID, regNodeWithMeta(meta))
}
type regNodeOption func(*structs.Node) error
func regNodeWithMeta(meta map[string]string) func(*structs.Node) error {
return func(node *structs.Node) error {
node.Meta = meta
return nil
}
}
func testRegisterNodeOpts(t *testing.T, s *Store, idx uint64, nodeID string, opts ...regNodeOption) {
node := &structs.Node{Node: nodeID}
for _, opt := range opts {
if err := opt(node); err != nil {
t.Fatalf("err: %s", err)
}
}
if err := s.EnsureNode(idx, node); err != nil { if err := s.EnsureNode(idx, node); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
@ -283,7 +302,7 @@ func TestStateStore_maxIndex(t *testing.T) {
testRegisterNode(t, s, 1, "bar") testRegisterNode(t, s, 1, "bar")
testRegisterService(t, s, 2, "foo", "consul") testRegisterService(t, s, 2, "foo", "consul")
if max := s.maxIndex("nodes", tableServices); max != 2 { if max := s.maxIndex(tableNodes, tableServices); max != 2 {
t.Fatalf("bad max: %d", max) t.Fatalf("bad max: %d", max)
} }
} }
@ -295,12 +314,12 @@ func TestStateStore_indexUpdateMaxTxn(t *testing.T) {
testRegisterNode(t, s, 1, "bar") testRegisterNode(t, s, 1, "bar")
tx := s.db.WriteTxnRestore() tx := s.db.WriteTxnRestore()
if err := indexUpdateMaxTxn(tx, 3, "nodes"); err != nil { if err := indexUpdateMaxTxn(tx, 3, tableNodes); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
require.NoError(t, tx.Commit()) require.NoError(t, tx.Commit())
if max := s.maxIndex("nodes"); max != 3 { if max := s.maxIndex(tableNodes); max != 3 {
t.Fatalf("bad max: %d", max) t.Fatalf("bad max: %d", max)
} }
} }

View File

@ -149,11 +149,13 @@ func (s *Store) txnNode(tx WriteTxn, idx uint64, op *structs.TxnNodeOp) (structs
var entry *structs.Node var entry *structs.Node
var err error var err error
// TODO(partitions): change these errors to include node partitions when printing
getNode := func() (*structs.Node, error) { getNode := func() (*structs.Node, error) {
if op.Node.ID != "" { if op.Node.ID != "" {
return getNodeIDTxn(tx, op.Node.ID) return getNodeIDTxn(tx, op.Node.ID)
} else { } else {
return getNodeTxn(tx, op.Node.Node) return getNodeTxn(tx, op.Node.Node, op.Node.GetEnterpriseMeta())
} }
} }
@ -180,11 +182,11 @@ func (s *Store) txnNode(tx WriteTxn, idx uint64, op *structs.TxnNodeOp) (structs
entry, err = getNode() entry, err = getNode()
case api.NodeDelete: case api.NodeDelete:
err = s.deleteNodeTxn(tx, idx, op.Node.Node) err = s.deleteNodeTxn(tx, idx, op.Node.Node, op.Node.GetEnterpriseMeta())
case api.NodeDeleteCAS: case api.NodeDeleteCAS:
var ok bool var ok bool
ok, err = s.deleteNodeCASTxn(tx, idx, op.Node.ModifyIndex, op.Node.Node) ok, err = s.deleteNodeCASTxn(tx, idx, op.Node.ModifyIndex, op.Node.Node, op.Node.GetEnterpriseMeta())
if !ok && err == nil { if !ok && err == nil {
err = fmt.Errorf("failed to delete node %q, index is stale", op.Node.Node) err = fmt.Errorf("failed to delete node %q, index is stale", op.Node.Node)
} }

View File

@ -320,7 +320,8 @@ func TestStateStore_Txn_Service(t *testing.T) {
// Make sure it looks as expected. // Make sure it looks as expected.
expectedServices := &structs.NodeServices{ expectedServices := &structs.NodeServices{
Node: &structs.Node{ Node: &structs.Node{
Node: "node1", Node: "node1",
Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
RaftIndex: structs.RaftIndex{ RaftIndex: structs.RaftIndex{
CreateIndex: 1, CreateIndex: 1,
ModifyIndex: 1, ModifyIndex: 1,

View File

@ -115,6 +115,7 @@ func TestServer_Subscribe_IntegrationWithBackend(t *testing.T) {
CheckServiceNode: &pbservice.CheckServiceNode{ CheckServiceNode: &pbservice.CheckServiceNode{
Node: &pbservice.Node{ Node: &pbservice.Node{
Node: "node1", Node: "node1",
Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
Datacenter: "dc1", Datacenter: "dc1",
Address: "3.4.5.6", Address: "3.4.5.6",
RaftIndex: raftIndex(ids, "reg2", "reg2"), RaftIndex: raftIndex(ids, "reg2", "reg2"),
@ -145,6 +146,7 @@ func TestServer_Subscribe_IntegrationWithBackend(t *testing.T) {
CheckServiceNode: &pbservice.CheckServiceNode{ CheckServiceNode: &pbservice.CheckServiceNode{
Node: &pbservice.Node{ Node: &pbservice.Node{
Node: "node2", Node: "node2",
Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
Datacenter: "dc1", Datacenter: "dc1",
Address: "1.2.3.4", Address: "1.2.3.4",
RaftIndex: raftIndex(ids, "reg3", "reg3"), RaftIndex: raftIndex(ids, "reg3", "reg3"),
@ -194,6 +196,7 @@ func TestServer_Subscribe_IntegrationWithBackend(t *testing.T) {
CheckServiceNode: &pbservice.CheckServiceNode{ CheckServiceNode: &pbservice.CheckServiceNode{
Node: &pbservice.Node{ Node: &pbservice.Node{
Node: "node2", Node: "node2",
Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
Datacenter: "dc1", Datacenter: "dc1",
Address: "1.2.3.4", Address: "1.2.3.4",
RaftIndex: raftIndex(ids, "reg3", "reg3"), RaftIndex: raftIndex(ids, "reg3", "reg3"),
@ -465,6 +468,7 @@ func TestServer_Subscribe_IntegrationWithBackend_ForwardToDC(t *testing.T) {
CheckServiceNode: &pbservice.CheckServiceNode{ CheckServiceNode: &pbservice.CheckServiceNode{
Node: &pbservice.Node{ Node: &pbservice.Node{
Node: "node1", Node: "node1",
Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
Datacenter: "dc2", Datacenter: "dc2",
Address: "3.4.5.6", Address: "3.4.5.6",
RaftIndex: raftIndex(ids, "reg2", "reg2"), RaftIndex: raftIndex(ids, "reg2", "reg2"),
@ -495,6 +499,7 @@ func TestServer_Subscribe_IntegrationWithBackend_ForwardToDC(t *testing.T) {
CheckServiceNode: &pbservice.CheckServiceNode{ CheckServiceNode: &pbservice.CheckServiceNode{
Node: &pbservice.Node{ Node: &pbservice.Node{
Node: "node2", Node: "node2",
Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
Datacenter: "dc2", Datacenter: "dc2",
Address: "1.2.3.4", Address: "1.2.3.4",
RaftIndex: raftIndex(ids, "reg3", "reg3"), RaftIndex: raftIndex(ids, "reg3", "reg3"),
@ -544,6 +549,7 @@ func TestServer_Subscribe_IntegrationWithBackend_ForwardToDC(t *testing.T) {
CheckServiceNode: &pbservice.CheckServiceNode{ CheckServiceNode: &pbservice.CheckServiceNode{
Node: &pbservice.Node{ Node: &pbservice.Node{
Node: "node2", Node: "node2",
Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
Datacenter: "dc2", Datacenter: "dc2",
Address: "1.2.3.4", Address: "1.2.3.4",
RaftIndex: raftIndex(ids, "reg3", "reg3"), RaftIndex: raftIndex(ids, "reg3", "reg3"),

View File

@ -437,7 +437,7 @@ func (r *RegisterRequest) ChangesNode(node *Node) bool {
// Check if any of the node-level fields are being changed. // Check if any of the node-level fields are being changed.
if r.ID != node.ID || if r.ID != node.ID ||
r.Node != node.Node || r.Node != node.Node ||
// TODO(partitions): do we need to check partition here? r.PartitionOrDefault() != node.PartitionOrDefault() ||
r.Address != node.Address || r.Address != node.Address ||
r.Datacenter != node.Datacenter || r.Datacenter != node.Datacenter ||
!reflect.DeepEqual(r.TaggedAddresses, node.TaggedAddresses) || !reflect.DeepEqual(r.TaggedAddresses, node.TaggedAddresses) ||

View File

@ -51,6 +51,7 @@ func TestAPI_CatalogNodes(t *testing.T) {
{ {
ID: s.Config.NodeID, ID: s.Config.NodeID,
Node: s.Config.NodeName, Node: s.Config.NodeName,
Partition: defaultPartition,
Address: "127.0.0.1", Address: "127.0.0.1",
Datacenter: "dc1", Datacenter: "dc1",
TaggedAddresses: map[string]string{ TaggedAddresses: map[string]string{

View File

@ -170,6 +170,7 @@ func TestAPI_ClientTxn(t *testing.T) {
Node: &Node{ Node: &Node{
ID: nodeID, ID: nodeID,
Node: "foo", Node: "foo",
Partition: defaultPartition,
Address: "2.2.2.2", Address: "2.2.2.2",
Datacenter: "dc1", Datacenter: "dc1",
CreateIndex: ret.Results[2].Node.CreateIndex, CreateIndex: ret.Results[2].Node.CreateIndex,
@ -269,6 +270,7 @@ func TestAPI_ClientTxn(t *testing.T) {
Node: &Node{ Node: &Node{
ID: s.Config.NodeID, ID: s.Config.NodeID,
Node: s.Config.NodeName, Node: s.Config.NodeName,
Partition: defaultPartition,
Address: "127.0.0.1", Address: "127.0.0.1",
Datacenter: "dc1", Datacenter: "dc1",
TaggedAddresses: map[string]string{ TaggedAddresses: map[string]string{
@ -283,7 +285,7 @@ func TestAPI_ClientTxn(t *testing.T) {
}, },
}, },
} }
require.Equal(r, ret.Results, expected) require.Equal(r, expected, ret.Results)
}) })
// Sanity check using the regular GET API. // Sanity check using the regular GET API.