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  }