github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/logs_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state_test 5 6 import ( 7 "math/rand" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/juju/loggo" 13 "github.com/juju/mgo/v3" 14 "github.com/juju/mgo/v3/bson" 15 jc "github.com/juju/testing/checkers" 16 "github.com/juju/version/v2" 17 gc "gopkg.in/check.v1" 18 19 corelogger "github.com/juju/juju/core/logger" 20 "github.com/juju/juju/state" 21 coretesting "github.com/juju/juju/testing" 22 jujuversion "github.com/juju/juju/version" 23 ) 24 25 type LogCollectionSuite struct { 26 ConnSuite 27 } 28 29 var _ = gc.Suite(&LogCollectionSuite{}) 30 31 func (s *LogCollectionSuite) TestCreateCollection(c *gc.C) { 32 session := s.State.MongoSession() 33 modelUUID := "00000000-0000-0000-0000-000000000001" 34 35 coll := session.DB("logs").C("logs." + modelUUID) 36 37 // Loop to test idempotency. 38 for i := 0; i < 2; i++ { 39 err := state.InitDbLogsForModel(s.Session, modelUUID, 1) 40 c.Assert(err, jc.ErrorIsNil) 41 capped, size, err := state.GetCollectionCappedInfo(coll) 42 c.Assert(err, jc.ErrorIsNil) 43 c.Assert(capped, jc.IsTrue) 44 c.Assert(size, gc.Equals, 1) 45 } 46 } 47 48 func (s *LogCollectionSuite) TestUpgradeCollection(c *gc.C) { 49 session := s.State.MongoSession() 50 modelUUID := "00000000-0000-0000-0000-000000000002" 51 52 coll := session.DB("logs").C("logs." + modelUUID) 53 // Create a non-capped collection. 54 err := coll.Create(&mgo.CollectionInfo{}) 55 c.Assert(err, jc.ErrorIsNil) 56 capped, size, err := state.GetCollectionCappedInfo(coll) 57 c.Assert(err, jc.ErrorIsNil) 58 c.Assert(capped, jc.IsFalse) 59 c.Assert(size, gc.Equals, 0) 60 61 // Ensure collection is "upgraded" to a capped collection. 62 err = state.InitDbLogsForModel(s.Session, modelUUID, 1) 63 c.Assert(err, jc.ErrorIsNil) 64 capped, size, err = state.GetCollectionCappedInfo(coll) 65 c.Assert(err, jc.ErrorIsNil) 66 c.Assert(capped, jc.IsTrue) 67 c.Assert(size, gc.Equals, 1) 68 } 69 70 func (s *LogCollectionSuite) TestResizeCollection(c *gc.C) { 71 session := s.State.MongoSession() 72 modelUUID := "00000000-0000-0000-0000-000000000003" 73 74 coll := session.DB("logs").C("logs." + modelUUID) 75 // Create a small collection. 76 err := state.InitDbLogsForModel(s.Session, modelUUID, 2) 77 c.Assert(err, jc.ErrorIsNil) 78 capped, size, err := state.GetCollectionCappedInfo(coll) 79 c.Assert(err, jc.ErrorIsNil) 80 c.Assert(capped, jc.IsTrue) 81 c.Assert(size, gc.Equals, 2) 82 83 // Make it bigger. 84 err = state.InitDbLogsForModel(s.Session, modelUUID, 3) 85 c.Assert(err, jc.ErrorIsNil) 86 capped, size, err = state.GetCollectionCappedInfo(coll) 87 c.Assert(err, jc.ErrorIsNil) 88 c.Assert(capped, jc.IsTrue) 89 c.Assert(size, gc.Equals, 3) 90 91 // Make it even smaller. 92 err = state.InitDbLogsForModel(s.Session, modelUUID, 1) 93 c.Assert(err, jc.ErrorIsNil) 94 capped, size, err = state.GetCollectionCappedInfo(coll) 95 c.Assert(err, jc.ErrorIsNil) 96 c.Assert(capped, jc.IsTrue) 97 c.Assert(size, gc.Equals, 1) 98 } 99 100 type LogsSuite struct { 101 ConnSuite 102 logsColl *mgo.Collection 103 104 logger loggo.Logger 105 } 106 107 var _ = gc.Suite(&LogsSuite{}) 108 109 func (s *LogsSuite) SetUpTest(c *gc.C) { 110 s.ConnSuite.SetUpTest(c) 111 s.logsColl = s.logCollFor(s.State) 112 s.logger = loggo.GetLogger("test") 113 } 114 115 func (s *LogsSuite) logCollFor(st *state.State) *mgo.Collection { 116 session := st.MongoSession() 117 return session.DB("logs").C("logs." + st.ModelUUID()) 118 } 119 120 func (s *LogsSuite) TestLastSentLogTrackerSetGet(c *gc.C) { 121 tracker := state.NewLastSentLogTracker(s.State, s.State.ModelUUID(), "test-sink") 122 defer tracker.Close() 123 124 err := tracker.Set(10, 100) 125 c.Assert(err, jc.ErrorIsNil) 126 id1, ts1, err := tracker.Get() 127 c.Assert(err, jc.ErrorIsNil) 128 err = tracker.Set(20, 200) 129 c.Assert(err, jc.ErrorIsNil) 130 id2, ts2, err := tracker.Get() 131 c.Assert(err, jc.ErrorIsNil) 132 133 c.Check(id1, gc.Equals, int64(10)) 134 c.Check(ts1, gc.Equals, int64(100)) 135 c.Check(id2, gc.Equals, int64(20)) 136 c.Check(ts2, gc.Equals, int64(200)) 137 } 138 139 func (s *LogsSuite) TestLastSentLogTrackerGetNeverSet(c *gc.C) { 140 tracker := state.NewLastSentLogTracker(s.State, s.State.ModelUUID(), "test") 141 defer tracker.Close() 142 143 _, _, err := tracker.Get() 144 145 c.Check(err, gc.ErrorMatches, state.ErrNeverForwarded.Error()) 146 } 147 148 func (s *LogsSuite) TestLastSentLogTrackerIndependentModels(c *gc.C) { 149 tracker0 := state.NewLastSentLogTracker(s.State, s.State.ModelUUID(), "test-sink") 150 defer tracker0.Close() 151 otherModel := s.NewStateForModelNamed(c, "test-model") 152 defer otherModel.Close() 153 tracker1 := state.NewLastSentLogTracker(otherModel, otherModel.ModelUUID(), "test-sink") // same sink 154 defer tracker1.Close() 155 err := tracker0.Set(10, 100) 156 c.Assert(err, jc.ErrorIsNil) 157 id0, ts0, err := tracker0.Get() 158 c.Assert(err, jc.ErrorIsNil) 159 c.Assert(id0, gc.Equals, int64(10)) 160 c.Assert(ts0, gc.Equals, int64(100)) 161 162 _, _, errBefore := tracker1.Get() 163 err = tracker1.Set(20, 200) 164 c.Assert(err, jc.ErrorIsNil) 165 id1, ts1, errAfter := tracker1.Get() 166 c.Assert(errAfter, jc.ErrorIsNil) 167 id0, ts0, err = tracker0.Get() 168 c.Assert(err, jc.ErrorIsNil) 169 170 c.Check(errBefore, gc.ErrorMatches, state.ErrNeverForwarded.Error()) 171 c.Check(id1, gc.Equals, int64(20)) 172 c.Check(ts1, gc.Equals, int64(200)) 173 c.Check(id0, gc.Equals, int64(10)) 174 c.Check(ts0, gc.Equals, int64(100)) 175 } 176 177 func (s *LogsSuite) TestLastSentLogTrackerIndependentSinks(c *gc.C) { 178 tracker0 := state.NewLastSentLogTracker(s.State, s.State.ModelUUID(), "test-sink0") 179 defer tracker0.Close() 180 tracker1 := state.NewLastSentLogTracker(s.State, s.State.ModelUUID(), "test-sink1") 181 defer tracker1.Close() 182 err := tracker0.Set(10, 100) 183 c.Assert(err, jc.ErrorIsNil) 184 id0, ts0, err := tracker0.Get() 185 c.Assert(err, jc.ErrorIsNil) 186 c.Assert(id0, gc.Equals, int64(10)) 187 c.Assert(ts0, gc.Equals, int64(100)) 188 189 _, _, errBefore := tracker1.Get() 190 err = tracker1.Set(20, 200) 191 c.Assert(err, jc.ErrorIsNil) 192 id1, ts1, errAfter := tracker1.Get() 193 c.Assert(errAfter, jc.ErrorIsNil) 194 id0, ts0, err = tracker0.Get() 195 c.Assert(err, jc.ErrorIsNil) 196 197 c.Check(errBefore, gc.ErrorMatches, state.ErrNeverForwarded.Error()) 198 c.Check(id1, gc.Equals, int64(20)) 199 c.Check(ts1, gc.Equals, int64(200)) 200 c.Check(id0, gc.Equals, int64(10)) 201 c.Check(ts0, gc.Equals, int64(100)) 202 } 203 204 func (s *LogsSuite) TestIndexesCreated(c *gc.C) { 205 // Indexes should be created on the logs collection when state is opened. 206 indexes, err := s.logsColl.Indexes() 207 c.Assert(err, jc.ErrorIsNil) 208 var keys []string 209 for _, index := range indexes { 210 keys = append(keys, strings.Join(index.Key, "-")) 211 } 212 c.Assert(keys, jc.SameContents, []string{ 213 "_id", // default index 214 "t-_id", // timestamp and ID 215 "n", // entity 216 "c", // optional labels 217 }) 218 } 219 220 func (s *LogsSuite) TestDbLogger(c *gc.C) { 221 logger := state.NewDbLogger(s.State) 222 defer logger.Close() 223 224 t0 := coretesting.ZeroTime().Truncate(time.Millisecond) // MongoDB only stores timestamps with ms precision. 225 t1 := t0.Add(time.Second) 226 err := logger.Log([]corelogger.LogRecord{{ 227 Time: t0, 228 Entity: "machine-45", 229 Module: "some.where", 230 Location: "foo.go:99", 231 Level: loggo.INFO, 232 Message: "all is well", 233 }, { 234 Time: t1, 235 Entity: "machine-47", 236 Module: "else.where", 237 Location: "bar.go:42", 238 Level: loggo.ERROR, 239 Message: "oh noes", 240 }}) 241 c.Assert(err, jc.ErrorIsNil) 242 243 var docs []bson.M 244 err = s.logsColl.Find(nil).Sort("t").All(&docs) 245 c.Assert(err, jc.ErrorIsNil) 246 c.Assert(docs, gc.HasLen, 2) 247 248 c.Assert(docs[0]["t"], gc.Equals, t0.UnixNano()) 249 c.Assert(docs[0]["n"], gc.Equals, "machine-45") 250 c.Assert(docs[0]["m"], gc.Equals, "some.where") 251 c.Assert(docs[0]["l"], gc.Equals, "foo.go:99") 252 c.Assert(docs[0]["v"], gc.Equals, int(loggo.INFO)) 253 c.Assert(docs[0]["x"], gc.Equals, "all is well") 254 255 c.Assert(docs[1]["t"], gc.Equals, t1.UnixNano()) 256 c.Assert(docs[1]["n"], gc.Equals, "machine-47") 257 c.Assert(docs[1]["m"], gc.Equals, "else.where") 258 c.Assert(docs[1]["l"], gc.Equals, "bar.go:42") 259 c.Assert(docs[1]["v"], gc.Equals, int(loggo.ERROR)) 260 c.Assert(docs[1]["x"], gc.Equals, "oh noes") 261 } 262 263 type LogTailerSuite struct { 264 ConnWithWallClockSuite 265 oplogColl *mgo.Collection 266 otherState *state.State 267 modelUUID, otherUUID string 268 } 269 270 var _ = gc.Suite(&LogTailerSuite{}) 271 272 func (s *LogTailerSuite) SetUpTest(c *gc.C) { 273 s.ConnWithWallClockSuite.SetUpTest(c) 274 275 session := s.State.MongoSession() 276 // Create a fake oplog collection. 277 s.oplogColl = session.DB("logs").C("oplog.fake") 278 err := s.oplogColl.Create(&mgo.CollectionInfo{ 279 Capped: true, 280 MaxBytes: 1024 * 1024, 281 }) 282 c.Assert(err, jc.ErrorIsNil) 283 s.AddCleanup(func(*gc.C) { s.oplogColl.DropCollection() }) 284 285 s.otherState = s.NewStateForModelNamed(c, "test-model") 286 c.Assert(s.otherState, gc.NotNil) 287 s.AddCleanup(func(c *gc.C) { 288 err := s.otherState.Close() 289 c.Assert(err, jc.ErrorIsNil) 290 }) 291 s.modelUUID = s.State.ModelUUID() 292 s.otherUUID = s.otherState.ModelUUID() 293 } 294 295 func (s *LogTailerSuite) getCollection(modelUUID string) *mgo.Collection { 296 return s.State.MongoSession().DB("logs").C("logs." + modelUUID) 297 } 298 299 func (s *LogTailerSuite) TestLogDeletionDuringTailing(c *gc.C) { 300 var tw loggo.TestWriter 301 err := loggo.RegisterWriter("test", &tw) 302 c.Assert(err, jc.ErrorIsNil) 303 defer loggo.RemoveWriter("test") 304 305 tailer, err := state.NewLogTailer(s.otherState, corelogger.LogTailerParams{}, s.oplogColl) 306 c.Assert(err, jc.ErrorIsNil) 307 defer tailer.Stop() 308 309 want := logTemplate{Message: "want"} 310 311 s.writeLogs(c, s.otherUUID, 2, want) 312 // Delete something. 313 s.deleteLogOplogEntry(s.otherUUID, want) 314 s.writeLogs(c, s.otherUUID, 2, want) 315 316 s.assertTailer(c, tailer, 4, want) 317 318 c.Assert(tw.Log(), gc.Not(jc.LogMatches), jc.SimpleMessages{{ 319 loggo.WARNING, 320 `.*log deserialization failed.*`, 321 }}) 322 } 323 324 func (s *LogTailerSuite) TestTimeFiltering(c *gc.C) { 325 // Add 10 logs that shouldn't be returned. 326 threshT := coretesting.NonZeroTime() 327 s.writeLogsT(c, 328 s.otherUUID, 329 threshT.Add(-5*time.Second), threshT.Add(-time.Millisecond), 5, 330 logTemplate{Message: "dont want"}, 331 ) 332 333 // Add 5 logs that should be returned. 334 want := logTemplate{Message: "want"} 335 s.writeLogsT(c, s.otherUUID, threshT, threshT.Add(5*time.Second), 5, want) 336 tailer, err := state.NewLogTailer(s.otherState, corelogger.LogTailerParams{StartTime: threshT}, s.oplogColl) 337 c.Assert(err, jc.ErrorIsNil) 338 defer tailer.Stop() 339 s.assertTailer(c, tailer, 5, want) 340 341 // Write more logs. These will be read from the the oplog. 342 want2 := logTemplate{Message: "want 2"} 343 s.writeLogsT(c, s.otherUUID, threshT.Add(6*time.Second), threshT.Add(10*time.Second), 5, want2) 344 s.assertTailer(c, tailer, 5, want2) 345 346 } 347 348 func (s *LogTailerSuite) TestOplogTransition(c *gc.C) { 349 // Ensure that logs aren't repeated as the log tailer moves from 350 // reading from the logs collection to tailing the oplog. 351 // 352 // All logs are written out with the same timestamp to create a 353 // challenging scenario for the tailer. 354 355 for i := 0; i < 5; i++ { 356 s.writeLogs(c, s.otherUUID, 1, logTemplate{Message: strconv.Itoa(i)}) 357 } 358 359 tailer, err := state.NewLogTailer(s.otherState, corelogger.LogTailerParams{}, s.oplogColl) 360 c.Assert(err, jc.ErrorIsNil) 361 defer tailer.Stop() 362 for i := 0; i < 5; i++ { 363 s.assertTailer(c, tailer, 1, logTemplate{Message: strconv.Itoa(i)}) 364 } 365 366 // Write more logs. These will be read from the the oplog. 367 for i := 5; i < 10; i++ { 368 lt := logTemplate{Message: strconv.Itoa(i)} 369 s.writeLogs(c, s.otherUUID, 2, lt) 370 s.assertTailer(c, tailer, 2, lt) 371 } 372 } 373 374 func (s *LogTailerSuite) TestModelFiltering(c *gc.C) { 375 good := logTemplate{Message: "good"} 376 writeLogs := func() { 377 s.writeLogs(c, "someuuid0", 1, logTemplate{ 378 Message: "bad", 379 }) 380 s.writeLogs(c, "someuuid1", 1, logTemplate{ 381 Message: "bad", 382 }) 383 s.writeLogs(c, s.otherUUID, 1, good) 384 } 385 386 assert := func(tailer corelogger.LogTailer) { 387 // Only the entries the s.State's UUID should be reported. 388 s.assertTailer(c, tailer, 1, good) 389 } 390 391 s.checkLogTailerFiltering(c, s.otherState, corelogger.LogTailerParams{}, writeLogs, assert) 392 } 393 394 func (s *LogTailerSuite) TestTailingLogsOnlyForOneModel(c *gc.C) { 395 writeLogs := func() { 396 s.writeLogs(c, s.otherUUID, 1, logTemplate{ 397 Message: "bad"}, 398 ) 399 s.writeLogs(c, s.modelUUID, 1, logTemplate{ 400 Message: "good1", 401 }) 402 s.writeLogs(c, s.modelUUID, 1, logTemplate{ 403 Message: "good2", 404 }) 405 } 406 407 assert := func(tailer corelogger.LogTailer) { 408 messages := map[string]bool{} 409 defer func() { 410 c.Assert(messages, gc.HasLen, 2) 411 for m := range messages { 412 if m != "good1" && m != "good2" { 413 c.Fatalf("received message: %v", m) 414 } 415 } 416 }() 417 count := 0 418 for { 419 select { 420 case log := <-tailer.Logs(): 421 c.Assert(log.ModelUUID, gc.Equals, s.State.ModelUUID()) 422 messages[log.Message] = true 423 count++ 424 c.Logf("count %d", count) 425 if count >= 2 { 426 return 427 } 428 case <-time.After(coretesting.ShortWait): 429 c.Fatalf("timeout waiting for logs %d", count) 430 } 431 } 432 } 433 s.checkLogTailerFiltering(c, s.State, corelogger.LogTailerParams{}, writeLogs, assert) 434 } 435 436 func (s *LogTailerSuite) TestLevelFiltering(c *gc.C) { 437 info := logTemplate{Level: loggo.INFO} 438 error := logTemplate{Level: loggo.ERROR} 439 writeLogs := func() { 440 s.writeLogs(c, s.otherUUID, 1, logTemplate{Level: loggo.DEBUG}) 441 s.writeLogs(c, s.otherUUID, 1, info) 442 s.writeLogs(c, s.otherUUID, 1, error) 443 } 444 params := corelogger.LogTailerParams{ 445 MinLevel: loggo.INFO, 446 } 447 assert := func(tailer corelogger.LogTailer) { 448 s.assertTailer(c, tailer, 1, info) 449 s.assertTailer(c, tailer, 1, error) 450 } 451 s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert) 452 } 453 454 func (s *LogTailerSuite) TestInitialLines(c *gc.C) { 455 expected := logTemplate{Message: "want"} 456 s.writeLogs(c, s.otherUUID, 3, logTemplate{Message: "dont want"}) 457 s.writeLogs(c, s.otherUUID, 5, expected) 458 459 tailer, err := state.NewLogTailer(s.otherState, corelogger.LogTailerParams{InitialLines: 5}, nil) 460 c.Assert(err, jc.ErrorIsNil) 461 defer tailer.Stop() 462 463 // Should see just the last 5 lines as requested. 464 s.assertTailer(c, tailer, 5, expected) 465 } 466 467 func (s *LogTailerSuite) TestRecordsAddedOutOfTimeOrder(c *gc.C) { 468 format := "2006-01-02 03:04" 469 t1, err := time.Parse(format, "2016-11-25 09:10") 470 c.Assert(err, jc.ErrorIsNil) 471 t2, err := time.Parse(format, "2016-11-25 09:20") 472 c.Assert(err, jc.ErrorIsNil) 473 here := logTemplate{Message: "logged here"} 474 s.writeLogsT(c, s.otherUUID, t2, t2, 1, here) 475 migrated := logTemplate{Message: "transferred by migration"} 476 s.writeLogsT(c, s.otherUUID, t1, t1, 1, migrated) 477 478 tailer, err := state.NewLogTailer(s.otherState, corelogger.LogTailerParams{}, nil) 479 c.Assert(err, jc.ErrorIsNil) 480 defer tailer.Stop() 481 482 // They still come back in the right time order. 483 s.assertTailer(c, tailer, 1, migrated) 484 s.assertTailer(c, tailer, 1, here) 485 } 486 487 func (s *LogTailerSuite) TestInitialLinesWithNotEnoughLines(c *gc.C) { 488 expected := logTemplate{Message: "want"} 489 s.writeLogs(c, s.otherUUID, 2, expected) 490 491 tailer, err := state.NewLogTailer(s.otherState, corelogger.LogTailerParams{InitialLines: 5}, nil) 492 c.Assert(err, jc.ErrorIsNil) 493 defer tailer.Stop() 494 495 // Should see just the 2 lines that existed, even though 5 were 496 // asked for. 497 s.assertTailer(c, tailer, 2, expected) 498 } 499 500 func (s *LogTailerSuite) TestNoTail(c *gc.C) { 501 expected := logTemplate{Message: "want"} 502 s.writeLogs(c, s.otherUUID, 2, expected) 503 504 // Write a log entry that's only in the oplog. 505 doc := s.logTemplateToDoc(logTemplate{Message: "dont want"}, coretesting.ZeroTime()) 506 err := s.writeLogToOplog(s.otherUUID, doc) 507 c.Assert(err, jc.ErrorIsNil) 508 509 tailer, err := state.NewLogTailer(s.otherState, corelogger.LogTailerParams{NoTail: true}, nil) 510 c.Assert(err, jc.ErrorIsNil) 511 // Not strictly necessary, just in case NoTail doesn't work in the test. 512 defer tailer.Stop() 513 514 // Logs only in the oplog shouldn't be reported and the tailer 515 // should stop itself once the log collection has been read. 516 s.assertTailer(c, tailer, 2, expected) 517 select { 518 case _, ok := <-tailer.Logs(): 519 if ok { 520 c.Fatal("shouldn't be any further logs") 521 } 522 case <-time.After(coretesting.LongWait): 523 c.Fatal("timed out waiting for logs channel to close") 524 } 525 526 select { 527 case <-tailer.Dying(): 528 // Success. 529 case <-time.After(coretesting.LongWait): 530 c.Fatal("tailer didn't stop itself") 531 } 532 } 533 534 func (s *LogTailerSuite) TestIncludeEntity(c *gc.C) { 535 machine0 := logTemplate{Entity: "machine-0"} 536 foo0 := logTemplate{Entity: "unit-foo-0"} 537 foo1 := logTemplate{Entity: "unit-foo-1"} 538 writeLogs := func() { 539 s.writeLogs(c, s.otherUUID, 3, machine0) 540 s.writeLogs(c, s.otherUUID, 2, foo0) 541 s.writeLogs(c, s.otherUUID, 1, foo1) 542 s.writeLogs(c, s.otherUUID, 3, machine0) 543 } 544 params := corelogger.LogTailerParams{ 545 IncludeEntity: []string{ 546 "unit-foo-0", 547 "unit-foo-1", 548 }, 549 } 550 assert := func(tailer corelogger.LogTailer) { 551 s.assertTailer(c, tailer, 2, foo0) 552 s.assertTailer(c, tailer, 1, foo1) 553 } 554 s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert) 555 } 556 557 func (s *LogTailerSuite) TestIncludeEntityWildcard(c *gc.C) { 558 machine0 := logTemplate{Entity: "machine-0"} 559 foo0 := logTemplate{Entity: "unit-foo-0"} 560 foo1 := logTemplate{Entity: "unit-foo-1"} 561 writeLogs := func() { 562 s.writeLogs(c, s.otherUUID, 3, machine0) 563 s.writeLogs(c, s.otherUUID, 2, foo0) 564 s.writeLogs(c, s.otherUUID, 1, foo1) 565 s.writeLogs(c, s.otherUUID, 3, machine0) 566 } 567 params := corelogger.LogTailerParams{ 568 IncludeEntity: []string{ 569 "unit-foo*", 570 }, 571 } 572 assert := func(tailer corelogger.LogTailer) { 573 s.assertTailer(c, tailer, 2, foo0) 574 s.assertTailer(c, tailer, 1, foo1) 575 } 576 s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert) 577 } 578 579 func (s *LogTailerSuite) TestExcludeEntity(c *gc.C) { 580 machine0 := logTemplate{Entity: "machine-0"} 581 foo0 := logTemplate{Entity: "unit-foo-0"} 582 foo1 := logTemplate{Entity: "unit-foo-1"} 583 writeLogs := func() { 584 s.writeLogs(c, s.otherUUID, 3, machine0) 585 s.writeLogs(c, s.otherUUID, 2, foo0) 586 s.writeLogs(c, s.otherUUID, 1, foo1) 587 s.writeLogs(c, s.otherUUID, 3, machine0) 588 } 589 params := corelogger.LogTailerParams{ 590 ExcludeEntity: []string{ 591 "machine-0", 592 "unit-foo-0", 593 }, 594 } 595 assert := func(tailer corelogger.LogTailer) { 596 s.assertTailer(c, tailer, 1, foo1) 597 } 598 s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert) 599 } 600 601 func (s *LogTailerSuite) TestExcludeEntityWildcard(c *gc.C) { 602 machine0 := logTemplate{Entity: "machine-0"} 603 foo0 := logTemplate{Entity: "unit-foo-0"} 604 foo1 := logTemplate{Entity: "unit-foo-1"} 605 writeLogs := func() { 606 s.writeLogs(c, s.otherUUID, 3, machine0) 607 s.writeLogs(c, s.otherUUID, 2, foo0) 608 s.writeLogs(c, s.otherUUID, 1, foo1) 609 s.writeLogs(c, s.otherUUID, 3, machine0) 610 } 611 params := corelogger.LogTailerParams{ 612 ExcludeEntity: []string{ 613 "machine*", 614 "unit-*-0", 615 }, 616 } 617 assert := func(tailer corelogger.LogTailer) { 618 s.assertTailer(c, tailer, 1, foo1) 619 } 620 s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert) 621 } 622 623 func (s *LogTailerSuite) TestIncludeModule(c *gc.C) { 624 mod0 := logTemplate{Module: "foo.bar"} 625 mod1 := logTemplate{Module: "juju.thing"} 626 subMod1 := logTemplate{Module: "juju.thing.hai"} 627 mod2 := logTemplate{Module: "elsewhere"} 628 writeLogs := func() { 629 s.writeLogs(c, s.otherUUID, 1, mod0) 630 s.writeLogs(c, s.otherUUID, 1, mod1) 631 s.writeLogs(c, s.otherUUID, 1, mod0) 632 s.writeLogs(c, s.otherUUID, 1, subMod1) 633 s.writeLogs(c, s.otherUUID, 1, mod0) 634 s.writeLogs(c, s.otherUUID, 1, mod2) 635 } 636 params := corelogger.LogTailerParams{ 637 IncludeModule: []string{"juju.thing", "elsewhere"}, 638 } 639 assert := func(tailer corelogger.LogTailer) { 640 s.assertTailer(c, tailer, 1, mod1) 641 s.assertTailer(c, tailer, 1, subMod1) 642 s.assertTailer(c, tailer, 1, mod2) 643 } 644 s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert) 645 } 646 647 func (s *LogTailerSuite) TestExcludeModule(c *gc.C) { 648 mod0 := logTemplate{Module: "foo.bar"} 649 mod1 := logTemplate{Module: "juju.thing"} 650 subMod1 := logTemplate{Module: "juju.thing.hai"} 651 mod2 := logTemplate{Module: "elsewhere"} 652 writeLogs := func() { 653 s.writeLogs(c, s.otherUUID, 1, mod0) 654 s.writeLogs(c, s.otherUUID, 1, mod1) 655 s.writeLogs(c, s.otherUUID, 1, mod0) 656 s.writeLogs(c, s.otherUUID, 1, subMod1) 657 s.writeLogs(c, s.otherUUID, 1, mod0) 658 s.writeLogs(c, s.otherUUID, 1, mod2) 659 } 660 params := corelogger.LogTailerParams{ 661 ExcludeModule: []string{"juju.thing", "elsewhere"}, 662 } 663 assert := func(tailer corelogger.LogTailer) { 664 s.assertTailer(c, tailer, 2, mod0) 665 } 666 s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert) 667 } 668 669 func (s *LogTailerSuite) TestIncludeExcludeModule(c *gc.C) { 670 foo := logTemplate{Module: "foo"} 671 bar := logTemplate{Module: "bar"} 672 barSub := logTemplate{Module: "bar.thing"} 673 baz := logTemplate{Module: "baz"} 674 qux := logTemplate{Module: "qux"} 675 writeLogs := func() { 676 s.writeLogs(c, s.otherUUID, 1, foo) 677 s.writeLogs(c, s.otherUUID, 1, bar) 678 s.writeLogs(c, s.otherUUID, 1, barSub) 679 s.writeLogs(c, s.otherUUID, 1, baz) 680 s.writeLogs(c, s.otherUUID, 1, qux) 681 } 682 params := corelogger.LogTailerParams{ 683 IncludeModule: []string{"foo", "bar", "qux"}, 684 ExcludeModule: []string{"foo", "bar"}, 685 } 686 assert := func(tailer corelogger.LogTailer) { 687 // Except just "qux" because "foo" and "bar" were included and 688 // then excluded. 689 s.assertTailer(c, tailer, 1, qux) 690 } 691 s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert) 692 } 693 694 func (s *LogTailerSuite) TestIncludeLabels(c *gc.C) { 695 mod0 := logTemplate{Labels: []string{"foo_bar"}} 696 mod1 := logTemplate{Labels: []string{"juju_thing"}} 697 subMod1 := logTemplate{Labels: []string{"juju_thing_hai"}} 698 mod2 := logTemplate{Labels: []string{"elsewhere"}} 699 writeLogs := func() { 700 s.writeLogs(c, s.otherUUID, 1, mod0) 701 s.writeLogs(c, s.otherUUID, 1, mod1) 702 s.writeLogs(c, s.otherUUID, 1, mod0) 703 s.writeLogs(c, s.otherUUID, 1, subMod1) 704 s.writeLogs(c, s.otherUUID, 1, mod0) 705 s.writeLogs(c, s.otherUUID, 1, mod2) 706 } 707 params := corelogger.LogTailerParams{ 708 IncludeLabel: []string{"juju_thing", "elsewhere"}, 709 } 710 assert := func(tailer corelogger.LogTailer) { 711 s.assertTailer(c, tailer, 1, mod1) 712 s.assertTailer(c, tailer, 1, mod2) 713 } 714 s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert) 715 } 716 717 func (s *LogTailerSuite) TestExcludeLabels(c *gc.C) { 718 mod0 := logTemplate{Labels: []string{"foo_bar"}} 719 mod1 := logTemplate{Labels: []string{"juju_thing"}} 720 subMod1 := logTemplate{Labels: []string{"juju_thing_hai"}} 721 mod2 := logTemplate{Labels: []string{"elsewhere"}} 722 writeLogs := func() { 723 s.writeLogs(c, s.otherUUID, 1, mod0) 724 s.writeLogs(c, s.otherUUID, 1, mod1) 725 s.writeLogs(c, s.otherUUID, 1, mod0) 726 s.writeLogs(c, s.otherUUID, 1, subMod1) 727 s.writeLogs(c, s.otherUUID, 1, mod0) 728 s.writeLogs(c, s.otherUUID, 1, mod2) 729 } 730 params := corelogger.LogTailerParams{ 731 ExcludeLabel: []string{"juju_thing", "juju_thing_hai", "elsewhere"}, 732 } 733 assert := func(tailer corelogger.LogTailer) { 734 s.assertTailer(c, tailer, 2, mod0) 735 } 736 s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert) 737 } 738 739 func (s *LogTailerSuite) TestIncludeExcludeLabels(c *gc.C) { 740 foo := logTemplate{Labels: []string{"foo"}} 741 bar := logTemplate{Labels: []string{"bar"}} 742 barSub := logTemplate{Labels: []string{"bar_thing"}} 743 baz := logTemplate{Labels: []string{"baz"}} 744 qux := logTemplate{Labels: []string{"qux"}} 745 writeLogs := func() { 746 s.writeLogs(c, s.otherUUID, 1, foo) 747 s.writeLogs(c, s.otherUUID, 1, bar) 748 s.writeLogs(c, s.otherUUID, 1, barSub) 749 s.writeLogs(c, s.otherUUID, 1, baz) 750 s.writeLogs(c, s.otherUUID, 1, qux) 751 } 752 params := corelogger.LogTailerParams{ 753 IncludeLabel: []string{"foo", "bar", "qux"}, 754 ExcludeLabel: []string{"foo", "bar"}, 755 } 756 assert := func(tailer corelogger.LogTailer) { 757 // Except just "qux" because "foo" and "bar" were included and 758 // then excluded. 759 s.assertTailer(c, tailer, 1, qux) 760 } 761 s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert) 762 } 763 764 func (s *LogTailerSuite) checkLogTailerFiltering( 765 c *gc.C, 766 st *state.State, 767 params corelogger.LogTailerParams, 768 writeLogs func(), 769 assertTailer func(corelogger.LogTailer), 770 ) { 771 // Check the tailer does the right thing when reading from the 772 // logs collection. 773 writeLogs() 774 tailer, err := state.NewLogTailer(st, params, s.oplogColl) 775 c.Assert(err, jc.ErrorIsNil) 776 defer tailer.Stop() 777 assertTailer(tailer) 778 779 // Now write out logs and check the tailer again. These will be 780 // read from the oplog. 781 writeLogs() 782 assertTailer(tailer) 783 } 784 785 type logTemplate struct { 786 Entity string 787 Version version.Number 788 Module string 789 Location string 790 Level loggo.Level 791 Message string 792 Labels []string 793 } 794 795 // writeLogs creates count log messages at the current time using 796 // the supplied template. As well as writing to the logs collection, 797 // entries are also made into the fake oplog collection. 798 func (s *LogTailerSuite) writeLogs(c *gc.C, modelUUID string, count int, lt logTemplate) { 799 t := coretesting.ZeroTime() 800 s.writeLogsT(c, modelUUID, t, t, count, lt) 801 } 802 803 // writeLogsT creates count log messages between startTime and 804 // endTime using the supplied template. As well as writing to the logs 805 // collection, entries are also made into the fake oplog collection. 806 func (s *LogTailerSuite) writeLogsT(c *gc.C, modelUUID string, startTime, endTime time.Time, count int, lt logTemplate) { 807 interval := endTime.Sub(startTime) / time.Duration(count) 808 t := startTime 809 for i := 0; i < count; i++ { 810 doc := s.logTemplateToDoc(lt, t) 811 err := s.writeLogToOplog(modelUUID, doc) 812 c.Assert(err, jc.ErrorIsNil) 813 err = s.getCollection(modelUUID).Insert(doc) 814 c.Assert(err, jc.ErrorIsNil) 815 t = t.Add(interval) 816 } 817 } 818 819 // writeLogToOplog writes out a log record to the a (probably fake) 820 // oplog collection. 821 func (s *LogTailerSuite) writeLogToOplog(modelUUID string, doc interface{}) error { 822 return s.oplogColl.Insert(bson.D{ 823 {"ts", bson.MongoTimestamp(coretesting.ZeroTime().Unix() << 32)}, // an approximation which will do 824 {"h", rand.Int63()}, // again, a suitable fake 825 {"op", "i"}, // this will always be an insert 826 {"ns", "logs.logs." + modelUUID}, 827 {"o", doc}, 828 }) 829 } 830 831 // deleteLogOplogEntry writes out a log record to the a (probably fake) 832 // oplog collection. 833 func (s *LogTailerSuite) deleteLogOplogEntry(modelUUID string, doc interface{}) error { 834 return s.oplogColl.Insert(bson.D{ 835 {"ts", bson.MongoTimestamp(coretesting.ZeroTime().Unix() << 32)}, // an approximation which will do 836 {"h", rand.Int63()}, // again, a suitable fake 837 {"op", "d"}, 838 {"ns", "logs.logs." + modelUUID}, 839 {"o", doc}, 840 }) 841 } 842 843 func (s *LogTailerSuite) normaliseLogTemplate(lt *logTemplate) { 844 if lt.Entity == "" { 845 lt.Entity = "not-a-tag" 846 } 847 if lt.Version == version.Zero { 848 lt.Version = jujuversion.Current 849 } 850 if lt.Module == "" { 851 lt.Module = "module" 852 } 853 if lt.Location == "" { 854 lt.Location = "loc" 855 } 856 if lt.Level == loggo.UNSPECIFIED { 857 lt.Level = loggo.INFO 858 } 859 if lt.Message == "" { 860 lt.Message = "message" 861 } 862 } 863 864 func (s *LogTailerSuite) logTemplateToDoc(lt logTemplate, t time.Time) interface{} { 865 s.normaliseLogTemplate(<) 866 return state.MakeLogDoc( 867 lt.Entity, 868 t, 869 lt.Module, 870 lt.Location, 871 lt.Level, 872 lt.Message, 873 lt.Labels, 874 ) 875 } 876 877 func (s *LogTailerSuite) assertTailer(c *gc.C, tailer corelogger.LogTailer, expectedCount int, lt logTemplate) { 878 s.normaliseLogTemplate(<) 879 880 timeout := time.After(coretesting.LongWait) 881 count := 0 882 for { 883 select { 884 case log, ok := <-tailer.Logs(): 885 if !ok { 886 c.Fatalf("tailer died unexpectedly: %v", tailer.Err()) 887 } 888 889 c.Assert(log.Version, gc.Equals, lt.Version) 890 c.Assert(log.Entity, gc.Equals, lt.Entity) 891 c.Assert(log.Module, gc.Equals, lt.Module) 892 c.Assert(log.Location, gc.Equals, lt.Location) 893 c.Assert(log.Level, gc.Equals, lt.Level) 894 c.Assert(log.Message, gc.Equals, lt.Message) 895 c.Assert(log.Labels, gc.DeepEquals, lt.Labels) 896 count++ 897 if count == expectedCount { 898 return 899 } 900 case <-timeout: 901 c.Fatalf("timed out waiting for logs (received %d)", count) 902 } 903 } 904 } 905 906 type DBLogSizeSuite struct { 907 coretesting.BaseSuite 908 } 909 910 var _ = gc.Suite(&DBLogSizeSuite{}) 911 912 func (*DBLogSizeSuite) TestDBLogSizeIntSize(c *gc.C) { 913 res, err := state.DBCollectionSizeToInt(bson.M{"size": int(12345)}, "coll-name") 914 c.Assert(err, jc.ErrorIsNil) 915 c.Check(res, gc.Equals, int(12345)) 916 } 917 918 func (*DBLogSizeSuite) TestDBLogSizeNoSize(c *gc.C) { 919 res, err := state.DBCollectionSizeToInt(bson.M{}, "coll-name") 920 // Old code didn't treat this as an error, if we know it doesn't happen often, we could start changing it to be an error. 921 c.Assert(err, jc.ErrorIsNil) 922 c.Check(res, gc.Equals, int(0)) 923 } 924 925 func (*DBLogSizeSuite) TestDBLogSizeInt64Size(c *gc.C) { 926 // Production results have shown that sometimes collStats can return an int64. 927 // See https://bugs.launchpad.net/juju/+bug/1790626 in case we ever figure out why 928 res, err := state.DBCollectionSizeToInt(bson.M{"size": int64(12345)}, "coll-name") 929 c.Assert(err, jc.ErrorIsNil) 930 c.Check(res, gc.Equals, int(12345)) 931 } 932 933 func (*DBLogSizeSuite) TestDBLogSizeInt64SizeOverflow(c *gc.C) { 934 // Just in case, it is unlikely this ever actually happens 935 res, err := state.DBCollectionSizeToInt(bson.M{"size": int64(12345678901)}, "coll-name") 936 c.Assert(err, jc.ErrorIsNil) 937 c.Check(res, gc.Equals, int((1<<31)-1)) 938 } 939 940 func (*DBLogSizeSuite) TestDBLogSizeNegativeSize(c *gc.C) { 941 _, err := state.DBCollectionSizeToInt(bson.M{"size": int(-10)}, "coll-name") 942 c.Check(err, gc.ErrorMatches, `mongo collStats for "coll-name" returned a negative value: -10`) 943 _, err = state.DBCollectionSizeToInt(bson.M{"size": int64(-10)}, "coll-name") 944 c.Check(err, gc.ErrorMatches, `mongo collStats for "coll-name" returned a negative value: -10`) 945 } 946 947 func (*DBLogSizeSuite) TestDBLogSizeUnknownType(c *gc.C) { 948 _, err := state.DBCollectionSizeToInt(bson.M{"size": float64(12345)}, "coll-name") 949 c.Check(err, gc.ErrorMatches, `mongo collStats for "coll-name" did not return an int or int64 for size, returned float64: 12345`) 950 }