github.com/aristanetworks/goarista@v0.0.0-20240514173732-cca2755bbd44/monitor/loglevel_test.go (about) 1 // Copyright (c) 2022 Arista Networks, Inc. 2 // Use of this source code is governed by the Apache License 2.0 3 // that can be found in the COPYING file. 4 5 package monitor 6 7 import ( 8 "io" 9 "net/http" 10 "net/http/httptest" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/aristanetworks/glog" 16 ) 17 18 func req(method string, params ...string) *http.Request { 19 req := httptest.NewRequest(method, "/whatever", nil) 20 q := req.URL.Query() 21 for i := 0; i < len(params); i += 2 { 22 q.Add(params[i], params[i+1]) 23 } 24 req.URL.RawQuery = q.Encode() 25 return req 26 } 27 28 func call(t *testing.T, srv *logsetSrv, req *http.Request) *http.Response { 29 t.Helper() 30 w := httptest.NewRecorder() 31 srv.ServeHTTP(w, req) 32 resp := w.Result() 33 body, _ := io.ReadAll(resp.Body) 34 t.Logf("req = %#v, resp = %q", req, string(body)) 35 return resp 36 } 37 38 func hasUpdate[T comparable](t *testing.T, req loglevelReq, key string, want *T) { 39 t.Helper() 40 got, ok := req.updates[key].(T) 41 if !ok { 42 if want == nil { 43 return 44 } 45 t.Fatalf("hasUpdate(%q): got updates %#v, should contain %#v", key, req.updates, *want) 46 } 47 if want == nil { 48 t.Fatalf("hasUpdate(%q): got updates %#v, should contain %#v", key, req.updates, want) 49 } 50 if got != *want { 51 t.Fatalf("hasUpdate(%q): got updates %#v, should contain %#v", key, req.updates, *want) 52 } 53 } 54 55 func TestRequestParsing(t *testing.T) { 56 tcases := map[string]struct { 57 req *http.Request 58 wantErr string 59 wantGlog *glogUpdater 60 wantVModule *vModuleUpdater 61 }{ 62 "GET": { 63 req: req("GET"), 64 wantErr: "method must be POST", 65 }, 66 "empty POST": { 67 req: req("POST"), 68 wantErr: "empty request", 69 }, 70 "only timeout": { 71 req: req("POST", "timeout", "5m"), 72 wantErr: "empty request", 73 }, 74 "error small": { 75 req: req("POST", "timeout", ".1s"), 76 wantErr: "timeout too small", 77 }, 78 "error large": { 79 req: req("POST", "timeout", "24h1s"), 80 wantErr: "timeout too large", 81 }, 82 83 // glog parsing 84 "invalid glog": { 85 req: req("POST", glogV, "??"), 86 wantErr: "invalid glog argument", 87 }, 88 "negative glog": { 89 req: req("POST", glogV, "-1"), 90 wantErr: "invalid glog argument", 91 }, 92 "glog": { 93 req: req("POST", glogV, "0"), 94 wantGlog: &glogUpdater{v: 0}, 95 }, 96 "glog with timeout": { 97 req: req("POST", glogV, "1", "timeout", "10s"), 98 wantGlog: &glogUpdater{v: 1}, 99 }, 100 "glog with 24h timeout": { 101 req: req("POST", glogV, "2", "timeout", "24h"), 102 wantGlog: &glogUpdater{v: 2}, 103 }, 104 "glog with 1s timeout": { 105 req: req("POST", glogV, "3", "timeout", "1s"), 106 wantGlog: &glogUpdater{v: 3}, 107 }, 108 109 // vmodule parsing 110 "invalid vmodule": { 111 req: req("POST", glogVModule, "not valid"), 112 wantErr: "invalid glog-vmodule argument", 113 }, 114 "invalid vmodule 2": { 115 req: req("POST", glogVModule, "x="), 116 wantErr: "invalid glog-vmodule argument", 117 }, 118 "invalid vmodule 3": { 119 req: req("POST", glogVModule, "x=09,asdf"), 120 wantErr: "invalid glog-vmodule argument", 121 }, 122 "valid vmodule": { 123 req: req("POST", glogVModule, "x=09,y=100,x/y/z=0"), 124 wantVModule: &vModuleUpdater{v: "x=09,y=100,x/y/z=0"}, 125 }, 126 "valid vmodule that should not work": { 127 req: req("POST", glogVModule, "invalid:;path:-_=10"), 128 wantVModule: &vModuleUpdater{v: "invalid:;path:-_=10"}, 129 }, 130 } 131 132 for name, tcase := range tcases { 133 t.Run(name, func(t *testing.T) { 134 req, err := parseLoglevelReq(tcase.req) 135 if tcase.wantErr != "" && err == nil { 136 t.Fatalf("expected error %v: got nil", tcase.wantErr) 137 } else if err != nil && !strings.Contains(err.Error(), tcase.wantErr) { 138 t.Fatalf("expected error to contain %q: got %q", tcase.wantErr, err.Error()) 139 } else if tcase.wantErr == "" && err != nil { 140 t.Fatalf("unexpected error: %v", err) 141 } 142 hasUpdate(t, req, glogV, tcase.wantGlog) 143 hasUpdate(t, req, glogVModule, tcase.wantVModule) 144 145 }) 146 } 147 } 148 149 func TestGlogLogset(t *testing.T) { 150 t.Run("updater", func(t *testing.T) { 151 defer glog.SetVGlobal(glog.SetVGlobal(42)) // init and reset 152 updater := glogUpdater{v: glog.Level(100)} 153 resetter, err := updater.Apply() 154 if err != nil { 155 t.Fatalf("error applying update: %v", err) 156 } 157 if got := glog.VGlobal(); got != 100 { 158 t.Fatalf("glog verbosity should be 100, got %#v", got) 159 } 160 resetter() 161 if got := glog.VGlobal(); got != 42 { 162 t.Fatalf("glog verbosity should be 42, got %#v", got) 163 } 164 165 }) 166 167 t.Run("request", func(t *testing.T) { 168 defer glog.SetVGlobal(glog.SetVGlobal(0)) // init and reset 169 ls := newLogsetSrv() 170 resp := call(t, ls, req("POST", glogV, "1")) 171 if resp.StatusCode != 200 { 172 t.Fatalf("expected status 200, wanted %v", resp.StatusCode) 173 } 174 if v := glog.VGlobal(); v != 1 { 175 t.Fatalf("expected glog %v, got %v", v, 1) 176 } 177 }) 178 } 179 180 func TestVModuleSet(t *testing.T) { 181 t.Run("updater", func(t *testing.T) { 182 // reset vmodule at end of test 183 prev, err := glog.SetVModule("prev=99") 184 if err != nil { 185 t.Fatalf("vmodule call failed: %v", err) 186 } 187 defer glog.SetVModule(prev) 188 updater := vModuleUpdater{v: "next=100"} 189 resetter, err := updater.Apply() 190 if err != nil { 191 t.Fatalf("error applying vmodule update: %v", err) 192 } 193 if got := glog.VModule(); got != "next=100" { 194 t.Fatalf("vmodule should be 'next=100', got %#v", got) 195 } 196 resetter() 197 if got := glog.VModule(); got != "prev=99" { 198 t.Fatalf("vmodule should be 'prev=99', got %#v", got) 199 } 200 201 }) 202 t.Run("request", func(t *testing.T) { 203 // reset vmodule at end of test 204 prev, err := glog.SetVModule("") 205 if err != nil { 206 t.Fatalf("vmodule call failed: %v", err) 207 } 208 defer glog.SetVModule(prev) 209 // make request 210 ls := newLogsetSrv() 211 resp := call(t, ls, req("POST", glogVModule, "y=001")) 212 if resp.StatusCode != 200 { 213 t.Fatalf("expected status 200, wanted %v", resp.StatusCode) 214 } 215 if v := glog.VModule(); v != "y=1" { 216 t.Fatalf("expected vmodule x=001, got %q", v) 217 } 218 }) 219 } 220 221 type mockedRequest struct { 222 timerCreated chan time.Duration // recevies the time Duration when timer is created 223 timerTrigger chan time.Time // channel that you can send a time to trigger timer 224 timerCancelled chan struct{} // will be closed if timer is cancelled 225 logApplied chan struct{} // request has been applied when this channel is closed 226 logReset chan struct{} // request has been reset when this channeel is closed 227 } 228 229 // Apply implements mockLogUpdater 230 func (m mockedRequest) Apply() (func(), error) { 231 close(m.logApplied) 232 return func() { 233 close(m.logReset) 234 }, nil 235 } 236 237 type mockTimerImpl struct { 238 c chan time.Time 239 } 240 241 func (m *mockTimerImpl) C() <-chan time.Time { 242 return m.c 243 } 244 245 func (m *mockTimerImpl) Stop() bool { 246 return true // returning true means we won't try and drain the channel; thats okay 247 } 248 249 func newMockedRequest(t *testing.T, ls *logsetSrv, opts ...string) mockedRequest { 250 m := mockedRequest{ 251 timerCreated: make(chan time.Duration, 1), 252 timerTrigger: make(chan time.Time), 253 logApplied: make(chan struct{}), 254 logReset: make(chan struct{}), 255 } 256 // setup dependency injected timer 257 newTimer := func(d time.Duration) timer { 258 m.timerCreated <- d 259 return &mockTimerImpl{c: m.timerTrigger} 260 } 261 262 ls.mu.Lock() 263 ls.timer = newTimer // has to be set on each new request 264 ls.mu.Unlock() 265 266 args := []string{glogV, "1"} 267 args = append(args, opts...) 268 req := req("POST", args...) 269 request, err := parseLoglevelReq(req) 270 if err != nil { 271 t.Fatalf("could not create glog request: %v", err) 272 } 273 request.updates[glogV] = m 274 275 t.Log("send logrequest", opts) 276 if err := ls.handle(request); err != nil { 277 t.Fatalf("unexpected error: %v", err) 278 } 279 280 ls.mu.Lock() 281 if v, exists := ls.resetTo[glogV]; exists { 282 m.timerCancelled = v.cancel 283 } 284 ls.mu.Unlock() 285 return m 286 } 287 288 func TestResetBehavior(t *testing.T) { 289 t.Run("reset is called", func(t *testing.T) { 290 ls := newLogsetSrv() 291 req := newMockedRequest(t, ls, "timeout", "1s") 292 <-req.logApplied 293 if timerD := <-req.timerCreated; timerD != time.Second { 294 t.Fatalf("expected timer to be set for %v, got %v", time.Second, timerD) 295 } 296 297 req.timerTrigger <- time.Time{} 298 <-req.logReset 299 300 t.Log("wait until all ongoing resets are done") 301 ls.wg.Wait() 302 }) 303 304 t.Run("updating loglevel twice with no overlapping timeout works", func(t *testing.T) { 305 ls := newLogsetSrv() 306 307 req1 := newMockedRequest(t, ls, "timeout", "33s") 308 <-req1.logApplied 309 if timerD := <-req1.timerCreated; timerD != time.Second*33 { 310 t.Fatalf("expected timer to be set for %v, got %v", time.Second*33, timerD) 311 } 312 313 req1.timerTrigger <- time.Time{} 314 t.Log("request 1 should be reset after timer is triggered") 315 <-req1.logReset 316 317 req2 := newMockedRequest(t, ls, "timeout", "45s") 318 <-req2.logApplied 319 if timerD := <-req2.timerCreated; timerD != 45*time.Second { 320 t.Fatalf("expected timer to be set for %v, got %v", 45*time.Second, timerD) 321 } 322 req2.timerTrigger <- time.Time{} 323 t.Log("request 2 should be reset after timer is triggered") 324 <-req2.logReset 325 326 t.Log("wait until all ongoing resets are done") 327 ls.wg.Wait() 328 }) 329 330 t.Run("updating loglevel with overlapping timeout behaves correctly", func(t *testing.T) { 331 ls := newLogsetSrv() 332 333 req1 := newMockedRequest(t, ls, "timeout", "10s") 334 <-req1.logApplied 335 if timerD := <-req1.timerCreated; timerD != time.Second*10 { 336 t.Fatalf("expected timer to be set for %v, got %v", time.Second*10, timerD) 337 } 338 339 req2 := newMockedRequest(t, ls, "timeout", "100s") 340 <-req2.logApplied 341 if timerD := <-req2.timerCreated; timerD != 100*time.Second { 342 t.Fatalf("expected timer to be set for %v, got %v", 100*time.Second, timerD) 343 } 344 345 t.Log("timer1 should be cancelled") 346 <-req1.timerCancelled 347 t.Log("timer should be triggered") 348 select { 349 case req1.timerTrigger <- time.Time{}: 350 t.Fatal("first request timer goroutine should not be running after cancellation") 351 default: 352 } 353 354 t.Log("triggering timer 2 should call the original reset function") 355 req2.timerTrigger <- time.Time{} 356 <-req1.logReset 357 358 t.Log("wait until all ongoing resets are done") 359 ls.wg.Wait() 360 select { 361 case <-req2.logReset: 362 t.Fatal("should not call second reset function") 363 default: 364 } 365 }) 366 367 t.Run("updating loglevel clears waiting reset timers", func(t *testing.T) { 368 ls := newLogsetSrv() 369 370 req1 := newMockedRequest(t, ls, "timeout", "11s") 371 <-req1.logApplied 372 if timerD := <-req1.timerCreated; timerD != time.Second*11 { 373 t.Fatalf("expected timer to be set for %v, got %v", time.Second*11, timerD) 374 } 375 376 req2 := newMockedRequest(t, ls) 377 <-req2.logApplied 378 379 t.Log("timer 1 should be cancelled") 380 <-req1.timerCancelled 381 382 req3 := newMockedRequest(t, ls, "timeout", "12s") 383 <-req3.logApplied 384 385 if timerD := <-req3.timerCreated; timerD != time.Second*12 { 386 t.Fatalf("expected timer to be set for %v, got %v", time.Second*12, timerD) 387 } 388 389 t.Log("triggering timer 3 should call the new reset function") 390 req3.timerTrigger <- time.Time{} 391 <-req3.logReset 392 393 t.Log("wait until all ongoing resets are done") 394 ls.wg.Wait() 395 396 select { 397 case req1.timerTrigger <- time.Time{}: 398 t.Fatal("first request timer goroutine should not be running after cancellation") 399 default: 400 } 401 402 t.Log("timer 2 should never be created") 403 select { 404 case <-req2.timerCreated: 405 t.Fatal("timer should never be created on request with no timeout set") 406 default: 407 } 408 409 select { 410 case req2.timerTrigger <- time.Time{}: 411 t.Fatal("second request timer should not be able to receive a time") 412 default: 413 } 414 415 t.Log("ensure we did not call either reset function") 416 select { 417 case <-req1.logReset: 418 t.Fatal("should not call first reset function") 419 case <-req2.logReset: 420 t.Fatal("should not call second reset function") 421 default: 422 } 423 }) 424 }