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 }