/
workload.go
495 lines (424 loc) · 16.7 KB
/
workload.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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
package models
import (
"strconv"
osapps_v1 "github.com/openshift/api/apps/v1"
apps_v1 "k8s.io/api/apps/v1"
batch_v1 "k8s.io/api/batch/v1"
core_v1 "k8s.io/api/core/v1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"github.com/kiali/kiali/config"
)
type ClusterWorkloads struct {
// Cluster where the apps live in
// required: true
// example: east
Cluster string `json:"cluster"`
// Workloads list for namespaces of a single cluster
// required: true
Workloads []WorkloadListItem `json:"workloads"`
Validations IstioValidations `json:"validations"`
}
type WorkloadList struct {
// Namespace where the workloads live in
// required: true
// example: bookinfo
Namespace string `json:"namespace"`
// Workloads for a given namespace
// required: true
Workloads []WorkloadListItem `json:"workloads"`
Validations IstioValidations `json:"validations"`
}
// WorkloadListItem has the necessary information to display the console workload list
type WorkloadListItem struct {
// Name of the workload
// required: true
// example: reviews-v1
Name string `json:"name"`
// Namespace of the workload
Namespace string `json:"namespace"`
// The kube cluster where this workload is located.
Cluster string `json:"cluster"`
// Type of the workload
// required: true
// example: deployment
Type string `json:"type"`
// Creation timestamp (in RFC3339 format)
// required: true
// example: 2018-07-31T12:24:17Z
CreatedAt string `json:"createdAt"`
// Kubernetes ResourceVersion
// required: true
// example: 192892127
ResourceVersion string `json:"resourceVersion"`
// Define if Workload has an explicit Istio policy annotation
// Istio supports this as a label as well - this will be defined if the label is set, too.
// If both annotation and label are set, if any is false, injection is disabled.
// It's mapped as a pointer to show three values nil, true, false
IstioInjectionAnnotation *bool `json:"istioInjectionAnnotation,omitempty"`
// Define if Pods related to this Workload has an IstioSidecar deployed
// required: true
// example: true
IstioSidecar bool `json:"istioSidecar"`
// Define if Pods related to this Workload has an IstioAmbient deployed
// required: true
// example: true
IstioAmbient bool `json:"istioAmbient"`
// Additional item sample, such as type of api being served (graphql, grpc, rest)
// example: rest
// required: false
AdditionalDetailSample *AdditionalItem `json:"additionalDetailSample"`
// Workload labels
Labels map[string]string `json:"labels"`
// Define if Pods related to this Workload has the label App
// required: true
// example: true
AppLabel bool `json:"appLabel"`
// Define if Pods related to this Workload has the label Version
// required: true
// example: true
VersionLabel bool `json:"versionLabel"`
// Number of current workload pods
// required: true
// example: 1
PodCount int `json:"podCount"`
// Annotations of Deployment
// required: false
Annotations map[string]string `json:"annotations"`
// HealthAnnotations
// required: false
HealthAnnotations map[string]string `json:"healthAnnotations"`
// Istio References
IstioReferences []*IstioValidationKey `json:"istioReferences"`
// Dashboard annotations
// required: false
DashboardAnnotations map[string]string `json:"dashboardAnnotations"`
// Names of the workload service accounts
ServiceAccountNames []string `json:"serviceAccountNames"`
// Health
Health WorkloadHealth `json:"health,omitempty"`
}
type WorkloadOverviews []*WorkloadListItem
// Workload has the details of a workload
type Workload struct {
WorkloadListItem
// Number of desired replicas defined by the user in the controller Spec
// required: true
// example: 2
DesiredReplicas int32 `json:"desiredReplicas"`
// Number of current replicas pods that matches controller selector labels
// required: true
// example: 2
CurrentReplicas int32 `json:"currentReplicas"`
// Number of available replicas
// required: true
// example: 1
AvailableReplicas int32 `json:"availableReplicas"`
// Pods bound to the workload
Pods Pods `json:"pods"`
// Services that match workload selector
Services []ServiceOverview `json:"services"`
// Runtimes and associated dashboards
Runtimes []Runtime `json:"runtimes"`
// Additional details to display, such as configured annotations
AdditionalDetails []AdditionalItem `json:"additionalDetails"`
Validations IstioValidations `json:"validations"`
// Ambient waypoint workloads
WaypointWorkloads []Workload `json:"waypointWorkloads"`
// Health
Health WorkloadHealth `json:"health"`
}
type Workloads []*Workload
func (workload *WorkloadListItem) ParseWorkload(w *Workload) {
conf := config.Get()
workload.Name = w.Name
workload.Namespace = w.Namespace
workload.Type = w.Type
workload.CreatedAt = w.CreatedAt
workload.ResourceVersion = w.ResourceVersion
workload.IstioSidecar = w.HasIstioSidecar()
workload.IstioAmbient = w.HasIstioAmbient()
workload.Labels = w.Labels
workload.PodCount = len(w.Pods)
workload.ServiceAccountNames = w.Pods.ServiceAccounts()
workload.AdditionalDetailSample = w.AdditionalDetailSample
if len(w.Annotations) > 0 {
workload.Annotations = w.Annotations
} else {
workload.Annotations = map[string]string{}
}
workload.HealthAnnotations = w.HealthAnnotations
workload.IstioReferences = []*IstioValidationKey{}
/** Check the labels app and version required by Istio in template Pods*/
_, workload.AppLabel = w.Labels[conf.IstioLabels.AppLabelName]
_, workload.VersionLabel = w.Labels[conf.IstioLabels.VersionLabelName]
}
func (workload *Workload) parseObjectMeta(meta *meta_v1.ObjectMeta, tplMeta *meta_v1.ObjectMeta) {
conf := config.Get()
workload.Name = meta.Name
if tplMeta != nil && tplMeta.Labels != nil {
workload.Labels = tplMeta.Labels
/** Check the labels app and version required by Istio in template Pods*/
_, workload.AppLabel = tplMeta.Labels[conf.IstioLabels.AppLabelName]
_, workload.VersionLabel = tplMeta.Labels[conf.IstioLabels.VersionLabelName]
} else {
workload.Labels = map[string]string{}
}
annotations := meta.Annotations
if tplMeta.Annotations != nil {
annotations = tplMeta.Annotations
}
// Check for automatic sidecar injection config at the workload level. This can be defined via label or annotation.
// This code ignores any namespace injection label - this determines auto-injection config as defined by workload-only label or annotation.
// If both are defined, label always overrides annotation (see https://github.com/kiali/kiali/issues/5713)
// If none are defined, assume injection is disabled (again, we ignore the possibility of a namespace label enabling injection)
labelExplicitlySet := false // true means the label is defined
label, exist := workload.Labels[conf.ExternalServices.Istio.IstioInjectionAnnotation]
if exist {
if value, err := strconv.ParseBool(label); err == nil {
workload.IstioInjectionAnnotation = &value
labelExplicitlySet = true
}
}
// do not bother to check the annotation if the label is explicitly set - label always overrides the annotation
if !labelExplicitlySet {
annotation, exist := annotations[conf.ExternalServices.Istio.IstioInjectionAnnotation]
if exist {
if value, err := strconv.ParseBool(annotation); err == nil {
if !value {
workload.IstioInjectionAnnotation = &value
}
}
}
}
workload.CreatedAt = formatTime(meta.CreationTimestamp.Time)
workload.ResourceVersion = meta.ResourceVersion
workload.AdditionalDetails = GetAdditionalDetails(conf, annotations)
workload.AdditionalDetailSample = GetFirstAdditionalIcon(conf, annotations)
workload.DashboardAnnotations = GetDashboardAnnotation(annotations)
workload.HealthAnnotations = GetHealthAnnotation(annotations, GetHealthConfigAnnotation())
}
func (workload *Workload) ParseDeployment(d *apps_v1.Deployment) {
workload.Type = "Deployment"
workload.parseObjectMeta(&d.ObjectMeta, &d.Spec.Template.ObjectMeta)
if d.Spec.Replicas != nil {
workload.DesiredReplicas = *d.Spec.Replicas
}
if len(d.Annotations) > 0 {
workload.Annotations = d.Annotations
} else {
workload.Annotations = map[string]string{}
}
workload.CurrentReplicas = d.Status.Replicas
workload.AvailableReplicas = d.Status.AvailableReplicas
}
func (workload *Workload) ParseReplicaSet(r *apps_v1.ReplicaSet) {
workload.Type = "ReplicaSet"
workload.parseObjectMeta(&r.ObjectMeta, &r.Spec.Template.ObjectMeta)
if r.Spec.Replicas != nil {
workload.DesiredReplicas = *r.Spec.Replicas
}
workload.CurrentReplicas = r.Status.Replicas
workload.AvailableReplicas = r.Status.AvailableReplicas
}
func (workload *Workload) ParseReplicaSetParent(r *apps_v1.ReplicaSet, workloadName string, workloadType string) {
// Some properties are taken from the ReplicaSet
workload.parseObjectMeta(&r.ObjectMeta, &r.Spec.Template.ObjectMeta)
// But name and type are coming from the parent
// Custom properties from parent controller are not processed by Kiali
workload.Type = workloadType
workload.Name = workloadName
if r.Spec.Replicas != nil {
workload.DesiredReplicas = *r.Spec.Replicas
}
workload.CurrentReplicas = r.Status.Replicas
workload.AvailableReplicas = r.Status.AvailableReplicas
}
func (workload *Workload) ParseReplicationController(r *core_v1.ReplicationController) {
workload.Type = "ReplicationController"
workload.parseObjectMeta(&r.ObjectMeta, &r.Spec.Template.ObjectMeta)
if r.Spec.Replicas != nil {
workload.DesiredReplicas = *r.Spec.Replicas
}
workload.CurrentReplicas = r.Status.Replicas
workload.AvailableReplicas = r.Status.AvailableReplicas
}
func (workload *Workload) ParseDeploymentConfig(dc *osapps_v1.DeploymentConfig) {
workload.Type = "DeploymentConfig"
workload.parseObjectMeta(&dc.ObjectMeta, &dc.Spec.Template.ObjectMeta)
workload.DesiredReplicas = dc.Spec.Replicas
workload.CurrentReplicas = dc.Status.Replicas
workload.AvailableReplicas = dc.Status.AvailableReplicas
}
func (workload *Workload) ParseStatefulSet(s *apps_v1.StatefulSet) {
workload.Type = "StatefulSet"
workload.parseObjectMeta(&s.ObjectMeta, &s.Spec.Template.ObjectMeta)
if s.Spec.Replicas != nil {
workload.DesiredReplicas = *s.Spec.Replicas
}
workload.CurrentReplicas = s.Status.Replicas
workload.AvailableReplicas = s.Status.ReadyReplicas
}
func (workload *Workload) ParsePod(pod *core_v1.Pod) {
workload.Type = "Pod"
workload.parseObjectMeta(&pod.ObjectMeta, &pod.ObjectMeta)
var podReplicas, podAvailableReplicas int32
podReplicas = 1
podAvailableReplicas = 1
// When a Workload is a single pod we don't have access to any controller replicas
// On this case we differentiate when pod is terminated with success versus not running
// Probably it might be more cases to refine here
if pod.Status.Phase == "Succeed" {
podReplicas = 0
podAvailableReplicas = 0
} else if pod.Status.Phase != "Running" {
podAvailableReplicas = 0
}
workload.DesiredReplicas = podReplicas
// Pod has not concept of replica
workload.CurrentReplicas = workload.DesiredReplicas
workload.AvailableReplicas = podAvailableReplicas
}
func (workload *Workload) ParseJob(job *batch_v1.Job) {
workload.Type = "Job"
workload.parseObjectMeta(&job.ObjectMeta, &job.ObjectMeta)
// Job controller does not use replica parameters as other controllers
// this is a workaround to use same values from Workload perspective
workload.DesiredReplicas = job.Status.Active + job.Status.Succeeded + job.Status.Failed
workload.CurrentReplicas = workload.DesiredReplicas
workload.AvailableReplicas = job.Status.Active + job.Status.Succeeded
}
func (workload *Workload) ParseCronJob(cnjb *batch_v1.CronJob) {
workload.Type = "CronJob"
workload.parseObjectMeta(&cnjb.ObjectMeta, &cnjb.ObjectMeta)
// We don't have the information of this controller
// We will infer the number of replicas as the number of pods without succeed state
// We will infer the number of available as the number of pods with running state
// If this is not enough, we should try to fetch the controller, it is not doing now to not overload kiali fetching all types of controllers
var podReplicas, podAvailableReplicas int32
podReplicas = 0
podAvailableReplicas = 0
for _, pod := range workload.Pods {
if pod.Status != "Succeeded" {
podReplicas++
}
if pod.Status == "Running" {
podAvailableReplicas++
}
}
workload.DesiredReplicas = podReplicas
workload.DesiredReplicas = workload.CurrentReplicas
workload.AvailableReplicas = podAvailableReplicas
workload.HealthAnnotations = GetHealthAnnotation(cnjb.Annotations, GetHealthConfigAnnotation())
}
func (workload *Workload) ParseDaemonSet(ds *apps_v1.DaemonSet) {
workload.Type = "DaemonSet"
workload.parseObjectMeta(&ds.ObjectMeta, &ds.Spec.Template.ObjectMeta)
// This is a cornercase for DaemonSet controllers
// Desired is the number of desired nodes in a cluster that are running a DaemonSet Pod
// We are not going to change that terminology in the backend model yet, but probably add a note in the UI in the future
workload.DesiredReplicas = ds.Status.DesiredNumberScheduled
workload.CurrentReplicas = ds.Status.CurrentNumberScheduled
workload.AvailableReplicas = ds.Status.NumberAvailable
workload.HealthAnnotations = GetHealthAnnotation(ds.Annotations, GetHealthConfigAnnotation())
}
func (workload *Workload) ParsePods(controllerName string, controllerType string, pods []core_v1.Pod) {
conf := config.Get()
workload.Name = controllerName
workload.Type = controllerType
// We don't have the information of this controller
// We will infer the number of replicas as the number of pods without succeed state
// We will infer the number of available as the number of pods with running state
// If this is not enough, we should try to fetch the controller, it is not doing now to not overload kiali fetching all types of controllers
var podReplicas, podAvailableReplicas int32
podReplicas = 0
podAvailableReplicas = 0
for _, pod := range pods {
if pod.Status.Phase != "Succeeded" {
podReplicas++
}
if pod.Status.Phase == "Running" {
podAvailableReplicas++
}
}
workload.DesiredReplicas = podReplicas
workload.CurrentReplicas = workload.DesiredReplicas
workload.AvailableReplicas = podAvailableReplicas
// We fetch one pod as template for labels
// There could be corner cases not correct, then we should support more controllers
workload.Labels = map[string]string{}
if len(pods) > 0 {
if pods[0].Labels != nil {
workload.Labels = pods[0].Labels
}
workload.CreatedAt = formatTime(pods[0].CreationTimestamp.Time)
workload.ResourceVersion = pods[0].ResourceVersion
}
/** Check the labels app and version required by Istio in template Pods*/
_, workload.AppLabel = workload.Labels[conf.IstioLabels.AppLabelName]
_, workload.VersionLabel = workload.Labels[conf.IstioLabels.VersionLabelName]
}
func (workload *Workload) SetPods(pods []core_v1.Pod) {
workload.Pods.Parse(pods)
workload.IstioSidecar = workload.HasIstioSidecar()
workload.IstioAmbient = workload.HasIstioAmbient()
}
func (workload *Workload) SetServices(svcs *ServiceList) {
workload.Services = svcs.Services
}
// HasIstioSidecar return true if there is at least one pod and all pods have sidecars
func (workload *Workload) HasIstioSidecar() bool {
// if no pods we can't prove there is no sidecar, so return true
if len(workload.Pods) == 0 {
return true
}
// All pods in a deployment should be the same
if workload.Type == "Deployment" {
return workload.Pods[0].HasIstioSidecar()
}
// Need to check each pod
return workload.Pods.HasIstioSidecar()
}
// IsGateway return true if the workload is Ingress or Egress Gateway
func (workload *Workload) IsGateway() bool {
if workload.Type == "Deployment" {
if labelValue, ok := workload.Labels["operator.istio.io/component"]; ok && (labelValue == "IngressGateways" || labelValue == "EgressGateways") {
return true
}
if labelValue, ok := workload.Labels["istio"]; ok && (labelValue == "ingressgateway" || labelValue == "egressgateway") {
return true
}
}
return false
}
// HasIstioAmbient returns true if the workload has any pod with Ambient mesh annotations
func (workload *Workload) HasIstioAmbient() bool {
// if no pods we can't prove that ambient is enabled, so return false (Default)
if len(workload.Pods) == 0 {
return false
}
// All pods in a deployment should be the same
if workload.Type == "Deployment" {
return workload.Pods[0].AmbientEnabled()
}
// Need to check each pod
return workload.Pods.HasAnyAmbient()
}
// HasIstioSidecar returns true if there is at least one workload which has a sidecar
func (workloads WorkloadOverviews) HasIstioSidecar() bool {
if len(workloads) > 0 {
for _, w := range workloads {
if w.IstioSidecar {
return true
}
}
}
return false
}
func (wl WorkloadList) GetLabels() []labels.Set {
wLabels := make([]labels.Set, 0, len(wl.Workloads))
for _, w := range wl.Workloads {
wLabels = append(wLabels, labels.Set(w.Labels))
}
return wLabels
}