github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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 "strings" 12 "sync" 13 "sync/atomic" 14 "time" 15 16 gitjujutesting "github.com/juju/testing" 17 jc "github.com/juju/testing/checkers" 18 "github.com/juju/utils/clock" 19 gc "gopkg.in/check.v1" 20 "gopkg.in/juju/names.v2" 21 22 "github.com/juju/juju/apiserver/params" 23 "github.com/juju/juju/instance" 24 "github.com/juju/juju/network" 25 "github.com/juju/juju/status" 26 coretesting "github.com/juju/juju/testing" 27 ) 28 29 var _ = gc.Suite(&machineSuite{}) 30 31 type machineSuite struct { 32 coretesting.BaseSuite 33 } 34 35 var testAddrs = network.NewAddresses("127.0.0.1") 36 37 func (s *machineSuite) TestSetsInstanceInfoInitially(c *gc.C) { 38 context := &testMachineContext{ 39 getInstanceInfo: instanceInfoGetter(c, "i1234", testAddrs, "running", nil), 40 dyingc: make(chan struct{}), 41 } 42 m := &testMachine{ 43 tag: names.NewMachineTag("99"), 44 instanceId: "i1234", 45 refresh: func() error { return nil }, 46 life: params.Alive, 47 } 48 died := make(chan machine) 49 // Change the poll intervals to be short, so that we know 50 // that we've polled (probably) at least a few times. 51 s.PatchValue(&ShortPoll, coretesting.ShortWait/10) 52 s.PatchValue(&LongPoll, coretesting.ShortWait/10) 53 54 go runMachine(context, m, nil, died, clock.WallClock) 55 time.Sleep(coretesting.ShortWait) 56 57 killMachineLoop(c, m, context.dyingc, died) 58 c.Assert(context.killErr, gc.Equals, nil) 59 c.Assert(m.addresses, gc.DeepEquals, testAddrs) 60 c.Assert(m.setAddressCount, gc.Equals, 1) 61 c.Assert(m.instStatusInfo, gc.Equals, "running") 62 } 63 64 func (s *machineSuite) TestSetsInstanceInfoDeadMachineInitially(c *gc.C) { 65 context := &testMachineContext{ 66 getInstanceInfo: instanceInfoGetter(c, "i1234", testAddrs, "deleting", nil), 67 dyingc: make(chan struct{}), 68 } 69 m := &testMachine{ 70 tag: names.NewMachineTag("99"), 71 instanceId: "i1234", 72 refresh: func() error { return nil }, 73 life: params.Dead, 74 } 75 died := make(chan machine) 76 // Change the poll intervals to be short, so that we know 77 // that we've polled (probably) at least a few times. 78 s.PatchValue(&ShortPoll, coretesting.ShortWait/10) 79 s.PatchValue(&LongPoll, coretesting.ShortWait/10) 80 81 go runMachine(context, m, nil, died, clock.WallClock) 82 time.Sleep(coretesting.ShortWait) 83 84 killMachineLoop(c, m, context.dyingc, died) 85 c.Assert(context.killErr, gc.Equals, nil) 86 c.Assert(m.setAddressCount, gc.Equals, 0) 87 c.Assert(m.instStatusInfo, gc.Equals, "deleting") 88 } 89 90 func (s *machineSuite) TestShortPollIntervalWhenNoAddress(c *gc.C) { 91 s.PatchValue(&ShortPoll, 1*time.Millisecond) 92 s.PatchValue(&LongPoll, coretesting.LongWait) 93 count := countPolls(c, nil, "i1234", "running", status.Started) 94 c.Assert(count, jc.GreaterThan, 2) 95 } 96 97 func (s *machineSuite) TestShortPollIntervalWhenNoStatus(c *gc.C) { 98 s.PatchValue(&ShortPoll, 1*time.Millisecond) 99 s.PatchValue(&LongPoll, coretesting.LongWait) 100 count := countPolls(c, testAddrs, "i1234", "", status.Status("")) 101 c.Assert(count, jc.GreaterThan, 2) 102 } 103 104 func (s *machineSuite) TestShortPollIntervalWhenNotStarted(c *gc.C) { 105 s.PatchValue(&ShortPoll, 1*time.Millisecond) 106 s.PatchValue(&LongPoll, coretesting.LongWait) 107 count := countPolls(c, testAddrs, "i1234", "pending", status.Pending) 108 c.Assert(count, jc.GreaterThan, 2) 109 } 110 111 func (s *machineSuite) TestShortPollIntervalWhenNotProvisioned(c *gc.C) { 112 s.PatchValue(&ShortPoll, 1*time.Millisecond) 113 s.PatchValue(&LongPoll, coretesting.LongWait) 114 count := countPolls(c, testAddrs, "", "pending", status.Pending) 115 c.Assert(count, gc.Equals, 0) 116 } 117 118 func (s *machineSuite) TestNoPollWhenNotProvisioned(c *gc.C) { 119 s.PatchValue(&ShortPoll, 1*time.Millisecond) 120 s.PatchValue(&LongPoll, coretesting.LongWait) 121 122 polled := make(chan struct{}, 1) 123 getInstanceInfo := func(id instance.Id) (instanceInfo, error) { 124 select { 125 case polled <- struct{}{}: 126 default: 127 } 128 return instanceInfo{testAddrs, instance.InstanceStatus{Status: status.Unknown, Message: "pending"}}, nil 129 } 130 context := &testMachineContext{ 131 getInstanceInfo: getInstanceInfo, 132 dyingc: make(chan struct{}), 133 } 134 m := &testMachine{ 135 tag: names.NewMachineTag("99"), 136 instanceId: instance.Id(""), 137 refresh: func() error { return nil }, 138 addresses: testAddrs, 139 life: params.Alive, 140 status: "pending", 141 } 142 died := make(chan machine) 143 144 clock := gitjujutesting.NewClock(time.Time{}) 145 changed := make(chan struct{}) 146 go runMachine(context, m, changed, died, clock) 147 148 expectPoll := func() { 149 // worker should be waiting for ShortPoll 150 select { 151 case <-clock.Alarms(): 152 case <-time.After(coretesting.LongWait): 153 c.Fatalf("expected time-based polling") 154 } 155 clock.Advance(ShortPoll) 156 } 157 158 expectPoll() 159 expectPoll() 160 select { 161 case <-polled: 162 c.Fatalf("unexpected instance poll") 163 case <-time.After(coretesting.ShortWait): 164 } 165 166 m.setInstanceId("inst-ance") 167 expectPoll() 168 select { 169 case <-polled: 170 case <-time.After(coretesting.LongWait): 171 c.Fatalf("expected instance poll") 172 } 173 174 killMachineLoop(c, m, context.dyingc, died) 175 c.Assert(context.killErr, gc.Equals, nil) 176 } 177 178 func (s *machineSuite) TestShortPollIntervalExponent(c *gc.C) { 179 s.PatchValue(&ShortPoll, 1*time.Microsecond) 180 s.PatchValue(&LongPoll, coretesting.LongWait) 181 s.PatchValue(&ShortPollBackoff, 2.0) 182 183 // With an exponent of 2, the maximum number of polls that can 184 // occur within the given interval ShortWait is log to the base 185 // ShortPollBackoff of ShortWait/ShortPoll, given that sleep will 186 // sleep for at least the requested interval. 187 maxCount := int(math.Log(float64(coretesting.ShortWait)/float64(ShortPoll))/math.Log(ShortPollBackoff) + 1) 188 count := countPolls(c, nil, "i1234", "", status.Started) 189 c.Assert(count, jc.GreaterThan, 2) 190 c.Assert(count, jc.LessThan, maxCount) 191 c.Logf("actual count: %v; max %v", count, maxCount) 192 } 193 194 func (s *machineSuite) TestLongPollIntervalWhenHasAllInstanceInfo(c *gc.C) { 195 s.PatchValue(&ShortPoll, coretesting.LongWait) 196 s.PatchValue(&LongPoll, 1*time.Millisecond) 197 count := countPolls(c, testAddrs, "i1234", "running", status.Started) 198 c.Assert(count, jc.GreaterThan, 2) 199 } 200 201 // countPolls sets up a machine loop with the given 202 // addresses and status to be returned from getInstanceInfo, 203 // waits for coretesting.ShortWait, and returns the 204 // number of times the instance is polled. 205 func countPolls(c *gc.C, addrs []network.Address, instId, instStatus string, machineStatus status.Status) int { 206 count := int32(0) 207 getInstanceInfo := func(id instance.Id) (instanceInfo, error) { 208 c.Check(string(id), gc.Equals, instId) 209 atomic.AddInt32(&count, 1) 210 if addrs == nil { 211 return instanceInfo{}, fmt.Errorf("no instance addresses available") 212 } 213 return instanceInfo{addrs, instance.InstanceStatus{Status: status.Unknown, Message: instStatus}}, nil 214 } 215 context := &testMachineContext{ 216 getInstanceInfo: getInstanceInfo, 217 dyingc: make(chan struct{}), 218 } 219 m := &testMachine{ 220 tag: names.NewMachineTag("99"), 221 instanceId: instance.Id(instId), 222 refresh: func() error { return nil }, 223 addresses: addrs, 224 life: params.Alive, 225 status: machineStatus, 226 } 227 died := make(chan machine) 228 229 go runMachine(context, m, nil, died, clock.WallClock) 230 231 time.Sleep(coretesting.ShortWait) 232 killMachineLoop(c, m, context.dyingc, died) 233 c.Assert(context.killErr, gc.Equals, nil) 234 return int(count) 235 } 236 237 func (*machineSuite) TestChangedRefreshes(c *gc.C) { 238 context := &testMachineContext{ 239 getInstanceInfo: instanceInfoGetter(c, "i1234", testAddrs, "running", nil), 240 dyingc: make(chan struct{}), 241 } 242 refreshc := make(chan struct{}) 243 m := &testMachine{ 244 tag: names.NewMachineTag("99"), 245 instanceId: "i1234", 246 refresh: func() error { 247 refreshc <- struct{}{} 248 return nil 249 }, 250 addresses: testAddrs, 251 life: params.Dead, 252 } 253 died := make(chan machine) 254 changed := make(chan struct{}) 255 go runMachine(context, m, changed, died, clock.WallClock) 256 select { 257 case <-died: 258 c.Fatalf("machine died prematurely") 259 case <-time.After(coretesting.ShortWait): 260 } 261 262 // Notify the machine that it has changed; it should 263 // refresh, and publish the fact that it no longer has 264 // an address. 265 changed <- struct{}{} 266 267 select { 268 case <-refreshc: 269 case <-time.After(coretesting.LongWait): 270 c.Fatalf("timed out waiting for refresh") 271 } 272 select { 273 case <-died: 274 case <-time.After(coretesting.LongWait): 275 c.Fatalf("expected death after life set to dying") 276 } 277 // The machine addresses should remain the same even 278 // after death. 279 c.Assert(m.addresses, gc.DeepEquals, testAddrs) 280 } 281 282 var terminatingErrorsTests = []struct { 283 about string 284 mutate func(m *testMachine, err error) 285 }{{ 286 about: "set addresses", 287 mutate: func(m *testMachine, err error) { 288 m.setAddressesErr = err 289 }, 290 }, { 291 about: "refresh", 292 mutate: func(m *testMachine, err error) { 293 m.refresh = func() error { 294 return err 295 } 296 }, 297 }, { 298 about: "instance id", 299 mutate: func(m *testMachine, err error) { 300 m.instanceIdErr = err 301 }, 302 }} 303 304 func (*machineSuite) TestTerminatingErrors(c *gc.C) { 305 for i, test := range terminatingErrorsTests { 306 c.Logf("test %d: %s", i, test.about) 307 testTerminatingErrors(c, test.mutate) 308 } 309 } 310 311 // 312 // testTerminatingErrors checks that when a testMachine is 313 // changed with the given mutate function, the machine goroutine 314 // will die having called its context's killAll function with the 315 // given error. The test is cunningly structured so that it in the normal course 316 // of things it will go through all possible places that can return an error. 317 func testTerminatingErrors(c *gc.C, mutate func(m *testMachine, err error)) { 318 context := &testMachineContext{ 319 getInstanceInfo: instanceInfoGetter(c, "i1234", testAddrs, "running", nil), 320 dyingc: make(chan struct{}), 321 } 322 expectErr := stderrors.New("a very unusual error") 323 m := &testMachine{ 324 tag: names.NewMachineTag("99"), 325 instanceId: "i1234", 326 refresh: func() error { return nil }, 327 life: params.Alive, 328 } 329 mutate(m, expectErr) 330 died := make(chan machine) 331 changed := make(chan struct{}, 1) 332 go runMachine(context, m, changed, died, clock.WallClock) 333 changed <- struct{}{} 334 select { 335 case <-died: 336 case <-time.After(coretesting.LongWait): 337 c.Fatalf("timed out waiting for machine to die") 338 } 339 c.Assert(context.killErr, gc.ErrorMatches, ".*"+expectErr.Error()) 340 } 341 342 func killMachineLoop(c *gc.C, m machine, dying chan struct{}, died <-chan machine) { 343 close(dying) 344 select { 345 case diedm := <-died: 346 c.Assert(diedm, gc.Equals, m) 347 case <-time.After(coretesting.LongWait): 348 c.Fatalf("updater did not die after dying channel was closed") 349 } 350 } 351 352 func instanceInfoGetter( 353 c *gc.C, expectId instance.Id, addrs []network.Address, 354 instanceStatus string, err error) func(id instance.Id) (instanceInfo, error) { 355 356 return func(id instance.Id) (instanceInfo, error) { 357 c.Check(id, gc.Equals, expectId) 358 return instanceInfo{addrs, instance.InstanceStatus{Status: status.Unknown, Message: instanceStatus}}, err 359 } 360 } 361 362 type testMachineContext struct { 363 killErr error 364 getInstanceInfo func(instance.Id) (instanceInfo, error) 365 dyingc chan struct{} 366 } 367 368 func (context *testMachineContext) kill(err error) { 369 if err == nil { 370 panic("kill with nil error") 371 } 372 context.killErr = err 373 } 374 375 func (context *testMachineContext) instanceInfo(id instance.Id) (instanceInfo, error) { 376 return context.getInstanceInfo(id) 377 } 378 379 func (context *testMachineContext) dying() <-chan struct{} { 380 return context.dyingc 381 } 382 383 func (context *testMachineContext) errDying() error { 384 return nil 385 } 386 387 type testMachine struct { 388 instanceId instance.Id 389 instanceIdErr error 390 tag names.MachineTag 391 instStatus status.Status 392 instStatusInfo string 393 status status.Status 394 refresh func() error 395 setAddressesErr error 396 // mu protects the following fields. 397 mu sync.Mutex 398 life params.Life 399 addresses []network.Address 400 setAddressCount int 401 } 402 403 func (m *testMachine) Tag() names.MachineTag { 404 return m.tag 405 } 406 407 func (m *testMachine) Id() string { 408 return m.tag.Id() 409 } 410 411 func (m *testMachine) ProviderAddresses() ([]network.Address, error) { 412 m.mu.Lock() 413 defer m.mu.Unlock() 414 415 return m.addresses, nil 416 } 417 418 func (m *testMachine) InstanceId() (instance.Id, error) { 419 m.mu.Lock() 420 defer m.mu.Unlock() 421 if m.instanceId == "" { 422 err := ¶ms.Error{ 423 Code: params.CodeNotProvisioned, 424 Message: fmt.Sprintf("machine %v not provisioned", m.Id()), 425 } 426 return "", err 427 } 428 return m.instanceId, m.instanceIdErr 429 } 430 431 func (m *testMachine) setInstanceId(id instance.Id) { 432 m.mu.Lock() 433 defer m.mu.Unlock() 434 m.instanceId = id 435 } 436 437 // This is stubbed out for testing. 438 var MachineStatus = func(m *testMachine) (params.StatusResult, error) { 439 return params.StatusResult{Status: m.status.String()}, nil 440 } 441 442 func (m *testMachine) Status() (params.StatusResult, error) { 443 return MachineStatus(m) 444 } 445 446 func (m *testMachine) IsManual() (bool, error) { 447 return strings.HasPrefix(string(m.instanceId), "manual:"), nil 448 } 449 450 func (m *testMachine) InstanceStatus() (params.StatusResult, error) { 451 m.mu.Lock() 452 defer m.mu.Unlock() 453 return params.StatusResult{Status: m.instStatus.String()}, nil 454 } 455 456 func (m *testMachine) SetInstanceStatus(machineStatus status.Status, info string, data map[string]interface{}) error { 457 m.mu.Lock() 458 defer m.mu.Unlock() 459 m.instStatus = machineStatus 460 m.instStatusInfo = info 461 return nil 462 } 463 464 func (m *testMachine) SetProviderAddresses(addrs ...network.Address) error { 465 if m.setAddressesErr != nil { 466 return m.setAddressesErr 467 } 468 m.mu.Lock() 469 defer m.mu.Unlock() 470 m.addresses = append(m.addresses[:0], addrs...) 471 m.setAddressCount++ 472 return nil 473 } 474 475 func (m *testMachine) String() string { 476 return m.tag.Id() 477 } 478 479 func (m *testMachine) Refresh() error { 480 return m.refresh() 481 } 482 483 func (m *testMachine) Life() params.Life { 484 m.mu.Lock() 485 defer m.mu.Unlock() 486 return m.life 487 } 488 489 func (m *testMachine) setLife(life params.Life) { 490 m.mu.Lock() 491 defer m.mu.Unlock() 492 m.life = life 493 }