Skip to content
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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
90 changes: 90 additions & 0 deletions cmd/query/app/analytics/http_gateway.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright (c) 2025 The Jaeger Authors.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is the file called http_gateway? It does nothing of the sort.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I had named it before when I had made the gateway struct. Will rename it to deep_dependencies_handler

// 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"
)

const (
routeGetDeepDependencies = "/api/deep-dependencies"
)

// HTTPGateway exposes analytics HTTP endpoints.
type HTTPGateway struct{}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not used

Suggested change
// HTTPGateway exposes analytics HTTP endpoints.
type HTTPGateway struct{}

func RegisterRoutes(router *mux.Router) {
addRoute(router, 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 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)
Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But wouldn't that only trace the parent span? As I understand, the parent tracer will not go recursively into the children spans. Please clariy once.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are not adding any child spans, you're adding another, duplicate layer of instrumentation. It's redundant.

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 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"},
},
},
},
}

res, err := json.Marshal(dependencies)
if err != nil {
http.Error(w, "Failed to encode response", http.StatusNotImplemented)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StatusNotImplemented

it IS implemented.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. I will use InternalServerError.

return
}

Check warning on line 67 in cmd/query/app/analytics/http_gateway.go

View check run for this annotation

Codecov / codecov/patch

cmd/query/app/analytics/http_gateway.go#L65-L67

Added lines #L65 - L67 were not covered by tests
w.Header().Set("Content-Type", "application/json")
w.Write(res)
}

// 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"`
}
97 changes: 97 additions & 0 deletions cmd/query/app/analytics/http_gateway_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) 2025 The Jaeger Authors.
// SPDX-License-Identifier: Apache-2.0

package analytics

import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"

"github.com/gorilla/mux"
"github.com/stretchr/testify/require"

_ "github.com/jaegertracing/jaeger/pkg/gogocodec" // force gogo codec registration
)

type testGateway struct {
url string
router *mux.Router
// used to set a tenancy header when executing requests
setupRequest func(*http.Request)
}

func TestHTTPGateway(
t *testing.T,
) {
gw := setupHTTPGateway(t, "/")
gw.setupRequest = func(_ *http.Request) {}
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)
}

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 setupHTTPGatewayNoServer(
_ *testing.T,
basePath string,
) *testGateway {
gw := &testGateway{}

gw.router = &mux.Router{}
if basePath != "" && basePath != "/" {
gw.router = gw.router.PathPrefix(basePath).Subrouter()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this relevant?

RegisterRoutes(gw.router)
return gw
}

func setupHTTPGateway(
t *testing.T,
basePath string,
) *testGateway {
gw := setupHTTPGatewayNoServer(t, basePath)

httpServer := httptest.NewServer(gw.router)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no need to set up a server to test the RegisterRouter function. You can do it all with a mock http response recorder.

t.Cleanup(func() { httpServer.Close() })

gw.url = httpServer.URL
if basePath != "/" {
gw.url += basePath
}
return gw
}
14 changes: 14 additions & 0 deletions cmd/query/app/analytics/package_test.go
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)
}
4 changes: 3 additions & 1 deletion cmd/query/app/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -183,6 +183,8 @@ func initRouter(
Tracer: telset.TracerProvider,
}).RegisterRoutes(r)

analytics.RegisterRoutes(r)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why isn't it called from apiHandler.RegisterRoutes(r)?


apiHandler.RegisterRoutes(r)
staticHandlerCloser := RegisterStaticHandler(r, telset.Logger, queryOpts, querySvc.GetCapabilities())

Expand Down
Loading