github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/core/lease/client.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 "strings" 8 "time" 9 10 "github.com/juju/errors" 11 ) 12 13 // Client manipulates leases directly, and is most likely to be seen set on a 14 // worker/lease.ManagerConfig struct (and used by the Manager). Implementations 15 // of Client are not expected to be goroutine-safe. 16 type Client interface { 17 18 // ClaimLease records the supplied holder's claim to the supplied lease. If 19 // it succeeds, the claim is guaranteed until at least the supplied duration 20 // after the call to ClaimLease was initiated. If it returns ErrInvalid, 21 // check Leases() for updated state. 22 ClaimLease(lease string, request Request) error 23 24 // ExtendLease records the supplied holder's continued claim to the supplied 25 // lease, if necessary. If it succeeds, the claim is guaranteed until at 26 // least the supplied duration after the call to ExtendLease was initiated. 27 // If it returns ErrInvalid, check Leases() for updated state. 28 ExtendLease(lease string, request Request) error 29 30 // ExpireLease records the vacation of the supplied lease. It will fail if 31 // we cannot verify that the lease's writer considers the expiry time to 32 // have passed. If it returns ErrInvalid, check Leases() for updated state. 33 ExpireLease(lease string) error 34 35 // Leases returns a recent snapshot of lease state. Expiry times are 36 // expressed according to the Clock the client was configured with. 37 Leases() map[string]Info 38 39 // Refresh reads all lease state from the database. 40 Refresh() error 41 } 42 43 // Info holds substrate-independent information about a lease; and a substrate- 44 // specific trapdoor func. 45 type Info struct { 46 47 // Holder is the name of the current leaseholder. 48 Holder string 49 50 // Expiry is the latest time at which it's possible the lease might still 51 // be valid. Attempting to expire the lease before this time will fail. 52 Expiry time.Time 53 54 // Trapdoor exposes the originating Client's persistence substrate, if the 55 // substrate exposes any such capability. It's useful specifically for 56 // integrating mgo/txn-based components: which thus get a mechanism for 57 // extracting assertion operations they can use to gate other substrate 58 // changes on lease state. 59 Trapdoor Trapdoor 60 } 61 62 // Trapdoor allows a client to use pre-agreed special knowledge to communicate 63 // with a Client substrate by passing a key with suitable properties. 64 type Trapdoor func(key interface{}) error 65 66 // LockedTrapdoor is a Trapdoor suitable for use by substrates that don't want 67 // or need to expose their internals. 68 func LockedTrapdoor(key interface{}) error { 69 if key != nil { 70 return errors.New("lease substrate not accessible") 71 } 72 return nil 73 } 74 75 // Request describes a lease request. 76 type Request struct { 77 78 // Holder identifies the lease holder. 79 Holder string 80 81 // Duration specifies the time for which the lease is required. 82 Duration time.Duration 83 } 84 85 // Validate returns an error if any fields are invalid or inconsistent. 86 func (request Request) Validate() error { 87 if err := ValidateString(request.Holder); err != nil { 88 return errors.Annotatef(err, "invalid holder") 89 } 90 if request.Duration <= 0 { 91 return errors.Errorf("invalid duration") 92 } 93 return nil 94 } 95 96 // ValidateString returns an error if the string is empty, or if it contains 97 // whitespace, or if it contains any character in `.#$`. Client implementations 98 // are expected to always reject invalid strings, and never to produce them. 99 func ValidateString(s string) error { 100 if s == "" { 101 return errors.New("string is empty") 102 } 103 if strings.ContainsAny(s, ".$# \t\r\n") { 104 return errors.New("string contains forbidden characters") 105 } 106 return nil 107 } 108 109 // ErrInvalid indicates that a Client operation failed because latest state 110 // indicates that it's a logical impossibility. It's a short-range signal to 111 // calling code only; that code should never pass it on, but should inspect 112 // the Client's updated Leases() and either attempt a new operation or return 113 // a new error at a suitable level of abstraction. 114 var ErrInvalid = errors.New("invalid lease operation")