github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/uniter/operation/state.go (about)

     1  // Copyright 2012-2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package operation
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"gopkg.in/yaml.v2"
     9  
    10  	"github.com/juju/juju/rpc/params"
    11  	"github.com/juju/juju/worker/uniter/hook"
    12  )
    13  
    14  // Kind enumerates the operations the uniter can perform.
    15  type Kind string
    16  
    17  const (
    18  	// Install indicates that the uniter is installing the charm.
    19  	Install Kind = "install"
    20  
    21  	// RunHook indicates that the uniter is running a hook.
    22  	RunHook Kind = "run-hook"
    23  
    24  	// RunAction indicates that the uniter is running an action.
    25  	RunAction Kind = "run-action"
    26  
    27  	// Upgrade indicates that the uniter is upgrading the charm.
    28  	Upgrade Kind = "upgrade"
    29  
    30  	// Continue indicates that the uniter should run ModeContinue
    31  	// to determine the next operation.
    32  	Continue Kind = "continue"
    33  
    34  	// RemoteInit indicates the CAAS uniter is installing/upgrading the
    35  	// charm on the remote instance.
    36  	RemoteInit Kind = "remote-init"
    37  )
    38  
    39  // Step describes the recorded progression of an operation.
    40  type Step string
    41  
    42  const (
    43  	// Queued indicates that the uniter should undertake the operation
    44  	// as soon as possible.
    45  	Queued Step = "queued"
    46  
    47  	// Pending indicates that the uniter has started, but not completed,
    48  	// the operation.
    49  	Pending Step = "pending"
    50  
    51  	// Done indicates that the uniter has completed the operation,
    52  	// but may not yet have synchronized all necessary secondary state.
    53  	Done Step = "done"
    54  )
    55  
    56  // State defines the local persistent state of the uniter, excluding relation
    57  // state.
    58  type State struct {
    59  
    60  	// Leader indicates whether a leader-elected hook has been queued to run, and
    61  	// no more recent leader-deposed hook has completed.
    62  	Leader bool `yaml:"leader"`
    63  
    64  	// Started indicates whether the start hook has run.
    65  	Started bool `yaml:"started"`
    66  
    67  	// Stopped indicates whether the stop hook has run.
    68  	Stopped bool `yaml:"stopped"`
    69  
    70  	// Installed indicates whether the install hook has run.
    71  	Installed bool `yaml:"installed"`
    72  
    73  	// Removed indicates whether the remove hook has run.
    74  	Removed bool `yaml:"removed"`
    75  
    76  	// StatusSet indicates whether the charm being deployed has ever invoked
    77  	// the status-set hook tool.
    78  	StatusSet bool `yaml:"status-set"`
    79  
    80  	// Kind indicates the current operation.
    81  	Kind Kind `yaml:"op"`
    82  
    83  	// Step indicates the current operation's progression.
    84  	Step Step `yaml:"opstep"`
    85  
    86  	// Hook holds hook information relevant to the current operation. If Kind
    87  	// is Continue, it holds the last hook that was executed; if Kind is RunHook,
    88  	// it holds the running hook; if Kind is Upgrade, a non-nil hook indicates
    89  	// that the uniter should return to that hook's Pending state after the
    90  	// upgrade is complete (instead of running an upgrade-charm hook).
    91  	Hook *hook.Info `yaml:"hook,omitempty"`
    92  
    93  	// HookStep records any hook operation's progression. It will only be set
    94  	// if Hook is also set. If not set, fallback to using just Step.
    95  	// HookStep is recorded separately to Step so as not to lose the hook
    96  	// state when initialising the agent and running any upgrade operation.
    97  	HookStep *Step `yaml:"hook-step,omitempty"`
    98  
    99  	// ActionId holds action information relevant to the current operation. If
   100  	// Kind is Continue, it holds the last action that was executed; if Kind is
   101  	// RunAction, it holds the running action.
   102  	ActionId *string `yaml:"action-id,omitempty"`
   103  
   104  	// Charm describes the charm being deployed by an Install or Upgrade
   105  	// operation, and is otherwise blank.
   106  	CharmURL string `yaml:"charm,omitempty"`
   107  
   108  	// ConfigHash stores a hash of the latest known charm
   109  	// configuration settings - it's used to determine whether we need
   110  	// to run config-changed.
   111  	ConfigHash string `yaml:"config-hash,omitempty"`
   112  
   113  	// TrustHash stores a hash of the latest known charm trust
   114  	// configuration settings - it's used to determine whether we need
   115  	// to run config-changed.
   116  	TrustHash string `yaml:"trust-hash,omitempty"`
   117  
   118  	// AddressesHash stores a hash of the latest known
   119  	// machine/container addresses - it's used to determine whether we
   120  	// need to run config-changed.
   121  	AddressesHash string `yaml:"addresses-hash,omitempty"`
   122  }
   123  
   124  // Validate returns an error if the state violates expectations.
   125  func (st State) Validate() (err error) {
   126  	defer errors.DeferredAnnotatef(&err, "invalid operation state")
   127  	hasHook := st.Hook != nil
   128  	hasActionId := st.ActionId != nil
   129  	hasCharm := st.CharmURL != ""
   130  	switch st.Kind {
   131  	case Install:
   132  		if st.Installed {
   133  			return errors.New("unexpected hook info with Kind Install")
   134  		}
   135  		fallthrough
   136  	case Upgrade:
   137  		switch {
   138  		case !hasCharm:
   139  			return errors.New("missing charm URL")
   140  		case hasActionId:
   141  			return errors.New("unexpected action id")
   142  		}
   143  	case RunAction:
   144  		switch {
   145  		case !hasActionId:
   146  			return errors.New("missing action id")
   147  		case hasCharm:
   148  			return errors.New("unexpected charm URL")
   149  		}
   150  	case RunHook:
   151  		switch {
   152  		case !hasHook:
   153  			return errors.New("missing hook info with Kind RunHook")
   154  		case hasCharm:
   155  			return errors.New("unexpected charm URL")
   156  		case hasActionId:
   157  			return errors.New("unexpected action id")
   158  		}
   159  	case Continue:
   160  		switch {
   161  		case hasCharm:
   162  			return errors.New("unexpected charm URL")
   163  		case hasActionId:
   164  			return errors.New("unexpected action id")
   165  		case hasHook:
   166  			return errors.New("unexpected hook info with Kind Continue")
   167  		}
   168  	case RemoteInit:
   169  		// Nothing to check for.
   170  	default:
   171  		return errors.Errorf("unknown operation %q", st.Kind)
   172  	}
   173  	switch st.Step {
   174  	case Queued, Pending, Done:
   175  	default:
   176  		return errors.Errorf("unknown operation step %q", st.Step)
   177  	}
   178  	if hasHook {
   179  		return st.Hook.Validate()
   180  	}
   181  	return nil
   182  }
   183  
   184  func (st State) Report() map[string]interface{} {
   185  	result := make(map[string]interface{})
   186  	result["started"] = st.Started
   187  	result["stopped"] = st.Stopped
   188  	result["installed"] = st.Installed
   189  	result["removed"] = st.Removed
   190  	result["operation-kind"] = st.Kind
   191  	result["operation-step"] = st.Step
   192  	result["leader"] = st.Leader
   193  	if st.Hook != nil {
   194  		hookStep := st.Step
   195  		if st.HookStep != nil {
   196  			hookStep = *st.HookStep
   197  		}
   198  		result["hook-kind"] = st.Hook.Kind
   199  		result["hook-step"] = hookStep
   200  	}
   201  	return result
   202  }
   203  
   204  func (st State) match(otherState State) bool {
   205  	stateYaml, _ := yaml.Marshal(st)
   206  	otherStateYaml, _ := yaml.Marshal(otherState)
   207  	return string(stateYaml) == string(otherStateYaml)
   208  }
   209  
   210  // stateChange is useful for a variety of Operation implementations.
   211  type stateChange struct {
   212  	Kind            Kind
   213  	Step            Step
   214  	Hook            *hook.Info
   215  	HookStep        *Step
   216  	ActionId        *string
   217  	CharmURL        string
   218  	HasRunStatusSet bool
   219  }
   220  
   221  func (change stateChange) apply(state State) *State {
   222  	state.Kind = change.Kind
   223  	state.Step = change.Step
   224  	state.Hook = change.Hook
   225  	state.HookStep = change.HookStep
   226  	state.ActionId = change.ActionId
   227  	state.CharmURL = change.CharmURL
   228  	state.StatusSet = state.StatusSet || change.HasRunStatusSet
   229  	return &state
   230  }
   231  
   232  // StateOps reads and writes uniter state from/to the controller.
   233  type StateOps struct {
   234  	unitStateRW UnitStateReadWriter
   235  }
   236  
   237  // NewStateOps returns a new StateOps.
   238  func NewStateOps(readwriter UnitStateReadWriter) *StateOps {
   239  	return &StateOps{unitStateRW: readwriter}
   240  }
   241  
   242  // UnitStateReadWriter encapsulates the methods from a state.Unit
   243  // required to set and get unit state.
   244  //
   245  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/uniterstaterw_mock.go github.com/juju/juju/worker/uniter/operation UnitStateReadWriter
   246  type UnitStateReadWriter interface {
   247  	State() (params.UnitStateResult, error)
   248  	SetState(unitState params.SetUnitStateArg) error
   249  }
   250  
   251  // Read a State from the controller. If the saved state does not exist
   252  // it returns ErrNoSavedState.
   253  func (f *StateOps) Read() (*State, error) {
   254  	var st State
   255  	unitState, err := f.unitStateRW.State()
   256  	if err != nil {
   257  		return nil, errors.Trace(err)
   258  	}
   259  	if unitState.UniterState == "" {
   260  		return nil, ErrNoSavedState
   261  	}
   262  	if yaml.Unmarshal([]byte(unitState.UniterState), &st) != nil {
   263  		return nil, errors.Trace(err)
   264  	}
   265  	if err := st.Validate(); err != nil {
   266  		return nil, errors.Errorf("validation of uniter state: %v", err)
   267  	}
   268  	return &st, nil
   269  }
   270  
   271  // Write stores the supplied state on the controller.
   272  func (f *StateOps) Write(st *State) error {
   273  	if err := st.Validate(); err != nil {
   274  		return errors.Trace(err)
   275  	}
   276  	data, err := yaml.Marshal(st)
   277  	if err != nil {
   278  		return errors.Trace(err)
   279  	}
   280  	s := string(data)
   281  	return f.unitStateRW.SetState(params.SetUnitStateArg{UniterState: &s})
   282  }