github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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 "time" 8 9 jujutesting "github.com/juju/testing" 10 jc "github.com/juju/testing/checkers" 11 gc "gopkg.in/check.v1" 12 "gopkg.in/mgo.v2" 13 "gopkg.in/mgo.v2/bson" 14 15 "github.com/juju/juju/mongo" 16 coretesting "github.com/juju/juju/testing" 17 "github.com/juju/juju/worker/peergrouper" 18 ) 19 20 type oplogSuite struct { 21 coretesting.BaseSuite 22 } 23 24 var _ = gc.Suite(&oplogSuite{}) 25 26 func (s *oplogSuite) TestWithRealOplog(c *gc.C) { 27 _, session := s.startMongoWithReplicaset(c) 28 29 // Watch for oplog entries for the "bar" collection in the "foo" 30 // DB. 31 oplog := mongo.GetOplog(session) 32 tailer := mongo.NewOplogTailer( 33 oplog, 34 bson.D{{"ns", "foo.bar"}}, 35 time.Now().Add(-time.Minute), 36 ) 37 defer tailer.Stop() 38 39 assertOplog := func(expectedOp string, expectedObj, expectedUpdate bson.D) { 40 doc := s.getNextOplog(c, tailer) 41 c.Assert(doc.Operation, gc.Equals, expectedOp) 42 43 var actualObj bson.D 44 err := doc.UnmarshalObject(&actualObj) 45 c.Assert(err, jc.ErrorIsNil) 46 c.Assert(actualObj, jc.DeepEquals, expectedObj) 47 48 var actualUpdate bson.D 49 err = doc.UnmarshalUpdate(&actualUpdate) 50 c.Assert(err, jc.ErrorIsNil) 51 c.Assert(actualUpdate, jc.DeepEquals, expectedUpdate) 52 } 53 54 // Insert into foo.bar and see that the oplog entry is reported. 55 db := session.DB("foo") 56 coll := db.C("bar") 57 s.insertDoc(c, session, coll, bson.M{"_id": "thing"}) 58 assertOplog("i", bson.D{{"_id", "thing"}}, nil) 59 60 // Update foo.bar and see the update reported. 61 err := coll.UpdateId("thing", bson.M{"$set": bson.M{"blah": 42}}) 62 c.Assert(err, jc.ErrorIsNil) 63 assertOplog("u", bson.D{{"$set", bson.D{{"blah", 42}}}}, bson.D{{"_id", "thing"}}) 64 65 // Insert into another collection (shouldn't be reported due to filter). 66 s.insertDoc(c, session, db.C("elsewhere"), bson.M{"_id": "boo"}) 67 s.assertNoOplog(c, tailer) 68 } 69 70 func (s *oplogSuite) TestHonoursInitialTs(c *gc.C) { 71 _, session := s.startMongo(c) 72 73 t := time.Now() 74 75 oplog := s.makeFakeOplog(c, session) 76 for offset := -1; offset <= 1; offset++ { 77 tDoc := t.Add(time.Duration(offset) * time.Second) 78 s.insertDoc(c, session, oplog, 79 &mongo.OplogDoc{Timestamp: mongo.NewMongoTimestamp(tDoc)}, 80 ) 81 } 82 83 tailer := mongo.NewOplogTailer(oplog, nil, t) 84 defer tailer.Stop() 85 86 for offset := 0; offset <= 1; offset++ { 87 doc := s.getNextOplog(c, tailer) 88 tExpected := t.Add(time.Duration(offset) * time.Second) 89 c.Assert(doc.Timestamp, gc.Equals, mongo.NewMongoTimestamp(tExpected)) 90 } 91 } 92 93 func (s *oplogSuite) TestStops(c *gc.C) { 94 _, session := s.startMongo(c) 95 96 oplog := s.makeFakeOplog(c, session) 97 tailer := mongo.NewOplogTailer(oplog, nil, time.Time{}) 98 defer tailer.Stop() 99 100 s.insertDoc(c, session, oplog, &mongo.OplogDoc{Timestamp: 1}) 101 s.getNextOplog(c, tailer) 102 103 err := tailer.Stop() 104 c.Assert(err, jc.ErrorIsNil) 105 106 s.assertStopped(c, tailer) 107 c.Assert(tailer.Err(), jc.ErrorIsNil) 108 } 109 110 func (s *oplogSuite) TestRestartsOnError(c *gc.C) { 111 _, session := s.startMongo(c) 112 113 oplog := s.makeFakeOplog(c, session) 114 tailer := mongo.NewOplogTailer(oplog, nil, time.Time{}) 115 defer tailer.Stop() 116 117 // First, ensure that the tailer is seeing oplog inserts. 118 s.insertDoc(c, session, oplog, &mongo.OplogDoc{ 119 Timestamp: 1, 120 OperationId: 99, 121 }) 122 doc := s.getNextOplog(c, tailer) 123 c.Assert(doc.Timestamp, gc.Equals, bson.MongoTimestamp(1)) 124 125 s.emptyCapped(c, oplog) 126 127 // Ensure that the tailer still works. 128 s.insertDoc(c, session, oplog, &mongo.OplogDoc{ 129 Timestamp: 2, 130 OperationId: 42, 131 }) 132 doc = s.getNextOplog(c, tailer) 133 c.Assert(doc.Timestamp, gc.Equals, bson.MongoTimestamp(2)) 134 } 135 136 func (s *oplogSuite) TestNoRepeatsAfterIterRestart(c *gc.C) { 137 _, session := s.startMongo(c) 138 139 oplog := s.makeFakeOplog(c, session) 140 tailer := mongo.NewOplogTailer(oplog, nil, time.Time{}) 141 defer tailer.Stop() 142 143 // Insert a bunch of oplog entries with the same timestamp (but 144 // with different ids) and see them reported. 145 for id := int64(10); id < 15; id++ { 146 s.insertDoc(c, session, oplog, &mongo.OplogDoc{ 147 Timestamp: 1, 148 OperationId: id, 149 }) 150 151 doc := s.getNextOplog(c, tailer) 152 c.Assert(doc.Timestamp, gc.Equals, bson.MongoTimestamp(1)) 153 c.Assert(doc.OperationId, gc.Equals, id) 154 } 155 156 // Force the OplogTailer's iterator to be recreated. 157 s.emptyCapped(c, oplog) 158 159 // Reinsert the oplog entries that were already there before and a 160 // few more. 161 for id := int64(10); id < 20; id++ { 162 s.insertDoc(c, session, oplog, &mongo.OplogDoc{ 163 Timestamp: 1, 164 OperationId: id, 165 }) 166 } 167 168 // Insert an entry for a later timestamp. 169 s.insertDoc(c, session, oplog, &mongo.OplogDoc{ 170 Timestamp: 2, 171 OperationId: 42, 172 }) 173 174 // Ensure that only previously unreported entries are now reported. 175 for id := int64(15); id < 20; id++ { 176 doc := s.getNextOplog(c, tailer) 177 c.Assert(doc.Timestamp, gc.Equals, bson.MongoTimestamp(1)) 178 c.Assert(doc.OperationId, gc.Equals, id) 179 } 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 _, session := s.startMongo(c) 188 oplog := s.makeFakeOplog(c, session) 189 s.insertDoc(c, session, oplog, &mongo.OplogDoc{Timestamp: 1}) 190 191 tailer := mongo.NewOplogTailer(oplog, nil, time.Time{}) 192 defer tailer.Stop() 193 194 doc := s.getNextOplog(c, tailer) 195 c.Assert(doc.Timestamp, gc.Equals, bson.MongoTimestamp(1)) 196 197 // Induce a fatal error by removing the oplog collection. 198 err := oplog.DropCollection() 199 c.Assert(err, jc.ErrorIsNil) 200 201 s.assertStopped(c, tailer) 202 // The actual error varies by MongoDB version so just check that 203 // there is one. 204 c.Assert(tailer.Err(), gc.Not(jc.ErrorIsNil)) 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.MaybeInitiateMongoServer(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 inst := &jujutesting.MgoInstance{ 243 Params: []string{ 244 "--setParameter", "enableTestCommands=1", // allows "emptycapped" command 245 }, 246 } 247 err := inst.Start(nil) 248 c.Assert(err, jc.ErrorIsNil) 249 s.AddCleanup(func(*gc.C) { inst.Destroy() }) 250 return inst, s.dialMongo(c, inst) 251 } 252 253 func (s *oplogSuite) emptyCapped(c *gc.C, coll *mgo.Collection) { 254 // Call the emptycapped (test) command on a capped 255 // collection. This invalidates any cursors on the collection. 256 err := coll.Database.Run(bson.D{{"emptycapped", coll.Name}}, nil) 257 c.Assert(err, jc.ErrorIsNil) 258 } 259 260 func (s *oplogSuite) dialMongo(c *gc.C, inst *jujutesting.MgoInstance) *mgo.Session { 261 session, err := inst.Dial() 262 c.Assert(err, jc.ErrorIsNil) 263 s.AddCleanup(func(*gc.C) { session.Close() }) 264 return session 265 } 266 267 func (s *oplogSuite) makeFakeOplog(c *gc.C, session *mgo.Session) *mgo.Collection { 268 db := session.DB("foo") 269 oplog := db.C("oplog.fake") 270 err := oplog.Create(&mgo.CollectionInfo{ 271 Capped: true, 272 MaxBytes: 1024 * 1024, 273 }) 274 c.Assert(err, jc.ErrorIsNil) 275 return oplog 276 } 277 278 func (s *oplogSuite) insertDoc(c *gc.C, srcSession *mgo.Session, coll *mgo.Collection, doc interface{}) { 279 session := srcSession.Copy() 280 defer session.Close() 281 err := coll.With(session).Insert(doc) 282 c.Assert(err, jc.ErrorIsNil) 283 } 284 285 func (s *oplogSuite) getNextOplog(c *gc.C, tailer *mongo.OplogTailer) *mongo.OplogDoc { 286 select { 287 case doc, ok := <-tailer.Out(): 288 if !ok { 289 c.Fatalf("tailer unexpectedly died: %v", tailer.Err()) 290 } 291 return doc 292 case <-time.After(coretesting.LongWait): 293 c.Fatal("timed out waiting for oplog doc") 294 } 295 return nil 296 } 297 298 func (s *oplogSuite) assertNoOplog(c *gc.C, tailer *mongo.OplogTailer) { 299 select { 300 case _, ok := <-tailer.Out(): 301 if !ok { 302 c.Fatalf("tailer unexpectedly died: %v", tailer.Err()) 303 } 304 c.Fatal("unexpected oplog activity reported") 305 case <-time.After(coretesting.ShortWait): 306 // Success 307 } 308 } 309 310 func (s *oplogSuite) assertStopped(c *gc.C, tailer *mongo.OplogTailer) { 311 // Output should close. 312 select { 313 case _, ok := <-tailer.Out(): 314 c.Assert(ok, jc.IsFalse) 315 case <-time.After(coretesting.LongWait): 316 c.Fatal("tailer output should have closed") 317 } 318 319 // OplogTailer should die. 320 select { 321 case <-tailer.Dying(): 322 // Success. 323 case <-time.After(coretesting.LongWait): 324 c.Fatal("tailer should have died") 325 } 326 }