launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/worker/instancepoller/machine_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // TODO(wallyworld) - move to instancepoller_test 5 package instancepoller 6 7 import ( 8 errgo "launchpad.net/errgo/errors" 9 "math" 10 "sync" 11 "sync/atomic" 12 "time" 13 14 gc "launchpad.net/gocheck" 15 16 "launchpad.net/juju-core/errors" 17 "launchpad.net/juju-core/instance" 18 "launchpad.net/juju-core/state" 19 "launchpad.net/juju-core/state/api/params" 20 coretesting "launchpad.net/juju-core/testing" 21 jc "launchpad.net/juju-core/testing/checkers" 22 "launchpad.net/juju-core/testing/testbase" 23 ) 24 25 var _ = gc.Suite(&machineSuite{}) 26 27 type machineSuite struct { 28 testbase.LoggingSuite 29 } 30 31 var testAddrs = []instance.Address{instance.NewAddress("127.0.0.1")} 32 33 func (*machineSuite) TestSetsInstanceInfoInitially(c *gc.C) { 34 context := &testMachineContext{ 35 getInstanceInfo: instanceInfoGetter(c, "i1234", testAddrs, "running", nil), 36 dyingc: make(chan struct{}), 37 } 38 m := &testMachine{ 39 id: "99", 40 instanceId: "i1234", 41 refresh: func() error { return nil }, 42 life: state.Alive, 43 } 44 died := make(chan machine) 45 // Change the poll intervals to be short, so that we know 46 // that we've polled (probably) at least a few times. 47 defer testbase.PatchValue(&ShortPoll, coretesting.ShortWait/10).Restore() 48 defer testbase.PatchValue(&LongPoll, coretesting.ShortWait/10).Restore() 49 50 go runMachine(context, m, nil, died) 51 time.Sleep(coretesting.ShortWait) 52 53 killMachineLoop(c, m, context.dyingc, died) 54 c.Assert(context.killAllErr, gc.Equals, nil) 55 c.Assert(m.addresses, gc.DeepEquals, testAddrs) 56 c.Assert(m.setAddressCount, gc.Equals, 1) 57 c.Assert(m.instStatus, gc.Equals, "running") 58 } 59 60 func (*machineSuite) TestShortPollIntervalWhenNoAddress(c *gc.C) { 61 defer testbase.PatchValue(&ShortPoll, 1*time.Millisecond).Restore() 62 defer testbase.PatchValue(&LongPoll, coretesting.LongWait).Restore() 63 count := countPolls(c, nil, "running", params.StatusStarted) 64 c.Assert(count, jc.GreaterThan, 2) 65 } 66 67 func (*machineSuite) TestShortPollIntervalWhenNoStatus(c *gc.C) { 68 defer testbase.PatchValue(&ShortPoll, 1*time.Millisecond).Restore() 69 defer testbase.PatchValue(&LongPoll, coretesting.LongWait).Restore() 70 count := countPolls(c, testAddrs, "", params.StatusStarted) 71 c.Assert(count, jc.GreaterThan, 2) 72 } 73 74 func (*machineSuite) TestShortPollIntervalWhenNotStarted(c *gc.C) { 75 defer testbase.PatchValue(&ShortPoll, 1*time.Millisecond).Restore() 76 defer testbase.PatchValue(&LongPoll, coretesting.LongWait).Restore() 77 count := countPolls(c, testAddrs, "pending", params.StatusPending) 78 c.Assert(count, jc.GreaterThan, 2) 79 } 80 81 func (*machineSuite) TestShortPollIntervalExponent(c *gc.C) { 82 defer testbase.PatchValue(&ShortPoll, 1*time.Microsecond).Restore() 83 defer testbase.PatchValue(&LongPoll, coretesting.LongWait).Restore() 84 defer testbase.PatchValue(&ShortPollBackoff, 2.0).Restore() 85 86 // With an exponent of 2, the maximum number of polls that can 87 // occur within the given interval ShortWait is log to the base 88 // ShortPollBackoff of ShortWait/ShortPoll, given that sleep will 89 // sleep for at least the requested interval. 90 maxCount := int(math.Log(float64(coretesting.ShortWait)/float64(ShortPoll))/math.Log(ShortPollBackoff) + 1) 91 count := countPolls(c, nil, "", params.StatusStarted) 92 c.Assert(count, jc.GreaterThan, 2) 93 c.Assert(count, jc.LessThan, maxCount) 94 c.Logf("actual count: %v; max %v", count, maxCount) 95 } 96 97 func (*machineSuite) TestLongPollIntervalWhenHasAllInstanceInfo(c *gc.C) { 98 defer testbase.PatchValue(&ShortPoll, coretesting.LongWait).Restore() 99 defer testbase.PatchValue(&LongPoll, 1*time.Millisecond).Restore() 100 count := countPolls(c, testAddrs, "running", params.StatusStarted) 101 c.Assert(count, jc.GreaterThan, 2) 102 } 103 104 // countPolls sets up a machine loop with the given 105 // addresses and status to be returned from getInstanceInfo, 106 // waits for coretesting.ShortWait, and returns the 107 // number of times the instance is polled. 108 func countPolls(c *gc.C, addrs []instance.Address, instStatus string, machineStatus params.Status) int { 109 count := int32(0) 110 getInstanceInfo := func(id instance.Id) (instanceInfo, error) { 111 c.Check(id, gc.Equals, instance.Id("i1234")) 112 atomic.AddInt32(&count, 1) 113 if addrs == nil { 114 return instanceInfo{}, errgo.Newf("no instance addresses available") 115 } 116 return instanceInfo{addrs, instStatus}, nil 117 } 118 context := &testMachineContext{ 119 getInstanceInfo: getInstanceInfo, 120 dyingc: make(chan struct{}), 121 } 122 m := &testMachine{ 123 id: "99", 124 instanceId: "i1234", 125 refresh: func() error { return nil }, 126 addresses: addrs, 127 life: state.Alive, 128 status: machineStatus, 129 } 130 died := make(chan machine) 131 132 go runMachine(context, m, nil, died) 133 134 time.Sleep(coretesting.ShortWait) 135 killMachineLoop(c, m, context.dyingc, died) 136 c.Assert(context.killAllErr, gc.Equals, nil) 137 return int(count) 138 } 139 140 func (*machineSuite) TestSinglePollWhenInstancInfoUnimplemented(c *gc.C) { 141 defer testbase.PatchValue(&ShortPoll, 1*time.Millisecond).Restore() 142 defer testbase.PatchValue(&LongPoll, 1*time.Millisecond).Restore() 143 count := int32(0) 144 getInstanceInfo := func(id instance.Id) (instanceInfo, error) { 145 c.Check(id, gc.Equals, instance.Id("i1234")) 146 atomic.AddInt32(&count, 1) 147 return instanceInfo{}, errors.NewNotImplementedError("instance address") 148 } 149 context := &testMachineContext{ 150 getInstanceInfo: getInstanceInfo, 151 dyingc: make(chan struct{}), 152 } 153 m := &testMachine{ 154 id: "99", 155 instanceId: "i1234", 156 refresh: func() error { return nil }, 157 life: state.Alive, 158 } 159 died := make(chan machine) 160 161 go runMachine(context, m, nil, died) 162 163 time.Sleep(coretesting.ShortWait) 164 killMachineLoop(c, m, context.dyingc, died) 165 c.Assert(context.killAllErr, gc.Equals, nil) 166 c.Assert(count, gc.Equals, int32(1)) 167 } 168 169 func (*machineSuite) TestChangedRefreshes(c *gc.C) { 170 context := &testMachineContext{ 171 getInstanceInfo: instanceInfoGetter(c, "i1234", testAddrs, "running", nil), 172 dyingc: make(chan struct{}), 173 } 174 refreshc := make(chan struct{}) 175 m := &testMachine{ 176 id: "99", 177 instanceId: "i1234", 178 refresh: func() error { 179 refreshc <- struct{}{} 180 return nil 181 }, 182 addresses: testAddrs, 183 life: state.Dead, 184 } 185 died := make(chan machine) 186 changed := make(chan struct{}) 187 go runMachine(context, m, changed, died) 188 select { 189 case <-died: 190 c.Fatalf("machine died prematurely") 191 case <-time.After(coretesting.ShortWait): 192 } 193 194 // Notify the machine that it has changed; it should 195 // refresh, and publish the fact that it no longer has 196 // an address. 197 changed <- struct{}{} 198 199 select { 200 case <-refreshc: 201 case <-time.After(coretesting.LongWait): 202 c.Fatalf("timed out waiting for refresh") 203 } 204 select { 205 case <-died: 206 case <-time.After(coretesting.LongWait): 207 c.Fatalf("expected death after life set to dying") 208 } 209 // The machine addresses should remain the same even 210 // after death. 211 c.Assert(m.addresses, gc.DeepEquals, testAddrs) 212 } 213 214 var terminatingErrorsTests = []struct { 215 about string 216 mutate func(m *testMachine, err error) 217 }{{ 218 about: "set addresses", 219 mutate: func(m *testMachine, err error) { 220 m.setAddressesErr = err 221 }, 222 }, { 223 about: "refresh", 224 mutate: func(m *testMachine, err error) { 225 m.refresh = func() error { 226 return err 227 } 228 }, 229 }, { 230 about: "instance id", 231 mutate: func(m *testMachine, err error) { 232 m.instanceIdErr = err 233 }, 234 }} 235 236 func (*machineSuite) TestTerminatingErrors(c *gc.C) { 237 for i, test := range terminatingErrorsTests { 238 c.Logf("test %d: %s", i, test.about) 239 testTerminatingErrors(c, test.mutate) 240 } 241 } 242 243 // 244 // testTerminatingErrors checks that when a testMachine is 245 // changed with the given mutate function, the machine goroutine 246 // will die having called its context's killAll function with the 247 // given error. The test is cunningly structured so that it in the normal course 248 // of things it will go through all possible places that can return an error. 249 func testTerminatingErrors(c *gc.C, mutate func(m *testMachine, err error)) { 250 context := &testMachineContext{ 251 getInstanceInfo: instanceInfoGetter(c, "i1234", testAddrs, "running", nil), 252 dyingc: make(chan struct{}), 253 } 254 expectErr := errgo.New("a very unusual error") 255 m := &testMachine{ 256 id: "99", 257 instanceId: "i1234", 258 refresh: func() error { return nil }, 259 life: state.Alive, 260 } 261 mutate(m, expectErr) 262 died := make(chan machine) 263 changed := make(chan struct{}, 1) 264 go runMachine(context, m, changed, died) 265 changed <- struct{}{} 266 select { 267 case <-died: 268 case <-time.After(coretesting.LongWait): 269 c.Fatalf("timed out waiting for machine to die") 270 } 271 c.Assert(context.killAllErr, gc.ErrorMatches, ".*"+expectErr.Error()) 272 } 273 274 func killMachineLoop(c *gc.C, m machine, dying chan struct{}, died <-chan machine) { 275 close(dying) 276 select { 277 case diedm := <-died: 278 c.Assert(diedm, gc.Equals, m) 279 case <-time.After(coretesting.LongWait): 280 c.Fatalf("updater did not die after dying channel was closed") 281 } 282 } 283 284 func instanceInfoGetter( 285 c *gc.C, expectId instance.Id, addrs []instance.Address, 286 status string, err error) func(id instance.Id) (instanceInfo, error) { 287 288 return func(id instance.Id) (instanceInfo, error) { 289 c.Check(id, gc.Equals, expectId) 290 return instanceInfo{addrs, status}, err 291 } 292 } 293 294 type testMachineContext struct { 295 killAllErr error 296 getInstanceInfo func(instance.Id) (instanceInfo, error) 297 dyingc chan struct{} 298 } 299 300 func (context *testMachineContext) killAll(err error) { 301 if err == nil { 302 panic("killAll with nil error") 303 } 304 context.killAllErr = err 305 } 306 307 func (context *testMachineContext) instanceInfo(id instance.Id) (instanceInfo, error) { 308 return context.getInstanceInfo(id) 309 } 310 311 func (context *testMachineContext) dying() <-chan struct{} { 312 return context.dyingc 313 } 314 315 type testMachine struct { 316 instanceId instance.Id 317 instanceIdErr error 318 id string 319 instStatus string 320 status params.Status 321 refresh func() error 322 setAddressesErr error 323 // mu protects the following fields. 324 mu sync.Mutex 325 life state.Life 326 addresses []instance.Address 327 setAddressCount int 328 } 329 330 func (m *testMachine) Id() string { 331 if m.id == "" { 332 panic("Id called but not set") 333 } 334 return m.id 335 } 336 337 func (m *testMachine) Addresses() []instance.Address { 338 m.mu.Lock() 339 defer m.mu.Unlock() 340 return m.addresses 341 } 342 343 func (m *testMachine) InstanceId() (instance.Id, error) { 344 return m.instanceId, m.instanceIdErr 345 } 346 347 func (m *testMachine) Status() (status params.Status, info string, data params.StatusData, err error) { 348 return m.status, "", nil, nil 349 } 350 351 func (m *testMachine) InstanceStatus() (string, error) { 352 m.mu.Lock() 353 defer m.mu.Unlock() 354 return m.instStatus, nil 355 } 356 357 func (m *testMachine) SetInstanceStatus(status string) error { 358 m.mu.Lock() 359 defer m.mu.Unlock() 360 m.instStatus = status 361 return nil 362 } 363 364 func (m *testMachine) SetAddresses(addrs []instance.Address) error { 365 if m.setAddressesErr != nil { 366 return m.setAddressesErr 367 } 368 m.mu.Lock() 369 defer m.mu.Unlock() 370 m.addresses = append(m.addresses[:0], addrs...) 371 m.setAddressCount++ 372 return nil 373 } 374 375 func (m *testMachine) String() string { 376 return m.id 377 } 378 379 func (m *testMachine) Refresh() error { 380 return m.refresh() 381 } 382 383 func (m *testMachine) Life() state.Life { 384 m.mu.Lock() 385 defer m.mu.Unlock() 386 return m.life 387 } 388 389 func (m *testMachine) setLife(life state.Life) { 390 m.mu.Lock() 391 defer m.mu.Unlock() 392 m.life = life 393 }