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  }