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 }