github.com/kiali/kiali@v1.84.0/business/services.go (about) 1 package business 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "time" 8 9 networking_v1beta1 "istio.io/client-go/pkg/apis/networking/v1beta1" 10 apps_v1 "k8s.io/api/apps/v1" 11 core_v1 "k8s.io/api/core/v1" 12 "k8s.io/apimachinery/pkg/api/errors" 13 "k8s.io/apimachinery/pkg/labels" 14 15 "github.com/kiali/kiali/business/checkers" 16 "github.com/kiali/kiali/config" 17 "github.com/kiali/kiali/kubernetes" 18 "github.com/kiali/kiali/kubernetes/cache" 19 "github.com/kiali/kiali/log" 20 "github.com/kiali/kiali/models" 21 "github.com/kiali/kiali/observability" 22 "github.com/kiali/kiali/prometheus" 23 ) 24 25 // SvcService deals with fetching istio/kubernetes services related content and convert to kiali model 26 type SvcService struct { 27 config config.Config 28 kialiCache cache.KialiCache 29 businessLayer *Layer 30 prom prometheus.ClientInterface 31 userClients map[string]kubernetes.ClientInterface 32 } 33 34 type ServiceCriteria struct { 35 Cluster string 36 Namespace string 37 IncludeHealth bool 38 IncludeIstioResources bool 39 IncludeOnlyDefinitions bool 40 ServiceSelector string 41 RateInterval string 42 QueryTime time.Time 43 } 44 45 // GetServiceList returns a list of all services for a given criteria 46 func (in *SvcService) GetServiceList(ctx context.Context, criteria ServiceCriteria) (*models.ServiceList, error) { 47 var end observability.EndFunc 48 conf := config.Get() 49 50 ctx, end = observability.StartSpan(ctx, "GetServiceList", 51 observability.Attribute("package", "business"), 52 observability.Attribute("cluster", criteria.Cluster), 53 observability.Attribute("namespace", criteria.Namespace), 54 observability.Attribute("includeHealth", criteria.IncludeHealth), 55 observability.Attribute("includeIstioResources", criteria.IncludeIstioResources), 56 observability.Attribute("includeOnlyDefinitions", criteria.IncludeOnlyDefinitions), 57 observability.Attribute("rateInterval", criteria.RateInterval), 58 observability.Attribute("queryTime", criteria.QueryTime), 59 ) 60 defer end() 61 62 serviceList := models.ServiceList{ 63 Services: []models.ServiceOverview{}, 64 Validations: models.IstioValidations{}, 65 } 66 // Check if user has access to the namespace (RBAC) in cache scenarios and/or 67 // if namespace is accessible from Kiali (Deployment.AccessibleNamespaces) 68 for cluster := range in.userClients { 69 if criteria.Cluster != "" && cluster != criteria.Cluster { 70 continue 71 } 72 73 if _, err := in.businessLayer.Namespace.GetClusterNamespace(ctx, criteria.Namespace, cluster); err != nil { 74 // We want to throw an error if we're single vs. multi cluster to be backward compatible 75 // TODO: Probably need this in a few other places as well. It'd be nice to have a 76 // centralized check for this in the config instead of this hacky one. 77 if len(in.userClients) == 1 { 78 return nil, err 79 } 80 81 if errors.IsNotFound(err) || errors.IsForbidden(err) { 82 // If a cluster is not found or not accessible, then we skip it 83 log.Debugf("Error while accessing to cluster [%s]: %s", cluster, err.Error()) 84 continue 85 } 86 87 // On any other error, abort and return the error. 88 return nil, err 89 } 90 91 singleClusterSVCList, err := in.getServiceListForCluster(ctx, criteria, cluster) 92 if err != nil { 93 if cluster == conf.KubernetesConfig.ClusterName { 94 return nil, err 95 } 96 97 log.Errorf("Unable to get services list from cluster: %s. Err: %s. Skipping", cluster, err) 98 continue 99 } 100 101 serviceList.Services = append(serviceList.Services, singleClusterSVCList.Services...) 102 serviceList.Namespace = singleClusterSVCList.Namespace 103 serviceList.Validations = serviceList.Validations.MergeValidations(singleClusterSVCList.Validations) 104 } 105 106 return &serviceList, nil 107 } 108 109 func (in *SvcService) getServiceListForCluster(ctx context.Context, criteria ServiceCriteria, cluster string) (*models.ServiceList, error) { 110 var ( 111 svcs []core_v1.Service 112 rSvcs []*kubernetes.RegistryService 113 pods []core_v1.Pod 114 deployments []apps_v1.Deployment 115 istioConfigList models.IstioConfigList 116 err error 117 kubeCache cache.KubeCache 118 ) 119 120 kubeCache, err = in.kialiCache.GetKubeCache(cluster) 121 if err != nil { 122 return nil, err 123 } 124 125 var selectorLabels map[string]string 126 if criteria.ServiceSelector != "" { 127 if selector, err := labels.ConvertSelectorToLabelsMap(criteria.ServiceSelector); err == nil { 128 selectorLabels = selector 129 } else { 130 log.Warningf("Services not filtered. Selector %s not valid", criteria.ServiceSelector) 131 } 132 } 133 134 svcs, err = kubeCache.GetServicesBySelectorLabels(criteria.Namespace, selectorLabels) 135 if err != nil { 136 log.Errorf("Error fetching Services per namespace %s: %s", criteria.Namespace, err) 137 return nil, err 138 } 139 140 if in.config.ExternalServices.Istio.IstioAPIEnabled && cluster == in.config.KubernetesConfig.ClusterName { 141 registryCriteria := RegistryCriteria{ 142 Namespace: criteria.Namespace, 143 ServiceSelector: criteria.ServiceSelector, 144 Cluster: cluster, 145 } 146 rSvcs = in.businessLayer.RegistryStatus.GetRegistryServices(registryCriteria) 147 } 148 149 if !criteria.IncludeOnlyDefinitions { 150 pods, err = kubeCache.GetPods(criteria.Namespace, "") 151 if err != nil { 152 log.Errorf("Error fetching Pods per namespace %s: %s", criteria.Namespace, err) 153 return nil, err 154 } 155 } 156 157 if !criteria.IncludeOnlyDefinitions { 158 deployments, err = kubeCache.GetDeployments(criteria.Namespace) 159 if err != nil { 160 log.Errorf("Error fetching Deployments per namespace %s: %s", criteria.Namespace, err) 161 return nil, err 162 } 163 } 164 165 // Cross-namespace query of all Istio Resources to find references 166 // References MAY have visibility for a user but not access if they are not allowed to access to the namespace 167 if criteria.IncludeIstioResources { 168 istioCriteria := IstioConfigCriteria{ 169 IncludeDestinationRules: true, 170 IncludeGateways: true, 171 IncludeK8sGateways: true, 172 IncludeK8sHTTPRoutes: true, 173 IncludeK8sReferenceGrants: true, 174 IncludeServiceEntries: true, 175 IncludeVirtualServices: true, 176 } 177 istioConfigs, err := in.businessLayer.IstioConfig.GetIstioConfigList(ctx, cluster, istioCriteria) 178 if err != nil { 179 log.Errorf("Error fetching IstioConfigList per cluster %s per namespace %s: %s", cluster, criteria.Namespace, err) 180 return nil, err 181 } 182 istioConfigList = *istioConfigs 183 } 184 185 // Convert to Kiali model 186 services := in.buildServiceList(cluster, criteria.Namespace, svcs, rSvcs, pods, deployments, istioConfigList, criteria) 187 188 // Check if we need to add health 189 190 if criteria.IncludeHealth { 191 for i, sv := range services.Services { 192 // TODO: Fix health for multi-cluster 193 services.Services[i].Health, err = in.businessLayer.Health.GetServiceHealth(ctx, criteria.Namespace, sv.Cluster, sv.Name, criteria.RateInterval, criteria.QueryTime, sv.ParseToService()) 194 if err != nil { 195 log.Errorf("Error fetching health per service %s: %s", sv.Name, err) 196 } 197 } 198 } 199 200 return services, nil 201 } 202 203 func getVSKialiScenario(vs []*networking_v1beta1.VirtualService) string { 204 scenario := "" 205 for _, v := range vs { 206 if scenario, ok := v.Labels["kiali_wizard"]; ok { 207 return scenario 208 } 209 } 210 return scenario 211 } 212 213 func getDRKialiScenario(dr []*networking_v1beta1.DestinationRule) string { 214 scenario := "" 215 for _, d := range dr { 216 if scenario, ok := d.Labels["kiali_wizard"]; ok { 217 return scenario 218 } 219 } 220 return scenario 221 } 222 223 func (in *SvcService) buildServiceList(cluster string, namespace string, svcs []core_v1.Service, rSvcs []*kubernetes.RegistryService, pods []core_v1.Pod, deployments []apps_v1.Deployment, istioConfigList models.IstioConfigList, criteria ServiceCriteria) *models.ServiceList { 224 services := []models.ServiceOverview{} 225 validations := models.IstioValidations{} 226 if !criteria.IncludeOnlyDefinitions { 227 validations = in.getServiceValidations(svcs, deployments, pods) 228 } 229 230 kubernetesServices := in.buildKubernetesServices(svcs, pods, istioConfigList, criteria.IncludeOnlyDefinitions) 231 services = append(services, kubernetesServices...) 232 // Add cluster to each kube service 233 for i := range services { 234 services[i].Cluster = cluster 235 } 236 237 // Add Istio Registry Services that are not present in the Kubernetes list 238 // TODO: Registry services are not associated to a cluster. They can have multiple clusters under 239 // "clusterVIPs". We need to decide how to handle this. 240 rSvcs = kubernetes.FilterRegistryServicesByServices(rSvcs, svcs) 241 registryServices := in.buildRegistryServices(rSvcs, istioConfigList) 242 services = append(services, registryServices...) 243 return &models.ServiceList{Namespace: namespace, Services: services, Validations: validations} 244 } 245 246 func (in *SvcService) buildKubernetesServices(svcs []core_v1.Service, pods []core_v1.Pod, istioConfigList models.IstioConfigList, onlyDefinitions bool) []models.ServiceOverview { 247 services := make([]models.ServiceOverview, len(svcs)) 248 conf := in.config 249 250 // Convert each k8sClients service into our model 251 for i, item := range svcs { 252 var kialiWizard string 253 hasSidecar := true 254 hasAmbient := false 255 svcReferences := make([]*models.IstioValidationKey, 0) 256 257 if !onlyDefinitions { 258 sPods := kubernetes.FilterPodsByService(&item, pods) 259 /** Check if Service has istioSidecar deployed */ 260 mPods := models.Pods{} 261 mPods.Parse(sPods) 262 hasSidecar = mPods.HasAnyIstioSidecar() 263 hasAmbient = mPods.HasAnyAmbient() 264 svcVirtualServices := kubernetes.FilterAutogeneratedVirtualServices(kubernetes.FilterVirtualServicesByService(istioConfigList.VirtualServices, item.Namespace, item.Name)) 265 svcDestinationRules := kubernetes.FilterDestinationRulesByService(istioConfigList.DestinationRules, item.Namespace, item.Name) 266 svcGateways := kubernetes.FilterGatewaysByVirtualServices(istioConfigList.Gateways, svcVirtualServices) 267 svcK8sHTTPRoutes := kubernetes.FilterK8sHTTPRoutesByService(istioConfigList.K8sHTTPRoutes, istioConfigList.K8sReferenceGrants, item.Namespace, item.Name) 268 svcK8sGateways := kubernetes.FilterK8sGatewaysByHTTPRoutes(istioConfigList.K8sGateways, svcK8sHTTPRoutes) 269 270 for _, vs := range svcVirtualServices { 271 ref := models.BuildKey(vs.Kind, vs.Name, vs.Namespace) 272 svcReferences = append(svcReferences, &ref) 273 } 274 for _, dr := range svcDestinationRules { 275 ref := models.BuildKey(dr.Kind, dr.Name, dr.Namespace) 276 svcReferences = append(svcReferences, &ref) 277 } 278 for _, gw := range svcGateways { 279 ref := models.BuildKey(gw.Kind, gw.Name, gw.Namespace) 280 svcReferences = append(svcReferences, &ref) 281 } 282 for _, gw := range svcK8sGateways { 283 // Should be K8s type to generate correct link 284 ref := models.BuildKey(kubernetes.K8sGatewayType, gw.Name, gw.Namespace) 285 svcReferences = append(svcReferences, &ref) 286 } 287 for _, route := range svcK8sHTTPRoutes { 288 // Should be K8s type to generate correct link 289 ref := models.BuildKey(kubernetes.K8sHTTPRouteType, route.Name, route.Namespace) 290 svcReferences = append(svcReferences, &ref) 291 } 292 svcReferences = FilterUniqueIstioReferences(svcReferences) 293 kialiWizard = getVSKialiScenario(svcVirtualServices) 294 if kialiWizard == "" { 295 kialiWizard = getDRKialiScenario(svcDestinationRules) 296 } 297 } 298 299 /** Check if Service has the label app required by Istio */ 300 _, appLabel := item.Spec.Selector[conf.IstioLabels.AppLabelName] 301 /** Check if Service has additional item icon */ 302 services[i] = models.ServiceOverview{ 303 Name: item.Name, 304 Namespace: item.Namespace, 305 IstioSidecar: hasSidecar, 306 IstioAmbient: hasAmbient, 307 AppLabel: appLabel, 308 AdditionalDetailSample: models.GetFirstAdditionalIcon(&conf, item.ObjectMeta.Annotations), 309 Health: models.EmptyServiceHealth(), 310 HealthAnnotations: models.GetHealthAnnotation(item.Annotations, models.GetHealthConfigAnnotation()), 311 Labels: item.Labels, 312 Selector: item.Spec.Selector, 313 IstioReferences: svcReferences, 314 KialiWizard: kialiWizard, 315 ServiceRegistry: "Kubernetes", 316 } 317 } 318 return services 319 } 320 321 func filterIstioServiceByClusterId(clusterId string, item *kubernetes.RegistryService) bool { 322 if clusterId == "Kubernetes" { 323 return true 324 } 325 // External and Federation services are always local to the control plane 326 if item.Attributes.ServiceRegistry != "Kubernetes" { 327 return true 328 } 329 if _, ok := item.ClusterVIPs12.Addresses[clusterId]; ok { 330 return true 331 } 332 if _, ok := item.ClusterVIPs11[clusterId]; ok { 333 return true 334 } 335 return false 336 } 337 338 func (in *SvcService) buildRegistryServices(rSvcs []*kubernetes.RegistryService, istioConfigList models.IstioConfigList) []models.ServiceOverview { 339 services := []models.ServiceOverview{} 340 conf := in.config 341 342 // The istiod registry doesn't have a explicit flag when a service is deployed in a different control plane. 343 // The only way to identify it is to check that the service has an address in the current cluster. 344 // To avoid side effects, Kiali will process only services that belongs to the current cluster. 345 // This should be revisited on more multi-cluster deployments scenarios. 346 // 347 // { 348 // "hostname": "test-svc.evil.svc.cluster.local", 349 // "clusterVIPs": { 350 // "Addresses": { 351 // "istio-west": [ 352 // "0.0.0.0" 353 // ] 354 // } 355 // } 356 // By default Istio uses "Kubernetes" as clusterId for single control planes scenarios. 357 // This clusterId is propagated into the Istio Registry and we need it to filter services in multi-cluster scenarios. 358 // I.e.: 359 // "clusterVIPs": { 360 // "Addresses": { 361 // "Kubernetes": [ 362 // "10.217.4.189" 363 // ] 364 // } 365 // } 366 clusterId := conf.KubernetesConfig.ClusterName 367 for _, item := range rSvcs { 368 if !filterIstioServiceByClusterId(clusterId, item) { 369 continue 370 } 371 _, appLabel := item.Attributes.LabelSelectors[conf.IstioLabels.AppLabelName] 372 // ServiceEntry/External and Federation will be marked as hasSidecar == true as they will have telemetry 373 hasSidecar := true 374 if item.Attributes.ServiceRegistry != "External" && item.Attributes.ServiceRegistry != "Federation" { 375 hasSidecar = false 376 } 377 // TODO wildcards may force additional checks on hostnames ? 378 svcServiceEntries := kubernetes.FilterServiceEntriesByHostname(istioConfigList.ServiceEntries, item.Hostname) 379 svcDestinationRules := kubernetes.FilterDestinationRulesByHostname(istioConfigList.DestinationRules, item.Hostname) 380 svcVirtualServices := kubernetes.FilterVirtualServicesByHostname(istioConfigList.VirtualServices, item.Hostname) 381 svcGateways := kubernetes.FilterGatewaysByVirtualServices(istioConfigList.Gateways, svcVirtualServices) 382 svcReferences := make([]*models.IstioValidationKey, 0) 383 for _, se := range svcServiceEntries { 384 ref := models.BuildKey(se.Kind, se.Name, se.Namespace) 385 svcReferences = append(svcReferences, &ref) 386 } 387 for _, vs := range svcVirtualServices { 388 ref := models.BuildKey(vs.Kind, vs.Name, vs.Namespace) 389 svcReferences = append(svcReferences, &ref) 390 } 391 for _, dr := range svcDestinationRules { 392 ref := models.BuildKey(dr.Kind, dr.Name, dr.Namespace) 393 svcReferences = append(svcReferences, &ref) 394 } 395 for _, gw := range svcGateways { 396 ref := models.BuildKey(gw.Kind, gw.Name, gw.Namespace) 397 svcReferences = append(svcReferences, &ref) 398 } 399 svcReferences = FilterUniqueIstioReferences(svcReferences) 400 // External Istio registries may have references to ServiceEntry and/or Federation 401 service := models.ServiceOverview{ 402 Name: item.Attributes.Name, 403 Namespace: item.Attributes.Namespace, 404 IstioSidecar: hasSidecar, 405 AppLabel: appLabel, 406 Health: models.EmptyServiceHealth(), 407 HealthAnnotations: map[string]string{}, 408 Labels: item.Attributes.Labels, 409 Selector: item.Attributes.LabelSelectors, 410 IstioReferences: svcReferences, 411 ServiceRegistry: item.Attributes.ServiceRegistry, 412 } 413 services = append(services, service) 414 } 415 return services 416 } 417 418 // GetService returns a single service and associated data using the interval and queryTime 419 func (in *SvcService) GetServiceDetails(ctx context.Context, cluster, namespace, service, interval string, queryTime time.Time) (*models.ServiceDetails, error) { 420 var end observability.EndFunc 421 ctx, end = observability.StartSpan(ctx, "GetServiceDetails", 422 observability.Attribute("package", "business"), 423 observability.Attribute("cluster", cluster), 424 observability.Attribute("namespace", namespace), 425 observability.Attribute("service", service), 426 observability.Attribute("interval", interval), 427 observability.Attribute("queryTime", queryTime), 428 ) 429 defer end() 430 431 // Check if user has access to the namespace (RBAC) in cache scenarios and/or 432 // if namespace is accessible from Kiali (Deployment.AccessibleNamespaces) 433 if _, err := in.businessLayer.Namespace.GetClusterNamespace(ctx, namespace, cluster); err != nil { 434 return nil, err 435 } 436 437 svc, err := in.GetService(ctx, cluster, namespace, service) 438 if err != nil { 439 return nil, err 440 } 441 442 kubeCache, err := in.kialiCache.GetKubeCache(cluster) 443 if err != nil { 444 return nil, err 445 } 446 447 var eps *core_v1.Endpoints 448 var pods []core_v1.Pod 449 var hth models.ServiceHealth 450 var istioConfigList *models.IstioConfigList 451 var ws models.Workloads 452 var rSvcs []*kubernetes.RegistryService 453 var nsmtls models.MTLSStatus 454 455 wg := sync.WaitGroup{} 456 // Max possible number of errors. It's ok if the buffer size exceeds the number of goroutines 457 // in cases where istio api is disabled. 458 errChan := make(chan error, 8) 459 460 labelsSelector := labels.Set(svc.Selectors).String() 461 // If service doesn't have any selector, we can't know which are the pods and workloads applying. 462 if labelsSelector != "" { 463 wg.Add(1) 464 go func() { 465 defer wg.Done() 466 var err2 error 467 pods, err2 = kubeCache.GetPods(namespace, labelsSelector) 468 if err2 != nil { 469 errChan <- err2 470 } 471 }() 472 473 wg.Add(1) 474 go func(ctx context.Context) { 475 defer wg.Done() 476 var err2 error 477 ws, err2 = in.businessLayer.Workload.fetchWorkloadsFromCluster(ctx, cluster, namespace, labelsSelector) 478 if err2 != nil { 479 log.Errorf("Error fetching Workloads per namespace %s and service %s: %s", namespace, service, err2) 480 errChan <- err2 481 } 482 }(ctx) 483 484 if in.config.ExternalServices.Istio.IstioAPIEnabled { 485 registryCriteria := RegistryCriteria{ 486 Namespace: namespace, 487 Cluster: cluster, 488 } 489 rSvcs = in.businessLayer.RegistryStatus.GetRegistryServices(registryCriteria) 490 } 491 } 492 493 wg.Add(1) 494 go func(ctx context.Context) { 495 defer wg.Done() 496 var err2 error 497 eps, err2 = kubeCache.GetEndpoints(namespace, service) 498 if err2 != nil && !errors.IsNotFound(err2) { 499 log.Errorf("Error fetching Endpoints namespace %s and service %s: %s", namespace, service, err2) 500 errChan <- err2 501 } 502 }(ctx) 503 504 wg.Add(1) 505 go func(ctx context.Context) { 506 defer wg.Done() 507 var err2 error 508 // TODO: Fix health for multi-cluster 509 hth, err2 = in.businessLayer.Health.GetServiceHealth(ctx, namespace, cluster, service, interval, queryTime, &svc) 510 if err2 != nil { 511 errChan <- err2 512 } 513 }(ctx) 514 515 wg.Add(1) 516 go func(ctx context.Context) { 517 defer wg.Done() 518 var err2 error 519 nsmtls, err2 = in.businessLayer.TLS.NamespaceWidemTLSStatus(ctx, namespace, cluster) 520 if err2 != nil { 521 errChan <- err2 522 } 523 }(ctx) 524 525 wg.Add(1) 526 go func(ctx context.Context) { 527 defer wg.Done() 528 var err2 error 529 criteria := IstioConfigCriteria{ 530 IncludeDestinationRules: true, 531 // TODO the frontend is merging the Gateways per ServiceDetails but it would be a clean design to locate it here 532 IncludeGateways: true, 533 IncludeK8sGateways: true, 534 IncludeK8sHTTPRoutes: true, 535 IncludeK8sReferenceGrants: true, 536 IncludeServiceEntries: true, 537 IncludeVirtualServices: true, 538 } 539 istioConfigList, err2 = in.businessLayer.IstioConfig.GetIstioConfigListForNamespace(ctx, cluster, namespace, criteria) 540 if err2 != nil { 541 log.Errorf("Error fetching IstioConfigList per namespace %s: %s", namespace, err2) 542 errChan <- err2 543 } 544 }(ctx) 545 546 var vsCreate, vsUpdate, vsDelete bool 547 wg.Add(1) 548 go func() { 549 defer wg.Done() 550 /* 551 We can safely assume that permissions for VirtualServices will be similar as DestinationRules. 552 553 Synced with: 554 https://github.com/kiali/kiali-operator/blob/master/roles/default/kiali-deploy/templates/kubernetes/role.yaml#L62 555 */ 556 userClient, found := in.userClients[cluster] 557 if !found { 558 errChan <- fmt.Errorf("client not found for cluster: %s", cluster) 559 return 560 } 561 vsCreate, vsUpdate, vsDelete = getPermissions(context.TODO(), userClient, cluster, namespace, kubernetes.VirtualServices) 562 }() 563 564 wg.Wait() 565 if len(errChan) != 0 { 566 err = <-errChan 567 return nil, err 568 } 569 570 wo := models.WorkloadOverviews{} 571 for _, w := range ws { 572 wi := &models.WorkloadListItem{} 573 wi.ParseWorkload(w) 574 wo = append(wo, wi) 575 } 576 577 serviceOverviews := make([]*models.ServiceOverview, 0) 578 // Convert filtered k8sClients services into ServiceOverview, only several attributes are needed 579 for _, item := range rSvcs { 580 // app label selector of services should match, loading all versions 581 if selector, err3 := labels.ConvertSelectorToLabelsMap(labelsSelector); err3 == nil { 582 if appSelector, ok := item.Attributes.LabelSelectors["app"]; ok && selector.Has("app") && appSelector == selector.Get("app") { 583 if _, ok1 := item.Attributes.LabelSelectors["version"]; ok1 { 584 ports := map[string]int{} 585 for _, port := range item.Ports { 586 ports[port.Name] = port.Port 587 } 588 serviceOverviews = append(serviceOverviews, &models.ServiceOverview{ 589 Name: item.Attributes.Name, 590 Ports: ports, 591 }) 592 } 593 } 594 } 595 } 596 // loading the single service if no versions 597 if len(serviceOverviews) == 0 { 598 ports := map[string]int{} 599 for _, port := range svc.Ports { 600 ports[port.Name] = int(port.Port) 601 } 602 serviceOverviews = append(serviceOverviews, &models.ServiceOverview{ 603 Name: svc.Name, 604 Ports: ports, 605 }) 606 } 607 608 s := models.ServiceDetails{Workloads: wo, Health: hth, NamespaceMTLS: nsmtls, SubServices: serviceOverviews} 609 s.Service = svc 610 s.SetPods(kubernetes.FilterPodsByEndpoints(eps, pods)) 611 // ServiceDetail will consider if the Service is a External/Federation entry 612 if s.Service.Type == "External" || s.Service.Type == "Federation" { 613 s.IstioSidecar = true 614 } else { 615 s.SetIstioSidecar(wo) 616 } 617 s.SetEndpoints(eps) 618 s.IstioPermissions = models.ResourcePermissions{ 619 Create: vsCreate, 620 Update: vsUpdate, 621 Delete: vsDelete, 622 } 623 s.VirtualServices = kubernetes.FilterAutogeneratedVirtualServices(kubernetes.FilterVirtualServicesByService(istioConfigList.VirtualServices, namespace, service)) 624 s.DestinationRules = kubernetes.FilterDestinationRulesByService(istioConfigList.DestinationRules, namespace, service) 625 s.K8sHTTPRoutes = kubernetes.FilterK8sHTTPRoutesByService(istioConfigList.K8sHTTPRoutes, istioConfigList.K8sReferenceGrants, namespace, service) 626 if s.Service.Type == "External" || s.Service.Type == "Federation" { 627 // On ServiceEntries cases the Service name is the hostname 628 s.ServiceEntries = kubernetes.FilterServiceEntriesByHostname(istioConfigList.ServiceEntries, s.Service.Name) 629 } 630 631 return &s, nil 632 } 633 634 func (in *SvcService) UpdateService(ctx context.Context, cluster, namespace, service string, interval string, queryTime time.Time, jsonPatch string, patchType string) (*models.ServiceDetails, error) { 635 var end observability.EndFunc 636 ctx, end = observability.StartSpan(ctx, "UpdateService", 637 observability.Attribute("package", "business"), 638 observability.Attribute("cluster", cluster), 639 observability.Attribute("namespace", namespace), 640 observability.Attribute("service", service), 641 observability.Attribute("interval", interval), 642 observability.Attribute("queryTime", queryTime), 643 observability.Attribute("jsonPatch", jsonPatch), 644 observability.Attribute("patchType", patchType), 645 ) 646 defer end() 647 648 // Identify controller and apply patch to workload 649 // Check if user has access to the namespace (RBAC) in cache scenarios and/or 650 // if namespace is accessible from Kiali (Deployment.AccessibleNamespaces) 651 if _, err := in.businessLayer.Namespace.GetClusterNamespace(context.TODO(), namespace, cluster); err != nil { 652 return nil, err 653 } 654 655 userClient, found := in.userClients[cluster] 656 if !found { 657 return nil, fmt.Errorf("cluster: %s not found", cluster) 658 } 659 660 if err := userClient.UpdateService(namespace, service, jsonPatch, patchType); err != nil { 661 return nil, err 662 } 663 664 // Stop and restart cache here after a Create/Update/Delete operation to force a refresh 665 // so that the next request that reads from the cache will be sure to have the write operation. 666 kubeCache, err := in.kialiCache.GetKubeCache(cluster) 667 if err != nil { 668 return nil, err 669 } 670 kubeCache.Refresh(namespace) 671 672 // After the update we fetch the whole workload 673 return in.GetServiceDetails(ctx, cluster, namespace, service, interval, queryTime) 674 } 675 676 func (in *SvcService) GetService(ctx context.Context, cluster, namespace, service string) (models.Service, error) { 677 var end observability.EndFunc 678 ctx, end = observability.StartSpan(ctx, "GetService", 679 observability.Attribute("package", "business"), 680 observability.Attribute("cluster", cluster), 681 observability.Attribute("namespace", namespace), 682 observability.Attribute("service", service), 683 ) 684 defer end() 685 686 // Check if user has access to the namespace (RBAC) in cache scenarios and/or 687 // if namespace is accessible from Kiali (Deployment.AccessibleNamespaces) 688 if _, err := in.businessLayer.Namespace.GetClusterNamespace(ctx, namespace, cluster); err != nil { 689 return models.Service{}, err 690 } 691 692 cache, err := in.kialiCache.GetKubeCache(cluster) 693 if err != nil { 694 return models.Service{}, err 695 } 696 697 svc := models.Service{} 698 // First try to get the service from kube. 699 // If it doesn't exist, try to get it from the Istio Registry. 700 kSvc, err := cache.GetService(namespace, service) 701 if err != nil { 702 // Check if this service is in the Istio Registry 703 criteria := RegistryCriteria{ 704 Namespace: namespace, 705 Cluster: cluster, 706 } 707 rSvcs := in.businessLayer.RegistryStatus.GetRegistryServices(criteria) 708 for _, rSvc := range rSvcs { 709 if rSvc.Attributes.Name == service { 710 svc.ParseRegistryService(cluster, rSvc) 711 break 712 } 713 } 714 // Service not found in Kubernetes and Istio 715 if svc.Name == "" { 716 return svc, kubernetes.NewNotFound(service, "Kiali", "Service") 717 } 718 } else { 719 svc.Parse(cluster, kSvc) 720 } 721 722 return svc, nil 723 } 724 725 func (in *SvcService) getServiceValidations(services []core_v1.Service, deployments []apps_v1.Deployment, pods []core_v1.Pod) models.IstioValidations { 726 validations := checkers.ServiceChecker{ 727 Services: services, 728 Deployments: deployments, 729 Pods: pods, 730 }.Check() 731 732 return validations 733 } 734 735 // GetServiceAppName returns the "Application" name (app label) that relates to a service 736 // This label is taken from the service selector, which means it is assumed that pods are selected using that label 737 func (in *SvcService) GetServiceAppName(ctx context.Context, cluster, namespace, service string) (string, error) { 738 var end observability.EndFunc 739 ctx, end = observability.StartSpan(ctx, "GetServiceAppName", 740 observability.Attribute("package", "business"), 741 observability.Attribute("cluster", cluster), 742 observability.Attribute("namespace", namespace), 743 observability.Attribute("service", service), 744 ) 745 defer end() 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 "", err 751 } 752 753 svc, err := in.GetService(ctx, cluster, namespace, service) 754 if err != nil { 755 return "", fmt.Errorf("Service [cluster: %s] [namespace: %s] [name: %s] doesn't exist.", cluster, namespace, service) 756 } 757 758 appLabelName := in.config.IstioLabels.AppLabelName 759 app := svc.Selectors[appLabelName] 760 return app, nil 761 }