github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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.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  	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  }