github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/uniter/operation/deploy.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.v6"
    11  	"gopkg.in/juju/charm.v6/hooks"
    12  
    13  	"github.com/juju/juju/worker/uniter/charm"
    14  	"github.com/juju/juju/worker/uniter/hook"
    15  )
    16  
    17  // deploy implements charm install and charm upgrade operations.
    18  type deploy struct {
    19  	DoesNotRequireMachineLock
    20  
    21  	kind     Kind
    22  	charmURL *corecharm.URL
    23  	revert   bool
    24  	resolved bool
    25  
    26  	callbacks Callbacks
    27  	deployer  charm.Deployer
    28  	abort     <-chan struct{}
    29  }
    30  
    31  // String is part of the Operation interface.
    32  func (d *deploy) String() string {
    33  	verb := "upgrade to"
    34  	prefix := ""
    35  	switch {
    36  	case d.kind == Install:
    37  		verb = "install"
    38  	case d.revert:
    39  		prefix = "switch "
    40  	case d.resolved:
    41  		prefix = "continue "
    42  	}
    43  	return fmt.Sprintf("%s%s %s", prefix, verb, d.charmURL)
    44  }
    45  
    46  // Prepare downloads and verifies the charm, and informs the controller
    47  // that the unit will be using it. If the supplied state indicates that a
    48  // hook was pending, that hook is recorded in the returned state.
    49  // Prepare is part of the Operation interface.
    50  func (d *deploy) Prepare(state State) (*State, error) {
    51  	if err := d.checkAlreadyDone(state); err != nil {
    52  		return nil, errors.Trace(err)
    53  	}
    54  	if d.kind == Install {
    55  		// When a 2nd principal or subordinate unit having an lxd profile,
    56  		// is added to a machine, it is installed on the machine via the same
    57  		// mechanism as a charm upgrade with a profile.  However there is not
    58  		// a watcher waiting for the instanceCharmProfileData doc status to
    59  		// go into a terminal state so it can be deleted.
    60  		//
    61  		if err := d.callbacks.RemoveUpgradeCharmProfileData(); err != nil {
    62  			return nil, errors.Trace(err)
    63  		}
    64  	}
    65  	info, err := d.callbacks.GetArchiveInfo(d.charmURL)
    66  	if err != nil {
    67  		return nil, errors.Trace(err)
    68  	}
    69  	if err := d.deployer.Stage(info, d.abort); err != nil {
    70  		return nil, errors.Trace(err)
    71  	}
    72  	// note: yes, this *should* be in Prepare, not Execute. Before we can safely
    73  	// write out local state referencing the charm url (by returning the new
    74  	// State to the Executor, below), we have to register our interest in that
    75  	// charm on the controller. If we neglected to do so, the operation could
    76  	// race with a new application-charm-url change on the controller, and lead to
    77  	// failures on resume in which we try to obtain archive info for a charm that
    78  	// has already been removed from the controller.
    79  	if err := d.callbacks.SetCurrentCharm(d.charmURL); err != nil {
    80  		return nil, errors.Trace(err)
    81  	}
    82  	return d.getState(state, Pending), nil
    83  }
    84  
    85  // Execute installs or upgrades the prepared charm, and preserves any hook
    86  // recorded in the supplied state.
    87  // Execute is part of the Operation interface.
    88  func (d *deploy) Execute(state State) (*State, error) {
    89  	if err := d.deployer.Deploy(); err == charm.ErrConflict {
    90  		return nil, NewDeployConflictError(d.charmURL)
    91  	} else if err != nil {
    92  		return nil, errors.Trace(err)
    93  	}
    94  	// Ensure that we always clean up the LXD profile status before Upgrade.
    95  	if d.kind == Upgrade && !d.revert && !d.resolved {
    96  		if err := d.callbacks.RemoveUpgradeCharmProfileData(); err != nil {
    97  			return nil, errors.Trace(err)
    98  		}
    99  	}
   100  	return d.getState(state, Done), nil
   101  }
   102  
   103  // Commit restores state for any interrupted hook, or queues an install or
   104  // upgrade-charm hook if no hook was interrupted.
   105  func (d *deploy) Commit(state State) (*State, error) {
   106  	change := &stateChange{
   107  		Kind: RunHook,
   108  	}
   109  	if hookInfo := d.interruptedHook(state); hookInfo != nil {
   110  		change.Hook = hookInfo
   111  		change.Step = Pending
   112  	} else {
   113  		change.Hook = &hook.Info{Kind: deployHookKinds[d.kind]}
   114  		change.Step = Queued
   115  	}
   116  	return change.apply(state), nil
   117  }
   118  
   119  func (d *deploy) checkAlreadyDone(state State) error {
   120  	if state.Kind != d.kind {
   121  		return nil
   122  	}
   123  	if *state.CharmURL != *d.charmURL {
   124  		return nil
   125  	}
   126  	if state.Step == Done {
   127  		return ErrSkipExecute
   128  	}
   129  	return nil
   130  }
   131  
   132  func (d *deploy) getState(state State, step Step) *State {
   133  	return stateChange{
   134  		Kind:     d.kind,
   135  		Step:     step,
   136  		CharmURL: d.charmURL,
   137  		Hook:     d.interruptedHook(state),
   138  	}.apply(state)
   139  }
   140  
   141  func (d *deploy) interruptedHook(state State) *hook.Info {
   142  	switch state.Kind {
   143  	case RunHook, Upgrade:
   144  		return state.Hook
   145  	}
   146  	return nil
   147  }
   148  
   149  // deployHookKinds determines what kind of hook should be queued after a
   150  // given deployment operation.
   151  var deployHookKinds = map[Kind]hooks.Kind{
   152  	Install: hooks.Install,
   153  	Upgrade: hooks.UpgradeCharm,
   154  }