github.com/kiali/kiali@v1.84.0/business/istio_status.go (about) 1 package business 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "time" 8 9 "k8s.io/apimachinery/pkg/labels" 10 11 "github.com/kiali/kiali/config" 12 "github.com/kiali/kiali/kubernetes" 13 "github.com/kiali/kiali/log" 14 "github.com/kiali/kiali/models" 15 "github.com/kiali/kiali/observability" 16 "github.com/kiali/kiali/util/httputil" 17 ) 18 19 func NewIstioStatusService(userClients map[string]kubernetes.ClientInterface, businessLayer *Layer, cpm ControlPlaneMonitor) IstioStatusService { 20 return IstioStatusService{ 21 userClients: userClients, 22 businessLayer: businessLayer, 23 controlPlaneMonitor: cpm, 24 } 25 } 26 27 // SvcService deals with fetching istio/kubernetes services related content and convert to kiali model 28 type IstioStatusService struct { 29 userClients map[string]kubernetes.ClientInterface 30 businessLayer *Layer 31 controlPlaneMonitor ControlPlaneMonitor 32 } 33 34 func (iss *IstioStatusService) GetStatus(ctx context.Context, cluster string) (kubernetes.IstioComponentStatus, error) { 35 var end observability.EndFunc 36 ctx, end = observability.StartSpan(ctx, "GetStatus", 37 observability.Attribute("package", "business"), 38 ) 39 defer end() 40 41 if !config.Get().ExternalServices.Istio.ComponentStatuses.Enabled || !config.Get().ExternalServices.Istio.IstioAPIEnabled { 42 return kubernetes.IstioComponentStatus{}, nil 43 } 44 45 ics, err := iss.getIstioComponentStatus(ctx, cluster) 46 if err != nil { 47 return nil, err 48 } 49 50 return ics.Merge(iss.getAddonComponentStatus()), nil 51 } 52 53 func (iss *IstioStatusService) getIstioComponentStatus(ctx context.Context, cluster string) (kubernetes.IstioComponentStatus, error) { 54 // Fetching workloads from component namespaces 55 workloads, err := iss.getComponentNamespacesWorkloads(ctx, cluster) 56 if err != nil { 57 return kubernetes.IstioComponentStatus{}, err 58 } 59 60 deploymentStatus, err := iss.getStatusOf(workloads) 61 if err != nil { 62 return kubernetes.IstioComponentStatus{}, err 63 } 64 65 k8s, ok := iss.userClients[cluster] 66 if !ok { 67 return kubernetes.IstioComponentStatus{}, fmt.Errorf("Cluster %s doesn't exist ", cluster) 68 } 69 70 istiodStatus, err := iss.controlPlaneMonitor.CanConnectToIstiod(k8s) 71 if err != nil { 72 return kubernetes.IstioComponentStatus{}, err 73 } 74 75 return deploymentStatus.Merge(istiodStatus), nil 76 } 77 78 func (iss *IstioStatusService) getComponentNamespacesWorkloads(ctx context.Context, cluster string) ([]*models.Workload, error) { 79 var wg sync.WaitGroup 80 81 nss := map[string]bool{} 82 wls := make([]*models.Workload, 0) 83 84 comNs := getComponentNamespaces() 85 86 wlChan := make(chan []*models.Workload, len(comNs)) 87 errChan := make(chan error, len(comNs)) 88 89 for _, n := range comNs { 90 if !nss[n] { 91 wg.Add(1) 92 nss[n] = true 93 94 go func(ctx context.Context, n string, wliChan chan []*models.Workload, errChan chan error) { 95 defer wg.Done() 96 var wls models.Workloads 97 var err error 98 wls, err = iss.businessLayer.Workload.fetchWorkloadsFromCluster(ctx, cluster, n, "") 99 wliChan <- wls 100 errChan <- err 101 }(ctx, n, wlChan, errChan) 102 } 103 } 104 105 wg.Wait() 106 107 close(wlChan) 108 close(errChan) 109 for err := range errChan { 110 if err != nil { 111 return nil, err 112 } 113 } 114 115 for wl := range wlChan { 116 if wl != nil { 117 wls = append(wls, wl...) 118 } 119 } 120 121 return wls, nil 122 } 123 124 func getComponentNamespaces() []string { 125 nss := make([]string, 0) 126 127 // By default, add the istio control plane namespace 128 nss = append(nss, config.Get().IstioNamespace) 129 130 // Adding Istio Components namespaces 131 externalServices := config.Get().ExternalServices 132 for _, cmp := range externalServices.Istio.ComponentStatuses.Components { 133 if cmp.Namespace != "" { 134 nss = append(nss, cmp.Namespace) 135 } 136 } 137 138 return nss 139 } 140 141 func istioCoreComponents() map[string]config.ComponentStatus { 142 components := map[string]config.ComponentStatus{} 143 cs := config.Get().ExternalServices.Istio.ComponentStatuses 144 for _, c := range cs.Components { 145 components[c.AppLabel] = c 146 } 147 return components 148 } 149 150 func (iss *IstioStatusService) getStatusOf(workloads []*models.Workload) (kubernetes.IstioComponentStatus, error) { 151 statusComponents := istioCoreComponents() 152 isc := kubernetes.IstioComponentStatus{} 153 cf := map[string]bool{} 154 mcf := map[string]int{} 155 156 // Map workloads there by app name 157 for _, workload := range workloads { 158 appLabel := labels.Set(workload.Labels).Get("app") 159 if appLabel == "" { 160 continue 161 } 162 163 stat, found := statusComponents[appLabel] 164 if !found { 165 continue 166 } 167 168 if stat.IsMultiCluster { 169 mcf[appLabel]++ 170 } else { 171 // Component found 172 cf[appLabel] = true 173 // @TODO when components exists on remote clusters only but config not marked multicluster 174 } 175 176 status := GetWorkloadStatus(*workload) 177 // Add status 178 isc = append(isc, kubernetes.ComponentStatus{ 179 Name: workload.Name, 180 Status: status, 181 IsCore: stat.IsCore, 182 }) 183 184 } 185 186 // Add missing deployments 187 componentNotFound := 0 188 for comp, stat := range statusComponents { 189 if _, found := cf[comp]; !found { 190 if number, mfound := mcf[comp]; !mfound || number < len(iss.userClients) { // multicluster components should exist on all clusters 191 componentNotFound += 1 192 isc = append(isc, kubernetes.ComponentStatus{ 193 Name: comp, 194 Status: kubernetes.ComponentNotFound, 195 IsCore: stat.IsCore, 196 }) 197 } 198 } 199 } 200 201 // When all the deployments are missing, 202 // Warn users that their kiali config might be wrong 203 if componentNotFound == len(statusComponents) { 204 return isc, fmt.Errorf( 205 "Kiali is unable to find any Istio deployment in namespace %s. Are you sure the Istio namespace is configured correctly in Kiali?", 206 config.Get().IstioNamespace) 207 } 208 209 return isc, nil 210 } 211 212 func GetWorkloadStatus(wl models.Workload) string { 213 status := kubernetes.ComponentUnhealthy 214 215 if wl.DesiredReplicas == 0 { 216 status = kubernetes.ComponentNotReady 217 } else if wl.DesiredReplicas == wl.AvailableReplicas && wl.DesiredReplicas == wl.CurrentReplicas { 218 status = kubernetes.ComponentHealthy 219 } 220 return status 221 } 222 223 func (iss *IstioStatusService) getAddonComponentStatus() kubernetes.IstioComponentStatus { 224 var wg sync.WaitGroup 225 wg.Add(4) 226 227 staChan := make(chan kubernetes.IstioComponentStatus, 4) 228 extServices := config.Get().ExternalServices 229 230 // https://github.com/kiali/kiali/issues/6966 - use the well-known Prom healthy endpoint 231 if extServices.Prometheus.HealthCheckUrl == "" { 232 extServices.Prometheus.HealthCheckUrl = extServices.Prometheus.URL + "/-/healthy" 233 } 234 235 ics := kubernetes.IstioComponentStatus{} 236 237 go getAddonStatus("prometheus", true, extServices.Prometheus.IsCore, &extServices.Prometheus.Auth, extServices.Prometheus.URL, extServices.Prometheus.HealthCheckUrl, staChan, &wg) 238 go getAddonStatus("grafana", extServices.Grafana.Enabled, extServices.Grafana.IsCore, &extServices.Grafana.Auth, extServices.Grafana.InClusterURL, extServices.Grafana.HealthCheckUrl, staChan, &wg) 239 go iss.getTracingStatus("tracing", extServices.Tracing.Enabled, extServices.Tracing.IsCore, staChan, &wg) 240 241 // Custom dashboards may use the main Prometheus config 242 customProm := extServices.CustomDashboards.Prometheus 243 if customProm.URL == "" { 244 customProm = extServices.Prometheus 245 } 246 go getAddonStatus("custom dashboards", extServices.CustomDashboards.Enabled, extServices.CustomDashboards.IsCore, &customProm.Auth, customProm.URL, customProm.HealthCheckUrl, staChan, &wg) 247 248 wg.Wait() 249 250 close(staChan) 251 for stat := range staChan { 252 ics.Merge(stat) 253 } 254 255 return ics 256 } 257 258 func getAddonStatus(name string, enabled bool, isCore bool, auth *config.Auth, url string, healthCheckUrl string, staChan chan<- kubernetes.IstioComponentStatus, wg *sync.WaitGroup) { 259 defer wg.Done() 260 261 // When the addOn is disabled, don't perform any check 262 if !enabled { 263 return 264 } 265 266 // Take the alternative health check url if present 267 if healthCheckUrl != "" { 268 url = healthCheckUrl 269 } 270 271 if auth.UseKialiToken { 272 token, _, err := kubernetes.GetKialiTokenForHomeCluster() 273 if err != nil { 274 log.Errorf("Could not read the Kiali Service Account token: %v", err) 275 } 276 auth.Token = token 277 } 278 279 status := kubernetes.ComponentHealthy 280 // Call the addOn service endpoint to find out whether is reachable or not 281 _, statusCode, _, err := httputil.HttpGet(url, auth, 10*time.Second, nil, nil) 282 if err != nil || statusCode > 399 { 283 log.Tracef("addon health check failed: name=[%v], url=[%v], code=[%v]", name, url, statusCode) 284 status = kubernetes.ComponentUnreachable 285 } 286 287 staChan <- kubernetes.IstioComponentStatus{ 288 kubernetes.ComponentStatus{ 289 Name: name, 290 Status: status, 291 IsCore: isCore, 292 }, 293 } 294 } 295 296 func (iss *IstioStatusService) getTracingStatus(name string, enabled bool, isCore bool, staChan chan<- kubernetes.IstioComponentStatus, wg *sync.WaitGroup) { 297 defer wg.Done() 298 299 if !enabled { 300 return 301 } 302 303 status := kubernetes.ComponentHealthy 304 305 accessible, err := iss.businessLayer.Tracing.GetStatus() 306 if !accessible { 307 log.Errorf("Error fetching availability of the tracing service: %v", err) 308 status = kubernetes.ComponentUnreachable 309 } 310 311 staChan <- kubernetes.IstioComponentStatus{ 312 kubernetes.ComponentStatus{ 313 Name: name, 314 Status: status, 315 IsCore: isCore, 316 }, 317 } 318 }