github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/lease/bound.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lease 5 6 import ( 7 "context" 8 "time" 9 10 "github.com/juju/errors" 11 12 "github.com/juju/juju/core/lease" 13 ) 14 15 // broker describes methods for manipulating and checking leases. 16 type broker interface { 17 lease.Checker 18 lease.Claimer 19 lease.Revoker 20 lease.Pinner 21 lease.Reader 22 } 23 24 // boundManager implements the broker interface. 25 // It represents a lease manager for a specific namespace and model. 26 type boundManager struct { 27 manager *Manager 28 secretary Secretary 29 namespace string 30 modelUUID string 31 } 32 33 // Claim is part of the lease.Claimer interface. 34 func (b *boundManager) Claim(leaseName, holderName string, duration time.Duration) error { 35 key := b.leaseKey(leaseName) 36 if err := b.secretary.CheckLease(key); err != nil { 37 return errors.Annotatef(err, "cannot claim lease %q", leaseName) 38 } 39 if err := b.secretary.CheckHolder(holderName); err != nil { 40 return errors.Annotatef(err, "cannot claim lease for holder %q", holderName) 41 } 42 if err := b.secretary.CheckDuration(duration); err != nil { 43 return errors.Annotatef(err, "cannot claim lease for %s", duration) 44 } 45 46 return claim{ 47 leaseKey: key, 48 holderName: holderName, 49 duration: duration, 50 response: make(chan error), 51 stop: b.manager.tomb.Dying(), 52 }.invoke(b.manager.claims) 53 } 54 55 // Revoke is part of the lease.Revoker interface. 56 func (b *boundManager) Revoke(leaseName, holderName string) error { 57 key := b.leaseKey(leaseName) 58 if err := b.secretary.CheckLease(key); err != nil { 59 return errors.Annotatef(err, "cannot revoke lease %q", leaseName) 60 } 61 if err := b.secretary.CheckHolder(holderName); err != nil { 62 return errors.Annotatef(err, "cannot revoke lease for holder %q", holderName) 63 } 64 65 return revoke{ 66 leaseKey: key, 67 holderName: holderName, 68 response: make(chan error), 69 stop: b.manager.tomb.Dying(), 70 }.invoke(b.manager.revokes) 71 } 72 73 // WaitUntilExpired is part of the lease.Claimer interface. 74 func (b *boundManager) WaitUntilExpired(leaseName string, cancel <-chan struct{}) error { 75 key := b.leaseKey(leaseName) 76 if err := b.secretary.CheckLease(key); err != nil { 77 return errors.Annotatef(err, "cannot wait for lease %q expiry", leaseName) 78 } 79 80 return block{ 81 leaseKey: key, 82 unblock: make(chan struct{}), 83 stop: b.manager.tomb.Dying(), 84 cancel: cancel, 85 }.invoke(b.manager.blocks) 86 } 87 88 // Token is part of the lease.Checker interface. 89 func (b *boundManager) Token(leaseName, holderName string) lease.Token { 90 return token{ 91 leaseKey: b.leaseKey(leaseName), 92 holderName: holderName, 93 secretary: b.secretary, 94 checks: b.manager.checks, 95 stop: b.manager.tomb.Dying(), 96 } 97 } 98 99 // Leases (lease.Reader) returns all leases and holders 100 // in the bound namespace/model. 101 func (b *boundManager) Leases() (map[string]string, error) { 102 ctx, cancel := b.tombContextWithCancel() 103 defer cancel() 104 105 leases, err := b.manager.leases(ctx, b.namespace, b.modelUUID) 106 return leases, errors.Trace(err) 107 } 108 109 // Pinned (lease.Pinner) returns applications and the entities requiring their 110 // pinned behaviour, for pinned leases in the bound namespace/model. 111 func (b *boundManager) Pinned() (map[string][]string, error) { 112 ctx, cancel := b.tombContextWithCancel() 113 defer cancel() 114 115 pinned, err := b.manager.pinned(ctx, b.namespace, b.modelUUID) 116 return pinned, errors.Trace(err) 117 } 118 119 // tombContextWithCancel is a work-around for the bound manager that exposes 120 // calls to lease store methods outside the worker loop. 121 // Here, we create a new cancelable context and use that as a parent for 122 // tomb.Context. This means that killing the tomb will cancel the returned 123 // context, ensuring that these calls cannot block worker shutdown. 124 // Every cancel func returned from this method must be called when the 125 // function its context was passed to returns. Contexts are stored in the tomb, 126 // which deletes those that are `done` whenever a new context is added. 127 func (b *boundManager) tombContextWithCancel() (context.Context, func()) { 128 parent, cancel := context.WithCancel(context.Background()) 129 return b.manager.tomb.Context(parent), cancel 130 } 131 132 // Pin (lease.Pinner) sends a pin message to the worker loop. 133 func (b *boundManager) Pin(leaseName string, entity string) error { 134 return errors.Trace(b.pinOp(leaseName, entity, b.manager.pins)) 135 } 136 137 // Unpin (lease.Pinner) sends an unpin message to the worker loop. 138 func (b *boundManager) Unpin(leaseName string, entity string) error { 139 return errors.Trace(b.pinOp(leaseName, entity, b.manager.unpins)) 140 } 141 142 // pinOp creates a pin instance from the input lease name, 143 // then sends it on the input channel. 144 func (b *boundManager) pinOp(leaseName string, entity string, ch chan pin) error { 145 return errors.Trace(pin{ 146 leaseKey: b.leaseKey(leaseName), 147 entity: entity, 148 response: make(chan error), 149 stop: b.manager.tomb.Dying(), 150 }.invoke(ch)) 151 } 152 153 // leaseKey returns a key for the manager's binding and the input lease name. 154 func (b *boundManager) leaseKey(leaseName string) lease.Key { 155 return lease.Key{ 156 Namespace: b.namespace, 157 ModelUUID: b.modelUUID, 158 Lease: leaseName, 159 } 160 }