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  }