github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/dblogpruner/worker_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package dblogpruner_test 5 6 import ( 7 stdtesting "testing" 8 "time" 9 10 "github.com/juju/clock" 11 "github.com/juju/clock/testclock" 12 "github.com/juju/loggo" 13 jujutesting "github.com/juju/testing" 14 jc "github.com/juju/testing/checkers" 15 "github.com/juju/utils/arch" 16 gc "gopkg.in/check.v1" 17 "gopkg.in/juju/names.v2" 18 "gopkg.in/juju/worker.v1" 19 "gopkg.in/juju/worker.v1/workertest" 20 "gopkg.in/mgo.v2" 21 "gopkg.in/mgo.v2/bson" 22 23 "github.com/juju/juju/state" 24 statetesting "github.com/juju/juju/state/testing" 25 "github.com/juju/juju/testing" 26 "github.com/juju/juju/version" 27 "github.com/juju/juju/worker/dblogpruner" 28 ) 29 30 func TestPackage(t *stdtesting.T) { 31 testing.MgoTestPackage(t) 32 } 33 34 var _ = gc.Suite(&suite{}) 35 36 type suite struct { 37 jujutesting.MgoSuite 38 testing.BaseSuite 39 40 state *state.State 41 pruner worker.Worker 42 logsColl *mgo.Collection 43 controllerColl *mgo.Collection 44 } 45 46 func (s *suite) SetUpSuite(c *gc.C) { 47 s.MgoSuite.SetUpSuite(c) 48 s.BaseSuite.SetUpSuite(c) 49 } 50 51 func (s *suite) TearDownSuite(c *gc.C) { 52 s.BaseSuite.TearDownSuite(c) 53 s.MgoSuite.TearDownSuite(c) 54 } 55 56 func (s *suite) SetUpTest(c *gc.C) { 57 s.MgoSuite.SetUpTest(c) 58 s.BaseSuite.SetUpTest(c) 59 } 60 61 func (s *suite) TearDownTest(c *gc.C) { 62 s.BaseSuite.TearDownTest(c) 63 s.MgoSuite.TearDownTest(c) 64 } 65 66 func (s *suite) setupState(c *gc.C, maxLogAge, maxCollectionMB string) { 67 controllerConfig := map[string]interface{}{ 68 "max-logs-age": maxLogAge, 69 "max-logs-size": maxCollectionMB, 70 } 71 72 ctlr := statetesting.InitializeWithArgs(c, statetesting.InitializeArgs{ 73 Owner: names.NewLocalUserTag("test-admin"), 74 Clock: testclock.NewClock(testing.NonZeroTime()), 75 ControllerConfig: controllerConfig, 76 }) 77 s.AddCleanup(func(*gc.C) { ctlr.Close() }) 78 s.state = ctlr.SystemState() 79 s.logsColl = s.state.MongoSession().DB("logs").C("logs." + s.state.ModelUUID()) 80 } 81 82 func (s *suite) startWorker(c *gc.C) { 83 pruner, err := dblogpruner.NewWorker(dblogpruner.Config{ 84 State: s.state, 85 Clock: clock.WallClock, 86 PruneInterval: time.Millisecond, 87 }) 88 c.Assert(err, jc.ErrorIsNil) 89 s.pruner = pruner 90 s.AddCleanup(func(c *gc.C) { workertest.CleanKill(c, s.pruner) }) 91 } 92 93 func (s *suite) TestWorkerHasReport(c *gc.C) { 94 s.setupState(c, "24h", "1024M") 95 s.startWorker(c) 96 97 type reporter interface { 98 Report() map[string]interface{} 99 } 100 101 r, ok := s.pruner.(reporter) 102 c.Assert(ok, jc.IsTrue) 103 report := r.Report() 104 // We can't generally check on particular values except that there are keys for 105 // prune-age and prune-size. 106 _, ok = report["prune-age"] 107 c.Assert(ok, jc.IsTrue) 108 _, ok = report["prune-size"] 109 c.Assert(ok, jc.IsTrue) 110 } 111 112 func (s *suite) TestPrunesOldLogs(c *gc.C) { 113 maxLogAge := 24 * time.Hour 114 s.setupState(c, "24h", "1000P") 115 s.startWorker(c) 116 117 now := time.Now() 118 addLogsToPrune := func(count int) { 119 // Add messages beyond the prune threshold. 120 tPrune := now.Add(-maxLogAge - 1) 121 s.addLogs(c, tPrune, "prune", count) 122 } 123 addLogsToKeep := func(count int) { 124 // Add messages within the prune threshold. 125 s.addLogs(c, now, "keep", count) 126 } 127 for i := 0; i < 10; i++ { 128 addLogsToKeep(5) 129 addLogsToPrune(5) 130 } 131 132 // Wait for all logs with the message "prune" to be removed. 133 for attempt := testing.LongAttempt.Start(); attempt.Next(); { 134 pruneRemaining, err := s.logsColl.Find(bson.M{"x": "prune"}).Count() 135 c.Assert(err, jc.ErrorIsNil) 136 if pruneRemaining == 0 { 137 // All the "keep" messages should still be there. 138 keepCount, err := s.logsColl.Find(bson.M{"x": "keep"}).Count() 139 c.Assert(err, jc.ErrorIsNil) 140 c.Assert(keepCount, gc.Equals, 50) 141 return 142 } 143 } 144 c.Fatal("pruning didn't happen as expected") 145 } 146 147 func (s *suite) TestPrunesLogsBySize(c *gc.C) { 148 s.setupState(c, "999h", "2M") 149 startingLogCount := 25000 150 // On some of the architectures, the logs collection is much 151 // smaller than amd64, so we need more logs to get the right size. 152 switch arch.HostArch() { 153 case arch.S390X, arch.PPC64EL, arch.ARM64: 154 startingLogCount *= 2 155 } 156 s.addLogs(c, time.Now(), "stuff", startingLogCount) 157 158 s.startWorker(c) 159 for attempt := testing.LongAttempt.Start(); attempt.Next(); { 160 count, err := s.logsColl.Count() 161 c.Assert(err, jc.ErrorIsNil) 162 // The space used by MongoDB by the collection isn't that 163 // predictable, so just treat any pruning due to size as 164 // success. 165 if count < startingLogCount { 166 return 167 } 168 } 169 c.Fatal("pruning didn't happen as expected") 170 } 171 172 func (s *suite) addLogs(c *gc.C, t0 time.Time, text string, count int) { 173 dbLogger := state.NewDbLogger(s.state) 174 defer dbLogger.Close() 175 176 for offset := 0; offset < count; offset++ { 177 t := t0.Add(-time.Duration(offset) * time.Second) 178 dbLogger.Log([]state.LogRecord{{ 179 Time: t, 180 Entity: names.NewMachineTag("0"), 181 Version: version.Current, 182 Module: "some.module", 183 Location: "foo.go:42", 184 Level: loggo.INFO, 185 Message: text, 186 }}) 187 } 188 }