github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/uniter/runner/context/leader.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package context
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  
     9  	"github.com/juju/juju/core/leadership"
    10  )
    11  
    12  var (
    13  	errIsMinion = errors.New("not the leader")
    14  )
    15  
    16  // LeadershipSettingsAccessor is an interface that allows us not to have
    17  // to use the concrete `api/uniter/LeadershipSettingsAccessor` type, thus
    18  // simplifying testing.
    19  type LeadershipSettingsAccessor interface {
    20  	Read(applicationName string) (map[string]string, error)
    21  	Merge(applicationName, unitName string, settings map[string]string) error
    22  }
    23  
    24  // LeadershipContext provides several hooks.Context methods. It
    25  // exists separately of HookContext for clarity, and ease of testing.
    26  type LeadershipContext interface {
    27  	IsLeader() (bool, error)
    28  	LeaderSettings() (map[string]string, error)
    29  	WriteLeaderSettings(map[string]string) error
    30  }
    31  
    32  type leadershipContext struct {
    33  	accessor        LeadershipSettingsAccessor
    34  	tracker         leadership.Tracker
    35  	applicationName string
    36  	unitName        string
    37  
    38  	isMinion bool
    39  	settings map[string]string
    40  }
    41  
    42  func NewLeadershipContext(accessor LeadershipSettingsAccessor, tracker leadership.Tracker, unitName string) LeadershipContext {
    43  	return &leadershipContext{
    44  		accessor:        accessor,
    45  		tracker:         tracker,
    46  		applicationName: tracker.ApplicationName(),
    47  		unitName:        unitName,
    48  	}
    49  }
    50  
    51  // newLeadershipContext allows us to swap out the leadership context creator for
    52  // factory tests.
    53  var newLeadershipContext = NewLeadershipContext
    54  
    55  // IsLeader is part of the hooks.Context interface.
    56  func (ctx *leadershipContext) IsLeader() (bool, error) {
    57  	// This doesn't technically need an error return, but that feels like a
    58  	// happy accident of the current implementation and not a reason to change
    59  	// the interface we're implementing.
    60  	err := ctx.ensureLeader()
    61  	switch err {
    62  	case nil:
    63  		return true, nil
    64  	case errIsMinion:
    65  		return false, nil
    66  	}
    67  	return false, errors.Trace(err)
    68  }
    69  
    70  // WriteLeaderSettings is part of the hooks.Context interface.
    71  func (ctx *leadershipContext) WriteLeaderSettings(settings map[string]string) error {
    72  	// This may trigger a lease refresh; it would be desirable to use a less
    73  	// eager approach here, but we're working around a race described in
    74  	// `apiserver/leadership.LeadershipSettingsAccessor.Merge`, and as of
    75  	// 2015-02-19 it's better to stay eager.
    76  	err := ctx.ensureLeader()
    77  	if err == errIsMinion {
    78  		logger.Warningf("skipping write settings; not the leader")
    79  		return nil
    80  	}
    81  	if err == nil {
    82  		// Clear local settings; if we need them again we should use the values
    83  		// as merged by the server. But we don't need to get them again right now;
    84  		// the charm may not need to ask again before the hook finishes.
    85  		ctx.settings = nil
    86  		err = ctx.accessor.Merge(ctx.applicationName, ctx.unitName, settings)
    87  	}
    88  	return errors.Annotate(err, "cannot write settings")
    89  }
    90  
    91  // LeaderSettings is part of the hooks.Context interface.
    92  func (ctx *leadershipContext) LeaderSettings() (map[string]string, error) {
    93  	if ctx.settings == nil {
    94  		var err error
    95  		ctx.settings, err = ctx.accessor.Read(ctx.applicationName)
    96  		if err != nil {
    97  			return nil, errors.Annotate(err, "cannot read settings")
    98  		}
    99  	}
   100  	result := map[string]string{}
   101  	for key, value := range ctx.settings {
   102  		result[key] = value
   103  	}
   104  	return result, nil
   105  }
   106  
   107  func (ctx *leadershipContext) ensureLeader() error {
   108  	if ctx.isMinion {
   109  		return errIsMinion
   110  	}
   111  	success := ctx.tracker.ClaimLeader().Wait()
   112  	if !success {
   113  		ctx.isMinion = true
   114  		return errIsMinion
   115  	}
   116  	return nil
   117  }