forked from kiali/kiali
-
Notifications
You must be signed in to change notification settings - Fork 0
/
apps.go
216 lines (186 loc) · 6.31 KB
/
apps.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package business
import (
"fmt"
"sync"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"github.com/kiali/kiali/config"
"github.com/kiali/kiali/kubernetes"
"github.com/kiali/kiali/log"
"github.com/kiali/kiali/models"
"github.com/kiali/kiali/prometheus"
"github.com/kiali/kiali/prometheus/internalmetrics"
)
// AppService deals with fetching Workloads group by "app" label, which will be identified as an "application"
type AppService struct {
prom prometheus.ClientInterface
k8s kubernetes.IstioClientInterface
}
// Temporal map of Workloads group by app label
type appsWorkload map[string][]*models.Workload
// Helper method to build a map of workloads for a given labelSelector
func (in *AppService) fetchWorkloadsPerApp(namespace, labelSelector string) (appsWorkload, error) {
cfg := config.Get()
ws, err := fetchWorkloads(in.k8s, namespace, labelSelector)
if err != nil {
return nil, err
}
apps := make(appsWorkload)
for _, w := range ws {
if appLabel, ok := w.Labels[cfg.IstioLabels.AppLabelName]; ok {
apps[appLabel] = append(apps[appLabel], w)
}
}
return apps, nil
}
// GetAppList is the API handler to fetch the list of applications in a given namespace
func (in *AppService) GetAppList(namespace string) (models.AppList, error) {
var err error
promtimer := internalmetrics.GetGoFunctionMetric("business", "AppService", "GetAppList")
defer promtimer.ObserveNow(&err)
appList := &models.AppList{
Namespace: models.Namespace{Name: namespace},
Apps: []models.AppListItem{},
}
apps, err := fetchNamespaceApps(in.k8s, namespace, "")
if err != nil {
return *appList, err
}
for keyApp, valueApp := range apps {
appItem := &models.AppListItem{Name: keyApp}
appItem.IstioSidecar = false
if len(valueApp.Workloads) > 0 {
appItem.IstioSidecar = true
}
for _, w := range valueApp.Workloads {
appItem.IstioSidecar = appItem.IstioSidecar && w.Pods.HasIstioSideCar()
}
(*appList).Apps = append((*appList).Apps, *appItem)
}
return *appList, nil
}
// GetApp is the API handler to fetch the details for a given namespace and app name
func (in *AppService) GetApp(namespace string, appName string) (models.App, error) {
var err error
promtimer := internalmetrics.GetGoFunctionMetric("business", "AppService", "GetApp")
defer promtimer.ObserveNow(&err)
appInstance := &models.App{Namespace: models.Namespace{Name: namespace}, Name: appName}
namespaceApps, err := fetchNamespaceApps(in.k8s, namespace, appName)
if err != nil {
return *appInstance, err
}
var appDetails *appDetails
var ok bool
// Send a NewNotFound if the app is not found in the deployment list, instead to send an empty result
if appDetails, ok = namespaceApps[appName]; !ok {
return *appInstance, kubernetes.NewNotFound(appName, "Kiali", "App")
}
(*appInstance).Workloads = make([]models.WorkloadItem, len(appDetails.Workloads))
for i, wkd := range appDetails.Workloads {
wkdSvc := &models.WorkloadItem{WorkloadName: wkd.Name}
wkdSvc.IstioSidecar = wkd.Pods.HasIstioSideCar()
(*appInstance).Workloads[i] = *wkdSvc
}
(*appInstance).ServiceNames = make([]string, len(appDetails.Services))
for i, svc := range appDetails.Services {
(*appInstance).ServiceNames[i] = svc.Name
}
in.fillCustomDashboardRefs(namespace, appInstance, appDetails)
return *appInstance, nil
}
// AppDetails holds Services and Workloads having the same "app" label
type appDetails struct {
app string
Services []v1.Service
Workloads models.Workloads
}
// NamespaceApps is a map of app_name x AppDetails
type namespaceApps = map[string]*appDetails
func castAppDetails(services []v1.Service, ws models.Workloads) namespaceApps {
allEntities := make(namespaceApps)
appLabel := config.Get().IstioLabels.AppLabelName
for _, service := range services {
if app, ok := service.Spec.Selector[appLabel]; ok {
if appEntities, ok := allEntities[app]; ok {
appEntities.Services = append(appEntities.Services, service)
} else {
allEntities[app] = &appDetails{
app: app,
Services: []v1.Service{service},
}
}
}
}
for _, w := range ws {
if app, ok := w.Labels[appLabel]; ok {
if appEntities, ok := allEntities[app]; ok {
appEntities.Workloads = append(appEntities.Workloads, w)
} else {
allEntities[app] = &appDetails{
app: app,
Workloads: models.Workloads{w},
}
}
}
}
return allEntities
}
// Helper method to fetch all applications for a given namespace.
// Optionally if appName parameter is provided, it filters apps for that name.
// Return an error on any problem.
func fetchNamespaceApps(k8s kubernetes.IstioClientInterface, namespace string, appName string) (namespaceApps, error) {
var services []v1.Service
var ws models.Workloads
cfg := config.Get()
labelSelector := cfg.IstioLabels.AppLabelName
if appName != "" {
labelSelector = fmt.Sprintf("%s=%s", cfg.IstioLabels.AppLabelName, appName)
}
wg := sync.WaitGroup{}
wg.Add(2)
errChan := make(chan error, 2)
go func() {
defer wg.Done()
var err error
services, err = k8s.GetServices(namespace, nil)
if appName != "" {
selector := labels.Set(map[string]string{cfg.IstioLabels.AppLabelName: appName}).AsSelector()
services = kubernetes.FilterServicesForSelector(selector, services)
}
if err != nil {
log.Errorf("Error fetching Services per namespace %s: %s", namespace, err)
errChan <- err
}
}()
go func() {
defer wg.Done()
var err error
ws, err = fetchWorkloads(k8s, namespace, labelSelector)
if err != nil {
log.Errorf("Error fetching Workload per namespace %s: %s", namespace, err)
errChan <- err
}
}()
wg.Wait()
if len(errChan) != 0 {
err := <-errChan
return nil, err
}
return castAppDetails(services, ws), nil
}
// fillCustomDashboardRefs finds all dashboard IDs and Titles associated to this app and add them to the model
func (in *AppService) fillCustomDashboardRefs(namespace string, app *models.App, details *appDetails) {
allPods := models.Pods{}
for _, workload := range details.Workloads {
allPods = append(allPods, workload.Pods...)
}
uniqueRefsList := getUniqueRuntimes(allPods)
mon, err := kubernetes.NewKialiMonitoringClient()
if err != nil {
// Do not fail the whole query, just log & return
log.Error("Cannot initialize Kiali Monitoring Client")
return
}
dash := NewDashboardsService(mon, in.prom)
app.Runtimes = dash.buildRuntimesList(namespace, uniqueRefsList)
}