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 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions cmd/query/app/analytics/gateway_test.go
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)
}
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.
// 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 {
Copy link
Member

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

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

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 {
Copy link
Member

Choose a reason for hiding this comment

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

  1. encode in memory
  2. if error return 501
  3. otherwise write content-type and output

h.Logger.Error("Failed to encode response", zap.Error(err))
Copy link
Member

Choose a reason for hiding this comment

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

no need for logging

}

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

View check run for this annotation

Codecov / codecov/patch

cmd/query/app/analytics/http_gateway.go#L68-L69

Added lines #L68 - L69 were not covered by tests
}

// 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"`
}
52 changes: 52 additions & 0 deletions cmd/query/app/analytics/http_gateway_test.go
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
Copy link
Member

Choose a reason for hiding this comment

The 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) {})
Copy link
Member

Choose a reason for hiding this comment

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

what's the value of this indirection?

}
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)
}
6 changes: 5 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,10 @@ func initRouter(
Tracer: telset.TracerProvider,
}).RegisterRoutes(r)

(&analytics.HTTPGateway{
Logger: telset.Logger,
}).RegisterRoutes(r)
Comment on lines +186 to +188
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
(&analytics.HTTPGateway{
Logger: telset.Logger,
}).RegisterRoutes(r)
analytics.RegisterRoutes(r)


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

Expand Down
Loading