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  }