github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/worker/peergrouper/worker_test.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package peergrouper 5 6 import ( 7 "errors" 8 "fmt" 9 "time" 10 11 gc "launchpad.net/gocheck" 12 13 "launchpad.net/juju-core/juju/testing" 14 coretesting "launchpad.net/juju-core/testing" 15 jc "launchpad.net/juju-core/testing/checkers" 16 "launchpad.net/juju-core/testing/testbase" 17 "launchpad.net/juju-core/utils/voyeur" 18 "launchpad.net/juju-core/worker" 19 ) 20 21 type workerJujuConnSuite struct { 22 testing.JujuConnSuite 23 } 24 25 var _ = gc.Suite(&workerJujuConnSuite{}) 26 27 func (s *workerJujuConnSuite) TestStartStop(c *gc.C) { 28 w, err := New(s.State) 29 c.Assert(err, gc.IsNil) 30 err = worker.Stop(w) 31 c.Assert(err, gc.IsNil) 32 } 33 34 type workerSuite struct { 35 testbase.LoggingSuite 36 } 37 38 var _ = gc.Suite(&workerSuite{}) 39 40 func (s *workerSuite) SetUpTest(c *gc.C) { 41 s.LoggingSuite.SetUpTest(c) 42 resetErrors() 43 } 44 45 // initState initializes the fake state with a single 46 // replicaset member and numMachines machines 47 // primed to vote. 48 func initState(c *gc.C, st *fakeState, numMachines int) { 49 var ids []string 50 for i := 10; i < 10+numMachines; i++ { 51 id := fmt.Sprint(i) 52 m := st.addMachine(id, true) 53 m.setStateHostPort(fmt.Sprintf("0.1.2.%d:%d", i, mongoPort)) 54 ids = append(ids, id) 55 } 56 st.machine("10").SetHasVote(true) 57 st.setStateServers(ids...) 58 st.session.Set(mkMembers("0v")) 59 st.session.setStatus(mkStatuses("0p")) 60 st.check = checkInvariants 61 } 62 63 func (s *workerSuite) TestSetsAndUpdatesMembers(c *gc.C) { 64 testbase.PatchValue(&pollInterval, 5*time.Millisecond) 65 66 st := newFakeState() 67 initState(c, st, 3) 68 69 memberWatcher := st.session.members.Watch() 70 mustNext(c, memberWatcher) 71 c.Assert(memberWatcher.Value(), jc.DeepEquals, mkMembers("0v")) 72 73 logger.Infof("starting worker") 74 w := newWorker(st) 75 defer func() { 76 c.Check(worker.Stop(w), gc.IsNil) 77 }() 78 79 // Wait for the worker to set the initial members. 80 mustNext(c, memberWatcher) 81 c.Assert(memberWatcher.Value(), jc.DeepEquals, mkMembers("0v 1 2")) 82 83 // Update the status of the new members 84 // and check that they become voting. 85 c.Logf("updating new member status") 86 st.session.setStatus(mkStatuses("0p 1s 2s")) 87 mustNext(c, memberWatcher) 88 c.Assert(memberWatcher.Value(), jc.DeepEquals, mkMembers("0v 1v 2v")) 89 90 c.Logf("adding another machine") 91 // Add another machine. 92 m13 := st.addMachine("13", false) 93 m13.setStateHostPort(fmt.Sprintf("0.1.2.%d:%d", 13, mongoPort)) 94 st.setStateServers("10", "11", "12", "13") 95 96 c.Logf("waiting for new member to be added") 97 mustNext(c, memberWatcher) 98 c.Assert(memberWatcher.Value(), jc.DeepEquals, mkMembers("0v 1v 2v 3")) 99 100 // Remove vote from an existing member; 101 // and give it to the new machine. 102 // Also set the status of the new machine to 103 // healthy. 104 c.Logf("removing vote from machine 10 and adding it to machine 13") 105 st.machine("10").setWantsVote(false) 106 st.machine("13").setWantsVote(true) 107 108 st.session.setStatus(mkStatuses("0p 1s 2s 3s")) 109 110 // Check that the new machine gets the vote and the 111 // old machine loses it. 112 c.Logf("waiting for vote switch") 113 mustNext(c, memberWatcher) 114 c.Assert(memberWatcher.Value(), jc.DeepEquals, mkMembers("0 1v 2v 3v")) 115 116 c.Logf("removing old machine") 117 // Remove the old machine. 118 st.removeMachine("10") 119 st.setStateServers("11", "12", "13") 120 121 // Check that it's removed from the members. 122 c.Logf("waiting for removal") 123 mustNext(c, memberWatcher) 124 c.Assert(memberWatcher.Value(), jc.DeepEquals, mkMembers("1v 2v 3v")) 125 } 126 127 func (s *workerSuite) TestAddressChange(c *gc.C) { 128 st := newFakeState() 129 initState(c, st, 3) 130 131 memberWatcher := st.session.members.Watch() 132 mustNext(c, memberWatcher) 133 c.Assert(memberWatcher.Value(), jc.DeepEquals, mkMembers("0v")) 134 135 logger.Infof("starting worker") 136 w := newWorker(st) 137 defer func() { 138 c.Check(worker.Stop(w), gc.IsNil) 139 }() 140 141 // Wait for the worker to set the initial members. 142 mustNext(c, memberWatcher) 143 c.Assert(memberWatcher.Value(), jc.DeepEquals, mkMembers("0v 1 2")) 144 145 // Change an address and wait for it to be changed in the 146 // members. 147 st.machine("11").setStateHostPort("0.1.99.99:9876") 148 149 mustNext(c, memberWatcher) 150 expectMembers := mkMembers("0v 1 2") 151 expectMembers[1].Address = "0.1.99.99:9876" 152 c.Assert(memberWatcher.Value(), jc.DeepEquals, expectMembers) 153 } 154 155 var fatalErrorsTests = []struct { 156 errPattern string 157 err error 158 expectErr string 159 }{{ 160 errPattern: "State.StateServerInfo", 161 expectErr: "cannot get state server info: sample", 162 }, { 163 errPattern: "Machine.SetHasVote 11 true", 164 expectErr: `cannot set voting status of "11" to true: sample`, 165 }, { 166 errPattern: "Session.CurrentStatus", 167 expectErr: "cannot get replica set status: sample", 168 }, { 169 errPattern: "Session.CurrentMembers", 170 expectErr: "cannot get replica set members: sample", 171 }, { 172 errPattern: "State.Machine *", 173 expectErr: `cannot get machine "10": sample`, 174 }} 175 176 func (s *workerSuite) TestFatalErrors(c *gc.C) { 177 testbase.PatchValue(&pollInterval, 5*time.Millisecond) 178 for i, test := range fatalErrorsTests { 179 c.Logf("test %d: %s -> %s", i, test.errPattern, test.expectErr) 180 resetErrors() 181 st := newFakeState() 182 st.session.InstantlyReady = true 183 initState(c, st, 3) 184 setErrorFor(test.errPattern, errors.New("sample")) 185 w := newWorker(st) 186 done := make(chan error) 187 go func() { 188 done <- w.Wait() 189 }() 190 select { 191 case err := <-done: 192 c.Assert(err, gc.ErrorMatches, test.expectErr) 193 case <-time.After(coretesting.LongWait): 194 c.Fatalf("timed out waiting for error") 195 } 196 } 197 } 198 199 func (s *workerSuite) TestSetMembersErrorIsNotFatal(c *gc.C) { 200 st := newFakeState() 201 initState(c, st, 3) 202 st.session.setStatus(mkStatuses("0p 1s 2s")) 203 var isSet voyeur.Value 204 count := 0 205 setErrorFuncFor("Session.Set", func() error { 206 isSet.Set(count) 207 count++ 208 return errors.New("sample") 209 }) 210 testbase.PatchValue(&retryInterval, 5*time.Millisecond) 211 w := newWorker(st) 212 defer func() { 213 c.Check(worker.Stop(w), gc.IsNil) 214 }() 215 isSetWatcher := isSet.Watch() 216 n0, _ := mustNext(c, isSetWatcher) 217 // The worker should not retry more than every 218 // retryInterval. 219 time.Sleep(retryInterval * 10) 220 n1, _ := mustNext(c, isSetWatcher) 221 c.Assert(n0.(int)-n0.(int), jc.LessThan, 11) 222 c.Assert(n1, jc.GreaterThan, n0) 223 } 224 225 func mustNext(c *gc.C, w *voyeur.Watcher) (val interface{}, ok bool) { 226 done := make(chan struct{}) 227 go func() { 228 c.Logf("mustNext %p", w) 229 ok = w.Next() 230 val = w.Value() 231 c.Logf("mustNext done %p, ok %v", w, ok) 232 done <- struct{}{} 233 }() 234 select { 235 case <-done: 236 return 237 case <-time.After(coretesting.LongWait): 238 c.Fatalf("timed out waiting for value to be set") 239 } 240 panic("unreachable") 241 }