github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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.v4"
    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  	// Started indicates whether the start hook has run.
    59  	Started bool `yaml:"started"`
    60  
    61  	// Kind indicates the current operation.
    62  	Kind Kind `yaml:"op"`
    63  
    64  	// Step indicates the current operation's progression.
    65  	Step Step `yaml:"opstep"`
    66  
    67  	// Hook holds hook information relevant to the current operation. If Kind
    68  	// is Continue, it holds the last hook that was executed; if Kind is RunHook,
    69  	// it holds the running hook; if Kind is Upgrade, a non-nil hook indicates
    70  	// that the uniter should return to that hook's Pending state after the
    71  	// upgrade is complete (instead of running an upgrade-charm hook).
    72  	Hook *hook.Info `yaml:"hook,omitempty"`
    73  
    74  	// ActionId holds action information relevant to the current operation. If
    75  	// Kind is Continue, it holds the last action that was executed; if Kind is
    76  	// RunAction, it holds the running action.
    77  	ActionId *string `yaml:"action-id,omitempty"`
    78  
    79  	// Charm describes the charm being deployed by an Install or Upgrade
    80  	// operation, and is otherwise blank.
    81  	CharmURL *charm.URL `yaml:"charm,omitempty"`
    82  
    83  	// CollectMetricsTime records the time the collect metrics hook was last run.
    84  	// It's set to nil if the hook was not run at all. Recording time as int64
    85  	// because the yaml encoder cannot encode the time.Time struct.
    86  	CollectMetricsTime int64 `yaml:"collectmetricstime,omitempty"`
    87  }
    88  
    89  // validate returns an error if the state violates expectations.
    90  func (st State) validate() (err error) {
    91  	defer errors.DeferredAnnotatef(&err, "invalid operation state")
    92  	hasHook := st.Hook != nil
    93  	hasActionId := st.ActionId != nil
    94  	hasCharm := st.CharmURL != nil
    95  	switch st.Kind {
    96  	case Install:
    97  		if hasHook {
    98  			return errors.New("unexpected hook info")
    99  		}
   100  		fallthrough
   101  	case Upgrade:
   102  		if !hasCharm {
   103  			return errors.New("missing charm URL")
   104  		} else if hasActionId {
   105  			return errors.New("unexpected action id")
   106  		}
   107  	case RunAction:
   108  		if !hasHook {
   109  			return errors.New("missing hook info")
   110  		} else if hasCharm {
   111  			return errors.New("unexpected charm URL")
   112  		} else if !hasActionId {
   113  			return errors.New("missing action id")
   114  		}
   115  	case RunHook:
   116  		if hasActionId {
   117  			return errors.New("unexpected action id")
   118  		}
   119  		fallthrough
   120  	case Continue:
   121  		if !hasHook {
   122  			return errors.New("missing hook info")
   123  		} else if hasCharm {
   124  			return errors.New("unexpected charm URL")
   125  		} else if hasActionId {
   126  			return errors.New("unexpected action id")
   127  		}
   128  	default:
   129  		return errors.Errorf("unknown operation %q", st.Kind)
   130  	}
   131  	switch st.Step {
   132  	case Queued, Pending, Done:
   133  	default:
   134  		return errors.Errorf("unknown operation step %q", st.Step)
   135  	}
   136  	if hasHook {
   137  		return st.Hook.Validate()
   138  	}
   139  	return nil
   140  }
   141  
   142  func (st State) CollectedMetricsAt() time.Time {
   143  	return time.Unix(st.CollectMetricsTime, 0)
   144  }
   145  
   146  // stateChange is useful for a variety of Operation implementations.
   147  type stateChange struct {
   148  	Kind     Kind
   149  	Step     Step
   150  	Hook     *hook.Info
   151  	ActionId *string
   152  	CharmURL *charm.URL
   153  }
   154  
   155  func (change stateChange) apply(state State) *State {
   156  	state.Kind = change.Kind
   157  	state.Step = change.Step
   158  	state.Hook = change.Hook
   159  	state.ActionId = change.ActionId
   160  	state.CharmURL = change.CharmURL
   161  	return &state
   162  }
   163  
   164  // StateFile holds the disk state for a uniter.
   165  type StateFile struct {
   166  	path string
   167  }
   168  
   169  // NewStateFile returns a new StateFile using path.
   170  func NewStateFile(path string) *StateFile {
   171  	return &StateFile{path}
   172  }
   173  
   174  // Read reads a State from the file. If the file does not exist it returns
   175  // ErrNoStateFile.
   176  func (f *StateFile) Read() (*State, error) {
   177  	var st State
   178  	if err := utils.ReadYaml(f.path, &st); err != nil {
   179  		if os.IsNotExist(err) {
   180  			return nil, ErrNoStateFile
   181  		}
   182  	}
   183  	if err := st.validate(); err != nil {
   184  		return nil, errors.Errorf("cannot read %q: %v", f.path, err)
   185  	}
   186  	return &st, nil
   187  }
   188  
   189  // Write stores the supplied state to the file.
   190  func (f *StateFile) Write(st *State) error {
   191  	if err := st.validate(); err != nil {
   192  		panic(err)
   193  	}
   194  	return utils.WriteYaml(f.path, st)
   195  }