mirror of https://github.com/hashicorp/consul
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
244 lines
6.2 KiB
244 lines
6.2 KiB
// 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 |
|
}
|
|
|