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 }