github.com/verrazzano/verrazzano@v1.7.1/tests/e2e/pkg/jaeger.go (about) 1 // Copyright (c) 2022, 2023, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package pkg 5 6 import ( 7 "context" 8 "encoding/json" 9 "fmt" 10 "github.com/verrazzano/verrazzano/pkg/vzcr" 11 "github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/component/opensearch" 12 "net/http" 13 "net/url" 14 "strconv" 15 "strings" 16 "time" 17 18 "github.com/hashicorp/go-retryablehttp" 19 globalconst "github.com/verrazzano/verrazzano/pkg/constants" 20 "github.com/verrazzano/verrazzano/pkg/k8sutil" 21 "github.com/verrazzano/verrazzano/platform-operator/constants" 22 appsv1 "k8s.io/api/apps/v1" 23 v1 "k8s.io/api/batch/v1" 24 "k8s.io/api/batch/v1beta1" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/labels" 27 "k8s.io/apimachinery/pkg/selection" 28 "k8s.io/client-go/kubernetes" 29 ) 30 31 const ( 32 jaegerSpanIndexPrefix = "verrazzano-jaeger-span" 33 jaegerClusterNameLabel = "verrazzano_cluster" 34 adminClusterName = "local" 35 jaegerOperatorSampleMetric = "jaeger_operator_instances_managed" 36 jaegerAgentSampleMetric = "jaeger_agent_collector_proxy_total" 37 jaegerQuerySampleMetric = "jaeger_query_requests_total" 38 jaegerCollectorSampleMetric = "jaeger_collector_queue_capacity" 39 jaegerESIndexCleanerJob = "jaeger-operator-jaeger-es-index-cleaner" 40 componentLabelKey = "app.kubernetes.io/component" 41 instanceLabelKey = "app.kubernetes.io/instance" 42 ) 43 44 const ( 45 jaegerListServicesErrFmt = "Error listing services in Jaeger: url=%s, error=%v" 46 jaegerListTracesErrFmt = "Error listing traces in Jaeger: url=%s, error=%v" 47 ) 48 49 var ( 50 // common services running in both admin and managed cluster 51 managedClusterSystemServiceNames = []string{ 52 "fluentd.verrazzano-system", 53 "verrazzano-authproxy.verrazzano-system", 54 } 55 56 // services that are common plus the ones unique to admin cluster 57 adminClusterSystemServiceNames = append(managedClusterSystemServiceNames, 58 "jaeger.verrazzano-monitoring") 59 ) 60 61 type JaegerTraceData struct { 62 TraceID string `json:"traceID"` 63 Spans []struct { 64 TraceID string `json:"traceID"` 65 SpanID string `json:"spanID"` 66 Flags int `json:"flags"` 67 OperationName string `json:"operationName"` 68 References []struct { 69 RefType string `json:"refType"` 70 TraceID string `json:"traceID"` 71 SpanID string `json:"spanID"` 72 } `json:"references"` 73 StartTime int64 `json:"startTime"` 74 Duration int `json:"duration"` 75 Tags []struct { 76 Key string `json:"key"` 77 Type string `json:"type"` 78 Value interface{} `json:"value"` 79 } `json:"tags"` 80 Logs []struct { 81 Timestamp int64 `json:"timestamp"` 82 Fields []struct { 83 Key string `json:"key"` 84 Type string `json:"type"` 85 Value string `json:"value"` 86 } `json:"fields"` 87 } `json:"logs"` 88 ProcessID string `json:"processID"` 89 Warnings interface{} `json:"warnings"` 90 } `json:"spans"` 91 Processes struct { 92 P1 struct { 93 ServiceName string `json:"serviceName"` 94 Tags []struct { 95 Key string `json:"key"` 96 Type string `json:"type"` 97 Value string `json:"value"` 98 } `json:"tags"` 99 } `json:"p1"` 100 } `json:"processes"` 101 Warnings interface{} `json:"warnings"` 102 } 103 104 type JaegerTraceDataWrapper struct { 105 Data []JaegerTraceData `json:"data"` 106 Total int `json:"total"` 107 Limit int `json:"limit"` 108 Offset int `json:"offset"` 109 Errors interface{} `json:"errors"` 110 } 111 112 // IsJaegerInstanceCreated checks whether the default Jaeger CR is created 113 func IsJaegerInstanceCreated(kubeconfigPath string) (bool, error) { 114 collectorDeployments, err := GetJaegerCollectorDeployments(kubeconfigPath, globalconst.JaegerInstanceName) 115 if err != nil { 116 return false, err 117 } 118 Log(Info, fmt.Sprintf("cluster has %d jaeger-collector deployments", len(collectorDeployments))) 119 queryDeployments, err := GetJaegerQueryDeployments(kubeconfigPath, globalconst.JaegerInstanceName) 120 if err != nil { 121 return false, err 122 } 123 Log(Info, fmt.Sprintf("cluster has %d jaeger-query deployments", len(queryDeployments))) 124 return len(collectorDeployments) > 0 && len(queryDeployments) > 0, nil 125 } 126 127 // GetJaegerCollectorDeployments returns the deployment object of the Jaeger collector corresponding to the given 128 // 129 // Jaeger instance. If no instance name is provided, then it returns all Jaeger collector pods in the 130 // 131 // // verrazzano-monitoring namespace. 132 func GetJaegerCollectorDeployments(kubeconfigPath, jaegerCRName string) ([]appsv1.Deployment, error) { 133 labels := map[string]string{ 134 componentLabelKey: globalconst.JaegerCollectorComponentName, 135 } 136 if jaegerCRName != "" { 137 labels[instanceLabelKey] = jaegerCRName 138 } 139 Log(Info, fmt.Sprintf("Checking for collector deployments with labels %v", labels)) 140 deployments, err := ListDeploymentsMatchingLabelsInCluster(kubeconfigPath, constants.VerrazzanoMonitoringNamespace, labels) 141 if err != nil { 142 return nil, err 143 } 144 return deployments.Items, err 145 } 146 147 // GetJaegerQueryDeployments returns the deployment object of the Jaeger query corresponding to the given 148 // 149 // Jaeger instance. If no Jaeger instance name is provided, then it returns all Jaeger query pods in the 150 // verrazzano-monitoring namespace 151 func GetJaegerQueryDeployments(kubeconfigPath, jaegerCRName string) ([]appsv1.Deployment, error) { 152 labels := map[string]string{ 153 componentLabelKey: globalconst.JaegerQueryComponentName, 154 } 155 if jaegerCRName != "" { 156 labels[instanceLabelKey] = jaegerCRName 157 } 158 Log(Info, fmt.Sprintf("Checking for query deployments with labels %v", labels)) 159 deployments, err := ListDeploymentsMatchingLabelsInCluster(kubeconfigPath, constants.VerrazzanoMonitoringNamespace, labels) 160 if err != nil { 161 return nil, err 162 } 163 return deployments.Items, err 164 } 165 166 // JaegerSpanRecordFoundInOpenSearch checks if jaeger span records are found in OpenSearch storage 167 func JaegerSpanRecordFoundInOpenSearch(kubeconfigPath string, after time.Time, serviceName string) bool { 168 indexName, err := GetJaegerSpanIndexName(kubeconfigPath) 169 if err != nil { 170 return false 171 } 172 fields := map[string]string{ 173 "process.serviceName": serviceName, 174 } 175 searchResult := querySystemOpenSearch(indexName, fields, kubeconfigPath, false) 176 if len(searchResult) == 0 { 177 Log(Info, fmt.Sprintf("Expected to find log record matching fields %v", fields)) 178 return false 179 } 180 found := findJaegerSpanHits(searchResult, &after) 181 if !found { 182 Log(Error, fmt.Sprintf("Failed to find recent jaeger span record for service %s", serviceName)) 183 } 184 return found 185 } 186 187 // GetJaegerSpanIndexName returns the index name used in OpenSearch used for storage 188 func GetJaegerSpanIndexName(kubeconfigPath string) (string, error) { 189 var jaegerIndices []string 190 for _, indexName := range listSystemOpenSearchIndices(kubeconfigPath) { 191 if strings.HasPrefix(indexName, jaegerSpanIndexPrefix) { 192 jaegerIndices = append(jaegerIndices, indexName) 193 break 194 } 195 } 196 if len(jaegerIndices) > 0 { 197 return jaegerIndices[0], nil 198 } 199 return "", fmt.Errorf("Jaeger Span index not found") 200 } 201 202 // ListJaegerTracesWithTags lists all trace ids for a given service with the given tags 203 func ListJaegerTracesWithTags(kubeconfigPath string, start time.Time, serviceName string, tags map[string]string) []string { 204 var traces []string 205 params := url.Values{} 206 params.Add("service", serviceName) 207 params.Add("start", strconv.FormatInt(start.UnixMicro(), 10)) 208 params.Add("end", strconv.FormatInt(time.Now().UnixMicro(), 10)) 209 jsonStr, err := json.Marshal(tags) 210 if err != nil { 211 Log(Error, fmt.Sprintf("Error parsing tags %v to JSON string", tags)) 212 return traces 213 } 214 params.Add("tags", string(jsonStr)) 215 url := fmt.Sprintf("%s/api/traces?%s", getJaegerURL(kubeconfigPath), params.Encode()) 216 username, password, err := getJaegerUsernamePassword(kubeconfigPath) 217 if err != nil { 218 return traces 219 } 220 resp, err := getJaegerWithBasicAuth(url, "", username, password, kubeconfigPath) 221 if err != nil { 222 Log(Error, fmt.Sprintf(jaegerListTracesErrFmt, url, err)) 223 return traces 224 } 225 if resp.StatusCode != http.StatusOK { 226 Log(Error, fmt.Sprintf(jaegerListTracesErrFmt, url, resp.StatusCode)) 227 return traces 228 } 229 var jaegerTraceDataWrapper JaegerTraceDataWrapper 230 json.Unmarshal(resp.Body, &jaegerTraceDataWrapper) 231 for _, traceObj := range jaegerTraceDataWrapper.Data { 232 traces = append(traces, traceObj.TraceID) 233 } 234 Log(Info, fmt.Sprintf("Found %d traces for service %s", len(traces), serviceName)) 235 return traces 236 } 237 238 // ListServicesInJaeger lists the services whose traces are available in Jaeger 239 func ListServicesInJaeger(kubeconfigPath string) []string { 240 var services []string 241 url := fmt.Sprintf("%s/api/services", getJaegerURL(kubeconfigPath)) 242 username, password, err := getJaegerUsernamePassword(kubeconfigPath) 243 if err != nil { 244 return services 245 } 246 resp, err := getJaegerWithBasicAuth(url, "", username, password, kubeconfigPath) 247 if err != nil { 248 Log(Error, fmt.Sprintf(jaegerListServicesErrFmt, url, err)) 249 return services 250 } 251 if resp.StatusCode != http.StatusOK { 252 Log(Error, fmt.Sprintf(jaegerListServicesErrFmt, url, resp.StatusCode)) 253 return services 254 } 255 var serviceMap map[string][]string 256 json.Unmarshal(resp.Body, &serviceMap) 257 services = append(services, serviceMap["data"]...) 258 return services 259 } 260 261 // DoesCronJobExist returns whether a cronjob with the given name and namespace exists for the cluster 262 func DoesCronJobExist(kubeconfigPath, namespace string, name string) (bool, error) { 263 cronjobs, err := ListCronJobNamesMatchingLabels(kubeconfigPath, namespace, nil) 264 if err != nil { 265 Log(Error, fmt.Sprintf("Failed listing deployments in cluster for namespace %s: %v", namespace, err)) 266 return false, err 267 } 268 for _, cronJobName := range cronjobs { 269 if strings.HasPrefix(cronJobName, name) { 270 return true, nil 271 } 272 } 273 return false, nil 274 } 275 276 // ListDeploymentsMatchingLabelsInCluster returns the list of deployments in a given namespace matching the given labels for the cluster 277 func ListDeploymentsMatchingLabelsInCluster(kubeconfigPath, namespace string, matchLabels map[string]string) (*appsv1.DeploymentList, error) { 278 // Get the Kubernetes clientset 279 clientset, err := GetKubernetesClientsetForCluster(kubeconfigPath) 280 if err != nil { 281 return nil, err 282 } 283 listOptions := metav1.ListOptions{} 284 if matchLabels != nil { 285 selector := labels.NewSelector() 286 for k, v := range matchLabels { 287 selectorLabel, _ := labels.NewRequirement(k, selection.Equals, []string{v}) 288 selector = selector.Add(*selectorLabel) 289 } 290 listOptions.LabelSelector = selector.String() 291 } 292 deployments, err := clientset.AppsV1().Deployments(namespace).List(context.TODO(), listOptions) 293 if err != nil { 294 Log(Error, fmt.Sprintf("Failed to list deployments in namespace %s: %v", namespace, err)) 295 return nil, err 296 } 297 return deployments, nil 298 } 299 300 // ListCronJobNamesMatchingLabels returns the list of cronjobs in a given namespace matching the given labels for the cluster 301 func ListCronJobNamesMatchingLabels(kubeconfigPath, namespace string, matchLabels map[string]string) ([]string, error) { 302 var cronjobNames []string 303 // Get the Kubernetes clientset 304 clientset, err := GetKubernetesClientsetForCluster(kubeconfigPath) 305 if err != nil { 306 return nil, err 307 } 308 info, err := clientset.ServerVersion() 309 if err != nil { 310 return nil, err 311 } 312 majorVersion, err := strconv.Atoi(info.Major) 313 if err != nil { 314 return nil, err 315 } 316 if majorVersion > 1 { 317 return nil, fmt.Errorf("Unknown major version %d", majorVersion) 318 } 319 minorVersion, err := strconv.Atoi(info.Minor) 320 if err != nil { 321 return nil, err 322 } 323 // For k8s version 1.20 and lesser, cronjobs are created under version batch/v1beta1 324 // For k8s version greater than 1.20, cronjobs are created under version batch/v1 325 if minorVersion <= 20 { 326 cronJobs, err := listV1Beta1CronJobNames(clientset, namespace, fillLabelSelectors(matchLabels)) 327 if err != nil { 328 return nil, err 329 } 330 for _, cronjob := range cronJobs { 331 cronjobNames = append(cronjobNames, cronjob.Name) 332 } 333 } else { 334 cronJobs, err := listV1CronJobNames(clientset, namespace, fillLabelSelectors(matchLabels)) 335 if err != nil { 336 return nil, err 337 } 338 for _, cronjob := range cronJobs { 339 cronjobNames = append(cronjobNames, cronjob.Name) 340 } 341 } 342 return cronjobNames, nil 343 } 344 345 // GetJaegerSystemServicesInManagedCluster returns the system services that needs to be running in a managed cluster 346 func GetJaegerSystemServicesInManagedCluster() []string { 347 return managedClusterSystemServiceNames 348 } 349 350 // GetJaegerSystemServicesInAdminCluster returns the system services that needs to be running in a admin cluster 351 func GetJaegerSystemServicesInAdminCluster() []string { 352 return adminClusterSystemServiceNames 353 } 354 355 // ValidateJaegerOperatorMetricFunc returns a function that validates if metrics of Jaeger operator is scraped by prometheus. 356 func ValidateJaegerOperatorMetricFunc(metricsTest MetricsTest) func() bool { 357 return func() bool { 358 return metricsTest.MetricsExist(jaegerOperatorSampleMetric, map[string]string{}) 359 } 360 } 361 362 // ValidateJaegerCollectorMetricFunc returns a function that validates if metrics of Jaeger collector is scraped by prometheus. 363 func ValidateJaegerCollectorMetricFunc(metricsTest MetricsTest) func() bool { 364 return func() bool { 365 return metricsTest.MetricsExist(jaegerCollectorSampleMetric, map[string]string{}) 366 } 367 } 368 369 // ValidateJaegerQueryMetricFunc returns a function that validates if metrics of Jaeger query is scraped by prometheus. 370 func ValidateJaegerQueryMetricFunc(metricsTest MetricsTest) func() bool { 371 return func() bool { 372 return metricsTest.MetricsExist(jaegerQuerySampleMetric, map[string]string{}) 373 } 374 } 375 376 // ValidateJaegerAgentMetricFunc returns a function that validates if metrics of Jaeger agent is scraped by prometheus. 377 func ValidateJaegerAgentMetricFunc(metricsTest MetricsTest) func() bool { 378 return func() bool { 379 return metricsTest.MetricsExist(jaegerAgentSampleMetric, map[string]string{}) 380 } 381 } 382 383 // ValidateEsIndexCleanerCronJobFunc returns a function that validates if cron job for periodically cleaning the OS indices are created. 384 func ValidateEsIndexCleanerCronJobFunc() func() (bool, error) { 385 return func() (bool, error) { 386 kubeconfigPath, err := k8sutil.GetKubeConfigLocation() 387 if err != nil { 388 return false, err 389 } 390 vz, err := GetVerrazzanoInstallResourceInCluster(kubeconfigPath) 391 if err != nil { 392 return false, err 393 } 394 create := vzcr.IsComponentStatusEnabled(vz, opensearch.ComponentName) 395 if create { 396 return DoesCronJobExist(kubeconfigPath, constants.VerrazzanoMonitoringNamespace, jaegerESIndexCleanerJob) 397 } 398 return false, nil 399 } 400 } 401 402 // ValidateSystemTracesFuncInCluster returns a function that validates if system traces for the given cluster can be successfully queried from Jaeger 403 func ValidateSystemTracesFuncInCluster(kubeconfigPath string, start time.Time, clusterName string) func() (bool, error) { 404 return func() (bool, error) { 405 // Check if the service name is registered in Jaeger and traces are present for that service 406 systemServices := GetJaegerSystemServicesInManagedCluster() 407 if clusterName == "admin" || clusterName == "local" { 408 systemServices = GetJaegerSystemServicesInAdminCluster() 409 } 410 tracesFound := true 411 for i := 0; i < len(systemServices); i++ { 412 Log(Info, fmt.Sprintf("Inspecting traces for service: %s", systemServices[i])) 413 if i == 0 { 414 tracesFound = 415 len(ListJaegerTracesWithTags(kubeconfigPath, start, systemServices[i], 416 map[string]string{"verrazzano_cluster": clusterName})) > 0 417 } else { 418 tracesFound = tracesFound && len(ListJaegerTracesWithTags(kubeconfigPath, start, systemServices[i], 419 map[string]string{"verrazzano_cluster": clusterName})) > 0 420 } 421 Log(Info, fmt.Sprintf("Trace found flag for service: %s is %v", systemServices[i], tracesFound)) 422 // return early and retry later 423 if !tracesFound { 424 return false, nil 425 } 426 } 427 return tracesFound, nil 428 } 429 } 430 431 // ValidateSystemTracesInOSFunc returns a function that validates if system traces are stored successfully in OS backend storage 432 func ValidateSystemTracesInOSFunc(start time.Time) func() bool { 433 return func() bool { 434 kubeconfigPath, err := k8sutil.GetKubeConfigLocation() 435 if err != nil { 436 return false 437 } 438 tracesFound := true 439 systemServices := GetJaegerSystemServicesInAdminCluster() 440 for i := 0; i < len(systemServices); i++ { 441 Log(Info, fmt.Sprintf("Finding traces for service %s after %s", systemServices[i], start.String())) 442 if i == 0 { 443 tracesFound = JaegerSpanRecordFoundInOpenSearch(kubeconfigPath, start, systemServices[i]) 444 } else { 445 tracesFound = tracesFound && JaegerSpanRecordFoundInOpenSearch(kubeconfigPath, start, systemServices[i]) 446 } 447 // return early and retry later 448 if !tracesFound { 449 return false 450 } 451 } 452 return tracesFound 453 } 454 } 455 456 // ValidateApplicationTracesInCluster returns a function that validates if application traces can be successfully queried from Jaeger 457 func ValidateApplicationTracesInCluster(kubeconfigPath string, start time.Time, appServiceName, clusterName string) func() (bool, error) { 458 return func() (bool, error) { 459 tracesFound := false 460 servicesWithJaegerTraces := ListServicesInJaeger(kubeconfigPath) 461 for _, serviceName := range servicesWithJaegerTraces { 462 Log(Info, fmt.Sprintf("Checking if service name %s matches the expected app service %s", serviceName, appServiceName)) 463 if strings.HasPrefix(serviceName, appServiceName) { 464 Log(Info, fmt.Sprintf("Finding traces for service %s after %s", serviceName, start.String())) 465 traceIds := ListJaegerTracesWithTags(kubeconfigPath, start, appServiceName, 466 map[string]string{"verrazzano_cluster": clusterName}) 467 tracesFound = len(traceIds) > 0 468 if !tracesFound { 469 errMsg := fmt.Sprintf("traces not found for service: %s", serviceName) 470 Log(Error, errMsg) 471 return false, fmt.Errorf(errMsg) 472 } 473 break 474 } 475 } 476 return tracesFound, nil 477 } 478 } 479 480 // ValidateApplicationTracesInOS returns a function that validates if application traces are stored successfully in OS backend storage 481 func ValidateApplicationTracesInOS(start time.Time, appServiceName string) func() bool { 482 return func() bool { 483 kubeconfigPath, err := k8sutil.GetKubeConfigLocation() 484 if err != nil { 485 return false 486 } 487 return JaegerSpanRecordFoundInOpenSearch(kubeconfigPath, start, appServiceName) 488 } 489 } 490 491 // GenerateTrafficForTraces creates some HTTP requests to the application so that the corresponding traces would be generated for them. 492 func GenerateTrafficForTraces(namespace, appConfigName, urlPath, kubeconfigPath string) error { 493 // Get the host URL from the gateway and send 10 test requests to generate traces 494 host, err := k8sutil.GetHostnameFromGatewayInCluster(namespace, appConfigName, kubeconfigPath) 495 if err != nil { 496 Log(Error, err.Error()) 497 return err 498 } 499 Log(Info, fmt.Sprintf("Found hostname %s from gateway", host)) 500 for i := 0; i < 10; i++ { 501 url := fmt.Sprintf("https://%s/%s", host, urlPath) 502 resp, err := GetWebPageInCluster(url, host, kubeconfigPath) 503 if err != nil { 504 Log(Error, fmt.Sprintf("Error sending request to %s app: %v", appConfigName, err.Error())) 505 return err 506 } 507 if resp.StatusCode == http.StatusOK { 508 Log(Info, fmt.Sprintf("Successfully sent request to %s app: %v", appConfigName, resp.StatusCode)) 509 } else { 510 err = fmt.Errorf("got error response code %v", resp.StatusCode) 511 Log(Error, err.Error()) 512 } 513 } 514 return nil 515 } 516 517 // fillLabelSelectors fills the match labels from map to be passed in list options 518 func fillLabelSelectors(matchLabels map[string]string) metav1.ListOptions { 519 listOptions := metav1.ListOptions{} 520 if matchLabels != nil { 521 var selector labels.Selector 522 for k, v := range matchLabels { 523 selectorLabel, _ := labels.NewRequirement(k, selection.Equals, []string{v}) 524 selector = labels.NewSelector() 525 selector = selector.Add(*selectorLabel) 526 } 527 listOptions.LabelSelector = selector.String() 528 } 529 return listOptions 530 } 531 532 // listV1CronJobNames lists the cronjob under batch/v1 api version for k8s version > 1.20 533 func listV1CronJobNames(clientset *kubernetes.Clientset, namespace string, listOptions metav1.ListOptions) ([]v1.CronJob, error) { 534 var cronJobs []v1.CronJob 535 cronJobList, err := clientset.BatchV1().CronJobs(namespace).List(context.TODO(), listOptions) 536 if err != nil { 537 Log(Error, fmt.Sprintf("Failed to list v1/cronjobs in namespace %s: %v", namespace, err)) 538 return cronJobs, err 539 } 540 return cronJobList.Items, nil 541 } 542 543 // listV1Beta1CronJobNames lists the cronjob under batch/v1beta1 api version for k8s version <= 1.20 544 func listV1Beta1CronJobNames(clientset *kubernetes.Clientset, namespace string, listOptions metav1.ListOptions) ([]v1beta1.CronJob, error) { 545 var cronJobs []v1beta1.CronJob 546 cronJobList, err := clientset.BatchV1beta1().CronJobs(namespace).List(context.TODO(), listOptions) 547 if err != nil { 548 Log(Error, fmt.Sprintf("Failed to list v1beta1/cronjobs in namespace %s: %v", namespace, err)) 549 return cronJobs, err 550 } 551 return cronJobList.Items, nil 552 } 553 554 // getJaegerWithBasicAuth access Jaeger with GET using basic auth, using a given kubeconfig 555 func getJaegerWithBasicAuth(url string, hostHeader string, username string, password string, kubeconfigPath string) (*HTTPResponse, error) { 556 retryableClient, err := getJaegerClient(kubeconfigPath) 557 if err != nil { 558 return nil, err 559 } 560 return doReq(url, "GET", "", hostHeader, username, password, nil, retryableClient) 561 } 562 563 // getJaegerClient returns the Jaeger client which can be used for GET/POST operations using a given kubeconfig 564 func getJaegerClient(kubeconfigPath string) (*retryablehttp.Client, error) { 565 client, err := GetVerrazzanoHTTPClient(kubeconfigPath) 566 if err != nil { 567 return nil, err 568 } 569 return client, err 570 } 571 572 // getJaegerURL returns Jaeger URL from the corresponding ingress resource using the given kubeconfig 573 func getJaegerURL(kubeconfigPath string) string { 574 clientset, err := GetKubernetesClientsetForCluster(kubeconfigPath) 575 if err != nil { 576 Log(Error, fmt.Sprintf("Failed to get clientset for cluster %v", err)) 577 return "" 578 } 579 ingressList, _ := clientset.NetworkingV1().Ingresses(VerrazzanoNamespace).List(context.TODO(), metav1.ListOptions{}) 580 for _, ingress := range ingressList.Items { 581 if ingress.Name == "verrazzano-jaeger" { 582 Log(Info, fmt.Sprintf("Found Jaeger Ingress %v, host %s", ingress.Name, ingress.Spec.Rules[0].Host)) 583 return fmt.Sprintf("https://%s", ingress.Spec.Rules[0].Host) 584 } 585 } 586 return "" 587 } 588 589 // getJaegerUsernamePassword returns the username/password for connecting to Jaeger 590 func getJaegerUsernamePassword(kubeconfigPath string) (username, password string, err error) { 591 password, err = GetVerrazzanoPasswordInCluster(kubeconfigPath) 592 if err != nil { 593 return "", "", err 594 } 595 return "verrazzano", password, err 596 } 597 598 // findJaegerSpanHits returns the number of span hits that are older than the given time 599 func findJaegerSpanHits(searchResult map[string]interface{}, after *time.Time) bool { 600 hits := Jq(searchResult, "hits", "hits") 601 if hits == nil { 602 Log(Info, "Expected to find hits in span record query results") 603 return false 604 } 605 Log(Info, fmt.Sprintf("Found %d records", len(hits.([]interface{})))) 606 if len(hits.([]interface{})) == 0 { 607 Log(Info, "Expected span record query results to contain at least one hit") 608 return false 609 } 610 if after == nil { 611 return true 612 } 613 for _, hit := range hits.([]interface{}) { 614 timestamp := Jq(hit, "_source", "startTimeMillis") 615 t := time.UnixMilli(int64(timestamp.(float64))) 616 if t.After(*after) { 617 Log(Info, fmt.Sprintf("Found recent record: %f", timestamp)) 618 return true 619 } 620 Log(Info, fmt.Sprintf("Found old record: %f", timestamp)) 621 } 622 return true 623 }