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 }