github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/instancepoller/aggregate.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  	"time"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/ratelimit"
    11  	"launchpad.net/tomb"
    12  
    13  	"github.com/juju/juju/environs"
    14  	"github.com/juju/juju/instance"
    15  )
    16  
    17  type instanceGetter interface {
    18  	Instances(ids []instance.Id) ([]instance.Instance, error)
    19  }
    20  
    21  type aggregator struct {
    22  	environ instanceGetter
    23  	reqc    chan instanceInfoReq
    24  	tomb    tomb.Tomb
    25  }
    26  
    27  func newAggregator(env instanceGetter) *aggregator {
    28  	a := &aggregator{
    29  		environ: env,
    30  		reqc:    make(chan instanceInfoReq),
    31  	}
    32  	go func() {
    33  		defer a.tomb.Done()
    34  		a.tomb.Kill(a.loop())
    35  	}()
    36  	return a
    37  }
    38  
    39  type instanceInfoReq struct {
    40  	instId instance.Id
    41  	reply  chan<- instanceInfoReply
    42  }
    43  
    44  type instanceInfoReply struct {
    45  	info instanceInfo
    46  	err  error
    47  }
    48  
    49  func (a *aggregator) instanceInfo(id instance.Id) (instanceInfo, error) {
    50  	reply := make(chan instanceInfoReply)
    51  	a.reqc <- instanceInfoReq{
    52  		instId: id,
    53  		reply:  reply,
    54  	}
    55  	r := <-reply
    56  	return r.info, r.err
    57  }
    58  
    59  var gatherTime = 3 * time.Second
    60  
    61  func (a *aggregator) loop() error {
    62  	timer := time.NewTimer(0)
    63  	timer.Stop()
    64  	var reqs []instanceInfoReq
    65  	// We use a capacity of 1 so that sporadic requests will
    66  	// be serviced immediately without having to wait.
    67  	bucket := ratelimit.NewBucket(gatherTime, 1)
    68  	for {
    69  		select {
    70  		case <-a.tomb.Dying():
    71  			return tomb.ErrDying
    72  		case req := <-a.reqc:
    73  			if len(reqs) == 0 {
    74  				waitTime := bucket.Take(1)
    75  				timer.Reset(waitTime)
    76  			}
    77  			reqs = append(reqs, req)
    78  		case <-timer.C:
    79  			ids := make([]instance.Id, len(reqs))
    80  			for i, req := range reqs {
    81  				ids[i] = req.instId
    82  			}
    83  			insts, err := a.environ.Instances(ids)
    84  			for i, req := range reqs {
    85  				var reply instanceInfoReply
    86  				if err != nil && err != environs.ErrPartialInstances {
    87  					reply.err = err
    88  				} else {
    89  					reply.info, reply.err = a.instInfo(req.instId, insts[i])
    90  				}
    91  				req.reply <- reply
    92  			}
    93  			reqs = nil
    94  		}
    95  	}
    96  }
    97  
    98  // instInfo returns the instance info for the given id
    99  // and instance. If inst is nil, it returns a not-found error.
   100  func (*aggregator) instInfo(id instance.Id, inst instance.Instance) (instanceInfo, error) {
   101  	if inst == nil {
   102  		return instanceInfo{}, errors.NotFoundf("instance %v", id)
   103  	}
   104  	addr, err := inst.Addresses()
   105  	if err != nil {
   106  		return instanceInfo{}, err
   107  	}
   108  	return instanceInfo{
   109  		addr,
   110  		inst.Status(),
   111  	}, nil
   112  }
   113  
   114  func (a *aggregator) Kill() {
   115  	a.tomb.Kill(nil)
   116  }
   117  
   118  func (a *aggregator) Wait() error {
   119  	return a.tomb.Wait()
   120  }