~ubuntu-branches/ubuntu/trusty/juju-core/trusty

« back to all changes in this revision

Viewing changes to src/launchpad.net/juju-core/cmd/juju/upgradejuju.go

  • Committer: Package Import Robot
  • Author(s): James Page
  • Date: 2014-03-24 16:05:44 UTC
  • mfrom: (1.1.20)
  • Revision ID: package-import@ubuntu.com-20140324160544-g8lsfufby18d5fj4
Tags: 1.17.6-0ubuntu1
* New upstream point release, including fixes for:
  - br0 not bought up by cloud-init with MAAS provider (LP: #1271144).
  - ppc64el enablement for juju/lxc (LP: #1273769).
  - juju userdata should not restart networking (LP: #1248283).
  - error detecting hardware characteristics (LP: #1276909).
  - juju instances not including the default security group (LP: #1129720).
  - juju bootstrap does not honor https_proxy (LP: #1240260).
* d/control,rules: Drop BD on bash-completion, install bash-completion
  direct from upstream source code.
* d/rules: Set HOME prior to generating man pages.
* d/control: Drop alternative dependency on mongodb-server; juju now only
  works on trusty with juju-mongodb.

Show diffs side-by-side

added added

removed removed

Lines of Context:
6
6
import (
7
7
        stderrors "errors"
8
8
        "fmt"
 
9
        "os"
 
10
        "path"
9
11
 
10
12
        "launchpad.net/gnuflag"
11
13
 
12
14
        "launchpad.net/juju-core/cmd"
13
15
        "launchpad.net/juju-core/environs"
 
16
        "launchpad.net/juju-core/environs/bootstrap"
14
17
        "launchpad.net/juju-core/environs/config"
15
18
        "launchpad.net/juju-core/environs/storage"
16
19
        "launchpad.net/juju-core/environs/sync"
17
20
        envtools "launchpad.net/juju-core/environs/tools"
18
21
        "launchpad.net/juju-core/errors"
19
22
        "launchpad.net/juju-core/juju"
20
 
        "launchpad.net/juju-core/log"
 
23
        "launchpad.net/juju-core/state/api"
21
24
        "launchpad.net/juju-core/state/api/params"
22
25
        coretools "launchpad.net/juju-core/tools"
23
26
        "launchpad.net/juju-core/version"
117
120
        defer client.Close()
118
121
        defer func() {
119
122
                if err == errUpToDate {
120
 
                        log.Noticef(err.Error())
 
123
                        logger.Infof(err.Error())
121
124
                        err = nil
122
125
                }
123
126
        }()
134
137
        if err != nil {
135
138
                return err
136
139
        }
137
 
        env, err := environs.New(cfg)
138
 
        if err != nil {
139
 
                return err
140
 
        }
141
 
        v, err := c.initVersions(cfg, env)
142
 
        if err != nil {
143
 
                return err
144
 
        }
145
 
        if c.UploadTools {
146
 
                series := getUploadSeries(cfg, c.Series)
147
 
                if err := v.uploadTools(env.Storage(), series); err != nil {
148
 
                        return err
149
 
                }
150
 
        }
151
 
        if err := v.validate(); err != nil {
152
 
                return err
153
 
        }
154
 
        log.Infof("upgrade version chosen: %s", v.chosen)
155
 
        // TODO(fwereade): this list may be incomplete, pending envtools.Upload change.
156
 
        log.Infof("available tools: %s", v.tools)
157
 
 
158
 
        if err := client.SetEnvironAgentVersion(v.chosen); err != nil {
159
 
                return err
160
 
        }
161
 
        log.Noticef("started upgrade to %s", v.chosen)
162
 
        return nil
163
 
}
164
 
 
165
 
// run1dot16 implements the command without access to the API. This is
166
 
// needed for compatibility, so 1.16 can be upgraded to newer
167
 
// releases. It should be removed in 1.18.
168
 
func (c *UpgradeJujuCommand) run1dot16() error {
169
 
        log.Warningf("running in 1.16 compatibility mode")
170
 
        conn, err := juju.NewConnFromName(c.EnvName)
171
 
        if err != nil {
172
 
                return err
173
 
        }
174
 
        defer conn.Close()
175
 
        defer func() {
176
 
                if err == errUpToDate {
177
 
                        log.Noticef(err.Error())
178
 
                        err = nil
179
 
                }
180
 
        }()
181
 
 
182
 
        // Determine the version to upgrade to, uploading tools if necessary.
183
 
        env := conn.Environ
184
 
        cfg, err := conn.State.EnvironConfig()
185
 
        if err != nil {
186
 
                return err
187
 
        }
188
 
        v, err := c.initVersions(cfg, env)
189
 
        if err != nil {
190
 
                return err
191
 
        }
192
 
        if c.UploadTools {
193
 
                series := getUploadSeries(cfg, c.Series)
194
 
                if err := v.uploadTools(env.Storage(), series); err != nil {
195
 
                        return err
196
 
                }
197
 
        }
198
 
        if err := v.validate(); err != nil {
199
 
                return err
200
 
        }
201
 
        log.Infof("upgrade version chosen: %s", v.chosen)
202
 
        // TODO(fwereade): this list may be incomplete, pending envtools.Upload change.
203
 
        log.Infof("available tools: %s", v.tools)
204
 
 
205
 
        if err := conn.State.SetEnvironAgentVersion(v.chosen); err != nil {
206
 
                return err
207
 
        }
208
 
        log.Noticef("started upgrade to %s", v.chosen)
 
140
        context, err := c.initVersions(client, cfg)
 
141
        if err != nil {
 
142
                return err
 
143
        }
 
144
        if c.UploadTools {
 
145
                series := bootstrap.SeriesToUpload(cfg, c.Series)
 
146
                if err := context.uploadTools(series); err != nil {
 
147
                        return err
 
148
                }
 
149
        }
 
150
        if err := context.validate(); err != nil {
 
151
                return err
 
152
        }
 
153
        logger.Infof("upgrade version chosen: %s", context.chosen)
 
154
        // TODO(fwereade): this list may be incomplete, pending envtools.Upload change.
 
155
        logger.Infof("available tools: %s", context.tools)
 
156
 
 
157
        if err := client.SetEnvironAgentVersion(context.chosen); err != nil {
 
158
                return err
 
159
        }
 
160
        logger.Infof("started upgrade to %s", context.chosen)
209
161
        return nil
210
162
}
211
163
 
213
165
// agent and client versions, and the list of currently available tools, will
214
166
// always be accurate; the chosen version, and the flag indicating development
215
167
// mode, may remain blank until uploadTools or validate is called.
216
 
func (c *UpgradeJujuCommand) initVersions(cfg *config.Config, env environs.Environ) (*upgradeVersions, error) {
 
168
func (c *UpgradeJujuCommand) initVersions(client *api.Client, cfg *config.Config) (*upgradeContext, error) {
217
169
        agent, ok := cfg.AgentVersion()
218
170
        if !ok {
219
171
                // Can't happen. In theory.
222
174
        if c.Version == agent {
223
175
                return nil, errUpToDate
224
176
        }
225
 
        client := version.Current.Number
226
 
        // TODO use an API call rather than requiring the environment,
227
 
        // so that we can restrict access to the provider secrets
228
 
        // while still allowing users to upgrade.
229
 
        available, err := envtools.FindTools(env, client.Major, -1, coretools.Filter{}, envtools.DoNotAllowRetry)
 
177
        clientVersion := version.Current.Number
 
178
        findResult, err := client.FindTools(clientVersion.Major, -1, "", "")
 
179
        var availableTools coretools.List
 
180
        if params.IsCodeNotImplemented(err) {
 
181
                availableTools, err = findTools1dot17(cfg)
 
182
        } else {
 
183
                availableTools = findResult.List
 
184
        }
230
185
        if err != nil {
231
 
                if !errors.IsNotFoundError(err) {
 
186
                return nil, err
 
187
        }
 
188
        err = findResult.Error
 
189
        if findResult.Error != nil {
 
190
                if !params.IsCodeNotFound(err) {
232
191
                        return nil, err
233
192
                }
234
193
                if !c.UploadTools {
235
 
                        // No tools found and we shouldn't upload any, so pretend
236
 
                        // there is no more recent version available.
237
 
                        if c.Version == version.Zero {
 
194
                        // No tools found and we shouldn't upload any, so if we are not asking for a
 
195
                        // major upgrade, pretend there is no more recent version available.
 
196
                        if c.Version == version.Zero && agent.Major == clientVersion.Major {
238
197
                                return nil, errUpToDate
239
198
                        }
240
199
                        return nil, err
241
200
                }
242
201
        }
243
 
        return &upgradeVersions{
244
 
                agent:  agent,
245
 
                client: client,
246
 
                chosen: c.Version,
247
 
                tools:  available,
 
202
        return &upgradeContext{
 
203
                agent:     agent,
 
204
                client:    clientVersion,
 
205
                chosen:    c.Version,
 
206
                tools:     availableTools,
 
207
                apiClient: client,
 
208
                config:    cfg,
248
209
        }, nil
249
210
}
250
211
 
251
 
// upgradeVersions holds the version information for making upgrade decisions.
252
 
type upgradeVersions struct {
253
 
        agent  version.Number
254
 
        client version.Number
255
 
        chosen version.Number
256
 
        tools  coretools.List
 
212
// findTools1dot17 allows 1.17.x versions to be upgraded.
 
213
func findTools1dot17(cfg *config.Config) (coretools.List, error) {
 
214
        logger.Warningf("running find tools in 1.17 compatibility mode")
 
215
        env, err := environs.New(cfg)
 
216
        if err != nil {
 
217
                return nil, err
 
218
        }
 
219
        clientVersion := version.Current.Number
 
220
        return envtools.FindTools(env, clientVersion.Major, -1, coretools.Filter{}, envtools.DoNotAllowRetry)
 
221
}
 
222
 
 
223
// upgradeContext holds the version information for making upgrade decisions.
 
224
type upgradeContext struct {
 
225
        agent     version.Number
 
226
        client    version.Number
 
227
        chosen    version.Number
 
228
        tools     coretools.List
 
229
        config    *config.Config
 
230
        apiClient *api.Client
257
231
}
258
232
 
259
233
// uploadTools compiles jujud from $GOPATH and uploads it into the supplied
263
237
// than that of any otherwise-matching available envtools.
264
238
// uploadTools resets the chosen version and replaces the available tools
265
239
// with the ones just uploaded.
266
 
func (v *upgradeVersions) uploadTools(storage storage.Storage, series []string) error {
 
240
func (context *upgradeContext) uploadTools(series []string) (err error) {
267
241
        // TODO(fwereade): this is kinda crack: we should not assume that
268
242
        // version.Current matches whatever source happens to be built. The
269
243
        // ideal would be:
276
250
        // ...but there's no way we have time for that now. In the meantime,
277
251
        // considering the use cases, this should work well enough; but it
278
252
        // won't detect an incompatible major-version change, which is a shame.
279
 
        if v.chosen == version.Zero {
280
 
                v.chosen = v.client
281
 
        }
282
 
        v.chosen = uploadVersion(v.chosen, v.tools)
283
 
 
284
 
        // TODO(fwereade): envtools.Upload should return envtools.List, and should
285
 
        // include all the extra series we build, so we can set *that* onto
286
 
        // v.available and maybe one day be able to check that a given upgrade
287
 
        // won't leave out-of-date machines lying around, starved of envtools.
288
 
        uploaded, err := sync.Upload(storage, &v.chosen, series...)
289
 
        if err != nil {
290
 
                return err
291
 
        }
292
 
        v.tools = coretools.List{uploaded}
 
253
        if context.chosen == version.Zero {
 
254
                context.chosen = context.client
 
255
        }
 
256
        context.chosen = uploadVersion(context.chosen, context.tools)
 
257
 
 
258
        builtTools, err := sync.BuildToolsTarball(&context.chosen)
 
259
        if err != nil {
 
260
                return err
 
261
        }
 
262
        defer os.RemoveAll(builtTools.Dir)
 
263
 
 
264
        var uploaded *coretools.Tools
 
265
        toolsPath := path.Join(builtTools.Dir, builtTools.StorageName)
 
266
        logger.Infof("uploading tools %v (%dkB) to Juju state server", builtTools.Version, (builtTools.Size+512)/1024)
 
267
        uploaded, err = context.apiClient.UploadTools(toolsPath, builtTools.Version, series...)
 
268
        if params.IsCodeNotImplemented(err) {
 
269
                uploaded, err = context.uploadTools1dot17(builtTools, series...)
 
270
        }
 
271
        if err != nil {
 
272
                return err
 
273
        }
 
274
        context.tools = coretools.List{uploaded}
293
275
        return nil
294
276
}
295
277
 
 
278
func (context *upgradeContext) uploadTools1dot17(builtTools *sync.BuiltTools,
 
279
        series ...string) (*coretools.Tools, error) {
 
280
 
 
281
        logger.Warningf("running upload tools in 1.17 compatibility mode")
 
282
        env, err := environs.New(context.config)
 
283
        if err != nil {
 
284
                return nil, err
 
285
        }
 
286
        return sync.SyncBuiltTools(env.Storage(), builtTools, series...)
 
287
}
 
288
 
296
289
// validate chooses an upgrade version, if one has not already been chosen,
297
290
// and ensures the tools list contains no entries that do not have that version.
298
291
// If validate returns no error, the environment agent-version can be set to
299
292
// the value of the chosen field.
300
 
func (v *upgradeVersions) validate() (err error) {
301
 
        if v.chosen == version.Zero {
302
 
                // No explicitly specified version, so find the next available
303
 
                // stable release to upgrade to, starting from the current agent
304
 
                // version and doing major.minor+1 or +2 as needed.
305
 
                nextStable := v.agent
306
 
                if v.agent.IsDev() {
307
 
                        nextStable.Minor += 1
 
293
func (context *upgradeContext) validate() (err error) {
 
294
        if context.chosen == version.Zero {
 
295
                // No explicitly specified version, so find the version to which we
 
296
                // need to upgrade. If the CLI and agent major versions match, we find
 
297
                // next available stable release to upgrade to by incrementing the
 
298
                // minor version, starting from the current agent version and doing
 
299
                // major.minor+1 or +2 as needed. If the CLI has a greater major version,
 
300
                // we just use the CLI version as is.
 
301
                nextVersion := context.agent
 
302
                if nextVersion.Major == context.client.Major {
 
303
                        if context.agent.IsDev() {
 
304
                                nextVersion.Minor += 1
 
305
                        } else {
 
306
                                nextVersion.Minor += 2
 
307
                        }
308
308
                } else {
309
 
                        nextStable.Minor += 2
 
309
                        nextVersion = context.client
310
310
                }
311
311
 
312
 
                newestNextStable, found := v.tools.NewestCompatible(nextStable)
 
312
                newestNextStable, found := context.tools.NewestCompatible(nextVersion)
313
313
                if found {
314
 
                        log.Debugf("found a more recent stable version %s", newestNextStable)
315
 
                        v.chosen = newestNextStable
 
314
                        logger.Debugf("found a more recent stable version %s", newestNextStable)
 
315
                        context.chosen = newestNextStable
316
316
                } else {
317
 
                        newestCurrent, found := v.tools.NewestCompatible(v.agent)
 
317
                        newestCurrent, found := context.tools.NewestCompatible(context.agent)
318
318
                        if found {
319
 
                                log.Debugf("found more recent current version %s", newestCurrent)
320
 
                                v.chosen = newestCurrent
 
319
                                logger.Debugf("found more recent current version %s", newestCurrent)
 
320
                                context.chosen = newestCurrent
321
321
                        } else {
322
 
                                return fmt.Errorf("no more recent supported versions available")
 
322
                                if context.agent.Major != context.client.Major {
 
323
                                        return fmt.Errorf("no compatible tools available")
 
324
                                } else {
 
325
                                        return fmt.Errorf("no more recent supported versions available")
 
326
                                }
323
327
                        }
324
328
                }
325
329
        } else {
326
330
                // If not completely specified already, pick a single tools version.
327
 
                filter := coretools.Filter{Number: v.chosen, Released: !v.chosen.IsDev()}
328
 
                if v.tools, err = v.tools.Match(filter); err != nil {
 
331
                filter := coretools.Filter{Number: context.chosen, Released: !context.chosen.IsDev()}
 
332
                if context.tools, err = context.tools.Match(filter); err != nil {
329
333
                        return err
330
334
                }
331
 
                v.chosen, v.tools = v.tools.Newest()
 
335
                context.chosen, context.tools = context.tools.Newest()
332
336
        }
333
 
        if v.chosen == v.agent {
 
337
        if context.chosen == context.agent {
334
338
                return errUpToDate
335
339
        }
336
340
 
337
 
        // Major version upgrade
338
 
        if v.chosen.Major < v.agent.Major {
 
341
        // Disallow major.minor version downgrades.
 
342
        if context.chosen.Major < context.agent.Major ||
 
343
                context.chosen.Major == context.agent.Major && context.chosen.Minor < context.agent.Minor {
339
344
                // TODO(fwereade): I'm a bit concerned about old agent/CLI tools even
340
345
                // *connecting* to environments with higher agent-versions; but ofc they
341
346
                // have to connect in order to discover they shouldn't. However, once
342
347
                // any of our tools detect an incompatible version, they should act to
343
348
                // minimize damage: the CLI should abort politely, and the agents should
344
349
                // run an Upgrader but no other tasks.
345
 
                return fmt.Errorf("cannot change major version from %d to %d", v.agent.Major, v.chosen.Major)
346
 
        } else if v.chosen.Major > v.agent.Major {
347
 
                return fmt.Errorf("major version upgrades are not supported yet")
 
350
                return fmt.Errorf("cannot change version from %s to %s", context.agent, context.chosen)
348
351
        }
349
352
 
350
353
        return nil
364
367
        }
365
368
        return vers
366
369
}
 
370
 
 
371
// run1dot16 implements the command without access to the API. This is
 
372
// needed for compatibility, so 1.16 can be upgraded to newer
 
373
// releases. It should be removed in 1.18.
 
374
func (c *UpgradeJujuCommand) run1dot16() error {
 
375
        logger.Warningf("running in 1.16 compatibility mode")
 
376
        conn, err := juju.NewConnFromName(c.EnvName)
 
377
        if err != nil {
 
378
                return err
 
379
        }
 
380
        defer conn.Close()
 
381
        defer func() {
 
382
                if err == errUpToDate {
 
383
                        logger.Infof(err.Error())
 
384
                        err = nil
 
385
                }
 
386
        }()
 
387
 
 
388
        // Determine the version to upgrade to, uploading tools if necessary.
 
389
        env := conn.Environ
 
390
        cfg, err := conn.State.EnvironConfig()
 
391
        if err != nil {
 
392
                return err
 
393
        }
 
394
        context, err := c.initVersions1dot16(cfg, env)
 
395
        if err != nil {
 
396
                return err
 
397
        }
 
398
        if c.UploadTools {
 
399
                series := bootstrap.SeriesToUpload(cfg, c.Series)
 
400
                if err := context.uploadTools1dot16(env.Storage(), series); err != nil {
 
401
                        return err
 
402
                }
 
403
        }
 
404
        if err := context.validate(); err != nil {
 
405
                return err
 
406
        }
 
407
        logger.Infof("upgrade version chosen: %s", context.chosen)
 
408
        logger.Infof("available tools: %s", context.tools)
 
409
 
 
410
        if err := conn.State.SetEnvironAgentVersion(context.chosen); err != nil {
 
411
                return err
 
412
        }
 
413
        logger.Infof("started upgrade to %s", context.chosen)
 
414
        return nil
 
415
}
 
416
 
 
417
func (c *UpgradeJujuCommand) initVersions1dot16(cfg *config.Config, env environs.Environ) (*upgradeContext, error) {
 
418
        agent, ok := cfg.AgentVersion()
 
419
        if !ok {
 
420
                // Can't happen. In theory.
 
421
                return nil, fmt.Errorf("incomplete environment configuration")
 
422
        }
 
423
        if c.Version == agent {
 
424
                return nil, errUpToDate
 
425
        }
 
426
        client := version.Current.Number
 
427
        available, err := envtools.FindTools(env, client.Major, -1, coretools.Filter{}, envtools.DoNotAllowRetry)
 
428
        if err != nil {
 
429
                if !errors.IsNotFoundError(err) {
 
430
                        return nil, err
 
431
                }
 
432
                if !c.UploadTools {
 
433
                        // No tools found and we shouldn't upload any, so if we are not asking for a
 
434
                        // major upgrade, pretend there is no more recent version available.
 
435
                        if c.Version == version.Zero && agent.Major == client.Major {
 
436
                                return nil, errUpToDate
 
437
                        }
 
438
                        return nil, err
 
439
                }
 
440
        }
 
441
        return &upgradeContext{
 
442
                agent:  agent,
 
443
                client: client,
 
444
                chosen: c.Version,
 
445
                tools:  available,
 
446
        }, nil
 
447
}
 
448
 
 
449
func (context *upgradeContext) uploadTools1dot16(storage storage.Storage, series []string) error {
 
450
        if context.chosen == version.Zero {
 
451
                context.chosen = context.client
 
452
        }
 
453
        context.chosen = uploadVersion(context.chosen, context.tools)
 
454
        uploaded, err := sync.Upload(storage, &context.chosen, series...)
 
455
        if err != nil {
 
456
                return err
 
457
        }
 
458
        context.tools = coretools.List{uploaded}
 
459
        return nil
 
460
}