-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create a /deep-dependencies endpoint #6654
base: main
Are you sure you want to change the base?
Changes from all commits
f2afcf1
d3aa3f0
c717744
c863013
bde7d9f
2389215
b7e4c9c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// Copyright (c) 2021 The Jaeger Authors. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package analytics | ||
|
||
import ( | ||
"encoding/json" | ||
"io" | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/gorilla/mux" | ||
"github.com/stretchr/testify/require" | ||
|
||
_ "github.com/jaegertracing/jaeger/pkg/gogocodec" // force gogo codec registration | ||
) | ||
|
||
// Utility functions used from http_gateway_test.go. | ||
type testGateway struct { | ||
url string | ||
router *mux.Router | ||
// used to set a tenancy header when executing requests | ||
setupRequest func(*http.Request) | ||
} | ||
|
||
func (gw *testGateway) execRequest(t *testing.T, url string) ([]byte, int) { | ||
req, err := http.NewRequest(http.MethodGet, gw.url+url, nil) | ||
require.NoError(t, err) | ||
req.Header.Set("Content-Type", "application/json") | ||
gw.setupRequest(req) | ||
response, err := http.DefaultClient.Do(req) | ||
require.NoError(t, err) | ||
body, err := io.ReadAll(response.Body) | ||
require.NoError(t, err) | ||
require.NoError(t, response.Body.Close()) | ||
return body, response.StatusCode | ||
} | ||
|
||
func runGatewayTests( | ||
t *testing.T, | ||
basePath string, | ||
setupRequest func(*http.Request), | ||
) { | ||
gw := setupHTTPGateway(t, basePath) | ||
gw.setupRequest = setupRequest | ||
t.Run("getDeepDependencies", gw.runGatewayGetDeepDependencies) | ||
} | ||
|
||
func (gw *testGateway) runGatewayGetDeepDependencies(t *testing.T) { | ||
expectedPath := []TDdgPayloadEntry{ | ||
{Service: "sample-serviceA", Operation: "sample-opA"}, | ||
{Service: "sample-serviceB", Operation: "sample-opB"}, | ||
} | ||
expectedAttributes := []Attribute{ | ||
{Key: "exemplar_trace_id", Value: "abc123"}, | ||
} | ||
|
||
body, statusCode := gw.execRequest(t, "/api/deep-dependencies") | ||
|
||
require.Equal(t, http.StatusOK, statusCode) | ||
|
||
var response TDdgPayload | ||
err := json.Unmarshal(body, &response) | ||
require.NoError(t, err) | ||
|
||
require.Len(t, response.Dependencies, 1) | ||
require.Equal(t, expectedPath, response.Dependencies[0].Path) | ||
require.Equal(t, expectedAttributes, response.Dependencies[0].Attributes) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
// Copyright (c) 2025 The Jaeger Authors. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package analytics | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
|
||
"github.com/gorilla/mux" | ||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" | ||
"go.opentelemetry.io/otel/trace" | ||
"go.uber.org/zap" | ||
) | ||
|
||
const ( | ||
routeGetDeepDependencies = "/api/deep-dependencies" | ||
) | ||
|
||
// HTTPGateway exposes analytics HTTP endpoints. | ||
type HTTPGateway struct { | ||
Logger *zap.Logger | ||
} | ||
|
||
func (h *HTTPGateway) RegisterRoutes(router *mux.Router) { | ||
h.addRoute(router, h.getDeepDependencies, routeGetDeepDependencies).Methods(http.MethodGet) | ||
} | ||
|
||
// addRoute adds a new endpoint to the router with given path and handler function. | ||
// This code is mostly copied from ../http_handler. | ||
func (*HTTPGateway) addRoute( | ||
router *mux.Router, | ||
f func(http.ResponseWriter, *http.Request), | ||
route string, | ||
_ ...any, /* args */ | ||
) *mux.Route { | ||
var handler http.Handler = http.HandlerFunc(f) | ||
handler = otelhttp.WithRouteTag(route, handler) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. isn't tracing of the endpoint already taken care of at the top level? |
||
handler = spanNameHandler(route, handler) | ||
return router.HandleFunc(route, handler.ServeHTTP) | ||
} | ||
|
||
func spanNameHandler(spanName string, handler http.Handler) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
span := trace.SpanFromContext(r.Context()) | ||
span.SetName(spanName) | ||
handler.ServeHTTP(w, r) | ||
}) | ||
} | ||
|
||
func (h *HTTPGateway) getDeepDependencies(w http.ResponseWriter, _ *http.Request) { | ||
dependencies := TDdgPayload{ | ||
Dependencies: []TDdgPayloadPath{ | ||
{ | ||
Path: []TDdgPayloadEntry{ | ||
{Service: "sample-serviceA", Operation: "sample-opA"}, | ||
{Service: "sample-serviceB", Operation: "sample-opB"}, | ||
}, | ||
Attributes: []Attribute{ | ||
{Key: "exemplar_trace_id", Value: "abc123"}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
w.Header().Set("Content-Type", "application/json") | ||
if err := json.NewEncoder(w).Encode(dependencies); err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
h.Logger.Error("Failed to encode response", zap.Error(err)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no need for logging |
||
} | ||
} | ||
|
||
// Structs that need to be used or created for deep dependencies | ||
type TDdgPayloadEntry struct { | ||
Operation string `json:"operation"` | ||
Service string `json:"service"` | ||
} | ||
|
||
type Attribute struct { | ||
Key string `json:"key"` | ||
Value string `json:"value"` | ||
} | ||
|
||
type TDdgPayloadPath struct { | ||
Path []TDdgPayloadEntry `json:"path"` | ||
Attributes []Attribute `json:"attributes"` | ||
} | ||
|
||
type TDdgPayload struct { | ||
Dependencies []TDdgPayloadPath `json:"dependencies"` | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// Copyright (c) 2025 The Jaeger Authors. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package analytics | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why two test files? |
||
|
||
import ( | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
"github.com/gorilla/mux" | ||
"go.uber.org/zap" | ||
) | ||
|
||
// setup the http gateway | ||
func setupHTTPGatewayNoServer( | ||
_ *testing.T, | ||
basePath string, | ||
) *testGateway { | ||
gw := &testGateway{} | ||
|
||
hgw := &HTTPGateway{ | ||
Logger: zap.NewNop(), | ||
} | ||
|
||
gw.router = &mux.Router{} | ||
if basePath != "" && basePath != "/" { | ||
gw.router = gw.router.PathPrefix(basePath).Subrouter() | ||
} | ||
hgw.RegisterRoutes(gw.router) | ||
return gw | ||
} | ||
|
||
func setupHTTPGateway( | ||
t *testing.T, | ||
basePath string, | ||
) *testGateway { | ||
gw := setupHTTPGatewayNoServer(t, basePath) | ||
|
||
httpServer := httptest.NewServer(gw.router) | ||
t.Cleanup(func() { httpServer.Close() }) | ||
|
||
gw.url = httpServer.URL | ||
if basePath != "/" { | ||
gw.url += basePath | ||
} | ||
return gw | ||
} | ||
|
||
func TestHTTPGateway(t *testing.T) { | ||
runGatewayTests(t, "/", func(_ *http.Request) {}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what's the value of this indirection? |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Copyright (c) 2023 The Jaeger Authors. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package analytics | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/jaegertracing/jaeger/pkg/testutils" | ||
) | ||
|
||
func TestMain(m *testing.M) { | ||
testutils.VerifyGoLeaks(m) | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -26,6 +26,7 @@ import ( | |||||||||
"google.golang.org/grpc/reflection" | ||||||||||
|
||||||||||
"github.com/jaegertracing/jaeger-idl/proto-gen/api_v2" | ||||||||||
"github.com/jaegertracing/jaeger/cmd/query/app/analytics" | ||||||||||
"github.com/jaegertracing/jaeger/cmd/query/app/apiv3" | ||||||||||
"github.com/jaegertracing/jaeger/cmd/query/app/querysvc" | ||||||||||
v2querysvc "github.com/jaegertracing/jaeger/cmd/query/app/querysvc/v2/querysvc" | ||||||||||
|
@@ -105,7 +106,6 @@ func registerGRPCHandlers( | |||||||||
|
||||||||||
api_v2.RegisterQueryServiceServer(server, handler) | ||||||||||
api_v3.RegisterQueryServiceServer(server, &apiv3.Handler{QueryService: v2QuerySvc}) | ||||||||||
|
||||||||||
healthServer.SetServingStatus("jaeger.api_v2.QueryService", grpc_health_v1.HealthCheckResponse_SERVING) | ||||||||||
healthServer.SetServingStatus("jaeger.api_v2.metrics.MetricsQueryService", grpc_health_v1.HealthCheckResponse_SERVING) | ||||||||||
healthServer.SetServingStatus("jaeger.api_v3.QueryService", grpc_health_v1.HealthCheckResponse_SERVING) | ||||||||||
|
@@ -183,6 +183,10 @@ func initRouter( | |||||||||
Tracer: telset.TracerProvider, | ||||||||||
}).RegisterRoutes(r) | ||||||||||
|
||||||||||
(&analytics.HTTPGateway{ | ||||||||||
Logger: telset.Logger, | ||||||||||
}).RegisterRoutes(r) | ||||||||||
Comment on lines
+186
to
+188
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
apiHandler.RegisterRoutes(r) | ||||||||||
staticHandlerCloser := RegisterStaticHandler(r, telset.Logger, queryOpts, querySvc.GetCapabilities()) | ||||||||||
|
||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this isn't a "gateway". I think you just need a static function RegisterRoutes for now. see https://github.com/jaegertracing/jaeger/pull/6608/files