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 }