github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/state/raftlease/target_test.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package raftlease_test
     5  
     6  import (
     7  	"bytes"
     8  	"strings"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	"github.com/juju/testing"
    13  	jc "github.com/juju/testing/checkers"
    14  	jujutxn "github.com/juju/txn"
    15  	txntesting "github.com/juju/txn/testing"
    16  	gc "gopkg.in/check.v1"
    17  	"gopkg.in/mgo.v2"
    18  	"gopkg.in/mgo.v2/bson"
    19  	"gopkg.in/mgo.v2/txn"
    20  
    21  	"github.com/juju/juju/core/lease"
    22  	coreraftlease "github.com/juju/juju/core/raftlease"
    23  	"github.com/juju/juju/mongo"
    24  	"github.com/juju/juju/state/raftlease"
    25  )
    26  
    27  const (
    28  	collection = "testleaseholders"
    29  	logPrefix  = `\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}\.\d{1,6} `
    30  )
    31  
    32  type targetSuite struct {
    33  	testing.IsolationSuite
    34  	testing.MgoSuite
    35  	db       *mgo.Database
    36  	mongo    *Mongo
    37  	errorLog loggo.Logger
    38  	leaseLog *bytes.Buffer
    39  }
    40  
    41  var _ = gc.Suite(&targetSuite{})
    42  
    43  func (s *targetSuite) SetUpSuite(c *gc.C) {
    44  	s.IsolationSuite.SetUpSuite(c)
    45  	s.MgoSuite.SetUpSuite(c)
    46  }
    47  
    48  func (s *targetSuite) TearDownSuite(c *gc.C) {
    49  	s.MgoSuite.TearDownSuite(c)
    50  	s.IsolationSuite.TearDownSuite(c)
    51  }
    52  
    53  func (s *targetSuite) SetUpTest(c *gc.C) {
    54  	s.IsolationSuite.SetUpTest(c)
    55  	s.MgoSuite.SetUpTest(c)
    56  	s.db = s.Session.DB("juju")
    57  	s.mongo = NewMongo(s.db)
    58  	s.errorLog = loggo.GetLogger("raftlease_test")
    59  	s.leaseLog = bytes.NewBuffer(nil)
    60  }
    61  
    62  func (s *targetSuite) TearDownTest(c *gc.C) {
    63  	s.MgoSuite.TearDownTest(c)
    64  	s.IsolationSuite.TearDownTest(c)
    65  }
    66  
    67  func (s *targetSuite) newTarget() coreraftlease.NotifyTarget {
    68  	return raftlease.NewNotifyTarget(s.mongo, collection, s.leaseLog, s.errorLog)
    69  }
    70  
    71  func (s *targetSuite) getRows(c *gc.C) []bson.M {
    72  	var results []bson.M
    73  	err := s.db.C(collection).Find(nil).Select(bson.M{
    74  		"namespace":  true,
    75  		"model-uuid": true,
    76  		"lease":      true,
    77  		"holder":     true,
    78  	}).All(&results)
    79  	c.Assert(err, jc.ErrorIsNil)
    80  	return results
    81  }
    82  
    83  func (s *targetSuite) TestClaimedNoRecord(c *gc.C) {
    84  	target := s.newTarget()
    85  	target.Claimed(lease.Key{"ns", "model", "ankles"}, "tailpipe")
    86  	c.Assert(s.getRows(c), gc.DeepEquals, []bson.M{{
    87  		"_id":        "model:ns#ankles#",
    88  		"namespace":  "ns",
    89  		"model-uuid": "model",
    90  		"lease":      "ankles",
    91  		"holder":     "tailpipe",
    92  	}})
    93  	s.assertLogMatches(c, `claimed "model:ns#ankles#" for "tailpipe"`)
    94  }
    95  
    96  func (s *targetSuite) TestClaimedAlreadyHolder(c *gc.C) {
    97  	err := s.db.C(collection).Insert(
    98  		bson.M{
    99  			"_id":        "model:leadership#twin#",
   100  			"namespace":  "leadership",
   101  			"model-uuid": "model",
   102  			"lease":      "twin",
   103  			"holder":     "voiid",
   104  		},
   105  	)
   106  	c.Assert(err, jc.ErrorIsNil)
   107  	s.newTarget().Claimed(
   108  		lease.Key{"leadership", "model", "twin"},
   109  		"voiid",
   110  	)
   111  	c.Assert(s.getRows(c), gc.DeepEquals, []bson.M{{
   112  		"_id":        "model:leadership#twin#",
   113  		"namespace":  "leadership",
   114  		"model-uuid": "model",
   115  		"lease":      "twin",
   116  		"holder":     "voiid",
   117  	}})
   118  	s.assertLogMatches(c, `claimed "model:leadership#twin#" for "voiid"`)
   119  }
   120  
   121  func (s *targetSuite) TestClaimedDifferentHolder(c *gc.C) {
   122  	err := s.db.C(collection).Insert(
   123  		bson.M{
   124  			"_id":        "model:leadership#twin#",
   125  			"namespace":  "leadership",
   126  			"model-uuid": "model",
   127  			"lease":      "twin",
   128  			"holder":     "shuffle",
   129  		},
   130  	)
   131  	c.Assert(err, jc.ErrorIsNil)
   132  	s.newTarget().Claimed(
   133  		lease.Key{"leadership", "model", "twin"},
   134  		"voiid",
   135  	)
   136  	c.Assert(s.getRows(c), gc.DeepEquals, []bson.M{{
   137  		"_id":        "model:leadership#twin#",
   138  		"namespace":  "leadership",
   139  		"model-uuid": "model",
   140  		"lease":      "twin",
   141  		"holder":     "voiid",
   142  	}})
   143  	s.assertLogMatches(c, `claimed "model:leadership#twin#" for "voiid"`)
   144  }
   145  
   146  func (s *targetSuite) TestClaimedRecordsChangeBetweenAttempts(c *gc.C) {
   147  	defer txntesting.SetBeforeHooks(c, s.mongo.runner, func() {
   148  		err := s.db.C(collection).Insert(
   149  			bson.M{
   150  				"_id":        "model:leadership#twin#",
   151  				"namespace":  "leadership",
   152  				"model-uuid": "model",
   153  				"lease":      "twin",
   154  				"holder":     "kitamura",
   155  			},
   156  		)
   157  		c.Assert(err, jc.ErrorIsNil)
   158  	}).Check()
   159  	s.newTarget().Claimed(
   160  		lease.Key{"leadership", "model", "twin"},
   161  		"voiid",
   162  	)
   163  	c.Assert(s.getRows(c), gc.DeepEquals, []bson.M{{
   164  		"_id":        "model:leadership#twin#",
   165  		"namespace":  "leadership",
   166  		"model-uuid": "model",
   167  		"lease":      "twin",
   168  		"holder":     "voiid",
   169  	}})
   170  	s.assertLogMatches(c, `claimed "model:leadership#twin#" for "voiid"`)
   171  }
   172  
   173  func (s *targetSuite) TestClaimedError(c *gc.C) {
   174  	var logWriter loggo.TestWriter
   175  	c.Assert(loggo.RegisterWriter("raftlease-target-tests", &logWriter), jc.ErrorIsNil)
   176  	s.mongo.txnErr = errors.Errorf("oh no!")
   177  	s.newTarget().Claimed(lease.Key{"ns", "model", "lease"}, "me")
   178  	c.Assert(s.getRows(c), gc.HasLen, 0)
   179  	c.Assert(logWriter.Log(), jc.LogMatches, []string{
   180  		`couldn't claim lease "model:ns#lease#" for "me" in db: oh no!`,
   181  	})
   182  	s.assertLogMatches(c,
   183  		`claimed "model:ns#lease#" for "me"`,
   184  		`couldn't claim lease "model:ns#lease#" for "me" in db: oh no!`,
   185  	)
   186  }
   187  
   188  func (s *targetSuite) assertLogMatches(c *gc.C, expectLines ...string) {
   189  	lines := strings.Split(string(s.leaseLog.Bytes()), "\n")
   190  	c.Assert(lines, gc.HasLen, len(expectLines)+1)
   191  	for i, expected := range expectLines {
   192  		c.Assert(lines[i], gc.Matches, logPrefix+expected, gc.Commentf("line %d", i))
   193  	}
   194  	c.Assert(lines[len(expectLines)], gc.Equals, "")
   195  }
   196  
   197  func (s *targetSuite) TestExpired(c *gc.C) {
   198  	err := s.db.C(collection).Insert(
   199  		bson.M{
   200  			"_id":        "model:leadership#twin#",
   201  			"namespace":  "leadership",
   202  			"model-uuid": "model",
   203  			"lease":      "twin",
   204  			"holder":     "kitamura",
   205  		},
   206  	)
   207  	c.Assert(err, jc.ErrorIsNil)
   208  	s.newTarget().Expired(lease.Key{"leadership", "model", "twin"})
   209  	c.Assert(s.getRows(c), gc.HasLen, 0)
   210  	s.assertLogMatches(c, `expired "model:leadership#twin#"`)
   211  }
   212  
   213  func (s *targetSuite) TestExpiredNoRecord(c *gc.C) {
   214  	s.newTarget().Expired(lease.Key{"leadership", "model", "twin"})
   215  	c.Assert(s.getRows(c), gc.HasLen, 0)
   216  	s.assertLogMatches(c, `expired "model:leadership#twin#"`)
   217  }
   218  
   219  func (s *targetSuite) TestExpiredRemovedWhileRunning(c *gc.C) {
   220  	coll := s.db.C(collection)
   221  	err := coll.Insert(
   222  		bson.M{
   223  			"_id":        "model:leadership#twin#",
   224  			"namespace":  "leadership",
   225  			"model-uuid": "model",
   226  			"lease":      "twin",
   227  			"holder":     "kitamura",
   228  		},
   229  	)
   230  	c.Assert(err, jc.ErrorIsNil)
   231  	defer txntesting.SetBeforeHooks(c, s.mongo.runner, func() {
   232  		c.Assert(coll.Remove(nil), jc.ErrorIsNil)
   233  	}).Check()
   234  	s.newTarget().Expired(lease.Key{"leadership", "model", "twin"})
   235  	c.Assert(s.getRows(c), gc.HasLen, 0)
   236  	s.assertLogMatches(c, `expired "model:leadership#twin#"`)
   237  }
   238  
   239  func (s *targetSuite) TestExpiredError(c *gc.C) {
   240  	var logWriter loggo.TestWriter
   241  	c.Assert(loggo.RegisterWriter("raftlease-target-tests", &logWriter), jc.ErrorIsNil)
   242  	err := s.db.C(collection).Insert(
   243  		bson.M{
   244  			"_id":        "model:leadership#twin#",
   245  			"namespace":  "leadership",
   246  			"model-uuid": "model",
   247  			"lease":      "twin",
   248  			"holder":     "kitamura",
   249  		},
   250  	)
   251  	c.Assert(err, jc.ErrorIsNil)
   252  	s.mongo.txnErr = errors.Errorf("oops!")
   253  	s.newTarget().Expired(lease.Key{"leadership", "model", "twin"})
   254  	c.Assert(logWriter.Log(), jc.LogMatches, []string{
   255  		`couldn't expire lease "model:leadership#twin#" in db: oops!`,
   256  	})
   257  	s.assertLogMatches(c,
   258  		`expired "model:leadership#twin#"`,
   259  		`couldn't expire lease "model:leadership#twin#" in db: oops!`,
   260  	)
   261  }
   262  
   263  func (s *targetSuite) TestTrapdoorAttempt0(c *gc.C) {
   264  	trapdoor := raftlease.MakeTrapdoorFunc(s.mongo, collection)(lease.Key{"ns", "model", "landfall"}, "roy")
   265  	var result []txn.Op
   266  	err := trapdoor(0, &result)
   267  	c.Assert(err, jc.ErrorIsNil)
   268  	c.Assert(result, gc.DeepEquals, []txn.Op{{
   269  		C:      collection,
   270  		Id:     "model:ns#landfall#",
   271  		Assert: bson.M{"holder": "roy"},
   272  	}})
   273  	var bad int
   274  	c.Assert(trapdoor(0, &bad), gc.ErrorMatches, `expected \*\[\]txn\.Op; \*int not valid`)
   275  }
   276  
   277  func (s *targetSuite) TestTrapdoorAttempt1NoHolderInDB(c *gc.C) {
   278  	key := lease.Key{"ns", "model", "landfall"}
   279  	trapdoor := raftlease.MakeTrapdoorFunc(s.mongo, collection)(key, "roy")
   280  	var result []txn.Op
   281  	err := trapdoor(1, &result)
   282  	c.Assert(err, jc.ErrorIsNil)
   283  	c.Assert(result, gc.DeepEquals, []txn.Op{{
   284  		C:      collection,
   285  		Id:     "model:ns#landfall#",
   286  		Assert: bson.M{"holder": "roy"},
   287  	}})
   288  	// It also updated the database to make the holder roy.
   289  	c.Assert(s.getRows(c), gc.DeepEquals, []bson.M{{
   290  		"_id":        "model:ns#landfall#",
   291  		"namespace":  "ns",
   292  		"model-uuid": "model",
   293  		"lease":      "landfall",
   294  		"holder":     "roy",
   295  	}})
   296  }
   297  
   298  func (s *targetSuite) TestTrapdoorAttempt1DifferentHolderInDB(c *gc.C) {
   299  	err := s.db.C(collection).Insert(
   300  		bson.M{
   301  			"_id":        "model:ns#landfall#",
   302  			"namespace":  "ns",
   303  			"model-uuid": "model",
   304  			"lease":      "landfall",
   305  			"holder":     "george",
   306  		},
   307  	)
   308  	c.Assert(err, jc.ErrorIsNil)
   309  	key := lease.Key{"ns", "model", "landfall"}
   310  	trapdoor := raftlease.MakeTrapdoorFunc(s.mongo, collection)(key, "roy")
   311  	var result []txn.Op
   312  	err = trapdoor(1, &result)
   313  	c.Assert(err, jc.ErrorIsNil)
   314  	c.Assert(result, gc.DeepEquals, []txn.Op{{
   315  		C:      collection,
   316  		Id:     "model:ns#landfall#",
   317  		Assert: bson.M{"holder": "roy"},
   318  	}})
   319  	// It also updated the database to make the holder roy.
   320  	c.Assert(s.getRows(c), gc.DeepEquals, []bson.M{{
   321  		"_id":        "model:ns#landfall#",
   322  		"namespace":  "ns",
   323  		"model-uuid": "model",
   324  		"lease":      "landfall",
   325  		"holder":     "roy",
   326  	}})
   327  }
   328  
   329  func (s *targetSuite) TestTrapdoorAttempt1ThisHolderInDB(c *gc.C) {
   330  	err := s.db.C(collection).Insert(
   331  		bson.M{
   332  			"_id":        "model:ns#landfall#",
   333  			"namespace":  "ns",
   334  			"model-uuid": "model",
   335  			"lease":      "landfall",
   336  			"holder":     "roy",
   337  		},
   338  	)
   339  	c.Assert(err, jc.ErrorIsNil)
   340  	trapdoor := raftlease.MakeTrapdoorFunc(s.mongo, collection)(lease.Key{"ns", "model", "landfall"}, "roy")
   341  	var result []txn.Op
   342  	err = trapdoor(0, &result)
   343  	c.Assert(result, gc.DeepEquals, []txn.Op{{
   344  		C:      collection,
   345  		Id:     "model:ns#landfall#",
   346  		Assert: bson.M{"holder": "roy"},
   347  	}})
   348  	// No change in the DB.
   349  	c.Assert(s.getRows(c), gc.DeepEquals, []bson.M{{
   350  		"_id":        "model:ns#landfall#",
   351  		"namespace":  "ns",
   352  		"model-uuid": "model",
   353  		"lease":      "landfall",
   354  		"holder":     "roy",
   355  	}})
   356  }
   357  
   358  func (s *targetSuite) TestLeaseHolders(c *gc.C) {
   359  	err := s.db.C(collection).Insert(
   360  		bson.M{
   361  			"namespace":  "singular",
   362  			"model-uuid": "model",
   363  			"lease":      "cogitans",
   364  			"holder":     "planete",
   365  		},
   366  		bson.M{
   367  			"namespace":  "leadership",
   368  			"model-uuid": "model",
   369  			"lease":      "cogitans",
   370  			"holder":     "res",
   371  		},
   372  		bson.M{
   373  			"namespace":  "leadership",
   374  			"model-uuid": "model",
   375  			"lease":      "twin",
   376  			"holder":     "voiid",
   377  		},
   378  		bson.M{
   379  			"namespace":  "leadership",
   380  			"model-uuid": "model2",
   381  			"lease":      "thorn",
   382  			"holder":     "dornik",
   383  		},
   384  	)
   385  	c.Assert(err, jc.ErrorIsNil)
   386  	holders, err := raftlease.LeaseHolders(s.mongo, collection, "leadership", "model")
   387  	c.Assert(err, jc.ErrorIsNil)
   388  	c.Assert(holders, gc.DeepEquals, map[string]string{
   389  		"cogitans": "res",
   390  		"twin":     "voiid",
   391  	})
   392  }
   393  
   394  // Mongo exposes database operations. It uses a real database -- we can't mock
   395  // mongo out, we need to check it really actually works -- but it's good to
   396  // have the runner accessible for adversarial transaction tests.
   397  type Mongo struct {
   398  	database *mgo.Database
   399  	runner   jujutxn.Runner
   400  	txnErr   error
   401  }
   402  
   403  // NewMongo returns a *Mongo backed by the supplied database.
   404  func NewMongo(database *mgo.Database) *Mongo {
   405  	return &Mongo{
   406  		database: database,
   407  		runner: jujutxn.NewRunner(jujutxn.RunnerParams{
   408  			Database: database,
   409  		}),
   410  	}
   411  }
   412  
   413  // GetCollection is part of the lease.Mongo interface.
   414  func (m *Mongo) GetCollection(name string) (mongo.Collection, func()) {
   415  	return mongo.CollectionFromName(m.database, name)
   416  }
   417  
   418  // RunTransaction is part of the lease.Mongo interface.
   419  func (m *Mongo) RunTransaction(getTxn jujutxn.TransactionSource) error {
   420  	if m.txnErr != nil {
   421  		return m.txnErr
   422  	}
   423  	return m.runner.Run(getTxn)
   424  }