github.com/kiali/kiali@v1.84.0/models/workload.go (about) 1 package models 2 3 import ( 4 "strconv" 5 6 osapps_v1 "github.com/openshift/api/apps/v1" 7 apps_v1 "k8s.io/api/apps/v1" 8 batch_v1 "k8s.io/api/batch/v1" 9 core_v1 "k8s.io/api/core/v1" 10 meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 "k8s.io/apimachinery/pkg/labels" 12 13 "github.com/kiali/kiali/config" 14 ) 15 16 type ClusterWorkloads struct { 17 // Cluster where the apps live in 18 // required: true 19 // example: east 20 Cluster string `json:"cluster"` 21 22 // Workloads list for namespaces of a single cluster 23 // required: true 24 Workloads []WorkloadListItem `json:"workloads"` 25 26 Validations IstioValidations `json:"validations"` 27 } 28 29 type WorkloadList struct { 30 // Namespace where the workloads live in 31 // required: true 32 // example: bookinfo 33 Namespace string `json:"namespace"` 34 35 // Workloads for a given namespace 36 // required: true 37 Workloads []WorkloadListItem `json:"workloads"` 38 39 Validations IstioValidations `json:"validations"` 40 } 41 42 type LogType string 43 44 const ( 45 LogTypeApp LogType = "app" 46 LogTypeProxy LogType = "proxy" 47 LogTypeWaypoint LogType = "waypoint" 48 LogTypeZtunnel LogType = "ztunnel" 49 ) 50 51 // WorkloadListItem has the necessary information to display the console workload list 52 type WorkloadListItem struct { 53 // Name of the workload 54 // required: true 55 // example: reviews-v1 56 Name string `json:"name"` 57 58 // Namespace of the workload 59 Namespace string `json:"namespace"` 60 61 // The kube cluster where this workload is located. 62 Cluster string `json:"cluster"` 63 64 // Type of the workload 65 // required: true 66 // example: deployment 67 Type string `json:"type"` 68 69 // Creation timestamp (in RFC3339 format) 70 // required: true 71 // example: 2018-07-31T12:24:17Z 72 CreatedAt string `json:"createdAt"` 73 74 // Kubernetes ResourceVersion 75 // required: true 76 // example: 192892127 77 ResourceVersion string `json:"resourceVersion"` 78 79 // Define if Workload has an explicit Istio policy annotation 80 // Istio supports this as a label as well - this will be defined if the label is set, too. 81 // If both annotation and label are set, if any is false, injection is disabled. 82 // It's mapped as a pointer to show three values nil, true, false 83 IstioInjectionAnnotation *bool `json:"istioInjectionAnnotation,omitempty"` 84 85 // Define if Pods related to this Workload has an IstioSidecar deployed 86 // required: true 87 // example: true 88 IstioSidecar bool `json:"istioSidecar"` 89 90 // Define if Pods related to this Workload has an IstioAmbient deployed 91 // required: true 92 // example: true 93 IstioAmbient bool `json:"istioAmbient"` 94 95 // Additional item sample, such as type of api being served (graphql, grpc, rest) 96 // example: rest 97 // required: false 98 AdditionalDetailSample *AdditionalItem `json:"additionalDetailSample"` 99 100 // Workload labels 101 Labels map[string]string `json:"labels"` 102 103 // Define if Pods related to this Workload has the label App 104 // required: true 105 // example: true 106 AppLabel bool `json:"appLabel"` 107 108 // Define if Pods related to this Workload has the label Version 109 // required: true 110 // example: true 111 VersionLabel bool `json:"versionLabel"` 112 113 // Number of current workload pods 114 // required: true 115 // example: 1 116 PodCount int `json:"podCount"` 117 118 // Annotations of Deployment 119 // required: false 120 Annotations map[string]string `json:"annotations"` 121 122 // HealthAnnotations 123 // required: false 124 HealthAnnotations map[string]string `json:"healthAnnotations"` 125 126 // Istio References 127 IstioReferences []*IstioValidationKey `json:"istioReferences"` 128 129 // Dashboard annotations 130 // required: false 131 DashboardAnnotations map[string]string `json:"dashboardAnnotations"` 132 133 // Names of the workload service accounts 134 ServiceAccountNames []string `json:"serviceAccountNames"` 135 136 // Health 137 Health WorkloadHealth `json:"health,omitempty"` 138 } 139 140 type WorkloadOverviews []*WorkloadListItem 141 142 // Workload has the details of a workload 143 type Workload struct { 144 WorkloadListItem 145 146 // Number of desired replicas defined by the user in the controller Spec 147 // required: true 148 // example: 2 149 DesiredReplicas int32 `json:"desiredReplicas"` 150 151 // Number of current replicas pods that matches controller selector labels 152 // required: true 153 // example: 2 154 CurrentReplicas int32 `json:"currentReplicas"` 155 156 // Number of available replicas 157 // required: true 158 // example: 1 159 AvailableReplicas int32 `json:"availableReplicas"` 160 161 // Pods bound to the workload 162 Pods Pods `json:"pods"` 163 164 // Services that match workload selector 165 Services []ServiceOverview `json:"services"` 166 167 // Runtimes and associated dashboards 168 Runtimes []Runtime `json:"runtimes"` 169 170 // Additional details to display, such as configured annotations 171 AdditionalDetails []AdditionalItem `json:"additionalDetails"` 172 173 Validations IstioValidations `json:"validations"` 174 175 // Ambient waypoint workloads 176 WaypointWorkloads []Workload `json:"waypointWorkloads"` 177 178 // Health 179 Health WorkloadHealth `json:"health"` 180 } 181 182 type Workloads []*Workload 183 184 func (workload *WorkloadListItem) ParseWorkload(w *Workload) { 185 conf := config.Get() 186 workload.Name = w.Name 187 workload.Namespace = w.Namespace 188 workload.Type = w.Type 189 workload.CreatedAt = w.CreatedAt 190 workload.ResourceVersion = w.ResourceVersion 191 workload.IstioSidecar = w.HasIstioSidecar() 192 workload.IstioAmbient = w.HasIstioAmbient() 193 workload.Labels = w.Labels 194 workload.PodCount = len(w.Pods) 195 workload.ServiceAccountNames = w.Pods.ServiceAccounts() 196 workload.AdditionalDetailSample = w.AdditionalDetailSample 197 if len(w.Annotations) > 0 { 198 workload.Annotations = w.Annotations 199 } else { 200 workload.Annotations = map[string]string{} 201 } 202 workload.HealthAnnotations = w.HealthAnnotations 203 workload.IstioReferences = []*IstioValidationKey{} 204 205 /** Check the labels app and version required by Istio in template Pods*/ 206 _, workload.AppLabel = w.Labels[conf.IstioLabels.AppLabelName] 207 _, workload.VersionLabel = w.Labels[conf.IstioLabels.VersionLabelName] 208 } 209 210 func (workload *Workload) parseObjectMeta(meta *meta_v1.ObjectMeta, tplMeta *meta_v1.ObjectMeta) { 211 conf := config.Get() 212 workload.Name = meta.Name 213 if tplMeta != nil && tplMeta.Labels != nil { 214 workload.Labels = tplMeta.Labels 215 /** Check the labels app and version required by Istio in template Pods*/ 216 _, workload.AppLabel = tplMeta.Labels[conf.IstioLabels.AppLabelName] 217 _, workload.VersionLabel = tplMeta.Labels[conf.IstioLabels.VersionLabelName] 218 } else { 219 workload.Labels = map[string]string{} 220 } 221 annotations := meta.Annotations 222 if tplMeta.Annotations != nil { 223 annotations = tplMeta.Annotations 224 } 225 226 // Check for automatic sidecar injection config at the workload level. This can be defined via label or annotation. 227 // This code ignores any namespace injection label - this determines auto-injection config as defined by workload-only label or annotation. 228 // If both are defined, label always overrides annotation (see https://github.com/kiali/kiali/issues/5713) 229 // If none are defined, assume injection is disabled (again, we ignore the possibility of a namespace label enabling injection) 230 labelExplicitlySet := false // true means the label is defined 231 label, exist := workload.Labels[conf.ExternalServices.Istio.IstioInjectionAnnotation] 232 if exist { 233 if value, err := strconv.ParseBool(label); err == nil { 234 workload.IstioInjectionAnnotation = &value 235 labelExplicitlySet = true 236 } 237 } 238 239 // do not bother to check the annotation if the label is explicitly set - label always overrides the annotation 240 if !labelExplicitlySet { 241 annotation, exist := annotations[conf.ExternalServices.Istio.IstioInjectionAnnotation] 242 if exist { 243 if value, err := strconv.ParseBool(annotation); err == nil { 244 if !value { 245 workload.IstioInjectionAnnotation = &value 246 } 247 } 248 } 249 } 250 251 workload.CreatedAt = formatTime(meta.CreationTimestamp.Time) 252 workload.ResourceVersion = meta.ResourceVersion 253 workload.AdditionalDetails = GetAdditionalDetails(conf, annotations) 254 workload.AdditionalDetailSample = GetFirstAdditionalIcon(conf, annotations) 255 workload.DashboardAnnotations = GetDashboardAnnotation(annotations) 256 workload.HealthAnnotations = GetHealthAnnotation(annotations, GetHealthConfigAnnotation()) 257 } 258 259 func (workload *Workload) ParseDeployment(d *apps_v1.Deployment) { 260 workload.Type = "Deployment" 261 workload.parseObjectMeta(&d.ObjectMeta, &d.Spec.Template.ObjectMeta) 262 if d.Spec.Replicas != nil { 263 workload.DesiredReplicas = *d.Spec.Replicas 264 } 265 if len(d.Annotations) > 0 { 266 workload.Annotations = d.Annotations 267 } else { 268 workload.Annotations = map[string]string{} 269 } 270 workload.CurrentReplicas = d.Status.Replicas 271 workload.AvailableReplicas = d.Status.AvailableReplicas 272 } 273 274 func (workload *Workload) ParseReplicaSet(r *apps_v1.ReplicaSet) { 275 workload.Type = "ReplicaSet" 276 workload.parseObjectMeta(&r.ObjectMeta, &r.Spec.Template.ObjectMeta) 277 if r.Spec.Replicas != nil { 278 workload.DesiredReplicas = *r.Spec.Replicas 279 } 280 workload.CurrentReplicas = r.Status.Replicas 281 workload.AvailableReplicas = r.Status.AvailableReplicas 282 } 283 284 func (workload *Workload) ParseReplicaSetParent(r *apps_v1.ReplicaSet, workloadName string, workloadType string) { 285 // Some properties are taken from the ReplicaSet 286 workload.parseObjectMeta(&r.ObjectMeta, &r.Spec.Template.ObjectMeta) 287 // But name and type are coming from the parent 288 // Custom properties from parent controller are not processed by Kiali 289 workload.Type = workloadType 290 workload.Name = workloadName 291 if r.Spec.Replicas != nil { 292 workload.DesiredReplicas = *r.Spec.Replicas 293 } 294 workload.CurrentReplicas = r.Status.Replicas 295 workload.AvailableReplicas = r.Status.AvailableReplicas 296 } 297 298 func (workload *Workload) ParseReplicationController(r *core_v1.ReplicationController) { 299 workload.Type = "ReplicationController" 300 workload.parseObjectMeta(&r.ObjectMeta, &r.Spec.Template.ObjectMeta) 301 if r.Spec.Replicas != nil { 302 workload.DesiredReplicas = *r.Spec.Replicas 303 } 304 workload.CurrentReplicas = r.Status.Replicas 305 workload.AvailableReplicas = r.Status.AvailableReplicas 306 } 307 308 func (workload *Workload) ParseDeploymentConfig(dc *osapps_v1.DeploymentConfig) { 309 workload.Type = "DeploymentConfig" 310 workload.parseObjectMeta(&dc.ObjectMeta, &dc.Spec.Template.ObjectMeta) 311 workload.DesiredReplicas = dc.Spec.Replicas 312 workload.CurrentReplicas = dc.Status.Replicas 313 workload.AvailableReplicas = dc.Status.AvailableReplicas 314 } 315 316 func (workload *Workload) ParseStatefulSet(s *apps_v1.StatefulSet) { 317 workload.Type = "StatefulSet" 318 workload.parseObjectMeta(&s.ObjectMeta, &s.Spec.Template.ObjectMeta) 319 if s.Spec.Replicas != nil { 320 workload.DesiredReplicas = *s.Spec.Replicas 321 } 322 workload.CurrentReplicas = s.Status.Replicas 323 workload.AvailableReplicas = s.Status.ReadyReplicas 324 } 325 326 func (workload *Workload) ParsePod(pod *core_v1.Pod) { 327 workload.Type = "Pod" 328 workload.parseObjectMeta(&pod.ObjectMeta, &pod.ObjectMeta) 329 330 var podReplicas, podAvailableReplicas int32 331 podReplicas = 1 332 podAvailableReplicas = 1 333 334 // When a Workload is a single pod we don't have access to any controller replicas 335 // On this case we differentiate when pod is terminated with success versus not running 336 // Probably it might be more cases to refine here 337 if pod.Status.Phase == "Succeed" { 338 podReplicas = 0 339 podAvailableReplicas = 0 340 } else if pod.Status.Phase != "Running" { 341 podAvailableReplicas = 0 342 } 343 344 workload.DesiredReplicas = podReplicas 345 // Pod has not concept of replica 346 workload.CurrentReplicas = workload.DesiredReplicas 347 workload.AvailableReplicas = podAvailableReplicas 348 } 349 350 func (workload *Workload) ParseJob(job *batch_v1.Job) { 351 workload.Type = "Job" 352 workload.parseObjectMeta(&job.ObjectMeta, &job.ObjectMeta) 353 // Job controller does not use replica parameters as other controllers 354 // this is a workaround to use same values from Workload perspective 355 workload.DesiredReplicas = job.Status.Active + job.Status.Succeeded + job.Status.Failed 356 workload.CurrentReplicas = workload.DesiredReplicas 357 workload.AvailableReplicas = job.Status.Active + job.Status.Succeeded 358 } 359 360 func (workload *Workload) ParseCronJob(cnjb *batch_v1.CronJob) { 361 workload.Type = "CronJob" 362 workload.parseObjectMeta(&cnjb.ObjectMeta, &cnjb.ObjectMeta) 363 364 // We don't have the information of this controller 365 // We will infer the number of replicas as the number of pods without succeed state 366 // We will infer the number of available as the number of pods with running state 367 // 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 368 var podReplicas, podAvailableReplicas int32 369 podReplicas = 0 370 podAvailableReplicas = 0 371 for _, pod := range workload.Pods { 372 if pod.Status != "Succeeded" { 373 podReplicas++ 374 } 375 if pod.Status == "Running" { 376 podAvailableReplicas++ 377 } 378 } 379 workload.DesiredReplicas = podReplicas 380 workload.DesiredReplicas = workload.CurrentReplicas 381 workload.AvailableReplicas = podAvailableReplicas 382 workload.HealthAnnotations = GetHealthAnnotation(cnjb.Annotations, GetHealthConfigAnnotation()) 383 } 384 385 func (workload *Workload) ParseDaemonSet(ds *apps_v1.DaemonSet) { 386 workload.Type = "DaemonSet" 387 workload.parseObjectMeta(&ds.ObjectMeta, &ds.Spec.Template.ObjectMeta) 388 // This is a cornercase for DaemonSet controllers 389 // Desired is the number of desired nodes in a cluster that are running a DaemonSet Pod 390 // We are not going to change that terminology in the backend model yet, but probably add a note in the UI in the future 391 workload.DesiredReplicas = ds.Status.DesiredNumberScheduled 392 workload.CurrentReplicas = ds.Status.CurrentNumberScheduled 393 workload.AvailableReplicas = ds.Status.NumberAvailable 394 workload.HealthAnnotations = GetHealthAnnotation(ds.Annotations, GetHealthConfigAnnotation()) 395 } 396 397 func (workload *Workload) ParsePods(controllerName string, controllerType string, pods []core_v1.Pod) { 398 conf := config.Get() 399 workload.Name = controllerName 400 workload.Type = controllerType 401 // We don't have the information of this controller 402 // We will infer the number of replicas as the number of pods without succeed state 403 // We will infer the number of available as the number of pods with running state 404 // 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 405 var podReplicas, podAvailableReplicas int32 406 podReplicas = 0 407 podAvailableReplicas = 0 408 for _, pod := range pods { 409 if pod.Status.Phase != "Succeeded" { 410 podReplicas++ 411 } 412 if pod.Status.Phase == "Running" { 413 podAvailableReplicas++ 414 } 415 } 416 workload.DesiredReplicas = podReplicas 417 workload.CurrentReplicas = workload.DesiredReplicas 418 workload.AvailableReplicas = podAvailableReplicas 419 // We fetch one pod as template for labels 420 // There could be corner cases not correct, then we should support more controllers 421 workload.Labels = map[string]string{} 422 if len(pods) > 0 { 423 if pods[0].Labels != nil { 424 workload.Labels = pods[0].Labels 425 } 426 workload.CreatedAt = formatTime(pods[0].CreationTimestamp.Time) 427 workload.ResourceVersion = pods[0].ResourceVersion 428 } 429 430 /** Check the labels app and version required by Istio in template Pods*/ 431 _, workload.AppLabel = workload.Labels[conf.IstioLabels.AppLabelName] 432 _, workload.VersionLabel = workload.Labels[conf.IstioLabels.VersionLabelName] 433 } 434 435 func (workload *Workload) SetPods(pods []core_v1.Pod) { 436 workload.Pods.Parse(pods) 437 workload.IstioSidecar = workload.HasIstioSidecar() 438 workload.IstioAmbient = workload.HasIstioAmbient() 439 } 440 441 func (workload *Workload) SetServices(svcs *ServiceList) { 442 workload.Services = svcs.Services 443 } 444 445 // HasIstioSidecar return true if there is at least one pod and all pods have sidecars 446 func (workload *Workload) HasIstioSidecar() bool { 447 // if no pods we can't prove there is no sidecar, so return true 448 if len(workload.Pods) == 0 { 449 return true 450 } 451 // All pods in a deployment should be the same 452 if workload.Type == "Deployment" { 453 return workload.Pods[0].HasIstioSidecar() 454 } 455 // Need to check each pod 456 return workload.Pods.HasIstioSidecar() 457 } 458 459 // IsGateway return true if the workload is Ingress or Egress Gateway 460 func (workload *Workload) IsGateway() bool { 461 if workload.Type == "Deployment" { 462 if labelValue, ok := workload.Labels["operator.istio.io/component"]; ok && (labelValue == "IngressGateways" || labelValue == "EgressGateways") { 463 return true 464 } 465 if labelValue, ok := workload.Labels["istio"]; ok && (labelValue == "ingressgateway" || labelValue == "egressgateway") { 466 return true 467 } 468 } 469 return false 470 } 471 472 // HasIstioAmbient returns true if the workload has any pod with Ambient mesh annotations 473 func (workload *Workload) HasIstioAmbient() bool { 474 // if no pods we can't prove that ambient is enabled, so return false (Default) 475 if len(workload.Pods) == 0 { 476 return false 477 } 478 // All pods in a deployment should be the same 479 if workload.Type == "Deployment" { 480 return workload.Pods[0].AmbientEnabled() 481 } 482 // Need to check each pod 483 return workload.Pods.HasAnyAmbient() 484 } 485 486 // HasIstioSidecar returns true if there is at least one workload which has a sidecar 487 func (workloads WorkloadOverviews) HasIstioSidecar() bool { 488 if len(workloads) > 0 { 489 for _, w := range workloads { 490 if w.IstioSidecar { 491 return true 492 } 493 } 494 } 495 return false 496 } 497 498 func (wl WorkloadList) GetLabels() []labels.Set { 499 wLabels := make([]labels.Set, 0, len(wl.Workloads)) 500 for _, w := range wl.Workloads { 501 wLabels = append(wLabels, labels.Set(w.Labels)) 502 } 503 return wLabels 504 }