github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/state/presence/pruner_test.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package presence 5 6 import ( 7 "fmt" 8 "math/rand" 9 "sort" 10 "sync" 11 "time" 12 13 gitjujutesting "github.com/juju/testing" 14 jc "github.com/juju/testing/checkers" 15 "github.com/juju/utils" 16 gc "gopkg.in/check.v1" 17 "gopkg.in/juju/names.v2" 18 "gopkg.in/juju/worker.v1" 19 "gopkg.in/mgo.v2" 20 21 "github.com/juju/juju/testing" 22 ) 23 24 type prunerSuite struct { 25 gitjujutesting.MgoSuite 26 testing.BaseSuite 27 presence *mgo.Collection 28 beings *mgo.Collection 29 pings *mgo.Collection 30 modelTag names.ModelTag 31 } 32 33 var _ = gc.Suite(&prunerSuite{}) 34 35 func (s *prunerSuite) SetUpSuite(c *gc.C) { 36 s.BaseSuite.SetUpSuite(c) 37 s.MgoSuite.SetUpSuite(c) 38 uuid, err := utils.NewUUID() 39 c.Assert(err, jc.ErrorIsNil) 40 s.modelTag = names.NewModelTag(uuid.String()) 41 } 42 43 func (s *prunerSuite) TearDownSuite(c *gc.C) { 44 s.MgoSuite.TearDownSuite(c) 45 s.BaseSuite.TearDownSuite(c) 46 } 47 48 func (s *prunerSuite) SetUpTest(c *gc.C) { 49 s.BaseSuite.SetUpTest(c) 50 s.MgoSuite.SetUpTest(c) 51 52 db := s.MgoSuite.Session.DB("presence") 53 s.presence = db.C("presence") 54 s.beings = db.C("presence.beings") 55 s.pings = db.C("presence.pings") 56 57 FakeTimeSlot(0) 58 } 59 60 func (s *prunerSuite) TearDownTest(c *gc.C) { 61 s.MgoSuite.TearDownTest(c) 62 s.BaseSuite.TearDownTest(c) 63 64 RealTimeSlot() 65 RealPeriod() 66 } 67 68 func findBeing(c *gc.C, beingsC *mgo.Collection, modelUUID string, seq int64) (beingInfo, error) { 69 var being beingInfo 70 err := beingsC.FindId(docIDInt64(modelUUID, seq)).One(&being) 71 return being, err 72 } 73 74 func checkCollectionCount(c *gc.C, coll *mgo.Collection, count int) { 75 count, err := coll.Count() 76 c.Assert(err, jc.ErrorIsNil) 77 c.Check(count, gc.Equals, count) 78 } 79 80 func (s *prunerSuite) getDirectRecorder() PingRecorder { 81 return DirectRecordFunc(s.presence) 82 } 83 84 func (s *prunerSuite) TestPrunesOldPingsAndBeings(c *gc.C) { 85 keys := []string{"key1", "key2"} 86 pingers := make([]*Pinger, len(keys)) 87 for i, key := range keys { 88 pingers[i] = NewPinger(s.presence, s.modelTag, key, s.getDirectRecorder) 89 } 90 const numSlots = 10 91 sequences := make([][]int64, len(keys)) 92 for i := range keys { 93 sequences[i] = make([]int64, numSlots) 94 } 95 96 for i := 0; i < numSlots; i++ { 97 FakeTimeSlot(i) 98 for j, p := range pingers { 99 // Create a new being sequence, and force a ping in this 100 // time slot. We don't Start()/Stop() them so we don't 101 // have to worry about things being async. 102 p.prepare() 103 p.ping() 104 sequences[j][i] = p.beingSeq 105 } 106 } 107 // At this point, we should have 10 ping slots active, and 10 different 108 // beings representing each key 109 checkCollectionCount(c, s.beings, numSlots*len(keys)) 110 checkCollectionCount(c, s.pings, numSlots) 111 // Now we prune them, and assert that it removed items, but preserved the 112 // latest beings (things referenced by the latest pings) 113 pruner := NewPruner(s.modelTag.Id(), s.beings, s.pings, 0) 114 c.Assert(pruner.Prune(nil), jc.ErrorIsNil) 115 checkCollectionCount(c, s.pings, 4) 116 checkCollectionCount(c, s.beings, 2*len(keys)) 117 for i, key := range keys { 118 expectedSeq := sequences[i][numSlots-2] 119 being, err := findBeing(c, s.beings, s.modelTag.Id(), expectedSeq) 120 c.Assert(err, jc.ErrorIsNil) 121 c.Check(being.Seq, gc.Equals, expectedSeq) 122 c.Check(being.Key, gc.Equals, key) 123 } 124 } 125 126 func (s *prunerSuite) TestPreservesLatestSequence(c *gc.C) { 127 FakePeriod(1) 128 129 key := "blah" 130 p1 := NewPinger(s.presence, s.modelTag, key, s.getDirectRecorder) 131 p1.Start() 132 assertStopped(c, p1) 133 p2 := NewPinger(s.presence, s.modelTag, key, s.getDirectRecorder) 134 p2.Start() 135 assertStopped(c, p2) 136 // we're starting p2 second, so it should get a higher sequence 137 c.Check(p1.beingSeq, gc.Not(gc.Equals), int64(0)) 138 c.Check(p1.beingSeq, jc.LessThan, p2.beingSeq) 139 // Before pruning, we expect both beings to exist 140 being, err := findBeing(c, s.beings, s.modelTag.Id(), p1.beingSeq) 141 c.Assert(err, jc.ErrorIsNil) 142 c.Check(being.Key, gc.Equals, key) 143 c.Check(being.Seq, gc.Equals, p1.beingSeq) 144 being, err = findBeing(c, s.beings, s.modelTag.Id(), p2.beingSeq) 145 c.Assert(err, jc.ErrorIsNil) 146 c.Check(being.Key, gc.Equals, key) 147 c.Check(being.Seq, gc.Equals, p2.beingSeq) 148 149 pruner := NewPruner(s.modelTag.Id(), s.beings, s.pings, 0) 150 c.Assert(pruner.Prune(nil), jc.ErrorIsNil) 151 // After pruning, p2 should still be available 152 being, err = findBeing(c, s.beings, s.modelTag.Id(), p2.beingSeq) 153 c.Assert(err, jc.ErrorIsNil) 154 c.Check(being.Seq, gc.Equals, p2.beingSeq) 155 } 156 157 func (s *prunerSuite) TestMultiplePingersClearMemoryCache(c *gc.C) { 158 FakePeriod(1) 159 160 key := "blah" 161 p1 := NewPinger(s.presence, s.modelTag, key, s.getDirectRecorder) 162 p1.Start() 163 assertStopped(c, p1) 164 highestSeq := p1.beingSeq 165 memCache := make(map[int64]string) 166 memCache[p1.beingSeq] = key 167 for i := 1; i < 10; i++ { 168 FakeTimeSlot(i) 169 newP := NewPinger(s.presence, s.modelTag, key, s.getDirectRecorder) 170 newP.Start() 171 assertStopped(c, newP) 172 highestSeq = newP.beingSeq 173 memCache[highestSeq] = key 174 } 175 // Before pruning, we expect the first and last beings to exist 176 being, err := findBeing(c, s.beings, s.modelTag.Id(), p1.beingSeq) 177 c.Assert(err, jc.ErrorIsNil) 178 c.Check(being.Key, gc.Equals, key) 179 c.Check(being.Seq, gc.Equals, p1.beingSeq) 180 being, err = findBeing(c, s.beings, s.modelTag.Id(), highestSeq) 181 c.Assert(err, jc.ErrorIsNil) 182 c.Check(being.Key, gc.Equals, key) 183 c.Check(being.Seq, gc.Equals, highestSeq) 184 185 c.Check(memCache[p1.beingSeq], gc.Equals, key) 186 c.Check(memCache[highestSeq], gc.Equals, key) 187 pruner := NewPruner(s.modelTag.Id(), s.beings, s.pings, 0) 188 c.Assert(pruner.Prune(memCache), jc.ErrorIsNil) 189 // The oldest should no longer be available, but the latest should 190 // And the old seq should be pruned from the memory cache 191 being, err = findBeing(c, s.beings, s.modelTag.Id(), highestSeq) 192 c.Assert(err, jc.ErrorIsNil) 193 c.Check(memCache[highestSeq], gc.Equals, key) 194 being, err = findBeing(c, s.beings, s.modelTag.Id(), p1.beingSeq) 195 c.Assert(err, gc.ErrorMatches, "not found") 196 // Not found 197 c.Check(memCache[p1.beingSeq], gc.Equals, "") 198 } 199 200 func waitForFirstChange(c *gc.C, watch <-chan Change, want Change) { 201 timeout := time.After(testing.LongWait) 202 for { 203 select { 204 case got := <-watch: 205 if got == want { 206 return 207 } 208 if got.Alive == false { 209 c.Fatalf("got a not-alive before the one we were expecting: %v (want %v)", got, want) 210 } 211 case <-timeout: 212 c.Fatalf("watch reported nothing, want %v", want) 213 } 214 } 215 } 216 217 // assertStopped stops a worker and waits until it reports stopped. 218 // Use this method in favor of defer w.Stop() because you _must_ ensure 219 // that the worker has stopped, and thus is no longer using its mgo 220 // session before TearDownTest shuts down the connection. 221 func assertStopped(c *gc.C, w worker.Worker) { 222 done := make(chan struct{}) 223 go func() { 224 c.Check(worker.Stop(w), jc.ErrorIsNil) 225 close(done) 226 }() 227 select { 228 case <-done: 229 return 230 case <-time.After(testing.ShortWait): 231 c.Fatalf("failed to stop worker %v after %v", w, testing.ShortWait) 232 } 233 } 234 235 func (s *prunerSuite) TestDeepStressStaysSane(c *gc.C) { 236 FakePeriod(2) 237 keys := make([]string, 50) 238 for i := 0; i < len(keys); i++ { 239 keys[i] = fmt.Sprintf("being-%04d", i) 240 } 241 // To create abuse on the system, we leave 2 pingers active for every 242 // key. We then keep creating new pingers for each one, and rotate them 243 // into old, and stop them when they rotate out. So we potentially have 244 // 3 active pingers for each key. We should never see any key go 245 // inactive, because we ping when we start a Pinger. 246 oldPingers := make([]*Pinger, len(keys)) 247 newPingers := make([]*Pinger, len(keys)) 248 ch := make(chan Change) 249 w := NewWatcher(s.presence, s.modelTag) 250 // Ensure that all pingers and the watcher are clean at exit 251 defer assertStopped(c, w) 252 pb := NewPingBatcher(s.presence, 500*time.Millisecond) 253 defer assertStopped(c, pb) 254 getPB := func() PingRecorder { return pb } 255 defer func() { 256 for i, p := range oldPingers { 257 if p == nil { 258 continue 259 } 260 assertStopped(c, p) 261 oldPingers[i] = nil 262 } 263 for i, p := range newPingers { 264 if p == nil { 265 continue 266 } 267 assertStopped(c, p) 268 newPingers[i] = nil 269 } 270 }() 271 t := time.Now() 272 FakeTimeSlot(1) 273 for i, key := range keys { 274 w.Watch(key, ch) 275 // we haven't started the pinger yet, so the initial state must be stopped 276 // As this is a busy channel, we may be queued up behind some other 277 // pinger showing up as alive, so allow up to LongWait for the event to show up 278 waitForFirstChange(c, ch, Change{key, false}) 279 p := NewPinger(s.presence, s.modelTag, key, getPB) 280 err := p.Start() 281 c.Assert(err, jc.ErrorIsNil) 282 newPingers[i] = p 283 } 284 c.Assert(pb.Sync(), jc.ErrorIsNil) 285 c.Logf("initialized %d pingers in %v\n", len(newPingers), time.Since(t)) 286 // Make sure all of the entities stay showing up as alive 287 deadKeys := make([]string, 0) 288 done := make(chan struct{}) 289 var wg sync.WaitGroup 290 wg.Add(1) 291 go func() { 292 for { 293 select { 294 case got := <-ch: 295 if !got.Alive { 296 deadKeys = append(deadKeys, got.Key) 297 } 298 case <-done: 299 wg.Done() 300 return 301 } 302 } 303 }() 304 beings := s.presence.Database.C(s.presence.Name + ".beings") 305 // Create a background Pruner task, that prunes items independently of 306 // when they are being updated 307 wg.Add(1) 308 go func() { 309 for { 310 select { 311 case <-done: 312 wg.Done() 313 return 314 case <-time.After(time.Duration(rand.Intn(500)+1000) * time.Millisecond): 315 oldPruner := NewPruner(s.modelTag.Id(), beings, s.pings, 0) 316 // Don't assert in a goroutine, as the panic may do bad things 317 c.Check(oldPruner.Prune(nil), jc.ErrorIsNil) 318 } 319 } 320 }() 321 const loopCount = 10 322 for loop := 0; loop < loopCount; loop++ { 323 FakeTimeSlot(loop + 2) 324 t := time.Now() 325 for _, i := range rand.Perm(len(keys)) { 326 old := oldPingers[i] 327 if old != nil { 328 assertStopped(c, old) 329 } 330 oldPingers[i] = newPingers[i] 331 p := NewPinger(s.presence, s.modelTag, keys[i], getPB) 332 err := p.Start() 333 c.Assert(err, jc.ErrorIsNil) 334 newPingers[i] = p 335 } 336 c.Assert(pb.Sync(), jc.ErrorIsNil) 337 c.Logf("loop %d in %v\n", loop, time.Since(t)) 338 } 339 // Now that we've gone through all of that, check that we've created as 340 // many sequences as we think we have 341 seq := s.presence.Database.C(s.presence.Name + ".seqs") 342 var sequence struct { 343 Seq int64 `bson:"seq"` 344 } 345 seqDocID := s.modelTag.Id() + ":beings" 346 err := seq.FindId(seqDocID).One(&sequence) 347 c.Assert(err, jc.ErrorIsNil) 348 // we should have created N keys Y+1 times (once in init, once per loop) 349 seqCount := int64(len(keys) * (loopCount + 1)) 350 c.Check(sequence.Seq, gc.Equals, seqCount) 351 oldPruner := NewPruner(s.modelTag.Id(), beings, s.pings, 0) 352 c.Assert(oldPruner.Prune(nil), jc.ErrorIsNil) 353 count, err := beings.Count() 354 c.Assert(err, jc.ErrorIsNil) 355 // After pruning, we should have at least one sequence for each key, 356 // but not more than fits in the last 4 ping slots 357 c.Check(count, jc.GreaterThan, len(keys)-1) 358 c.Check(count, jc.LessThan, len(keys)*8) 359 // Run the pruner again, it should essentially be a no-op 360 oldPruner = NewPruner(s.modelTag.Id(), beings, s.pings, 0) 361 c.Assert(oldPruner.Prune(nil), jc.ErrorIsNil) 362 close(done) 363 wg.Wait() 364 sort.Strings(deadKeys) 365 c.Check(len(deadKeys), gc.Equals, 0) 366 c.Check(deadKeys, jc.DeepEquals, []string{}) 367 }