Skip to content

Commit 2dd7138

Browse files
committed
Refine app overview dependencies and history
1 parent 1e5a5f6 commit 2dd7138

5 files changed

Lines changed: 414 additions & 121 deletions

File tree

internal/server/applications.go

Lines changed: 151 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -167,21 +167,33 @@ type appHistoryIncident struct {
167167
}
168168

169169
// appRelationships is the structural neighborhood of an app, derived from the
170-
// topology graph: what fronts it (Services/Ingress/Routes) and what supports it
171-
// (config, autoscalers, disruption budgets). Counts where names add no value.
170+
// topology graph: what fronts it (Services/Ingress/Routes) and what supports it.
172171
type appRelationships struct {
173-
Services []string `json:"services,omitempty"`
174-
Ingresses []string `json:"ingresses,omitempty"`
175-
Routes []string `json:"routes,omitempty"`
176-
Configs int `json:"configs,omitempty"`
177-
Scalers int `json:"scalers,omitempty"`
178-
Storage int `json:"storage,omitempty"`
179-
PDBs int `json:"pdbs,omitempty"`
180-
181-
configRefs map[string]struct{}
182-
scalerRefs map[string]struct{}
183-
storageRefs map[string]struct{}
184-
pdbRefs map[string]struct{}
172+
Services []string `json:"services,omitempty"`
173+
Ingresses []string `json:"ingresses,omitempty"`
174+
Routes []string `json:"routes,omitempty"`
175+
Configs int `json:"configs,omitempty"`
176+
Scalers int `json:"scalers,omitempty"`
177+
Storage int `json:"storage,omitempty"`
178+
PDBs int `json:"pdbs,omitempty"`
179+
NetworkPolicies int `json:"networkPolicies,omitempty"`
180+
ServiceRefs []topology.ResourceRef `json:"serviceRefs,omitempty"`
181+
IngressRefs []topology.ResourceRef `json:"ingressRefs,omitempty"`
182+
RouteRefs []topology.ResourceRef `json:"routeRefs,omitempty"`
183+
ConfigRefs []topology.ResourceRef `json:"configRefs,omitempty"`
184+
ScalerRefs []topology.ResourceRef `json:"scalerRefs,omitempty"`
185+
StorageRefs []topology.ResourceRef `json:"storageRefs,omitempty"`
186+
PDBRefs []topology.ResourceRef `json:"pdbRefs,omitempty"`
187+
NetworkPolicyRefs []topology.ResourceRef `json:"networkPolicyRefs,omitempty"`
188+
189+
serviceRefs map[string]topology.ResourceRef
190+
ingressRefs map[string]topology.ResourceRef
191+
routeRefs map[string]topology.ResourceRef
192+
configRefs map[string]topology.ResourceRef
193+
scalerRefs map[string]topology.ResourceRef
194+
storageRefs map[string]topology.ResourceRef
195+
pdbRefs map[string]topology.ResourceRef
196+
networkPolicyRefs map[string]topology.ResourceRef
185197
}
186198

