github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/uniter/runner/context/leader.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package context 5 6 import ( 7 "github.com/juju/errors" 8 9 "github.com/juju/juju/core/leadership" 10 ) 11 12 var ( 13 errIsMinion = errors.New("not the leader") 14 ) 15 16 // LeadershipSettingsAccessor is an interface that allows us not to have 17 // to use the concrete `api/uniter/LeadershipSettingsAccessor` type, thus 18 // simplifying testing. 19 type LeadershipSettingsAccessor interface { 20 Read(serviceName string) (map[string]string, error) 21 Merge(serviceName string, settings map[string]string) error 22 } 23 24 // LeadershipContext provides several jujuc.Context methods. It 25 // exists separately of HookContext for clarity, and ease of testing. 26 type LeadershipContext interface { 27 IsLeader() (bool, error) 28 LeaderSettings() (map[string]string, error) 29 WriteLeaderSettings(map[string]string) error 30 } 31 32 type leadershipContext struct { 33 accessor LeadershipSettingsAccessor 34 tracker leadership.Tracker 35 applicationName string 36 37 isMinion bool 38 settings map[string]string 39 } 40 41 func NewLeadershipContext(accessor LeadershipSettingsAccessor, tracker leadership.Tracker) LeadershipContext { 42 return &leadershipContext{ 43 accessor: accessor, 44 tracker: tracker, 45 applicationName: tracker.ApplicationName(), 46 } 47 } 48 49 // newLeadershipContext allows us to swap out the leadership context creator for 50 // factory tests. 51 var newLeadershipContext = NewLeadershipContext 52 53 // IsLeader is part of the jujuc.Context interface. 54 func (ctx *leadershipContext) IsLeader() (bool, error) { 55 // This doesn't technically need an error return, but that feels like a 56 // happy accident of the current implementation and not a reason to change 57 // the interface we're implementing. 58 err := ctx.ensureLeader() 59 switch err { 60 case nil: 61 return true, nil 62 case errIsMinion: 63 return false, nil 64 } 65 return false, errors.Trace(err) 66 } 67 68 // WriteLeaderSettings is part of the jujuc.Context interface. 69 func (ctx *leadershipContext) WriteLeaderSettings(settings map[string]string) error { 70 // This may trigger a lease refresh; it would be desirable to use a less 71 // eager approach here, but we're working around a race described in 72 // `apiserver/leadership.LeadershipSettingsAccessor.Merge`, and as of 73 // 2015-02-19 it's better to stay eager. 74 err := ctx.ensureLeader() 75 if err == nil { 76 // Clear local settings; if we need them again we should use the values 77 // as merged by the server. But we don't need to get them again right now; 78 // the charm may not need to ask again before the hook finishes. 79 ctx.settings = nil 80 err = ctx.accessor.Merge(ctx.applicationName, settings) 81 } 82 return errors.Annotate(err, "cannot write settings") 83 } 84 85 // LeaderSettings is part of the jujuc.Context interface. 86 func (ctx *leadershipContext) LeaderSettings() (map[string]string, error) { 87 if ctx.settings == nil { 88 var err error 89 ctx.settings, err = ctx.accessor.Read(ctx.applicationName) 90 if err != nil { 91 return nil, errors.Annotate(err, "cannot read settings") 92 } 93 } 94 result := map[string]string{} 95 for key, value := range ctx.settings { 96 result[key] = value 97 } 98 return result, nil 99 } 100 101 func (ctx *leadershipContext) ensureLeader() error { 102 if ctx.isMinion { 103 return errIsMinion 104 } 105 success := ctx.tracker.ClaimLeader().Wait() 106 if !success { 107 ctx.isMinion = true 108 return errIsMinion 109 } 110 return nil 111 }