github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/apiserver/leadership/settings.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package leadership 5 6 import ( 7 "github.com/juju/names" 8 9 "github.com/juju/juju/apiserver/common" 10 "github.com/juju/juju/apiserver/params" 11 ) 12 13 // NewLeadershipSettingsAccessor creates a new 14 // LeadershipSettingsAccessor. 15 func NewLeadershipSettingsAccessor( 16 authorizer common.Authorizer, 17 registerWatcherFn RegisterWatcherFn, 18 getSettingsFn GetSettingsFn, 19 mergeSettingsChunkFn MergeSettingsChunkFn, 20 isLeaderFn IsLeaderFn, 21 ) *LeadershipSettingsAccessor { 22 23 return &LeadershipSettingsAccessor{ 24 authorizer: authorizer, 25 registerWatcherFn: registerWatcherFn, 26 getSettingsFn: getSettingsFn, 27 mergeSettingsChunkFn: mergeSettingsChunkFn, 28 isLeaderFn: isLeaderFn, 29 } 30 } 31 32 // SettingsChangeNotifierFn declares a function-type which will return 33 // a channel that can be blocked on to be notified of setting changes 34 // for the provided document key. 35 type RegisterWatcherFn func(serviceId string) (watcherId string, _ error) 36 37 // GetSettingsFn declares a function-type which will return leadership 38 // settings for the given service ID. 39 type GetSettingsFn func(serviceId string) (map[string]string, error) 40 41 // MergeSettingsChunk declares a function-type which will write the 42 // provided settings chunk into the greater leadership settings for 43 // the provided service ID. 44 type MergeSettingsChunkFn func(serviceId string, settings map[string]string) error 45 46 // IsLeaderFn declares a function-type which will return whether the 47 // given service-unit-id combination is currently the leader. 48 type IsLeaderFn func(serviceId, unitId string) (bool, error) 49 50 // LeadershipSettingsAccessor provides a type which can read, write, 51 // and watch leadership settings. 52 type LeadershipSettingsAccessor struct { 53 authorizer common.Authorizer 54 registerWatcherFn RegisterWatcherFn 55 getSettingsFn GetSettingsFn 56 mergeSettingsChunkFn MergeSettingsChunkFn 57 isLeaderFn IsLeaderFn 58 } 59 60 // Merge merges in the provided leadership settings. Only leaders for 61 // the given service may perform this operation. 62 func (lsa *LeadershipSettingsAccessor) Merge(bulkArgs params.MergeLeadershipSettingsBulkParams) (params.ErrorResults, error) { 63 64 callerUnitId := lsa.authorizer.GetAuthTag().Id() 65 errors := make([]params.ErrorResult, len(bulkArgs.Params)) 66 67 for argIdx, arg := range bulkArgs.Params { 68 69 currErr := &errors[argIdx] 70 serviceTag, parseErr := parseServiceTag(arg.ServiceTag) 71 if parseErr != nil { 72 currErr.Error = parseErr 73 continue 74 } 75 76 // Check to ensure we can write settings. 77 isLeader, err := lsa.isLeaderFn(serviceTag.Id(), callerUnitId) 78 if err != nil { 79 currErr.Error = common.ServerError(err) 80 continue 81 } 82 if !isLeader || !lsa.authorizer.AuthUnitAgent() { 83 currErr.Error = common.ServerError(common.ErrPerm) 84 continue 85 } 86 87 // TODO(katco-): <2015-01-21 Wed> 88 // There is a race-condition here: if this unit should lose 89 // leadership status between the check above, and actually 90 // writing the settings, another unit could obtain leadership, 91 // write settings, and then those settings could be 92 // overwritten by this request. This will be addressed in a 93 // future PR. 94 95 err = lsa.mergeSettingsChunkFn(serviceTag.Id(), arg.Settings) 96 if err != nil { 97 currErr.Error = common.ServerError(err) 98 } 99 } 100 101 return params.ErrorResults{Results: errors}, nil 102 } 103 104 // Read reads leadership settings for the provided service ID. Any 105 // unit of the service may perform this operation. 106 func (lsa *LeadershipSettingsAccessor) Read(bulkArgs params.Entities) (params.GetLeadershipSettingsBulkResults, error) { 107 108 results := make([]params.GetLeadershipSettingsResult, len(bulkArgs.Entities)) 109 for argIdx, arg := range bulkArgs.Entities { 110 111 result := &results[argIdx] 112 113 serviceTag, parseErr := parseServiceTag(arg.Tag) 114 if parseErr != nil { 115 result.Error = parseErr 116 continue 117 } 118 119 if !lsa.authorizer.AuthUnitAgent() { 120 result.Error = common.ServerError(common.ErrPerm) 121 continue 122 } 123 124 settings, err := lsa.getSettingsFn(serviceTag.Id()) 125 if err != nil { 126 result.Error = common.ServerError(err) 127 continue 128 } 129 130 result.Settings = settings 131 } 132 133 return params.GetLeadershipSettingsBulkResults{results}, nil 134 } 135 136 // WatchLeadershipSettings will block the caller until leadership settings 137 // for the given service ID change. 138 func (lsa *LeadershipSettingsAccessor) WatchLeadershipSettings(arg params.Entities) (params.NotifyWatchResults, error) { 139 140 results := make([]params.NotifyWatchResult, len(arg.Entities)) 141 for entIdx, entity := range arg.Entities { 142 result := &results[entIdx] 143 144 serviceTag, parseErr := parseServiceTag(entity.Tag) 145 if parseErr != nil { 146 result.Error = parseErr 147 continue 148 } 149 150 watcherId, err := lsa.registerWatcherFn(serviceTag.Id()) 151 if err != nil { 152 result.Error = common.ServerError(err) 153 continue 154 } 155 156 result.NotifyWatcherId = watcherId 157 } 158 return params.NotifyWatchResults{Results: results}, nil 159 } 160 161 // parseServiceTag attempts to parse the given serviceTag, and if it 162 // fails returns an error which is safe to return to the client -- in 163 // both a structure and security context. 164 func parseServiceTag(serviceTag string) (names.ServiceTag, *params.Error) { 165 parsedTag, err := names.ParseServiceTag(serviceTag) 166 if err != nil { 167 // We intentionally mask the real error for security purposes. 168 return names.ServiceTag{}, common.ServerError(common.ErrPerm) 169 } 170 return parsedTag, nil 171 }