github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/mongo/oplog_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package mongo_test 5 6 import ( 7 "errors" 8 "reflect" 9 "time" 10 11 jujutesting "github.com/juju/testing" 12 jc "github.com/juju/testing/checkers" 13 gc "gopkg.in/check.v1" 14 "gopkg.in/mgo.v2" 15 "gopkg.in/mgo.v2/bson" 16 17 "github.com/juju/juju/mongo" 18 coretesting "github.com/juju/juju/testing" 19 "github.com/juju/juju/worker/peergrouper" 20 ) 21 22 type oplogSuite struct { 23 coretesting.BaseSuite 24 } 25 26 var _ = gc.Suite(&oplogSuite{}) 27 28 func (s *oplogSuite) TestWithRealOplog(c *gc.C) { 29 _, session := s.startMongoWithReplicaset(c) 30 31 // Watch for oplog entries for the "bar" collection in the "foo" 32 // DB. 33 oplog := mongo.GetOplog(session) 34 tailer := mongo.NewOplogTailer( 35 mongo.NewOplogSession( 36 oplog, 37 bson.D{{"ns", "foo.bar"}}, 38 ), 39 time.Now().Add(-time.Minute), 40 ) 41 defer tailer.Stop() 42 43 assertOplog := func(expectedOp string, expectedObj, expectedUpdate bson.D) { 44 doc := s.getNextOplog(c, tailer) 45 c.Assert(doc.Operation, gc.Equals, expectedOp) 46 47 var actualObj bson.D 48 err := doc.UnmarshalObject(&actualObj) 49 c.Assert(err, jc.ErrorIsNil) 50 c.Assert(actualObj, jc.DeepEquals, expectedObj) 51 52 var actualUpdate bson.D 53 err = doc.UnmarshalUpdate(&actualUpdate) 54 c.Assert(err, jc.ErrorIsNil) 55 c.Assert(actualUpdate, jc.DeepEquals, expectedUpdate) 56 } 57 58 // Insert into foo.bar and see that the oplog entry is reported. 59 db := session.DB("foo") 60 coll := db.C("bar") 61 s.insertDoc(c, session, coll, bson.M{"_id": "thing"}) 62 assertOplog("i", bson.D{{"_id", "thing"}}, nil) 63 64 // Update foo.bar and see the update reported. 65 err := coll.UpdateId("thing", bson.M{"$set": bson.M{"blah": 42}}) 66 c.Assert(err, jc.ErrorIsNil) 67 assertOplog("u", bson.D{{"$set", bson.D{{"blah", 42}}}}, bson.D{{"_id", "thing"}}) 68 69 // Insert into another collection (shouldn't be reported due to filter). 70 s.insertDoc(c, session, db.C("elsewhere"), bson.M{"_id": "boo"}) 71 s.assertNoOplog(c, tailer) 72 } 73 74 func (s *oplogSuite) TestHonoursInitialTs(c *gc.C) { 75 _, session := s.startMongo(c) 76 77 t := time.Now() 78 79 oplog := s.makeFakeOplog(c, session) 80 for offset := -1; offset <= 1; offset++ { 81 tDoc := t.Add(time.Duration(offset) * time.Second) 82 s.insertDoc(c, session, oplog, 83 &mongo.OplogDoc{Timestamp: mongo.NewMongoTimestamp(tDoc)}, 84 ) 85 } 86 87 tailer := mongo.NewOplogTailer(mongo.NewOplogSession(oplog, nil), t) 88 defer tailer.Stop() 89 90 for offset := 0; offset <= 1; offset++ { 91 doc := s.getNextOplog(c, tailer) 92 tExpected := t.Add(time.Duration(offset) * time.Second) 93 c.Assert(doc.Timestamp, gc.Equals, mongo.NewMongoTimestamp(tExpected)) 94 } 95 } 96 97 func (s *oplogSuite) TestStops(c *gc.C) { 98 _, session := s.startMongo(c) 99 100 oplog := s.makeFakeOplog(c, session) 101 tailer := mongo.NewOplogTailer(mongo.NewOplogSession(oplog, nil), time.Time{}) 102 defer tailer.Stop() 103 104 s.insertDoc(c, session, oplog, &mongo.OplogDoc{Timestamp: 1}) 105 s.getNextOplog(c, tailer) 106 107 err := tailer.Stop() 108 c.Assert(err, jc.ErrorIsNil) 109 110 s.assertStopped(c, tailer) 111 c.Assert(tailer.Err(), jc.ErrorIsNil) 112 } 113 114 func (s *oplogSuite) TestRestartsOnErrCursor(c *gc.C) { 115 session := newFakeSession( 116 // First iterator terminates with an ErrCursor 117 newFakeIterator(mgo.ErrCursor, &mongo.OplogDoc{Timestamp: 1, OperationId: 99}), 118 newFakeIterator(nil, &mongo.OplogDoc{Timestamp: 2, OperationId: 42}), 119 ) 120 tailer := mongo.NewOplogTailer(session, time.Time{}) 121 defer tailer.Stop() 122 123 // First, ensure that the tailer is seeing oplog rows and handles 124 // the ErrCursor that occurs at the end. 125 doc := s.getNextOplog(c, tailer) 126 c.Check(doc.Timestamp, gc.Equals, bson.MongoTimestamp(1)) 127 session.checkLastArgs(c, mongo.NewMongoTimestamp(time.Time{}), nil) 128 129 // Ensure that the tailer continues after getting a new iterator. 130 doc = s.getNextOplog(c, tailer) 131 c.Check(doc.Timestamp, gc.Equals, bson.MongoTimestamp(2)) 132 session.checkLastArgs(c, bson.MongoTimestamp(1), []int64{99}) 133 } 134 135 func (s *oplogSuite) TestNoRepeatsAfterIterRestart(c *gc.C) { 136 // A bunch of documents with the same timestamp but different ids. 137 // These will be split across 2 iterators. 138 docs := make([]*mongo.OplogDoc, 11) 139 for i := 0; i < 10; i++ { 140 id := int64(i + 10) 141 docs[i] = &mongo.OplogDoc{ 142 Timestamp: 1, 143 OperationId: id, 144 } 145 } 146 // Add one more with a different timestamp. 147 docs[10] = &mongo.OplogDoc{ 148 Timestamp: 2, 149 OperationId: 42, 150 } 151 session := newFakeSession( 152 // First block of documents, all time 1 153 newFakeIterator(nil, docs[:5]...), 154 // Second block, some time 1, one time 2 155 newFakeIterator(nil, docs[5:]...), 156 ) 157 tailer := mongo.NewOplogTailer(session, time.Time{}) 158 defer tailer.Stop() 159 160 for id := int64(10); id < 15; id++ { 161 doc := s.getNextOplog(c, tailer) 162 c.Assert(doc.Timestamp, gc.Equals, bson.MongoTimestamp(1)) 163 c.Assert(doc.OperationId, gc.Equals, id) 164 } 165 166 // Check the query doesn't exclude any in the first request. 167 session.checkLastArgs(c, mongo.NewMongoTimestamp(time.Time{}), nil) 168 169 // The OplogTailer will fall off the end of the iterator and get a new one. 170 171 // Ensure that only previously unreported entries are now reported. 172 for id := int64(15); id < 20; id++ { 173 doc := s.getNextOplog(c, tailer) 174 c.Assert(doc.Timestamp, gc.Equals, bson.MongoTimestamp(1)) 175 c.Assert(doc.OperationId, gc.Equals, id) 176 } 177 178 // Check we got the next block correctly 179 session.checkLastArgs(c, bson.MongoTimestamp(1), []int64{10, 11, 12, 13, 14}) 180 181 doc := s.getNextOplog(c, tailer) 182 c.Assert(doc.Timestamp, gc.Equals, bson.MongoTimestamp(2)) 183 c.Assert(doc.OperationId, gc.Equals, int64(42)) 184 } 185 186 func (s *oplogSuite) TestDiesOnFatalError(c *gc.C) { 187 expectedErr := errors.New("oh no, the collection went away!") 188 session := newFakeSession( 189 newFakeIterator(expectedErr, &mongo.OplogDoc{Timestamp: 1}), 190 ) 191 192 tailer := mongo.NewOplogTailer(session, time.Time{}) 193 defer tailer.Stop() 194 195 doc := s.getNextOplog(c, tailer) 196 c.Assert(doc.Timestamp, gc.Equals, bson.MongoTimestamp(1)) 197 s.assertStopped(c, tailer) 198 c.Assert(tailer.Err(), gc.Equals, expectedErr) 199 } 200 201 func (s *oplogSuite) TestNewMongoTimestamp(c *gc.C) { 202 t := time.Date(2015, 6, 24, 12, 47, 0, 0, time.FixedZone("somewhere", 5*3600)) 203 204 expected := bson.MongoTimestamp(6163845091342417920) 205 c.Assert(mongo.NewMongoTimestamp(t), gc.Equals, expected) 206 c.Assert(mongo.NewMongoTimestamp(t.In(time.UTC)), gc.Equals, expected) 207 } 208 209 func (s *oplogSuite) TestNewMongoTimestampBeforeUnixEpoch(c *gc.C) { 210 c.Assert(mongo.NewMongoTimestamp(time.Time{}), gc.Equals, bson.MongoTimestamp(0)) 211 } 212 213 func (s *oplogSuite) startMongoWithReplicaset(c *gc.C) (*jujutesting.MgoInstance, *mgo.Session) { 214 inst := &jujutesting.MgoInstance{ 215 Params: []string{ 216 "--replSet", "juju", 217 }, 218 } 219 err := inst.Start(nil) 220 c.Assert(err, jc.ErrorIsNil) 221 s.AddCleanup(func(*gc.C) { inst.Destroy() }) 222 223 // Initiate replicaset. 224 info := inst.DialInfo() 225 args := peergrouper.InitiateMongoParams{ 226 DialInfo: info, 227 MemberHostPort: inst.Addr(), 228 } 229 err = peergrouper.InitiateMongoServer(args) 230 c.Assert(err, jc.ErrorIsNil) 231 232 return inst, s.dialMongo(c, inst) 233 } 234 235 func (s *oplogSuite) startMongo(c *gc.C) (*jujutesting.MgoInstance, *mgo.Session) { 236 var inst jujutesting.MgoInstance 237 err := inst.Start(nil) 238 c.Assert(err, jc.ErrorIsNil) 239 s.AddCleanup(func(*gc.C) { inst.Destroy() }) 240 return &inst, s.dialMongo(c, &inst) 241 } 242 243 func (s *oplogSuite) emptyCapped(c *gc.C, coll *mgo.Collection) { 244 // Call the emptycapped (test) command on a capped 245 // collection. This invalidates any cursors on the collection. 246 err := coll.Database.Run(bson.D{{"emptycapped", coll.Name}}, nil) 247 c.Assert(err, jc.ErrorIsNil) 248 } 249 250 func (s *oplogSuite) dialMongo(c *gc.C, inst *jujutesting.MgoInstance) *mgo.Session { 251 session, err := inst.Dial() 252 c.Assert(err, jc.ErrorIsNil) 253 s.AddCleanup(func(*gc.C) { session.Close() }) 254 return session 255 } 256 257 func (s *oplogSuite) makeFakeOplog(c *gc.C, session *mgo.Session) *mgo.Collection { 258 db := session.DB("foo") 259 oplog := db.C("oplog.fake") 260 err := oplog.Create(&mgo.CollectionInfo{ 261 Capped: true, 262 MaxBytes: 1024 * 1024, 263 }) 264 c.Assert(err, jc.ErrorIsNil) 265 return oplog 266 } 267 268 func (s *oplogSuite) insertDoc(c *gc.C, srcSession *mgo.Session, coll *mgo.Collection, doc interface{}) { 269 session := srcSession.Copy() 270 defer session.Close() 271 err := coll.With(session).Insert(doc) 272 c.Assert(err, jc.ErrorIsNil) 273 } 274 275 func (s *oplogSuite) getNextOplog(c *gc.C, tailer *mongo.OplogTailer) *mongo.OplogDoc { 276 select { 277 case doc, ok := <-tailer.Out(): 278 if !ok { 279 c.Fatalf("tailer unexpectedly died: %v", tailer.Err()) 280 } 281 return doc 282 case <-time.After(coretesting.LongWait): 283 c.Fatal("timed out waiting for oplog doc") 284 } 285 return nil 286 } 287 288 func (s *oplogSuite) assertNoOplog(c *gc.C, tailer *mongo.OplogTailer) { 289 select { 290 case _, ok := <-tailer.Out(): 291 if !ok { 292 c.Fatalf("tailer unexpectedly died: %v", tailer.Err()) 293 } 294 c.Fatal("unexpected oplog activity reported") 295 case <-time.After(coretesting.ShortWait): 296 // Success 297 } 298 } 299 300 func (s *oplogSuite) assertStopped(c *gc.C, tailer *mongo.OplogTailer) { 301 // Output should close. 302 select { 303 case _, ok := <-tailer.Out(): 304 c.Assert(ok, jc.IsFalse) 305 case <-time.After(coretesting.LongWait): 306 c.Fatal("tailer output should have closed") 307 } 308 309 // OplogTailer should die. 310 select { 311 case <-tailer.Dying(): 312 // Success. 313 case <-time.After(coretesting.LongWait): 314 c.Fatal("tailer should have died") 315 } 316 } 317 318 type fakeIterator struct { 319 docs []*mongo.OplogDoc 320 pos int 321 err error 322 timeout bool 323 } 324 325 func (i *fakeIterator) Next(result interface{}) bool { 326 if i.pos >= len(i.docs) { 327 return false 328 } 329 target := reflect.ValueOf(result).Elem() 330 target.Set(reflect.ValueOf(*i.docs[i.pos])) 331 i.pos++ 332 return true 333 } 334 335 func (i *fakeIterator) Err() error { 336 if i.pos < len(i.docs) { 337 return nil 338 } 339 return i.err 340 } 341 342 func (i *fakeIterator) Timeout() bool { 343 if i.pos < len(i.docs) { 344 return false 345 } 346 return i.timeout 347 } 348 349 func newFakeIterator(err error, docs ...*mongo.OplogDoc) *fakeIterator { 350 return &fakeIterator{docs: docs, err: err} 351 } 352 353 type iterArgs struct { 354 timestamp bson.MongoTimestamp 355 excludeIds []int64 356 } 357 358 type fakeSession struct { 359 iterators []*fakeIterator 360 pos int 361 args chan iterArgs 362 } 363 364 var timeoutIterator = fakeIterator{timeout: true} 365 366 func (s *fakeSession) NewIter(ts bson.MongoTimestamp, ids []int64) mongo.OplogIterator { 367 if s.pos >= len(s.iterators) { 368 // We've run out of results - at this point the calls to get 369 // more data would just keep timing out. 370 return &timeoutIterator 371 } 372 select { 373 case <-time.After(coretesting.LongWait): 374 panic("took too long to save args") 375 case s.args <- iterArgs{ts, ids}: 376 } 377 result := s.iterators[s.pos] 378 s.pos++ 379 return result 380 } 381 382 func (s *fakeSession) Close() {} 383 384 func (s *fakeSession) checkLastArgs(c *gc.C, ts bson.MongoTimestamp, ids []int64) { 385 select { 386 case <-time.After(coretesting.LongWait): 387 c.Logf("timeout getting iter args - test problem") 388 c.FailNow() 389 case res := <-s.args: 390 c.Check(res.timestamp, gc.Equals, ts) 391 c.Check(res.excludeIds, gc.DeepEquals, ids) 392 } 393 } 394 395 func newFakeSession(iterators ...*fakeIterator) *fakeSession { 396 return &fakeSession{ 397 iterators: iterators, 398 args: make(chan iterArgs, 5), 399 } 400 }