github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"os"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/utils"
    11  	"gopkg.in/juju/charm.v6"
    12  
    13  	"github.com/juju/juju/worker/uniter/hook"
    14  )
    15  
    16  // Kind enumerates the operations the uniter can perform.
    17  type Kind string
    18  
    19  const (
    20  	// Install indicates that the uniter is installing the charm.
    21  	Install Kind = "install"
    22  
    23  	// RunHook indicates that the uniter is running a hook.
    24  	RunHook Kind = "run-hook"
    25  
    26  	// RunAction indicates that the uniter is running an action.
    27  	RunAction Kind = "run-action"
    28  
    29  	// Upgrade indicates that the uniter is upgrading the charm.
    30  	Upgrade Kind = "upgrade"
    31  
    32  	// Continue indicates that the uniter should run ModeContinue
    33  	// to determine the next operation.
    34  	Continue Kind = "continue"
    35  )
    36  
    37  // Step describes the recorded progression of an operation.
    38  type Step string
    39  
    40  const (
    41  	// Queued indicates that the uniter should undertake the operation
    42  	// as soon as possible.
    43  	Queued Step = "queued"
    44  
    45  	// Pending indicates that the uniter has started, but not completed,
    46  	// the operation.
    47  	Pending Step = "pending"
    48  
    49  	// Done indicates that the uniter has completed the operation,
    50  	// but may not yet have synchronized all necessary secondary state.
    51  	Done Step = "done"
    52  )
    53  
    54  // State defines the local persistent state of the uniter, excluding relation
    55  // state.
    56  type State struct {
    57  
    58  	// Leader indicates whether a leader-elected hook has been queued to run, and
    59  	// no more recent leader-deposed hook has completed.
    60  	Leader bool `yaml:"leader"`
    61  
    62  	// Started indicates whether the start hook has run.
    63  	Started bool `yaml:"started"`
    64  
    65  	// Stopped indicates whether the stop hook has run.
    66  	Stopped bool `yaml:"stopped"`
    67  
    68  	// Installed indicates whether the install hook has run.
    69  	Installed bool `yaml:"installed"`
    70  
    71  	// StatusSet indicates whether the charm being deployed has ever invoked
    72  	// the status-set hook tool.
    73  	StatusSet bool `yaml:"status-set"`
    74  
    75  	// Kind indicates the current operation.
    76  	Kind Kind `yaml:"op"`
    77  
    78  	// Step indicates the current operation's progression.
    79  	Step Step `yaml:"opstep"`
    80  
    81  	// Hook holds hook information relevant to the current operation. If Kind
    82  	// is Continue, it holds the last hook that was executed; if Kind is RunHook,
    83  	// it holds the running hook; if Kind is Upgrade, a non-nil hook indicates
    84  	// that the uniter should return to that hook's Pending state after the
    85  	// upgrade is complete (instead of running an upgrade-charm hook).
    86  	Hook *hook.Info `yaml:"hook,omitempty"`
    87  
    88  	// ActionId holds action information relevant to the current operation. If
    89  	// Kind is Continue, it holds the last action that was executed; if Kind is
    90  	// RunAction, it holds the running action.
    91  	ActionId *string `yaml:"action-id,omitempty"`
    92  
    93  	// Charm describes the charm being deployed by an Install or Upgrade
    94  	// operation, and is otherwise blank.
    95  	CharmURL *charm.URL `yaml:"charm,omitempty"`
    96  
    97  	// ConfigHash stores a hash of the latest known charm
    98  	// configuration settings - it's used to determine whether we need
    99  	// to run config-changed.
   100  	ConfigHash string `yaml:"config-hash,omitempty"`
   101  
   102  	// TrustHash stores a hash of the latest known charm trust
   103  	// configuration settings - it's used to determine whether we need
   104  	// to run config-changed.
   105  	TrustHash string `yaml:"trust-hash,omitempty"`
   106  
   107  	// AddressesHash stores a hash of the latest known
   108  	// machine/container addresses - it's used to determine whether we
   109  	// need to run config-changed.
   110  	AddressesHash string `yaml:"addresses-hash,omitempty"`
   111  }
   112  
   113  // validate returns an error if the state violates expectations.
   114  func (st State) validate() (err error) {
   115  	defer errors.DeferredAnnotatef(&err, "invalid operation state")
   116  	hasHook := st.Hook != nil
   117  	hasActionId := st.ActionId != nil
   118  	hasCharm := st.CharmURL != nil
   119  	switch st.Kind {
   120  	case Install:
   121  		if st.Installed {
   122  			return errors.New("unexpected hook info with Kind Install")
   123  		}
   124  		fallthrough
   125  	case Upgrade:
   126  		switch {
   127  		case !hasCharm:
   128  			return errors.New("missing charm URL")
   129  		case hasActionId:
   130  			return errors.New("unexpected action id")
   131  		}
   132  	case RunAction:
   133  		switch {
   134  		case !hasActionId:
   135  			return errors.New("missing action id")
   136  		case hasCharm:
   137  			return errors.New("unexpected charm URL")
   138  		}
   139  	case RunHook:
   140  		switch {
   141  		case !hasHook:
   142  			return errors.New("missing hook info with Kind RunHook")
   143  		case hasCharm:
   144  			return errors.New("unexpected charm URL")
   145  		case hasActionId:
   146  			return errors.New("unexpected action id")
   147  		}
   148  	case Continue:
   149  		// TODO(jw4) LP-1438489
   150  		// ModeContinue should no longer have a Hook, but until the upgrade is
   151  		// fixed we can't fail the validation if it does.
   152  		if hasHook {
   153  			logger.Errorf("unexpected hook info with Kind Continue")
   154  		}
   155  		switch {
   156  		case hasCharm:
   157  			return errors.New("unexpected charm URL")
   158  		case hasActionId:
   159  			return errors.New("unexpected action id")
   160  		}
   161  	default:
   162  		return errors.Errorf("unknown operation %q", st.Kind)
   163  	}
   164  	switch st.Step {
   165  	case Queued, Pending, Done:
   166  	default:
   167  		return errors.Errorf("unknown operation step %q", st.Step)
   168  	}
   169  	if hasHook {
   170  		return st.Hook.Validate()
   171  	}
   172  	return nil
   173  }
   174  
   175  // stateChange is useful for a variety of Operation implementations.
   176  type stateChange struct {
   177  	Kind            Kind
   178  	Step            Step
   179  	Hook            *hook.Info
   180  	ActionId        *string
   181  	CharmURL        *charm.URL
   182  	HasRunStatusSet bool
   183  }
   184  
   185  func (change stateChange) apply(state State) *State {
   186  	state.Kind = change.Kind
   187  	state.Step = change.Step
   188  	state.Hook = change.Hook
   189  	state.ActionId = change.ActionId
   190  	state.CharmURL = change.CharmURL
   191  	state.StatusSet = state.StatusSet || change.HasRunStatusSet
   192  	return &state
   193  }
   194  
   195  // StateFile holds the disk state for a uniter.
   196  type StateFile struct {
   197  	path string
   198  }
   199  
   200  // NewStateFile returns a new StateFile using path.
   201  func NewStateFile(path string) *StateFile {
   202  	return &StateFile{path}
   203  }
   204  
   205  // Read reads a State from the file. If the file does not exist it returns
   206  // ErrNoStateFile.
   207  func (f *StateFile) Read() (*State, error) {
   208  	var st State
   209  	if err := utils.ReadYaml(f.path, &st); err != nil {
   210  		if os.IsNotExist(err) {
   211  			return nil, ErrNoStateFile
   212  		}
   213  	}
   214  	if err := st.validate(); err != nil {
   215  		return nil, errors.Errorf("cannot read %q: %v", f.path, err)
   216  	}
   217  	return &st, nil
   218  }
   219  
   220  // Write stores the supplied state to the file.
   221  func (f *StateFile) Write(st *State) error {
   222  	if err := st.validate(); err != nil {
   223  		return errors.Trace(err)
   224  	}
   225  	return utils.WriteYaml(f.path, st)
   226  }