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