launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/replicaset/replicaset_test.go (about) 1 package replicaset 2 3 import ( 4 "fmt" 5 "testing" 6 "time" 7 8 gc "launchpad.net/gocheck" 9 10 "labix.org/v2/mgo" 11 12 coretesting "launchpad.net/juju-core/testing" 13 "launchpad.net/juju-core/utils" 14 ) 15 16 var ( 17 name = "juju" 18 root *coretesting.MgoInstance 19 ) 20 21 func TestPackage(t *testing.T) { 22 gc.TestingT(t) 23 } 24 25 func newServer() (*coretesting.MgoInstance, error) { 26 inst := &coretesting.MgoInstance{Params: []string{"--replSet", name}} 27 28 err := inst.Start(true) 29 if err != nil { 30 return nil, fmt.Errorf("Error starting mongo server: %s", err.Error()) 31 } 32 33 // by dialing right now, we'll wait until it's running 34 strategy := utils.AttemptStrategy{Total: time.Second * 5, Delay: time.Millisecond * 100} 35 attempt := strategy.Start() 36 for attempt.Next() { 37 var session *mgo.Session 38 session, err = inst.DialDirect() 39 if err != nil { 40 err = fmt.Errorf("Error dialing mongo server %q: %s", inst.Addr(), err.Error()) 41 } else { 42 session.SetMode(mgo.Monotonic, true) 43 err = session.Ping() 44 if err != nil { 45 err = fmt.Errorf("Error pinging mongo server %q: %s", inst.Addr(), err.Error()) 46 } 47 session.Close() 48 } 49 if err == nil || !attempt.HasNext() { 50 break 51 } 52 } 53 return inst, err 54 } 55 56 type MongoSuite struct{} 57 58 var _ = gc.Suite(&MongoSuite{}) 59 60 func (s *MongoSuite) SetUpSuite(c *gc.C) { 61 var err error 62 // do all this stuff here, since we don't want to have to redo it for each test 63 root, err = newServer() 64 if err != nil { 65 c.Fatalf("Got error from Start of root server: %s", err.Error()) 66 } 67 // note, this is an actual test around Initiate, but again, I don't want to 68 // have to redo it, so I just do it once. 69 dialAndTestInitiate(c) 70 } 71 72 func (s *MongoSuite) TearDownTest(c *gc.C) { 73 // remove all secondaries from the replicaset on test teardown 74 session, err := root.DialDirect() 75 if err != nil { 76 c.Logf("Failed to dial root during test cleanup: %v", err) 77 return 78 } 79 defer session.Close() 80 mems, err := CurrentMembers(session) 81 if err != nil { 82 c.Logf("Failed to get list of memners during test cleanup: %v", err) 83 return 84 } 85 86 addrs := []string{} 87 for _, m := range mems { 88 if root.Addr() != m.Address { 89 addrs = append(addrs, m.Address) 90 } 91 } 92 if err = Remove(session, addrs...); err != nil { 93 c.Logf("Error removing secondaries: %v", err) 94 } 95 } 96 97 func dialAndTestInitiate(c *gc.C) { 98 session := root.MustDialDirect() 99 defer session.Close() 100 101 err := Initiate(session, root.Addr(), name) 102 c.Assert(err, gc.IsNil) 103 104 // Ids start at 1 for us, so we can differentiate between set and unset 105 expectedMembers := []Member{Member{Id: 1, Address: root.Addr()}} 106 107 // need to set mode to strong so that we wait for the write to succeed 108 // before reading and thus ensure that we're getting consistent reads. 109 session.SetMode(mgo.Strong, false) 110 111 mems, err := CurrentMembers(session) 112 c.Assert(err, gc.IsNil) 113 c.Assert(mems, gc.DeepEquals, expectedMembers) 114 115 // now add some data so we get a more real-life test 116 loadData(session, c) 117 } 118 119 func loadData(session *mgo.Session, c *gc.C) { 120 type foo struct { 121 Name string 122 Address string 123 Count int 124 } 125 126 for col := 0; col < 10; col++ { 127 foos := make([]foo, 10000) 128 for n := range foos { 129 foos[n] = foo{ 130 Name: fmt.Sprintf("name_%d_%d", col, n), 131 Address: fmt.Sprintf("address_%d_%d", col, n), 132 Count: n * (col + 1), 133 } 134 } 135 136 err := session.DB("testing").C(fmt.Sprintf("data%d", col)).Insert(foos) 137 c.Assert(err, gc.IsNil) 138 } 139 } 140 141 func (s *MongoSuite) TearDownSuite(c *gc.C) { 142 root.Destroy() 143 } 144 145 func (s *MongoSuite) TestAddRemoveSet(c *gc.C) { 146 session := root.MustDial() 147 defer session.Close() 148 149 members := make([]Member, 0, 5) 150 151 // Add should be idempotent, so re-adding root here shouldn't result in 152 // two copies of root in the replica set 153 members = append(members, Member{Address: root.Addr()}) 154 155 instances := make([]*coretesting.MgoInstance, 0, 5) 156 instances = append(instances, root) 157 158 for x := 0; x < 4; x++ { 159 inst, err := newServer() 160 c.Assert(err, gc.IsNil) 161 instances = append(instances, inst) 162 defer inst.Destroy() 163 defer Remove(session, inst.Addr()) 164 165 key := fmt.Sprintf("key%d", x) 166 val := fmt.Sprintf("val%d", x) 167 168 tags := map[string]string{key: val} 169 170 members = append(members, Member{Address: inst.Addr(), Tags: tags}) 171 } 172 173 var err error 174 175 strategy := utils.AttemptStrategy{Total: time.Second * 30, Delay: time.Millisecond * 100} 176 attempt := strategy.Start() 177 for attempt.Next() { 178 err = Add(session, members...) 179 if err == nil || !attempt.HasNext() { 180 break 181 } 182 } 183 c.Assert(err, gc.IsNil) 184 185 expectedMembers := make([]Member, len(members)) 186 for x, m := range members { 187 // Ids should start at 1 (for the root) and go up 188 m.Id = x + 1 189 expectedMembers[x] = m 190 } 191 192 var cfg *Config 193 attempt = strategy.Start() 194 for attempt.Next() { 195 cfg, err = CurrentConfig(session) 196 if err == nil || !attempt.HasNext() { 197 break 198 } 199 } 200 201 c.Assert(err, gc.IsNil) 202 c.Assert(cfg.Name, gc.Equals, name) 203 204 // 2 since we already changed it once 205 c.Assert(cfg.Version, gc.Equals, 2) 206 207 mems := cfg.Members 208 209 c.Assert(mems, gc.DeepEquals, expectedMembers) 210 211 // Now remove the last two Members 212 attempt = strategy.Start() 213 for attempt.Next() { 214 err = Remove(session, members[3].Address, members[4].Address) 215 if err == nil || !attempt.HasNext() { 216 break 217 } 218 } 219 c.Assert(err, gc.IsNil) 220 221 expectedMembers = expectedMembers[0:3] 222 223 mems, err = CurrentMembers(session) 224 c.Assert(err, gc.IsNil) 225 c.Assert(mems, gc.DeepEquals, expectedMembers) 226 227 // now let's mix it up and set the new members to a mix of the previous 228 // plus the new arbiter 229 mems = []Member{members[3], mems[2], mems[0], members[4]} 230 231 attempt = strategy.Start() 232 for attempt.Next() { 233 err = Set(session, mems) 234 if err == nil || !attempt.HasNext() { 235 break 236 } 237 } 238 239 c.Assert(err, gc.IsNil) 240 241 attempt = strategy.Start() 242 for attempt.Next() { 243 // can dial whichever replica address here, mongo will figure it out 244 session = instances[0].MustDialDirect() 245 err = session.Ping() 246 if err == nil || !attempt.HasNext() { 247 break 248 } 249 } 250 c.Assert(err, gc.IsNil) 251 252 expectedMembers = []Member{members[3], expectedMembers[2], expectedMembers[0], members[4]} 253 254 // any new members will get an id of max(other_ids...)+1 255 expectedMembers[0].Id = 4 256 expectedMembers[3].Id = 5 257 258 attempt = strategy.Start() 259 for attempt.Next() { 260 mems, err = CurrentMembers(session) 261 if err == nil || !attempt.HasNext() { 262 break 263 } 264 } 265 c.Assert(err, gc.IsNil) 266 c.Assert(mems, gc.DeepEquals, expectedMembers) 267 } 268 269 func (s *MongoSuite) TestIsMaster(c *gc.C) { 270 session := root.MustDial() 271 defer session.Close() 272 273 expected := IsMasterResults{ 274 // The following fields hold information about the specific mongodb node. 275 IsMaster: true, 276 Secondary: false, 277 Arbiter: false, 278 Address: root.Addr(), 279 LocalTime: time.Time{}, 280 281 // The following fields hold information about the replica set. 282 ReplicaSetName: name, 283 Addresses: []string{root.Addr()}, 284 Arbiters: nil, 285 PrimaryAddress: root.Addr(), 286 } 287 288 res, err := IsMaster(session) 289 c.Assert(err, gc.IsNil) 290 c.Check(closeEnough(res.LocalTime, time.Now()), gc.Equals, true) 291 res.LocalTime = time.Time{} 292 c.Check(*res, gc.DeepEquals, expected) 293 } 294 295 func (s *MongoSuite) TestCurrentStatus(c *gc.C) { 296 session := root.MustDial() 297 defer session.Close() 298 299 inst1, err := newServer() 300 c.Assert(err, gc.IsNil) 301 defer inst1.Destroy() 302 defer Remove(session, inst1.Addr()) 303 304 inst2, err := newServer() 305 c.Assert(err, gc.IsNil) 306 defer inst2.Destroy() 307 defer Remove(session, inst2.Addr()) 308 309 strategy := utils.AttemptStrategy{Total: time.Second * 30, Delay: time.Millisecond * 100} 310 attempt := strategy.Start() 311 for attempt.Next() { 312 err = Add(session, Member{Address: inst1.Addr()}, Member{Address: inst2.Addr()}) 313 if err == nil || !attempt.HasNext() { 314 break 315 } 316 } 317 c.Assert(err, gc.IsNil) 318 319 expected := &Status{ 320 Name: name, 321 Members: []MemberStatus{{ 322 Id: 1, 323 Address: root.Addr(), 324 Self: true, 325 ErrMsg: "", 326 Healthy: true, 327 State: PrimaryState, 328 }, { 329 Id: 2, 330 Address: inst1.Addr(), 331 Self: false, 332 ErrMsg: "", 333 Healthy: true, 334 State: SecondaryState, 335 }, { 336 Id: 3, 337 Address: inst2.Addr(), 338 Self: false, 339 ErrMsg: "", 340 Healthy: true, 341 State: SecondaryState, 342 }}, 343 } 344 345 strategy.Total = time.Second * 60 346 attempt = strategy.Start() 347 var res *Status 348 for attempt.Next() { 349 var err error 350 res, err = CurrentStatus(session) 351 if err != nil { 352 if !attempt.HasNext() { 353 c.Errorf("Couldn't get status before timeout, got err: %v", err) 354 return 355 } else { 356 // try again 357 continue 358 } 359 } 360 361 if res.Members[0].State == PrimaryState && 362 res.Members[1].State == SecondaryState && 363 res.Members[2].State == SecondaryState { 364 break 365 } 366 if !attempt.HasNext() { 367 c.Errorf("Servers did not get into final state before timeout. Status: %#v", res) 368 return 369 } 370 } 371 372 for x, _ := range res.Members { 373 // non-empty uptime and ping 374 c.Check(res.Members[x].Uptime, gc.Not(gc.Equals), 0) 375 376 // ping is always going to be zero since we're on localhost 377 // so we can't really test it right now 378 379 // now overwrite Uptime so it won't throw off DeepEquals 380 res.Members[x].Uptime = 0 381 } 382 c.Check(res, gc.DeepEquals, expected) 383 } 384 385 func closeEnough(expected, obtained time.Time) bool { 386 t := obtained.Sub(expected) 387 return (-500*time.Millisecond) < t && t < (500*time.Millisecond) 388 }