mirror of
https://github.com/openziti/ziti.git
synced 2026-09-21 01:53:22 +00:00
329 lines
10 KiB
Go
329 lines
10 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 network
|
|
|
|
import (
|
|
"github.com/michaelquigley/pfxlog"
|
|
"github.com/openziti/foundation/v2/goroutines"
|
|
"github.com/openziti/foundation/v2/versions"
|
|
"github.com/openziti/storage/ast"
|
|
"github.com/openziti/storage/boltz"
|
|
"github.com/openziti/ziti/common/pb/cmd_pb"
|
|
"github.com/openziti/ziti/controller/change"
|
|
"github.com/openziti/ziti/controller/command"
|
|
"github.com/openziti/ziti/controller/db"
|
|
"github.com/openziti/ziti/controller/fields"
|
|
"github.com/openziti/ziti/controller/idgen"
|
|
"github.com/openziti/ziti/controller/ioc"
|
|
"github.com/openziti/ziti/controller/models"
|
|
"go.etcd.io/bbolt"
|
|
)
|
|
|
|
const (
|
|
CreateDecoder = "CreateDecoder"
|
|
UpdateDecoder = "UpdateDecoder"
|
|
DeleteDecoder = "DeleteDecoder"
|
|
)
|
|
|
|
type Managers struct {
|
|
network *Network
|
|
db boltz.Db
|
|
stores *db.Stores
|
|
Terminators *TerminatorManager
|
|
Routers *RouterManager
|
|
Services *ServiceManager
|
|
Inspections *InspectionsManager
|
|
Command *CommandManager
|
|
Dispatcher command.Dispatcher
|
|
Registry ioc.Registry
|
|
RouterMessaging *RouterMessaging
|
|
}
|
|
|
|
func (self *Managers) getDb() boltz.Db {
|
|
return self.db
|
|
}
|
|
|
|
func (self *Managers) Dispatch(command command.Command) error {
|
|
return self.Dispatcher.Dispatch(command)
|
|
}
|
|
|
|
type creator[T models.Entity] interface {
|
|
command.EntityCreator[T]
|
|
Dispatch(cmd command.Command) error
|
|
}
|
|
|
|
type updater[T models.Entity] interface {
|
|
command.EntityUpdater[T]
|
|
Dispatch(cmd command.Command) error
|
|
}
|
|
|
|
func DispatchCreate[T models.Entity](c creator[T], entity T, ctx *change.Context) error {
|
|
if entity.GetId() == "" {
|
|
id := idgen.NewUUIDString()
|
|
entity.SetId(id)
|
|
}
|
|
|
|
cmd := &command.CreateEntityCommand[T]{
|
|
Context: ctx,
|
|
Creator: c,
|
|
Entity: entity,
|
|
}
|
|
|
|
return c.Dispatch(cmd)
|
|
}
|
|
|
|
func DispatchUpdate[T models.Entity](u updater[T], entity T, updatedFields fields.UpdatedFields, ctx *change.Context) error {
|
|
cmd := &command.UpdateEntityCommand[T]{
|
|
Context: ctx,
|
|
Updater: u,
|
|
Entity: entity,
|
|
UpdatedFields: updatedFields,
|
|
}
|
|
|
|
return u.Dispatch(cmd)
|
|
}
|
|
|
|
type createDecoderF func(cmd *cmd_pb.CreateEntityCommand) (command.Command, error)
|
|
|
|
func RegisterCreateDecoder[T models.Entity](managers *Managers, creator command.EntityCreator[T]) {
|
|
entityType := creator.GetEntityTypeId()
|
|
managers.Registry.RegisterSingleton(entityType+CreateDecoder, createDecoderF(func(cmd *cmd_pb.CreateEntityCommand) (command.Command, error) {
|
|
entity, err := creator.Unmarshall(cmd.EntityData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &command.CreateEntityCommand[T]{
|
|
Context: change.FromProtoBuf(cmd.Ctx),
|
|
Entity: entity,
|
|
Creator: creator,
|
|
Flags: cmd.Flags,
|
|
}, nil
|
|
}))
|
|
}
|
|
|
|
type updateDecoderF func(cmd *cmd_pb.UpdateEntityCommand) (command.Command, error)
|
|
|
|
func RegisterUpdateDecoder[T models.Entity](managers *Managers, updater command.EntityUpdater[T]) {
|
|
entityType := updater.GetEntityTypeId()
|
|
managers.Registry.RegisterSingleton(entityType+UpdateDecoder, updateDecoderF(func(cmd *cmd_pb.UpdateEntityCommand) (command.Command, error) {
|
|
entity, err := updater.Unmarshall(cmd.EntityData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &command.UpdateEntityCommand[T]{
|
|
Context: change.FromProtoBuf(cmd.Ctx),
|
|
Entity: entity,
|
|
Updater: updater,
|
|
UpdatedFields: fields.SliceToUpdatedFields(cmd.UpdatedFields),
|
|
Flags: cmd.Flags,
|
|
}, nil
|
|
}))
|
|
}
|
|
|
|
type deleteDecoderF func(cmd *cmd_pb.DeleteEntityCommand) (command.Command, error)
|
|
|
|
func RegisterDeleteDecoder(managers *Managers, deleter command.EntityDeleter) {
|
|
entityType := deleter.GetEntityTypeId()
|
|
managers.Registry.RegisterSingleton(entityType+DeleteDecoder, deleteDecoderF(func(cmd *cmd_pb.DeleteEntityCommand) (command.Command, error) {
|
|
return &command.DeleteEntityCommand{
|
|
Context: change.FromProtoBuf(cmd.Ctx),
|
|
Deleter: deleter,
|
|
Id: cmd.EntityId,
|
|
}, nil
|
|
}))
|
|
}
|
|
|
|
func RegisterManagerDecoder[T models.Entity](managers *Managers, ctrl command.EntityManager[T]) {
|
|
RegisterCreateDecoder[T](managers, ctrl)
|
|
RegisterUpdateDecoder[T](managers, ctrl)
|
|
RegisterDeleteDecoder(managers, ctrl)
|
|
}
|
|
|
|
func NewManagers(network *Network, dispatcher command.Dispatcher, db boltz.Db, stores *db.Stores, routerCommPool goroutines.Pool) *Managers {
|
|
result := &Managers{
|
|
network: network,
|
|
db: db,
|
|
stores: stores,
|
|
Dispatcher: dispatcher,
|
|
Registry: ioc.NewRegistry(),
|
|
}
|
|
result.Command = newCommandManager(result)
|
|
result.Terminators = newTerminatorManager(result)
|
|
result.Routers = newRouterManager(result)
|
|
result.Services = newServiceManager(result)
|
|
result.Inspections = NewInspectionsManager(network)
|
|
if result.Dispatcher == nil {
|
|
devVersion := versions.MustParseSemVer("0.0.0")
|
|
version := versions.MustParseSemVer(network.VersionProvider.Version())
|
|
result.Dispatcher = &command.LocalDispatcher{
|
|
EncodeDecodeCommands: devVersion.Equals(version),
|
|
}
|
|
}
|
|
result.Command.registerGenericCommands()
|
|
|
|
result.RouterMessaging = NewRouterMessaging(result, routerCommPool)
|
|
|
|
RegisterManagerDecoder[*Service](result, result.Services)
|
|
RegisterManagerDecoder[*Router](result, result.Routers)
|
|
RegisterManagerDecoder[*Terminator](result, result.Terminators)
|
|
RegisterCommand(result, &DeleteTerminatorsBatchCommand{}, &cmd_pb.DeleteTerminatorsBatchCommand{})
|
|
|
|
return result
|
|
}
|
|
|
|
type Controller[T models.Entity] interface {
|
|
models.EntityRetriever[T]
|
|
getManagers() *Managers
|
|
}
|
|
|
|
func newBaseEntityManager[ME models.Entity, PE boltz.ExtEntity](managers *Managers, store boltz.EntityStore[PE], newModelEntity func() ME) baseEntityManager[ME, PE] {
|
|
return baseEntityManager[ME, PE]{
|
|
BaseEntityManager: models.BaseEntityManager[PE]{
|
|
Store: store,
|
|
},
|
|
Managers: managers,
|
|
newModelEntity: newModelEntity,
|
|
}
|
|
}
|
|
|
|
type baseEntityManager[T models.Entity, PE boltz.ExtEntity] struct {
|
|
models.BaseEntityManager[PE]
|
|
*Managers
|
|
newModelEntity func() T
|
|
populateEntity func(entity T, tx *bbolt.Tx, boltEntity boltz.Entity) error
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) GetEntityTypeId() string {
|
|
// default this to the store entity type and let individual managers override it where
|
|
// needed to avoid collisions (e.g. edge service/router)
|
|
return self.GetStore().GetEntityType()
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) Delete(id string, ctx *change.Context) error {
|
|
cmd := &command.DeleteEntityCommand{
|
|
Context: ctx,
|
|
Deleter: self,
|
|
Id: id,
|
|
}
|
|
return self.Managers.Dispatch(cmd)
|
|
}
|
|
|
|
func (self *baseEntityManager[ME, PE]) ApplyDelete(cmd *command.DeleteEntityCommand, ctx boltz.MutateContext) error {
|
|
return self.db.Update(ctx, func(mutateCtx boltz.MutateContext) error {
|
|
return self.Store.DeleteById(ctx, cmd.Id)
|
|
})
|
|
}
|
|
|
|
func (ctrl *baseEntityManager[ME, PE]) BaseLoad(id string) (ME, error) {
|
|
entity := ctrl.newModelEntity()
|
|
if err := ctrl.readEntity(id, entity); err != nil {
|
|
return *new(ME), err
|
|
}
|
|
return entity, nil
|
|
}
|
|
|
|
func (ctrl *baseEntityManager[ME, PE]) BaseLoadInTx(tx *bbolt.Tx, id string) (ME, error) {
|
|
entity := ctrl.newModelEntity()
|
|
if err := ctrl.readEntityInTx(tx, id, entity); err != nil {
|
|
return *new(ME), err
|
|
}
|
|
return entity, nil
|
|
}
|
|
|
|
func (ctrl *baseEntityManager[ME, PE]) readEntity(id string, modelEntity ME) error {
|
|
return ctrl.db.View(func(tx *bbolt.Tx) error {
|
|
return ctrl.readEntityInTx(tx, id, modelEntity)
|
|
})
|
|
}
|
|
|
|
func (ctrl *baseEntityManager[ME, PE]) readEntityInTx(tx *bbolt.Tx, id string, modelEntity ME) error {
|
|
boltEntity, found, err := ctrl.GetStore().FindById(tx, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !found {
|
|
return boltz.NewNotFoundError(ctrl.GetStore().GetSingularEntityType(), "id", id)
|
|
}
|
|
|
|
return ctrl.populateEntity(modelEntity, tx, boltEntity)
|
|
}
|
|
|
|
func (ctrl *baseEntityManager[ME, PE]) BaseList(query string) (*models.EntityListResult[ME], error) {
|
|
result := &models.EntityListResult[ME]{Loader: ctrl}
|
|
err := ctrl.ListWithHandler(query, result.Collect)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (ctrl *baseEntityManager[ME, PE]) ListWithHandler(queryString string, resultHandler models.ListResultHandler) error {
|
|
return ctrl.db.View(func(tx *bbolt.Tx) error {
|
|
return ctrl.ListWithTx(tx, queryString, resultHandler)
|
|
})
|
|
}
|
|
|
|
func (ctrl *baseEntityManager[ME, PE]) BasePreparedList(query ast.Query) (*models.EntityListResult[ME], error) {
|
|
result := &models.EntityListResult[ME]{Loader: ctrl}
|
|
err := ctrl.PreparedListWithHandler(query, result.Collect)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (ctrl *baseEntityManager[ME, PE]) PreparedListWithHandler(query ast.Query, resultHandler models.ListResultHandler) error {
|
|
return ctrl.db.View(func(tx *bbolt.Tx) error {
|
|
return ctrl.PreparedListWithTx(tx, query, resultHandler)
|
|
})
|
|
}
|
|
|
|
func (ctrl *baseEntityManager[ME, PE]) PreparedListAssociatedWithHandler(id string, association string, query ast.Query, handler models.ListResultHandler) error {
|
|
return ctrl.db.View(func(tx *bbolt.Tx) error {
|
|
return ctrl.PreparedListAssociatedWithTx(tx, id, association, query, handler)
|
|
})
|
|
}
|
|
|
|
type boltEntitySource[PE boltz.ExtEntity] interface {
|
|
models.Entity
|
|
toBolt() PE
|
|
}
|
|
|
|
func (ctrl *baseEntityManager[ME, PE]) updateGeneral(ctx boltz.MutateContext, modelEntity boltEntitySource[PE], checker boltz.FieldChecker) error {
|
|
return ctrl.db.Update(ctx, func(ctx boltz.MutateContext) error {
|
|
existing, found, err := ctrl.GetStore().FindById(ctx.Tx(), modelEntity.GetId())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !found {
|
|
return boltz.NewNotFoundError(ctrl.GetStore().GetSingularEntityType(), "id", modelEntity.GetId())
|
|
}
|
|
|
|
boltEntity := modelEntity.toBolt()
|
|
|
|
if err := ctrl.ValidateNameOnUpdate(ctx, boltEntity, existing, checker); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := ctrl.GetStore().Update(ctx, boltEntity, checker); err != nil {
|
|
pfxlog.Logger().WithError(err).Errorf("could not update %v entity", ctrl.GetStore().GetEntityType())
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
}
|