github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/common/leadership.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package common 5 6 import ( 7 "github.com/juju/errors" 8 "github.com/juju/names/v5" 9 10 apiservererrors "github.com/juju/juju/apiserver/errors" 11 "github.com/juju/juju/apiserver/facade" 12 "github.com/juju/juju/core/leadership" 13 "github.com/juju/juju/core/permission" 14 "github.com/juju/juju/rpc/params" 15 "github.com/juju/juju/state" 16 ) 17 18 //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/leadership.go github.com/juju/juju/apiserver/common LeadershipPinningBackend,LeadershipMachine 19 20 // LeadershipMachine is an indirection for state.machine. 21 type LeadershipMachine interface { 22 ApplicationNames() ([]string, error) 23 } 24 25 type leadershipMachine struct { 26 *state.Machine 27 } 28 29 // LeadershipPinningBackend describes state method wrappers used by this API. 30 type LeadershipPinningBackend interface { 31 Machine(string) (LeadershipMachine, error) 32 } 33 34 type leadershipPinningBackend struct { 35 *state.State 36 } 37 38 // Machine wraps state.Machine to return an implementation 39 // of the LeadershipMachine indirection. 40 func (s leadershipPinningBackend) Machine(name string) (LeadershipMachine, error) { 41 m, err := s.State.Machine(name) 42 if err != nil { 43 return nil, err 44 } 45 return leadershipMachine{m}, nil 46 } 47 48 // NewLeadershipPinningFromContext creates and returns a new leadership from 49 // a facade context. 50 // This signature is suitable for facade registration. 51 func NewLeadershipPinningFromContext(ctx facade.Context) (*LeadershipPinning, error) { 52 st := ctx.State() 53 model, err := st.Model() 54 if err != nil { 55 return nil, errors.Trace(err) 56 } 57 pinner, err := ctx.LeadershipPinner(model.UUID()) 58 if err != nil { 59 return nil, errors.Trace(err) 60 } 61 return NewLeadershipPinning(leadershipPinningBackend{st}, model.ModelTag(), pinner, ctx.Auth()) 62 } 63 64 // NewLeadershipPinning creates and returns a new leadership API from the 65 // input tag, Pinner implementation and facade Authorizer. 66 func NewLeadershipPinning( 67 st LeadershipPinningBackend, modelTag names.ModelTag, pinner leadership.Pinner, authorizer facade.Authorizer, 68 ) (*LeadershipPinning, error) { 69 return &LeadershipPinning{ 70 st: st, 71 modelTag: modelTag, 72 pinner: pinner, 73 authorizer: authorizer, 74 }, nil 75 } 76 77 // LeadershipPinning defines a type for pinning and unpinning application 78 // leaders. 79 type LeadershipPinning struct { 80 st LeadershipPinningBackend 81 modelTag names.ModelTag 82 pinner leadership.Pinner 83 authorizer facade.Authorizer 84 } 85 86 // PinnedLeadership returns all pinned applications and the entities that 87 // require their pinned behaviour, for leadership in the current model. 88 func (a *LeadershipPinning) PinnedLeadership() (params.PinnedLeadershipResult, error) { 89 result := params.PinnedLeadershipResult{} 90 91 err := a.authorizer.HasPermission(permission.ReadAccess, a.modelTag) 92 if err != nil { 93 return result, errors.Trace(err) 94 } 95 96 result.Result, err = a.pinner.PinnedLeadership() 97 if err != nil { 98 result.Error = apiservererrors.ServerError(err) 99 } 100 return result, nil 101 } 102 103 // PinApplicationLeaders pins leadership for applications based on the auth 104 // tag provided. 105 func (a *LeadershipPinning) PinApplicationLeaders() (params.PinApplicationsResults, error) { 106 if !a.authorizer.AuthMachineAgent() { 107 return params.PinApplicationsResults{}, apiservererrors.ErrPerm 108 } 109 110 tag := a.authorizer.GetAuthTag() 111 switch tag.Kind() { 112 case names.MachineTagKind: 113 return a.pinMachineApplications(tag) 114 default: 115 return params.PinApplicationsResults{}, apiservererrors.ErrPerm 116 } 117 } 118 119 // UnpinApplicationLeaders unpins leadership for applications based on the auth 120 // tag provided. 121 func (a *LeadershipPinning) UnpinApplicationLeaders() (params.PinApplicationsResults, error) { 122 if !a.authorizer.AuthMachineAgent() { 123 return params.PinApplicationsResults{}, apiservererrors.ErrPerm 124 } 125 126 tag := a.authorizer.GetAuthTag() 127 switch tag.Kind() { 128 case names.MachineTagKind: 129 return a.unpinMachineApplications(tag) 130 default: 131 return params.PinApplicationsResults{}, apiservererrors.ErrPerm 132 } 133 } 134 135 // GetMachineApplicationNames returns the applications associated with a 136 // machine. 137 func (a *LeadershipPinning) GetMachineApplicationNames(id string) ([]string, error) { 138 m, err := a.st.Machine(id) 139 if err != nil { 140 return nil, errors.Trace(err) 141 } 142 apps, err := m.ApplicationNames() 143 if err != nil { 144 return nil, errors.Trace(err) 145 } 146 return apps, nil 147 } 148 149 // PinApplicationLeadersByName takes a slice of application names and attempts 150 // to pin them accordingly. 151 func (a *LeadershipPinning) PinApplicationLeadersByName(tag names.Tag, appNames []string) (params.PinApplicationsResults, error) { 152 return a.pinAppLeadersOps(tag, appNames, a.pinner.PinLeadership) 153 } 154 155 // UnpinApplicationLeadersByName takes a slice of application names and 156 // attempts to unpin them accordingly. 157 func (a *LeadershipPinning) UnpinApplicationLeadersByName(tag names.Tag, appNames []string) (params.PinApplicationsResults, error) { 158 return a.pinAppLeadersOps(tag, appNames, a.pinner.UnpinLeadership) 159 } 160 161 // pinMachineApplications pins leadership for applications represented by units 162 // running on the auth'd machine. 163 func (a *LeadershipPinning) pinMachineApplications(tag names.Tag) (params.PinApplicationsResults, error) { 164 appNames, err := a.GetMachineApplicationNames(tag.Id()) 165 if err != nil { 166 return params.PinApplicationsResults{}, apiservererrors.ErrPerm 167 } 168 return a.pinAppLeadersOps(tag, appNames, a.pinner.PinLeadership) 169 } 170 171 // unpinMachineApplications unpins leadership for applications represented by 172 // units running on the auth'd machine. 173 func (a *LeadershipPinning) unpinMachineApplications(tag names.Tag) (params.PinApplicationsResults, error) { 174 appNames, err := a.GetMachineApplicationNames(tag.Id()) 175 if err != nil { 176 return params.PinApplicationsResults{}, apiservererrors.ErrPerm 177 } 178 return a.pinAppLeadersOps(tag, appNames, a.pinner.UnpinLeadership) 179 } 180 181 // pinAppLeadersOps runs the input pin/unpin operation against all 182 // applications entities. 183 // An assumption is made that the validity of the auth tag has been verified 184 // by the caller. 185 func (a *LeadershipPinning) pinAppLeadersOps(tag names.Tag, appNames []string, op func(string, string) error) (params.PinApplicationsResults, error) { 186 var result params.PinApplicationsResults 187 188 results := make([]params.PinApplicationResult, len(appNames)) 189 for i, app := range appNames { 190 results[i] = params.PinApplicationResult{ 191 ApplicationName: app, 192 } 193 if err := op(app, tag.String()); err != nil { 194 results[i].Error = apiservererrors.ServerError(err) 195 } 196 } 197 result.Results = results 198 return result, nil 199 }