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