Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(467)

Side by Side Diff: cmd/jujud/upgrade.go

Issue 12183043: cmd/jujud: use API-based upgrader
Patch Set: cmd/jujud: use API-based upgrader Created 11 years, 8 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2012, 2013 Canonical Ltd.
2 // Licensed under the AGPLv3, see LICENCE file for details.
3
4 package main
5
6 import (
7 "fmt"
8 "os"
9 "time"
10
11 "launchpad.net/tomb"
12
13 "launchpad.net/juju-core/agent/tools"
14 "launchpad.net/juju-core/downloader"
15 "launchpad.net/juju-core/environs"
16 "launchpad.net/juju-core/errors"
17 "launchpad.net/juju-core/log"
18 "launchpad.net/juju-core/state"
19 "launchpad.net/juju-core/state/watcher"
20 "launchpad.net/juju-core/version"
21 )
22
23 var upgraderKillDelay = 5 * time.Minute
24
25 // An Upgrader observes the version information for an agent in the
26 // environment state, and handles the downloading and unpacking of
27 // new versions of the juju tools when necessary.
28 //
29 // When a new version is available Wait and Stop return UpgradeReadyError.
30 type Upgrader struct {
31 tomb tomb.Tomb
32 st *state.State
33 agentState AgentState
34 dataDir string
35 }
36
37 // UpgradeReadyError is returned by an Upgrader to report that
38 // an upgrade is ready to be performed and a restart is due.
39 type UpgradeReadyError struct {
40 AgentName string
41 OldTools *tools.Tools
42 NewTools *tools.Tools
43 DataDir string
44 }
45
46 func (e *UpgradeReadyError) Error() string {
47 return "must restart: an agent upgrade is available"
48 }
49
50 // ChangeAgentTools does the actual agent upgrade.
51 func (e *UpgradeReadyError) ChangeAgentTools() error {
52 tools, err := tools.ChangeAgentTools(e.DataDir, e.AgentName, e.NewTools. Version)
53 if err != nil {
54 return err
55 }
56 log.Infof("upgrader upgraded from %v to %v (%q)", e.OldTools.Version, to ols.Version, tools.URL)
57 return nil
58 }
59
60 // NewUpgrader returns a new Upgrader watching the given agent.
61 func NewUpgrader(st *state.State, agentState AgentState, dataDir string) *Upgrad er {
62 u := &Upgrader{
63 st: st,
64 agentState: agentState,
65 dataDir: dataDir,
66 }
67 go func() {
68 defer u.tomb.Done()
69 u.tomb.Kill(u.run())
70 }()
71 return u
72 }
73
74 func (u *Upgrader) String() string {
75 return "upgrader"
76 }
77
78 func (u *Upgrader) Kill() {
79 u.tomb.Kill(nil)
80 }
81
82 func (u *Upgrader) Stop() error {
83 u.tomb.Kill(nil)
84 return u.tomb.Wait()
85 }
86
87 func (u *Upgrader) Wait() error {
88 return u.tomb.Wait()
89 }
90
91 func (u *Upgrader) run() error {
92 // Let the state know the version that is currently running.
93 currentTools, err := tools.ReadTools(u.dataDir, version.Current)
94 if err != nil {
95 // Don't abort everything because we can't find the tools direct ory.
96 // The problem should sort itself out as we will immediately
97 // download some more tools and upgrade.
98 log.Warningf("upgrader cannot read current tools: %v", err)
99 currentTools = &tools.Tools{
100 Version: version.Current,
101 }
102 }
103 err = u.agentState.SetAgentTools(currentTools)
104 if err != nil {
105 return err
106 }
107
108 // TODO(fwereade): this whole package should be ignorant of environs,
109 // so it shouldn't be watching environ config (and it shouldn't be
110 // looking in storage): we should be able to find out what to download
111 // from state, exactly as we do for charms.
112 w := u.st.WatchEnvironConfig()
113 defer watcher.Stop(w, &u.tomb)
114
115 // Rather than using worker.WaitForEnviron, invalid environments are
116 // managed explicitly so that all configuration changes are observed
117 // by the loop below.
118 var environ environs.Environ
119
120 // TODO(rog) retry downloads when they fail.
121 var (
122 download *downloader.Download
123 downloadTools *tools.Tools
124 downloadDone <-chan downloader.Status
125 )
126 // If we're killed early on (probably as a result of some other
127 // task dying) we allow ourselves some time to try to connect to
128 // the state and download a new version. We return to normal
129 // undelayed behaviour when:
130 // 1) We find there's no upgrade to do.
131 // 2) A download fails.
132 tomb := delayedTomb(&u.tomb, upgraderKillDelay)
133 noDelay := func() {
134 if tomb != &u.tomb {
135 tomb.Kill(nil)
136 tomb = &u.tomb
137 }
138 }
139 for {
140 // We wait for the tools to change while we're downloading
141 // so that if something goes wrong (for instance a bad URL
142 // hangs up) another change to the proposed tools can
143 // potentially fix things.
144 select {
145 case cfg, ok := <-w.Changes():
146 if !ok {
147 return watcher.MustErr(w)
148 }
149 var err error
150 if environ == nil {
151 environ, err = environs.New(cfg)
152 if err != nil {
153 log.Errorf("upgrader loaded invalid init ial environment configuration: %v", err)
154 break
155 }
156 } else {
157 err = environ.SetConfig(cfg)
158 if err != nil {
159 log.Warningf("upgrader loaded invalid en vironment configuration: %v", err)
160 // continue on, because the version numb er is still significant.
161 }
162 }
163 proposed, ok := cfg.AgentVersion()
164 if !ok {
165 // This shouldn't be possible; but if it happens it's no reason
166 // to kill this task. Just wait for the config t o change again.
167 continue
168 }
169 if download != nil {
170 // There's a download in progress, stop it if we need to.
171 if downloadTools.Version.Number == proposed {
172 // We are already downloading the reques ted tools.
173 break
174 }
175 // Tools changed. We need to stop and restart.
176 download.Stop()
177 download, downloadTools, downloadDone = nil, nil , nil
178 }
179 // TODO: major version upgrades.
180 if proposed.Major != version.Current.Major {
181 log.Errorf("major version upgrades are not suppo rted yet")
182 noDelay()
183 break
184 }
185 if proposed == version.Current.Number {
186 noDelay()
187 break
188 }
189 required := version.Binary{
190 Number: proposed,
191 Series: version.Current.Series,
192 Arch: version.Current.Arch,
193 }
194 if tools, err := tools.ReadTools(u.dataDir, required); e rr == nil {
195 // The exact tools have already been downloaded, so use them.
196 return u.upgradeReady(currentTools, tools)
197 }
198 tools, err := environs.FindExactTools(environ, required)
199 if err != nil {
200 log.Errorf("upgrader error finding tools for %v: %v", required, err)
201 if !errors.IsNotFoundError(err) {
202 return err
203 }
204 noDelay()
205 // TODO(rog): poll until tools become available.
206 break
207 }
208 log.Infof("upgrader downloading %q", tools.URL)
209 download = downloader.New(tools.URL, "")
210 downloadTools = tools
211 downloadDone = download.Done()
212 case status := <-downloadDone:
213 newTools := downloadTools
214 download, downloadTools, downloadDone = nil, nil, nil
215 if status.Err != nil {
216 log.Errorf("upgrader download of %v failed: %v", newTools.Version, status.Err)
217 noDelay()
218 break
219 }
220 err := tools.UnpackTools(u.dataDir, newTools, status.Fil e)
221 status.File.Close()
222 if err := os.Remove(status.File.Name()); err != nil {
223 log.Warningf("upgrader cannot remove temporary d ownload file: %v", err)
224 }
225 if err != nil {
226 log.Errorf("upgrader cannot unpack %v tools: %v" , newTools.Version, err)
227 noDelay()
228 break
229 }
230 return u.upgradeReady(currentTools, newTools)
231 case <-tomb.Dying():
232 if download != nil {
233 return fmt.Errorf("upgrader aborted download of %q", downloadTools.URL)
234 }
235 return nil
236 }
237 }
238 panic("not reached")
239 }
240
241 func (u *Upgrader) upgradeReady(old, new *tools.Tools) *UpgradeReadyError {
242 return &UpgradeReadyError{
243 AgentName: u.agentState.Tag(),
244 OldTools: old,
245 DataDir: u.dataDir,
246 NewTools: new,
247 }
248 }
249
250 // delayedTomb returns a tomb that starts dying a given duration
251 // after t starts dying.
252 func delayedTomb(t *tomb.Tomb, d time.Duration) *tomb.Tomb {
253 var delayed tomb.Tomb
254 go func() {
255 select {
256 case <-t.Dying():
257 time.Sleep(d)
258 delayed.Kill(nil)
259 case <-delayed.Dying():
260 return
261 }
262 }()
263 return &delayed
264 }
OLDNEW

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b