187199
// appEvent is a recent k8s Warning event correlated to an app's workloads/pods
@@ -685,27 +697,68 @@ func (g *appGraph) relationshipsFor(kind, ns, name string) *appRelationships {
685697
if rel == nil {
686698
return nil
687699
}
688-
out := &appRelationships{Configs: len(rel.ConfigRefs), Scalers: len(rel.Scalers), Storage: len(rel.StorageRefs), PDBs: len(rel.PDBs)}
689-
out.configRefs = refsSet(rel.ConfigRefs)
690-
out.scalerRefs = refsSet(rel.Scalers)
691-
out.storageRefs = refsSet(rel.StorageRefs)
692-
out.pdbRefs = refsSet(rel.PDBs)
693-
for _, s := range rel.Services {
694-
out.Services = append(out.Services, s.Name)
700+
out := &appRelationships{
701+
Configs: len(rel.ConfigRefs),
702+
Scalers: len(rel.Scalers),
703+
Storage: len(rel.StorageRefs),
704+
PDBs: len(rel.PDBs),
705+
NetworkPolicies: len(rel.NetworkPolicies),
706+
serviceRefs: refsByKey(rel.Services),
707+
ingressRefs: refsByKey(rel.Ingresses),
708+
routeRefs: refsByKey(appRouteRefs(rel.Routes, rel.Gateways)),
709+
configRefs: refsByKey(rel.ConfigRefs),
710+
scalerRefs: refsByKey(rel.Scalers),
711+
storageRefs: refsByKey(rel.StorageRefs),
712+
pdbRefs: refsByKey(rel.PDBs),
713+
networkPolicyRefs: refsByKey(rel.NetworkPolicies),
714+
}
715+
g.addServiceEntrypoints(out, rel.Services)
716+
if len(out.Services) == 0 && len(out.Ingresses) == 0 && len(out.Routes) == 0 &&
717+
len(out.serviceRefs) == 0 && len(out.ingressRefs) == 0 && len(out.routeRefs) == 0 &&
718+
out.Configs == 0 && out.Scalers == 0 && out.Storage == 0 && out.PDBs == 0 && out.NetworkPolicies == 0 {
719+
return nil
695720
}
696-
for _, i := range rel.Ingresses {
697-
out.Ingresses = append(out.Ingresses, i.Name)
721+
return out
722+
}
723+
724+
func (g *appGraph) addServiceEntrypoints(out *appRelationships, services []topology.ResourceRef) {
725+
if g == nil || g.topo == nil || out == nil {
726+
return
698727
}
699-
for _, r := range rel.Routes {
700-
out.Routes = append(out.Routes, r.Name)
728+
for _, svc := range services {
729+
if !strings.EqualFold(svc.Kind, "Service") {
730+
continue
731+
}
732+
rel := topology.GetRelationshipsWithIndex(svc.Kind, svc.Namespace, svc.Name, g.topo, g.provider, g.dp, g.idx)
733+
if rel == nil {
734+
continue
735+
}
736+
out.ingressRefs = mergeRefs(out.ingressRefs, refsByKey(rel.Ingresses))
737+
out.routeRefs = mergeRefs(out.routeRefs, refsByKey(appRouteRefs(rel.Routes, rel.Gateways, rel.Services)))
701738
}
702-
if len(out.Services) == 0 && len(out.Ingresses) == 0 && len(out.Routes) == 0 &&
703-
out.Configs == 0 && out.Scalers == 0 && out.Storage == 0 && out.PDBs == 0 {
704-
return nil
739+
}
740+
741+
func appRouteRefs(routes []topology.ResourceRef, routeLikeGroups ...[]topology.ResourceRef) []topology.ResourceRef {
742+
out := append([]topology.ResourceRef{}, routes...)
743+
for _, group := range routeLikeGroups {
744+
for _, ref := range group {
745+
if isAppRouteKind(ref.Kind) {
746+
out = append(out, ref)
747+
}
748+
}
705749
}
706750
return out
707751
}
708752

753+
func isAppRouteKind(kind string) bool {
754+
switch strings.ToLower(kind) {
755+
case "httproute", "grpcroute", "tcproute", "tlsroute", "route", "ingressroute", "ingressroutetcp", "ingressrouteudp", "virtualservice", "httpproxy":
756+
return true
757+
default:
758+
return false
759+
}
760+
}
761+
709762
// appWorkloadInput is the pre-grouping shape: one workload plus the signals
710763
// that decide which app it belongs to (structural root + label overlay) and how
711764
// it is classified.
@@ -1208,10 +1261,14 @@ func mergeRelationships(r *appRow, rel *appRelationships) {
12081261
agg.Services = append(agg.Services, rel.Services...)
12091262
agg.Ingresses = append(agg.Ingresses, rel.Ingresses...)
12101263
agg.Routes = append(agg.Routes, rel.Routes...)
1211-
agg.configRefs = mergeRefSets(agg.configRefs, rel.configRefs)
1212-
agg.scalerRefs = mergeRefSets(agg.scalerRefs, rel.scalerRefs)
1213-
agg.storageRefs = mergeRefSets(agg.storageRefs, rel.storageRefs)
1214-
agg.pdbRefs = mergeRefSets(agg.pdbRefs, rel.pdbRefs)
1264+
agg.serviceRefs = mergeRefs(agg.serviceRefs, rel.serviceRefs)
1265+
agg.ingressRefs = mergeRefs(agg.ingressRefs, rel.ingressRefs)
1266+
agg.routeRefs = mergeRefs(agg.routeRefs, rel.routeRefs)
1267+
agg.configRefs = mergeRefs(agg.configRefs, rel.configRefs)
1268+
agg.scalerRefs = mergeRefs(agg.scalerRefs, rel.scalerRefs)
1269+
agg.storageRefs = mergeRefs(agg.storageRefs, rel.storageRefs)
1270+
agg.pdbRefs = mergeRefs(agg.pdbRefs, rel.pdbRefs)
1271+
agg.networkPolicyRefs = mergeRefs(agg.networkPolicyRefs, rel.networkPolicyRefs)
12151272
if len(rel.configRefs) == 0 {
12161273
agg.Configs += rel.Configs
12171274
}
@@ -1224,6 +1281,9 @@ func mergeRelationships(r *appRow, rel *appRelationships) {
12241281
if len(rel.pdbRefs) == 0 {
12251282
agg.PDBs += rel.PDBs
12261283
}
1284+
if len(rel.networkPolicyRefs) == 0 {
1285+
agg.NetworkPolicies += rel.NetworkPolicies
1286+
}
12271287
}
12281288

12291289
func finalizeRelationships(r *appRow) {
@@ -1233,44 +1293,94 @@ func finalizeRelationships(r *appRow) {
12331293
r.Relationships.Services = dedupSorted(r.Relationships.Services, 20)
12341294
r.Relationships.Ingresses = dedupSorted(r.Relationships.Ingresses, 20)
12351295
r.Relationships.Routes = dedupSorted(r.Relationships.Routes, 20)
1296+
if len(r.Relationships.serviceRefs) > 0 {
1297+
r.Relationships.ServiceRefs = sortedRefs(r.Relationships.serviceRefs, 20)
1298+
r.Relationships.Services = refNames(r.Relationships.ServiceRefs, 20)
1299+
}
1300+
if len(r.Relationships.ingressRefs) > 0 {
1301+
r.Relationships.IngressRefs = sortedRefs(r.Relationships.ingressRefs, 20)
1302+
r.Relationships.Ingresses = refNames(r.Relationships.IngressRefs, 20)
1303+
}
1304+
if len(r.Relationships.routeRefs) > 0 {
1305+
r.Relationships.RouteRefs = sortedRefs(r.Relationships.routeRefs, 20)
1306+
r.Relationships.Routes = refNames(r.Relationships.RouteRefs, 20)
1307+
}
12361308
if len(r.Relationships.configRefs) > 0 {
12371309
r.Relationships.Configs = len(r.Relationships.configRefs)
1310+
r.Relationships.ConfigRefs = sortedRefs(r.Relationships.configRefs, 20)
12381311
}
12391312
if len(r.Relationships.scalerRefs) > 0 {
12401313
r.Relationships.Scalers = len(r.Relationships.scalerRefs)
1314+
r.Relationships.ScalerRefs = sortedRefs(r.Relationships.scalerRefs, 20)
12411315
}
12421316
if len(r.Relationships.storageRefs) > 0 {
12431317
r.Relationships.Storage = len(r.Relationships.storageRefs)
1318+
r.Relationships.StorageRefs = sortedRefs(r.Relationships.storageRefs, 20)
12441319
}
12451320
if len(r.Relationships.pdbRefs) > 0 {
12461321
r.Relationships.PDBs = len(r.Relationships.pdbRefs)
1322+
r.Relationships.PDBRefs = sortedRefs(r.Relationships.pdbRefs, 20)
1323+
}
1324+
if len(r.Relationships.networkPolicyRefs) > 0 {
1325+
r.Relationships.NetworkPolicies = len(r.Relationships.networkPolicyRefs)
1326+
r.Relationships.NetworkPolicyRefs = sortedRefs(r.Relationships.networkPolicyRefs, 20)
12471327
}
12481328
}
12491329

1250-
func refsSet(refs []topology.ResourceRef) map[string]struct{} {
1330+
func refsByKey(refs []topology.ResourceRef) map[string]topology.ResourceRef {
12511331
if len(refs) == 0 {
12521332
return nil
12531333
}
1254-
out := make(map[string]struct{}, len(refs))
1334+
out := make(map[string]topology.ResourceRef, len(refs))
12551335
for _, r := range refs {
1256-
out[refKey(r)] = struct{}{}
1336+
out[refKey(r)] = r
12571337
}
12581338
return out
12591339
}
12601340

1261-
func mergeRefSets(dst, src map[string]struct{}) map[string]struct{} {
1341+
func mergeRefs(dst, src map[string]topology.ResourceRef) map[string]topology.ResourceRef {
12621342
if len(src) == 0 {
12631343
return dst
12641344
}
12651345
if dst == nil {
1266-
dst = map[string]struct{}{}
1346+
dst = map[string]topology.ResourceRef{}
12671347
}
1268-
for k := range src {
1269-
dst[k] = struct{}{}
1348+
for k, ref := range src {
1349+
dst[k] = ref
12701350
}
12711351
return dst
12721352
}
12731353

1354+
func sortedRefs(refs map[string]topology.ResourceRef, limit int) []topology.ResourceRef {
1355+
if len(refs) == 0 || limit <= 0 {
1356+
return nil
1357+
}
1358+
keys := make([]string, 0, len(refs))
1359+
for key := range refs {
1360+
keys = append(keys, key)
1361+
}
1362+
sort.Strings(keys)
1363+
if len(keys) > limit {
1364+
keys = keys[:limit]
1365+
}
1366+
out := make([]topology.ResourceRef, 0, len(keys))
1367+
for _, key := range keys {
1368+
out = append(out, refs[key])
1369+
}
1370+
return out
1371+
}
1372+
1373+
func refNames(refs []topology.ResourceRef, limit int) []string {
1374+
if len(refs) == 0 || limit <= 0 {
1375+
return nil
1376+
}
1377+
names := make([]string, 0, len(refs))
1378+
for _, ref := range refs {
1379+
names = append(names, ref.Name)
1380+
}
1381+
return dedupSorted(names, limit)
1382+
}
1383+
12741384
func refKey(r topology.ResourceRef) string {
12751385
return r.Group + "/" + r.Kind + "/" + r.Namespace + "/" + r.Name
12761386
}
@@ -1280,7 +1390,8 @@ func classifyWorkload(kind string, rels *appRelationships) string {
12801390
case "Job", "CronJob":
12811391
return "job"
12821392
case "Deployment", "StatefulSet", "DaemonSet":
1283-
if rels != nil && (len(rels.Services) > 0 || len(rels.Ingresses) > 0 || len(rels.Routes) > 0) {
1393+
if rels != nil && (len(rels.Services) > 0 || len(rels.Ingresses) > 0 || len(rels.Routes) > 0 ||
1394+
len(rels.serviceRefs) > 0 || len(rels.ingressRefs) > 0 || len(rels.routeRefs) > 0) {
12841395
return "service"
12851396
}
12861397
return "worker"

0 commit comments

Comments
 (0)