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 }