/* 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 }) }