251 lines
6.3 KiB
Go
251 lines
6.3 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package inmem
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/go-memdb"
|
|
|
|
"github.com/hashicorp/consul/internal/storage"
|
|
"github.com/hashicorp/consul/proto-public/pbresource"
|
|
)
|
|
|
|
const (
|
|
tableNameMetadata = "metadata"
|
|
tableNameResources = "resources"
|
|
|
|
indexNameID = "id"
|
|
indexNameOwner = "owner"
|
|
|
|
metaKeyEventIndex = "index"
|
|
)
|
|
|
|
func newDB() (*memdb.MemDB, error) {
|
|
return memdb.NewMemDB(&memdb.DBSchema{
|
|
Tables: map[string]*memdb.TableSchema{
|
|
tableNameMetadata: {
|
|
Name: tableNameMetadata,
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
indexNameID: {
|
|
Name: indexNameID,
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.StringFieldIndex{Field: "Key"},
|
|
},
|
|
},
|
|
},
|
|
tableNameResources: {
|
|
Name: tableNameResources,
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
indexNameID: {
|
|
Name: indexNameID,
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: idIndexer{},
|
|
},
|
|
indexNameOwner: {
|
|
Name: indexNameOwner,
|
|
AllowMissing: true,
|
|
Unique: false,
|
|
Indexer: ownerIndexer{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
// indexSeparator delimits the segments of our radix tree keys.
|
|
const indexSeparator = "\x00"
|
|
|
|
// idIndexer implements the memdb.Indexer, memdb.SingleIndexer and
|
|
// memdb.PrefixIndexer interfaces. It is used for indexing resources
|
|
// by their IDs.
|
|
type idIndexer struct{}
|
|
|
|
// FromArgs constructs a radix tree key from an ID for lookup.
|
|
func (i idIndexer) FromArgs(args ...any) ([]byte, error) {
|
|
if l := len(args); l != 1 {
|
|
return nil, fmt.Errorf("expected 1 arg, got: %d", l)
|
|
}
|
|
id, ok := args[0].(*pbresource.ID)
|
|
if !ok {
|
|
return nil, fmt.Errorf("expected *pbresource.ID, got: %T", args[0])
|
|
}
|
|
return indexFromID(id, false), nil
|
|
}
|
|
|
|
// FromObject constructs a radix tree key from a Resource at write-time, or an
|
|
// ID at delete-time.
|
|
func (i idIndexer) FromObject(raw any) (bool, []byte, error) {
|
|
switch t := raw.(type) {
|
|
case *pbresource.ID:
|
|
return true, indexFromID(t, false), nil
|
|
case *pbresource.Resource:
|
|
return true, indexFromID(t.Id, false), nil
|
|
}
|
|
return false, nil, fmt.Errorf("expected *pbresource.Resource or *pbresource.ID, got: %T", raw)
|
|
}
|
|
|
|
// PrefixFromArgs constructs a radix tree key prefix from a query for listing.
|
|
func (i idIndexer) PrefixFromArgs(args ...any) ([]byte, error) {
|
|
if l := len(args); l != 1 {
|
|
return nil, fmt.Errorf("expected 1 arg, got: %d", l)
|
|
}
|
|
|
|
q, ok := args[0].(query)
|
|
if !ok {
|
|
return nil, fmt.Errorf("expected query, got: %T", args[0])
|
|
}
|
|
return q.indexPrefix(), nil
|
|
}
|
|
|
|
// ownerIndexer implements the memdb.Indexer and memdb.SingleIndexer interfaces.
|
|
// It is used for indexing resources by their owners.
|
|
type ownerIndexer struct{}
|
|
|
|
// FromArgs constructs a radix tree key from an ID for lookup.
|
|
func (i ownerIndexer) FromArgs(args ...any) ([]byte, error) {
|
|
if l := len(args); l != 1 {
|
|
return nil, fmt.Errorf("expected 1 arg, got: %d", l)
|
|
}
|
|
id, ok := args[0].(*pbresource.ID)
|
|
if !ok {
|
|
return nil, fmt.Errorf("expected *pbresource.ID, got: %T", args[0])
|
|
}
|
|
return indexFromID(id, true), nil
|
|
}
|
|
|
|
// FromObject constructs a radix key tree from a Resource at write-time.
|
|
func (i ownerIndexer) FromObject(raw any) (bool, []byte, error) {
|
|
res, ok := raw.(*pbresource.Resource)
|
|
if !ok {
|
|
return false, nil, fmt.Errorf("expected *pbresource.Resource, got: %T", raw)
|
|
}
|
|
if res.Owner == nil {
|
|
return false, nil, nil
|
|
}
|
|
return true, indexFromID(res.Owner, true), nil
|
|
}
|
|
|
|
func indexFromType(t storage.UnversionedType) []byte {
|
|
var b indexBuilder
|
|
b.String(t.Group)
|
|
b.String(t.Kind)
|
|
return b.Bytes()
|
|
}
|
|
|
|
func indexFromTenancy(t *pbresource.Tenancy) []byte {
|
|
var b indexBuilder
|
|
b.String(t.Partition)
|
|
b.String(t.PeerName)
|
|
b.String(t.Namespace)
|
|
return b.Bytes()
|
|
}
|
|
|
|
func indexFromID(id *pbresource.ID, includeUid bool) []byte {
|
|
var b indexBuilder
|
|
b.Raw(indexFromType(storage.UnversionedTypeFrom(id.Type)))
|
|
b.Raw(indexFromTenancy(id.Tenancy))
|
|
b.String(id.Name)
|
|
if includeUid {
|
|
b.String(id.Uid)
|
|
}
|
|
return b.Bytes()
|
|
}
|
|
|
|
type indexBuilder bytes.Buffer
|
|
|
|
func (i *indexBuilder) Raw(v []byte) {
|
|
(*bytes.Buffer)(i).Write(v)
|
|
}
|
|
|
|
func (i *indexBuilder) String(s string) {
|
|
(*bytes.Buffer)(i).WriteString(s)
|
|
(*bytes.Buffer)(i).WriteString(indexSeparator)
|
|
}
|
|
|
|
func (i *indexBuilder) Bytes() []byte {
|
|
return (*bytes.Buffer)(i).Bytes()
|
|
}
|
|
|
|
type query struct {
|
|
resourceType storage.UnversionedType
|
|
tenancy *pbresource.Tenancy
|
|
namePrefix string
|
|
}
|
|
|
|
// indexPrefix is called by idIndexer.PrefixFromArgs to construct a radix tree
|
|
// key prefix for list queries.
|
|
//
|
|
// Our radix tree keys are structured like so:
|
|
//
|
|
// <type><partition><peer><namespace><name>
|
|
//
|
|
// Where each segment is followed by a NULL terminator.
|
|
//
|
|
// In order to handle wildcard queries, we return a prefix up to the wildcarded
|
|
// field. For example:
|
|
//
|
|
// Query: type={mesh,v1,service}, partition=default, peer=*, namespace=default
|
|
// Prefix: mesh[NULL]v1[NULL]service[NULL]default[NULL]
|
|
//
|
|
// Which means that we must manually apply filters after the wildcard (i.e.
|
|
// namespace in the above example) in the matches method.
|
|
func (q query) indexPrefix() []byte {
|
|
var b indexBuilder
|
|
b.Raw(indexFromType(q.resourceType))
|
|
|
|
if v := q.tenancy.Partition; v == storage.Wildcard {
|
|
return b.Bytes()
|
|
} else {
|
|
b.String(v)
|
|
}
|
|
|
|
if v := q.tenancy.PeerName; v == storage.Wildcard {
|
|
return b.Bytes()
|
|
} else {
|
|
b.String(v)
|
|
}
|
|
|
|
if v := q.tenancy.Namespace; v == storage.Wildcard {
|
|
return b.Bytes()
|
|
} else {
|
|
b.String(v)
|
|
}
|
|
|
|
if q.namePrefix != "" {
|
|
b.Raw([]byte(q.namePrefix))
|
|
}
|
|
|
|
return b.Bytes()
|
|
}
|
|
|
|
// matches applies filters that couldn't be applied by just doing a radix tree
|
|
// prefix scan, because an earlier segment of the key prefix was wildcarded.
|
|
//
|
|
// See docs on query.indexPrefix for an example.
|
|
func (q query) matches(res *pbresource.Resource) bool {
|
|
if q.tenancy.Partition != storage.Wildcard && res.Id.Tenancy.Partition != q.tenancy.Partition {
|
|
return false
|
|
}
|
|
|
|
if q.tenancy.PeerName != storage.Wildcard && res.Id.Tenancy.PeerName != q.tenancy.PeerName {
|
|
return false
|
|
}
|
|
|
|
if q.tenancy.Namespace != storage.Wildcard && res.Id.Tenancy.Namespace != q.tenancy.Namespace {
|
|
return false
|
|
}
|
|
|
|
if len(q.namePrefix) != 0 && !strings.HasPrefix(res.Id.Name, q.namePrefix) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|