github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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 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 if !lsa.isLeaderFn(serviceTag.Id(), callerUnitId) || !lsa.authorizer.AuthUnitAgent() { 78 currErr.Error = common.ServerError(common.ErrPerm) 79 continue 80 } 81 82 // TODO(katco-): <2015-01-21 Wed> 83 // There is a race-condition here: if this unit should lose 84 // leadership status between the check above, and actually 85 // writing the settings, another unit could obtain leadership, 86 // write settings, and then those settings could be 87 // overwritten by this request. This will be addressed in a 88 // future PR. 89 90 err := lsa.mergeSettingsChunkFn(serviceTag.Id(), arg.Settings) 91 if err != nil { 92 currErr.Error = common.ServerError(err) 93 } 94 } 95 96 return params.ErrorResults{Results: errors}, nil 97 } 98 99 // Read reads leadership settings for the provided service ID. Any 100 // unit of the service may perform this operation. 101 func (lsa *LeadershipSettingsAccessor) Read(bulkArgs params.Entities) (params.GetLeadershipSettingsBulkResults, error) { 102 103 results := make([]params.GetLeadershipSettingsResult, len(bulkArgs.Entities)) 104 for argIdx, arg := range bulkArgs.Entities { 105 106 result := &results[argIdx] 107 108 serviceTag, parseErr := parseServiceTag(arg.Tag) 109 if parseErr != nil { 110 result.Error = parseErr 111 continue 112 } 113 114 if !lsa.authorizer.AuthUnitAgent() { 115 result.Error = common.ServerError(common.ErrPerm) 116 continue 117 } 118 119 settings, err := lsa.getSettingsFn(serviceTag.Id()) 120 if err != nil { 121 result.Error = common.ServerError(err) 122 continue 123 } 124 125 result.Settings = settings 126 } 127 128 return params.GetLeadershipSettingsBulkResults{results}, nil 129 } 130 131 // WatchLeadershipSettings will block the caller until leadership settings 132 // for the given service ID change. 133 func (lsa *LeadershipSettingsAccessor) WatchLeadershipSettings(arg params.Entities) (params.NotifyWatchResults, error) { 134 135 results := make([]params.NotifyWatchResult, len(arg.Entities)) 136 for entIdx, entity := range arg.Entities { 137 result := &results[entIdx] 138 139 serviceTag, parseErr := parseServiceTag(entity.Tag) 140 if parseErr != nil { 141 result.Error = parseErr 142 continue 143 } 144 145 watcherId, err := lsa.registerWatcherFn(serviceTag.Id()) 146 if err != nil { 147 result.Error = common.ServerError(err) 148 continue 149 } 150 151 result.NotifyWatcherId = watcherId 152 } 153 return params.NotifyWatchResults{Results: results}, nil 154 } 155 156 // parseServiceTag attempts to parse the given serviceTag, and if it 157 // fails returns an error which is safe to return to the client -- in 158 // both a structure and security context. 159 func parseServiceTag(serviceTag string) (names.ServiceTag, *params.Error) { 160 parsedTag, err := names.ParseServiceTag(serviceTag) 161 if err != nil { 162 // We intentionally mask the real error for security purposes. 163 return names.ServiceTag{}, common.ServerError(common.ErrPerm) 164 } 165 return parsedTag, nil 166 }