k8s.io/apiserver@v0.31.1/pkg/endpoints/filterlatency/filterlatency_test.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package filterlatency
    18  
    19  import (
    20  	"context"
    21  	"net/http"
    22  	"net/http/httptest"
    23  	"testing"
    24  	"time"
    25  
    26  	sdktrace "go.opentelemetry.io/otel/sdk/trace"
    27  	"go.opentelemetry.io/otel/sdk/trace/tracetest"
    28  	noopoteltrace "go.opentelemetry.io/otel/trace/noop"
    29  
    30  	testingclock "k8s.io/utils/clock/testing"
    31  )
    32  
    33  func TestTrackStartedWithContextAlreadyHasFilterRecord(t *testing.T) {
    34  	filterName := "my-filter"
    35  	var (
    36  		callCount    int
    37  		filterRecord *requestFilterRecord
    38  	)
    39  	handler := http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) {
    40  		// we expect the handler to be invoked just once.
    41  		callCount++
    42  
    43  		// we expect the filter record to be set in the context
    44  		filterRecord = requestFilterRecordFrom(req.Context())
    45  	})
    46  
    47  	requestFilterStarted := time.Now()
    48  	wrapped := trackStarted(handler, noopoteltrace.NewTracerProvider(), filterName, testingclock.NewFakeClock(requestFilterStarted))
    49  
    50  	testRequest, err := http.NewRequest(http.MethodGet, "/api/v1/namespaces", nil)
    51  	if err != nil {
    52  		t.Fatalf("failed to create new http request - %v", err)
    53  	}
    54  	testRequest = testRequest.WithContext(withRequestFilterRecord(testRequest.Context(), &requestFilterRecord{
    55  		name:             "foo",
    56  		startedTimestamp: time.Now(),
    57  	}))
    58  
    59  	w := httptest.NewRecorder()
    60  	wrapped.ServeHTTP(w, testRequest)
    61  
    62  	if callCount != 1 {
    63  		t.Errorf("expected the given handler to be invoked once, but was actually invoked %d times", callCount)
    64  	}
    65  	if filterRecord == nil {
    66  		t.Fatal("expected a filter record in the request context, but got nil")
    67  	}
    68  	if filterName != filterRecord.name {
    69  		t.Errorf("expected filter name=%s but got=%s", filterName, filterRecord.name)
    70  	}
    71  	if requestFilterStarted != filterRecord.startedTimestamp {
    72  		t.Errorf("expected filter started timestamp=%s but got=%s", requestFilterStarted, filterRecord.startedTimestamp)
    73  	}
    74  }
    75  
    76  func TestTrackStartedWithContextDoesNotHaveFilterRecord(t *testing.T) {
    77  	filterName := "my-filter"
    78  	var (
    79  		callCount    int
    80  		filterRecord *requestFilterRecord
    81  	)
    82  	handler := http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) {
    83  		// we expect the handler to be invoked just once.
    84  		callCount++
    85  
    86  		// we expect the filter record to be set in the context
    87  		filterRecord = requestFilterRecordFrom(req.Context())
    88  	})
    89  
    90  	requestFilterStarted := time.Now()
    91  	wrapped := trackStarted(handler, noopoteltrace.NewTracerProvider(), filterName, testingclock.NewFakeClock(requestFilterStarted))
    92  
    93  	testRequest, err := http.NewRequest(http.MethodGet, "/api/v1/namespaces", nil)
    94  	if err != nil {
    95  		t.Fatalf("failed to create new http request - %v", err)
    96  	}
    97  
    98  	w := httptest.NewRecorder()
    99  	wrapped.ServeHTTP(w, testRequest)
   100  
   101  	if callCount != 1 {
   102  		t.Errorf("expected the given handler to be invoked once, but was actually invoked %d times", callCount)
   103  	}
   104  	if filterRecord == nil {
   105  		t.Fatal("expected a filter record in the request context, but got nil")
   106  	}
   107  	if filterName != filterRecord.name {
   108  		t.Errorf("expected filter name=%s but got=%s", filterName, filterRecord.name)
   109  	}
   110  	if requestFilterStarted != filterRecord.startedTimestamp {
   111  		t.Errorf("expected filter started timestamp=%s but got=%s", requestFilterStarted, filterRecord.startedTimestamp)
   112  	}
   113  }
   114  
   115  func TestTrackCompletedContextHasFilterRecord(t *testing.T) {
   116  	var (
   117  		handlerCallCount     int
   118  		actionCallCount      int
   119  		filterRecordGot      *requestFilterRecord
   120  		filterCompletedAtGot time.Time
   121  	)
   122  	handler := http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) {
   123  		// we expect the handler to be invoked just once.
   124  		handlerCallCount++
   125  	})
   126  
   127  	requestFilterEndedAt := time.Now()
   128  	wrapped := trackCompleted(handler, testingclock.NewFakeClock(requestFilterEndedAt), func(_ context.Context, fr *requestFilterRecord, completedAt time.Time) {
   129  		actionCallCount++
   130  		filterRecordGot = fr
   131  		filterCompletedAtGot = completedAt
   132  	})
   133  
   134  	testRequest, err := http.NewRequest(http.MethodGet, "/api/v1/namespaces", nil)
   135  	if err != nil {
   136  		t.Fatalf("failed to create new http request - %v", err)
   137  	}
   138  
   139  	testRequest = testRequest.WithContext(withRequestFilterRecord(testRequest.Context(), &requestFilterRecord{}))
   140  
   141  	w := httptest.NewRecorder()
   142  	wrapped.ServeHTTP(w, testRequest)
   143  
   144  	if handlerCallCount != 1 {
   145  		t.Errorf("expected the given handler to be invoked once, but was actually invoked %d times", handlerCallCount)
   146  	}
   147  	if actionCallCount != 1 {
   148  		t.Errorf("expected the action callback to be invoked once, but was actually invoked %d times", actionCallCount)
   149  	}
   150  	if filterRecordGot == nil {
   151  		t.Fatal("expected a filter record in the request context, but got nil")
   152  	}
   153  	if requestFilterEndedAt != filterCompletedAtGot {
   154  		t.Errorf("expected filter ended timestamp=%s but got=%s", requestFilterEndedAt, filterCompletedAtGot)
   155  	}
   156  }
   157  
   158  func TestTrackCompletedContextDoesNotHaveFilterRecord(t *testing.T) {
   159  	var actionCallCount, handlerCallCount int
   160  	handler := http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) {
   161  		handlerCallCount++
   162  	})
   163  
   164  	wrapped := trackCompleted(handler, testingclock.NewFakeClock(time.Now()), func(_ context.Context, _ *requestFilterRecord, _ time.Time) {
   165  		actionCallCount++
   166  	})
   167  
   168  	testRequest, err := http.NewRequest(http.MethodGet, "/api/v1/namespaces", nil)
   169  	if err != nil {
   170  		t.Fatalf("failed to create new http request - %v", err)
   171  	}
   172  
   173  	w := httptest.NewRecorder()
   174  	wrapped.ServeHTTP(w, testRequest)
   175  
   176  	if handlerCallCount != 1 {
   177  		t.Errorf("expected the given handler to be invoked once, but was actually invoked %d times", handlerCallCount)
   178  	}
   179  	if actionCallCount != 0 {
   180  		t.Errorf("expected the callback to not be invoked, but was actually invoked %d times", actionCallCount)
   181  	}
   182  }
   183  
   184  func TestStartedAndCompletedOpenTelemetryTracing(t *testing.T) {
   185  	filterName := "my-filter"
   186  	// Seup OTel for testing
   187  	fakeRecorder := tracetest.NewSpanRecorder()
   188  	tp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(fakeRecorder))
   189  
   190  	// base handler func
   191  	var callCount int
   192  	handler := http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) {
   193  		// we expect the handler to be invoked just once.
   194  		callCount++
   195  	})
   196  	// wrap with start and completed handler
   197  	wrapped := TrackCompleted(handler)
   198  	wrapped = TrackStarted(wrapped, tp, filterName)
   199  
   200  	testRequest, err := http.NewRequest(http.MethodGet, "/api/v1/namespaces", nil)
   201  	if err != nil {
   202  		t.Fatalf("failed to create new http request - %v", err)
   203  	}
   204  
   205  	wrapped.ServeHTTP(httptest.NewRecorder(), testRequest)
   206  
   207  	if callCount != 1 {
   208  		t.Errorf("expected the given handler to be invoked once, but was actually invoked %d times", callCount)
   209  	}
   210  	output := fakeRecorder.Ended()
   211  	if len(output) != 1 {
   212  		t.Fatalf("got %d; expected len(output) == 1", len(output))
   213  	}
   214  	span := output[0]
   215  	if span.Name() != filterName {
   216  		t.Fatalf("got %s; expected span.Name == my-filter", span.Name())
   217  	}
   218  }