mirror of
https://github.com/status-im/consul.git
synced 2025-01-23 20:19:29 +00:00
34a32d4ce5
The peer name will eventually show up elsewhere in the resource. For now though this rips it out of where we don’t want it to be.
245 lines
6.2 KiB
Go
245 lines
6.2 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
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.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))
|
|
// TODO(peering/v2) handle peer tenancy for indexing
|
|
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><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, 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.Namespace; v == storage.Wildcard {
|
|
return b.Bytes()
|
|
} else {
|
|
b.String(v)
|
|
}
|
|
|
|
// TODO(peering/v2) handle peer tenancies
|
|
|
|
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.Namespace != storage.Wildcard && res.Id.Tenancy.Namespace != q.tenancy.Namespace {
|
|
return false
|
|
}
|
|
|
|
// TODO(peering/v2) handle peer tenancies
|
|
|
|
if len(q.namePrefix) != 0 && !strings.HasPrefix(res.Id.Name, q.namePrefix) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|