Files
akvorado/outlet/core/root.go
Vincent Bernat 1a7fbf66cb outlet/kafka: prevent discarding flows on shutdown
When shutting down the outlet, the core component is first shutdown, and
only after that Kafka and ClickHouse. This means we were still consuming
messages from Kafka and throwing them away. Instead, let the core
component stop the Kafka workers before shutting down itself.

Fix #2100
2025-11-17 21:54:45 +01:00

108 lines
2.8 KiB
Go

// SPDX-FileCopyrightText: 2022 Free Mobile
// SPDX-License-Identifier: AGPL-3.0-only
// Package core plumbs all the other components together.
package core
import (
"time"
"gopkg.in/tomb.v2"
"akvorado/common/daemon"
"akvorado/common/helpers/cache"
"akvorado/common/httpserver"
"akvorado/common/reporter"
"akvorado/common/schema"
"akvorado/outlet/clickhouse"
"akvorado/outlet/flow"
"akvorado/outlet/kafka"
"akvorado/outlet/metadata"
"akvorado/outlet/routing"
)
// Component represents the HTTP compomenent.
type Component struct {
r *reporter.Reporter
d *Dependencies
t tomb.Tomb
config Configuration
metrics metrics
httpFlowClients uint32 // for dumping flows
httpFlowChannel chan []byte
httpFlowFlushDelay time.Duration
classifierExporterCache *cache.Cache[exporterInfo, exporterClassification]
classifierInterfaceCache *cache.Cache[exporterAndInterfaceInfo, interfaceClassification]
classifierErrLogger reporter.Logger
}
// Dependencies define the dependencies of the HTTP component.
type Dependencies struct {
Daemon daemon.Component
Flow *flow.Component
Metadata *metadata.Component
Routing *routing.Component
Kafka kafka.Component
ClickHouse clickhouse.Component
HTTP *httpserver.Component
Schema *schema.Component
}
// New creates a new core component.
func New(r *reporter.Reporter, configuration Configuration, dependencies Dependencies) (*Component, error) {
c := Component{
r: r,
d: &dependencies,
config: configuration,
httpFlowClients: 0,
httpFlowChannel: make(chan []byte, 10),
httpFlowFlushDelay: time.Second,
classifierExporterCache: cache.New[exporterInfo, exporterClassification](),
classifierInterfaceCache: cache.New[exporterAndInterfaceInfo, interfaceClassification](),
classifierErrLogger: r.Sample(reporter.BurstSampler(10*time.Second, 3)),
}
c.d.Daemon.Track(&c.t, "outlet/core")
c.initMetrics()
return &c, nil
}
// Start starts the core component.
func (c *Component) Start() error {
c.r.Info().Msg("starting core component")
c.d.Kafka.StartWorkers(c.newWorker)
// Classifier cache expiration
c.t.Go(func() error {
for {
select {
case <-c.t.Dying():
return nil
case <-time.After(c.config.ClassifierCacheDuration):
before := time.Now().Add(-c.config.ClassifierCacheDuration)
c.classifierExporterCache.DeleteLastAccessedBefore(before)
c.classifierInterfaceCache.DeleteLastAccessedBefore(before)
}
}
})
c.d.HTTP.GinRouter.GET("/api/v0/outlet/flows", c.FlowsHTTPHandler)
return nil
}
// Stop stops the core component.
func (c *Component) Stop() error {
defer func() {
close(c.httpFlowChannel)
c.r.Info().Msg("core component stopped")
}()
c.r.Info().Msg("stopping core component")
c.d.Kafka.StopWorkers()
c.t.Kill(nil)
return c.t.Wait()
}