129 lines
4.6 KiB
Go
129 lines
4.6 KiB
Go
// Copyright 2015 Matthew Holt and The Caddy Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package reverseproxy
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/codes"
|
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
|
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
|
"go.opentelemetry.io/otel/trace"
|
|
|
|
"github.com/caddyserver/caddy/v2"
|
|
)
|
|
|
|
func TestUpstreamClientSpans(t *testing.T) {
|
|
var gotTraceparent []string
|
|
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
gotTraceparent = append(gotTraceparent, r.Header.Get("Traceparent"))
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
t.Cleanup(upstream.Close)
|
|
|
|
t.Run("traced request", func(t *testing.T) {
|
|
gotTraceparent = nil
|
|
recorder := tracetest.NewSpanRecorder()
|
|
tp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder))
|
|
t.Cleanup(func() { _ = tp.Shutdown(context.Background()) })
|
|
|
|
// round robin selects index 1 first, which refuses connections, so
|
|
// the retry produces a second client span under the same server span
|
|
h := minimalHandler(1,
|
|
&Upstream{Host: new(Host), Dial: upstream.Listener.Addr().String()},
|
|
&Upstream{Host: new(Host), Dial: deadUpstreamAddr(t)},
|
|
)
|
|
// HTTPTransport sets the URL scheme itself, so the span only records
|
|
// it if roundTrip calls SetScheme before the traced transport runs
|
|
caddyCtx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
|
|
t.Cleanup(cancel)
|
|
ht := new(HTTPTransport)
|
|
if err := ht.Provision(caddyCtx); err != nil {
|
|
t.Fatalf("provisioning transport: %v", err)
|
|
}
|
|
h.Transport = ht
|
|
h.tracedTransport = newTracedTransport(h.Transport)
|
|
|
|
ctx, serverSpan := tp.Tracer("test").Start(context.Background(), "server",
|
|
trace.WithSpanKind(trace.SpanKindServer))
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/", nil).WithContext(ctx)
|
|
req = prepareTestRequest(req)
|
|
rec := httptest.NewRecorder()
|
|
if err := h.ServeHTTP(rec, req, nil); err != nil {
|
|
t.Fatalf("ServeHTTP: %v", err)
|
|
}
|
|
serverSpan.End()
|
|
|
|
var clientSpans []sdktrace.ReadOnlySpan
|
|
for _, s := range recorder.Ended() {
|
|
if s.SpanKind() == trace.SpanKindClient {
|
|
clientSpans = append(clientSpans, s)
|
|
}
|
|
}
|
|
if len(clientSpans) != 2 {
|
|
t.Fatalf("expected 2 client spans, got %d", len(clientSpans))
|
|
}
|
|
for i, s := range clientSpans {
|
|
if s.Parent().SpanID() != serverSpan.SpanContext().SpanID() {
|
|
t.Errorf("client span %d: parent is not the server span", i)
|
|
}
|
|
}
|
|
if clientSpans[0].Status().Code != codes.Error {
|
|
t.Errorf("expected failed attempt to have error status, got %v", clientSpans[0].Status().Code)
|
|
}
|
|
|
|
upstreamHost, upstreamPort, _ := net.SplitHostPort(upstream.Listener.Addr().String())
|
|
attrs := attribute.NewSet(clientSpans[1].Attributes()...)
|
|
for key, want := range map[attribute.Key]string{
|
|
"url.full": "http://" + upstream.Listener.Addr().String() + "/",
|
|
"server.address": upstreamHost,
|
|
"server.port": upstreamPort,
|
|
"http.response.status_code": "200",
|
|
} {
|
|
if got, _ := attrs.Value(key); got.Emit() != want {
|
|
t.Errorf("client span attribute %s: expected %q, got %q", key, want, got.Emit())
|
|
}
|
|
}
|
|
|
|
if len(gotTraceparent) != 1 {
|
|
t.Fatalf("expected 1 upstream request, got %d", len(gotTraceparent))
|
|
}
|
|
want := clientSpans[1].SpanContext().SpanID().String()
|
|
if !strings.Contains(gotTraceparent[0], want) {
|
|
t.Errorf("upstream traceparent %q does not reference client span %s", gotTraceparent[0], want)
|
|
}
|
|
})
|
|
|
|
t.Run("untraced request", func(t *testing.T) {
|
|
gotTraceparent = nil
|
|
h := minimalHandler(0, &Upstream{Host: new(Host), Dial: upstream.Listener.Addr().String()})
|
|
h.tracedTransport = newTracedTransport(h.Transport)
|
|
|
|
req := prepareTestRequest(httptest.NewRequest(http.MethodGet, "http://example.com/", nil))
|
|
if err := h.ServeHTTP(httptest.NewRecorder(), req, nil); err != nil {
|
|
t.Fatalf("ServeHTTP: %v", err)
|
|
}
|
|
if len(gotTraceparent) != 1 || gotTraceparent[0] != "" {
|
|
t.Errorf("expected no traceparent upstream, got %q", gotTraceparent)
|
|
}
|
|
})
|
|
}
|