@ -134,8 +134,9 @@ type AppConnector struct {
updatePub * eventbus . Publisher [ appctype . RouteUpdate ]
storePub * eventbus . Publisher [ appctype . RouteInfo ]
// storeRoutesFunc will be called to persist routes if it is not nil.
storeRoutesFunc func ( * appctype . RouteInfo ) error
// hasStoredRoutes records whether the connector was initialized with
// persisted route information.
hasStoredRoutes bool
// mu guards the fields that follow
mu sync . Mutex
@ -168,16 +169,14 @@ type Config struct {
EventBus * eventbus . Bus
// RouteAdvertiser allows the connector to update the set of advertised routes.
// It must be non-nil.
RouteAdvertiser RouteAdvertiser
// RouteInfo, if non-nil, use used as the initial set of routes for the
// connector. If nil, the connector starts empty.
RouteInfo * appctype . RouteInfo
// StoreRoutesFunc, if non-nil, is called when the connector's routes
// change, to allow the routes to be persisted.
StoreRoutesFunc func ( * appctype . RouteInfo ) error
// HasStoredRoutes indicates that the connector should assume stored routes.
HasStoredRoutes bool
}
// NewAppConnector creates a new AppConnector.
@ -187,8 +186,6 @@ func NewAppConnector(c Config) *AppConnector {
panic ( "missing logger" )
case c . EventBus == nil :
panic ( "missing event bus" )
case c . RouteAdvertiser == nil :
panic ( "missing route advertiser" )
}
ec := c . EventBus . Client ( "appc.AppConnector" )
@ -199,7 +196,7 @@ func NewAppConnector(c Config) *AppConnector {
updatePub : eventbus . Publish [ appctype . RouteUpdate ] ( ec ) ,
storePub : eventbus . Publish [ appctype . RouteInfo ] ( ec ) ,
routeAdvertiser : c . RouteAdvertiser ,
storeRoutesFunc : c . StoreRoutesFunc ,
ha sS tored Routes: c . Has Stored Routes,
}
if c . RouteInfo != nil {
ac . domains = c . RouteInfo . Domains
@ -218,13 +215,19 @@ func NewAppConnector(c Config) *AppConnector {
// ShouldStoreRoutes returns true if the appconnector was created with the controlknob on
// and is storing its discovered routes persistently.
func ( e * AppConnector ) ShouldStoreRoutes ( ) bool {
return e . storeRoutesFunc != nil
}
func ( e * AppConnector ) ShouldStoreRoutes ( ) bool { return e . hasStoredRoutes }
// storeRoutesLocked takes the current state of the AppConnector and persists it
func ( e * AppConnector ) storeRoutesLocked ( ) error {
func ( e * AppConnector ) storeRoutesLocked ( ) {
if e . storePub . ShouldPublish ( ) {
// log write rate and write size
numRoutes := int64 ( len ( e . controlRoutes ) )
for _ , rs := range e . domains {
numRoutes += int64 ( len ( rs ) )
}
e . writeRateMinute . update ( numRoutes )
e . writeRateDay . update ( numRoutes )
e . storePub . Publish ( appctype . RouteInfo {
// Clone here, as the subscriber will handle these outside our lock.
Control : slices . Clone ( e . controlRoutes ) ,
@ -232,24 +235,6 @@ func (e *AppConnector) storeRoutesLocked() error {
Wildcards : slices . Clone ( e . wildcards ) ,
} )
}
if ! e . ShouldStoreRoutes ( ) {
return nil
}
// log write rate and write size
numRoutes := int64 ( len ( e . controlRoutes ) )
for _ , rs := range e . domains {
numRoutes += int64 ( len ( rs ) )
}
e . writeRateMinute . update ( numRoutes )
e . writeRateDay . update ( numRoutes )
// TODO(creachdair): Remove this once it's delivered over the event bus.
return e . storeRoutesFunc ( & appctype . RouteInfo {
Control : e . controlRoutes ,
Domains : e . domains ,
Wildcards : e . wildcards ,
} )
}
// ClearRoutes removes all route state from the AppConnector.
@ -259,7 +244,8 @@ func (e *AppConnector) ClearRoutes() error {
e . controlRoutes = nil
e . domains = nil
e . wildcards = nil
return e . storeRoutesLocked ( )
e . storeRoutesLocked ( )
return nil
}
// UpdateDomainsAndRoutes starts an asynchronous update of the configuration
@ -331,9 +317,9 @@ func (e *AppConnector) updateDomains(domains []string) {
}
}
// Everything left in oldDomains is a domain we're no longer tracking
// and if we are storing route info we can unadvertise the routes
if e . ShouldStoreRoutes ( ) {
// Everything left in oldDomains is a domain we're no longer tracking and we
// can unadvertise the routes.
if e . hasStoredRoutes {
toRemove := [ ] netip . Prefix { }
for _ , addrs := range oldDomains {
for _ , a := range addrs {
@ -342,11 +328,13 @@ func (e *AppConnector) updateDomains(domains []string) {
}
if len ( toRemove ) != 0 {
e . queue . Add ( func ( ) {
if err := e . routeAdvertiser . UnadvertiseRoute ( toRemove ... ) ; err != nil {
e . logf ( "failed to unadvertise routes on domain removal: %v: %v: %v" , slicesx . MapKeys ( oldDomains ) , toRemove , err )
}
} )
if ra := e . routeAdvertiser ; ra != nil {
e . queue . Add ( func ( ) {
if err := e . routeAdvertiser . UnadvertiseRoute ( toRemove ... ) ; err != nil {
e . logf ( "failed to unadvertise routes on domain removal: %v: %v: %v" , slicesx . MapKeys ( oldDomains ) , toRemove , err )
}
} )
}
e . updatePub . Publish ( appctype . RouteUpdate { Unadvertise : toRemove } )
}
}
@ -369,11 +357,10 @@ func (e *AppConnector) updateRoutes(routes []netip.Prefix) {
var toRemove [ ] netip . Prefix
// If we're storing routes and know e.controlRoutes is a good
// representation of what should be in AdvertisedRoutes we can stop
// advertising routes that used to be in e.controlRoutes but are not
// in routes.
if e . ShouldStoreRoutes ( ) {
// If we know e.controlRoutes is a good representation of what should be in
// AdvertisedRoutes we can stop advertising routes that used to be in
// e.controlRoutes but are not in routes.
if e . hasStoredRoutes {
toRemove = routesWithout ( e . controlRoutes , routes )
}
@ -390,23 +377,23 @@ nextRoute:
}
}
e . queue . Add ( func ( ) {
if err := e . routeAdvertiser . AdvertiseRoute ( routes ... ) ; err != nil {
e . logf ( "failed to advertise routes: %v: %v" , routes , err )
}
if err := e . routeAdvertiser . UnadvertiseRoute ( toRemove ... ) ; err != nil {
e . logf ( "failed to unadvertise routes: %v: %v" , toRemove , err )
}
} )
if e . routeAdvertiser != nil {
e . queue . Add ( func ( ) {
if err := e . routeAdvertiser . AdvertiseRoute ( routes ... ) ; err != nil {
e . logf ( "failed to advertise routes: %v: %v" , routes , err )
}
if err := e . routeAdvertiser . UnadvertiseRoute ( toRemove ... ) ; err != nil {
e . logf ( "failed to unadvertise routes: %v: %v" , toRemove , err )
}
} )
}
e . updatePub . Publish ( appctype . RouteUpdate {
Advertise : routes ,
Unadvertise : toRemove ,
} )
e . controlRoutes = routes
if err := e . storeRoutesLocked ( ) ; err != nil {
e . logf ( "failed to store route info: %v" , err )
}
e . storeRoutesLocked ( )
}
// Domains returns the currently configured domain list.
@ -485,9 +472,11 @@ func (e *AppConnector) isAddrKnownLocked(domain string, addr netip.Addr) bool {
// associated with the given domain.
func ( e * AppConnector ) scheduleAdvertisement ( domain string , routes ... netip . Prefix ) {
e . queue . Add ( func ( ) {
if err := e . routeAdvertiser . AdvertiseRoute ( routes ... ) ; err != nil {
e . logf ( "failed to advertise routes for %s: %v: %v" , domain , routes , err )
return
if e . routeAdvertiser != nil {
if err := e . routeAdvertiser . AdvertiseRoute ( routes ... ) ; err != nil {
e . logf ( "failed to advertise routes for %s: %v: %v" , domain , routes , err )
return
}
}
e . updatePub . Publish ( appctype . RouteUpdate { Advertise : routes } )
e . mu . Lock ( )
@ -503,9 +492,7 @@ func (e *AppConnector) scheduleAdvertisement(domain string, routes ...netip.Pref
e . logf ( "[v2] advertised route for %v: %v" , domain , addr )
}
}
if err := e . storeRoutesLocked ( ) ; err != nil {
e . logf ( "failed to store route info: %v" , err )
}
e . storeRoutesLocked ( )
} )
}