github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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/utils/clock"
    11  
    12  	"github.com/juju/juju/environs"
    13  	"github.com/juju/juju/instance"
    14  	"github.com/juju/juju/worker/catacomb"
    15  )
    16  
    17  type InstanceGetter interface {
    18  	Instances(ids []instance.Id) ([]instance.Instance, error)
    19  }
    20  
    21  type aggregatorConfig struct {
    22  	Clock   clock.Clock
    23  	Delay   time.Duration
    24  	Environ InstanceGetter
    25  }
    26  
    27  func (c aggregatorConfig) validate() error {
    28  	if c.Clock == nil {
    29  		return errors.NotValidf("nil clock.Clock")
    30  	}
    31  	if c.Delay == 0 {
    32  		return errors.NotValidf("zero Delay")
    33  	}
    34  	if c.Environ == nil {
    35  		return errors.NotValidf("nil Environ")
    36  	}
    37  	return nil
    38  
    39  }
    40  
    41  type aggregator struct {
    42  	config   aggregatorConfig
    43  	catacomb catacomb.Catacomb
    44  	reqc     chan instanceInfoReq
    45  }
    46  
    47  func newAggregator(config aggregatorConfig) (*aggregator, error) {
    48  	if err := config.validate(); err != nil {
    49  		return nil, errors.Trace(err)
    50  	}
    51  	a := &aggregator{
    52  		config: config,
    53  		reqc:   make(chan instanceInfoReq),
    54  	}
    55  	err := catacomb.Invoke(catacomb.Plan{
    56  		Site: &a.catacomb,
    57  		Work: a.loop,
    58  	})
    59  	if err != nil {
    60  		return nil, errors.Trace(err)
    61  	}
    62  	return a, nil
    63  }
    64  
    65  type instanceInfoReq struct {
    66  	instId instance.Id
    67  	reply  chan<- instanceInfoReply
    68  }
    69  
    70  type instanceInfoReply struct {
    71  	info instanceInfo
    72  	err  error
    73  }
    74  
    75  func (a *aggregator) instanceInfo(id instance.Id) (instanceInfo, error) {
    76  	reply := make(chan instanceInfoReply)
    77  	reqc := a.reqc
    78  	for {
    79  		select {
    80  		case <-a.catacomb.Dying():
    81  			return instanceInfo{}, errors.New("instanceInfo call aborted")
    82  		case reqc <- instanceInfoReq{id, reply}:
    83  			reqc = nil
    84  		case r := <-reply:
    85  			return r.info, r.err
    86  		}
    87  	}
    88  }
    89  
    90  func (a *aggregator) loop() error {
    91  	var (
    92  		next time.Time
    93  		reqs []instanceInfoReq
    94  	)
    95  
    96  	for {
    97  		var ready <-chan time.Time
    98  		if !next.IsZero() {
    99  			when := next.Add(a.config.Delay)
   100  			ready = clock.Alarm(a.config.Clock, when)
   101  		}
   102  		select {
   103  		case <-a.catacomb.Dying():
   104  			return a.catacomb.ErrDying()
   105  
   106  		case req := <-a.reqc:
   107  			reqs = append(reqs, req)
   108  
   109  			if next.IsZero() {
   110  				next = a.config.Clock.Now()
   111  			}
   112  
   113  		case <-ready:
   114  			if err := a.doRequests(reqs); err != nil {
   115  				return errors.Trace(err)
   116  			}
   117  			next = time.Time{}
   118  			reqs = nil
   119  		}
   120  	}
   121  }
   122  
   123  func (a *aggregator) doRequests(reqs []instanceInfoReq) error {
   124  	ids := make([]instance.Id, len(reqs))
   125  	for i, req := range reqs {
   126  		ids[i] = req.instId
   127  	}
   128  	insts, err := a.config.Environ.Instances(ids)
   129  	for i, req := range reqs {
   130  		var reply instanceInfoReply
   131  		if err != nil && err != environs.ErrPartialInstances {
   132  			reply.err = err
   133  		} else {
   134  			reply.info, reply.err = a.instInfo(req.instId, insts[i])
   135  		}
   136  		select {
   137  		// Per review http://reviews.vapour.ws/r/4885/ it's dumb to block
   138  		// the main goroutine on these responses.
   139  		case <-a.catacomb.Dying():
   140  			return a.catacomb.ErrDying()
   141  		case req.reply <- reply:
   142  		}
   143  	}
   144  	return nil
   145  }
   146  
   147  // instInfo returns the instance info for the given id
   148  // and instance. If inst is nil, it returns a not-found error.
   149  func (*aggregator) instInfo(id instance.Id, inst instance.Instance) (instanceInfo, error) {
   150  	if inst == nil {
   151  		return instanceInfo{}, errors.NotFoundf("instance %v", id)
   152  	}
   153  	addr, err := inst.Addresses()
   154  	if err != nil {
   155  		return instanceInfo{}, err
   156  	}
   157  	return instanceInfo{
   158  		addr,
   159  		inst.Status(),
   160  	}, nil
   161  }
   162  
   163  func (a *aggregator) Kill() {
   164  	a.catacomb.Kill(nil)
   165  }
   166  
   167  func (a *aggregator) Wait() error {
   168  	return a.catacomb.Wait()
   169  }