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