github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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(applicationName string) (map[string]string, error) 21 Merge(applicationName, unitName string, settings map[string]string) error 22 } 23 24 // LeadershipContext provides several hooks.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 unitName string 37 38 isMinion bool 39 settings map[string]string 40 } 41 42 func NewLeadershipContext(accessor LeadershipSettingsAccessor, tracker leadership.Tracker, unitName string) LeadershipContext { 43 return &leadershipContext{ 44 accessor: accessor, 45 tracker: tracker, 46 applicationName: tracker.ApplicationName(), 47 unitName: unitName, 48 } 49 } 50 51 // newLeadershipContext allows us to swap out the leadership context creator for 52 // factory tests. 53 var newLeadershipContext = NewLeadershipContext 54 55 // IsLeader is part of the hooks.Context interface. 56 func (ctx *leadershipContext) IsLeader() (bool, error) { 57 // This doesn't technically need an error return, but that feels like a 58 // happy accident of the current implementation and not a reason to change 59 // the interface we're implementing. 60 err := ctx.ensureLeader() 61 switch err { 62 case nil: 63 return true, nil 64 case errIsMinion: 65 return false, nil 66 } 67 return false, errors.Trace(err) 68 } 69 70 // WriteLeaderSettings is part of the hooks.Context interface. 71 func (ctx *leadershipContext) WriteLeaderSettings(settings map[string]string) error { 72 // This may trigger a lease refresh; it would be desirable to use a less 73 // eager approach here, but we're working around a race described in 74 // `apiserver/leadership.LeadershipSettingsAccessor.Merge`, and as of 75 // 2015-02-19 it's better to stay eager. 76 err := ctx.ensureLeader() 77 if err == errIsMinion { 78 logger.Warningf("skipping write settings; not the leader") 79 return nil 80 } 81 if err == nil { 82 // Clear local settings; if we need them again we should use the values 83 // as merged by the server. But we don't need to get them again right now; 84 // the charm may not need to ask again before the hook finishes. 85 ctx.settings = nil 86 err = ctx.accessor.Merge(ctx.applicationName, ctx.unitName, settings) 87 } 88 return errors.Annotate(err, "cannot write settings") 89 } 90 91 // LeaderSettings is part of the hooks.Context interface. 92 func (ctx *leadershipContext) LeaderSettings() (map[string]string, error) { 93 if ctx.settings == nil { 94 var err error 95 ctx.settings, err = ctx.accessor.Read(ctx.applicationName) 96 if err != nil { 97 return nil, errors.Annotate(err, "cannot read settings") 98 } 99 } 100 result := map[string]string{} 101 for key, value := range ctx.settings { 102 result[key] = value 103 } 104 return result, nil 105 } 106 107 func (ctx *leadershipContext) ensureLeader() error { 108 if ctx.isMinion { 109 return errIsMinion 110 } 111 success := ctx.tracker.ClaimLeader().Wait() 112 if !success { 113 ctx.isMinion = true 114 return errIsMinion 115 } 116 return nil 117 }