mirror of
https://github.com/openziti/ziti.git
synced 2026-10-08 05:41:20 +00:00
1664 lines
53 KiB
Go
1664 lines
53 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 sync_strats
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha1"
|
|
"crypto/tls"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/lucsky/cuid"
|
|
"github.com/michaelquigley/pfxlog"
|
|
"github.com/openziti/channel/v2"
|
|
"github.com/openziti/foundation/v2/debugz"
|
|
"github.com/openziti/foundation/v2/genext"
|
|
nfPem "github.com/openziti/foundation/v2/pem"
|
|
"github.com/openziti/storage/ast"
|
|
"github.com/openziti/storage/boltz"
|
|
"github.com/openziti/ziti/common"
|
|
"github.com/openziti/ziti/common/build"
|
|
"github.com/openziti/ziti/common/pb/edge_ctrl_pb"
|
|
"github.com/openziti/ziti/controller/change"
|
|
"github.com/openziti/ziti/controller/db"
|
|
"github.com/openziti/ziti/controller/env"
|
|
"github.com/openziti/ziti/controller/handler_edge_ctrl"
|
|
"github.com/openziti/ziti/controller/model"
|
|
"github.com/openziti/ziti/controller/network"
|
|
cmap "github.com/orcaman/concurrent-map/v2"
|
|
"github.com/pkg/errors"
|
|
"go.etcd.io/bbolt"
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
"reflect"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
RouterSyncStrategyInstant env.RouterSyncStrategyType = "instant"
|
|
ZdbIndexKey string = "index"
|
|
ZdbKey string = "zdb"
|
|
)
|
|
|
|
var _ env.RouterSyncStrategy = (*InstantStrategy)(nil)
|
|
|
|
// InstantStrategyOptions is the options for the instant strategy.
|
|
// - MaxQueuedRouterConnects - max number of router connected events to buffer
|
|
// - MaxQueuedClientHellos - max number of client hello messages to buffer
|
|
// - RouterConnectWorkerCount - max number of workers used to process router connections
|
|
// - SyncWorkerCount - max number of workers used to send api sessions/session data
|
|
// - RouterTxBufferSize - max number of messages buffered to be send to a router
|
|
// - HelloSendTimeout - the max amount of time per worker to wait to send hellos
|
|
// - SessionChunkSize - the number of sessions to send in each message
|
|
type InstantStrategyOptions struct {
|
|
MaxQueuedRouterConnects int32
|
|
MaxQueuedClientHellos int32
|
|
RouterConnectWorkerCount int32
|
|
SyncWorkerCount int32
|
|
RouterTxBufferSize int
|
|
HelloSendTimeout time.Duration
|
|
SessionChunkSize int
|
|
}
|
|
|
|
// InstantStrategy assumes that on connect, the router requires and instant
|
|
// and full set of API Sessions. Send individual create, update, delete events for sessions after synchronization.
|
|
//
|
|
// This strategy uses a series of queues and workers to managed synchronization state. The order of events is as follows:
|
|
// 1. An edge router connects to the controller, triggering RouterConnected()
|
|
// 2. A RouterSender is created encapsulating the Edge Router, Router, and Sync State
|
|
// 3. The RouterSender is queued on the routerConnectedQueue channel which buffers up to options.MaxQueuedRouterConnects
|
|
// 4. The routerConnectedQueue is read and the edge server hello is sent
|
|
// 5. The controller waits for a client hello to be received via ReceiveClientHello message
|
|
// 6. The client hello is used to identity the RouterSender associated with the client and is queued on
|
|
// the receivedClientHelloQueue channel which buffers up to options.MaxQueuedClientHellos
|
|
// 7. A startSynchronizeWorker will pick up the RouterSender from the receivedClientHelloQueue and being to
|
|
// send data to the edge router via the RouterSender
|
|
type InstantStrategy struct {
|
|
InstantStrategyOptions
|
|
|
|
rtxMap *routerTxMap
|
|
|
|
helloHandler channel.TypedReceiveHandler
|
|
resyncHandler channel.TypedReceiveHandler
|
|
ae *env.AppEnv
|
|
|
|
routerConnectedQueue chan *RouterSender
|
|
receivedClientHelloQueue chan *RouterSender
|
|
|
|
indexProvider IndexProvider
|
|
|
|
stopNotify chan struct{}
|
|
stopped atomic.Bool
|
|
*common.RouterDataModel
|
|
servicePolicyHandler *constraintToIndexedEvents[*db.ServicePolicy]
|
|
identityHandler *constraintToIndexedEvents[*db.Identity]
|
|
postureCheckHandler *constraintToIndexedEvents[*db.PostureCheck]
|
|
serviceHandler *constraintToIndexedEvents[*db.EdgeService]
|
|
caHandler *constraintToIndexedEvents[*db.Ca]
|
|
revocationHandler *constraintToIndexedEvents[*db.Revocation]
|
|
controllerHandler *constraintToIndexedEvents[*db.Controller]
|
|
|
|
changeSetLock sync.Mutex
|
|
changeSets map[uint64]*edge_ctrl_pb.DataState_ChangeSet
|
|
}
|
|
|
|
func (strategy *InstantStrategy) AddPublicKey(cert *tls.Certificate) {
|
|
publicKey := newPublicKey(cert.Certificate[0], edge_ctrl_pb.DataState_PublicKey_X509CertDer, []edge_ctrl_pb.DataState_PublicKey_Usage{edge_ctrl_pb.DataState_PublicKey_ClientX509CertValidation, edge_ctrl_pb.DataState_PublicKey_JWTValidation})
|
|
newModel := &edge_ctrl_pb.DataState_Event_PublicKey{PublicKey: publicKey}
|
|
newEvent := &edge_ctrl_pb.DataState_Event{
|
|
Action: edge_ctrl_pb.DataState_Create,
|
|
Model: newModel,
|
|
}
|
|
|
|
strategy.HandlePublicKeyEvent(newEvent, newModel)
|
|
}
|
|
|
|
// Initialize implements RouterDataModelCache
|
|
func (strategy *InstantStrategy) Initialize(logSize uint64, bufferSize uint) error {
|
|
strategy.RouterDataModel = common.NewSenderRouterDataModel(logSize, bufferSize)
|
|
|
|
if strategy.ae.HostController.IsRaftEnabled() {
|
|
strategy.indexProvider = &RaftIndexProvider{
|
|
index: strategy.ae.GetHostController().GetRaftIndex(),
|
|
}
|
|
} else {
|
|
strategy.indexProvider = &NonHaIndexProvider{
|
|
ae: strategy.ae,
|
|
}
|
|
}
|
|
|
|
err := strategy.BuildAll()
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
go strategy.handleRouterModelEvents(strategy.RouterDataModel.NewListener())
|
|
|
|
//policy create/delete/update
|
|
strategy.servicePolicyHandler = &constraintToIndexedEvents[*db.ServicePolicy]{
|
|
indexProvider: strategy.indexProvider,
|
|
createHandler: strategy.ServicePolicyCreate,
|
|
updateHandler: strategy.ServicePolicyUpdate,
|
|
deleteHandler: strategy.ServicePolicyDelete,
|
|
}
|
|
strategy.ae.GetStores().ServicePolicy.AddEntityConstraint(strategy.servicePolicyHandler)
|
|
|
|
//identity create/delete/update
|
|
strategy.identityHandler = &constraintToIndexedEvents[*db.Identity]{
|
|
indexProvider: strategy.indexProvider,
|
|
createHandler: strategy.IdentityCreate,
|
|
updateHandler: strategy.IdentityUpdate,
|
|
deleteHandler: strategy.IdentityDelete,
|
|
}
|
|
strategy.ae.GetStores().Identity.AddEntityConstraint(strategy.identityHandler)
|
|
|
|
//posture check create/delete/update
|
|
strategy.postureCheckHandler = &constraintToIndexedEvents[*db.PostureCheck]{
|
|
indexProvider: strategy.indexProvider,
|
|
createHandler: strategy.PostureCheckCreate,
|
|
updateHandler: strategy.PostureCheckUpdate,
|
|
deleteHandler: strategy.PostureCheckDelete,
|
|
}
|
|
strategy.ae.GetStores().PostureCheck.AddEntityConstraint(strategy.postureCheckHandler)
|
|
|
|
//service create/delete/update
|
|
strategy.serviceHandler = &constraintToIndexedEvents[*db.EdgeService]{
|
|
indexProvider: strategy.indexProvider,
|
|
createHandler: strategy.ServiceCreate,
|
|
updateHandler: strategy.ServiceUpdate,
|
|
deleteHandler: strategy.ServiceDelete,
|
|
}
|
|
strategy.ae.GetStores().EdgeService.AddEntityConstraint(strategy.serviceHandler)
|
|
|
|
//ca create/delete/update
|
|
strategy.caHandler = &constraintToIndexedEvents[*db.Ca]{
|
|
indexProvider: strategy.indexProvider,
|
|
createHandler: strategy.CaCreate,
|
|
updateHandler: strategy.CaUpdate,
|
|
deleteHandler: strategy.CaDelete,
|
|
}
|
|
strategy.ae.GetStores().Ca.AddEntityConstraint(strategy.caHandler)
|
|
|
|
//revocation create/delete/update
|
|
strategy.revocationHandler = &constraintToIndexedEvents[*db.Revocation]{
|
|
indexProvider: strategy.indexProvider,
|
|
createHandler: strategy.RevocationCreate,
|
|
updateHandler: strategy.RevocationUpdate,
|
|
deleteHandler: strategy.RevocationDelete,
|
|
}
|
|
strategy.ae.GetStores().Revocation.AddEntityConstraint(strategy.revocationHandler)
|
|
|
|
strategy.controllerHandler = &constraintToIndexedEvents[*db.Controller]{
|
|
indexProvider: strategy.indexProvider,
|
|
createHandler: strategy.ControllerCreate,
|
|
updateHandler: strategy.ControllerUpdate,
|
|
}
|
|
|
|
strategy.ae.GetDbProvider().GetDb().AddTxCompleteListener(strategy.completeChangeSet)
|
|
|
|
return nil
|
|
}
|
|
|
|
func NewInstantStrategy(ae *env.AppEnv, options InstantStrategyOptions) *InstantStrategy {
|
|
if options.MaxQueuedRouterConnects <= 0 {
|
|
pfxlog.Logger().Panicf("MaxQueuedRouterConnects for InstantStrategy cannot be less than 1, got %d", options.MaxQueuedRouterConnects)
|
|
}
|
|
|
|
if options.MaxQueuedClientHellos <= 0 {
|
|
pfxlog.Logger().Panicf("MaxQueuedClientHellos for InstantStrategy cannot be less than 1, got %d", options.MaxQueuedClientHellos)
|
|
}
|
|
|
|
if options.RouterConnectWorkerCount <= 0 {
|
|
pfxlog.Logger().Panicf("RouterConnectWorkerCount for InstantStrategy cannot be less than 1, got %d", options.RouterConnectWorkerCount)
|
|
}
|
|
|
|
if options.SyncWorkerCount <= 0 {
|
|
pfxlog.Logger().Panicf("SyncWorkerCount for InstantStrategy cannot be less than 1, got %d", options.SyncWorkerCount)
|
|
}
|
|
|
|
if options.RouterTxBufferSize < 0 {
|
|
pfxlog.Logger().Panicf("RouterTxBufferSize for InstantStrategy cannot be less than 0, got %d", options.MaxQueuedRouterConnects)
|
|
}
|
|
|
|
if options.SessionChunkSize <= 0 {
|
|
pfxlog.Logger().Panicf("SessionChunkSize for InstantStrategy cannot be less than 1, got %d", options.SessionChunkSize)
|
|
}
|
|
|
|
strategy := &InstantStrategy{
|
|
InstantStrategyOptions: options,
|
|
rtxMap: &routerTxMap{
|
|
internalMap: cmap.New[*RouterSender](),
|
|
},
|
|
ae: ae,
|
|
routerConnectedQueue: make(chan *RouterSender, options.MaxQueuedRouterConnects),
|
|
receivedClientHelloQueue: make(chan *RouterSender, options.MaxQueuedClientHellos),
|
|
RouterDataModel: common.NewSenderRouterDataModel(10000, 10000),
|
|
stopNotify: make(chan struct{}),
|
|
changeSets: map[uint64]*edge_ctrl_pb.DataState_ChangeSet{},
|
|
}
|
|
|
|
err := strategy.Initialize(10000, 1000)
|
|
|
|
if err != nil {
|
|
pfxlog.Logger().WithError(err).Fatal("could not build initial data model for router synchronization")
|
|
}
|
|
|
|
strategy.helloHandler = handler_edge_ctrl.NewHelloHandler(ae, strategy.ReceiveClientHello)
|
|
strategy.resyncHandler = handler_edge_ctrl.NewResyncHandler(ae, strategy.ReceiveResync)
|
|
|
|
for i := int32(0); i < options.RouterConnectWorkerCount; i++ {
|
|
go strategy.startHandleRouterConnectWorker()
|
|
}
|
|
|
|
for i := int32(0); i < options.SyncWorkerCount; i++ {
|
|
go strategy.startSynchronizeWorker()
|
|
}
|
|
|
|
ae.GetHostController().GetNetwork().AddInspectTarget(strategy.inspect)
|
|
|
|
return strategy
|
|
}
|
|
|
|
func (strategy *InstantStrategy) GetEdgeRouterState(id string) env.RouterStateValues {
|
|
return strategy.rtxMap.GetState(id)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) Type() env.RouterSyncStrategyType {
|
|
return RouterSyncStrategyInstant
|
|
}
|
|
|
|
func (strategy *InstantStrategy) Stop() {
|
|
if strategy.stopped.CompareAndSwap(false, true) {
|
|
close(strategy.stopNotify)
|
|
}
|
|
}
|
|
|
|
func (strategy *InstantStrategy) RouterConnected(edgeRouter *model.EdgeRouter, router *network.Router) {
|
|
log := pfxlog.Logger().WithField("sync_strategy", strategy.Type()).
|
|
WithField("routerId", router.Id).
|
|
WithField("routerName", router.Name).
|
|
WithField("routerFingerprint", *router.Fingerprint)
|
|
|
|
//connecting router has closed control channel
|
|
if router.Control.IsClosed() {
|
|
log.Errorf("connecting router has closed control channel [id: %s], ignoring", router.Id)
|
|
return
|
|
}
|
|
|
|
existingRtx := strategy.rtxMap.Get(router.Id)
|
|
|
|
//same channel, do nothing
|
|
if existingRtx != nil && existingRtx.Router.Control == router.Control {
|
|
log.Errorf("duplicate router connection detected [id: %s], channels are the same, ignoring", router.Id)
|
|
return
|
|
}
|
|
|
|
rtx := newRouterSender(edgeRouter, router, strategy.RouterTxBufferSize)
|
|
rtx.SetSyncStatus(env.RouterSyncQueued)
|
|
rtx.SetIsOnline(true)
|
|
|
|
log.WithField("syncStatus", rtx.SyncStatus()).Info("edge router connected, adding to sync routerConnectedQueue")
|
|
|
|
strategy.rtxMap.Add(router.Id, rtx)
|
|
|
|
strategy.routerConnectedQueue <- rtx
|
|
}
|
|
|
|
func (strategy *InstantStrategy) RouterDisconnected(router *network.Router) {
|
|
log := pfxlog.Logger().WithField("sync_strategy", strategy.Type()).
|
|
WithField("routerId", router.Id).
|
|
WithField("routerName", router.Name).
|
|
WithField("routerFingerprint", genext.OrDefault(router.Fingerprint))
|
|
|
|
existingRtx := strategy.rtxMap.Get(router.Id)
|
|
|
|
if existingRtx == nil {
|
|
log.Infof("edge router [%s] disconnect event, but no rtx found, ignoring", router.Id)
|
|
return
|
|
}
|
|
|
|
if existingRtx.Router.Control != router.Control && !existingRtx.Router.Control.IsClosed() {
|
|
log.Infof("edge router [%s] disconnect event, but channels do not match and existing channel is still open, ignoring", router.Id)
|
|
return
|
|
}
|
|
|
|
log.Infof("edge router [%s] disconnect event, router rtx removed", router.Id)
|
|
existingRtx.SetIsOnline(false)
|
|
strategy.rtxMap.Remove(router)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) GetReceiveHandlers() []channel.TypedReceiveHandler {
|
|
var result []channel.TypedReceiveHandler
|
|
if strategy.helloHandler != nil {
|
|
result = append(result, strategy.helloHandler)
|
|
}
|
|
if strategy.resyncHandler != nil {
|
|
result = append(result, strategy.resyncHandler)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (strategy *InstantStrategy) ApiSessionAdded(apiSession *db.ApiSession) {
|
|
logger := pfxlog.Logger().WithField("strategy", strategy.Type())
|
|
|
|
apiSessionProto, err := apiSessionToProto(strategy.ae, apiSession.Token, apiSession.IdentityId, apiSession.Id)
|
|
|
|
if err != nil {
|
|
logger.WithField("apiSessionId", apiSession.Id).
|
|
Errorf("error for individual api session added, could not convert to proto: %v", err)
|
|
return
|
|
}
|
|
|
|
logger.WithField("apiSessionId", apiSession.Id).WithField("fingerprints", apiSessionProto.CertFingerprints).Debug("adding apiSession")
|
|
|
|
state := &InstantSyncState{
|
|
Id: cuid.New(),
|
|
IsLast: true,
|
|
Sequence: 0,
|
|
}
|
|
|
|
strategy.rtxMap.Range(func(rtx *RouterSender) {
|
|
_ = strategy.sendApiSessionAdded(rtx, false, state, []*edge_ctrl_pb.ApiSession{apiSessionProto})
|
|
})
|
|
}
|
|
|
|
func (strategy *InstantStrategy) ApiSessionUpdated(apiSession *db.ApiSession, _ *db.ApiSessionCertificate) {
|
|
logger := pfxlog.Logger().WithField("strategy", strategy.Type())
|
|
|
|
apiSessionProto, err := apiSessionToProto(strategy.ae, apiSession.Token, apiSession.IdentityId, apiSession.Id)
|
|
|
|
if err != nil {
|
|
logger.WithField("apiSessionId", apiSession.Id).
|
|
Errorf("error for individual api session added, could not convert to proto: %v", err)
|
|
return
|
|
}
|
|
|
|
logger.WithField("apiSessionId", apiSession.Id).WithField("fingerprints", apiSessionProto.CertFingerprints).Debug("updating apiSession")
|
|
|
|
apiSessionAdded := &edge_ctrl_pb.ApiSessionUpdated{
|
|
ApiSessions: []*edge_ctrl_pb.ApiSession{apiSessionProto},
|
|
}
|
|
|
|
strategy.rtxMap.Range(func(rtx *RouterSender) {
|
|
content, _ := proto.Marshal(apiSessionAdded)
|
|
msg := channel.NewMessage(env.ApiSessionUpdatedType, content)
|
|
msg.Headers[env.SyncStrategyTypeHeader] = []byte(strategy.Type())
|
|
msg.Headers[env.SyncStrategyStateHeader] = nil
|
|
_ = rtx.Send(msg)
|
|
})
|
|
}
|
|
|
|
func (strategy *InstantStrategy) ApiSessionDeleted(apiSession *db.ApiSession) {
|
|
sessionRemoved := &edge_ctrl_pb.ApiSessionRemoved{
|
|
Tokens: []string{apiSession.Token},
|
|
}
|
|
|
|
strategy.rtxMap.Range(func(rtx *RouterSender) {
|
|
content, _ := proto.Marshal(sessionRemoved)
|
|
msg := channel.NewMessage(env.ApiSessionRemovedType, content)
|
|
_ = rtx.Send(msg)
|
|
})
|
|
}
|
|
|
|
func (strategy *InstantStrategy) SessionDeleted(session *db.Session) {
|
|
sessionRemoved := &edge_ctrl_pb.SessionRemoved{
|
|
Tokens: []string{session.Token},
|
|
}
|
|
|
|
strategy.rtxMap.Range(func(rtx *RouterSender) {
|
|
content, _ := proto.Marshal(sessionRemoved)
|
|
msg := channel.NewMessage(env.SessionRemovedType, content)
|
|
_ = rtx.Send(msg)
|
|
})
|
|
}
|
|
|
|
func (strategy *InstantStrategy) startHandleRouterConnectWorker() {
|
|
for {
|
|
select {
|
|
case <-strategy.stopNotify:
|
|
return
|
|
case rtx := <-strategy.routerConnectedQueue:
|
|
func() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
pfxlog.Logger().Errorf("router connect worker panic, worker recovering: %v\n%v", r, debugz.GenerateLocalStack())
|
|
rtx.SetSyncStatus(env.RouterSyncError)
|
|
rtx.logger().Errorf("panic during edge router connection, sync failed")
|
|
}
|
|
}()
|
|
strategy.hello(rtx)
|
|
}()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (strategy *InstantStrategy) startSynchronizeWorker() {
|
|
for {
|
|
select {
|
|
case <-strategy.stopNotify:
|
|
return
|
|
case rtx := <-strategy.receivedClientHelloQueue:
|
|
func() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
pfxlog.Logger().Errorf("sync worker panic, worker recovering: %v\n%v", r, debugz.GenerateLocalStack())
|
|
rtx.SetSyncStatus(env.RouterSyncError)
|
|
rtx.logger().Errorf("panic during edge router sync, sync failed")
|
|
}
|
|
}()
|
|
strategy.synchronize(rtx)
|
|
}()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (strategy *InstantStrategy) hello(rtx *RouterSender) {
|
|
logger := rtx.logger().WithField("strategy", strategy.Type())
|
|
|
|
logger.Info("edge router sync starting")
|
|
|
|
if rtx.Router.Control.IsClosed() {
|
|
rtx.SetSyncStatus(env.RouterSyncDisconnected)
|
|
logger.WithField("syncStatus", rtx.SyncStatus()).Info("edge router sync aborting, edge router disconnected before sync started")
|
|
return
|
|
}
|
|
|
|
rtx.SetSyncStatus(env.RouterSyncHello)
|
|
logger.WithField("syncStatus", rtx.SyncStatus()).Info("sending edge router hello")
|
|
strategy.sendHello(rtx)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) sendHello(rtx *RouterSender) {
|
|
logger := rtx.logger().WithField("strategy", strategy.Type())
|
|
serverVersion := build.GetBuildInfo().Version()
|
|
serverHello := &edge_ctrl_pb.ServerHello{
|
|
Version: serverVersion,
|
|
Data: map[string]string{
|
|
"instant": "true",
|
|
"routerModel": "true",
|
|
},
|
|
ByteData: map[string][]byte{},
|
|
}
|
|
|
|
buf, err := proto.Marshal(serverHello)
|
|
if err != nil {
|
|
logger.WithError(err).Error("could not marshal serverHello")
|
|
return
|
|
}
|
|
msg := channel.NewMessage(env.ServerHelloType, buf)
|
|
|
|
msg.PutBoolHeader(int32(edge_ctrl_pb.Header_RouterDataModel), true)
|
|
|
|
if err = msg.WithTimeout(strategy.HelloSendTimeout).Send(rtx.Router.Control); err != nil {
|
|
if rtx.Router.Control.IsClosed() {
|
|
rtx.SetSyncStatus(env.RouterSyncDisconnected)
|
|
rtx.logger().WithError(err).Error("timed out sending serverHello message for edge router, connection closed, giving up")
|
|
} else {
|
|
rtx.SetSyncStatus(env.RouterSyncHelloTimeout)
|
|
rtx.logger().WithError(err).Error("timed out sending serverHello message for edge router, queuing again")
|
|
go func() {
|
|
strategy.routerConnectedQueue <- rtx
|
|
}()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (strategy *InstantStrategy) ReceiveResync(routerId string, _ *edge_ctrl_pb.RequestClientReSync) {
|
|
rtx := strategy.rtxMap.Get(routerId)
|
|
|
|
if rtx == nil {
|
|
routerName := "<unable to retrieve>"
|
|
if router, _ := strategy.ae.Managers.Router.Read(routerId); router != nil {
|
|
routerName = router.Name
|
|
}
|
|
pfxlog.Logger().
|
|
WithField("strategy", strategy.Type()).
|
|
WithField("routerId", routerId).
|
|
WithField("routerName", routerName).
|
|
Error("received resync from router that is currently not tracked by the strategy, dropping resync")
|
|
return
|
|
}
|
|
|
|
rtx.SetSyncStatus(env.RouterSyncResyncWait)
|
|
|
|
rtx.logger().WithField("strategy", strategy.Type()).Info("received resync from router, queuing")
|
|
|
|
rtx.RouterModelIndex = nil
|
|
|
|
strategy.queueClientHello(rtx)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) queueClientHello(rtx *RouterSender) {
|
|
select {
|
|
case strategy.receivedClientHelloQueue <- rtx:
|
|
return
|
|
default:
|
|
}
|
|
|
|
ticker := time.NewTicker(time.Second)
|
|
defer ticker.Stop()
|
|
|
|
go func() {
|
|
for {
|
|
if ch := rtx.Router.Control; ch == nil || ch.IsClosed() {
|
|
return
|
|
}
|
|
|
|
select {
|
|
case strategy.receivedClientHelloQueue <- rtx:
|
|
return
|
|
case <-strategy.stopNotify:
|
|
return
|
|
case <-ticker.C:
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (strategy *InstantStrategy) ReceiveClientHello(routerId string, msg *channel.Message, respHello *edge_ctrl_pb.ClientHello) {
|
|
rtx := strategy.rtxMap.Get(routerId)
|
|
|
|
if rtx == nil {
|
|
routerName := "<unable to retrieve>"
|
|
if router, _ := strategy.ae.Managers.Router.Read(routerId); router != nil {
|
|
routerName = router.Name
|
|
}
|
|
pfxlog.Logger().
|
|
WithField("strategy", strategy.Type()).
|
|
WithField("routerId", routerId).
|
|
WithField("routerName", routerName).
|
|
Error("received hello from router that is currently not tracked by the strategy, dropping hello")
|
|
return
|
|
}
|
|
rtx.SetSyncStatus(env.RouterSyncHelloWait)
|
|
|
|
logger := rtx.logger().WithField("strategy", strategy.Type()).
|
|
WithField("protocols", respHello.Protocols).
|
|
WithField("protocolPorts", respHello.ProtocolPorts).
|
|
WithField("listeners", respHello.Listeners).
|
|
WithField("data", respHello.Data).
|
|
WithField("version", rtx.Router.VersionInfo.Version).
|
|
WithField("revision", rtx.Router.VersionInfo.Revision).
|
|
WithField("buildDate", rtx.Router.VersionInfo.BuildDate).
|
|
WithField("os", rtx.Router.VersionInfo.OS).
|
|
WithField("arch", rtx.Router.VersionInfo.Arch)
|
|
|
|
if supported, ok := msg.Headers.GetBoolHeader(int32(edge_ctrl_pb.Header_RouterDataModel)); ok && supported {
|
|
rtx.SupportsRouterModel = true
|
|
|
|
if index, ok := msg.Headers.GetUint64Header(int32(edge_ctrl_pb.Header_RouterDataModelIndex)); ok {
|
|
rtx.RouterModelIndex = &index
|
|
}
|
|
}
|
|
|
|
protocols := map[string]string{}
|
|
|
|
if len(respHello.Listeners) > 0 {
|
|
for _, listener := range respHello.Listeners {
|
|
protocols[listener.Advertise.Protocol] = fmt.Sprintf("%s://%s:%d", listener.Advertise.Protocol, listener.Advertise.Hostname, listener.Advertise.Port)
|
|
}
|
|
} else {
|
|
for idx, protocol := range respHello.Protocols {
|
|
if len(respHello.ProtocolPorts) > idx {
|
|
port := respHello.ProtocolPorts[idx]
|
|
ingressUrl := fmt.Sprintf("%s://%s:%s", protocol, respHello.Hostname, port)
|
|
protocols[protocol] = ingressUrl
|
|
}
|
|
}
|
|
}
|
|
|
|
rtx.SetHostname(respHello.Hostname)
|
|
rtx.SetProtocols(protocols)
|
|
rtx.SetVersionInfo(*rtx.Router.VersionInfo)
|
|
|
|
serverVersion := build.GetBuildInfo().Version()
|
|
logger.Infof("edge router sent hello with version [%s] to controller with version [%s]", respHello.Version, serverVersion)
|
|
strategy.queueClientHello(rtx)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) synchronize(rtx *RouterSender) {
|
|
defer func() {
|
|
rtx.logger().WithField("strategy", strategy.Type()).WithField("SupportsRouterModel", rtx.SupportsRouterModel).Infof("exiting synchronization, final status: %s", rtx.SyncStatus())
|
|
}()
|
|
|
|
if rtx.Router.Control.IsClosed() {
|
|
rtx.SetSyncStatus(env.RouterSyncDisconnected)
|
|
rtx.logger().WithField("strategy", strategy.Type()).WithField("SupportsRouterModel", rtx.SupportsRouterModel).Error("attempting to start synchronization with edge router, but it has disconnected")
|
|
}
|
|
|
|
rtx.SetSyncStatus(env.RouterSynInProgress)
|
|
logger := rtx.logger().WithField("strategy", strategy.Type()).WithField("SupportsRouterModel", rtx.SupportsRouterModel)
|
|
logger.Info("started synchronizing edge router")
|
|
|
|
chunkSize := 100
|
|
err := strategy.ae.GetDbProvider().GetDb().View(func(tx *bbolt.Tx) error {
|
|
var apiSessions []*edge_ctrl_pb.ApiSession
|
|
|
|
state := &InstantSyncState{
|
|
Id: cuid.New(),
|
|
IsLast: true,
|
|
Sequence: 0,
|
|
}
|
|
|
|
for cursor := strategy.ae.GetStores().ApiSession.IterateIds(tx, ast.BoolNodeTrue); cursor.IsValid(); cursor.Next() {
|
|
current := cursor.Current()
|
|
|
|
apiSession, err := strategy.ae.GetStores().ApiSession.LoadById(tx, string(current))
|
|
|
|
if err != nil {
|
|
logger.WithError(err).WithField("apiSessionId", string(current)).Errorf("error querying api session [%s]: %v", string(current), err)
|
|
continue
|
|
}
|
|
|
|
apiSessionProto, err := apiSessionToProtoWithTx(tx, strategy.ae, apiSession.Token, apiSession.IdentityId, apiSession.Id)
|
|
|
|
if err != nil {
|
|
logger.WithError(err).WithField("apiSessionId", string(current)).Errorf("error turning apiSession [%s] into proto: %v", string(current), err)
|
|
continue
|
|
}
|
|
|
|
apiSessions = append(apiSessions, apiSessionProto)
|
|
|
|
if len(apiSessions) >= chunkSize {
|
|
state.IsLast = !cursor.IsValid()
|
|
if err = strategy.sendApiSessionAdded(rtx, true, state, apiSessions); err != nil {
|
|
return err
|
|
}
|
|
|
|
state.Sequence = state.Sequence + 1
|
|
apiSessions = []*edge_ctrl_pb.ApiSession{}
|
|
}
|
|
}
|
|
|
|
if len(apiSessions) > 0 {
|
|
state.IsLast = true
|
|
if err := strategy.sendApiSessionAdded(rtx, true, state, apiSessions); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
logger.WithError(err).Error("failure synchronizing api sessions")
|
|
rtx.SetSyncStatus(env.RouterSyncError)
|
|
return
|
|
}
|
|
|
|
if rtx.SupportsRouterModel {
|
|
replayFrom := rtx.RouterModelIndex
|
|
|
|
if replayFrom != nil {
|
|
rtx.RouterModelIndex = nil
|
|
events, ok := strategy.RouterDataModel.ReplayFrom(*replayFrom)
|
|
|
|
if ok {
|
|
var err error
|
|
for _, curEvent := range events {
|
|
err = strategy.sendDataStateChangeSet(rtx, curEvent)
|
|
if err != nil {
|
|
pfxlog.Logger().WithError(err).
|
|
WithField("eventIndex", curEvent.Index).
|
|
WithField("evenType", reflect.TypeOf(curEvent).String()).
|
|
WithField("eventIsSynthetic", curEvent.IsSynthetic).
|
|
Error("could not send data state event")
|
|
}
|
|
}
|
|
|
|
var pks []*edge_ctrl_pb.DataState_PublicKey
|
|
strategy.RouterDataModel.PublicKeys.IterCb(func(_ string, v *edge_ctrl_pb.DataState_PublicKey) {
|
|
pks = append(pks, v)
|
|
})
|
|
|
|
for _, pk := range pks {
|
|
peerEvent := &edge_ctrl_pb.DataState_Event{
|
|
IsSynthetic: true,
|
|
Action: edge_ctrl_pb.DataState_Create,
|
|
Model: &edge_ctrl_pb.DataState_Event_PublicKey{
|
|
PublicKey: newPublicKey(pk.Data, pk.Format, pk.Usages),
|
|
},
|
|
}
|
|
|
|
changeSet := &edge_ctrl_pb.DataState_ChangeSet{
|
|
Changes: []*edge_ctrl_pb.DataState_Event{peerEvent},
|
|
}
|
|
|
|
err = strategy.sendDataStateChangeSet(rtx, changeSet)
|
|
|
|
if err != nil {
|
|
pfxlog.Logger().WithError(err).
|
|
WithField("evenType", reflect.TypeOf(peerEvent).String()).
|
|
WithField("eventAction", peerEvent.Action).
|
|
WithField("eventIsSynthetic", peerEvent.IsSynthetic).
|
|
Error("could not send data state event for peers")
|
|
}
|
|
}
|
|
}
|
|
|
|
// no error sync is done, if err try full state
|
|
if err == nil {
|
|
rtx.SetSyncStatus(env.RouterSyncDone)
|
|
return
|
|
}
|
|
|
|
pfxlog.Logger().WithError(err).Error("could not send events for router sync, attempting full state")
|
|
}
|
|
|
|
//full sync
|
|
dataState := strategy.RouterDataModel.GetDataState()
|
|
|
|
if dataState == nil {
|
|
return
|
|
}
|
|
dataState.EndIndex = strategy.indexProvider.CurrentIndex()
|
|
|
|
if err := strategy.sendDataState(rtx, dataState); err != nil {
|
|
logger.WithError(err).Error("failure sending full data state")
|
|
rtx.SetSyncStatus(env.RouterSyncError)
|
|
return
|
|
}
|
|
}
|
|
|
|
rtx.SetSyncStatus(env.RouterSyncDone)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) sendApiSessionAdded(rtx *RouterSender, isFullState bool, state *InstantSyncState, apiSessions []*edge_ctrl_pb.ApiSession) error {
|
|
stateBytes, _ := json.Marshal(state)
|
|
|
|
msgContent := &edge_ctrl_pb.ApiSessionAdded{
|
|
IsFullState: isFullState,
|
|
ApiSessions: apiSessions,
|
|
}
|
|
|
|
msgContentBytes, _ := proto.Marshal(msgContent)
|
|
|
|
msg := channel.NewMessage(env.ApiSessionAddedType, msgContentBytes)
|
|
|
|
msg.Headers[env.SyncStrategyTypeHeader] = []byte(strategy.Type())
|
|
msg.Headers[env.SyncStrategyStateHeader] = stateBytes
|
|
|
|
return rtx.Send(msg)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) handleRouterModelEvents(eventChannel <-chan *edge_ctrl_pb.DataState_ChangeSet) {
|
|
for {
|
|
select {
|
|
case newEvent := <-eventChannel:
|
|
strategy.rtxMap.Range(func(rtx *RouterSender) {
|
|
|
|
if !rtx.SupportsRouterModel {
|
|
return
|
|
}
|
|
|
|
err := strategy.sendDataStateChangeSet(rtx, newEvent)
|
|
|
|
if err != nil {
|
|
pfxlog.Logger().WithError(err).WithField("routerId", rtx.Router.Id).Error("error sending data state to router")
|
|
}
|
|
})
|
|
case <-strategy.ae.HostController.GetCloseNotifyChannel():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
type InstantSyncState struct {
|
|
Id string `json:"id"` //unique id for the sync attempt
|
|
IsLast bool `json:"isLast"` //
|
|
Sequence int `json:"sequence"` //increasing id from 0 per id for the
|
|
}
|
|
|
|
func (strategy *InstantStrategy) BuildServicePolicies(tx *bbolt.Tx) error {
|
|
for cursor := strategy.ae.GetStores().ServicePolicy.IterateIds(tx, ast.BoolNodeTrue); cursor.IsValid(); cursor.Next() {
|
|
currentBytes := cursor.Current()
|
|
currentId := string(currentBytes)
|
|
|
|
storeModel, err := strategy.ae.GetStores().ServicePolicy.LoadById(tx, currentId)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
servicePolicy := newServicePolicy(storeModel)
|
|
|
|
newModel := &edge_ctrl_pb.DataState_Event_ServicePolicy{ServicePolicy: servicePolicy}
|
|
newEvent := &edge_ctrl_pb.DataState_Event{
|
|
Action: edge_ctrl_pb.DataState_Create,
|
|
Model: newModel,
|
|
}
|
|
strategy.HandleServicePolicyEvent(newEvent, newModel)
|
|
|
|
result := strategy.ae.GetManagers().ServicePolicy.ListAssociatedIds(tx, storeModel.Id)
|
|
|
|
addServicesEvent := &edge_ctrl_pb.DataState_ServicePolicyChange{
|
|
PolicyId: currentId,
|
|
RelatedEntityIds: result.ServiceIds,
|
|
RelatedEntityType: edge_ctrl_pb.ServicePolicyRelatedEntityType_RelatedService,
|
|
Add: true,
|
|
}
|
|
|
|
strategy.HandleServicePolicyChange(addServicesEvent)
|
|
|
|
addIdentitiesEvent := &edge_ctrl_pb.DataState_ServicePolicyChange{
|
|
PolicyId: currentId,
|
|
RelatedEntityIds: result.IdentityIds,
|
|
RelatedEntityType: edge_ctrl_pb.ServicePolicyRelatedEntityType_RelatedIdentity,
|
|
Add: true,
|
|
}
|
|
strategy.HandleServicePolicyChange(addIdentitiesEvent)
|
|
|
|
addPostureChecksEvent := &edge_ctrl_pb.DataState_ServicePolicyChange{
|
|
PolicyId: currentId,
|
|
RelatedEntityIds: result.PostureCheckIds,
|
|
RelatedEntityType: edge_ctrl_pb.ServicePolicyRelatedEntityType_RelatedPostureCheck,
|
|
Add: true,
|
|
}
|
|
strategy.HandleServicePolicyChange(addPostureChecksEvent)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (strategy *InstantStrategy) BuildPublicKeys(tx *bbolt.Tx) error {
|
|
for cursor := strategy.ae.GetStores().Controller.IterateIds(tx, ast.BoolNodeTrue); cursor.IsValid(); cursor.Next() {
|
|
currentBytes := cursor.Current()
|
|
currentId := string(currentBytes)
|
|
|
|
storeModel, err := strategy.ae.GetStores().Controller.LoadById(tx, currentId)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
certs := nfPem.PemStringToCertificates(storeModel.CertPem)
|
|
|
|
newModel := &edge_ctrl_pb.DataState_Event_PublicKey{PublicKey: newPublicKey(certs[0].Raw, edge_ctrl_pb.DataState_PublicKey_X509CertDer, []edge_ctrl_pb.DataState_PublicKey_Usage{edge_ctrl_pb.DataState_PublicKey_JWTValidation, edge_ctrl_pb.DataState_PublicKey_ClientX509CertValidation})}
|
|
newEvent := &edge_ctrl_pb.DataState_Event{
|
|
Action: edge_ctrl_pb.DataState_Create,
|
|
Model: newModel,
|
|
}
|
|
strategy.HandlePublicKeyEvent(newEvent, newModel)
|
|
}
|
|
|
|
caPEMs := strategy.ae.Config.CaPems()
|
|
caCerts := nfPem.PemBytesToCertificates(caPEMs)
|
|
|
|
for _, caCert := range caCerts {
|
|
publicKey := newPublicKey(caCert.Raw, edge_ctrl_pb.DataState_PublicKey_X509CertDer, []edge_ctrl_pb.DataState_PublicKey_Usage{edge_ctrl_pb.DataState_PublicKey_JWTValidation, edge_ctrl_pb.DataState_PublicKey_ClientX509CertValidation})
|
|
newModel := &edge_ctrl_pb.DataState_Event_PublicKey{PublicKey: publicKey}
|
|
newEvent := &edge_ctrl_pb.DataState_Event{
|
|
Action: edge_ctrl_pb.DataState_Create,
|
|
Model: newModel,
|
|
}
|
|
|
|
strategy.HandlePublicKeyEvent(newEvent, newModel)
|
|
}
|
|
|
|
for cursor := strategy.ae.GetStores().Ca.IterateIds(tx, ast.BoolNodeTrue); cursor.IsValid(); cursor.Next() {
|
|
currentBytes := cursor.Current()
|
|
currentId := string(currentBytes)
|
|
|
|
ca, err := strategy.ae.GetStores().Ca.LoadById(tx, currentId)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
certs := nfPem.PemStringToCertificates(ca.CertPem)
|
|
|
|
if len(certs) == 0 {
|
|
continue
|
|
}
|
|
|
|
publicKey := newPublicKey(certs[0].Raw, edge_ctrl_pb.DataState_PublicKey_X509CertDer, []edge_ctrl_pb.DataState_PublicKey_Usage{edge_ctrl_pb.DataState_PublicKey_ClientX509CertValidation})
|
|
|
|
newModel := &edge_ctrl_pb.DataState_Event_PublicKey{PublicKey: publicKey}
|
|
newEvent := &edge_ctrl_pb.DataState_Event{
|
|
Action: edge_ctrl_pb.DataState_Create,
|
|
Model: newModel,
|
|
}
|
|
strategy.HandlePublicKeyEvent(newEvent, newModel)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (strategy *InstantStrategy) BuildAll() error {
|
|
err := strategy.ae.GetDbProvider().GetDb().View(func(tx *bbolt.Tx) error {
|
|
if err := strategy.BuildIdentities(tx); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := strategy.BuildServices(tx); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := strategy.BuildPostureChecks(tx); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := strategy.BuildServicePolicies(tx); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := strategy.BuildPublicKeys(tx); err != nil {
|
|
return err
|
|
}
|
|
|
|
strategy.SetCurrentIndex(strategy.indexProvider.CurrentIndex())
|
|
|
|
return nil
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
func (strategy *InstantStrategy) BuildIdentities(tx *bbolt.Tx) error {
|
|
for cursor := strategy.ae.GetStores().Identity.IterateIds(tx, ast.BoolNodeTrue); cursor.IsValid(); cursor.Next() {
|
|
currentBytes := cursor.Current()
|
|
currentId := string(currentBytes)
|
|
|
|
identity, err := newIdentityById(tx, strategy.ae, currentId)
|
|
|
|
if err != nil {
|
|
return err
|
|
|
|
}
|
|
|
|
newModel := &edge_ctrl_pb.DataState_Event_Identity{Identity: identity}
|
|
newEvent := &edge_ctrl_pb.DataState_Event{
|
|
Action: edge_ctrl_pb.DataState_Create,
|
|
Model: newModel,
|
|
}
|
|
strategy.HandleIdentityEvent(newEvent, newModel)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (strategy *InstantStrategy) BuildServices(tx *bbolt.Tx) error {
|
|
for cursor := strategy.ae.GetStores().EdgeService.IterateIds(tx, ast.BoolNodeTrue); cursor.IsValid(); cursor.Next() {
|
|
currentBytes := cursor.Current()
|
|
currentId := string(currentBytes)
|
|
|
|
service, err := newServiceById(tx, strategy.ae, currentId)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newModel := &edge_ctrl_pb.DataState_Event_Service{Service: service}
|
|
newEvent := &edge_ctrl_pb.DataState_Event{
|
|
Action: edge_ctrl_pb.DataState_Create,
|
|
Model: newModel,
|
|
}
|
|
strategy.HandleServiceEvent(newEvent, newModel)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (strategy *InstantStrategy) BuildPostureChecks(tx *bbolt.Tx) error {
|
|
for cursor := strategy.ae.GetStores().PostureCheck.IterateIds(tx, ast.BoolNodeTrue); cursor.IsValid(); cursor.Next() {
|
|
currentBytes := cursor.Current()
|
|
currentId := string(currentBytes)
|
|
|
|
postureCheck, err := newPostureCheckById(tx, strategy.ae, currentId)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newModel := &edge_ctrl_pb.DataState_Event_PostureCheck{PostureCheck: postureCheck}
|
|
newEvent := &edge_ctrl_pb.DataState_Event{
|
|
Action: edge_ctrl_pb.DataState_Create,
|
|
Model: newModel,
|
|
}
|
|
strategy.HandlePostureCheckEvent(newEvent, newModel)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func newIdentityById(tx *bbolt.Tx, ae *env.AppEnv, id string) (*edge_ctrl_pb.DataState_Identity, error) {
|
|
identityModel, err := ae.GetStores().Identity.LoadById(tx, id)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return newIdentity(identityModel), nil
|
|
}
|
|
|
|
func newIdentity(identityModel *db.Identity) *edge_ctrl_pb.DataState_Identity {
|
|
var hostingPrecedences map[string]edge_ctrl_pb.TerminatorPrecedence
|
|
if identityModel.ServiceHostingPrecedences != nil {
|
|
hostingPrecedences = map[string]edge_ctrl_pb.TerminatorPrecedence{}
|
|
for k, v := range identityModel.ServiceHostingPrecedences {
|
|
hostingPrecedences[k] = edge_ctrl_pb.GetPrecedence(v)
|
|
}
|
|
}
|
|
|
|
var hostingCosts map[string]uint32
|
|
if identityModel.ServiceHostingCosts != nil {
|
|
hostingCosts = map[string]uint32{}
|
|
for k, v := range identityModel.ServiceHostingCosts {
|
|
hostingCosts[k] = uint32(v)
|
|
}
|
|
}
|
|
|
|
var appDataJson []byte
|
|
if identityModel.AppData != nil {
|
|
var err error
|
|
appDataJson, err = json.Marshal(identityModel.AppData)
|
|
if err != nil {
|
|
pfxlog.Logger().WithError(err).Error("Failed to marshal app data")
|
|
}
|
|
}
|
|
|
|
return &edge_ctrl_pb.DataState_Identity{
|
|
Id: identityModel.Id,
|
|
Name: identityModel.Name,
|
|
DefaultHostingPrecedence: edge_ctrl_pb.GetPrecedence(identityModel.DefaultHostingPrecedence),
|
|
DefaultHostingCost: uint32(identityModel.DefaultHostingCost),
|
|
ServiceHostingPrecedences: hostingPrecedences,
|
|
ServiceHostingCosts: hostingCosts,
|
|
AppDataJson: appDataJson,
|
|
}
|
|
}
|
|
|
|
func newServicePolicy(storeModel *db.ServicePolicy) *edge_ctrl_pb.DataState_ServicePolicy {
|
|
servicePolicy := &edge_ctrl_pb.DataState_ServicePolicy{
|
|
Id: storeModel.Id,
|
|
Name: storeModel.Name,
|
|
PolicyType: edge_ctrl_pb.PolicyType(storeModel.PolicyType.Id()),
|
|
}
|
|
|
|
return servicePolicy
|
|
}
|
|
|
|
func newServiceById(tx *bbolt.Tx, ae *env.AppEnv, id string) (*edge_ctrl_pb.DataState_Service, error) {
|
|
storeModel, err := ae.GetStores().EdgeService.LoadById(tx, id)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return newService(storeModel), nil
|
|
}
|
|
|
|
func newService(storeModel *db.EdgeService) *edge_ctrl_pb.DataState_Service {
|
|
return &edge_ctrl_pb.DataState_Service{
|
|
Id: storeModel.Id,
|
|
Name: storeModel.Name,
|
|
}
|
|
}
|
|
|
|
func newPublicKey(data []byte, format edge_ctrl_pb.DataState_PublicKey_Format, usages []edge_ctrl_pb.DataState_PublicKey_Usage) *edge_ctrl_pb.DataState_PublicKey {
|
|
return &edge_ctrl_pb.DataState_PublicKey{
|
|
Data: data,
|
|
Kid: fmt.Sprintf("%x", sha1.Sum(data)),
|
|
Usages: usages,
|
|
Format: format,
|
|
}
|
|
}
|
|
|
|
func newPostureCheckById(tx *bbolt.Tx, ae *env.AppEnv, id string) (*edge_ctrl_pb.DataState_PostureCheck, error) {
|
|
postureModel, err := ae.GetStores().PostureCheck.LoadById(tx, id)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return newPostureCheck(postureModel), nil
|
|
}
|
|
|
|
func newPostureCheck(postureModel *db.PostureCheck) *edge_ctrl_pb.DataState_PostureCheck {
|
|
newVal := &edge_ctrl_pb.DataState_PostureCheck{
|
|
Id: postureModel.Id,
|
|
Name: postureModel.Name,
|
|
TypeId: postureModel.TypeId,
|
|
}
|
|
|
|
switch subType := postureModel.SubType.(type) {
|
|
case *db.PostureCheckProcess:
|
|
newVal.Subtype = &edge_ctrl_pb.DataState_PostureCheck_Process_{
|
|
Process: &edge_ctrl_pb.DataState_PostureCheck_Process{
|
|
OsType: subType.OperatingSystem,
|
|
Path: subType.Path,
|
|
Hashes: subType.Hashes,
|
|
Fingerprints: []string{subType.Fingerprint},
|
|
},
|
|
}
|
|
case *db.PostureCheckProcessMulti:
|
|
processList := &edge_ctrl_pb.DataState_PostureCheck_ProcessMulti_{
|
|
ProcessMulti: &edge_ctrl_pb.DataState_PostureCheck_ProcessMulti{
|
|
Semantic: subType.Semantic,
|
|
},
|
|
}
|
|
|
|
for _, process := range subType.Processes {
|
|
newProc := &edge_ctrl_pb.DataState_PostureCheck_Process{
|
|
OsType: process.OsType,
|
|
Path: process.Path,
|
|
Hashes: process.Hashes,
|
|
Fingerprints: process.SignerFingerprints,
|
|
}
|
|
|
|
processList.ProcessMulti.Processes = append(processList.ProcessMulti.Processes, newProc)
|
|
}
|
|
|
|
newVal.Subtype = processList
|
|
case *db.PostureCheckMfa:
|
|
newVal.Subtype = &edge_ctrl_pb.DataState_PostureCheck_Mfa_{
|
|
Mfa: &edge_ctrl_pb.DataState_PostureCheck_Mfa{
|
|
TimeoutSeconds: subType.TimeoutSeconds,
|
|
PromptOnWake: subType.PromptOnWake,
|
|
PromptOnUnlock: subType.PromptOnUnlock,
|
|
IgnoreLegacyEndpoints: subType.IgnoreLegacyEndpoints,
|
|
},
|
|
}
|
|
|
|
case *db.PostureCheckWindowsDomains:
|
|
newVal.Subtype = &edge_ctrl_pb.DataState_PostureCheck_Domains_{
|
|
Domains: &edge_ctrl_pb.DataState_PostureCheck_Domains{
|
|
Domains: subType.Domains,
|
|
},
|
|
}
|
|
case *db.PostureCheckMacAddresses:
|
|
newVal.Subtype = &edge_ctrl_pb.DataState_PostureCheck_Mac_{
|
|
Mac: &edge_ctrl_pb.DataState_PostureCheck_Mac{
|
|
MacAddresses: subType.MacAddresses,
|
|
},
|
|
}
|
|
case *db.PostureCheckOperatingSystem:
|
|
|
|
osList := &edge_ctrl_pb.DataState_PostureCheck_OsList{}
|
|
|
|
for _, os := range subType.OperatingSystems {
|
|
newOs := &edge_ctrl_pb.DataState_PostureCheck_Os{
|
|
OsType: os.OsType,
|
|
OsVersions: os.OsVersions,
|
|
}
|
|
|
|
osList.OsList = append(osList.OsList, newOs)
|
|
}
|
|
|
|
newVal.Subtype = &edge_ctrl_pb.DataState_PostureCheck_OsList_{
|
|
OsList: osList,
|
|
}
|
|
}
|
|
|
|
return newVal
|
|
}
|
|
|
|
func (strategy *InstantStrategy) ServicePolicyCreate(index uint64, servicePolicy *db.ServicePolicy) {
|
|
strategy.handleServicePolicy(index, edge_ctrl_pb.DataState_Create, servicePolicy)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) ServicePolicyUpdate(index uint64, servicePolicy *db.ServicePolicy) {
|
|
strategy.handleServicePolicy(index, edge_ctrl_pb.DataState_Update, servicePolicy)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) ServicePolicyDelete(index uint64, servicePolicy *db.ServicePolicy) {
|
|
strategy.handleServicePolicy(index, edge_ctrl_pb.DataState_Delete, servicePolicy)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) handleServicePolicy(index uint64, action edge_ctrl_pb.DataState_Action, servicePolicy *db.ServicePolicy) {
|
|
sp := newServicePolicy(servicePolicy)
|
|
|
|
strategy.addToChangeSet(index, &edge_ctrl_pb.DataState_Event{
|
|
Action: action,
|
|
Model: &edge_ctrl_pb.DataState_Event_ServicePolicy{
|
|
ServicePolicy: sp,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (strategy *InstantStrategy) IdentityCreate(index uint64, identity *db.Identity) {
|
|
strategy.handleIdentity(index, edge_ctrl_pb.DataState_Create, identity)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) IdentityUpdate(index uint64, identity *db.Identity) {
|
|
strategy.handleIdentity(index, edge_ctrl_pb.DataState_Update, identity)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) IdentityDelete(index uint64, identity *db.Identity) {
|
|
strategy.handleIdentity(index, edge_ctrl_pb.DataState_Delete, identity)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) handleIdentity(index uint64, action edge_ctrl_pb.DataState_Action, identity *db.Identity) {
|
|
id := newIdentity(identity)
|
|
|
|
strategy.addToChangeSet(index, &edge_ctrl_pb.DataState_Event{
|
|
Action: action,
|
|
Model: &edge_ctrl_pb.DataState_Event_Identity{
|
|
Identity: id,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (strategy *InstantStrategy) ServiceCreate(index uint64, service *db.EdgeService) {
|
|
strategy.handleService(index, edge_ctrl_pb.DataState_Create, service)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) ServiceUpdate(index uint64, service *db.EdgeService) {
|
|
strategy.handleService(index, edge_ctrl_pb.DataState_Update, service)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) ServiceDelete(index uint64, service *db.EdgeService) {
|
|
strategy.handleService(index, edge_ctrl_pb.DataState_Delete, service)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) handleService(index uint64, action edge_ctrl_pb.DataState_Action, service *db.EdgeService) {
|
|
svc := newService(service)
|
|
|
|
strategy.addToChangeSet(index, &edge_ctrl_pb.DataState_Event{
|
|
Action: action,
|
|
Model: &edge_ctrl_pb.DataState_Event_Service{
|
|
Service: svc,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (strategy *InstantStrategy) handlePostureCheck(index uint64, action edge_ctrl_pb.DataState_Action, postureCheck *db.PostureCheck) {
|
|
pc := newPostureCheck(postureCheck)
|
|
|
|
strategy.addToChangeSet(index, &edge_ctrl_pb.DataState_Event{
|
|
Action: action,
|
|
Model: &edge_ctrl_pb.DataState_Event_PostureCheck{
|
|
PostureCheck: pc,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (strategy *InstantStrategy) PostureCheckCreate(index uint64, postureCheck *db.PostureCheck) {
|
|
strategy.handlePostureCheck(index, edge_ctrl_pb.DataState_Create, postureCheck)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) PostureCheckUpdate(index uint64, postureCheck *db.PostureCheck) {
|
|
strategy.handlePostureCheck(index, edge_ctrl_pb.DataState_Update, postureCheck)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) PostureCheckDelete(index uint64, postureCheck *db.PostureCheck) {
|
|
strategy.handlePostureCheck(index, edge_ctrl_pb.DataState_Delete, postureCheck)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) ControllerCreate(index uint64, controller *db.Controller) {
|
|
certs := nfPem.PemStringToCertificates(controller.CertPem)
|
|
cert := certs[0]
|
|
strategy.handlePublicKey(index, edge_ctrl_pb.DataState_Create, newPublicKey(cert.Raw, edge_ctrl_pb.DataState_PublicKey_X509CertDer, []edge_ctrl_pb.DataState_PublicKey_Usage{edge_ctrl_pb.DataState_PublicKey_ClientX509CertValidation, edge_ctrl_pb.DataState_PublicKey_JWTValidation}))
|
|
}
|
|
|
|
func (strategy *InstantStrategy) ControllerUpdate(index uint64, controller *db.Controller) {
|
|
certs := nfPem.PemStringToCertificates(controller.CertPem)
|
|
cert := certs[0]
|
|
strategy.handlePublicKey(index, edge_ctrl_pb.DataState_Create, newPublicKey(cert.Raw, edge_ctrl_pb.DataState_PublicKey_X509CertDer, []edge_ctrl_pb.DataState_PublicKey_Usage{edge_ctrl_pb.DataState_PublicKey_ClientX509CertValidation, edge_ctrl_pb.DataState_PublicKey_JWTValidation}))
|
|
}
|
|
|
|
func (strategy *InstantStrategy) CaCreate(index uint64, ca *db.Ca) {
|
|
certs := nfPem.PemBytesToCertificates([]byte(ca.CertPem))
|
|
|
|
if len(certs) > 0 {
|
|
strategy.handlePublicKey(index, edge_ctrl_pb.DataState_Create, newPublicKey(certs[0].Raw, edge_ctrl_pb.DataState_PublicKey_X509CertDer, []edge_ctrl_pb.DataState_PublicKey_Usage{edge_ctrl_pb.DataState_PublicKey_ClientX509CertValidation}))
|
|
}
|
|
}
|
|
|
|
func (strategy *InstantStrategy) CaUpdate(index uint64, ca *db.Ca) {
|
|
certs := nfPem.PemBytesToCertificates([]byte(ca.CertPem))
|
|
|
|
if len(certs) > 0 {
|
|
strategy.handlePublicKey(index, edge_ctrl_pb.DataState_Update, newPublicKey(certs[0].Raw, edge_ctrl_pb.DataState_PublicKey_X509CertDer, []edge_ctrl_pb.DataState_PublicKey_Usage{edge_ctrl_pb.DataState_PublicKey_ClientX509CertValidation}))
|
|
}
|
|
}
|
|
|
|
func (strategy *InstantStrategy) CaDelete(index uint64, ca *db.Ca) {
|
|
certs := nfPem.PemBytesToCertificates([]byte(ca.CertPem))
|
|
|
|
if len(certs) > 0 {
|
|
strategy.handlePublicKey(index, edge_ctrl_pb.DataState_Delete, newPublicKey(certs[0].Raw, edge_ctrl_pb.DataState_PublicKey_X509CertDer, []edge_ctrl_pb.DataState_PublicKey_Usage{edge_ctrl_pb.DataState_PublicKey_ClientX509CertValidation}))
|
|
}
|
|
}
|
|
|
|
func (strategy *InstantStrategy) RevocationCreate(index uint64, revocation *db.Revocation) {
|
|
strategy.handleRevocation(index, edge_ctrl_pb.DataState_Create, revocation)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) RevocationUpdate(index uint64, revocation *db.Revocation) {
|
|
strategy.handleRevocation(index, edge_ctrl_pb.DataState_Create, revocation)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) RevocationDelete(index uint64, revocation *db.Revocation) {
|
|
strategy.handleRevocation(index, edge_ctrl_pb.DataState_Create, revocation)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) handlePublicKey(index uint64, action edge_ctrl_pb.DataState_Action, publicKey *edge_ctrl_pb.DataState_PublicKey) {
|
|
strategy.addToChangeSet(index, &edge_ctrl_pb.DataState_Event{
|
|
Action: action,
|
|
Model: &edge_ctrl_pb.DataState_Event_PublicKey{
|
|
PublicKey: publicKey,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (strategy *InstantStrategy) sendDataStateChangeSet(rtx *RouterSender, stateEvent *edge_ctrl_pb.DataState_ChangeSet) error {
|
|
content, err := proto.Marshal(stateEvent)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
msg := channel.NewMessage(env.DataStateChangeSetType, content)
|
|
|
|
return rtx.Send(msg)
|
|
|
|
}
|
|
|
|
func (strategy *InstantStrategy) sendDataState(rtx *RouterSender, state *edge_ctrl_pb.DataState) error {
|
|
content, err := proto.Marshal(state)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
msg := channel.NewMessage(env.DataStateType, content)
|
|
|
|
return rtx.Send(msg)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) handleRevocation(index uint64, action edge_ctrl_pb.DataState_Action, revocation *db.Revocation) {
|
|
strategy.addToChangeSet(index, &edge_ctrl_pb.DataState_Event{
|
|
Action: action,
|
|
Model: &edge_ctrl_pb.DataState_Event_Revocation{
|
|
Revocation: &edge_ctrl_pb.DataState_Revocation{
|
|
Id: revocation.Id,
|
|
ExpiresAt: timestamppb.New(revocation.ExpiresAt),
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func (strategy *InstantStrategy) addToChangeSet(index uint64, event *edge_ctrl_pb.DataState_Event) {
|
|
strategy.changeSetLock.Lock()
|
|
defer strategy.changeSetLock.Unlock()
|
|
|
|
changeSet, found := strategy.changeSets[index]
|
|
if !found {
|
|
changeSet = &edge_ctrl_pb.DataState_ChangeSet{
|
|
Index: index,
|
|
}
|
|
strategy.changeSets[index] = changeSet
|
|
}
|
|
changeSet.Changes = append(changeSet.Changes, event)
|
|
}
|
|
|
|
func (strategy *InstantStrategy) completeChangeSet(ctx boltz.MutateContext) {
|
|
strategy.changeSetLock.Lock()
|
|
defer strategy.changeSetLock.Unlock()
|
|
|
|
indexPtr := strategy.indexProvider.ContextIndex(ctx)
|
|
if indexPtr == nil {
|
|
return
|
|
}
|
|
index := *indexPtr
|
|
changeSet := strategy.changeSets[index]
|
|
|
|
for k := range strategy.changeSets {
|
|
if k <= index {
|
|
delete(strategy.changeSets, k)
|
|
}
|
|
}
|
|
|
|
v := ctx.Context().Value(db.ServicePolicyEventsKey)
|
|
if v != nil {
|
|
policyEvents := v.([]*edge_ctrl_pb.DataState_ServicePolicyChange)
|
|
if len(policyEvents) > 0 {
|
|
if changeSet == nil {
|
|
changeSet = &edge_ctrl_pb.DataState_ChangeSet{
|
|
Index: index,
|
|
}
|
|
}
|
|
for _, policyEvent := range policyEvents {
|
|
changeSet.Changes = append(changeSet.Changes, &edge_ctrl_pb.DataState_Event{
|
|
Action: 0,
|
|
Model: &edge_ctrl_pb.DataState_Event_ServicePolicyChange{
|
|
ServicePolicyChange: policyEvent,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
if changeSet != nil {
|
|
strategy.ApplyChangeSet(changeSet)
|
|
}
|
|
}
|
|
|
|
func (strategy *InstantStrategy) inspect(val string) (bool, *string, error) {
|
|
if val == "router-data-model" {
|
|
rdm := strategy.RouterDataModel
|
|
js, err := json.Marshal(rdm)
|
|
if err != nil {
|
|
return true, nil, err
|
|
}
|
|
result := string(js)
|
|
return true, &result, nil
|
|
}
|
|
return false, nil, nil
|
|
}
|
|
|
|
type IndexProvider interface {
|
|
// NextIndex provides an index for the supplied MutateContext.
|
|
NextIndex(ctx boltz.MutateContext) (uint64, error)
|
|
|
|
// CurrentIndex provides the current index
|
|
CurrentIndex() uint64
|
|
|
|
ContextIndex(ctx boltz.MutateContext) *uint64
|
|
}
|
|
|
|
type nonHahIndexKeyType string
|
|
|
|
const nonHaIndexKey = nonHahIndexKeyType("non-ha.index")
|
|
|
|
type NonHaIndexProvider struct {
|
|
ae *env.AppEnv
|
|
initialLoad sync.Once
|
|
index uint64
|
|
|
|
lock sync.Mutex
|
|
}
|
|
|
|
func (p *NonHaIndexProvider) load() {
|
|
p.lock.Lock()
|
|
defer p.lock.Unlock()
|
|
|
|
ctx := boltz.NewMutateContext(context.Background())
|
|
err := p.ae.GetDbProvider().GetDb().Update(ctx, func(ctx boltz.MutateContext) error {
|
|
zdb, err := ctx.Tx().CreateBucketIfNotExists([]byte(ZdbKey))
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
indexBytes := zdb.Get([]byte(ZdbIndexKey))
|
|
|
|
if len(indexBytes) == 8 {
|
|
p.index = binary.BigEndian.Uint64(indexBytes)
|
|
} else {
|
|
p.index = 0
|
|
indexBytes = make([]byte, 8)
|
|
binary.BigEndian.PutUint64(indexBytes, p.index)
|
|
_ = zdb.Put([]byte(ZdbIndexKey), indexBytes)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
pfxlog.Logger().WithError(err).Fatal("could not load initial index")
|
|
}
|
|
}
|
|
|
|
func (p *NonHaIndexProvider) NextIndex(ctx boltz.MutateContext) (uint64, error) {
|
|
p.initialLoad.Do(p.load)
|
|
|
|
p.lock.Lock()
|
|
defer p.lock.Unlock()
|
|
|
|
if val := ctx.Context().Value(nonHaIndexKey); val != nil {
|
|
return val.(uint64), nil
|
|
}
|
|
|
|
updateCtx := boltz.NewMutateContext(context.Background())
|
|
err := p.ae.GetDbProvider().GetDb().Update(updateCtx, func(updateCtx boltz.MutateContext) error {
|
|
zdb := updateCtx.Tx().Bucket([]byte(ZdbKey))
|
|
|
|
newIndex := p.index + 1
|
|
|
|
indexBytes := make([]byte, 8) // Create a byte slice with 8 bytes
|
|
binary.BigEndian.PutUint64(indexBytes, newIndex)
|
|
err := zdb.Put([]byte(ZdbIndexKey), indexBytes)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
p.index = newIndex
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
ctx.UpdateContext(func(ctx context.Context) context.Context {
|
|
return context.WithValue(ctx, nonHaIndexKey, p.index)
|
|
})
|
|
|
|
return p.index, nil
|
|
}
|
|
|
|
func (p *NonHaIndexProvider) CurrentIndex() uint64 {
|
|
p.initialLoad.Do(p.load)
|
|
|
|
p.lock.Lock()
|
|
defer p.lock.Unlock()
|
|
|
|
return p.index
|
|
}
|
|
|
|
func (p *NonHaIndexProvider) ContextIndex(ctx boltz.MutateContext) *uint64 {
|
|
if val := ctx.Context().Value(nonHaIndexKey); val != nil {
|
|
result, ok := val.(uint64)
|
|
if ok {
|
|
return &result
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type RaftIndexProvider struct {
|
|
index uint64
|
|
lock sync.Mutex
|
|
}
|
|
|
|
func (p *RaftIndexProvider) NextIndex(ctx boltz.MutateContext) (uint64, error) {
|
|
p.lock.Lock()
|
|
defer p.lock.Unlock()
|
|
|
|
changeCtx := change.FromContext(ctx.Context())
|
|
if changeCtx != nil {
|
|
p.index = changeCtx.RaftIndex
|
|
return changeCtx.RaftIndex, nil
|
|
}
|
|
|
|
return 0, errors.New("could not locate raft index from MutateContext")
|
|
}
|
|
|
|
func (p *RaftIndexProvider) CurrentIndex() uint64 {
|
|
p.lock.Lock()
|
|
defer p.lock.Unlock()
|
|
|
|
return p.index
|
|
}
|
|
|
|
func (p *RaftIndexProvider) ContextIndex(ctx boltz.MutateContext) *uint64 {
|
|
changeCtx := change.FromContext(ctx.Context())
|
|
if changeCtx != nil {
|
|
return &changeCtx.RaftIndex
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// constraintToIndexedEvents allows constraint events to be converted to events that provide the end state of an
|
|
// entity and the index it was modified on. In non-HA scenarios, the index should be a locally tracked integer.
|
|
// In HA scenarios, it should be the index from the event store. Constraint events are used as they provide
|
|
// access to the HA raft index.
|
|
type constraintToIndexedEvents[E boltz.Entity] struct {
|
|
indexProvider IndexProvider
|
|
|
|
createHandler func(uint64, E)
|
|
updateHandler func(uint64, E)
|
|
deleteHandler func(uint64, E)
|
|
}
|
|
|
|
// ProcessPreCommit is a pass through to satisfy interface requirements.
|
|
func (h *constraintToIndexedEvents[E]) ProcessPreCommit(_ *boltz.EntityChangeState[E]) error {
|
|
return nil
|
|
}
|
|
|
|
func (h *constraintToIndexedEvents[E]) ProcessPostCommit(state *boltz.EntityChangeState[E]) {
|
|
switch state.ChangeType {
|
|
case boltz.EntityCreated:
|
|
if h.createHandler != nil {
|
|
index, err := h.indexProvider.NextIndex(state.Ctx)
|
|
|
|
if err != nil {
|
|
pfxlog.Logger().WithError(err).Errorf("could not process post commit create for %T, could not acquire index", state.FinalState)
|
|
return
|
|
}
|
|
|
|
h.createHandler(index, state.FinalState)
|
|
}
|
|
case boltz.EntityUpdated:
|
|
if h.updateHandler != nil {
|
|
index, err := h.indexProvider.NextIndex(state.Ctx)
|
|
|
|
if err != nil {
|
|
pfxlog.Logger().WithError(err).Errorf("could not process post commit update for %T, could not acquire index", state.FinalState)
|
|
return
|
|
}
|
|
|
|
h.updateHandler(index, state.FinalState)
|
|
}
|
|
case boltz.EntityDeleted:
|
|
if h.deleteHandler != nil {
|
|
index, err := h.indexProvider.NextIndex(state.Ctx)
|
|
|
|
if err != nil {
|
|
pfxlog.Logger().WithError(err).Errorf("could not process post commit delete for %T, could not acquire index", state.FinalState)
|
|
return
|
|
}
|
|
|
|
//initial state for delete has the actual value, final state is nil
|
|
h.deleteHandler(index, state.InitialState)
|
|
}
|
|
}
|
|
}
|