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