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