model/application.go
Rama main · réplica de lectura del repositorio original.
package model
import (
"net"
"sort"
"strconv"
"github.com/coroot/coroot/timeseries"
"github.com/coroot/coroot/utils"
"golang.org/x/exp/maps"
)
type ClusterManager string
const (
ClusterManagerCNPG ClusterManager = "cnpg"
ClusterManagerCrunchy ClusterManager = "crunchy"
ClusterManagerZalando ClusterManager = "zalando"
ClusterManagerStackGres ClusterManager = "stackgres"
ClusterManagerPerconaMongoDB ClusterManager = "percona-mongodb"
ClusterManagerPerconaXtraDB ClusterManager = "percona-xtradb"
ClusterManagerPerconaServer ClusterManager = "percona-server-mysql"
)
type ApplicationCluster struct {
Manager ClusterManager
Backups *DBBackups
}
type Application struct {
Id ApplicationId
Custom bool
Category ApplicationCategory
Cluster ApplicationCluster
Annotations ApplicationAnnotations
Instances []*Instance
instancesByName map[string][]*Instance
Downstreams map[ApplicationId]*AppToAppConnection
Upstreams map[ApplicationId]*AppToAppConnection
TrafficStats TrafficStats
DesiredInstances *timeseries.TimeSeries
LatencySLIs []*LatencySLI
AvailabilitySLIs []*AvailabilitySLI
LLMLatencySLI *LatencySLI // latency of inbound LLM inference requests, nil if the app doesn't serve them
Events []*ApplicationEvent
Deployments []*ApplicationDeployment
Incidents []*ApplicationIncident
LogMessages map[Severity]*LogMessages
Status Status
Reports []*AuditReport
Settings *ApplicationSettings
KubernetesServices []*Service
DNSRequests map[DNSRequest]map[string]*timeseries.TimeSeries
DNSRequestsHistogram map[float32]*timeseries.TimeSeries
InboundRequestsCount map[string]*timeseries.TimeSeries
InboundRequestsHistogram map[float32]*timeseries.TimeSeries
InboundLLMRequestsHistogram map[float32]*timeseries.TimeSeries
PeriodicSystemdJob bool
}
func NewApplication(id ApplicationId) *Application {
app := &Application{
Id: id,
Annotations: ApplicationAnnotations{},
instancesByName: map[string][]*Instance{},
LogMessages: map[Severity]*LogMessages{},
Upstreams: map[ApplicationId]*AppToAppConnection{},
Downstreams: map[ApplicationId]*AppToAppConnection{},
DNSRequests: map[DNSRequest]map[string]*timeseries.TimeSeries{},
DNSRequestsHistogram: map[float32]*timeseries.TimeSeries{},
InboundRequestsCount: map[string]*timeseries.TimeSeries{},
InboundRequestsHistogram: map[float32]*timeseries.TimeSeries{},
InboundLLMRequestsHistogram: map[float32]*timeseries.TimeSeries{},
}
return app
}
func (app *Application) SLOStatus() Status {
for _, r := range app.Reports {
if r.Name == AuditReportSLO {
return r.Status
}
}
return UNKNOWN
}
func (app *Application) OtelLogService(candidates []string, w *World) string {
if app.Settings != nil && app.Settings.Logs != nil {
return app.Settings.Logs.Service
}
return GuessService(candidates, w, app)
}
func (app *Application) LogQueryServices(source LogSource, otelService string) []string {
switch source {
case LogSourceOtel:
if otelService != "" {
return []string{otelService}
}
case LogSourceAgent:
return app.LogServices()
}
return nil
}
func (app *Application) LogServices() []string {
res := utils.NewStringSet()
for _, i := range app.Instances {
for _, c := range i.Containers {
res.Add(ContainerIdToServiceName(c.Id))
}
if i.Rds != nil && i.Rds.Id != "" {
res.Add("/aws/rds/" + i.Rds.Id)
}
if i.Elasticache != nil && i.Elasticache.Id != "" {
res.Add("/aws/elasticache/" + i.Elasticache.Id)
}
if i.CloudSQL != nil && i.CloudSQL.Id != "" {
res.Add("/gcp/cloudsql/" + i.CloudSQL.Id)
}
if i.Memorystore != nil && i.Memorystore.Id != "" {
res.Add("/gcp/memorystore/" + i.Memorystore.Id)
}
if i.OCIDB != nil && i.OCIDB.Id != "" {
res.Add("/oci/db/" + i.OCIDB.Id)
}
if i.OCICache != nil && i.OCICache.Id != "" {
res.Add("/oci/cache/" + i.OCICache.Id)
}
}
return res.Items()
}
func (app *Application) SimilarLogPatternHashes(hash string) []string {
res := utils.NewStringSet(hash)
for _, msgs := range app.LogMessages {
for _, pattern := range msgs.Patterns {
if similar := pattern.SimilarPatternHashes; similar != nil {
if similar.Has(hash) {
res.Add(similar.Items()...)
}
}
}
}
return res.Items()
}
func (app *Application) GetInstance(name, node string) *Instance {
for _, i := range app.instancesByName[name] {
switch app.Id.Kind {
case ApplicationKindStatefulSet:
if node == "" || i.NodeName() == "" || i.NodeName() == node {
return i
}
default:
return i
}
}
return nil
}
func (app *Application) GetOrCreateInstance(name string, node *Node) *Instance {
nodeName := ""
if node != nil {
nodeName = node.GetName()
}
instance := app.GetInstance(name, nodeName)
if instance == nil {
instance = NewInstance(name, app)
app.Instances = append(app.Instances, instance)
app.instancesByName[name] = append(app.instancesByName[name], instance)
if node != nil {
instance.Node = node
node.Instances = append(node.Instances, instance)
}
}
return instance
}
func (app *Application) Labels() Labels {
res := Labels{}
switch app.Id.Kind {
case ApplicationKindRds:
res["db"] = app.Instances[0].Rds.Engine.Value() + " (RDS)"
case ApplicationKindElasticacheCluster:
res["db"] = app.Instances[0].Elasticache.Engine.Value() + " (EC)"
case ApplicationKindCloudSQL:
res["db"] = app.Instances[0].CloudSQL.Engine.Value() + " (Cloud SQL)"
case ApplicationKindMemorystore:
res["db"] = app.Instances[0].Memorystore.Engine.Value() + " (Memorystore)"
case ApplicationKindOCIDB:
res["db"] = app.Instances[0].OCIDB.Engine.Value() + " (OCI)"
case ApplicationKindOCICache:
res["db"] = app.Instances[0].OCICache.Engine.Value() + " (OCI Cache)"
case ApplicationKindUnknown, ApplicationKindDockerSwarmService, ApplicationKindNomadJobGroup, ApplicationKindECSService:
if app.Id.Namespace != "_" {
res["ns"] = app.Id.Namespace
} else {
res["instances"] = strconv.Itoa(len(app.Instances))
}
case ApplicationKindExternalService:
eps := utils.NewStringSet()
for _, instance := range app.Instances {
for listen := range instance.TcpListens {
eps.Add(listen.IP)
}
}
if eps.Len() > 0 {
name := "external endpoint"
value := eps.Items()[0]
if eps.Len() > 1 {
name += "s"
value += ",..."
}
res[name] = value
}
default:
res["ns"] = app.Id.Namespace
}
for _, i := range app.Instances {
for _, c := range i.Containers {
for t := range c.ApplicationTypes {
if t.IsDatabase() {
res["db"] = string(t)
}
if t.IsQueue() {
res["queue"] = string(t)
}
}
}
}
return res
}
func (app *Application) IsRedis() bool {
for _, i := range app.Instances {
if i.Redis != nil {
return true
}
}
return false
}
func (app *Application) IsMongodb() bool {
for _, i := range app.Instances {
if i.Mongodb != nil {
return true
}
}
return false
}
func (app *Application) IsMysql() bool {
for _, i := range app.Instances {
if i.Mysql != nil {
return true
}
}
return false
}
func (app *Application) IsMemcached() bool {
for _, i := range app.Instances {
if i.Memcached != nil {
return true
}
}
return false
}
func (app *Application) IsPostgres() bool {
for _, i := range app.Instances {
if i.Postgres != nil {
return true
}
}
return false
}
func (app *Application) IsJvm() bool {
for _, i := range app.Instances {
if len(i.Jvms) > 0 {
return true
}
}
return false
}
func (app *Application) IsDotNet() bool {
for _, i := range app.Instances {
if len(i.DotNet) > 0 {
return true
}
}
return false
}
func (app *Application) IsPython() bool {
for _, i := range app.Instances {
if i.Python != nil {
return true
}
}
return false
}
func (app *Application) IsNodejs() bool {
for _, i := range app.Instances {
if i.Nodejs != nil {
return true
}
}
return false
}
func (app *Application) IsStandalone() bool {
for _, d := range app.Downstreams {
if d.Application != d.RemoteApplication {
return false
}
}
for _, u := range app.Upstreams {
if u.Application != u.RemoteApplication {
return false
}
}
return true
}
func (app *Application) IsDatabase() bool {
switch app.Id.Kind {
case ApplicationKindRds, ApplicationKindElasticacheCluster, ApplicationKindCloudSQL, ApplicationKindMemorystore, ApplicationKindOCIDB, ApplicationKindOCICache:
return true
}
for t := range app.ApplicationTypes() {
if t.IsDatabase() {
return true
}
}
return false
}
func (app *Application) IsQueue() bool {
for t := range app.ApplicationTypes() {
if t.IsQueue() {
return true
}
}
return false
}
func (app *Application) IsK8s() bool {
switch app.Id.Kind {
case ApplicationKindCronJob, ApplicationKindJob, ApplicationKindDeployment, ApplicationKindDaemonSet,
ApplicationKindPod, ApplicationKindReplicaSet, ApplicationKindStatefulSet, ApplicationKindStaticPods,
ApplicationKindArgoWorkflow, ApplicationKindSparkApplication, ApplicationKindGitLabJob, ApplicationKindInferenceService, ApplicationKindDynamoGraphDeployment, ApplicationKindDisaggregatedSet, ApplicationKindLeaderWorkerSet, ApplicationKindSandbox:
return true
}
for _, i := range app.Instances {
if i.Pod != nil {
return true
}
}
return false
}
func (app *Application) hasClientsInAWS() bool {
for _, d := range app.Downstreams {
for _, i := range d.Application.Instances {
if i.Node != nil {
provider := i.Node.CloudProvider.Value()
if provider == CloudProviderAWS {
return true
}
}
}
}
return false
}
func (app *Application) AddReport(name AuditReportName, widgets ...*Widget) {
app.Reports = append(app.Reports, &AuditReport{Name: name, Widgets: widgets})
}
func (app *Application) ApplicationTypes() map[ApplicationType]bool {
res := map[ApplicationType]bool{}
for _, i := range app.Instances {
for t := range i.ApplicationTypes() {
res[t] = true
}
}
if app.Id.Kind == ApplicationKindExternalService {
for _, d := range app.Downstreams {
for _, ep := range d.Endpoints.Items() {
_, port, _ := net.SplitHostPort(ep)
var t ApplicationType
switch port {
case "5432":
t = ApplicationTypePostgres
case "3306":
t = ApplicationTypeMysql
case "6379":
t = ApplicationTypeRedis
case "11211":
t = ApplicationTypeMemcached
case "27017", "27018":
t = ApplicationTypeMongodb
}
if t != "" {
res[t] = true
}
}
for p := range d.RequestsCount {
t := p.ToApplicationType()
if t == ApplicationTypeUnknown {
continue
}
res[t] = true
}
}
}
return res
}
func (app *Application) ApplicationType() ApplicationType {
types := maps.Keys(app.ApplicationTypes())
if len(types) == 0 {
return ApplicationTypeUnknown
}
if len(types) == 1 {
return types[0]
}
sort.Slice(types, func(i, j int) bool {
ti, tj := types[i], types[j]
tiw, tjw := ti.Weight(), tj.Weight()
if tiw == tjw {
return ti < tj
}
return tiw < tjw
})
return types[0]
}
func (app *Application) PeriodicJob() bool {
switch app.Id.Kind {
case ApplicationKindJob, ApplicationKindCronJob, ApplicationKindGitLabJob, ApplicationKindSandbox:
return true
}
return app.PeriodicSystemdJob
}
func (app *Application) AlertingDisabled() bool {
switch app.Id.Kind {
case ApplicationKindGitLabJob, ApplicationKindStaticPods, ApplicationKindCronJob,
ApplicationKindJob, ApplicationKindArgoWorkflow, ApplicationKindSandbox:
return true
}
return false
}
func (app *Application) WindowsApp() bool {
for _, i := range app.Instances {
if i.Node != nil && i.Node.GetOS() == OSWindows {
return true
}
}
return false
}
func (app *Application) ListensTCP() bool {
for _, i := range app.Instances {
for l := range i.TcpListens {
if l.Port != "0" {
return true
}
}
}
return false
}
func (app *Application) IsCorootComponent() bool {
for t := range app.ApplicationTypes() {
if t.IsCorootComponent() {
return true
}
}
return false
}
func (app *Application) GetCheckWithReport(checkId CheckId) (*Check, AuditReportName) {
for _, r := range app.Reports {
for _, ch := range r.Checks {
if ch.Id == checkId {
return ch, r.Name
}
}
}
return nil, ""
}