github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/worker/instancepoller/aggregate_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package instancepoller
     5  
     6  import (
     7  	"fmt"
     8  	"sync"
     9  	"sync/atomic"
    10  	"time"
    11  
    12  	"github.com/juju/errors"
    13  	jc "github.com/juju/testing/checkers"
    14  	gc "gopkg.in/check.v1"
    15  
    16  	"github.com/juju/juju/environs"
    17  	"github.com/juju/juju/instance"
    18  	"github.com/juju/juju/network"
    19  	"github.com/juju/juju/testing"
    20  )
    21  
    22  type aggregateSuite struct {
    23  	testing.BaseSuite
    24  }
    25  
    26  var _ = gc.Suite(&aggregateSuite{})
    27  
    28  type testInstance struct {
    29  	instance.Instance
    30  	id        instance.Id
    31  	addresses []network.Address
    32  	status    string
    33  	err       error
    34  }
    35  
    36  var _ instance.Instance = (*testInstance)(nil)
    37  
    38  func (t *testInstance) Id() instance.Id {
    39  	return t.id
    40  }
    41  
    42  func (t *testInstance) Addresses() ([]network.Address, error) {
    43  	if t.err != nil {
    44  		return nil, t.err
    45  	}
    46  	return t.addresses, nil
    47  }
    48  
    49  func (t *testInstance) Status() string {
    50  	return t.status
    51  }
    52  
    53  type testInstanceGetter struct {
    54  	// ids is set when the Instances method is called.
    55  	ids     []instance.Id
    56  	results map[instance.Id]instance.Instance
    57  	err     error
    58  	counter int32
    59  }
    60  
    61  func (tig *testInstanceGetter) Instances(ids []instance.Id) (result []instance.Instance, err error) {
    62  	tig.ids = ids
    63  	atomic.AddInt32(&tig.counter, 1)
    64  	results := make([]instance.Instance, len(ids))
    65  	for i, id := range ids {
    66  		// We don't check 'ok' here, because we want the Instance{nil}
    67  		// response for those
    68  		results[i] = tig.results[id]
    69  	}
    70  	return results, tig.err
    71  }
    72  
    73  func (tig *testInstanceGetter) newTestInstance(id instance.Id, status string, addresses []string) *testInstance {
    74  	if tig.results == nil {
    75  		tig.results = make(map[instance.Id]instance.Instance)
    76  	}
    77  	thisInstance := &testInstance{
    78  		id:        id,
    79  		status:    status,
    80  		addresses: network.NewAddresses(addresses...),
    81  	}
    82  	tig.results[thisInstance.Id()] = thisInstance
    83  	return thisInstance
    84  }
    85  
    86  func (s *aggregateSuite) TestSingleRequest(c *gc.C) {
    87  	testGetter := new(testInstanceGetter)
    88  	instance1 := testGetter.newTestInstance("foo", "foobar", []string{"127.0.0.1", "192.168.1.1"})
    89  	aggregator := newAggregator(testGetter)
    90  
    91  	info, err := aggregator.instanceInfo("foo")
    92  	c.Assert(err, jc.ErrorIsNil)
    93  	c.Assert(info, gc.DeepEquals, instanceInfo{
    94  		status:    "foobar",
    95  		addresses: instance1.addresses,
    96  	})
    97  	c.Assert(testGetter.ids, gc.DeepEquals, []instance.Id{"foo"})
    98  }
    99  
   100  func (s *aggregateSuite) TestMultipleResponseHandling(c *gc.C) {
   101  	s.PatchValue(&gatherTime, 30*time.Millisecond)
   102  	testGetter := new(testInstanceGetter)
   103  
   104  	testGetter.newTestInstance("foo", "foobar", []string{"127.0.0.1", "192.168.1.1"})
   105  	aggregator := newAggregator(testGetter)
   106  
   107  	replyChan := make(chan instanceInfoReply)
   108  	req := instanceInfoReq{
   109  		reply:  replyChan,
   110  		instId: instance.Id("foo"),
   111  	}
   112  	aggregator.reqc <- req
   113  	reply := <-replyChan
   114  	c.Assert(reply.err, gc.IsNil)
   115  
   116  	testGetter.newTestInstance("foo2", "not foobar", []string{"192.168.1.2"})
   117  	testGetter.newTestInstance("foo3", "ok-ish", []string{"192.168.1.3"})
   118  
   119  	var wg sync.WaitGroup
   120  	checkInfo := func(id instance.Id, expectStatus string) {
   121  		info, err := aggregator.instanceInfo(id)
   122  		c.Check(err, jc.ErrorIsNil)
   123  		c.Check(info.status, gc.Equals, expectStatus)
   124  		wg.Done()
   125  	}
   126  
   127  	wg.Add(2)
   128  	go checkInfo("foo2", "not foobar")
   129  	go checkInfo("foo3", "ok-ish")
   130  	wg.Wait()
   131  
   132  	c.Assert(len(testGetter.ids), gc.DeepEquals, 2)
   133  }
   134  
   135  type batchingInstanceGetter struct {
   136  	testInstanceGetter
   137  	wg         sync.WaitGroup
   138  	aggregator *aggregator
   139  	totalCount int
   140  	batchSize  int
   141  	started    int
   142  }
   143  
   144  func (g *batchingInstanceGetter) Instances(ids []instance.Id) ([]instance.Instance, error) {
   145  	insts, err := g.testInstanceGetter.Instances(ids)
   146  	g.startRequests()
   147  	return insts, err
   148  }
   149  
   150  func (g *batchingInstanceGetter) startRequests() {
   151  	n := g.totalCount - g.started
   152  	if n > g.batchSize {
   153  		n = g.batchSize
   154  	}
   155  	for i := 0; i < n; i++ {
   156  		g.startRequest()
   157  	}
   158  }
   159  
   160  func (g *batchingInstanceGetter) startRequest() {
   161  	g.started++
   162  	go func() {
   163  		_, err := g.aggregator.instanceInfo("foo")
   164  		if err != nil {
   165  			panic(err)
   166  		}
   167  		g.wg.Done()
   168  	}()
   169  }
   170  
   171  func (s *aggregateSuite) TestBatching(c *gc.C) {
   172  	s.PatchValue(&gatherTime, 10*time.Millisecond)
   173  	var testGetter batchingInstanceGetter
   174  	testGetter.aggregator = newAggregator(&testGetter)
   175  	// We only need to inform the system about 1 instance, because all the
   176  	// requests are for the same instance.
   177  	testGetter.newTestInstance("foo", "foobar", []string{"127.0.0.1", "192.168.1.1"})
   178  	testGetter.totalCount = 100
   179  	testGetter.batchSize = 10
   180  	testGetter.wg.Add(testGetter.totalCount)
   181  	// startRequest will trigger one request, which ends up calling
   182  	// Instances, which will turn around and trigger batchSize requests,
   183  	// which should get aggregated into a single call to Instances, which
   184  	// then should trigger another round of batchSize requests.
   185  	testGetter.startRequest()
   186  	testGetter.wg.Wait()
   187  	c.Assert(testGetter.counter, gc.Equals, int32(testGetter.totalCount/testGetter.batchSize)+1)
   188  }
   189  
   190  func (s *aggregateSuite) TestError(c *gc.C) {
   191  	testGetter := new(testInstanceGetter)
   192  	ourError := fmt.Errorf("Some error")
   193  	testGetter.err = ourError
   194  
   195  	aggregator := newAggregator(testGetter)
   196  
   197  	_, err := aggregator.instanceInfo("foo")
   198  	c.Assert(err, gc.Equals, ourError)
   199  }
   200  
   201  func (s *aggregateSuite) TestPartialErrResponse(c *gc.C) {
   202  	testGetter := new(testInstanceGetter)
   203  	testGetter.err = environs.ErrPartialInstances
   204  
   205  	aggregator := newAggregator(testGetter)
   206  	_, err := aggregator.instanceInfo("foo")
   207  
   208  	c.Assert(err, gc.ErrorMatches, "instance foo not found")
   209  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   210  }
   211  
   212  func (s *aggregateSuite) TestAddressesError(c *gc.C) {
   213  	testGetter := new(testInstanceGetter)
   214  	instance1 := testGetter.newTestInstance("foo", "foobar", []string{"127.0.0.1", "192.168.1.1"})
   215  	ourError := fmt.Errorf("gotcha")
   216  	instance1.err = ourError
   217  
   218  	aggregator := newAggregator(testGetter)
   219  	_, err := aggregator.instanceInfo("foo")
   220  	c.Assert(err, gc.Equals, ourError)
   221  }
   222  
   223  func (s *aggregateSuite) TestKillAndWait(c *gc.C) {
   224  	testGetter := new(testInstanceGetter)
   225  	aggregator := newAggregator(testGetter)
   226  	aggregator.Kill()
   227  	err := aggregator.Wait()
   228  	c.Assert(err, jc.ErrorIsNil)
   229  }