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  }