mirror of
https://github.com/openziti/ziti.git
synced 2026-09-10 08:45:41 +00:00
445 lines
14 KiB
Go
445 lines
14 KiB
Go
/*
|
|
Copyright NetFoundry Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package model
|
|
|
|
import (
|
|
"github.com/michaelquigley/pfxlog"
|
|
"github.com/openziti/ziti/v2/controller/storage/ast"
|
|
"github.com/openziti/ziti/v2/controller/storage/boltz"
|
|
"github.com/openziti/ziti/v2/common/eid"
|
|
"github.com/openziti/ziti/v2/controller/change"
|
|
"github.com/openziti/ziti/v2/controller/command"
|
|
"github.com/openziti/ziti/v2/controller/models"
|
|
"github.com/pkg/errors"
|
|
"go.etcd.io/bbolt"
|
|
)
|
|
|
|
const annotationsBucketName = "annotations"
|
|
|
|
type EntityManager[E models.Entity] interface {
|
|
models.EntityRetriever[E]
|
|
command.EntityDeleter
|
|
GetEnv() Env
|
|
|
|
NewModelEntity() E
|
|
readEntityInTx(tx *bbolt.Tx, id string, modelEntity E) error
|
|
}
|
|
|
|
func newBaseEntityManager[ME edgeEntity[PE], PE boltz.ExtEntity](env Env, store boltz.EntityStore[PE]) baseEntityManager[ME, PE] {
|
|
return baseEntityManager[ME, PE]{
|
|
BaseEntityManager: models.BaseEntityManager[PE]{
|
|
Store: store,
|
|
},
|
|
env: env,
|
|
}
|
|
}
|
|
|
|
type baseEntityManager[ME edgeEntity[PE], PE boltz.ExtEntity] struct {
|
|
models.BaseEntityManager[PE]
|
|
env Env
|
|
impl EntityManager[ME]
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) Dispatch(command command.Command) error {
|
|
return self.env.GetManagers().Dispatcher.Dispatch(command)
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) GetEntityTypeId() string {
|
|
// default this to the store entity type and let individual controllers override it where
|
|
// needed to avoid collisions (e.g. edge service/router)
|
|
return self.GetStore().GetEntityType()
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) GetStore() boltz.EntityStore[PE] {
|
|
return self.Store
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) GetDb() boltz.Db {
|
|
return self.env.GetDb()
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) GetEnv() Env {
|
|
return self.env
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) BaseLoad(id string) (ME, error) {
|
|
entity := self.impl.NewModelEntity()
|
|
if err := self.readEntity(id, entity); err != nil {
|
|
return *new(ME), err
|
|
}
|
|
return entity, nil
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) BaseLoadInTx(tx *bbolt.Tx, id string) (ME, error) {
|
|
entity := self.impl.NewModelEntity()
|
|
if err := self.readEntityInTx(tx, id, entity); err != nil {
|
|
return *new(ME), err
|
|
}
|
|
return entity, nil
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) BaseList(query string) (*models.EntityListResult[ME], error) {
|
|
result := &models.EntityListResult[ME]{Loader: self}
|
|
if err := self.ListWithHandler(query, result.Collect); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) BasePreparedList(query ast.Query) (*models.EntityListResult[ME], error) {
|
|
result := &models.EntityListResult[ME]{Loader: self}
|
|
if err := self.PreparedListWithHandler(query, result.Collect); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) BasePreparedListIndexed(cursorProvider ast.SetCursorProvider, query ast.Query) (*models.EntityListResult[ME], error) {
|
|
result := &models.EntityListResult[ME]{Loader: self}
|
|
if err := self.PreparedListIndexed(cursorProvider, query, result.Collect); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) PreparedListWithHandler(query ast.Query, resultHandler models.ListResultHandler) error {
|
|
return self.GetDb().View(func(tx *bbolt.Tx) error {
|
|
return self.PreparedListWithTx(tx, query, resultHandler)
|
|
})
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) PreparedListIndexed(cursorProvider ast.SetCursorProvider, query ast.Query, resultHandler models.ListResultHandler) error {
|
|
return self.GetDb().View(func(tx *bbolt.Tx) error {
|
|
return self.PreparedListIndexedWithTx(tx, cursorProvider, query, resultHandler)
|
|
})
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) PreparedListAssociatedWithHandler(id string, association string, query ast.Query, handler models.ListResultHandler) error {
|
|
return self.GetDb().View(func(tx *bbolt.Tx) error {
|
|
return self.PreparedListAssociatedWithTx(tx, id, association, query, handler)
|
|
})
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) createEntity(modelEntity edgeEntity[PE], ctx boltz.MutateContext) (string, error) {
|
|
var id string
|
|
err := self.GetDb().Update(ctx, func(ctx boltz.MutateContext) error {
|
|
var err error
|
|
id, err = self.createEntityInTx(ctx, modelEntity)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) createEntityInTx(ctx boltz.MutateContext, modelEntity edgeEntity[PE]) (string, error) {
|
|
if modelEntity == nil {
|
|
return "", errors.Errorf("can't create %v with nil value", self.Store.GetEntityType())
|
|
}
|
|
if modelEntity.GetId() == "" {
|
|
modelEntity.SetId(eid.New())
|
|
}
|
|
|
|
boltEntity, err := modelEntity.toBoltEntityForCreate(ctx.Tx(), self.env)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if err = self.ValidateNameOnCreate(ctx.Tx(), boltEntity); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if err := self.GetStore().Create(ctx, boltEntity); err != nil {
|
|
pfxlog.Logger().WithError(err).Errorf("could not create %v in bolt storage", self.GetStore().GetSingularEntityType())
|
|
return "", err
|
|
}
|
|
|
|
return modelEntity.GetId(), nil
|
|
}
|
|
|
|
//nolint:unused
|
|
func (self *baseEntityManager[ME, PE]) updateEntityBatch(modelEntity edgeEntity[PE], checker boltz.FieldChecker, changeCtx *change.Context) error {
|
|
return self.GetDb().Batch(changeCtx.NewMutateContext(), func(ctx boltz.MutateContext) error {
|
|
existing, found, err := self.GetStore().FindById(ctx.Tx(), modelEntity.GetId())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !found {
|
|
return boltz.NewNotFoundError(self.GetStore().GetSingularEntityType(), "id", modelEntity.GetId())
|
|
}
|
|
boltEntity, err := modelEntity.toBoltEntityForUpdate(ctx.Tx(), self.env, checker)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = self.ValidateNameOnUpdate(ctx, boltEntity, existing, checker); err != nil {
|
|
return nil
|
|
}
|
|
|
|
if err := self.GetStore().Update(ctx, boltEntity, checker); err != nil {
|
|
pfxlog.Logger().WithError(err).Errorf("batch: could not update %v entity", self.GetStore().GetEntityType())
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) updateEntity(modelEntity ME, checker boltz.FieldChecker, ctx boltz.MutateContext) error {
|
|
return self.GetDb().Update(ctx, func(ctx boltz.MutateContext) error {
|
|
existing, found, err := self.GetStore().FindById(ctx.Tx(), modelEntity.GetId())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !found {
|
|
return boltz.NewNotFoundError(self.GetStore().GetSingularEntityType(), "id", modelEntity.GetId())
|
|
}
|
|
|
|
boltEntity, err := modelEntity.toBoltEntityForUpdate(ctx.Tx(), self.env, checker)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = self.ValidateNameOnUpdate(ctx, boltEntity, existing, checker); err != nil {
|
|
return nil
|
|
}
|
|
|
|
if err := self.GetStore().Update(ctx, boltEntity, checker); err != nil {
|
|
pfxlog.Logger().WithError(err).Errorf("could not update %v entity", self.GetStore().GetEntityType())
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) Read(id string) (ME, error) {
|
|
modelEntity := self.impl.NewModelEntity()
|
|
err := self.GetDb().View(func(tx *bbolt.Tx) error {
|
|
return self.readEntityInTx(tx, id, modelEntity)
|
|
})
|
|
if err != nil {
|
|
return *new(ME), err
|
|
}
|
|
return modelEntity, nil
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) readInTx(tx *bbolt.Tx, id string) (ME, error) {
|
|
modelEntity := self.impl.NewModelEntity()
|
|
if err := self.readEntityInTx(tx, id, modelEntity); err != nil {
|
|
return *new(ME), err
|
|
}
|
|
return modelEntity, nil
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) IsEntityPresent(id string) (bool, error) {
|
|
result := false
|
|
err := self.GetDb().View(func(tx *bbolt.Tx) error {
|
|
result = self.Store.IsEntityPresent(tx, id)
|
|
return nil
|
|
})
|
|
return result, err
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) readEntity(id string, modelEntity ME) error {
|
|
return self.GetDb().View(func(tx *bbolt.Tx) error {
|
|
return self.readEntityInTx(tx, id, modelEntity)
|
|
})
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) readEntityInTx(tx *bbolt.Tx, id string, modelEntity ME) error {
|
|
boltEntity := self.GetStore().GetEntityStrategy().NewEntity()
|
|
found, err := self.GetStore().LoadEntity(tx, id, boltEntity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !found {
|
|
return boltz.NewNotFoundError(self.GetStore().GetSingularEntityType(), "id", id)
|
|
}
|
|
|
|
return modelEntity.fillFrom(self.env, tx, boltEntity)
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) readEntityWithIndex(name string, key []byte, index boltz.ReadIndex, modelEntity ME) error {
|
|
return self.GetDb().View(func(tx *bbolt.Tx) error {
|
|
return self.readEntityInTxWithIndex(name, tx, key, index, modelEntity)
|
|
})
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) readEntityInTxWithIndex(name string, tx *bbolt.Tx, key []byte, index boltz.ReadIndex, modelEntity ME) error {
|
|
id := index.Read(tx, key)
|
|
if id == nil {
|
|
return boltz.NewNotFoundError(self.GetStore().GetSingularEntityType(), name, string(key))
|
|
}
|
|
return self.readEntityInTx(tx, string(id), modelEntity)
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) readEntityByQuery(query string) (models.Entity, error) {
|
|
result, err := self.BaseList(query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(result.GetEntities()) > 0 {
|
|
return result.GetEntities()[0], nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) Delete(id string, ctx *change.Context) error {
|
|
cmd := &command.DeleteEntityCommand{
|
|
Context: ctx,
|
|
Deleter: self.impl, // needs to be impl, otherwise we will miss overrides to GetEntityTypeId
|
|
Id: id,
|
|
}
|
|
return self.Dispatch(cmd)
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) ApplyDelete(cmd *command.DeleteEntityCommand, ctx boltz.MutateContext) error {
|
|
return self.GetDb().Update(ctx, func(ctx boltz.MutateContext) error {
|
|
return self.Store.DeleteById(ctx, cmd.Id)
|
|
})
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) deleteEntity(id string, changeCtx *change.Context) error {
|
|
return self.GetDb().Update(changeCtx.NewMutateContext(), func(ctx boltz.MutateContext) error {
|
|
return self.GetStore().DeleteById(ctx, id)
|
|
})
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) deleteEntityBatch(ids []string, changeCtx *change.Context) error {
|
|
return self.GetDb().Update(changeCtx.NewMutateContext(), func(ctx boltz.MutateContext) error {
|
|
for _, id := range ids {
|
|
if err := self.GetStore().DeleteById(ctx, id); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) ListWithHandler(queryString string, resultHandler models.ListResultHandler) error {
|
|
return self.GetDb().View(func(tx *bbolt.Tx) error {
|
|
return self.ListWithTx(tx, queryString, resultHandler)
|
|
})
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) queryRoleAttributes(index boltz.SetReadIndex, queryString string) ([]string, *models.QueryMetaData, error) {
|
|
query, err := ast.Parse(self.env.GetStores().Index, queryString)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
var results []string
|
|
var count int64
|
|
err = self.GetDb().View(func(tx *bbolt.Tx) error {
|
|
results, count, err = self.env.GetStores().Index.QueryWithCursorC(tx, index.OpenKeyCursor, query)
|
|
return err
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
qmd := &models.QueryMetaData{
|
|
Count: count,
|
|
Limit: *query.GetLimit(),
|
|
Offset: *query.GetSkip(),
|
|
FilterableFields: self.env.GetStores().Index.GetPublicSymbols(),
|
|
}
|
|
|
|
return results, qmd, nil
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) iterateRelatedEntities(id, field string, f func(tx *bbolt.Tx, relatedId string) error) error {
|
|
return self.GetDb().View(func(tx *bbolt.Tx) error {
|
|
return self.iterateRelatedEntitiesInTx(tx, id, field, f)
|
|
})
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) iterateRelatedEntitiesInTx(tx *bbolt.Tx, id, field string, f func(tx *bbolt.Tx, relatedId string) error) error {
|
|
cursor := self.Store.GetRelatedEntitiesCursor(tx, id, field, true)
|
|
for cursor.IsValid() {
|
|
key := cursor.Current()
|
|
if err := f(tx, string(key)); err != nil {
|
|
return err
|
|
}
|
|
cursor.Next()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) Annotate(ctx boltz.MutateContext, entityId string, key, value string) error {
|
|
entityBucket := self.GetStore().GetEntityBucket(ctx.Tx(), []byte(entityId))
|
|
if entityBucket == nil {
|
|
return boltz.NewNotFoundError(self.GetStore().GetEntityType(), "id", entityId)
|
|
}
|
|
annotationsBucket := entityBucket.GetOrCreatePath(annotationsBucketName)
|
|
annotationsBucket.SetString(key, value, nil)
|
|
return annotationsBucket.GetError()
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) GetAnnotation(entityId string, key string) (*string, error) {
|
|
var result *string
|
|
err := self.GetDb().View(func(tx *bbolt.Tx) error {
|
|
entityBucket := self.GetStore().GetEntityBucket(tx, []byte(entityId))
|
|
if entityBucket == nil {
|
|
return nil
|
|
}
|
|
if annotationsBucket := entityBucket.GetPath(annotationsBucketName); annotationsBucket != nil {
|
|
result = annotationsBucket.GetString(key)
|
|
}
|
|
return nil
|
|
})
|
|
return result, err
|
|
}
|
|
|
|
type AndFieldChecker struct {
|
|
first boltz.FieldChecker
|
|
second boltz.FieldChecker
|
|
}
|
|
|
|
func (checker *AndFieldChecker) IsUpdated(field string) bool {
|
|
return checker.first.IsUpdated(field) && checker.second.IsUpdated(field)
|
|
}
|
|
|
|
type OrFieldChecker struct {
|
|
first boltz.FieldChecker
|
|
second boltz.FieldChecker
|
|
}
|
|
|
|
func NewOrFieldChecker(checker boltz.FieldChecker, fields ...string) *OrFieldChecker {
|
|
return &OrFieldChecker{first: NewFieldChecker(fields...), second: checker}
|
|
}
|
|
|
|
func (checker *OrFieldChecker) IsUpdated(field string) bool {
|
|
return checker.first.IsUpdated(field) || checker.second.IsUpdated(field)
|
|
}
|
|
|
|
func NewFieldChecker(fields ...string) boltz.FieldChecker {
|
|
result := boltz.MapFieldChecker{}
|
|
for _, field := range fields {
|
|
result[field] = struct{}{}
|
|
}
|
|
return result
|
|
}
|
|
|
|
type NotFieldChecker map[string]struct{}
|
|
|
|
func (checker NotFieldChecker) IsUpdated(field string) bool {
|
|
_, ok := checker[field]
|
|
return !ok
|
|
}
|