Files
ziti/controller/persistence/edge_router_store.go
T

371 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 persistence
import (
"fmt"
"github.com/michaelquigley/pfxlog"
"github.com/openziti/edge/eid"
"github.com/openziti/fabric/controller/db"
"github.com/openziti/foundation/storage/ast"
"github.com/openziti/foundation/storage/boltz"
"github.com/openziti/foundation/util/errorz"
"github.com/sirupsen/logrus"
"go.etcd.io/bbolt"
"reflect"
)
const (
FieldEdgeRouters = "edgeRouters"
FieldEdgeRouterCertPEM = "certPem"
FieldEdgeRouterIsVerified = "isVerified"
FieldEdgeRouterHostname = "hostname"
FieldEdgeRouterProtocols = "protocols"
FieldEdgeRouterEnrollments = "enrollments"
FieldEdgeRouterIsTunnelerEnabled = "isTunnelerEnabled"
FieldEdgeRouterAppData = "appData"
)
func newEdgeRouter(name string, roleAttributes ...string) *EdgeRouter {
return &EdgeRouter{
Router: db.Router{
BaseExtEntity: boltz.BaseExtEntity{Id: eid.New()},
Name: name,
},
RoleAttributes: roleAttributes,
}
}
type EdgeRouter struct {
db.Router
IsVerified bool
CertPem *string
Hostname *string
EdgeRouterProtocols map[string]string
RoleAttributes []string
Enrollments []string
IsTunnelerEnabled bool
AppData map[string]interface{}
}
func (entity *EdgeRouter) LoadValues(store boltz.CrudStore, bucket *boltz.TypedBucket) {
_, err := store.GetParentStore().BaseLoadOneById(bucket.Tx(), entity.Id, &entity.Router)
bucket.SetError(err)
entity.CertPem = bucket.GetString(FieldEdgeRouterCertPEM)
entity.IsVerified = bucket.GetBoolWithDefault(FieldEdgeRouterIsVerified, false)
entity.IsTunnelerEnabled = bucket.GetBoolWithDefault(FieldEdgeRouterIsTunnelerEnabled, entity.IsTunnelerEnabled)
//old v4, migrations only
entity.Enrollments = bucket.GetStringList(FieldEdgeRouterEnrollments)
entity.Hostname = bucket.GetString(FieldEdgeRouterHostname)
entity.EdgeRouterProtocols = toStringStringMap(bucket.GetMap(FieldEdgeRouterProtocols))
entity.RoleAttributes = bucket.GetStringList(FieldRoleAttributes)
entity.AppData = bucket.GetMap(FieldIdentityAppData)
}
func (entity *EdgeRouter) SetValues(ctx *boltz.PersistContext) {
entity.Router.SetValues(ctx.GetParentContext())
store := ctx.Store.(*edgeRouterStoreImpl)
ctx.SetStringP(FieldEdgeRouterCertPEM, entity.CertPem)
ctx.SetBool(FieldEdgeRouterIsVerified, entity.IsVerified)
ctx.SetStringP(FieldEdgeRouterHostname, entity.Hostname)
ctx.SetMap(FieldEdgeRouterProtocols, toStringInterfaceMap(entity.EdgeRouterProtocols))
ctx.SetStringList(FieldRoleAttributes, entity.RoleAttributes)
ctx.SetBool(FieldEdgeRouterIsTunnelerEnabled, entity.IsTunnelerEnabled)
ctx.Bucket.PutMap(FieldEdgeRouterAppData, entity.AppData, ctx.FieldChecker, false)
// index change won't fire if we don't have any roles on create, but we need to evaluate if we match any #all roles
if ctx.IsCreate && len(entity.RoleAttributes) == 0 {
store.rolesChanged(ctx.MutateContext, []byte(entity.Id), nil, nil, ctx.Bucket)
}
}
func (entity *EdgeRouter) GetName() string {
return entity.Name
}
type EdgeRouterStore interface {
NameIndexedStore
LoadOneById(tx *bbolt.Tx, id string) (*EdgeRouter, error)
LoadOneByName(tx *bbolt.Tx, id string) (*EdgeRouter, error)
GetRoleAttributesIndex() boltz.SetReadIndex
GetRoleAttributesCursorProvider(values []string, semantic string) (ast.SetCursorProvider, error)
}
func newEdgeRouterStore(stores *stores) *edgeRouterStoreImpl {
store := &edgeRouterStoreImpl{}
stores.Router.AddDeleteHandler(store.cleanupEdgeRouter) // do cleanup first
store.baseStore = newChildBaseStore(stores, stores.Router)
store.InitImpl(store)
return store
}
type edgeRouterStoreImpl struct {
*baseStore
indexName boltz.ReadIndex
indexRoleAttributes boltz.SetReadIndex
symbolRoleAttributes boltz.EntitySetSymbol
symbolEdgeRouterPolicies boltz.EntitySetSymbol
symbolServiceEdgeRouterPolicies boltz.EntitySetSymbol
symbolEnrollments boltz.EntitySetSymbol
symbolIdentities boltz.EntitySetSymbol
symbolServices boltz.EntitySetSymbol
identitiesCollection boltz.RefCountedLinkCollection
servicesCollection boltz.RefCountedLinkCollection
}
func (store *edgeRouterStoreImpl) NewStoreEntity() boltz.Entity {
return &EdgeRouter{}
}
func (store *edgeRouterStoreImpl) GetRoleAttributesIndex() boltz.SetReadIndex {
return store.indexRoleAttributes
}
func (store *edgeRouterStoreImpl) initializeLocal() {
store.GetParentStore().GrantSymbols(store)
store.symbolRoleAttributes = store.AddSetSymbol(FieldRoleAttributes, ast.NodeTypeString)
store.indexName = store.GetParentStore().(db.RouterStore).GetNameIndex()
store.indexRoleAttributes = store.AddSetIndex(store.symbolRoleAttributes)
store.AddSymbol(FieldEdgeRouterIsVerified, ast.NodeTypeBool)
store.AddSymbol(FieldEdgeRouterIsTunnelerEnabled, ast.NodeTypeBool)
store.symbolEnrollments = store.AddFkSetSymbol(FieldEdgeRouterEnrollments, store.stores.enrollment)
store.symbolEdgeRouterPolicies = store.AddFkSetSymbol(EntityTypeEdgeRouterPolicies, store.stores.edgeRouterPolicy)
store.symbolServiceEdgeRouterPolicies = store.AddFkSetSymbol(EntityTypeServiceEdgeRouterPolicies, store.stores.serviceEdgeRouterPolicy)
store.symbolIdentities = store.AddFkSetSymbol(EntityTypeIdentities, store.stores.identity)
store.symbolServices = store.AddFkSetSymbol(db.EntityTypeServices, store.stores.edgeService)
store.indexRoleAttributes.AddListener(store.rolesChanged)
}
func (store *edgeRouterStoreImpl) initializeLinked() {
store.AddLinkCollection(store.symbolEdgeRouterPolicies, store.stores.edgeRouterPolicy.symbolEdgeRouters)
store.AddLinkCollection(store.symbolServiceEdgeRouterPolicies, store.stores.serviceEdgeRouterPolicy.symbolEdgeRouters)
store.identitiesCollection = store.AddRefCountedLinkCollection(store.symbolIdentities, store.stores.identity.symbolEdgeRouters)
store.servicesCollection = store.AddRefCountedLinkCollection(store.symbolServices, store.stores.edgeService.symbolEdgeRouters)
store.AddConstraint(&routerIdentityConstraint{
stores: store.stores,
routerNameSymbol: store.GetSymbol(FieldName),
tunnelerEnabledSymbol: store.GetSymbol(FieldEdgeRouterIsTunnelerEnabled),
})
store.EventEmmiter.AddListener(boltz.EventUpdate, func(i ...interface{}) {
if len(i) != 1 {
return
}
router, ok := i[0].(*EdgeRouter)
if !ok {
pfxlog.Logger().Warnf("unexpected type in edge router event: %v", reflect.TypeOf(i[0]))
return
}
store.stores.DbProvider.NotifyRouterRenamed(router.Id, router.Name)
pfxlog.Logger().WithField("id", router).Debugf("notified fabric of router updated")
})
}
func (store *edgeRouterStoreImpl) rolesChanged(mutateCtx boltz.MutateContext, rowId []byte, _ []boltz.FieldTypeAndValue, new []boltz.FieldTypeAndValue, holder errorz.ErrorHolder) {
// Recalculate edge router policy links
ctx := &roleAttributeChangeContext{
tx: mutateCtx.Tx(),
rolesSymbol: store.stores.edgeRouterPolicy.symbolEdgeRouterRoles,
linkCollection: store.stores.edgeRouterPolicy.edgeRouterCollection,
relatedLinkCollection: store.stores.edgeRouterPolicy.identityCollection,
denormLinkCollection: store.identitiesCollection,
ErrorHolder: holder,
}
UpdateRelatedRoles(ctx, rowId, new, store.stores.edgeRouterPolicy.symbolSemantic)
// Recalculate service edge router policy links
ctx = &roleAttributeChangeContext{
tx: mutateCtx.Tx(),
rolesSymbol: store.stores.serviceEdgeRouterPolicy.symbolEdgeRouterRoles,
linkCollection: store.stores.serviceEdgeRouterPolicy.edgeRouterCollection,
relatedLinkCollection: store.stores.serviceEdgeRouterPolicy.serviceCollection,
denormLinkCollection: store.servicesCollection,
ErrorHolder: holder,
}
UpdateRelatedRoles(ctx, rowId, new, store.stores.serviceEdgeRouterPolicy.symbolSemantic)
}
func (store *edgeRouterStoreImpl) GetNameIndex() boltz.ReadIndex {
return store.indexName
}
func (store *edgeRouterStoreImpl) LoadOneById(tx *bbolt.Tx, id string) (*EdgeRouter, error) {
entity := &EdgeRouter{}
if err := store.baseLoadOneById(tx, id, entity); err != nil {
return nil, err
}
return entity, nil
}
func (store *edgeRouterStoreImpl) LoadOneByName(tx *bbolt.Tx, name string) (*EdgeRouter, error) {
id := store.indexName.Read(tx, []byte(name))
if id != nil {
return store.LoadOneById(tx, string(id))
}
return nil, nil
}
func (store *edgeRouterStoreImpl) cleanupEdgeRouter(ctx boltz.MutateContext, id string) error {
if entity, _ := store.LoadOneById(ctx.Tx(), id); entity != nil {
// Remove entity from EdgeRouterRoles in edge router policies
if err := store.deleteEntityReferences(ctx.Tx(), entity, store.stores.edgeRouterPolicy.symbolEdgeRouterRoles); err != nil {
return err
}
// Remove entity from EdgeRouterRoles in service edge router policies
if err := store.deleteEntityReferences(ctx.Tx(), entity, store.stores.serviceEdgeRouterPolicy.symbolEdgeRouterRoles); err != nil {
return err
}
// Remove outstanding enrollments
if err := store.stores.enrollment.DeleteWhere(ctx, fmt.Sprintf(`edgeRouter="%s"`, entity.Id)); err != nil {
return err
}
}
return nil
}
func (store *edgeRouterStoreImpl) GetRoleAttributesCursorProvider(values []string, semantic string) (ast.SetCursorProvider, error) {
return store.getRoleAttributesCursorProvider(store.indexRoleAttributes, values, semantic)
}
type routerIdentityConstraint struct {
stores *stores
routerNameSymbol boltz.EntitySymbol
tunnelerEnabledSymbol boltz.EntitySymbol
}
func (self *routerIdentityConstraint) ProcessBeforeUpdate(ctx *boltz.IndexingContext) {
if !ctx.IsCreate {
t, v := self.routerNameSymbol.Eval(ctx.Tx(), ctx.RowId)
ctx.PushState(self, t, v)
t, v = self.tunnelerEnabledSymbol.Eval(ctx.Tx(), ctx.RowId)
ctx.PushState(self, t, v)
}
}
func (self *routerIdentityConstraint) ProcessAfterUpdate(ctx *boltz.IndexingContext) {
oldName := ctx.PopStateString(self)
oldTunnelerEnabled := ctx.PopStateBool(self)
_, currentName := self.routerNameSymbol.Eval(ctx.Tx(), ctx.RowId)
currentTunnelerEnabledP := boltz.FieldToBool(self.tunnelerEnabledSymbol.Eval(ctx.Tx(), ctx.RowId))
currentTunnelerEnabled := currentTunnelerEnabledP != nil && *currentTunnelerEnabledP
name := string(currentName)
createEntities := !oldTunnelerEnabled && currentTunnelerEnabled
routerId := string(ctx.RowId)
if createEntities {
identity := &Identity{
BaseExtEntity: boltz.BaseExtEntity{
Id: string(ctx.RowId),
},
Name: name,
IdentityTypeId: RouterIdentityType,
IsDefaultAdmin: false,
IsAdmin: false,
}
ctx.ErrHolder.SetError(self.stores.identity.Create(ctx.Ctx, identity))
edgeRouterPolicy := &EdgeRouterPolicy{
BaseExtEntity: boltz.BaseExtEntity{
Id: routerId,
IsSystem: true,
},
Name: getSystemEdgeRouterPolicyName(string(ctx.RowId)),
Semantic: "AnyOf",
IdentityRoles: []string{"@" + identity.Id},
EdgeRouterRoles: []string{"@" + string(ctx.RowId)},
}
ctx.ErrHolder.SetError(self.stores.edgeRouterPolicy.Create(ctx.Ctx.GetSystemContext(), edgeRouterPolicy))
} else if oldTunnelerEnabled && !currentTunnelerEnabled {
self.deleteAssociatedEntities(ctx)
}
if !createEntities && currentTunnelerEnabled && oldName != name {
identity, err := self.stores.identity.LoadOneById(ctx.Tx(), routerId)
if err != nil {
if boltz.IsErrNotFoundErr(err) {
logrus.Errorf("identity for router with id %v not found", routerId)
} else {
ctx.ErrHolder.SetError(err)
}
return
}
if identity.IdentityTypeId != RouterIdentityType {
logrus.Errorf("identity matching router with name %v is not a router identity, not updating name", oldName)
return
}
identity.Name = name
err = self.stores.identity.Update(ctx.Ctx, identity, boltz.MapFieldChecker{
FieldName: struct{}{},
})
ctx.ErrHolder.SetError(err)
}
}
func (self *routerIdentityConstraint) ProcessBeforeDelete(ctx *boltz.IndexingContext) {
self.deleteAssociatedEntities(ctx)
}
func (self *routerIdentityConstraint) deleteAssociatedEntities(ctx *boltz.IndexingContext) {
routerId := string(ctx.RowId)
// cleanup associated auto-generated edge router policy
if self.stores.edgeRouterPolicy.IsEntityPresent(ctx.Tx(), routerId) {
ctx.ErrHolder.SetError(self.stores.edgeRouterPolicy.DeleteById(ctx.Ctx.GetSystemContext(), routerId))
}
if self.stores.identity.IsEntityPresent(ctx.Ctx.Tx(), routerId) {
_, identityType := self.stores.identity.symbolIdentityTypeId.Eval(ctx.Tx(), ctx.RowId)
if string(identityType) != RouterIdentityType {
logrus.Debugf("identity matching router with id %v is not a router identity, not deleting", routerId)
return
}
ctx.ErrHolder.SetError(self.stores.identity.DeleteById(ctx.Ctx.GetSystemContext(), routerId))
}
}
func (self *routerIdentityConstraint) Initialize(_ *bbolt.Tx, _ errorz.ErrorHolder) {}
func (self *routerIdentityConstraint) CheckIntegrity(_ *bbolt.Tx, _ bool, _ func(err error, fixed bool)) error {
return nil
}
func getSystemEdgeRouterPolicyName(edgeRouterId string) string {
return "edge-router-" + edgeRouterId + "-system"
}