github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/agent/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/errors"
     8  	"gopkg.in/juju/names.v2"
     9  
    10  	"github.com/juju/juju/apiserver/common"
    11  	"github.com/juju/juju/apiserver/facade"
    12  	"github.com/juju/juju/apiserver/params"
    13  	"github.com/juju/juju/core/leadership"
    14  )
    15  
    16  // NewLeadershipSettingsAccessor creates a new
    17  // LeadershipSettingsAccessor.
    18  func NewLeadershipSettingsAccessor(
    19  	authorizer facade.Authorizer,
    20  	registerWatcherFn RegisterWatcherFn,
    21  	getSettingsFn GetSettingsFn,
    22  	leaderCheckFn LeaderCheckFn,
    23  	mergeSettingsChunkFn MergeSettingsChunkFn,
    24  ) *LeadershipSettingsAccessor {
    25  
    26  	return &LeadershipSettingsAccessor{
    27  		authorizer:           authorizer,
    28  		registerWatcherFn:    registerWatcherFn,
    29  		getSettingsFn:        getSettingsFn,
    30  		leaderCheckFn:        leaderCheckFn,
    31  		mergeSettingsChunkFn: mergeSettingsChunkFn,
    32  	}
    33  }
    34  
    35  // SettingsChangeNotifierFn declares a function-type which will return
    36  // a channel that can be blocked on to be notified of setting changes
    37  // for the provided document key.
    38  type RegisterWatcherFn func(serviceId string) (watcherId string, _ error)
    39  
    40  // GetSettingsFn declares a function-type which will return leadership
    41  // settings for the given service ID.
    42  type GetSettingsFn func(serviceId string) (map[string]string, error)
    43  
    44  // LeaderCheckFn returns a Token whose Check method will return an error
    45  // if the unit is not leader of the service.
    46  type LeaderCheckFn func(serviceId, unitId string) leadership.Token
    47  
    48  // MergeSettingsChunk declares a function-type which will write the
    49  // provided settings chunk into the greater leadership settings for
    50  // the provided service ID, so long as the supplied Token remains
    51  // valid.
    52  type MergeSettingsChunkFn func(token leadership.Token, serviceId string, settings map[string]string) error
    53  
    54  // LeadershipSettingsAccessor provides a type which can read, write,
    55  // and watch leadership settings.
    56  type LeadershipSettingsAccessor struct {
    57  	authorizer           facade.Authorizer
    58  	registerWatcherFn    RegisterWatcherFn
    59  	getSettingsFn        GetSettingsFn
    60  	leaderCheckFn        LeaderCheckFn
    61  	mergeSettingsChunkFn MergeSettingsChunkFn
    62  }
    63  
    64  func (lsa *LeadershipSettingsAccessor) callerApplication() (string, error) {
    65  	var appName string
    66  	switch authTag := lsa.authorizer.GetAuthTag().(type) {
    67  	case names.UnitTag:
    68  		var err error
    69  		appName, err = names.UnitApplication(authTag.Id())
    70  		if err != nil {
    71  			return "", err
    72  		}
    73  	case names.ApplicationTag:
    74  		appName = authTag.Id()
    75  	default:
    76  		return "", errors.Errorf("invalid auth tag type %T: %v", authTag, authTag.String())
    77  	}
    78  	return appName, nil
    79  }
    80  
    81  // Merge merges in the provided leadership settings. Only leaders for
    82  // the given service may perform this operation.
    83  func (lsa *LeadershipSettingsAccessor) Merge(bulkArgs params.MergeLeadershipSettingsBulkParams) (params.ErrorResults, error) {
    84  
    85  	requireAppName, err := lsa.callerApplication()
    86  	if err != nil {
    87  		return params.ErrorResults{}, err
    88  	}
    89  	// Start out assuming the caller is a unit (for older clients).
    90  	callerUnitId := lsa.authorizer.GetAuthTag().Id()
    91  
    92  	results := make([]params.ErrorResult, len(bulkArgs.Params))
    93  
    94  	for i, arg := range bulkArgs.Params {
    95  		result := &results[i]
    96  
    97  		// TODO(fwereade): we shoudn't assume a ApplicationTag: we should
    98  		// use an actual auth func to determine permissions.
    99  		applicationTag, err := names.ParseApplicationTag(arg.ApplicationTag)
   100  		if err != nil {
   101  			result.Error = common.ServerError(err)
   102  			continue
   103  		}
   104  
   105  		// If a unit is passed in as an arg, use that instead of the caller id.
   106  		if arg.UnitTag != "" {
   107  			unitTag, err := names.ParseUnitTag(arg.UnitTag)
   108  			if err != nil {
   109  				result.Error = common.ServerError(err)
   110  				continue
   111  			}
   112  			callerUnitId = unitTag.Id()
   113  			unitAppName, err := names.UnitApplication(callerUnitId)
   114  			if err != nil || unitAppName != requireAppName {
   115  				result.Error = common.ServerError(common.ErrPerm)
   116  				continue
   117  			}
   118  		}
   119  
   120  		appName := applicationTag.Id()
   121  		if appName != requireAppName {
   122  			result.Error = common.ServerError(common.ErrPerm)
   123  			continue
   124  		}
   125  
   126  		token := lsa.leaderCheckFn(appName, callerUnitId)
   127  		err = lsa.mergeSettingsChunkFn(token, appName, arg.Settings)
   128  		if err != nil {
   129  			result.Error = common.ServerError(err)
   130  		}
   131  	}
   132  
   133  	return params.ErrorResults{Results: results}, nil
   134  }
   135  
   136  // Read reads leadership settings for the provided service ID. Any
   137  // unit of the service may perform this operation.
   138  func (lsa *LeadershipSettingsAccessor) Read(bulkArgs params.Entities) (params.GetLeadershipSettingsBulkResults, error) {
   139  
   140  	requireAppName, err := lsa.callerApplication()
   141  	if err != nil {
   142  		return params.GetLeadershipSettingsBulkResults{}, err
   143  	}
   144  	results := make([]params.GetLeadershipSettingsResult, len(bulkArgs.Entities))
   145  
   146  	for i, arg := range bulkArgs.Entities {
   147  		result := &results[i]
   148  
   149  		// TODO(fwereade): we shoudn't assume a ApplicationTag: we should
   150  		// use an actual auth func to determine permissions.
   151  		applicationTag, err := names.ParseApplicationTag(arg.Tag)
   152  		if err != nil {
   153  			result.Error = common.ServerError(err)
   154  			continue
   155  		}
   156  
   157  		appName := applicationTag.Id()
   158  		if appName != requireAppName {
   159  			result.Error = common.ServerError(common.ErrPerm)
   160  			continue
   161  		}
   162  
   163  		settings, err := lsa.getSettingsFn(appName)
   164  		if err != nil {
   165  			result.Error = common.ServerError(err)
   166  			continue
   167  		}
   168  
   169  		result.Settings = settings
   170  	}
   171  
   172  	return params.GetLeadershipSettingsBulkResults{results}, nil
   173  }
   174  
   175  // WatchLeadershipSettings will block the caller until leadership settings
   176  // for the given service ID change.
   177  func (lsa *LeadershipSettingsAccessor) WatchLeadershipSettings(bulkArgs params.Entities) (params.NotifyWatchResults, error) {
   178  
   179  	requireAppName, err := lsa.callerApplication()
   180  	if err != nil {
   181  		return params.NotifyWatchResults{}, err
   182  	}
   183  	results := make([]params.NotifyWatchResult, len(bulkArgs.Entities))
   184  
   185  	for i, arg := range bulkArgs.Entities {
   186  		result := &results[i]
   187  
   188  		// TODO(fwereade): we shoudn't assume a ApplicationTag: we should
   189  		// use an actual auth func to determine permissions.
   190  		applicationTag, err := names.ParseApplicationTag(arg.Tag)
   191  		if err != nil {
   192  			result.Error = common.ServerError(err)
   193  			continue
   194  		}
   195  
   196  		appName := applicationTag.Id()
   197  		if appName != requireAppName {
   198  			result.Error = common.ServerError(common.ErrPerm)
   199  			continue
   200  		}
   201  
   202  		watcherId, err := lsa.registerWatcherFn(appName)
   203  		if err != nil {
   204  			result.Error = common.ServerError(err)
   205  			continue
   206  		}
   207  
   208  		result.NotifyWatcherId = watcherId
   209  	}
   210  	return params.NotifyWatchResults{Results: results}, nil
   211  }