github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/common/unitstate.go (about)

     1  // Copyright 2020 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/loggo"
     9  	"github.com/juju/names/v5"
    10  
    11  	apiservererrors "github.com/juju/juju/apiserver/errors"
    12  	"github.com/juju/juju/apiserver/facade"
    13  	"github.com/juju/juju/controller"
    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/unitstate.go github.com/juju/juju/apiserver/common UnitStateBackend,UnitStateUnit
    19  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/modeloperation.go github.com/juju/juju/state ModelOperation
    20  
    21  // UnitStateUnit describes unit-receiver state methods required
    22  // for UnitStateAPI.
    23  type UnitStateBackend interface {
    24  	ApplyOperation(state.ModelOperation) error
    25  	Unit(string) (UnitStateUnit, error)
    26  	ControllerConfig() (controller.Config, error)
    27  }
    28  
    29  // UnitStateUnit describes unit-receiver state methods required
    30  // for UnitStateAPI.
    31  type UnitStateUnit interface {
    32  	SetStateOperation(*state.UnitState, state.UnitStateSizeLimits) state.ModelOperation
    33  	State() (*state.UnitState, error)
    34  }
    35  
    36  // UnitStateState implements the UnitStateBackend indirection
    37  // over state.State.
    38  type UnitStateState struct {
    39  	St *state.State
    40  }
    41  
    42  func (s UnitStateState) ApplyOperation(op state.ModelOperation) error {
    43  	return s.St.ApplyOperation(op)
    44  }
    45  
    46  func (s UnitStateState) Unit(name string) (UnitStateUnit, error) {
    47  	return s.St.Unit(name)
    48  }
    49  
    50  func (s UnitStateState) ControllerConfig() (controller.Config, error) {
    51  	return s.St.ControllerConfig()
    52  }
    53  
    54  type UnitStateAPI struct {
    55  	backend   UnitStateBackend
    56  	resources facade.Resources
    57  
    58  	logger loggo.Logger
    59  
    60  	AccessMachine GetAuthFunc
    61  	accessUnit    GetAuthFunc
    62  }
    63  
    64  // NewExternalUnitStateAPI can be used for API registration.
    65  func NewExternalUnitStateAPI(
    66  	st *state.State,
    67  	resources facade.Resources,
    68  	authorizer facade.Authorizer,
    69  	accessUnit GetAuthFunc,
    70  	logger loggo.Logger,
    71  ) *UnitStateAPI {
    72  	return NewUnitStateAPI(UnitStateState{St: st}, resources, authorizer, accessUnit, logger)
    73  }
    74  
    75  // NewUnitStateAPI returns a new UnitStateAPI. Currently both
    76  // GetAuthFuncs can used to determine current permissions.
    77  func NewUnitStateAPI(
    78  	backend UnitStateBackend,
    79  	resources facade.Resources,
    80  	authorizer facade.Authorizer,
    81  	accessUnit GetAuthFunc,
    82  	logger loggo.Logger,
    83  ) *UnitStateAPI {
    84  	return &UnitStateAPI{
    85  		backend:    backend,
    86  		resources:  resources,
    87  		accessUnit: accessUnit,
    88  		logger:     logger,
    89  	}
    90  }
    91  
    92  // State returns the state persisted by the charm running in this unit
    93  // and the state internal to the uniter for this unit.
    94  func (u *UnitStateAPI) State(args params.Entities) (params.UnitStateResults, error) {
    95  	canAccess, err := u.accessUnit()
    96  	if err != nil {
    97  		return params.UnitStateResults{}, errors.Trace(err)
    98  	}
    99  
   100  	res := make([]params.UnitStateResult, len(args.Entities))
   101  	for i, entity := range args.Entities {
   102  		unitTag, err := names.ParseUnitTag(entity.Tag)
   103  		if err != nil {
   104  			res[i].Error = apiservererrors.ServerError(err)
   105  			continue
   106  		}
   107  
   108  		if !canAccess(unitTag) {
   109  			res[i].Error = apiservererrors.ServerError(apiservererrors.ErrPerm)
   110  			continue
   111  		}
   112  
   113  		unit, err := u.getUnit(unitTag)
   114  		if err != nil {
   115  			res[i].Error = apiservererrors.ServerError(err)
   116  			continue
   117  		}
   118  		unitState, err := unit.State()
   119  		if err != nil {
   120  			res[i].Error = apiservererrors.ServerError(err)
   121  			continue
   122  		}
   123  
   124  		res[i].CharmState, _ = unitState.CharmState()
   125  		res[i].UniterState, _ = unitState.UniterState()
   126  		res[i].RelationState, _ = unitState.RelationState()
   127  		res[i].StorageState, _ = unitState.StorageState()
   128  		res[i].SecretState, _ = unitState.SecretState()
   129  		res[i].MeterStatusState, _ = unitState.MeterStatusState()
   130  	}
   131  
   132  	return params.UnitStateResults{Results: res}, nil
   133  }
   134  
   135  // SetState sets the state persisted by the charm running in this unit
   136  // and the state internal to the uniter for this unit.
   137  func (u *UnitStateAPI) SetState(args params.SetUnitStateArgs) (params.ErrorResults, error) {
   138  	canAccess, err := u.accessUnit()
   139  	if err != nil {
   140  		return params.ErrorResults{}, errors.Trace(err)
   141  	}
   142  
   143  	ctrlCfg, err := u.backend.ControllerConfig()
   144  	if err != nil {
   145  		return params.ErrorResults{}, errors.Trace(err)
   146  	}
   147  
   148  	res := make([]params.ErrorResult, len(args.Args))
   149  	for i, arg := range args.Args {
   150  		unitTag, err := names.ParseUnitTag(arg.Tag)
   151  		if err != nil {
   152  			res[i].Error = apiservererrors.ServerError(err)
   153  			continue
   154  		}
   155  
   156  		if !canAccess(unitTag) {
   157  			res[i].Error = apiservererrors.ServerError(apiservererrors.ErrPerm)
   158  			continue
   159  		}
   160  
   161  		unit, err := u.getUnit(unitTag)
   162  		if err != nil {
   163  			res[i].Error = apiservererrors.ServerError(err)
   164  			continue
   165  		}
   166  
   167  		unitState := state.NewUnitState()
   168  		if arg.CharmState != nil {
   169  			unitState.SetCharmState(*arg.CharmState)
   170  		}
   171  		if arg.UniterState != nil {
   172  			unitState.SetUniterState(*arg.UniterState)
   173  		}
   174  		if arg.RelationState != nil {
   175  			unitState.SetRelationState(*arg.RelationState)
   176  		}
   177  		if arg.StorageState != nil {
   178  			unitState.SetStorageState(*arg.StorageState)
   179  		}
   180  		if arg.SecretState != nil {
   181  			unitState.SetSecretState(*arg.SecretState)
   182  		}
   183  		if arg.MeterStatusState != nil {
   184  			unitState.SetMeterStatusState(*arg.MeterStatusState)
   185  		}
   186  
   187  		ops := unit.SetStateOperation(
   188  			unitState,
   189  			state.UnitStateSizeLimits{
   190  				MaxCharmStateSize: ctrlCfg.MaxCharmStateSize(),
   191  				MaxAgentStateSize: ctrlCfg.MaxAgentStateSize(),
   192  			},
   193  		)
   194  		if err = u.backend.ApplyOperation(ops); err != nil {
   195  			// Log quota-related errors to aid operators
   196  			if errors.IsQuotaLimitExceeded(err) {
   197  				logger.Errorf("%s: %v", unitTag, err)
   198  			}
   199  			res[i].Error = apiservererrors.ServerError(err)
   200  		}
   201  	}
   202  
   203  	return params.ErrorResults{Results: res}, nil
   204  }
   205  
   206  func (u *UnitStateAPI) getUnit(tag names.UnitTag) (UnitStateUnit, error) {
   207  	return u.backend.Unit(tag.Id())
   208  }