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