github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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-unstable" 11 "gopkg.in/juju/charm.v6-unstable/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.revert { 55 if err := d.deployer.NotifyRevert(); err != nil { 56 return nil, errors.Trace(err) 57 } 58 } 59 if d.resolved { 60 if err := d.deployer.NotifyResolved(); err != nil { 61 return nil, errors.Trace(err) 62 } 63 } 64 info, err := d.callbacks.GetArchiveInfo(d.charmURL) 65 if err != nil { 66 return nil, errors.Trace(err) 67 } 68 if err := d.deployer.Stage(info, d.abort); err != nil { 69 return nil, errors.Trace(err) 70 } 71 // note: yes, this *should* be in Prepare, not Execute. Before we can safely 72 // write out local state referencing the charm url (by returning the new 73 // State to the Executor, below), we have to register our interest in that 74 // charm on the controller. If we neglected to do so, the operation could 75 // race with a new service-charm-url change on the controller, and lead to 76 // failures on resume in which we try to obtain archive info for a charm that 77 // has already been removed from the controller. 78 if err := d.callbacks.SetCurrentCharm(d.charmURL); err != nil { 79 return nil, errors.Trace(err) 80 } 81 return d.getState(state, Pending), nil 82 } 83 84 // Execute installs or upgrades the prepared charm, and preserves any hook 85 // recorded in the supplied state. 86 // Execute is part of the Operation interface. 87 func (d *deploy) Execute(state State) (*State, error) { 88 if err := d.deployer.Deploy(); err == charm.ErrConflict { 89 return nil, NewDeployConflictError(d.charmURL) 90 } else if err != nil { 91 return nil, errors.Trace(err) 92 } 93 return d.getState(state, Done), nil 94 } 95 96 // Commit restores state for any interrupted hook, or queues an install or 97 // upgrade-charm hook if no hook was interrupted. 98 func (d *deploy) Commit(state State) (*State, error) { 99 change := &stateChange{ 100 Kind: RunHook, 101 } 102 if hookInfo := d.interruptedHook(state); hookInfo != nil { 103 change.Hook = hookInfo 104 change.Step = Pending 105 } else { 106 change.Hook = &hook.Info{Kind: deployHookKinds[d.kind]} 107 change.Step = Queued 108 } 109 return change.apply(state), nil 110 } 111 112 func (d *deploy) checkAlreadyDone(state State) error { 113 if state.Kind != d.kind { 114 return nil 115 } 116 if *state.CharmURL != *d.charmURL { 117 return nil 118 } 119 if state.Step == Done { 120 return ErrSkipExecute 121 } 122 return nil 123 } 124 125 func (d *deploy) getState(state State, step Step) *State { 126 return stateChange{ 127 Kind: d.kind, 128 Step: step, 129 CharmURL: d.charmURL, 130 Hook: d.interruptedHook(state), 131 }.apply(state) 132 } 133 134 func (d *deploy) interruptedHook(state State) *hook.Info { 135 switch state.Kind { 136 case RunHook, Upgrade: 137 return state.Hook 138 } 139 return nil 140 } 141 142 // deployHookKinds determines what kind of hook should be queued after a 143 // given deployment operation. 144 var deployHookKinds = map[Kind]hooks.Kind{ 145 Install: hooks.Install, 146 Upgrade: hooks.UpgradeCharm, 147 }