github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  	reqc := a.reqc
    52  	for {
    53  		select {
    54  		case <-a.tomb.Dying():
    55  			return instanceInfo{}, errors.New("instanceInfo call aborted")
    56  		case reqc <- instanceInfoReq{id, reply}:
    57  			reqc = nil
    58  		case r := <-reply:
    59  			return r.info, r.err
    60  		}
    61  	}
    62  }
    63  
    64  var gatherTime = 3 * time.Second
    65  
    66  func (a *aggregator) loop() error {
    67  	// TODO(fwereade): 2016-03-17 lp:1558657
    68  	timer := time.NewTimer(0)
    69  	timer.Stop()
    70  	var reqs []instanceInfoReq
    71  	// We use a capacity of 1 so that sporadic requests will
    72  	// be serviced immediately without having to wait.
    73  	bucket := ratelimit.NewBucket(gatherTime, 1)
    74  	for {
    75  		select {
    76  		case <-a.tomb.Dying():
    77  			return tomb.ErrDying
    78  		case req := <-a.reqc:
    79  			if len(reqs) == 0 {
    80  				waitTime := bucket.Take(1)
    81  				timer.Reset(waitTime)
    82  			}
    83  			reqs = append(reqs, req)
    84  		case <-timer.C:
    85  			ids := make([]instance.Id, len(reqs))
    86  			for i, req := range reqs {
    87  				ids[i] = req.instId
    88  			}
    89  			insts, err := a.environ.Instances(ids)
    90  			for i, req := range reqs {
    91  				var reply instanceInfoReply
    92  				if err != nil && err != environs.ErrPartialInstances {
    93  					reply.err = err
    94  				} else {
    95  					reply.info, reply.err = a.instInfo(req.instId, insts[i])
    96  				}
    97  				select {
    98  				case <-a.tomb.Dying():
    99  					return tomb.ErrDying
   100  				case req.reply <- reply:
   101  				}
   102  			}
   103  			reqs = nil
   104  		}
   105  	}
   106  }
   107  
   108  // instInfo returns the instance info for the given id
   109  // and instance. If inst is nil, it returns a not-found error.
   110  func (*aggregator) instInfo(id instance.Id, inst instance.Instance) (instanceInfo, error) {
   111  	if inst == nil {
   112  		return instanceInfo{}, errors.NotFoundf("instance %v", id)
   113  	}
   114  	addr, err := inst.Addresses()
   115  	if err != nil {
   116  		return instanceInfo{}, err
   117  	}
   118  	return instanceInfo{
   119  		addr,
   120  		inst.Status(),
   121  	}, nil
   122  }
   123  
   124  func (a *aggregator) Kill() {
   125  	a.tomb.Kill(nil)
   126  }
   127  
   128  func (a *aggregator) Wait() error {
   129  	return a.tomb.Wait()
   130  }