github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/state/presence/pingbatcher_test.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package presence_test 5 6 import ( 7 "fmt" 8 "time" 9 10 gitjujutesting "github.com/juju/testing" 11 jc "github.com/juju/testing/checkers" 12 "github.com/juju/utils" 13 gc "gopkg.in/check.v1" 14 "gopkg.in/juju/names.v2" 15 "gopkg.in/mgo.v2" 16 "gopkg.in/mgo.v2/bson" 17 18 "github.com/juju/juju/state/presence" 19 "github.com/juju/juju/testing" 20 ) 21 22 type PingBatcherSuite struct { 23 gitjujutesting.MgoSuite 24 testing.BaseSuite 25 presence *mgo.Collection 26 pings *mgo.Collection 27 modelTag names.ModelTag 28 } 29 30 var _ = gc.Suite(&PingBatcherSuite{}) 31 32 func (s *PingBatcherSuite) SetUpSuite(c *gc.C) { 33 s.BaseSuite.SetUpSuite(c) 34 s.MgoSuite.SetUpSuite(c) 35 uuid, err := utils.NewUUID() 36 c.Assert(err, jc.ErrorIsNil) 37 s.modelTag = names.NewModelTag(uuid.String()) 38 } 39 40 func (s *PingBatcherSuite) TearDownSuite(c *gc.C) { 41 s.MgoSuite.TearDownSuite(c) 42 s.BaseSuite.TearDownSuite(c) 43 } 44 45 func (s *PingBatcherSuite) SetUpTest(c *gc.C) { 46 s.BaseSuite.SetUpTest(c) 47 s.MgoSuite.SetUpTest(c) 48 49 db := s.MgoSuite.Session.DB("presence") 50 s.presence = db.C("presence") 51 s.pings = db.C("presence.pings") 52 53 presence.FakeTimeSlot(0) 54 } 55 56 func (s *PingBatcherSuite) TearDownTest(c *gc.C) { 57 s.MgoSuite.TearDownTest(c) 58 s.BaseSuite.TearDownTest(c) 59 60 presence.RealTimeSlot() 61 presence.RealPeriod() 62 } 63 64 func (s *PingBatcherSuite) assertPingsRecorded(c *gc.C, pb *presence.PingBatcher) { 65 // UnixNano time rounded to 30s interval 66 slot := int64(1497960150) 67 c.Assert(pb.Ping("test-uuid", slot, "0", 8), jc.ErrorIsNil) 68 c.Assert(pb.Ping("test-uuid", slot, "0", 16), jc.ErrorIsNil) 69 c.Assert(pb.Ping("test-uuid", slot, "1", 128), jc.ErrorIsNil) 70 c.Assert(pb.Ping("test-uuid", slot, "1", 1), jc.ErrorIsNil) 71 c.Assert(pb.Sync(), jc.ErrorIsNil) 72 docId := "test-uuid:1497960150" 73 var res bson.M 74 c.Assert(s.pings.FindId(docId).One(&res), jc.ErrorIsNil) 75 c.Check(res["slot"], gc.Equals, slot) 76 c.Check(res["alive"], jc.DeepEquals, bson.M{ 77 "0": int64(24), 78 "1": int64(129), 79 }) 80 } 81 func (s *PingBatcherSuite) TestRecordsPings(c *gc.C) { 82 pb := presence.NewPingBatcher(s.presence, time.Second) 83 defer assertStopped(c, pb) 84 s.assertPingsRecorded(c, pb) 85 } 86 87 func (s *PingBatcherSuite) TestRecordsPingsUsingInc(c *gc.C) { 88 pb := presence.NewPingBatcher(s.presence, time.Second) 89 pb.ForceUpdatesUsingInc() 90 defer assertStopped(c, pb) 91 s.assertPingsRecorded(c, pb) 92 } 93 94 func (s *PingBatcherSuite) TestMultipleUUIDs(c *gc.C) { 95 pb := presence.NewPingBatcher(s.presence, time.Second) 96 defer assertStopped(c, pb) 97 98 // UnixNano time rounded to 30s interval 99 slot := int64(1497960150) 100 uuid1 := "test-uuid1" 101 uuid2 := "test-uuid2" 102 c.Assert(pb.Ping(uuid1, slot, "0", 8), jc.ErrorIsNil) 103 c.Assert(pb.Ping(uuid2, slot, "0", 8), jc.ErrorIsNil) 104 c.Assert(pb.Ping(uuid2, slot, "0", 4), jc.ErrorIsNil) 105 c.Assert(pb.Sync(), jc.ErrorIsNil) 106 docId1 := fmt.Sprintf("%s:%d", uuid1, slot) 107 var res bson.M 108 c.Assert(s.pings.FindId(docId1).One(&res), jc.ErrorIsNil) 109 c.Check(res["slot"], gc.Equals, slot) 110 c.Check(res["alive"], jc.DeepEquals, bson.M{ 111 "0": int64(8), 112 }) 113 docId2 := fmt.Sprintf("%s:%d", uuid2, slot) 114 c.Assert(s.pings.FindId(docId2).One(&res), jc.ErrorIsNil) 115 c.Check(res["slot"], gc.Equals, slot) 116 c.Check(res["alive"], jc.DeepEquals, bson.M{ 117 "0": int64(12), 118 }) 119 } 120 121 func (s *PingBatcherSuite) TestMultipleFlushes(c *gc.C) { 122 pb := presence.NewPingBatcher(s.presence, time.Second) 123 defer assertStopped(c, pb) 124 125 slot := int64(1497960150) 126 uuid1 := "test-uuid1" 127 c.Assert(pb.Ping(uuid1, slot, "0", 8), jc.ErrorIsNil) 128 c.Assert(pb.Sync(), jc.ErrorIsNil) 129 130 docId1 := fmt.Sprintf("%s:%d", uuid1, slot) 131 var res bson.M 132 c.Assert(s.pings.FindId(docId1).One(&res), jc.ErrorIsNil) 133 c.Check(res, gc.DeepEquals, bson.M{ 134 "_id": docId1, 135 "slot": slot, 136 "alive": bson.M{ 137 "0": int64(8), 138 }, 139 }) 140 141 c.Assert(pb.Ping(uuid1, slot, "0", 1024), jc.ErrorIsNil) 142 c.Assert(pb.Sync(), jc.ErrorIsNil) 143 c.Assert(s.pings.FindId(docId1).One(&res), jc.ErrorIsNil) 144 c.Check(res, gc.DeepEquals, bson.M{ 145 "_id": docId1, 146 "slot": slot, 147 "alive": bson.M{ 148 "0": int64(1032), 149 }, 150 }) 151 } 152 153 func (s *PingBatcherSuite) TestMultipleSlots(c *gc.C) { 154 pb := presence.NewPingBatcher(s.presence, time.Second) 155 defer assertStopped(c, pb) 156 157 slot1 := int64(1497960150) 158 slot2 := int64(1497960180) 159 uuid1 := "test-uuid1" 160 c.Assert(pb.Ping(uuid1, slot1, "0", 8), jc.ErrorIsNil) 161 c.Assert(pb.Ping(uuid1, slot1, "0", 32), jc.ErrorIsNil) 162 c.Assert(pb.Ping(uuid1, slot2, "1", 16), jc.ErrorIsNil) 163 c.Assert(pb.Ping(uuid1, slot2, "0", 8), jc.ErrorIsNil) 164 c.Assert(pb.Sync(), jc.ErrorIsNil) 165 166 docId1 := fmt.Sprintf("%s:%d", uuid1, slot1) 167 var res bson.M 168 c.Assert(s.pings.FindId(docId1).One(&res), jc.ErrorIsNil) 169 c.Check(res, gc.DeepEquals, bson.M{ 170 "_id": docId1, 171 "slot": slot1, 172 "alive": bson.M{ 173 "0": int64(40), 174 }, 175 }) 176 177 docId2 := fmt.Sprintf("%s:%d", uuid1, slot2) 178 c.Assert(s.pings.FindId(docId2).One(&res), jc.ErrorIsNil) 179 c.Check(res["slot"], gc.Equals, slot2) 180 c.Check(res, gc.DeepEquals, bson.M{ 181 "_id": docId2, 182 "slot": slot2, 183 "alive": bson.M{ 184 "0": int64(8), 185 "1": int64(16), 186 }, 187 }) 188 } 189 190 func (s *PingBatcherSuite) TestDocBatchSize(c *gc.C) { 191 // We don't want to hit an internal flush 192 pb := presence.NewPingBatcher(s.presence, time.Hour) 193 defer assertStopped(c, pb) 194 195 slotBase := int64(1497960150) 196 fieldKey := "0" 197 fieldBit := uint64(64) 198 // 100 slots * 100 models should be 10,000 docs that we are inserting. 199 // mgo.Bulk fails if you try to do more than 1000 requests at once, so this would trigger it if we didn't batch properly. 200 for modelCounter := 0; modelCounter < 100; modelCounter++ { 201 for slotOffset := 0; slotOffset < 100; slotOffset++ { 202 slot := slotBase + int64(slotOffset*30) 203 uuid := fmt.Sprintf("uuid-%d", modelCounter) 204 c.Assert(pb.Ping(uuid, slot, fieldKey, fieldBit), jc.ErrorIsNil) 205 } 206 } 207 c.Assert(pb.Sync(), jc.ErrorIsNil) 208 count, err := s.pings.Count() 209 c.Assert(err, jc.ErrorIsNil) 210 c.Check(count, gc.Equals, 100*100) 211 } 212 213 func (s *PingBatcherSuite) TestBatchFlushesByTime(c *gc.C) { 214 t := time.Now() 215 pb := presence.NewPingBatcher(s.presence, 50*time.Millisecond) 216 defer assertStopped(c, pb) 217 218 slot := int64(1497960150) 219 uuid := "test-uuid" 220 c.Assert(pb.Ping("test-uuid", slot, "0", 8), jc.ErrorIsNil) 221 c.Assert(pb.Ping("test-uuid", slot, "0", 16), jc.ErrorIsNil) 222 docId := fmt.Sprintf("%s:%d", uuid, slot) 223 var res bson.M 224 // We should not have found it yet 225 c.Assert(s.pings.FindId(docId).One(&res), gc.Equals, mgo.ErrNotFound) 226 // We will wait up to 1s for the write to succeed. While waiting, we will ping on another slot, to 227 // hint at other pingers that are still active, without messing up our final assertion. 228 slot2 := slot + 30 229 for i := 0; i < 1000; i++ { 230 time.Sleep(time.Millisecond) 231 err := s.pings.FindId(docId).One(&res) 232 slot2 = slot2 + 30 233 pb.Ping("test-uuid", slot2, "0", 1) 234 waitTime := time.Since(t) 235 if waitTime < time.Duration(35*time.Millisecond) { 236 // Officially it should take a minimum of 50*0.8 = 40ms. 237 // make sure the timer hasn't flushed yet 238 c.Assert(err, gc.Equals, mgo.ErrNotFound, 239 gc.Commentf("PingBatcher flushed too soon, expected at least 15ms")) 240 continue 241 } 242 if err == nil { 243 c.Logf("found the document after %v", waitTime) 244 break 245 } else { 246 c.Logf("no document after %v", waitTime) 247 c.Assert(err, gc.Equals, mgo.ErrNotFound) 248 } 249 } 250 // If it wasn't found, this check will fail 251 c.Check(res, gc.DeepEquals, bson.M{ 252 "_id": docId, 253 "slot": slot, 254 "alive": bson.M{ 255 "0": int64(24), 256 }, 257 }) 258 } 259 260 func (s *PingBatcherSuite) TestPingBatcherFlushesOnShutdown(c *gc.C) { 261 // Make sure it doesn't flush on a timer 262 pb := presence.NewPingBatcher(s.presence, time.Hour) 263 defer assertStopped(c, pb) 264 slot := int64(1497960150) 265 uuid := "test-uuid" 266 docId := fmt.Sprintf("%s:%d", uuid, slot) 267 var res bson.M 268 // We should not have found it yet 269 c.Assert(s.pings.FindId(docId).One(&res), gc.Equals, mgo.ErrNotFound) 270 c.Assert(pb.Ping("test-uuid", slot, "0", 8), jc.ErrorIsNil) 271 // Still not found, as it hasn't been flushed 272 c.Assert(s.pings.FindId(docId).One(&res), gc.Equals, mgo.ErrNotFound) 273 c.Assert(pb.Stop(), jc.ErrorIsNil) 274 // And now we find it 275 c.Assert(s.pings.FindId(docId).One(&res), jc.ErrorIsNil) 276 c.Check(res, gc.DeepEquals, bson.M{ 277 "_id": docId, 278 "slot": slot, 279 "alive": bson.M{ 280 "0": int64(8), 281 }, 282 }) 283 } 284 285 func (s *PingBatcherSuite) TestStoppedPingerRejectsPings(c *gc.C) { 286 pb := presence.NewPingBatcher(s.presence, testing.ShortWait) 287 defer assertStopped(c, pb) 288 c.Assert(pb.Stop(), jc.ErrorIsNil) 289 slot := int64(1497960150) 290 uuid := "test-uuid" 291 err := pb.Ping(uuid, slot, "0", 8) 292 c.Assert(err, gc.ErrorMatches, "PingBatcher is stopped") 293 err = pb.Sync() 294 c.Assert(err, gc.ErrorMatches, "PingBatcher is stopped") 295 } 296 297 func (s *PingBatcherSuite) TestNewDeadPingBatcher(c *gc.C) { 298 testErr := fmt.Errorf("this is an error") 299 pb := presence.NewDeadPingBatcher(testErr) 300 slot := int64(1497960150) 301 uuid := "test-uuid" 302 err := pb.Ping(uuid, slot, "0", 8) 303 c.Assert(err, gc.ErrorMatches, "this is an error") 304 305 err = pb.Stop() 306 c.Assert(err, gc.ErrorMatches, "this is an error") 307 }