github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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(serviceName string) (map[string]string, error)
    21  	Merge(serviceName string, settings map[string]string) error
    22  }
    23  
    24  // LeadershipContext provides several jujuc.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  
    37  	isMinion bool
    38  	settings map[string]string
    39  }
    40  
    41  func NewLeadershipContext(accessor LeadershipSettingsAccessor, tracker leadership.Tracker) LeadershipContext {
    42  	return &leadershipContext{
    43  		accessor:        accessor,
    44  		tracker:         tracker,
    45  		applicationName: tracker.ApplicationName(),
    46  	}
    47  }
    48  
    49  // newLeadershipContext allows us to swap out the leadership context creator for
    50  // factory tests.
    51  var newLeadershipContext = NewLeadershipContext
    52  
    53  // IsLeader is part of the jujuc.Context interface.
    54  func (ctx *leadershipContext) IsLeader() (bool, error) {
    55  	// This doesn't technically need an error return, but that feels like a
    56  	// happy accident of the current implementation and not a reason to change
    57  	// the interface we're implementing.
    58  	err := ctx.ensureLeader()
    59  	switch err {
    60  	case nil:
    61  		return true, nil
    62  	case errIsMinion:
    63  		return false, nil
    64  	}
    65  	return false, errors.Trace(err)
    66  }
    67  
    68  // WriteLeaderSettings is part of the jujuc.Context interface.
    69  func (ctx *leadershipContext) WriteLeaderSettings(settings map[string]string) error {
    70  	// This may trigger a lease refresh; it would be desirable to use a less
    71  	// eager approach here, but we're working around a race described in
    72  	// `apiserver/leadership.LeadershipSettingsAccessor.Merge`, and as of
    73  	// 2015-02-19 it's better to stay eager.
    74  	err := ctx.ensureLeader()
    75  	if err == nil {
    76  		// Clear local settings; if we need them again we should use the values
    77  		// as merged by the server. But we don't need to get them again right now;
    78  		// the charm may not need to ask again before the hook finishes.
    79  		ctx.settings = nil
    80  		err = ctx.accessor.Merge(ctx.applicationName, settings)
    81  	}
    82  	return errors.Annotate(err, "cannot write settings")
    83  }
    84  
    85  // LeaderSettings is part of the jujuc.Context interface.
    86  func (ctx *leadershipContext) LeaderSettings() (map[string]string, error) {
    87  	if ctx.settings == nil {
    88  		var err error
    89  		ctx.settings, err = ctx.accessor.Read(ctx.applicationName)
    90  		if err != nil {
    91  			return nil, errors.Annotate(err, "cannot read settings")
    92  		}
    93  	}
    94  	result := map[string]string{}
    95  	for key, value := range ctx.settings {
    96  		result[key] = value
    97  	}
    98  	return result, nil
    99  }
   100  
   101  func (ctx *leadershipContext) ensureLeader() error {
   102  	if ctx.isMinion {
   103  		return errIsMinion
   104  	}
   105  	success := ctx.tracker.ClaimLeader().Wait()
   106  	if !success {
   107  		ctx.isMinion = true
   108  		return errIsMinion
   109  	}
   110  	return nil
   111  }