github.com/kiali/kiali@v1.84.0/tracing/jaeger/http_client.go (about)

     1  package jaeger
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"net/url"
     9  	"path"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/kiali/kiali/config"
    15  	"github.com/kiali/kiali/log"
    16  	"github.com/kiali/kiali/models"
    17  	"github.com/kiali/kiali/tracing/jaeger/model"
    18  	"github.com/kiali/kiali/util"
    19  )
    20  
    21  type JaegerHTTPClient struct {
    22  	IgnoreCluster bool
    23  }
    24  
    25  // New client
    26  func NewJaegerClient(client http.Client, baseURL *url.URL) (jaegerClient *JaegerHTTPClient, err error) {
    27  
    28  	url := *baseURL
    29  	conf := config.Get()
    30  	var ignoreCluster bool
    31  	var jaegerService string
    32  	services := model.Services{}
    33  
    34  	url.Path = path.Join(url.Path, "/api/services")
    35  	resp, code, reqError := makeRequest(client, url.String(), nil)
    36  	if code != 200 || reqError != nil {
    37  		log.Debugf("Error getting query for tracing. cluster tags will be disabled.")
    38  		ignoreCluster = true
    39  		return &JaegerHTTPClient{IgnoreCluster: ignoreCluster}, nil
    40  	}
    41  	errUnmarshall := json.Unmarshal(resp, &services)
    42  	if errUnmarshall != nil {
    43  		log.Debugf("Error getting query for tracing. cluster tags will be disabled.")
    44  		ignoreCluster = true
    45  		return &JaegerHTTPClient{IgnoreCluster: ignoreCluster}, nil
    46  	}
    47  	for _, service := range services.Data {
    48  		if !strings.Contains(service, "istio") && !strings.Contains(service, "jaeger") {
    49  			jaegerService = service
    50  			break
    51  		}
    52  	}
    53  	urlTraces := *baseURL
    54  	urlTraces.Path = path.Join(urlTraces.Path, "/api/traces")
    55  
    56  	// if cluster exists in tags, use it
    57  	query := models.TracingQuery{}
    58  	tags := map[string]string{
    59  		"cluster": conf.KubernetesConfig.ClusterName,
    60  	}
    61  	query.Tags = tags
    62  	query.End = time.Now()
    63  	query.Start = query.End.Add(-10 * time.Minute)
    64  	query.Limit = 100
    65  	prepareQuery(&urlTraces, jaegerService, query, false)
    66  	r, err := queryTracesHTTP(client, &urlTraces)
    67  
    68  	if r != nil && err == nil && len(r.Data) == 0 || err != nil {
    69  		log.Debugf("Error getting query for tracing. cluster tags will be disabled.")
    70  		ignoreCluster = true
    71  	} else {
    72  		ignoreCluster = false
    73  	}
    74  
    75  	return &JaegerHTTPClient{IgnoreCluster: ignoreCluster}, nil
    76  }
    77  
    78  func (jc JaegerHTTPClient) GetAppTracesHTTP(client http.Client, baseURL *url.URL, serviceName string, q models.TracingQuery) (response *model.TracingResponse, err error) {
    79  	url := *baseURL
    80  	url.Path = path.Join(url.Path, "/api/traces")
    81  
    82  	// if cluster exists in tags, use it
    83  	prepareQuery(&url, serviceName, q, jc.IgnoreCluster)
    84  	r, err := queryTracesHTTP(client, &url)
    85  
    86  	if r != nil {
    87  		r.TracingServiceName = serviceName
    88  		if jc.IgnoreCluster {
    89  			r.FromAllClusters = true
    90  		}
    91  
    92  	}
    93  
    94  	return r, err
    95  }
    96  
    97  func (jc JaegerHTTPClient) GetTraceDetailHTTP(client http.Client, endpoint *url.URL, traceID string) (*model.TracingSingleTrace, error) {
    98  	u := *endpoint
    99  	// /querier/api/traces/<traceid>?mode=xxxx&blockStart=0000&blockEnd=FFFF&start=<start>&end=<end>
   100  	u.Path = path.Join(u.Path, "/api/traces/"+traceID)
   101  	resp, code, reqError := makeRequest(client, u.String(), nil)
   102  	if reqError != nil {
   103  		log.Errorf("Jaeger query error: %s [code: %d, URL: %v]", reqError, code, u)
   104  		return nil, reqError
   105  	}
   106  	// Jaeger would return "200 OK" when trace is not found, with an empty response
   107  	if len(resp) == 0 {
   108  		return nil, nil
   109  	}
   110  	response, err := unmarshal(resp, &u)
   111  	if err != nil {
   112  		return nil, err
   113  	}
   114  	if len(response.Data) == 0 {
   115  		return &model.TracingSingleTrace{Errors: response.Errors}, nil
   116  	}
   117  	return &model.TracingSingleTrace{
   118  		Data:   response.Data[0],
   119  		Errors: response.Errors,
   120  	}, nil
   121  }
   122  
   123  func (jc JaegerHTTPClient) GetServiceStatusHTTP(client http.Client, baseURL *url.URL) (bool, error) {
   124  	url := *baseURL
   125  	url.Path = path.Join(url.Path, "/api/services")
   126  	_, _, reqError := makeRequest(client, url.String(), nil)
   127  	return reqError == nil, reqError
   128  }
   129  
   130  func queryTracesHTTP(client http.Client, u *url.URL) (*model.TracingResponse, error) {
   131  	// HTTP and GRPC requests co-exist, but when minDuration is present, for HTTP it requires a unit (us)
   132  	// https://github.com/kiali/kiali/issues/3939
   133  	minDuration := u.Query().Get("minDuration")
   134  	if minDuration != "" && !strings.HasSuffix(minDuration, "us") {
   135  		query := u.Query()
   136  		query.Set("minDuration", minDuration+"us")
   137  		u.RawQuery = query.Encode()
   138  	}
   139  	resp, code, reqError := makeRequest(client, u.String(), nil)
   140  	if reqError != nil {
   141  		log.Errorf("Jaeger query error: %s [code: %d, URL: %v]", reqError, code, u)
   142  		return &model.TracingResponse{}, reqError
   143  	}
   144  	return unmarshal(resp, u)
   145  }
   146  
   147  func unmarshal(r []byte, u *url.URL) (*model.TracingResponse, error) {
   148  	var response model.TracingResponse
   149  	if errMarshal := json.Unmarshal(r, &response); errMarshal != nil {
   150  		log.Errorf("Error unmarshalling Jaeger response: %s [URL: %v]", errMarshal, u)
   151  		return nil, errMarshal
   152  	}
   153  	return &response, nil
   154  }
   155  
   156  func prepareQuery(u *url.URL, jaegerServiceName string, query models.TracingQuery, ignoreCluster bool) {
   157  	q := url.Values{}
   158  	q.Set("service", jaegerServiceName)
   159  	q.Set("start", fmt.Sprintf("%d", query.Start.Unix()*time.Second.Microseconds()))
   160  	q.Set("end", fmt.Sprintf("%d", query.End.Unix()*time.Second.Microseconds()))
   161  	var tags = util.CopyStringMap(query.Tags)
   162  
   163  	if ignoreCluster {
   164  		delete(tags, "cluster")
   165  	}
   166  	if len(tags) > 0 {
   167  		// Tags must be json encoded
   168  		tagsJson, err := json.Marshal(tags)
   169  		if err != nil {
   170  			log.Errorf("Jaeger query: error while marshalling tags to json: %v", err)
   171  		}
   172  		q.Set("tags", string(tagsJson))
   173  	}
   174  	if query.MinDuration > 0 {
   175  		q.Set("minDuration", fmt.Sprintf("%d", query.MinDuration.Microseconds()))
   176  	}
   177  	if query.Limit > 0 {
   178  		q.Set("limit", strconv.Itoa(query.Limit))
   179  	}
   180  	u.RawQuery = q.Encode()
   181  	log.Debugf("Prepared Jaeger query: %v", u)
   182  }
   183  
   184  func makeRequest(client http.Client, endpoint string, body io.Reader) (response []byte, status int, err error) {
   185  	response = nil
   186  	status = 0
   187  
   188  	req, err := http.NewRequest(http.MethodGet, endpoint, body)
   189  	if err != nil {
   190  		return
   191  	}
   192  	req.Header.Add("Accept", "application/json")
   193  	req.Header.Add("Content-Type", "application/json")
   194  	resp, err := client.Do(req)
   195  	if err != nil {
   196  		return
   197  	}
   198  	defer resp.Body.Close()
   199  	response, err = io.ReadAll(resp.Body)
   200  	status = resp.StatusCode
   201  	return
   202  }