github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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-unstable"
    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  
    98  // validate returns an error if the state violates expectations.
    99  func (st State) validate() (err error) {
   100  	defer errors.DeferredAnnotatef(&err, "invalid operation state")
   101  	hasHook := st.Hook != nil
   102  	hasActionId := st.ActionId != nil
   103  	hasCharm := st.CharmURL != nil
   104  	switch st.Kind {
   105  	case Install:
   106  		if st.Installed {
   107  			return errors.New("unexpected hook info with Kind Install")
   108  		}
   109  		fallthrough
   110  	case Upgrade:
   111  		switch {
   112  		case !hasCharm:
   113  			return errors.New("missing charm URL")
   114  		case hasActionId:
   115  			return errors.New("unexpected action id")
   116  		}
   117  	case RunAction:
   118  		switch {
   119  		case !hasActionId:
   120  			return errors.New("missing action id")
   121  		case hasCharm:
   122  			return errors.New("unexpected charm URL")
   123  		}
   124  	case RunHook:
   125  		switch {
   126  		case !hasHook:
   127  			return errors.New("missing hook info with Kind RunHook")
   128  		case hasCharm:
   129  			return errors.New("unexpected charm URL")
   130  		case hasActionId:
   131  			return errors.New("unexpected action id")
   132  		}
   133  	case Continue:
   134  		// TODO(jw4) LP-1438489
   135  		// ModeContinue should no longer have a Hook, but until the upgrade is
   136  		// fixed we can't fail the validation if it does.
   137  		if hasHook {
   138  			logger.Errorf("unexpected hook info with Kind Continue")
   139  		}
   140  		switch {
   141  		case hasCharm:
   142  			return errors.New("unexpected charm URL")
   143  		case hasActionId:
   144  			return errors.New("unexpected action id")
   145  		}
   146  	default:
   147  		return errors.Errorf("unknown operation %q", st.Kind)
   148  	}
   149  	switch st.Step {
   150  	case Queued, Pending, Done:
   151  	default:
   152  		return errors.Errorf("unknown operation step %q", st.Step)
   153  	}
   154  	if hasHook {
   155  		return st.Hook.Validate()
   156  	}
   157  	return nil
   158  }
   159  
   160  // stateChange is useful for a variety of Operation implementations.
   161  type stateChange struct {
   162  	Kind            Kind
   163  	Step            Step
   164  	Hook            *hook.Info
   165  	ActionId        *string
   166  	CharmURL        *charm.URL
   167  	HasRunStatusSet bool
   168  }
   169  
   170  func (change stateChange) apply(state State) *State {
   171  	state.Kind = change.Kind
   172  	state.Step = change.Step
   173  	state.Hook = change.Hook
   174  	state.ActionId = change.ActionId
   175  	state.CharmURL = change.CharmURL
   176  	state.StatusSet = state.StatusSet || change.HasRunStatusSet
   177  	return &state
   178  }
   179  
   180  // StateFile holds the disk state for a uniter.
   181  type StateFile struct {
   182  	path string
   183  }
   184  
   185  // NewStateFile returns a new StateFile using path.
   186  func NewStateFile(path string) *StateFile {
   187  	return &StateFile{path}
   188  }
   189  
   190  // Read reads a State from the file. If the file does not exist it returns
   191  // ErrNoStateFile.
   192  func (f *StateFile) Read() (*State, error) {
   193  	var st State
   194  	if err := utils.ReadYaml(f.path, &st); err != nil {
   195  		if os.IsNotExist(err) {
   196  			return nil, ErrNoStateFile
   197  		}
   198  	}
   199  	if err := st.validate(); err != nil {
   200  		return nil, errors.Errorf("cannot read %q: %v", f.path, err)
   201  	}
   202  	return &st, nil
   203  }
   204  
   205  // Write stores the supplied state to the file.
   206  func (f *StateFile) Write(st *State) error {
   207  	if err := st.validate(); err != nil {
   208  		return errors.Trace(err)
   209  	}
   210  	return utils.WriteYaml(f.path, st)
   211  }