github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/uniter/operation/leader.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package operation
     5  
     6  import (
     7  	"github.com/juju/charm/v12/hooks"
     8  	"github.com/juju/errors"
     9  
    10  	"github.com/juju/juju/worker/uniter/hook"
    11  	"github.com/juju/juju/worker/uniter/remotestate"
    12  )
    13  
    14  type acceptLeadership struct {
    15  	DoesNotRequireMachineLock
    16  }
    17  
    18  // String is part of the Operation interface.
    19  func (al *acceptLeadership) String() string {
    20  	return "accept leadership"
    21  }
    22  
    23  // Prepare is part of the Operation interface.
    24  func (al *acceptLeadership) Prepare(state State) (*State, error) {
    25  	if err := al.checkState(state); err != nil {
    26  		return nil, err
    27  	}
    28  	return nil, ErrSkipExecute
    29  }
    30  
    31  // Execute is part of the Operation interface.
    32  func (al *acceptLeadership) Execute(state State) (*State, error) {
    33  	return nil, errors.New("prepare always errors; Execute is never valid")
    34  }
    35  
    36  // Commit is part of the Operation interface.
    37  func (al *acceptLeadership) Commit(state State) (*State, error) {
    38  	if err := al.checkState(state); err != nil {
    39  		return nil, err
    40  	}
    41  	if state.Leader {
    42  		// Nothing needs to be done -- leader is only set when queueing a
    43  		// leader-elected hook. Therefore, if leader is true, the appropriate
    44  		// hook must be either queued or already run.
    45  		return nil, nil
    46  	}
    47  	newState := stateChange{
    48  		Kind: RunHook,
    49  		Step: Queued,
    50  		Hook: &hook.Info{Kind: hooks.LeaderElected},
    51  	}.apply(state)
    52  	newState.Leader = true
    53  	return newState, nil
    54  }
    55  
    56  // RemoteStateChanged is called when the remote state changed during execution
    57  // of the operation.
    58  func (al *acceptLeadership) RemoteStateChanged(snapshot remotestate.Snapshot) {
    59  }
    60  
    61  func (al *acceptLeadership) checkState(state State) error {
    62  	if state.Kind != Continue {
    63  		// We'll need to queue up a hook, and we can't do that without
    64  		// stomping on existing state.
    65  		return ErrCannotAcceptLeadership
    66  	}
    67  	return nil
    68  }
    69  
    70  type resignLeadership struct {
    71  	DoesNotRequireMachineLock
    72  	logger Logger
    73  }
    74  
    75  // String is part of the Operation interface.
    76  func (rl *resignLeadership) String() string {
    77  	return "resign leadership"
    78  }
    79  
    80  // Prepare is part of the Operation interface.
    81  func (rl *resignLeadership) Prepare(state State) (*State, error) {
    82  	if !state.Leader {
    83  		// Nothing needs to be done -- state.Leader should only be set to
    84  		// false when committing the leader-deposed hook. This code is not
    85  		// helpful while Execute is a no-op, but it will become so.
    86  		return nil, ErrSkipExecute
    87  	}
    88  	return nil, nil
    89  }
    90  
    91  // Execute is part of the Operation interface.
    92  func (rl *resignLeadership) Execute(state State) (*State, error) {
    93  	// TODO(fwereade): this hits a lot of interestingly intersecting problems.
    94  	//
    95  	// 1) we can't yet create a sufficiently dumbed-down hook context for a
    96  	//    leader-deposed hook to run as specced. (This is the proximate issue,
    97  	//    and is sufficient to prevent us from implementing this op right.)
    98  	// 2) we want to write a state-file change, so this has to be an operation
    99  	//    (or, at least, it has to be serialized with all other operations).
   100  	//      * note that the change we write must *not* include the RunHook
   101  	//        operation for leader-deposed -- we want to run this at high
   102  	//        priority, in any possible state, and not to disturn what's
   103  	//        there other than to note that we no longer think we're leader.
   104  	// 3) the hook execution itself *might* not need to be serialized with
   105  	//    other operations, which is moot until we consider that:
   106  	// 4) we want to invoke this behaviour from elsewhere (ie when we don't
   107  	//    have an api connection available), but:
   108  	// 5) we can't get around the serialization requirement in (2).
   109  	//
   110  	// So. I *think* that the right approach is to implement a no-api uniter
   111  	// variant, that we run *instead of* the normal uniter when the API is
   112  	// unavailable, and replace with a real uniter when appropriate; this
   113  	// implies that we need to take care not to allow the implementations to
   114  	// diverge, but implementing them both as "uniters" is probably the best
   115  	// way to encourage logic-sharing and prevent that problem.
   116  	//
   117  	// In the short term, though, we can just run leader-deposed as soon as we
   118  	// can build the right environment. Not sure whether this particular type
   119  	// will still be justified, or whether it'll just be a plain old RunHook --
   120  	// I *think* it will stay, because the state-writing behaviour will stay
   121  	// very different (ie just write `.Leader = false` and don't step on pre-
   122  	// queued hooks).
   123  	rl.logger.Warningf("we should run a leader-deposed hook here, but we can't yet")
   124  	return nil, nil
   125  }
   126  
   127  // Commit is part of the Operation interface.
   128  func (rl *resignLeadership) Commit(state State) (*State, error) {
   129  	state.Leader = false
   130  	return &state, nil
   131  }
   132  
   133  // RemoteStateChanged is called when the remote state changed during execution
   134  // of the operation.
   135  func (rl *resignLeadership) RemoteStateChanged(snapshot remotestate.Snapshot) {
   136  }