github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/lease/manager.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lease 5 6 import ( 7 "sort" 8 "time" 9 10 "github.com/juju/errors" 11 "github.com/juju/loggo" 12 "github.com/juju/utils/clock" 13 14 "github.com/juju/juju/core/lease" 15 "github.com/juju/juju/worker/catacomb" 16 ) 17 18 var logger = loggo.GetLogger("juju.worker.lease") 19 20 // errStopped is returned to clients when an operation cannot complete because 21 // the manager has started (and possibly finished) shutdown. 22 var errStopped = errors.New("lease manager stopped") 23 24 // NewManager returns a new *Manager configured as supplied. The caller takes 25 // responsibility for killing, and handling errors from, the returned Worker. 26 func NewManager(config ManagerConfig) (*Manager, error) { 27 if err := config.Validate(); err != nil { 28 return nil, errors.Trace(err) 29 } 30 manager := &Manager{ 31 config: config, 32 claims: make(chan claim), 33 checks: make(chan check), 34 blocks: make(chan block), 35 } 36 err := catacomb.Invoke(catacomb.Plan{ 37 Site: &manager.catacomb, 38 Work: manager.loop, 39 }) 40 if err != nil { 41 return nil, errors.Trace(err) 42 } 43 return manager, nil 44 } 45 46 // Manager implements lease.Claimer, lease.Checker, and worker.Worker. 47 type Manager struct { 48 catacomb catacomb.Catacomb 49 50 // config collects all external configuration and dependencies. 51 config ManagerConfig 52 53 // claims is used to deliver lease claim requests to the loop. 54 claims chan claim 55 56 // checks is used to deliver lease check requests to the loop. 57 checks chan check 58 59 // blocks is used to deliver expiry block requests to the loop. 60 blocks chan block 61 } 62 63 // Kill is part of the worker.Worker interface. 64 func (manager *Manager) Kill() { 65 manager.catacomb.Kill(nil) 66 } 67 68 // Wait is part of the worker.Worker interface. 69 func (manager *Manager) Wait() error { 70 return manager.catacomb.Wait() 71 } 72 73 // loop runs until the manager is stopped. 74 func (manager *Manager) loop() error { 75 blocks := make(blocks) 76 for { 77 if err := manager.choose(blocks); err != nil { 78 return errors.Trace(err) 79 } 80 81 leases := manager.config.Client.Leases() 82 for leaseName := range blocks { 83 if _, found := leases[leaseName]; !found { 84 blocks.unblock(leaseName) 85 } 86 } 87 } 88 } 89 90 // choose breaks the select out of loop to make the blocking logic clearer. 91 func (manager *Manager) choose(blocks blocks) error { 92 select { 93 case <-manager.catacomb.Dying(): 94 return manager.catacomb.ErrDying() 95 case <-manager.nextTick(): 96 return manager.tick() 97 case claim := <-manager.claims: 98 return manager.handleClaim(claim) 99 case check := <-manager.checks: 100 return manager.handleCheck(check) 101 case block := <-manager.blocks: 102 blocks.add(block) 103 return nil 104 } 105 } 106 107 // Claim is part of the lease.Claimer interface. 108 func (manager *Manager) Claim(leaseName, holderName string, duration time.Duration) error { 109 if err := manager.config.Secretary.CheckLease(leaseName); err != nil { 110 return errors.Annotatef(err, "cannot claim lease %q", leaseName) 111 } 112 if err := manager.config.Secretary.CheckHolder(holderName); err != nil { 113 return errors.Annotatef(err, "cannot claim lease for holder %q", holderName) 114 } 115 if err := manager.config.Secretary.CheckDuration(duration); err != nil { 116 return errors.Annotatef(err, "cannot claim lease for %s", duration) 117 } 118 return claim{ 119 leaseName: leaseName, 120 holderName: holderName, 121 duration: duration, 122 response: make(chan bool), 123 abort: manager.catacomb.Dying(), 124 }.invoke(manager.claims) 125 } 126 127 // handleClaim processes and responds to the supplied claim. It will only return 128 // unrecoverable errors; mere failure to claim just indicates a bad request, and 129 // is communicated back to the claim's originator. 130 func (manager *Manager) handleClaim(claim claim) error { 131 client := manager.config.Client 132 request := lease.Request{claim.holderName, claim.duration} 133 err := lease.ErrInvalid 134 for err == lease.ErrInvalid { 135 select { 136 case <-manager.catacomb.Dying(): 137 return manager.catacomb.ErrDying() 138 default: 139 info, found := client.Leases()[claim.leaseName] 140 switch { 141 case !found: 142 err = client.ClaimLease(claim.leaseName, request) 143 case info.Holder == claim.holderName: 144 err = client.ExtendLease(claim.leaseName, request) 145 default: 146 claim.respond(false) 147 return nil 148 } 149 } 150 } 151 if err != nil { 152 return errors.Trace(err) 153 } 154 claim.respond(true) 155 return nil 156 } 157 158 // Token is part of the lease.Checker interface. 159 func (manager *Manager) Token(leaseName, holderName string) lease.Token { 160 return token{ 161 leaseName: leaseName, 162 holderName: holderName, 163 secretary: manager.config.Secretary, 164 checks: manager.checks, 165 abort: manager.catacomb.Dying(), 166 } 167 } 168 169 // handleCheck processes and responds to the supplied check. It will only return 170 // unrecoverable errors; mere untruth of the assertion just indicates a bad 171 // request, and is communicated back to the check's originator. 172 func (manager *Manager) handleCheck(check check) error { 173 client := manager.config.Client 174 info, found := client.Leases()[check.leaseName] 175 if !found || info.Holder != check.holderName { 176 if err := client.Refresh(); err != nil { 177 return errors.Trace(err) 178 } 179 info, found = client.Leases()[check.leaseName] 180 } 181 182 var response error 183 if !found || info.Holder != check.holderName { 184 response = lease.ErrNotHeld 185 } else if check.trapdoorKey != nil { 186 response = info.Trapdoor(check.trapdoorKey) 187 } 188 check.respond(errors.Trace(response)) 189 return nil 190 } 191 192 // WaitUntilExpired is part of the lease.Claimer interface. 193 func (manager *Manager) WaitUntilExpired(leaseName string) error { 194 if err := manager.config.Secretary.CheckLease(leaseName); err != nil { 195 return errors.Annotatef(err, "cannot wait for lease %q expiry", leaseName) 196 } 197 return block{ 198 leaseName: leaseName, 199 unblock: make(chan struct{}), 200 abort: manager.catacomb.Dying(), 201 }.invoke(manager.blocks) 202 } 203 204 // nextTick returns a channel that will send a value at some point when 205 // we expect to have to do some work; either because at least one lease 206 // may be ready to expire, or because enough enough time has passed that 207 // it's worth checking for stalled collaborators. 208 func (manager *Manager) nextTick() <-chan time.Time { 209 now := manager.config.Clock.Now() 210 nextTick := now.Add(manager.config.MaxSleep) 211 for _, info := range manager.config.Client.Leases() { 212 if info.Expiry.After(nextTick) { 213 continue 214 } 215 nextTick = info.Expiry 216 } 217 logger.Debugf("waking to check leases at %s", nextTick) 218 return clock.Alarm(manager.config.Clock, nextTick) 219 } 220 221 // tick snapshots recent leases and expires any that it can. There 222 // might be none that need attention; or those that do might already 223 // have been extended or expired by someone else; so ErrInvalid is 224 // expected, and ignored, comfortable that the client will have been 225 // updated in the background; and that we'll see fresh info when we 226 // subsequently check nextWake(). 227 // 228 // It will return only unrecoverable errors. 229 func (manager *Manager) tick() error { 230 logger.Tracef("refreshing leases...") 231 client := manager.config.Client 232 if err := client.Refresh(); err != nil { 233 return errors.Trace(err) 234 } 235 leases := client.Leases() 236 237 // Sort lease names so we expire in a predictable order for the tests. 238 names := make([]string, 0, len(leases)) 239 for name := range leases { 240 names = append(names, name) 241 } 242 sort.Strings(names) 243 244 logger.Tracef("expiring leases...") 245 now := manager.config.Clock.Now() 246 for _, name := range names { 247 if leases[name].Expiry.After(now) { 248 continue 249 } 250 switch err := client.ExpireLease(name); err { 251 case nil, lease.ErrInvalid: 252 default: 253 return errors.Trace(err) 254 } 255 } 256 return nil 257 }