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