github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/logsink/logsink_test.go (about) 1 // Copyright 2015-2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package logsink_test 5 6 import ( 7 "errors" 8 "net/http" 9 "net/http/httptest" 10 "net/url" 11 "runtime/debug" 12 "sync" 13 "time" 14 15 "github.com/gorilla/websocket" 16 "github.com/juju/clock/testclock" 17 "github.com/juju/loggo" 18 "github.com/juju/testing" 19 jc "github.com/juju/testing/checkers" 20 "github.com/juju/utils/v3" 21 "go.uber.org/mock/gomock" 22 gc "gopkg.in/check.v1" 23 24 "github.com/juju/juju/apiserver/logsink" 25 "github.com/juju/juju/apiserver/logsink/mocks" 26 "github.com/juju/juju/apiserver/websocket/websockettest" 27 "github.com/juju/juju/rpc/params" 28 coretesting "github.com/juju/juju/testing" 29 ) 30 31 var shortAttempt = &utils.AttemptStrategy{ 32 Total: coretesting.ShortWait, 33 Delay: 10 * time.Millisecond, 34 } 35 36 var longAttempt = &utils.AttemptStrategy{ 37 Total: coretesting.LongWait, 38 Delay: 10 * time.Millisecond, 39 } 40 41 type logsinkSuite struct { 42 testing.IsolationSuite 43 44 abort chan struct{} 45 46 stub *testing.Stub 47 written chan params.LogRecord 48 49 lastStack []byte 50 stackMu sync.Mutex 51 } 52 53 var _ = gc.Suite(&logsinkSuite{}) 54 55 func (s *logsinkSuite) SetUpTest(c *gc.C) { 56 s.IsolationSuite.SetUpTest(c) 57 s.abort = make(chan struct{}) 58 s.written = make(chan params.LogRecord, 1) 59 s.stub = &testing.Stub{} 60 s.stackMu.Lock() 61 s.lastStack = nil 62 s.stackMu.Unlock() 63 } 64 65 func (s *logsinkSuite) dialWebsocket(c *gc.C, srv *httptest.Server) *websocket.Conn { 66 u, err := url.Parse(srv.URL) 67 c.Assert(err, jc.ErrorIsNil) 68 u.Scheme = "ws" 69 conn, _, err := websocket.DefaultDialer.Dial(u.String(), nil) 70 c.Assert(err, jc.ErrorIsNil) 71 s.AddCleanup(func(*gc.C) { conn.Close() }) 72 return conn 73 } 74 75 func (s *logsinkSuite) TestSuccess(c *gc.C) { 76 srv, finish := s.createServer(c) 77 defer finish() 78 79 conn := s.dialWebsocket(c, srv) 80 websockettest.AssertJSONInitialErrorNil(c, conn) 81 82 t0 := time.Date(2015, time.June, 1, 23, 2, 1, 0, time.UTC) 83 record := params.LogRecord{ 84 Time: t0, 85 Module: "some.where", 86 Location: "foo.go:42", 87 Level: loggo.INFO.String(), 88 Message: "all is well", 89 } 90 err := conn.WriteJSON(&record) 91 c.Assert(err, jc.ErrorIsNil) 92 93 select { 94 case written, ok := <-s.written: 95 c.Assert(ok, jc.IsTrue) 96 c.Assert(written, jc.DeepEquals, record) 97 case <-time.After(coretesting.LongWait): 98 c.Fatal("timed out waiting for log record to be written") 99 } 100 select { 101 case <-s.written: 102 c.Fatal("unexpected log record") 103 case <-time.After(coretesting.ShortWait): 104 } 105 s.stub.CheckCallNames(c, "Open", "WriteLog") 106 107 s.stackMu.Lock() 108 if s.lastStack != nil { 109 c.Logf("last Close call stack: \n%s", string(s.lastStack)) 110 } 111 s.stackMu.Unlock() 112 113 err = conn.Close() 114 c.Assert(err, jc.ErrorIsNil) 115 for a := longAttempt.Start(); a.Next(); { 116 if len(s.stub.Calls()) == 3 { 117 break 118 } 119 } 120 s.stub.CheckCallNames(c, "Open", "WriteLog", "Close") 121 } 122 123 func (s *logsinkSuite) TestLogMessages(c *gc.C) { 124 srv, finish := s.createServer(c) 125 defer finish() 126 127 var logs loggo.TestWriter 128 writer := loggo.NewMinimumLevelWriter(&logs, loggo.INFO) 129 c.Assert(loggo.RegisterWriter("logsink-tests", writer), jc.ErrorIsNil) 130 131 // Open, then close connection. 132 conn := s.dialWebsocket(c, srv) 133 websockettest.AssertJSONInitialErrorNil(c, conn) 134 err := conn.Close() 135 c.Assert(err, jc.ErrorIsNil) 136 137 // Ensure that no error is logged when the connection is closed normally. 138 for a := shortAttempt.Start(); a.Next(); { 139 for _, log := range logs.Log() { 140 c.Assert(log.Level, jc.LessThan, loggo.ERROR, gc.Commentf("log: %#v", log)) 141 } 142 } 143 } 144 145 func (s *logsinkSuite) TestSuccessWithLabels(c *gc.C) { 146 srv, finish := s.createServer(c) 147 defer finish() 148 149 var logs loggo.TestWriter 150 writer := loggo.NewMinimumLevelWriter(&logs, loggo.INFO) 151 c.Assert(loggo.RegisterWriter("logsink-tests", writer), jc.ErrorIsNil) 152 153 conn := s.dialWebsocket(c, srv) 154 websockettest.AssertJSONInitialErrorNil(c, conn) 155 156 t0 := time.Date(2015, time.June, 1, 23, 2, 1, 0, time.UTC) 157 records := []params.LogRecord{{ 158 Time: t0, 159 Module: "some.where", 160 Location: "foo.go:42", 161 Level: loggo.INFO.String(), 162 Message: "all is well", 163 }, { 164 Time: t0, 165 Module: "some.where.else", 166 Location: "bar.go:122", 167 Level: loggo.INFO.String(), 168 Message: "all is nice", 169 Entity: "entity.name", 170 Labels: []string{"bar"}, 171 }, { 172 Time: t0, 173 Module: "some.where", 174 Location: "foo.go:33", 175 Level: loggo.INFO.String(), 176 Message: "all is fine", 177 }} 178 for _, record := range records { 179 err := conn.WriteJSON(&record) 180 c.Assert(err, jc.ErrorIsNil) 181 182 select { 183 case written, ok := <-s.written: 184 c.Assert(ok, jc.IsTrue) 185 c.Assert(written, jc.DeepEquals, record) 186 case <-time.After(coretesting.LongWait): 187 c.Fatal("timed out waiting for log record to be written") 188 } 189 select { 190 case <-s.written: 191 c.Fatal("unexpected log record") 192 case <-time.After(coretesting.ShortWait): 193 } 194 195 } 196 197 s.stub.CheckCallNames(c, "Open", "WriteLog", "WriteLog", "WriteLog") 198 199 s.stackMu.Lock() 200 if s.lastStack != nil { 201 c.Logf("last Close call stack: \n%s", string(s.lastStack)) 202 } 203 s.stackMu.Unlock() 204 205 err := conn.Close() 206 c.Assert(err, jc.ErrorIsNil) 207 for a := longAttempt.Start(); a.Next(); { 208 if len(s.stub.Calls()) == 3 { 209 break 210 } 211 } 212 s.stub.CheckCallNames(c, "Open", "WriteLog", "WriteLog", "WriteLog", "Close") 213 } 214 215 func (s *logsinkSuite) TestLogOpenFails(c *gc.C) { 216 srv, finish := s.createServer(c) 217 defer finish() 218 219 s.stub.SetErrors(errors.New("rats")) 220 conn := s.dialWebsocket(c, srv) 221 websockettest.AssertJSONError(c, conn, "rats") 222 websockettest.AssertWebsocketClosed(c, conn) 223 } 224 225 func (s *logsinkSuite) TestLogWriteFails(c *gc.C) { 226 srv, finish := s.createServer(c) 227 defer finish() 228 229 s.stub.SetErrors(nil, errors.New("cannae write")) 230 conn := s.dialWebsocket(c, srv) 231 websockettest.AssertJSONInitialErrorNil(c, conn) 232 233 err := conn.WriteJSON(¶ms.LogRecord{}) 234 c.Assert(err, jc.ErrorIsNil) 235 236 websockettest.AssertJSONError(c, conn, "cannae write") 237 websockettest.AssertWebsocketClosed(c, conn) 238 } 239 240 func (s *logsinkSuite) TestReceiveErrorBreaksConn(c *gc.C) { 241 srv, finish := s.createServer(c) 242 defer finish() 243 244 conn := s.dialWebsocket(c, srv) 245 websockettest.AssertJSONInitialErrorNil(c, conn) 246 247 // The logsink handler expects JSON messages. Send some 248 // junk to verify that the server closes the connection. 249 err := conn.WriteMessage(websocket.TextMessage, []byte("junk!")) 250 c.Assert(err, jc.ErrorIsNil) 251 252 websockettest.AssertWebsocketClosed(c, conn) 253 } 254 255 func (s *logsinkSuite) TestRateLimit(c *gc.C) { 256 modelUUID, err := utils.NewUUID() 257 c.Assert(err, jc.ErrorIsNil) 258 259 metricsCollector, finish := createMockMetrics(c, modelUUID.String()) 260 defer finish() 261 262 testClock := testclock.NewClock(time.Time{}) 263 srv := httptest.NewServer(logsink.NewHTTPHandler( 264 func(req *http.Request) (logsink.LogWriteCloser, error) { 265 s.stub.AddCall("Open") 266 return &mockLogWriteCloser{ 267 s.stub, 268 s.written, 269 nil, 270 }, s.stub.NextErr() 271 }, 272 s.abort, 273 &logsink.RateLimitConfig{ 274 Burst: 2, 275 Refill: time.Second, 276 Clock: testClock, 277 }, 278 metricsCollector, 279 modelUUID.String(), 280 )) 281 defer srv.Close() 282 283 conn := s.dialWebsocket(c, srv) 284 websockettest.AssertJSONInitialErrorNil(c, conn) 285 286 record := params.LogRecord{ 287 Time: time.Date(2015, time.June, 1, 23, 2, 1, 0, time.UTC), 288 Module: "some.where", 289 Location: "foo.go:42", 290 Level: loggo.INFO.String(), 291 Message: "all is well", 292 } 293 for i := 0; i < 4; i++ { 294 err := conn.WriteJSON(&record) 295 c.Assert(err, jc.ErrorIsNil) 296 } 297 298 expectRecord := func() { 299 select { 300 case written, ok := <-s.written: 301 c.Assert(ok, jc.IsTrue) 302 c.Assert(written, jc.DeepEquals, record) 303 case <-time.After(coretesting.LongWait): 304 c.Fatal("timed out waiting for log record to be written") 305 } 306 } 307 expectNoRecord := func() { 308 select { 309 case <-s.written: 310 c.Fatal("unexpected log record") 311 case <-time.After(coretesting.ShortWait): 312 } 313 } 314 315 // There should be 2 records received immediately, 316 // and then rate-limiting should kick in. 317 expectRecord() 318 expectRecord() 319 expectNoRecord() 320 testClock.WaitAdvance(time.Second, coretesting.LongWait, 1) 321 expectRecord() 322 expectNoRecord() 323 testClock.WaitAdvance(time.Second, coretesting.LongWait, 1) 324 expectRecord() 325 expectNoRecord() 326 } 327 328 func (s *logsinkSuite) TestReceiverStopsWhenAsked(c *gc.C) { 329 myStopCh := make(chan struct{}) 330 331 modelUUID, err := utils.NewUUID() 332 c.Assert(err, jc.ErrorIsNil) 333 334 metricsCollector, finish := createMockMetrics(c, modelUUID.String()) 335 defer finish() 336 337 handler := logsink.NewHTTPHandlerForTest( 338 func(req *http.Request) (logsink.LogWriteCloser, error) { 339 s.stub.AddCall("Open") 340 return &slowWriteCloser{}, s.stub.NextErr() 341 }, 342 s.abort, 343 nil, 344 metricsCollector, 345 modelUUID.String(), 346 func() (chan struct{}, func()) { 347 return myStopCh, func() {} 348 }, 349 ) 350 srv := httptest.NewServer(handler) 351 defer srv.Close() 352 353 conn := s.dialWebsocket(c, srv) 354 websockettest.AssertJSONInitialErrorNil(c, conn) 355 356 close(myStopCh) 357 358 // Send 2 log messages so we're sure the receiver gets a chance to 359 // go down the stop channel leg, since the writes are slow. 360 t0 := time.Date(2015, time.June, 1, 23, 2, 1, 0, time.UTC) 361 record := params.LogRecord{ 362 Time: t0, 363 Module: "some.where", 364 Location: "foo.go:42", 365 Level: loggo.INFO.String(), 366 Message: "all is well", 367 } 368 err = conn.WriteJSON(&record) 369 c.Assert(err, jc.ErrorIsNil) 370 // The second write might error (if the receiver stopped after the 371 // first message). 372 _ = conn.WriteJSON(&record) 373 374 for a := longAttempt.Start(); a.Next(); { 375 if logsink.ReceiverStopped(c, handler) { 376 break 377 } 378 } 379 c.Assert(logsink.ReceiverStopped(c, handler), gc.Equals, true) 380 381 err = conn.Close() 382 c.Assert(err, jc.ErrorIsNil) 383 } 384 385 func (s *logsinkSuite) TestHandlerClosesStopChannel(c *gc.C) { 386 modelUUID, err := utils.NewUUID() 387 c.Assert(err, jc.ErrorIsNil) 388 389 metricsCollector, finish := createMockMetrics(c, modelUUID.String()) 390 defer finish() 391 392 var stub testing.Stub 393 handler := logsink.NewHTTPHandlerForTest( 394 func(req *http.Request) (logsink.LogWriteCloser, error) { 395 return &mockLogWriteCloser{ 396 s.stub, 397 s.written, 398 nil, 399 }, s.stub.NextErr() 400 }, 401 s.abort, 402 nil, 403 metricsCollector, 404 modelUUID.String(), 405 func() (chan struct{}, func()) { 406 ch := make(chan struct{}) 407 return ch, func() { 408 stub.AddCall("close stop channel") 409 close(ch) 410 } 411 }, 412 ) 413 srv := httptest.NewServer(handler) 414 defer srv.Close() 415 conn := s.dialWebsocket(c, srv) 416 websockettest.AssertJSONInitialErrorNil(c, conn) 417 418 t0 := time.Date(2015, time.June, 1, 23, 2, 1, 0, time.UTC) 419 record := params.LogRecord{ 420 Time: t0, 421 Module: "some.where", 422 Location: "foo.go:42", 423 Level: loggo.INFO.String(), 424 Message: "all is well", 425 } 426 err = conn.WriteJSON(&record) 427 c.Assert(err, jc.ErrorIsNil) 428 429 select { 430 case written, ok := <-s.written: 431 c.Assert(ok, jc.IsTrue) 432 c.Assert(written, jc.DeepEquals, record) 433 case <-time.After(coretesting.LongWait): 434 c.Fatal("timed out waiting for log record to be written") 435 } 436 437 err = conn.Close() 438 c.Assert(err, jc.ErrorIsNil) 439 for a := longAttempt.Start(); a.Next(); { 440 if len(stub.Calls()) == 1 { 441 break 442 } 443 } 444 stub.CheckCallNames(c, "close stop channel") 445 } 446 447 func (s *logsinkSuite) createServer(c *gc.C) (*httptest.Server, func()) { 448 recordStack := func() { 449 s.stackMu.Lock() 450 defer s.stackMu.Unlock() 451 s.lastStack = debug.Stack() 452 } 453 454 modelUUID, err := utils.NewUUID() 455 c.Assert(err, jc.ErrorIsNil) 456 457 metricsCollector, finish := createMockMetrics(c, modelUUID.String()) 458 459 srv := httptest.NewServer(logsink.NewHTTPHandler( 460 func(req *http.Request) (logsink.LogWriteCloser, error) { 461 s.stub.AddCall("Open") 462 return &mockLogWriteCloser{ 463 s.stub, 464 s.written, 465 recordStack, 466 }, s.stub.NextErr() 467 }, 468 s.abort, 469 nil, // no rate-limiting 470 metricsCollector, 471 modelUUID.String(), 472 )) 473 return srv, func() { 474 finish() 475 srv.Close() 476 } 477 } 478 479 type mockLogWriteCloser struct { 480 *testing.Stub 481 written chan<- params.LogRecord 482 callback func() 483 } 484 485 func (m *mockLogWriteCloser) Close() error { 486 m.MethodCall(m, "Close") 487 if m.callback != nil { 488 m.callback() 489 } 490 return m.NextErr() 491 } 492 493 func (m *mockLogWriteCloser) WriteLog(r params.LogRecord) error { 494 m.MethodCall(m, "WriteLog", r) 495 m.written <- r 496 return m.NextErr() 497 } 498 499 type slowWriteCloser struct{} 500 501 func (slowWriteCloser) Close() error { 502 return nil 503 } 504 505 func (slowWriteCloser) WriteLog(params.LogRecord) error { 506 // This makes it more likely that the goroutine will notice the 507 // stop channel is closed, because logCh won't be ready for 508 // sending. 509 time.Sleep(testing.ShortWait) 510 return nil 511 } 512 513 func createMockMetrics(c *gc.C, modelUUID string) (*mocks.MockMetricsCollector, func()) { 514 ctrl := gomock.NewController(c) 515 516 counter := mocks.NewMockCounter(ctrl) 517 counter.EXPECT().Inc().AnyTimes() 518 519 gauge := mocks.NewMockGauge(ctrl) 520 gauge.EXPECT().Inc().AnyTimes() 521 gauge.EXPECT().Dec().AnyTimes() 522 523 metricsCollector := mocks.NewMockMetricsCollector(ctrl) 524 metricsCollector.EXPECT().TotalConnections().Return(counter).AnyTimes() 525 metricsCollector.EXPECT().Connections().Return(gauge).AnyTimes() 526 metricsCollector.EXPECT().LogWriteCount(modelUUID, gomock.Any()).Return(counter).AnyTimes() 527 metricsCollector.EXPECT().LogReadCount(modelUUID, gomock.Any()).Return(counter).AnyTimes() 528 529 return metricsCollector, ctrl.Finish 530 }