github.com/kiali/kiali@v1.84.0/business/workloads.go (about)

     1  package business
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"regexp"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	"github.com/nitishm/engarde/pkg/parser"
    18  	osapps_v1 "github.com/openshift/api/apps/v1"
    19  	security_v1beta1 "istio.io/client-go/pkg/apis/security/v1beta1"
    20  	apps_v1 "k8s.io/api/apps/v1"
    21  	batch_v1 "k8s.io/api/batch/v1"
    22  	core_v1 "k8s.io/api/core/v1"
    23  	"k8s.io/apimachinery/pkg/api/errors"
    24  	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/labels"
    26  
    27  	"github.com/kiali/kiali/business/checkers"
    28  	"github.com/kiali/kiali/config"
    29  	"github.com/kiali/kiali/kubernetes"
    30  	"github.com/kiali/kiali/kubernetes/cache"
    31  	"github.com/kiali/kiali/log"
    32  	"github.com/kiali/kiali/models"
    33  	"github.com/kiali/kiali/observability"
    34  	"github.com/kiali/kiali/prometheus"
    35  )
    36  
    37  func NewWorkloadService(userClients map[string]kubernetes.ClientInterface, prom prometheus.ClientInterface, cache cache.KialiCache, layer *Layer, config *config.Config) *WorkloadService {
    38  	excludedWorkloads := make(map[string]bool)
    39  	for _, w := range config.KubernetesConfig.ExcludeWorkloads {
    40  		excludedWorkloads[w] = true
    41  	}
    42  
    43  	return &WorkloadService{
    44  		businessLayer:     layer,
    45  		cache:             cache,
    46  		config:            config,
    47  		excludedWorkloads: excludedWorkloads,
    48  		prom:              prom,
    49  		userClients:       userClients,
    50  	}
    51  }
    52  
    53  // WorkloadService deals with fetching istio/kubernetes workloads related content and convert to kiali model
    54  type WorkloadService struct {
    55  	// Careful not to call the workload service from here as that would be an infinite loop.
    56  	businessLayer *Layer
    57  	// The global kiali cache. This should be passed into the workload service rather than created inside of it.
    58  	cache cache.KialiCache
    59  	// The global kiali config.
    60  	config            *config.Config
    61  	excludedWorkloads map[string]bool
    62  	prom              prometheus.ClientInterface
    63  	userClients       map[string]kubernetes.ClientInterface
    64  }
    65  
    66  type WorkloadCriteria struct {
    67  	Cluster               string
    68  	Namespace             string
    69  	WorkloadName          string
    70  	WorkloadType          string
    71  	IncludeIstioResources bool
    72  	IncludeServices       bool
    73  	IncludeHealth         bool
    74  	RateInterval          string
    75  	QueryTime             time.Time
    76  }
    77  
    78  // PodLog reports log entries
    79  type PodLog struct {
    80  	Entries        []LogEntry `json:"entries,omitempty"`
    81  	LinesTruncated bool       `json:"linesTruncated,omitempty"`
    82  }
    83  
    84  // AccessLogEntry provides parsed info from a single proxy access log entry
    85  type AccessLogEntry struct {
    86  	Timestamp     string `json:"timestamp,omitempty"`
    87  	TimestampUnix int64  `json:"timestampUnix,omitempty"`
    88  }
    89  
    90  // LogEntry holds a single log entry
    91  type LogEntry struct {
    92  	Message       string            `json:"message,omitempty"`
    93  	Severity      string            `json:"severity,omitempty"`
    94  	OriginalTime  time.Time         `json:"-"`
    95  	Timestamp     string            `json:"timestamp,omitempty"`
    96  	TimestampUnix int64             `json:"timestampUnix,omitempty"`
    97  	AccessLog     *parser.AccessLog `json:"accessLog,omitempty"`
    98  }
    99  
   100  type filterOpts struct {
   101  	destWk string
   102  	destNs string
   103  	srcWk  string
   104  	srcNs  string
   105  }
   106  
   107  // LogOptions holds query parameter values
   108  type LogOptions struct {
   109  	Duration *time.Duration
   110  	LogType  models.LogType
   111  	MaxLines *int
   112  	core_v1.PodLogOptions
   113  	filter filterOpts
   114  }
   115  
   116  // Matches an ISO8601 full date
   117  var severityRegexp = regexp.MustCompile(`(?i)ERROR|WARN|DEBUG|TRACE`)
   118  
   119  func (in *WorkloadService) isWorkloadIncluded(workload string) bool {
   120  	if in.excludedWorkloads == nil {
   121  		return true
   122  	}
   123  	return !in.excludedWorkloads[workload]
   124  }
   125  
   126  // isWorkloadValid returns true if it is a known workload type and it is not configured as excluded
   127  func (in *WorkloadService) isWorkloadValid(workloadType string) bool {
   128  	_, knownWorkloadType := controllerOrder[workloadType]
   129  	return knownWorkloadType && in.isWorkloadIncluded(workloadType)
   130  }
   131  
   132  // @TODO do validations per cluster
   133  func (in *WorkloadService) getWorkloadValidations(authpolicies []*security_v1beta1.AuthorizationPolicy, workloadsPerNamespace map[string]models.WorkloadList) models.IstioValidations {
   134  	validations := checkers.WorkloadChecker{
   135  		AuthorizationPolicies: authpolicies,
   136  		WorkloadsPerNamespace: workloadsPerNamespace,
   137  	}.Check()
   138  
   139  	return validations
   140  }
   141  
   142  // GetWorkloadList is the API handler to fetch the list of workloads in a given namespace.
   143  func (in *WorkloadService) GetWorkloadList(ctx context.Context, criteria WorkloadCriteria) (models.WorkloadList, error) {
   144  	var end observability.EndFunc
   145  	ctx, end = observability.StartSpan(ctx, "GetWorkloadList",
   146  		observability.Attribute("package", "business"),
   147  		observability.Attribute("includeHealth", criteria.IncludeHealth),
   148  		observability.Attribute("includeIstioResources", criteria.IncludeIstioResources),
   149  		observability.Attribute("cluster", criteria.Cluster),
   150  		observability.Attribute("namespace", criteria.Namespace),
   151  		observability.Attribute("rateInterval", criteria.RateInterval),
   152  		observability.Attribute("queryTime", criteria.QueryTime),
   153  	)
   154  	defer end()
   155  
   156  	namespace := criteria.Namespace
   157  	cluster := criteria.Cluster
   158  
   159  	workloadList := &models.WorkloadList{
   160  		Namespace:   namespace,
   161  		Workloads:   []models.WorkloadListItem{},
   162  		Validations: models.IstioValidations{},
   163  	}
   164  
   165  	if _, ok := in.userClients[cluster]; !ok {
   166  		return *workloadList, fmt.Errorf("Cluster [%s] is not found or is not accessible for Kiali", cluster)
   167  	}
   168  
   169  	if _, err := in.businessLayer.Namespace.GetClusterNamespace(ctx, namespace, cluster); err != nil {
   170  		return *workloadList, err
   171  	}
   172  
   173  	var ws models.Workloads
   174  	// var authpolicies []*security_v1beta1.AuthorizationPolicy
   175  	var err error
   176  
   177  	nFetches := 1
   178  	if criteria.IncludeIstioResources {
   179  		nFetches = 2
   180  	}
   181  
   182  	wg := sync.WaitGroup{}
   183  	wg.Add(nFetches)
   184  	errChan := make(chan error, nFetches)
   185  
   186  	go func(ctx context.Context) {
   187  		defer wg.Done()
   188  		var err2 error
   189  		ws, err2 = in.fetchWorkloadsFromCluster(ctx, cluster, namespace, "")
   190  		if err2 != nil {
   191  			log.Errorf("Error fetching Workloads per namespace %s: %s", namespace, err2)
   192  			errChan <- err2
   193  		}
   194  	}(ctx)
   195  
   196  	istioConfigCriteria := IstioConfigCriteria{
   197  		IncludeAuthorizationPolicies:  true,
   198  		IncludeEnvoyFilters:           true,
   199  		IncludeGateways:               true,
   200  		IncludePeerAuthentications:    true,
   201  		IncludeRequestAuthentications: true,
   202  		IncludeSidecars:               true,
   203  	}
   204  	var istioConfigMap models.IstioConfigMap
   205  
   206  	if criteria.IncludeIstioResources {
   207  		go func(ctx context.Context) {
   208  			defer wg.Done()
   209  			var err2 error
   210  			istioConfigMap, err2 = in.businessLayer.IstioConfig.GetIstioConfigMap(ctx, namespace, istioConfigCriteria)
   211  			if err2 != nil {
   212  				log.Errorf("Error fetching Istio Config per namespace %s: %s", namespace, err2)
   213  				errChan <- err2
   214  			}
   215  		}(ctx)
   216  	}
   217  
   218  	wg.Wait()
   219  	if len(errChan) != 0 {
   220  		err = <-errChan
   221  		return *workloadList, err
   222  	}
   223  
   224  	for _, w := range ws {
   225  		wItem := &models.WorkloadListItem{Health: *models.EmptyWorkloadHealth()}
   226  		wItem.ParseWorkload(w)
   227  		if istioConfigList, ok := istioConfigMap[cluster]; ok && criteria.IncludeIstioResources {
   228  			wSelector := labels.Set(wItem.Labels).AsSelector().String()
   229  			wItem.IstioReferences = FilterUniqueIstioReferences(FilterWorkloadReferences(wSelector, istioConfigList))
   230  		}
   231  		if criteria.IncludeHealth {
   232  			wItem.Health, err = in.businessLayer.Health.GetWorkloadHealth(ctx, namespace, cluster, wItem.Name, criteria.RateInterval, criteria.QueryTime, w)
   233  			if err != nil {
   234  				log.Errorf("Error fetching Health in namespace %s for workload %s: %s", namespace, wItem.Name, err)
   235  			}
   236  		}
   237  		wItem.Cluster = cluster
   238  		wItem.Namespace = namespace
   239  		workloadList.Workloads = append(workloadList.Workloads, *wItem)
   240  	}
   241  
   242  	for _, istioConfigList := range istioConfigMap {
   243  		// @TODO multi cluster validations
   244  		authpolicies := istioConfigList.AuthorizationPolicies
   245  		allWorkloads := map[string]models.WorkloadList{}
   246  		allWorkloads[namespace] = *workloadList
   247  		validations := in.getWorkloadValidations(authpolicies, allWorkloads)
   248  		validations.StripIgnoredChecks()
   249  		workloadList.Validations = workloadList.Validations.MergeValidations(validations)
   250  	}
   251  
   252  	return *workloadList, nil
   253  }
   254  
   255  func FilterWorkloadReferences(wSelector string, istioConfigList models.IstioConfigList) []*models.IstioValidationKey {
   256  	wkdReferences := make([]*models.IstioValidationKey, 0)
   257  	gwFiltered := kubernetes.FilterGatewaysBySelector(wSelector, istioConfigList.Gateways)
   258  	for _, g := range gwFiltered {
   259  		ref := models.BuildKey(g.Kind, g.Name, g.Namespace)
   260  		exist := false
   261  		for _, r := range wkdReferences {
   262  			exist = exist || *r == ref
   263  		}
   264  		if !exist {
   265  			wkdReferences = append(wkdReferences, &ref)
   266  		}
   267  	}
   268  	apFiltered := kubernetes.FilterAuthorizationPoliciesBySelector(wSelector, istioConfigList.AuthorizationPolicies)
   269  	for _, a := range apFiltered {
   270  		ref := models.BuildKey(a.Kind, a.Name, a.Namespace)
   271  		exist := false
   272  		for _, r := range wkdReferences {
   273  			exist = exist || *r == ref
   274  		}
   275  		if !exist {
   276  			wkdReferences = append(wkdReferences, &ref)
   277  		}
   278  	}
   279  	paFiltered := kubernetes.FilterPeerAuthenticationsBySelector(wSelector, istioConfigList.PeerAuthentications)
   280  	for _, p := range paFiltered {
   281  		ref := models.BuildKey(p.Kind, p.Name, p.Namespace)
   282  		exist := false
   283  		for _, r := range wkdReferences {
   284  			exist = exist || *r == ref
   285  		}
   286  		if !exist {
   287  			wkdReferences = append(wkdReferences, &ref)
   288  		}
   289  	}
   290  	scFiltered := kubernetes.FilterSidecarsBySelector(wSelector, istioConfigList.Sidecars)
   291  	for _, s := range scFiltered {
   292  		ref := models.BuildKey(s.Kind, s.Name, s.Namespace)
   293  		exist := false
   294  		for _, r := range wkdReferences {
   295  			exist = exist || *r == ref
   296  		}
   297  		if !exist {
   298  			wkdReferences = append(wkdReferences, &ref)
   299  		}
   300  	}
   301  	raFiltered := kubernetes.FilterRequestAuthenticationsBySelector(wSelector, istioConfigList.RequestAuthentications)
   302  	for _, ra := range raFiltered {
   303  		ref := models.BuildKey(ra.Kind, ra.Name, ra.Namespace)
   304  		exist := false
   305  		for _, r := range wkdReferences {
   306  			exist = exist || *r == ref
   307  		}
   308  		if !exist {
   309  			wkdReferences = append(wkdReferences, &ref)
   310  		}
   311  	}
   312  	efFiltered := kubernetes.FilterEnvoyFiltersBySelector(wSelector, istioConfigList.EnvoyFilters)
   313  	for _, ef := range efFiltered {
   314  		ref := models.BuildKey(ef.Kind, ef.Name, ef.Namespace)
   315  		exist := false
   316  		for _, r := range wkdReferences {
   317  			exist = exist || *r == ref
   318  		}
   319  		if !exist {
   320  			wkdReferences = append(wkdReferences, &ref)
   321  		}
   322  	}
   323  	return wkdReferences
   324  }
   325  
   326  func FilterUniqueIstioReferences(refs []*models.IstioValidationKey) []*models.IstioValidationKey {
   327  	refMap := make(map[models.IstioValidationKey]struct{})
   328  	for _, ref := range refs {
   329  		if _, exist := refMap[*ref]; !exist {
   330  			refMap[*ref] = struct{}{}
   331  		}
   332  	}
   333  	filtered := make([]*models.IstioValidationKey, 0)
   334  	for k := range refMap {
   335  		filtered = append(filtered, &models.IstioValidationKey{
   336  			ObjectType: k.ObjectType,
   337  			Name:       k.Name,
   338  			Namespace:  k.Namespace,
   339  		})
   340  	}
   341  	return filtered
   342  }
   343  
   344  // GetWorkload is the API handler to fetch details of a specific workload.
   345  // If includeServices is set true, the Workload will fetch all services related
   346  func (in *WorkloadService) GetWorkload(ctx context.Context, criteria WorkloadCriteria) (*models.Workload, error) {
   347  	var end observability.EndFunc
   348  	ctx, end = observability.StartSpan(ctx, "GetWorkload",
   349  		observability.Attribute("package", "business"),
   350  		observability.Attribute("cluster", criteria.Cluster),
   351  		observability.Attribute("namespace", criteria.Namespace),
   352  		observability.Attribute("workloadName", criteria.WorkloadName),
   353  		observability.Attribute("workloadType", criteria.WorkloadType),
   354  		observability.Attribute("includeServices", criteria.IncludeServices),
   355  		observability.Attribute("rateInterval", criteria.RateInterval),
   356  		observability.Attribute("queryTime", criteria.QueryTime),
   357  	)
   358  	defer end()
   359  
   360  	ns, err := in.businessLayer.Namespace.GetClusterNamespace(ctx, criteria.Namespace, criteria.Cluster)
   361  	if err != nil {
   362  		return nil, err
   363  	}
   364  
   365  	workload, err2 := in.fetchWorkload(ctx, criteria)
   366  
   367  	if err2 != nil {
   368  		return nil, err2
   369  	}
   370  
   371  	var runtimes []models.Runtime
   372  	wg := sync.WaitGroup{}
   373  	wg.Add(1)
   374  	go func() {
   375  		defer wg.Done()
   376  		conf := in.config
   377  		app := workload.Labels[conf.IstioLabels.AppLabelName]
   378  		version := workload.Labels[conf.IstioLabels.VersionLabelName]
   379  		runtimes = NewDashboardsService(ns, workload).GetCustomDashboardRefs(criteria.Namespace, app, version, workload.Pods)
   380  	}()
   381  
   382  	if criteria.IncludeServices {
   383  		var services *models.ServiceList
   384  		var err error
   385  
   386  		serviceCriteria := ServiceCriteria{
   387  			Cluster:                criteria.Cluster,
   388  			Namespace:              criteria.Namespace,
   389  			ServiceSelector:        labels.Set(workload.Labels).String(),
   390  			IncludeHealth:          false,
   391  			IncludeOnlyDefinitions: true,
   392  		}
   393  		services, err = in.businessLayer.Svc.GetServiceList(ctx, serviceCriteria)
   394  		if err != nil {
   395  			return nil, err
   396  		}
   397  		workload.SetServices(services)
   398  	}
   399  
   400  	wg.Wait()
   401  	workload.Runtimes = runtimes
   402  
   403  	return workload, nil
   404  }
   405  
   406  func (in *WorkloadService) UpdateWorkload(ctx context.Context, cluster string, namespace string, workloadName string, workloadType string, includeServices bool, jsonPatch string, patchType string) (*models.Workload, error) {
   407  	var end observability.EndFunc
   408  	ctx, end = observability.StartSpan(ctx, "UpdateWorkload",
   409  		observability.Attribute("package", "business"),
   410  		observability.Attribute("cluster", cluster),
   411  		observability.Attribute("namespace", namespace),
   412  		observability.Attribute("workloadName", workloadName),
   413  		observability.Attribute("workloadType", workloadType),
   414  		observability.Attribute("includeServices", includeServices),
   415  		observability.Attribute("jsonPatch", jsonPatch),
   416  		observability.Attribute("patchType", patchType),
   417  	)
   418  	defer end()
   419  
   420  	// Identify controller and apply patch to workload
   421  	err := in.updateWorkload(ctx, cluster, namespace, workloadName, workloadType, jsonPatch, patchType)
   422  	if err != nil {
   423  		return nil, err
   424  	}
   425  
   426  	// Cache is stopped after a Create/Update/Delete operation to force a refresh.
   427  	// Refresh once after all the updates have gone through since Update Workload will update
   428  	// every single workload type of that matches name/namespace and we only want to refresh once.
   429  	cache, err := kialiCache.GetKubeCache(cluster)
   430  	if err != nil {
   431  		return nil, err
   432  	}
   433  	cache.Refresh(namespace)
   434  
   435  	// After the update we fetch the whole workload
   436  	return in.GetWorkload(ctx, WorkloadCriteria{Cluster: cluster, Namespace: namespace, WorkloadName: workloadName, WorkloadType: workloadType, IncludeServices: includeServices})
   437  }
   438  
   439  func (in *WorkloadService) GetPod(cluster, namespace, name string) (*models.Pod, error) {
   440  	k8s, ok := in.userClients[cluster]
   441  	if !ok {
   442  		return nil, fmt.Errorf("cluster [%s] is not found or is not accessible for Kiali", cluster)
   443  	}
   444  
   445  	// This isn't using the cache for some reason but it never has.
   446  	p, err := k8s.GetPod(namespace, name)
   447  	if err != nil {
   448  		return nil, err
   449  	}
   450  
   451  	pod := models.Pod{}
   452  	pod.Parse(p)
   453  	return &pod, nil
   454  }
   455  
   456  func (in *WorkloadService) BuildLogOptionsCriteria(container, duration string, logType models.LogType, sinceTime, maxLines string) (*LogOptions, error) {
   457  	opts := &LogOptions{}
   458  	opts.PodLogOptions = core_v1.PodLogOptions{Timestamps: true}
   459  
   460  	if container != "" {
   461  		opts.Container = container
   462  	}
   463  
   464  	if duration != "" {
   465  		duration, err := time.ParseDuration(duration)
   466  		if err != nil {
   467  			return nil, fmt.Errorf("invalid duration [%s]: %v", duration, err)
   468  		}
   469  
   470  		opts.Duration = &duration
   471  	}
   472  
   473  	opts.LogType = logType
   474  
   475  	if sinceTime != "" {
   476  		numTime, err := strconv.ParseInt(sinceTime, 10, 64)
   477  		if err != nil {
   478  			return nil, fmt.Errorf("invalid sinceTime [%s]: %v", sinceTime, err)
   479  		}
   480  
   481  		opts.SinceTime = &meta_v1.Time{Time: time.Unix(numTime, 0)}
   482  	}
   483  
   484  	if maxLines != "" {
   485  		if numLines, err := strconv.Atoi(maxLines); err == nil {
   486  			if numLines > 0 {
   487  				opts.MaxLines = &numLines
   488  			}
   489  		} else {
   490  			return nil, fmt.Errorf("invalid maxLines [%s]: %v", maxLines, err)
   491  		}
   492  	}
   493  
   494  	return opts, nil
   495  }
   496  
   497  func parseLogLine(line string, isProxy bool, engardeParser *parser.Parser) *LogEntry {
   498  	entry := LogEntry{
   499  		Message:       "",
   500  		Timestamp:     "",
   501  		TimestampUnix: 0,
   502  		Severity:      "INFO",
   503  	}
   504  
   505  	splitted := strings.SplitN(line, " ", 2)
   506  	if len(splitted) != 2 {
   507  		log.Debugf("Skipping unexpected log line [%s]", line)
   508  		return nil
   509  	}
   510  
   511  	// k8s promises RFC3339 or RFC3339Nano timestamp, ensure RFC3339
   512  	// Split by blanks, to get the miliseconds for sorting, try RFC3339Nano
   513  	entry.Timestamp = splitted[0]
   514  
   515  	entry.Message = strings.TrimSpace(splitted[1])
   516  	if entry.Message == "" {
   517  		log.Debugf("Skipping empty log line [%s]", line)
   518  		return nil
   519  	}
   520  
   521  	// If we are past the requested time window then stop processing
   522  	parsedTimestamp, err := time.Parse(time.RFC3339Nano, entry.Timestamp)
   523  	entry.OriginalTime = parsedTimestamp
   524  	if err != nil {
   525  		log.Debugf("Failed to parse log timestamp (skipping) [%s], %s", entry.Timestamp, err.Error())
   526  		return nil
   527  	}
   528  
   529  	severity := severityRegexp.FindString(line)
   530  	if severity != "" {
   531  		entry.Severity = strings.ToUpper(severity)
   532  	}
   533  
   534  	// If this is an istio access log, then parse it out. Prefer the access log time over the k8s time
   535  	// as it is the actual time as opposed to the k8s store time.
   536  	if isProxy {
   537  		al, err := engardeParser.Parse(entry.Message)
   538  		// engardeParser.Parse will not throw errors even if no fields
   539  		// were parsed out. Checking here that some fields were actually
   540  		// set before setting the AccessLog to an empty object. See issue #4346.
   541  		if err != nil || isAccessLogEmpty(al) {
   542  			if err != nil {
   543  				log.Debugf("AccessLog parse failure: %s", err.Error())
   544  			}
   545  			// try to parse out the time manually
   546  			tokens := strings.SplitN(entry.Message, " ", 2)
   547  			timestampToken := strings.Trim(tokens[0], "[]")
   548  			t, err := time.Parse(time.RFC3339, timestampToken)
   549  			if err == nil {
   550  				parsedTimestamp = t
   551  			}
   552  		} else {
   553  			entry.AccessLog = al
   554  			t, err := time.Parse(time.RFC3339, al.Timestamp)
   555  			if err == nil {
   556  				parsedTimestamp = t
   557  			}
   558  
   559  			// clear accessLog fields we don't need in the returned JSON
   560  			entry.AccessLog.MixerStatus = ""
   561  			entry.AccessLog.OriginalMessage = ""
   562  			entry.AccessLog.ParseError = ""
   563  		}
   564  	}
   565  
   566  	// override the timestamp with a simpler format
   567  	timestamp := parseTimestamp(parsedTimestamp)
   568  	entry.Timestamp = timestamp
   569  	entry.TimestampUnix = parsedTimestamp.UnixMilli()
   570  
   571  	return &entry
   572  }
   573  
   574  func parseZtunnelLine(line string) *LogEntry {
   575  	entry := LogEntry{
   576  		Message:       "",
   577  		Timestamp:     "",
   578  		TimestampUnix: 0,
   579  		Severity:      "INFO",
   580  	}
   581  
   582  	splitted := strings.SplitN(line, " ", 2)
   583  	if len(splitted) != 2 {
   584  		log.Debugf("Skipping unexpected log line [%s]", line)
   585  		return nil
   586  	}
   587  
   588  	msgSplit := strings.Split(line, "\t")
   589  
   590  	if len(msgSplit) < 5 {
   591  		log.Debugf("Error splitting log line [%s]", line)
   592  		entry.Message = line
   593  		return &entry
   594  	}
   595  
   596  	entry.Message = msgSplit[4]
   597  	if entry.Message == "" {
   598  		log.Debugf("Skipping empty log line [%s]", line)
   599  		entry.Message = line
   600  		return &entry
   601  	}
   602  
   603  	// k8s promises RFC3339 or RFC3339Nano timestamp, ensure RFC3339
   604  	// Split by blanks, to get the milliseconds for sorting, try RFC3339Nano
   605  	ts := strings.Split(msgSplit[0], " ") // Sometime timestamp is duplicated
   606  	entry.Timestamp = ts[0]
   607  
   608  	// If we are past the requested time window then stop processing
   609  	parsedTimestamp, err := time.Parse(time.RFC3339Nano, entry.Timestamp)
   610  	entry.OriginalTime = parsedTimestamp
   611  	if err != nil {
   612  		log.Debugf("Failed to parse log timestamp (skipping) [%s], %s", entry.Timestamp, err.Error())
   613  		return nil
   614  	}
   615  
   616  	if splitted[1] != "" {
   617  		entry.Severity = strings.ToUpper(splitted[1])
   618  	}
   619  
   620  	// override the timestamp with a simpler format
   621  	timestamp := parseTimestamp(parsedTimestamp)
   622  	entry.Timestamp = timestamp
   623  	entry.TimestampUnix = parsedTimestamp.UnixMilli()
   624  
   625  	// Process some access log data
   626  	// More validations can be done. Data is in format direction=outbound
   627  	// Also, more data could be added?
   628  	al := parser.AccessLog{}
   629  	al.Timestamp = timestamp
   630  	if len(msgSplit) > 4 {
   631  		accessLog := strings.Split(msgSplit[4], " ")
   632  		for _, field := range accessLog {
   633  			parsed := strings.SplitN(field, "=", 2)
   634  			if len(parsed) == 2 {
   635  				parsed[1] = strings.Replace(parsed[1], "\"", "", -1)
   636  				switch parsed[0] {
   637  				case "src.identity":
   638  					al.UpstreamCluster = parsed[1]
   639  				case "duration":
   640  					al.Duration = parsed[1]
   641  				case "bytes_recv":
   642  					al.BytesReceived = parsed[1]
   643  				case "bytes_sent":
   644  					al.BytesSent = parsed[1]
   645  				case "dst.service":
   646  					al.RequestedServer = parsed[1]
   647  				case "error":
   648  					al.ParseError = parsed[1]
   649  				case "dst.addr":
   650  					al.UpstreamService = parsed[1]
   651  				case "src.addr":
   652  					al.DownstreamRemote = parsed[1]
   653  				}
   654  			}
   655  		}
   656  	}
   657  
   658  	entry.AccessLog = &al
   659  
   660  	return &entry
   661  }
   662  
   663  func parseTimestamp(parsedTimestamp time.Time) string {
   664  	precision := strings.Split(parsedTimestamp.String(), ".")
   665  	var milliseconds string
   666  	if len(precision) > 1 {
   667  		ms := precision[1]
   668  		milliseconds = ms[:3]
   669  		splittedms := strings.Fields(milliseconds) // This is needed to avoid invalid dates in ms like 200
   670  		milliseconds = splittedms[0]
   671  	} else {
   672  		milliseconds = "000"
   673  	}
   674  
   675  	timestamp := fmt.Sprintf("%d-%02d-%02d %02d:%02d:%02d.%s",
   676  		parsedTimestamp.Year(), parsedTimestamp.Month(), parsedTimestamp.Day(),
   677  		parsedTimestamp.Hour(), parsedTimestamp.Minute(), parsedTimestamp.Second(), milliseconds)
   678  	return timestamp
   679  }
   680  
   681  func isAccessLogEmpty(al *parser.AccessLog) bool {
   682  	if al == nil {
   683  		return true
   684  	}
   685  
   686  	return (al.Timestamp == "" &&
   687  		al.Authority == "" &&
   688  		al.BytesReceived == "" &&
   689  		al.BytesSent == "" &&
   690  		al.DownstreamLocal == "" &&
   691  		al.DownstreamRemote == "" &&
   692  		al.Duration == "" &&
   693  		al.ForwardedFor == "" &&
   694  		al.Method == "" &&
   695  		al.MixerStatus == "" &&
   696  		al.Protocol == "" &&
   697  		al.RequestId == "" &&
   698  		al.RequestedServer == "" &&
   699  		al.ResponseFlags == "" &&
   700  		al.RouteName == "" &&
   701  		al.StatusCode == "" &&
   702  		al.TcpServiceTime == "" &&
   703  		al.UpstreamCluster == "" &&
   704  		al.UpstreamFailureReason == "" &&
   705  		al.UpstreamLocal == "" &&
   706  		al.UpstreamService == "" &&
   707  		al.UpstreamServiceTime == "" &&
   708  		al.UriParam == "" &&
   709  		al.UriPath == "" &&
   710  		al.UserAgent == "")
   711  }
   712  
   713  func (in *WorkloadService) fetchWorkloads(ctx context.Context, namespace string, labelSelector string) (models.Workloads, error) {
   714  	allWls := models.Workloads{}
   715  	for c := range in.userClients {
   716  		ws, err := in.fetchWorkloadsFromCluster(ctx, c, namespace, labelSelector)
   717  		if err != nil {
   718  			if errors.IsNotFound(err) || errors.IsForbidden(err) {
   719  				// If a cluster is not found or not accessible, then we skip it
   720  				log.Debugf("Error while accessing to cluster [%s]: %s", c, err.Error())
   721  				continue
   722  			} else {
   723  				// On any other error, abort and return the error.
   724  				return nil, err
   725  			}
   726  		} else {
   727  			allWls = append(allWls, ws...)
   728  		}
   729  	}
   730  
   731  	return allWls, nil
   732  }
   733  
   734  func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluster string, namespace string, labelSelector string) (models.Workloads, error) {
   735  	var pods []core_v1.Pod
   736  	var repcon []core_v1.ReplicationController
   737  	var dep []apps_v1.Deployment
   738  	var repset []apps_v1.ReplicaSet
   739  	var depcon []osapps_v1.DeploymentConfig
   740  	var fulset []apps_v1.StatefulSet
   741  	var jbs []batch_v1.Job
   742  	var conjbs []batch_v1.CronJob
   743  	var daeset []apps_v1.DaemonSet
   744  
   745  	ws := models.Workloads{}
   746  
   747  	// Check if user has access to the namespace (RBAC) in cache scenarios and/or
   748  	// if namespace is accessible from Kiali (Deployment.AccessibleNamespaces)
   749  	if _, err := in.businessLayer.Namespace.GetClusterNamespace(ctx, namespace, cluster); err != nil {
   750  		return nil, err
   751  	}
   752  
   753  	userClient, ok := in.userClients[cluster]
   754  	if !ok {
   755  		return nil, fmt.Errorf("Cluster [%s] is not found or is not accessible for Kiali", cluster)
   756  	}
   757  
   758  	kubeCache := in.cache.GetKubeCaches()[cluster]
   759  	if kubeCache == nil {
   760  		return nil, fmt.Errorf("Cluster [%s] is not found or is not accessible for Kiali", cluster)
   761  	}
   762  
   763  	wg := sync.WaitGroup{}
   764  	wg.Add(9)
   765  	errChan := make(chan error, 9)
   766  
   767  	// Pods are always fetched
   768  	go func() {
   769  		defer wg.Done()
   770  		var err error
   771  		pods, err = kubeCache.GetPods(namespace, labelSelector)
   772  		if err != nil {
   773  			log.Errorf("Error fetching Pods per namespace %s: %s", namespace, err)
   774  			errChan <- err
   775  		}
   776  	}()
   777  
   778  	// Deployments are always fetched
   779  	go func() {
   780  		defer wg.Done()
   781  		var err error
   782  		dep, err = kubeCache.GetDeployments(namespace)
   783  		if err != nil {
   784  			log.Errorf("Error fetching Deployments per namespace %s: %s", namespace, err)
   785  			errChan <- err
   786  		}
   787  	}()
   788  
   789  	// ReplicaSets are always fetched
   790  	go func() {
   791  		defer wg.Done()
   792  		var err error
   793  		repset, err = kubeCache.GetReplicaSets(namespace)
   794  		if err != nil {
   795  			log.Errorf("Error fetching ReplicaSets per namespace %s: %s", namespace, err)
   796  			errChan <- err
   797  		}
   798  	}()
   799  
   800  	// ReplicaControllers are fetched only when included
   801  	go func() {
   802  		defer wg.Done()
   803  
   804  		var err error
   805  		if in.isWorkloadIncluded(kubernetes.ReplicationControllerType) {
   806  			// No Cache for ReplicationControllers
   807  			repcon, err = userClient.GetReplicationControllers(namespace)
   808  			if err != nil {
   809  				log.Errorf("Error fetching GetReplicationControllers per namespace %s: %s", namespace, err)
   810  				errChan <- err
   811  			}
   812  		}
   813  	}()
   814  
   815  	// DeploymentConfigs are fetched only when included
   816  	go func() {
   817  		defer wg.Done()
   818  
   819  		var err error
   820  		if userClient.IsOpenShift() && in.isWorkloadIncluded(kubernetes.DeploymentConfigType) {
   821  			// No cache for DeploymentConfigs
   822  			depcon, err = userClient.GetDeploymentConfigs(ctx, namespace)
   823  			if err != nil {
   824  				log.Errorf("Error fetching DeploymentConfigs per namespace %s: %s", namespace, err)
   825  				errChan <- err
   826  			}
   827  		}
   828  	}()
   829  
   830  	// StatefulSets are fetched only when included
   831  	go func() {
   832  		defer wg.Done()
   833  
   834  		var err error
   835  		if in.isWorkloadIncluded(kubernetes.StatefulSetType) {
   836  			fulset, err = kubeCache.GetStatefulSets(namespace)
   837  			if err != nil {
   838  				log.Errorf("Error fetching StatefulSets per namespace %s: %s", namespace, err)
   839  				errChan <- err
   840  			}
   841  		}
   842  	}()
   843  
   844  	// CronJobs are fetched only when included
   845  	go func() {
   846  		defer wg.Done()
   847  
   848  		var err error
   849  		if in.isWorkloadIncluded(kubernetes.CronJobType) {
   850  			// No cache for Cronjobs
   851  			conjbs, err = userClient.GetCronJobs(namespace)
   852  			if err != nil {
   853  				log.Errorf("Error fetching CronJobs per namespace %s: %s", namespace, err)
   854  				errChan <- err
   855  			}
   856  		}
   857  	}()
   858  
   859  	// Jobs are fetched only when included
   860  	go func() {
   861  		defer wg.Done()
   862  
   863  		var err error
   864  		if in.isWorkloadIncluded(kubernetes.JobType) {
   865  			// No cache for Jobs
   866  			jbs, err = userClient.GetJobs(namespace)
   867  			if err != nil {
   868  				log.Errorf("Error fetching Jobs per namespace %s: %s", namespace, err)
   869  				errChan <- err
   870  			}
   871  		}
   872  	}()
   873  
   874  	// DaemonSets are fetched only when included
   875  	go func() {
   876  		defer wg.Done()
   877  
   878  		var err error
   879  		if in.isWorkloadIncluded(kubernetes.DaemonSetType) {
   880  			daeset, err = kubeCache.GetDaemonSets(namespace)
   881  			if err != nil {
   882  				log.Errorf("Error fetching DaemonSets per namespace %s: %s", namespace, err)
   883  			}
   884  		}
   885  	}()
   886  
   887  	wg.Wait()
   888  	if len(errChan) != 0 {
   889  		err := <-errChan
   890  		return ws, err
   891  	}
   892  
   893  	// Key: name of controller; Value: type of controller
   894  	controllers := map[string]string{}
   895  
   896  	// Find controllers from pods
   897  	for _, pod := range pods {
   898  		if len(pod.OwnerReferences) != 0 {
   899  			for _, ref := range pod.OwnerReferences {
   900  				if ref.Controller != nil && *ref.Controller && in.isWorkloadIncluded(ref.Kind) {
   901  					if _, exist := controllers[ref.Name]; !exist {
   902  						controllers[ref.Name] = ref.Kind
   903  					} else {
   904  						if controllers[ref.Name] != ref.Kind {
   905  							controllers[ref.Name] = controllerPriority(controllers[ref.Name], ref.Kind)
   906  						}
   907  					}
   908  				}
   909  			}
   910  		} else {
   911  			if _, exist := controllers[pod.Name]; !exist {
   912  				// Pod without controller
   913  				controllers[pod.Name] = "Pod"
   914  			}
   915  		}
   916  	}
   917  
   918  	// Resolve ReplicaSets from Deployments
   919  	// Resolve ReplicationControllers from DeploymentConfigs
   920  	// Resolve Jobs from CronJobs
   921  	for controllerName, controllerType := range controllers {
   922  		if controllerType == kubernetes.ReplicaSetType {
   923  			found := false
   924  			iFound := -1
   925  			for i, rs := range repset {
   926  				if rs.Name == controllerName {
   927  					iFound = i
   928  					found = true
   929  					break
   930  				}
   931  			}
   932  			if found && len(repset[iFound].OwnerReferences) > 0 {
   933  				for _, ref := range repset[iFound].OwnerReferences {
   934  					if ref.Controller != nil && *ref.Controller {
   935  						if _, exist := controllers[ref.Name]; !exist {
   936  							// For valid owner controllers, delete the child ReplicaSet and add the parent controller,
   937  							// otherwise (for custom controllers), defer to the replica set.
   938  							if in.isWorkloadValid(ref.Kind) {
   939  								controllers[ref.Name] = ref.Kind
   940  								delete(controllers, controllerName)
   941  							} else {
   942  								log.Debugf("Add ReplicaSet to workload list for custom controller [%s][%s]", ref.Name, ref.Kind)
   943  							}
   944  						} else {
   945  							if controllers[ref.Name] != ref.Kind {
   946  								controllers[ref.Name] = controllerPriority(controllers[ref.Name], ref.Kind)
   947  							}
   948  							delete(controllers, controllerName)
   949  						}
   950  					}
   951  				}
   952  			}
   953  		}
   954  		if controllerType == kubernetes.ReplicationControllerType {
   955  			found := false
   956  			iFound := -1
   957  			for i, rc := range repcon {
   958  				if rc.Name == controllerName {
   959  					iFound = i
   960  					found = true
   961  					break
   962  				}
   963  			}
   964  			if found && len(repcon[iFound].OwnerReferences) > 0 {
   965  				for _, ref := range repcon[iFound].OwnerReferences {
   966  					if ref.Controller != nil && *ref.Controller {
   967  						// Delete the child ReplicationController and add the parent controller
   968  						if _, exist := controllers[ref.Name]; !exist {
   969  							controllers[ref.Name] = ref.Kind
   970  						} else {
   971  							if controllers[ref.Name] != ref.Kind {
   972  								controllers[ref.Name] = controllerPriority(controllers[ref.Name], ref.Kind)
   973  							}
   974  						}
   975  						delete(controllers, controllerName)
   976  					}
   977  				}
   978  			}
   979  		}
   980  		if controllerType == kubernetes.JobType {
   981  			found := false
   982  			iFound := -1
   983  			for i, jb := range jbs {
   984  				if jb.Name == controllerName {
   985  					iFound = i
   986  					found = true
   987  					break
   988  				}
   989  			}
   990  			if found && len(jbs[iFound].OwnerReferences) > 0 {
   991  				for _, ref := range jbs[iFound].OwnerReferences {
   992  					if ref.Controller != nil && *ref.Controller {
   993  						// Delete the child Job and add the parent controller
   994  						if _, exist := controllers[ref.Name]; !exist {
   995  							controllers[ref.Name] = ref.Kind
   996  						} else {
   997  							if controllers[ref.Name] != ref.Kind {
   998  								controllers[ref.Name] = controllerPriority(controllers[ref.Name], ref.Kind)
   999  							}
  1000  						}
  1001  						// Jobs are special as deleting CronJob parent doesn't delete children
  1002  						// So we need to check that parent exists before to delete children controller
  1003  						cnExist := false
  1004  						for _, cnj := range conjbs {
  1005  							if cnj.Name == ref.Name {
  1006  								cnExist = true
  1007  								break
  1008  							}
  1009  						}
  1010  						if cnExist {
  1011  							delete(controllers, controllerName)
  1012  						}
  1013  					}
  1014  				}
  1015  			}
  1016  		}
  1017  	}
  1018  
  1019  	// Cornercase, check for controllers without pods, to show them as a workload
  1020  	var selector labels.Selector
  1021  	var selErr error
  1022  	if labelSelector != "" {
  1023  		selector, selErr = labels.Parse(labelSelector)
  1024  		if selErr != nil {
  1025  			log.Errorf("%s can not be processed as selector: %v", labelSelector, selErr)
  1026  		}
  1027  	}
  1028  	for _, d := range dep {
  1029  		selectorCheck := true
  1030  		if selector != nil {
  1031  			selectorCheck = selector.Matches(labels.Set(d.Spec.Template.Labels))
  1032  		}
  1033  		if _, exist := controllers[d.Name]; !exist && selectorCheck {
  1034  			controllers[d.Name] = "Deployment"
  1035  		}
  1036  	}
  1037  	for _, rs := range repset {
  1038  		selectorCheck := true
  1039  		if selector != nil {
  1040  			selectorCheck = selector.Matches(labels.Set(rs.Spec.Template.Labels))
  1041  		}
  1042  		if _, exist := controllers[rs.Name]; !exist && len(rs.OwnerReferences) == 0 && selectorCheck {
  1043  			controllers[rs.Name] = "ReplicaSet"
  1044  		}
  1045  	}
  1046  	for _, dc := range depcon {
  1047  		selectorCheck := true
  1048  		if selector != nil {
  1049  			selectorCheck = selector.Matches(labels.Set(dc.Spec.Template.Labels))
  1050  		}
  1051  		if _, exist := controllers[dc.Name]; !exist && selectorCheck {
  1052  			controllers[dc.Name] = "DeploymentConfig"
  1053  		}
  1054  	}
  1055  	for _, rc := range repcon {
  1056  		selectorCheck := true
  1057  		if selector != nil {
  1058  			selectorCheck = selector.Matches(labels.Set(rc.Spec.Template.Labels))
  1059  		}
  1060  		if _, exist := controllers[rc.Name]; !exist && len(rc.OwnerReferences) == 0 && selectorCheck {
  1061  			controllers[rc.Name] = "ReplicationController"
  1062  		}
  1063  	}
  1064  	for _, fs := range fulset {
  1065  		selectorCheck := true
  1066  		if selector != nil {
  1067  			selectorCheck = selector.Matches(labels.Set(fs.Spec.Template.Labels))
  1068  		}
  1069  		if _, exist := controllers[fs.Name]; !exist && selectorCheck {
  1070  			controllers[fs.Name] = "StatefulSet"
  1071  		}
  1072  	}
  1073  	for _, ds := range daeset {
  1074  		selectorCheck := true
  1075  		if selector != nil {
  1076  			selectorCheck = selector.Matches(labels.Set(ds.Spec.Template.Labels))
  1077  		}
  1078  		if _, exist := controllers[ds.Name]; !exist && selectorCheck {
  1079  			controllers[ds.Name] = "DaemonSet"
  1080  		}
  1081  	}
  1082  
  1083  	// Build workloads from controllers
  1084  	var controllerNames []string
  1085  	for k := range controllers {
  1086  		controllerNames = append(controllerNames, k)
  1087  	}
  1088  	sort.Strings(controllerNames)
  1089  	for _, controllerName := range controllerNames {
  1090  		w := &models.Workload{
  1091  			Pods:     models.Pods{},
  1092  			Services: []models.ServiceOverview{},
  1093  		}
  1094  		w.Cluster = cluster
  1095  		w.Namespace = namespace
  1096  		controllerType := controllers[controllerName]
  1097  		// Flag to add a controller if it is found
  1098  		cnFound := true
  1099  		switch controllerType {
  1100  		case kubernetes.DeploymentType:
  1101  			found := false
  1102  			iFound := -1
  1103  			for i, dp := range dep {
  1104  				if dp.Name == controllerName {
  1105  					found = true
  1106  					iFound = i
  1107  					break
  1108  				}
  1109  			}
  1110  			if found {
  1111  				selector := labels.Set(dep[iFound].Spec.Template.Labels).AsSelector()
  1112  				w.SetPods(kubernetes.FilterPodsBySelector(selector, pods))
  1113  				w.ParseDeployment(&dep[iFound])
  1114  			} else {
  1115  				log.Errorf("Workload %s is not found as Deployment", controllerName)
  1116  				cnFound = false
  1117  			}
  1118  		case kubernetes.ReplicaSetType:
  1119  			found := false
  1120  			iFound := -1
  1121  			for i, rs := range repset {
  1122  				if rs.Name == controllerName {
  1123  					found = true
  1124  					iFound = i
  1125  					break
  1126  				}
  1127  			}
  1128  			if found {
  1129  				selector := labels.Set(repset[iFound].Spec.Template.Labels).AsSelector()
  1130  				w.SetPods(kubernetes.FilterPodsBySelector(selector, pods))
  1131  				w.ParseReplicaSet(&repset[iFound])
  1132  			} else {
  1133  				log.Errorf("Workload %s is not found as ReplicaSet", controllerName)
  1134  				cnFound = false
  1135  			}
  1136  		case kubernetes.ReplicationControllerType:
  1137  			found := false
  1138  			iFound := -1
  1139  			for i, rc := range repcon {
  1140  				if rc.Name == controllerName {
  1141  					found = true
  1142  					iFound = i
  1143  					break
  1144  				}
  1145  			}
  1146  			if found {
  1147  				selector := labels.Set(repcon[iFound].Spec.Template.Labels).AsSelector()
  1148  				w.SetPods(kubernetes.FilterPodsBySelector(selector, pods))
  1149  				w.ParseReplicationController(&repcon[iFound])
  1150  			} else {
  1151  				log.Errorf("Workload %s is not found as ReplicationController", controllerName)
  1152  				cnFound = false
  1153  			}
  1154  		case kubernetes.DeploymentConfigType:
  1155  			found := false
  1156  			iFound := -1
  1157  			for i, dc := range depcon {
  1158  				if dc.Name == controllerName {
  1159  					found = true
  1160  					iFound = i
  1161  					break
  1162  				}
  1163  			}
  1164  			if found {
  1165  				selector := labels.Set(depcon[iFound].Spec.Template.Labels).AsSelector()
  1166  				w.SetPods(kubernetes.FilterPodsBySelector(selector, pods))
  1167  				w.ParseDeploymentConfig(&depcon[iFound])
  1168  			} else {
  1169  				log.Errorf("Workload %s is not found as DeploymentConfig", controllerName)
  1170  				cnFound = false
  1171  			}
  1172  		case kubernetes.StatefulSetType:
  1173  			found := false
  1174  			iFound := -1
  1175  			for i, fs := range fulset {
  1176  				if fs.Name == controllerName {
  1177  					found = true
  1178  					iFound = i
  1179  					break
  1180  				}
  1181  			}
  1182  			if found {
  1183  				selector := labels.Set(fulset[iFound].Spec.Template.Labels).AsSelector()
  1184  				w.SetPods(kubernetes.FilterPodsBySelector(selector, pods))
  1185  				w.ParseStatefulSet(&fulset[iFound])
  1186  			} else {
  1187  				log.Errorf("Workload %s is not found as StatefulSet", controllerName)
  1188  				cnFound = false
  1189  			}
  1190  		case kubernetes.PodType:
  1191  			found := false
  1192  			iFound := -1
  1193  			for i, pod := range pods {
  1194  				if pod.Name == controllerName {
  1195  					found = true
  1196  					iFound = i
  1197  					break
  1198  				}
  1199  			}
  1200  			if found {
  1201  				w.SetPods([]core_v1.Pod{pods[iFound]})
  1202  				w.ParsePod(&pods[iFound])
  1203  			} else {
  1204  				log.Errorf("Workload %s is not found as Pod", controllerName)
  1205  				cnFound = false
  1206  			}
  1207  		case kubernetes.JobType:
  1208  			found := false
  1209  			iFound := -1
  1210  			for i, jb := range jbs {
  1211  				if jb.Name == controllerName {
  1212  					found = true
  1213  					iFound = i
  1214  					break
  1215  				}
  1216  			}
  1217  			if found {
  1218  				selector := labels.Set(jbs[iFound].Spec.Template.Labels).AsSelector()
  1219  				w.SetPods(kubernetes.FilterPodsBySelector(selector, pods))
  1220  				w.ParseJob(&jbs[iFound])
  1221  			} else {
  1222  				log.Errorf("Workload %s is not found as Job", controllerName)
  1223  				cnFound = false
  1224  			}
  1225  		case kubernetes.CronJobType:
  1226  			found := false
  1227  			iFound := -1
  1228  			for i, cjb := range conjbs {
  1229  				if cjb.Name == controllerName {
  1230  					found = true
  1231  					iFound = i
  1232  					break
  1233  				}
  1234  			}
  1235  			if found {
  1236  				selector := labels.Set(conjbs[iFound].Spec.JobTemplate.Spec.Template.Labels).AsSelector()
  1237  				w.SetPods(kubernetes.FilterPodsBySelector(selector, pods))
  1238  				w.ParseCronJob(&conjbs[iFound])
  1239  			} else {
  1240  				log.Warningf("Workload %s is not found as CronJob (CronJob could be deleted but children are still in the namespace)", controllerName)
  1241  				cnFound = false
  1242  			}
  1243  		case kubernetes.DaemonSetType:
  1244  			found := false
  1245  			iFound := -1
  1246  			for i, ds := range daeset {
  1247  				if ds.Name == controllerName {
  1248  					found = true
  1249  					iFound = i
  1250  					break
  1251  				}
  1252  			}
  1253  			if found {
  1254  				selector := labels.Set(daeset[iFound].Spec.Template.Labels).AsSelector()
  1255  				w.SetPods(kubernetes.FilterPodsBySelector(selector, pods))
  1256  				w.ParseDaemonSet(&daeset[iFound])
  1257  			} else {
  1258  				log.Errorf("Workload %s is not found as Deployment", controllerName)
  1259  				cnFound = false
  1260  			}
  1261  		default:
  1262  			// This covers the scenario of a custom controller without replicaset, controlling pods directly.
  1263  			// Note that a custom controller with replicaset(s) will return the replicaset(s) as the workloads.
  1264  			var cPods []core_v1.Pod
  1265  			cPods = kubernetes.FilterPodsByController(controllerName, controllerType, pods)
  1266  			if len(cPods) > 0 {
  1267  				w.ParsePods(controllerName, controllerType, cPods)
  1268  				log.Debugf("Workload %s of type %s has no ReplicaSet as a child controller (this may be ok, but is unusual)", controllerName, controllerType)
  1269  			}
  1270  			w.SetPods(cPods)
  1271  		}
  1272  
  1273  		// Add the Proxy Status to the workload
  1274  		for _, pod := range w.Pods {
  1275  			if pod.HasIstioSidecar() && !w.IsGateway() && config.Get().ExternalServices.Istio.IstioAPIEnabled {
  1276  				pod.ProxyStatus = in.businessLayer.ProxyStatus.GetPodProxyStatus(cluster, namespace, pod.Name)
  1277  			}
  1278  		}
  1279  
  1280  		if cnFound {
  1281  			ws = append(ws, w)
  1282  		}
  1283  	}
  1284  	return ws, nil
  1285  }
  1286  
  1287  func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadCriteria) (*models.Workload, error) {
  1288  	var pods []core_v1.Pod
  1289  	var repcon []core_v1.ReplicationController
  1290  	var dep *apps_v1.Deployment
  1291  	var repset []apps_v1.ReplicaSet
  1292  	var depcon *osapps_v1.DeploymentConfig
  1293  	var fulset *apps_v1.StatefulSet
  1294  	var jbs []batch_v1.Job
  1295  	var conjbs []batch_v1.CronJob
  1296  	var ds *apps_v1.DaemonSet
  1297  
  1298  	wl := &models.Workload{
  1299  		WorkloadListItem: models.WorkloadListItem{
  1300  			Cluster:   criteria.Cluster,
  1301  			Namespace: criteria.Namespace,
  1302  		},
  1303  		Pods:              models.Pods{},
  1304  		Services:          []models.ServiceOverview{},
  1305  		Runtimes:          []models.Runtime{},
  1306  		AdditionalDetails: []models.AdditionalItem{},
  1307  		Health:            *models.EmptyWorkloadHealth(),
  1308  	}
  1309  
  1310  	// Check if user has access to the namespace (RBAC) in cache scenarios and/or
  1311  	// if namespace is accessible from Kiali (Deployment.AccessibleNamespaces)
  1312  	if _, err := in.businessLayer.Namespace.GetClusterNamespace(ctx, criteria.Namespace, criteria.Cluster); err != nil {
  1313  		return nil, err
  1314  	}
  1315  
  1316  	// Flag used for custom controllers
  1317  	// i.e. a third party framework creates its own "Deployment" controller with extra features
  1318  	// on this case, Kiali will collect basic info from the ReplicaSet controller
  1319  	_, knownWorkloadType := controllerOrder[criteria.WorkloadType]
  1320  
  1321  	wg := sync.WaitGroup{}
  1322  	wg.Add(9)
  1323  	errChan := make(chan error, 9)
  1324  
  1325  	kialiCache, err := in.cache.GetKubeCache(criteria.Cluster)
  1326  	if err != nil {
  1327  		return nil, err
  1328  	}
  1329  
  1330  	client, ok := in.userClients[criteria.Cluster]
  1331  	if !ok {
  1332  		return nil, fmt.Errorf("no user client for cluster [%s]", criteria.Cluster)
  1333  	}
  1334  
  1335  	// Pods are always fetched for all workload types
  1336  	go func() {
  1337  		defer wg.Done()
  1338  		var err error
  1339  		pods, err = kialiCache.GetPods(criteria.Namespace, "")
  1340  		if err != nil {
  1341  			log.Errorf("Error fetching Pods per namespace %s: %s", criteria.Namespace, err)
  1342  			errChan <- err
  1343  		}
  1344  	}()
  1345  
  1346  	// fetch as Deployment when workloadType is Deployment or unspecified
  1347  	go func() {
  1348  		defer wg.Done()
  1349  		var err error
  1350  
  1351  		if criteria.WorkloadType != "" && criteria.WorkloadType != kubernetes.DeploymentType {
  1352  			return
  1353  		}
  1354  		dep, err = kialiCache.GetDeployment(criteria.Namespace, criteria.WorkloadName)
  1355  		if err != nil {
  1356  			if errors.IsNotFound(err) {
  1357  				dep = nil
  1358  			} else {
  1359  				log.Errorf("Error fetching Deployment per namespace %s and name %s: %s", criteria.Namespace, criteria.WorkloadName, err)
  1360  				errChan <- err
  1361  			}
  1362  		}
  1363  	}()
  1364  
  1365  	// fetch as ReplicaSet(s) when workloadType is ReplicaSet, unspecified, *or custom*
  1366  	go func() {
  1367  		defer wg.Done()
  1368  
  1369  		if criteria.WorkloadType != "" && criteria.WorkloadType != kubernetes.ReplicaSetType && knownWorkloadType {
  1370  			return
  1371  		}
  1372  		var err error
  1373  		repset, err = kialiCache.GetReplicaSets(criteria.Namespace)
  1374  		if err != nil {
  1375  			log.Errorf("Error fetching ReplicaSets per namespace %s: %s", criteria.Namespace, err)
  1376  			errChan <- err
  1377  		}
  1378  	}()
  1379  
  1380  	// fetch as ReplicationControllerType when included, and workloadType is ReplicationControllerType or unspecified
  1381  	go func() {
  1382  		defer wg.Done()
  1383  
  1384  		if criteria.WorkloadType != "" && criteria.WorkloadType != kubernetes.ReplicationControllerType {
  1385  			return
  1386  		}
  1387  
  1388  		var err error
  1389  		if in.isWorkloadIncluded(kubernetes.ReplicationControllerType) {
  1390  			// No cache for ReplicationControllers
  1391  			repcon, err = client.GetReplicationControllers(criteria.Namespace)
  1392  			if err != nil {
  1393  				log.Errorf("Error fetching GetReplicationControllers per namespace %s: %s", criteria.Namespace, err)
  1394  				errChan <- err
  1395  			}
  1396  		}
  1397  	}()
  1398  
  1399  	// fetch as DeploymentConfigType when included, and workloadType is DeploymentConfigType or unspecified
  1400  	go func() {
  1401  		defer wg.Done()
  1402  
  1403  		if criteria.WorkloadType != "" && criteria.WorkloadType != kubernetes.DeploymentConfigType {
  1404  			return
  1405  		}
  1406  
  1407  		var err error
  1408  		if client.IsOpenShift() && in.isWorkloadIncluded(kubernetes.DeploymentConfigType) {
  1409  			// No cache for deploymentConfigs
  1410  			depcon, err = client.GetDeploymentConfig(ctx, criteria.Namespace, criteria.WorkloadName)
  1411  			if err != nil {
  1412  				depcon = nil
  1413  			}
  1414  		}
  1415  	}()
  1416  
  1417  	// fetch as StatefulSetType when included, and workloadType is StatefulSetType or unspecified
  1418  	go func() {
  1419  		defer wg.Done()
  1420  
  1421  		if criteria.WorkloadType != "" && criteria.WorkloadType != kubernetes.StatefulSetType {
  1422  			return
  1423  		}
  1424  
  1425  		var err error
  1426  		if in.isWorkloadIncluded(kubernetes.StatefulSetType) {
  1427  			fulset, err = kialiCache.GetStatefulSet(criteria.Namespace, criteria.WorkloadName)
  1428  			if err != nil {
  1429  				fulset = nil
  1430  			}
  1431  		}
  1432  	}()
  1433  
  1434  	// fetch as CronJobType when included, and workloadType is CronJobType or unspecified
  1435  	go func() {
  1436  		defer wg.Done()
  1437  
  1438  		if criteria.WorkloadType != "" && criteria.WorkloadType != kubernetes.CronJobType {
  1439  			return
  1440  		}
  1441  
  1442  		var err error
  1443  		if in.isWorkloadIncluded(kubernetes.CronJobType) {
  1444  			// No cache for CronJobs
  1445  			conjbs, err = client.GetCronJobs(criteria.Namespace)
  1446  			if err != nil {
  1447  				log.Errorf("Error fetching CronJobs per namespace %s: %s", criteria.Namespace, err)
  1448  				errChan <- err
  1449  			}
  1450  		}
  1451  	}()
  1452  
  1453  	// fetch as JobType when included, and workloadType is JobType or unspecified
  1454  	go func() {
  1455  		defer wg.Done()
  1456  
  1457  		if criteria.WorkloadType != "" && criteria.WorkloadType != kubernetes.JobType {
  1458  			return
  1459  		}
  1460  
  1461  		var err error
  1462  		if in.isWorkloadIncluded(kubernetes.JobType) {
  1463  			// No cache for Jobs
  1464  			jbs, err = client.GetJobs(criteria.Namespace)
  1465  			if err != nil {
  1466  				log.Errorf("Error fetching Jobs per namespace %s: %s", criteria.Namespace, err)
  1467  				errChan <- err
  1468  			}
  1469  		}
  1470  	}()
  1471  
  1472  	// fetch as DaemonSetType when included, and workloadType is DaemonSetType or unspecified
  1473  	go func() {
  1474  		defer wg.Done()
  1475  
  1476  		if criteria.WorkloadType != "" && criteria.WorkloadType != kubernetes.DaemonSetType {
  1477  			return
  1478  		}
  1479  
  1480  		var err error
  1481  		if in.isWorkloadIncluded(kubernetes.DaemonSetType) {
  1482  			ds, err = kialiCache.GetDaemonSet(criteria.Namespace, criteria.WorkloadName)
  1483  			if err != nil {
  1484  				ds = nil
  1485  			}
  1486  		}
  1487  	}()
  1488  
  1489  	wg.Wait()
  1490  	if len(errChan) != 0 {
  1491  		err := <-errChan
  1492  		return wl, err
  1493  	}
  1494  
  1495  	// Key: name of controller; Value: type of controller
  1496  	controllers := map[string]string{}
  1497  
  1498  	// Find controllers from pods
  1499  	for _, pod := range pods {
  1500  		if len(pod.OwnerReferences) != 0 {
  1501  			for _, ref := range pod.OwnerReferences {
  1502  				if ref.Controller != nil && *ref.Controller && in.isWorkloadIncluded(ref.Kind) {
  1503  					if _, exist := controllers[ref.Name]; !exist {
  1504  						controllers[ref.Name] = ref.Kind
  1505  					} else {
  1506  						if controllers[ref.Name] != ref.Kind {
  1507  							controllers[ref.Name] = controllerPriority(controllers[ref.Name], ref.Kind)
  1508  						}
  1509  					}
  1510  				}
  1511  			}
  1512  		} else {
  1513  			if _, exist := controllers[pod.Name]; !exist {
  1514  				// Pod without controller
  1515  				controllers[pod.Name] = "Pod"
  1516  			}
  1517  		}
  1518  	}
  1519  
  1520  	// Resolve ReplicaSets from Deployments
  1521  	// Resolve ReplicationControllers from DeploymentConfigs
  1522  	// Resolve Jobs from CronJobs
  1523  	for controllerName, controllerType := range controllers {
  1524  		if controllerType == kubernetes.ReplicaSetType {
  1525  			found := false
  1526  			iFound := -1
  1527  			for i, rs := range repset {
  1528  				if rs.Name == controllerName {
  1529  					iFound = i
  1530  					found = true
  1531  					break
  1532  				}
  1533  			}
  1534  			if found && len(repset[iFound].OwnerReferences) > 0 {
  1535  				for _, ref := range repset[iFound].OwnerReferences {
  1536  					if ref.Controller != nil && *ref.Controller {
  1537  						// For valid owner controllers, delete the child ReplicaSet and add the parent controller,
  1538  						// otherwise (for custom controllers), defer to the replica set.
  1539  						if _, exist := controllers[ref.Name]; !exist {
  1540  							if in.isWorkloadValid(ref.Kind) {
  1541  								controllers[ref.Name] = ref.Kind
  1542  								delete(controllers, controllerName)
  1543  							} else {
  1544  								log.Debugf("Use ReplicaSet as workload for custom controller [%s][%s]", ref.Name, ref.Kind)
  1545  							}
  1546  						} else {
  1547  							if controllers[ref.Name] != ref.Kind {
  1548  								controllers[ref.Name] = controllerPriority(controllers[ref.Name], ref.Kind)
  1549  							}
  1550  							delete(controllers, controllerName)
  1551  						}
  1552  					}
  1553  				}
  1554  			}
  1555  		}
  1556  		if controllerType == kubernetes.ReplicationControllerType {
  1557  			found := false
  1558  			iFound := -1
  1559  			for i, rc := range repcon {
  1560  				if rc.Name == controllerName {
  1561  					iFound = i
  1562  					found = true
  1563  					break
  1564  				}
  1565  			}
  1566  			if found && len(repcon[iFound].OwnerReferences) > 0 {
  1567  				for _, ref := range repcon[iFound].OwnerReferences {
  1568  					if ref.Controller != nil && *ref.Controller {
  1569  						// Delete the child ReplicationController and add the parent controller
  1570  						if _, exist := controllers[ref.Name]; !exist {
  1571  							controllers[ref.Name] = ref.Kind
  1572  						} else {
  1573  							if controllers[ref.Name] != ref.Kind {
  1574  								controllers[ref.Name] = controllerPriority(controllers[ref.Name], ref.Kind)
  1575  							}
  1576  						}
  1577  						delete(controllers, controllerName)
  1578  					}
  1579  				}
  1580  			}
  1581  		}
  1582  		if controllerType == kubernetes.JobType {
  1583  			found := false
  1584  			iFound := -1
  1585  			for i, jb := range jbs {
  1586  				if jb.Name == controllerName {
  1587  					iFound = i
  1588  					found = true
  1589  					break
  1590  				}
  1591  			}
  1592  			if found && len(jbs[iFound].OwnerReferences) > 0 {
  1593  				for _, ref := range jbs[iFound].OwnerReferences {
  1594  					if ref.Controller != nil && *ref.Controller {
  1595  						// Delete the child Job and add the parent controller
  1596  						if _, exist := controllers[ref.Name]; !exist {
  1597  							controllers[ref.Name] = ref.Kind
  1598  						} else {
  1599  							if controllers[ref.Name] != ref.Kind {
  1600  								controllers[ref.Name] = controllerPriority(controllers[ref.Name], ref.Kind)
  1601  							}
  1602  						}
  1603  						// Jobs are special as deleting CronJob parent doesn't delete children
  1604  						// So we need to check that parent exists before to delete children controller
  1605  						cnExist := false
  1606  						for _, cnj := range conjbs {
  1607  							if cnj.Name == ref.Name {
  1608  								cnExist = true
  1609  								break
  1610  							}
  1611  						}
  1612  						if cnExist {
  1613  							delete(controllers, controllerName)
  1614  						}
  1615  					}
  1616  				}
  1617  			}
  1618  		}
  1619  	}
  1620  
  1621  	// Cornercase, check for controllers without pods, to show them as a workload
  1622  	if dep != nil {
  1623  		if _, exist := controllers[dep.Name]; !exist {
  1624  			controllers[dep.Name] = kubernetes.DeploymentType
  1625  		}
  1626  	}
  1627  	for _, rs := range repset {
  1628  		if _, exist := controllers[rs.Name]; !exist && len(rs.OwnerReferences) == 0 {
  1629  			controllers[rs.Name] = kubernetes.ReplicaSetType
  1630  		}
  1631  	}
  1632  	if depcon != nil {
  1633  		if _, exist := controllers[depcon.Name]; !exist {
  1634  			controllers[depcon.Name] = kubernetes.DeploymentConfigType
  1635  		}
  1636  	}
  1637  	for _, rc := range repcon {
  1638  		if _, exist := controllers[rc.Name]; !exist && len(rc.OwnerReferences) == 0 {
  1639  			controllers[rc.Name] = kubernetes.ReplicationControllerType
  1640  		}
  1641  	}
  1642  	if fulset != nil {
  1643  		if _, exist := controllers[fulset.Name]; !exist {
  1644  			controllers[fulset.Name] = kubernetes.StatefulSetType
  1645  		}
  1646  	}
  1647  	if ds != nil {
  1648  		if _, exist := controllers[ds.Name]; !exist {
  1649  			controllers[ds.Name] = kubernetes.DaemonSetType
  1650  		}
  1651  	}
  1652  
  1653  	// Build workload from controllers
  1654  
  1655  	if _, exist := controllers[criteria.WorkloadName]; exist {
  1656  		w := models.Workload{
  1657  			WorkloadListItem: models.WorkloadListItem{
  1658  				Cluster:   criteria.Cluster,
  1659  				Namespace: criteria.Namespace,
  1660  			},
  1661  			Pods:              models.Pods{},
  1662  			Services:          []models.ServiceOverview{},
  1663  			Runtimes:          []models.Runtime{},
  1664  			AdditionalDetails: []models.AdditionalItem{},
  1665  			Health:            *models.EmptyWorkloadHealth(),
  1666  		}
  1667  
  1668  		// We have a controller with criteria.workloadName but if criteria.WorkloadType is specified and does
  1669  		// not match then we may not yet have fetched the workload definition.
  1670  		// For known types: respect criteria.WorkloadType and return NotFound if necessary.
  1671  		// For custom types: fall through to the default handler and try to get the workload definition working
  1672  		// up from the pods or replicas sets.
  1673  		// see https://github.com/kiali/kiali/issues/3830
  1674  		discoveredControllerType := controllers[criteria.WorkloadName]
  1675  		controllerType := discoveredControllerType
  1676  		if criteria.WorkloadType != "" && discoveredControllerType != criteria.WorkloadType {
  1677  			controllerType = criteria.WorkloadType
  1678  		}
  1679  
  1680  		// Handle the known types...
  1681  		cnFound := true
  1682  		switch controllerType {
  1683  		case kubernetes.DeploymentType:
  1684  			if dep != nil && dep.Name == criteria.WorkloadName {
  1685  				selector := labels.Set(dep.Spec.Template.Labels).AsSelector()
  1686  				w.SetPods(kubernetes.FilterPodsBySelector(selector, pods))
  1687  				w.ParseDeployment(dep)
  1688  			} else {
  1689  				log.Errorf("Workload %s is not found as Deployment", criteria.WorkloadName)
  1690  				cnFound = false
  1691  			}
  1692  		case kubernetes.ReplicaSetType:
  1693  			found := false
  1694  			iFound := -1
  1695  			for i, rs := range repset {
  1696  				if rs.Name == criteria.WorkloadName {
  1697  					found = true
  1698  					iFound = i
  1699  					break
  1700  				}
  1701  			}
  1702  			if found {
  1703  				selector := labels.Set(repset[iFound].Spec.Template.Labels).AsSelector()
  1704  				w.SetPods(kubernetes.FilterPodsBySelector(selector, pods))
  1705  				w.ParseReplicaSet(&repset[iFound])
  1706  			} else {
  1707  				log.Errorf("Workload %s is not found as ReplicaSet", criteria.WorkloadName)
  1708  				cnFound = false
  1709  			}
  1710  		case kubernetes.ReplicationControllerType:
  1711  			found := false
  1712  			iFound := -1
  1713  			for i, rc := range repcon {
  1714  				if rc.Name == criteria.WorkloadName {
  1715  					found = true
  1716  					iFound = i
  1717  					break
  1718  				}
  1719  			}
  1720  			if found {
  1721  				selector := labels.Set(repcon[iFound].Spec.Template.Labels).AsSelector()
  1722  				w.SetPods(kubernetes.FilterPodsBySelector(selector, pods))
  1723  				w.ParseReplicationController(&repcon[iFound])
  1724  			} else {
  1725  				log.Errorf("Workload %s is not found as ReplicationController", criteria.WorkloadName)
  1726  				cnFound = false
  1727  			}
  1728  		case kubernetes.DeploymentConfigType:
  1729  			if depcon != nil && depcon.Name == criteria.WorkloadName {
  1730  				selector := labels.Set(depcon.Spec.Template.Labels).AsSelector()
  1731  				w.SetPods(kubernetes.FilterPodsBySelector(selector, pods))
  1732  				w.ParseDeploymentConfig(depcon)
  1733  			} else {
  1734  				log.Errorf("Workload %s is not found as DeploymentConfig", criteria.WorkloadName)
  1735  				cnFound = false
  1736  			}
  1737  		case kubernetes.StatefulSetType:
  1738  			if fulset != nil && fulset.Name == criteria.WorkloadName {
  1739  				selector := labels.Set(fulset.Spec.Template.Labels).AsSelector()
  1740  				w.SetPods(kubernetes.FilterPodsBySelector(selector, pods))
  1741  				w.ParseStatefulSet(fulset)
  1742  			} else {
  1743  				log.Errorf("Workload %s is not found as StatefulSet", criteria.WorkloadName)
  1744  				cnFound = false
  1745  			}
  1746  		case kubernetes.PodType:
  1747  			found := false
  1748  			iFound := -1
  1749  			for i, pod := range pods {
  1750  				if pod.Name == criteria.WorkloadName {
  1751  					found = true
  1752  					iFound = i
  1753  					break
  1754  				}
  1755  			}
  1756  			if found {
  1757  				w.SetPods([]core_v1.Pod{pods[iFound]})
  1758  				w.ParsePod(&pods[iFound])
  1759  			} else {
  1760  				log.Errorf("Workload %s is not found as Pod", criteria.WorkloadName)
  1761  				cnFound = false
  1762  			}
  1763  		case kubernetes.JobType:
  1764  			found := false
  1765  			iFound := -1
  1766  			for i, jb := range jbs {
  1767  				if jb.Name == criteria.WorkloadName {
  1768  					found = true
  1769  					iFound = i
  1770  					break
  1771  				}
  1772  			}
  1773  			if found {
  1774  				selector := labels.Set(jbs[iFound].Spec.Template.Labels).AsSelector()
  1775  				w.SetPods(kubernetes.FilterPodsBySelector(selector, pods))
  1776  				w.ParseJob(&jbs[iFound])
  1777  			} else {
  1778  				log.Errorf("Workload %s is not found as Job", criteria.WorkloadName)
  1779  				cnFound = false
  1780  			}
  1781  		case kubernetes.CronJobType:
  1782  			found := false
  1783  			iFound := -1
  1784  			for i, cjb := range conjbs {
  1785  				if cjb.Name == criteria.WorkloadName {
  1786  					found = true
  1787  					iFound = i
  1788  					break
  1789  				}
  1790  			}
  1791  			if found {
  1792  				selector := labels.Set(conjbs[iFound].Spec.JobTemplate.Spec.Template.Labels).AsSelector()
  1793  				w.SetPods(kubernetes.FilterPodsBySelector(selector, pods))
  1794  				w.ParseCronJob(&conjbs[iFound])
  1795  			} else {
  1796  				log.Warningf("Workload %s is not found as CronJob (CronJob could be deleted but children are still in the namespace)", criteria.WorkloadName)
  1797  				cnFound = false
  1798  			}
  1799  		case kubernetes.DaemonSetType:
  1800  			if ds != nil && ds.Name == criteria.WorkloadName {
  1801  				selector := labels.Set(ds.Spec.Template.Labels).AsSelector()
  1802  				w.SetPods(kubernetes.FilterPodsBySelector(selector, pods))
  1803  				w.ParseDaemonSet(ds)
  1804  			} else {
  1805  				log.Errorf("Workload %s is not found as DaemonSet", criteria.WorkloadName)
  1806  				cnFound = false
  1807  			}
  1808  		default:
  1809  			// Handle a custom type (criteria.WorkloadType is not a known type).
  1810  			// 1. Custom controller with replicaset
  1811  			// 2. Custom controller without replicaset controlling pods directly
  1812  
  1813  			// 1. use the controller type found in the Pod resolution and ignore the unknown criteria type
  1814  			var cPods []core_v1.Pod
  1815  			for _, rs := range repset {
  1816  				if discoveredControllerType == kubernetes.ReplicaSetType && criteria.WorkloadName == rs.Name {
  1817  					w.ParseReplicaSet(&rs)
  1818  				} else {
  1819  					rsOwnerRef := meta_v1.GetControllerOf(&rs.ObjectMeta)
  1820  					if rsOwnerRef != nil && rsOwnerRef.Name == criteria.WorkloadName && rsOwnerRef.Kind == discoveredControllerType {
  1821  						w.ParseReplicaSetParent(&rs, criteria.WorkloadName, discoveredControllerType)
  1822  					} else {
  1823  						continue
  1824  					}
  1825  				}
  1826  				for _, pod := range pods {
  1827  					if meta_v1.IsControlledBy(&pod, &rs) {
  1828  						cPods = append(cPods, pod)
  1829  					}
  1830  				}
  1831  				break
  1832  			}
  1833  
  1834  			// 2. If no pods we're found for a ReplicaSet type, it's possible the controller
  1835  			//    is managing the pods itself i.e. the pod's have an owner ref directly to the controller type.
  1836  			if len(cPods) == 0 {
  1837  				cPods = kubernetes.FilterPodsByController(criteria.WorkloadName, discoveredControllerType, pods)
  1838  				if len(cPods) > 0 {
  1839  					w.ParsePods(criteria.WorkloadName, discoveredControllerType, cPods)
  1840  					log.Debugf("Workload %s of type %s has not a ReplicaSet as a child controller, it may need a revisit", criteria.WorkloadName, discoveredControllerType)
  1841  				}
  1842  			}
  1843  
  1844  			w.SetPods(cPods)
  1845  		}
  1846  
  1847  		// Add the Proxy Status to the workload
  1848  		for _, pod := range w.Pods {
  1849  			if pod.HasIstioSidecar() && !w.IsGateway() && config.Get().ExternalServices.Istio.IstioAPIEnabled {
  1850  				pod.ProxyStatus = in.businessLayer.ProxyStatus.GetPodProxyStatus(criteria.Cluster, criteria.Namespace, pod.Name)
  1851  			}
  1852  			// If Ambient is enabled for pod, check if has any Waypoint proxy
  1853  			if pod.AmbientEnabled() {
  1854  				w.WaypointWorkloads = in.getWaypointForWorkload(ctx, criteria.Namespace, w)
  1855  			}
  1856  			// If the pod is a waypoint proxy, check if it is attached to a namespace or to a service account, and get the affected workloads
  1857  			if pod.IsWaypoint() {
  1858  				// Get waypoint workloads from a namespace
  1859  				if pod.Labels["istio.io/gateway-name"] == "namespace" {
  1860  					w.WaypointWorkloads = append(w.WaypointWorkloads, in.listWaypointWorkloadsForNamespace(ctx, criteria.Namespace)...)
  1861  				} else {
  1862  					// Get waypoint workloads from a service account
  1863  					sa := pod.Annotations["istio.io/for-service-account"]
  1864  					w.WaypointWorkloads = append(w.WaypointWorkloads, in.listWaypointWorkloadsForSA(ctx, criteria.Namespace, sa)...)
  1865  				}
  1866  			}
  1867  		}
  1868  
  1869  		if cnFound {
  1870  			return &w, nil
  1871  		}
  1872  	}
  1873  	return wl, kubernetes.NewNotFound(criteria.WorkloadName, "Kiali", "Workload")
  1874  }
  1875  
  1876  // Get the Waypoint proxy for a workload
  1877  func (in *WorkloadService) getWaypointForWorkload(ctx context.Context, namespace string, workload models.Workload) []models.Workload {
  1878  	wlist, err := in.fetchWorkloads(ctx, namespace, "")
  1879  	if err != nil {
  1880  		log.Errorf("Error fetching workloads")
  1881  		return nil
  1882  	}
  1883  
  1884  	var workloadslist []models.Workload
  1885  	// Get service Account name for each pod from the workload
  1886  	for _, wk := range wlist {
  1887  		if wk.Labels[config.WaypointLabel] == "istio.io-mesh-controller" {
  1888  			for _, pod := range wk.Pods {
  1889  				if pod.Labels["istio.io/gateway-name"] == "namespace" {
  1890  					workloadslist = append(workloadslist, *wk)
  1891  					break
  1892  				} else {
  1893  					// Get waypoint workloads from a service account
  1894  					sa := pod.Annotations["istio.io/for-service-account"]
  1895  					for _, workloadDef := range workload.Pods {
  1896  						if workloadDef.ServiceAccountName == sa {
  1897  							workloadslist = append(workloadslist, *wk)
  1898  							break
  1899  						}
  1900  					}
  1901  
  1902  				}
  1903  			}
  1904  		}
  1905  	}
  1906  	return workloadslist
  1907  }
  1908  
  1909  // Return the list of workloads binded to a service account, valid when the waypoint proxy is applied to a service account
  1910  // TODO: This is scoped by namespace
  1911  func (in *WorkloadService) listWaypointWorkloadsForSA(ctx context.Context, namespace string, sa string) []models.Workload {
  1912  	wlist, err := in.fetchWorkloads(ctx, namespace, "")
  1913  	if err != nil {
  1914  		log.Errorf("Error fetching workloads")
  1915  	}
  1916  
  1917  	var workloadslist []models.Workload
  1918  	// Get service Account name for each pod from the workload
  1919  	for _, workload := range wlist {
  1920  		if workload.Labels[config.WaypointLabel] != "istio.io-mesh-controller" {
  1921  			for _, pod := range workload.Pods {
  1922  				if pod.ServiceAccountName == sa {
  1923  					workloadslist = append(workloadslist, *workload)
  1924  					break
  1925  
  1926  				}
  1927  			}
  1928  		}
  1929  	}
  1930  	return workloadslist
  1931  }
  1932  
  1933  // Return the list of workloads when the waypoint proxy is applied per namespace
  1934  func (in *WorkloadService) listWaypointWorkloadsForNamespace(ctx context.Context, namespace string) []models.Workload {
  1935  	wlist, err := in.fetchWorkloads(ctx, namespace, "")
  1936  	if err != nil {
  1937  		log.Errorf("Error fetching workloads")
  1938  	}
  1939  
  1940  	var workloadslist []models.Workload
  1941  	// Get service Account name for each pod from the workload
  1942  	for _, workload := range wlist {
  1943  		if workload.Labels[config.WaypointLabel] != "istio.io-mesh-controller" {
  1944  			workloadslist = append(workloadslist, *workload)
  1945  		}
  1946  	}
  1947  	return workloadslist
  1948  }
  1949  
  1950  func (in *WorkloadService) updateWorkload(ctx context.Context, cluster string, namespace string, workloadName string, workloadType string, jsonPatch string, patchType string) error {
  1951  	// Check if user has access to the namespace (RBAC) in cache scenarios and/or
  1952  	// if namespace is accessible from Kiali (Deployment.AccessibleNamespaces)
  1953  	if _, err := in.businessLayer.Namespace.GetClusterNamespace(ctx, namespace, cluster); err != nil {
  1954  		return err
  1955  	}
  1956  
  1957  	userClient, ok := in.userClients[cluster]
  1958  	if !ok {
  1959  		return fmt.Errorf("user client for cluster [%s] not found", cluster)
  1960  	}
  1961  
  1962  	workloadTypes := []string{
  1963  		kubernetes.DeploymentType,
  1964  		kubernetes.ReplicaSetType,
  1965  		kubernetes.ReplicationControllerType,
  1966  		kubernetes.DeploymentConfigType,
  1967  		kubernetes.StatefulSetType,
  1968  		kubernetes.JobType,
  1969  		kubernetes.CronJobType,
  1970  		kubernetes.PodType,
  1971  		kubernetes.DaemonSetType,
  1972  	}
  1973  
  1974  	// workloadType is an optional parameter used to optimize the workload type fetch
  1975  	// By default workloads use only the "name" but not the pair "name,type".
  1976  	if workloadType != "" {
  1977  		found := false
  1978  		for _, wt := range workloadTypes {
  1979  			if workloadType == wt {
  1980  				found = true
  1981  				break
  1982  			}
  1983  		}
  1984  		if found {
  1985  			workloadTypes = []string{workloadType}
  1986  		}
  1987  	}
  1988  
  1989  	wg := sync.WaitGroup{}
  1990  	wg.Add(len(workloadTypes))
  1991  	errChan := make(chan error, len(workloadTypes))
  1992  
  1993  	for _, workloadType := range workloadTypes {
  1994  		go func(wkType string) {
  1995  			defer wg.Done()
  1996  			var err error
  1997  			if in.isWorkloadIncluded(wkType) {
  1998  				err = userClient.UpdateWorkload(namespace, workloadName, wkType, jsonPatch, patchType)
  1999  			}
  2000  			if err != nil {
  2001  				if !errors.IsNotFound(err) {
  2002  					log.Errorf("Error fetching %s per namespace %s and name %s: %s", wkType, namespace, workloadName, err)
  2003  					errChan <- err
  2004  				}
  2005  			}
  2006  		}(workloadType)
  2007  	}
  2008  
  2009  	wg.Wait()
  2010  	if len(errChan) != 0 {
  2011  		err := <-errChan
  2012  		return err
  2013  	}
  2014  
  2015  	return nil
  2016  }
  2017  
  2018  // KIALI-1730
  2019  // This method is used to decide the priority of the controller in the cornercase when two controllers have same labels
  2020  // on the selector. Note that this is a situation that user should control as it is described in the documentation:
  2021  // https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
  2022  // But Istio only identifies one controller as workload (it doesn't note which one).
  2023  // Kiali can select one on the list of workloads and other in the details and this should be consistent.
  2024  var controllerOrder = map[string]int{
  2025  	"Deployment":            6,
  2026  	"DeploymentConfig":      5,
  2027  	"ReplicaSet":            4,
  2028  	"ReplicationController": 3,
  2029  	"StatefulSet":           2,
  2030  	"Job":                   1,
  2031  	"DaemonSet":             0,
  2032  	"Pod":                   -1,
  2033  }
  2034  
  2035  func controllerPriority(type1, type2 string) string {
  2036  	w1, e1 := controllerOrder[type1]
  2037  	if !e1 {
  2038  		log.Errorf("This controller %s is assigned in a Pod and it's not properly managed", type1)
  2039  	}
  2040  	w2, e2 := controllerOrder[type2]
  2041  	if !e2 {
  2042  		log.Errorf("This controller %s is assigned in a Pod and it's not properly managed", type2)
  2043  	}
  2044  	if w1 >= w2 {
  2045  		return type1
  2046  	} else {
  2047  		return type2
  2048  	}
  2049  }
  2050  
  2051  // GetWorkloadAppName returns the "Application" name (app label) that relates to a workload
  2052  func (in *WorkloadService) GetWorkloadAppName(ctx context.Context, cluster, namespace, workload string) (string, error) {
  2053  	var end observability.EndFunc
  2054  	ctx, end = observability.StartSpan(ctx, "GetWorkloadAppName",
  2055  		observability.Attribute("package", "business"),
  2056  		observability.Attribute("cluster", cluster),
  2057  		observability.Attribute("namespace", namespace),
  2058  		observability.Attribute("workload", workload),
  2059  	)
  2060  	defer end()
  2061  
  2062  	wkd, err := in.fetchWorkload(ctx, WorkloadCriteria{Cluster: cluster, Namespace: namespace, WorkloadName: workload, WorkloadType: ""})
  2063  	if err != nil {
  2064  		return "", err
  2065  	}
  2066  
  2067  	appLabelName := in.config.IstioLabels.AppLabelName
  2068  	app := wkd.Labels[appLabelName]
  2069  	return app, nil
  2070  }
  2071  
  2072  // streamParsedLogs fetches logs from a container in a pod, parses and decorates each log line with some metadata (if possible) and
  2073  // sends the processed lines to the client in JSON format. Results are sent as processing is performed, so in case of any error when
  2074  // doing processing the JSON document will be truncated.
  2075  func (in *WorkloadService) streamParsedLogs(cluster, namespace, name string, opts *LogOptions, w http.ResponseWriter) error {
  2076  	userClient, ok := in.userClients[cluster]
  2077  	if !ok {
  2078  		return fmt.Errorf("user client for cluster [%s] not found", cluster)
  2079  	}
  2080  
  2081  	var engardeParser *parser.Parser
  2082  	if opts.LogType == models.LogTypeProxy {
  2083  		engardeParser = parser.New(parser.IstioProxyAccessLogsPattern)
  2084  	}
  2085  
  2086  	k8sOpts := opts.PodLogOptions
  2087  	// the k8s API does not support "endTime/beforeTime". So for bounded time ranges we need to
  2088  	// discard the logs after sinceTime+duration
  2089  	isBounded := opts.Duration != nil
  2090  
  2091  	logsReader, err := userClient.StreamPodLogs(namespace, name, &k8sOpts)
  2092  	if err != nil {
  2093  		return err
  2094  	}
  2095  
  2096  	defer func() {
  2097  		e := logsReader.Close()
  2098  		if e != nil {
  2099  			log.Errorf("Error when closing the connection streaming logs of a pod: %s", e.Error())
  2100  		}
  2101  	}()
  2102  
  2103  	bufferedReader := bufio.NewReader(logsReader)
  2104  
  2105  	var startTime *time.Time
  2106  	var endTime *time.Time
  2107  	if k8sOpts.SinceTime != nil {
  2108  		startTime = &k8sOpts.SinceTime.Time
  2109  		if isBounded {
  2110  			end := startTime.Add(*opts.Duration)
  2111  			endTime = &end
  2112  		}
  2113  	}
  2114  
  2115  	// To avoid high memory usage, the JSON will be written
  2116  	// to the HTTP Response as it's received from the cluster API.
  2117  	// That is, each log line is parsed, decorated with Kiali's metadata,
  2118  	// marshalled to JSON and immediately written to the HTTP Response.
  2119  	// This means that it is needed to push HTTP headers and start writing
  2120  	// the response body right now and any errors at the middle of the log
  2121  	// processing can no longer be informed to the client. So, starting
  2122  	// these lines, the best we can do if some error happens is to simply
  2123  	// log the error and stop/truncate the response, which will have the
  2124  	// effect of sending an incomplete JSON document that the browser will fail
  2125  	// to parse. Hopefully, the client/UI can catch the parsing error and
  2126  	// properly show an error message about the failure retrieving logs.
  2127  	w.Header().Set("Content-Type", "application/json")
  2128  	_, writeErr := w.Write([]byte("{\"entries\":[")) // This starts the JSON document
  2129  	if writeErr != nil {
  2130  		return writeErr
  2131  	}
  2132  
  2133  	firstEntry := true
  2134  	line, readErr := bufferedReader.ReadString('\n')
  2135  	linesWritten := 0
  2136  	for ; readErr == nil || (readErr == io.EOF && len(line) > 0); line, readErr = bufferedReader.ReadString('\n') {
  2137  		// Abort if we already reached the requested max-lines limit
  2138  		if opts.MaxLines != nil && linesWritten >= *opts.MaxLines {
  2139  			break
  2140  		}
  2141  
  2142  		var entry *LogEntry
  2143  		if opts.LogType == models.LogTypeZtunnel {
  2144  			entry = parseZtunnelLine(line)
  2145  		} else {
  2146  			entry = parseLogLine(line, opts.LogType == models.LogTypeProxy, engardeParser)
  2147  		}
  2148  
  2149  		if entry == nil {
  2150  			continue
  2151  		}
  2152  
  2153  		if opts.LogType == models.LogTypeZtunnel && !filterMatches(entry.Message, opts.filter) {
  2154  			continue
  2155  		}
  2156  
  2157  		// If we are past the requested time window then stop processing
  2158  		if startTime == nil {
  2159  			startTime = &entry.OriginalTime
  2160  		}
  2161  
  2162  		if isBounded {
  2163  			if endTime == nil {
  2164  				end := entry.OriginalTime.Add(*opts.Duration)
  2165  				endTime = &end
  2166  			}
  2167  
  2168  			if entry.OriginalTime.After(*endTime) {
  2169  				break
  2170  			}
  2171  		}
  2172  
  2173  		// Send to client the processed log line
  2174  
  2175  		response, err := json.Marshal(entry)
  2176  		if err != nil {
  2177  			// Remember that since the HTTP Response body is already being sent,
  2178  			// it is not possible to change the response code. So, log the error
  2179  			// and terminate early the response.
  2180  			log.Errorf("Error when marshalling JSON while streaming pod logs: %s", err.Error())
  2181  			return nil
  2182  		}
  2183  
  2184  		if firstEntry {
  2185  			firstEntry = false
  2186  		} else {
  2187  			_, writeErr = w.Write([]byte{','})
  2188  			if writeErr != nil {
  2189  				// Remember that since the HTTP Response body is already being sent,
  2190  				// it is not possible to change the response code. So, log the error
  2191  				// and terminate early the response.
  2192  				log.Errorf("Error when writing log entries separator: %s", writeErr.Error())
  2193  				return nil
  2194  			}
  2195  		}
  2196  
  2197  		_, writeErr = w.Write(response)
  2198  		if writeErr != nil {
  2199  			log.Errorf("Error when writing a processed log entry while streaming pod logs: %s", writeErr.Error())
  2200  			return nil
  2201  		}
  2202  
  2203  		linesWritten++
  2204  	}
  2205  
  2206  	if readErr == nil && opts.MaxLines != nil && linesWritten >= *opts.MaxLines {
  2207  		// End the JSON document, setting the max-lines truncated flag
  2208  		_, writeErr = w.Write([]byte("], \"linesTruncated\": true}"))
  2209  	} else {
  2210  		// End the JSON document
  2211  		_, writeErr = w.Write([]byte("]}"))
  2212  	}
  2213  	if writeErr != nil {
  2214  		log.Errorf("Error when writing the outro of the JSON document while streaming pod logs: %s", err.Error())
  2215  	}
  2216  
  2217  	return nil
  2218  }
  2219  
  2220  // StreamPodLogs streams pod logs to an HTTP Response given the provided options
  2221  func (in *WorkloadService) StreamPodLogs(cluster, namespace, name string, opts *LogOptions, w http.ResponseWriter) error {
  2222  
  2223  	if opts.LogType == models.LogTypeZtunnel {
  2224  		// First, get ztunnel namespace and containers
  2225  		pods := in.cache.GetZtunnelPods(cluster)
  2226  		// This is needed for the K8S client
  2227  		opts.PodLogOptions.Container = models.IstioProxy
  2228  		// The ztunnel line should include the pod and the namespace
  2229  		fs := filterOpts{
  2230  			destWk: fmt.Sprintf("dst.workload=\"%s\"", name),
  2231  			destNs: fmt.Sprintf("dst.namespace=\"%s\"", namespace),
  2232  			srcWk:  fmt.Sprintf("src.workload=\"%s\"", name),
  2233  			srcNs:  fmt.Sprintf("src.namespace=\"%s\"", namespace),
  2234  		}
  2235  		opts.filter = fs
  2236  		var streamErr error
  2237  		for _, pod := range pods {
  2238  			streamErr = in.streamParsedLogs(cluster, pod.Namespace, pod.Name, opts, w)
  2239  		}
  2240  		return streamErr
  2241  	}
  2242  	return in.streamParsedLogs(cluster, namespace, name, opts, w)
  2243  }
  2244  
  2245  // AND filter
  2246  func filterMatches(line string, filter filterOpts) bool {
  2247  	if (strings.Contains(line, filter.destNs) && strings.Contains(line, filter.destWk)) || (strings.Contains(line, filter.srcNs) && strings.Contains(line, filter.srcWk)) {
  2248  		return true
  2249  	}
  2250  	return false
  2251  }