github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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  	"time"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/utils"
    12  	"gopkg.in/juju/charm.v5"
    13  
    14  	"github.com/juju/juju/worker/uniter/hook"
    15  )
    16  
    17  // Kind enumerates the operations the uniter can perform.
    18  type Kind string
    19  
    20  const (
    21  	// Install indicates that the uniter is installing the charm.
    22  	Install Kind = "install"
    23  
    24  	// RunHook indicates that the uniter is running a hook.
    25  	RunHook Kind = "run-hook"
    26  
    27  	// RunAction indicates that the uniter is running an action.
    28  	RunAction Kind = "run-action"
    29  
    30  	// Upgrade indicates that the uniter is upgrading the charm.
    31  	Upgrade Kind = "upgrade"
    32  
    33  	// Continue indicates that the uniter should run ModeContinue
    34  	// to determine the next operation.
    35  	Continue Kind = "continue"
    36  )
    37  
    38  // Step describes the recorded progression of an operation.
    39  type Step string
    40  
    41  const (
    42  	// Queued indicates that the uniter should undertake the operation
    43  	// as soon as possible.
    44  	Queued Step = "queued"
    45  
    46  	// Pending indicates that the uniter has started, but not completed,
    47  	// the operation.
    48  	Pending Step = "pending"
    49  
    50  	// Done indicates that the uniter has completed the operation,
    51  	// but may not yet have synchronized all necessary secondary state.
    52  	Done Step = "done"
    53  )
    54  
    55  // State defines the local persistent state of the uniter, excluding relation
    56  // state.
    57  type State struct {
    58  
    59  	// Leader indicates whether a leader-elected hook has been queued to run, and
    60  	// no more recent leader-deposed hook has completed.
    61  	Leader bool `yaml:"leader"`
    62  
    63  	// Started indicates whether the start hook has run.
    64  	Started bool `yaml:"started"`
    65  
    66  	// Stopped indicates whether the stop hook has run.
    67  	Stopped bool `yaml:"stopped"`
    68  
    69  	// StatusSet indicates whether the charm being deployed has ever invoked
    70  	// the status-set hook tool.
    71  	StatusSet bool `yaml:"status-set"`
    72  
    73  	// Kind indicates the current operation.
    74  	Kind Kind `yaml:"op"`
    75  
    76  	// Step indicates the current operation's progression.
    77  	Step Step `yaml:"opstep"`
    78  
    79  	// Hook holds hook information relevant to the current operation. If Kind
    80  	// is Continue, it holds the last hook that was executed; if Kind is RunHook,
    81  	// it holds the running hook; if Kind is Upgrade, a non-nil hook indicates
    82  	// that the uniter should return to that hook's Pending state after the
    83  	// upgrade is complete (instead of running an upgrade-charm hook).
    84  	Hook *hook.Info `yaml:"hook,omitempty"`
    85  
    86  	// ActionId holds action information relevant to the current operation. If
    87  	// Kind is Continue, it holds the last action that was executed; if Kind is
    88  	// RunAction, it holds the running action.
    89  	ActionId *string `yaml:"action-id,omitempty"`
    90  
    91  	// Charm describes the charm being deployed by an Install or Upgrade
    92  	// operation, and is otherwise blank.
    93  	CharmURL *charm.URL `yaml:"charm,omitempty"`
    94  
    95  	// CollectMetricsTime records the time the collect metrics hook was last run.
    96  	// It's set to nil if the hook was not run at all. Recording time as int64
    97  	// because the yaml encoder cannot encode the time.Time struct.
    98  	CollectMetricsTime int64 `yaml:"collectmetricstime,omitempty"`
    99  
   100  	// SendMetricsTime records the time when metrics were last sent to the
   101  	// state server (see also CollectMetricsTime).
   102  	SendMetricsTime int64 `yaml:"sendmetricstime,omitempty"`
   103  
   104  	// UpdateStatusTime records the time the update status hook was last run.
   105  	// It's set to nil if the hook was not run at all.
   106  	UpdateStatusTime int64 `yaml:"updatestatustime,omitempty"`
   107  }
   108  
   109  // validate returns an error if the state violates expectations.
   110  func (st State) validate() (err error) {
   111  	defer errors.DeferredAnnotatef(&err, "invalid operation state")
   112  	hasHook := st.Hook != nil
   113  	hasActionId := st.ActionId != nil
   114  	hasCharm := st.CharmURL != nil
   115  	switch st.Kind {
   116  	case Install:
   117  		if hasHook {
   118  			return errors.New("unexpected hook info with Kind Install")
   119  		}
   120  		fallthrough
   121  	case Upgrade:
   122  		switch {
   123  		case !hasCharm:
   124  			return errors.New("missing charm URL")
   125  		case hasActionId:
   126  			return errors.New("unexpected action id")
   127  		}
   128  	case RunAction:
   129  		switch {
   130  		case !hasActionId:
   131  			return errors.New("missing action id")
   132  		case hasCharm:
   133  			return errors.New("unexpected charm URL")
   134  		}
   135  	case RunHook:
   136  		switch {
   137  		case !hasHook:
   138  			return errors.New("missing hook info with Kind RunHook")
   139  		case hasCharm:
   140  			return errors.New("unexpected charm URL")
   141  		case hasActionId:
   142  			return errors.New("unexpected action id")
   143  		}
   144  	case Continue:
   145  		// TODO(jw4) LP-1438489
   146  		// ModeContinue should no longer have a Hook, but until the upgrade is
   147  		// fixed we can't fail the validation if it does.
   148  		if hasHook {
   149  			logger.Errorf("unexpected hook info with Kind Continue")
   150  		}
   151  		switch {
   152  		case hasCharm:
   153  			return errors.New("unexpected charm URL")
   154  		case hasActionId:
   155  			return errors.New("unexpected action id")
   156  		}
   157  	default:
   158  		return errors.Errorf("unknown operation %q", st.Kind)
   159  	}
   160  	switch st.Step {
   161  	case Queued, Pending, Done:
   162  	default:
   163  		return errors.Errorf("unknown operation step %q", st.Step)
   164  	}
   165  	if hasHook {
   166  		return st.Hook.Validate()
   167  	}
   168  	return nil
   169  }
   170  
   171  func (st State) CollectedMetricsAt() time.Time {
   172  	return time.Unix(st.CollectMetricsTime, 0)
   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  		panic(err)
   224  	}
   225  	return utils.WriteYaml(f.path, st)
   226  }