Ctrl K
ComunidadGuías…

model/application.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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, ""
}