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 }