github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/worker/uniter/operation/executor.go (about)

     1  // Copyright 2014-2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package operation
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/errors"
    10  	corecharm "gopkg.in/juju/charm.v4"
    11  )
    12  
    13  type executorStep struct {
    14  	verb string
    15  	run  func(op Operation, state State) (*State, error)
    16  }
    17  
    18  func (step executorStep) message(op Operation) string {
    19  	return fmt.Sprintf("%s operation %q", step.verb, op)
    20  }
    21  
    22  var (
    23  	stepPrepare = executorStep{"preparing", Operation.Prepare}
    24  	stepExecute = executorStep{"executing", Operation.Execute}
    25  	stepCommit  = executorStep{"committing", Operation.Commit}
    26  )
    27  
    28  type executor struct {
    29  	file  *StateFile
    30  	state *State
    31  }
    32  
    33  // NewExecutor returns an Executor which takes its starting state from the
    34  // supplied path, and records state changes there. If no state file exists,
    35  // the executor's starting state will include a queued Install hook, for
    36  // the charm identified by the supplied func.
    37  func NewExecutor(stateFilePath string, getInstallCharm func() (*corecharm.URL, error)) (Executor, error) {
    38  	file := NewStateFile(stateFilePath)
    39  	state, err := file.Read()
    40  	if err == ErrNoStateFile {
    41  		charmURL, err := getInstallCharm()
    42  		if err != nil {
    43  			return nil, err
    44  		}
    45  		state = &State{
    46  			Kind:     Install,
    47  			Step:     Queued,
    48  			CharmURL: charmURL,
    49  		}
    50  	} else if err != nil {
    51  		return nil, err
    52  	}
    53  	return &executor{
    54  		file:  file,
    55  		state: state,
    56  	}, nil
    57  }
    58  
    59  // State is part of the Executor interface.
    60  func (x *executor) State() State {
    61  	return *x.state
    62  }
    63  
    64  // Run is part of the Executor interface.
    65  func (x *executor) Run(op Operation) error {
    66  	logger.Infof("running operation %v", op)
    67  	switch err := x.do(op, stepPrepare); errors.Cause(err) {
    68  	case ErrSkipExecute:
    69  	case nil:
    70  		if err := x.do(op, stepExecute); err != nil {
    71  			return err
    72  		}
    73  	default:
    74  		return err
    75  	}
    76  	return x.do(op, stepCommit)
    77  }
    78  
    79  // Skip is part of the Executor interface.
    80  func (x *executor) Skip(op Operation) error {
    81  	logger.Infof("skipping operation %v", op)
    82  	return x.do(op, stepCommit)
    83  }
    84  
    85  func (x *executor) do(op Operation, step executorStep) (err error) {
    86  	message := step.message(op)
    87  	logger.Infof(message)
    88  	newState, firstErr := step.run(op, *x.state)
    89  	if newState != nil {
    90  		writeErr := x.writeState(*newState)
    91  		if firstErr == nil {
    92  			firstErr = writeErr
    93  		} else if writeErr != nil {
    94  			logger.Errorf("after %s: %v", message, writeErr)
    95  		}
    96  	}
    97  	return errors.Annotatef(firstErr, message)
    98  }
    99  
   100  func (x *executor) writeState(newState State) error {
   101  	if err := newState.validate(); err != nil {
   102  		return err
   103  	}
   104  	if err := x.file.Write(&newState); err != nil {
   105  		return errors.Annotatef(err, "writing state")
   106  	}
   107  	x.state = &newState
   108  	return nil
   109  }