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  }