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 }