~juju-qa/ubuntu/xenial/juju/xenial-2.0-beta3

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/cmd/juju/commands/bootstrap.go

  • Committer: Martin Packman
  • Date: 2016-03-30 19:31:08 UTC
  • mfrom: (1.1.41)
  • Revision ID: martin.packman@canonical.com-20160330193108-h9iz3ak334uk0z5r
Merge new upstream source 2.0~beta3

Show diffs side-by-side

added added

removed removed

Lines of Context:
5
5
 
6
6
import (
7
7
        "fmt"
8
 
        "io/ioutil"
 
8
        "io"
9
9
        "os"
10
10
        "strings"
11
11
        "time"
14
14
        "github.com/juju/errors"
15
15
        "github.com/juju/utils"
16
16
        "github.com/juju/utils/featureflag"
 
17
        "github.com/juju/version"
17
18
        "gopkg.in/juju/charm.v6-unstable"
18
19
        "launchpad.net/gnuflag"
19
20
 
20
21
        apiblock "github.com/juju/juju/api/block"
21
 
        "github.com/juju/juju/apiserver"
 
22
        "github.com/juju/juju/apiserver/params"
22
23
        jujucloud "github.com/juju/juju/cloud"
23
24
        "github.com/juju/juju/cmd/juju/block"
 
25
        "github.com/juju/juju/cmd/juju/common"
24
26
        "github.com/juju/juju/cmd/modelcmd"
25
27
        "github.com/juju/juju/constraints"
26
28
        "github.com/juju/juju/environs"
27
29
        "github.com/juju/juju/environs/bootstrap"
28
30
        "github.com/juju/juju/environs/config"
29
 
        "github.com/juju/juju/environs/configstore"
30
31
        "github.com/juju/juju/feature"
31
32
        "github.com/juju/juju/instance"
32
33
        "github.com/juju/juju/juju"
33
34
        "github.com/juju/juju/juju/osenv"
34
35
        "github.com/juju/juju/jujuclient"
35
36
        "github.com/juju/juju/network"
36
 
        "github.com/juju/juju/version"
 
37
        jujuversion "github.com/juju/juju/version"
37
38
)
38
39
 
39
40
// provisionalProviders is the names of providers that are hidden behind
51
52
The controller will be setup with an intial controller model called "admin" as well
52
53
as a hosted model which can be used to run workloads.
53
54
 
54
 
If boostrap-constraints are specified in the bootstrap command, 
55
 
they will apply to the machine provisioned for the juju controller, 
 
55
If boostrap-constraints are specified in the bootstrap command,
 
56
they will apply to the machine provisioned for the juju controller,
56
57
and any future controllers provisioned for HA.
57
58
 
58
 
If constraints are specified, they will be set as the default constraints 
59
 
on the model for all future workload machines, 
 
59
If constraints are specified, they will be set as the default constraints
 
60
on the model for all future workload machines,
60
61
exactly as if the constraints were set with juju set-constraints.
61
62
 
62
63
It is possible to override constraints and the automatic machine selection
99
100
   juju help placement
100
101
`
101
102
 
 
103
// defaultHostedModelName is the name of the hosted model created in each
 
104
// controller for deploying workloads to, in addition to the "admin" model.
 
105
const defaultHostedModelName = "default"
 
106
 
102
107
func newBootstrapCommand() cmd.Command {
103
 
        return modelcmd.Wrap(&bootstrapCommand{
104
 
                CredentialStore: jujuclient.NewFileCredentialStore(),
105
 
        })
 
108
        return modelcmd.Wrap(
 
109
                &bootstrapCommand{},
 
110
                modelcmd.ModelSkipFlags, modelcmd.ModelSkipDefault,
 
111
        )
106
112
}
107
113
 
108
114
// bootstrapCommand is responsible for launching the first machine in a juju
109
115
// environment, and setting up everything necessary to continue working.
110
116
type bootstrapCommand struct {
111
117
        modelcmd.ModelCommandBase
112
 
        CredentialStore jujuclient.CredentialStore
113
118
 
114
119
        Constraints           constraints.Value
115
120
        BootstrapConstraints  constraints.Value
122
127
        AutoUpgrade           bool
123
128
        AgentVersionParam     string
124
129
        AgentVersion          *version.Number
125
 
        config                configFlag
 
130
        config                common.ConfigFlag
126
131
 
127
 
        controllerName string
128
 
        CredentialName string
129
 
        Cloud          string
130
 
        Region         string
 
132
        controllerName  string
 
133
        hostedModelName string
 
134
        CredentialName  string
 
135
        Cloud           string
 
136
        Region          string
131
137
}
132
138
 
133
139
func (c *bootstrapCommand) Info() *cmd.Info {
154
160
        f.StringVar(&c.AgentVersionParam, "agent-version", "", "the version of tools to use for Juju agents")
155
161
        f.StringVar(&c.CredentialName, "credential", "", "the credentials to use when bootstrapping")
156
162
        f.Var(&c.config, "config", "specify a controller config file, or one or more controller configuration options (--config config.yaml [--config k=v ...])")
 
163
        f.StringVar(&c.hostedModelName, "d", defaultHostedModelName, "the name of the default hosted model for the controller")
 
164
        f.StringVar(&c.hostedModelName, "default-model", defaultHostedModelName, "the name of the default hosted model for the controller")
157
165
}
158
166
 
159
167
func (c *bootstrapCommand) Init(args []string) (err error) {
187
195
        }
188
196
        if !c.AutoUpgrade {
189
197
                // With no auto upgrade chosen, we default to the version matching the bootstrap client.
190
 
                vers := version.Current
 
198
                vers := jujuversion.Current
191
199
                c.AgentVersion = &vers
192
200
        }
193
201
        if c.AgentVersionParam != "" {
199
207
                        return err
200
208
                }
201
209
        }
202
 
        if c.AgentVersion != nil && (c.AgentVersion.Major != version.Current.Major || c.AgentVersion.Minor != version.Current.Minor) {
 
210
        if c.AgentVersion != nil && (c.AgentVersion.Major != jujuversion.Current.Major || c.AgentVersion.Minor != jujuversion.Current.Minor) {
203
211
                return fmt.Errorf("requested agent version major.minor mismatch")
204
212
        }
205
213
 
295
303
        }
296
304
 
297
305
        // Get the credentials and region name.
298
 
        credential, regionName, err := c.getCredentials(ctx, c.Cloud, cloud)
 
306
        store := c.ClientStore()
 
307
        credential, credentialName, regionName, err := modelcmd.GetCredentials(
 
308
                store, c.Region, c.CredentialName, c.Cloud, cloud.Type,
 
309
        )
299
310
        if errors.IsNotFound(err) && c.CredentialName == "" {
300
311
                // No credential was explicitly specified, and no credential
301
312
                // was found in credentials.yaml; have the provider detect
302
313
                // credentials from the environment.
303
314
                ctx.Verbosef("no credentials found, checking environment")
304
 
                provider, err := environs.Provider(cloud.Type)
305
 
                if err != nil {
306
 
                        return errors.Trace(err)
307
 
                }
308
 
                detected, err := provider.DetectCredentials()
309
 
                if err != nil {
310
 
                        return errors.Annotatef(err, "detecting credentials for %q cloud provider", c.Cloud)
311
 
                }
312
 
                logger.Tracef("provider detected credentials: %v", detected)
313
 
                if len(detected.AuthCredentials) == 0 {
314
 
                        return errors.NotFoundf("credentials for cloud %q", c.Cloud)
315
 
                }
316
 
                if len(detected.AuthCredentials) > 1 {
 
315
                detected, err := modelcmd.DetectCredential(c.Cloud, cloud.Type)
 
316
                if errors.Cause(err) == modelcmd.ErrMultipleCredentials {
317
317
                        return ambiguousCredentialError
 
318
                } else if err != nil {
 
319
                        return errors.Trace(err)
318
320
                }
319
321
                // We have one credential so extract it from the map.
320
322
                var oneCredential jujucloud.Credential
335
337
                return errors.Trace(err)
336
338
        }
337
339
 
338
 
        // Create an environment config from the cloud and credentials. The
339
 
        // controller's model should be called "admin".
 
340
        hostedModelUUID, err := utils.NewUUID()
 
341
        if err != nil {
 
342
                return errors.Trace(err)
 
343
        }
 
344
        controllerUUID, err := utils.NewUUID()
 
345
        if err != nil {
 
346
                return errors.Trace(err)
 
347
        }
 
348
 
 
349
        // Create an environment config from the cloud and credentials.
340
350
        configAttrs := map[string]interface{}{
341
 
                "type": cloud.Type,
342
 
                // TODO(axw) for now we call the initial model the same as the
343
 
                // controller, without the "local." prefix. This is necessary
344
 
                // to make CI happy. Once CI is updated, we'll switch over to
345
 
                // "admin".
346
 
                "name": configstore.AdminModelName(c.controllerName),
 
351
                "type":                   cloud.Type,
 
352
                "name":                   environs.ControllerModelName,
 
353
                config.UUIDKey:           controllerUUID.String(),
 
354
                config.ControllerUUIDKey: controllerUUID.String(),
347
355
        }
348
356
        userConfigAttrs, err := c.config.ReadAttrs(ctx)
349
357
        if err != nil {
353
361
                configAttrs[k] = v
354
362
        }
355
363
        logger.Debugf("preparing controller with config: %v", configAttrs)
356
 
        cfg, err := config.New(config.UseDefaults, configAttrs)
357
 
        if err != nil {
358
 
                return errors.Annotate(err, "creating environment configuration")
359
 
        }
360
 
        store, err := configstore.Default()
361
 
        if err != nil {
362
 
                return errors.Trace(err)
363
 
        }
364
 
        controllerStore := c.ClientStore()
 
364
 
 
365
        // Read existing current controller, account, model so we can clean up on error.
 
366
        var oldCurrentController string
 
367
        oldCurrentController, err = modelcmd.ReadCurrentController()
 
368
        if err != nil {
 
369
                return errors.Annotate(err, "error reading current controller")
 
370
        }
 
371
 
 
372
        defer func() {
 
373
                if resultErr == nil || errors.IsAlreadyExists(resultErr) {
 
374
                        return
 
375
                }
 
376
                if oldCurrentController != "" {
 
377
                        if err := modelcmd.WriteCurrentController(oldCurrentController); err != nil {
 
378
                                logger.Warningf(
 
379
                                        "cannot reset current controller to %q: %v",
 
380
                                        oldCurrentController, err,
 
381
                                )
 
382
                        }
 
383
                }
 
384
                if err := store.RemoveController(c.controllerName); err != nil {
 
385
                        logger.Warningf(
 
386
                                "cannot destroy newly created controller %q details: %v",
 
387
                                c.controllerName, err,
 
388
                        )
 
389
                }
 
390
        }()
 
391
 
365
392
        environ, err := environsPrepare(
366
 
                modelcmd.BootstrapContext(ctx), store, controllerStore, c.controllerName,
367
 
                environs.PrepareForBootstrapParams{
368
 
                        Config:               cfg,
369
 
                        Credentials:          *credential,
 
393
                modelcmd.BootstrapContext(ctx), store,
 
394
                environs.PrepareParams{
 
395
                        BaseConfig:           configAttrs,
 
396
                        ControllerName:       c.controllerName,
 
397
                        CloudName:            c.Cloud,
370
398
                        CloudRegion:          region.Name,
371
399
                        CloudEndpoint:        region.Endpoint,
372
400
                        CloudStorageEndpoint: region.StorageEndpoint,
 
401
                        Credential:           *credential,
 
402
                        CredentialName:       credentialName,
373
403
                },
374
404
        )
375
405
        if err != nil {
376
406
                return errors.Trace(err)
377
407
        }
378
408
 
 
409
        // Set the current model to the initial hosted model.
 
410
        accountName, err := store.CurrentAccount(c.controllerName)
 
411
        if err != nil {
 
412
                return errors.Trace(err)
 
413
        }
 
414
        if err := store.UpdateModel(c.controllerName, accountName, c.hostedModelName, jujuclient.ModelDetails{
 
415
                hostedModelUUID.String(),
 
416
        }); err != nil {
 
417
                return errors.Trace(err)
 
418
        }
 
419
        if err := store.SetCurrentModel(c.controllerName, accountName, c.hostedModelName); err != nil {
 
420
                return errors.Trace(err)
 
421
        }
 
422
 
379
423
        // Set the current controller so "juju status" can be run while
380
424
        // bootstrapping is underway.
381
425
        if err := modelcmd.WriteCurrentController(c.controllerName); err != nil {
402
446
                        } else {
403
447
                                handleBootstrapError(ctx, resultErr, func() error {
404
448
                                        return environsDestroy(
405
 
                                                c.controllerName, environ, store, controllerStore,
 
449
                                                c.controllerName, environ, store,
406
450
                                        )
407
451
                                })
408
452
                        }
441
485
        }
442
486
        logger.Infof("combined bootstrap constraints: %v", bootstrapConstraints)
443
487
 
 
488
        hostedModelConfig := map[string]interface{}{
 
489
                "name":         c.hostedModelName,
 
490
                config.UUIDKey: hostedModelUUID.String(),
 
491
        }
 
492
 
 
493
        // We copy across any user supplied attributes to the hosted model config.
 
494
        // But only if the attributes have not been removed from the controller
 
495
        // model config as part of preparing the controller model.
 
496
        controllerConfigAttrs := environ.Config().AllAttrs()
 
497
        for k, v := range userConfigAttrs {
 
498
                if _, ok := controllerConfigAttrs[k]; ok {
 
499
                        hostedModelConfig[k] = v
 
500
                }
 
501
        }
 
502
        // Ensure that certain config attributes are not included in the hosted
 
503
        // model config. These attributes may be modified during bootstrap; by
 
504
        // removing them from this map, we ensure the modified values are
 
505
        // inherited.
 
506
        delete(hostedModelConfig, config.AuthKeysConfig)
 
507
        delete(hostedModelConfig, config.AgentVersionKey)
 
508
 
444
509
        err = bootstrapFuncs.Bootstrap(modelcmd.BootstrapContext(ctx), environ, bootstrap.BootstrapParams{
445
 
                EnvironConstraints:   c.Constraints,
 
510
                ModelConstraints:     c.Constraints,
446
511
                BootstrapConstraints: bootstrapConstraints,
447
512
                BootstrapSeries:      c.BootstrapSeries,
448
513
                BootstrapImage:       c.BootstrapImage,
450
515
                UploadTools:          c.UploadTools,
451
516
                AgentVersion:         c.AgentVersion,
452
517
                MetadataDir:          metadataDir,
 
518
                HostedModelConfig:    hostedModelConfig,
453
519
        })
454
520
        if err != nil {
455
521
                return errors.Annotate(err, "failed to bootstrap model")
456
522
        }
457
523
 
458
 
        if err := c.SetModelName(cfg.Name()); err != nil {
 
524
        if err := c.SetModelName(c.hostedModelName); err != nil {
459
525
                return errors.Trace(err)
460
526
        }
461
527
 
462
 
        err = c.setBootstrapEndpointAddress(store, environ)
 
528
        err = c.setBootstrapEndpointAddress(environ)
463
529
        if err != nil {
464
530
                return errors.Annotate(err, "saving bootstrap endpoint address")
465
531
        }
470
536
        return c.waitForAgentInitialisation(ctx)
471
537
}
472
538
 
473
 
// credentialByName returns the credential and default region to use for the
474
 
// specified cloud, optionally specifying a credential name. If no credential
475
 
// name is specified, then use the default credential for the cloud if one has
476
 
// been specified. The credential name is returned also, in case the default
477
 
// credential is used. If there is only one credential, it is implicitly the
478
 
// default.
479
 
//
480
 
// If there exists no matching credentials, an error satisfying
481
 
// errors.IsNotFound will be returned.
482
 
func credentialByName(
483
 
        store jujuclient.CredentialGetter, cloudName, credentialName string,
484
 
) (_ *jujucloud.Credential, credentialNameUsed string, defaultRegion string, _ error) {
485
 
 
486
 
        cloudCredentials, err := store.CredentialForCloud(cloudName)
487
 
        if err != nil {
488
 
                return nil, "", "", errors.Annotate(err, "loading credentials")
489
 
        }
490
 
        if credentialName == "" {
491
 
                // No credential specified, so use the default for the cloud.
492
 
                credentialName = cloudCredentials.DefaultCredential
493
 
                if credentialName == "" && len(cloudCredentials.AuthCredentials) == 1 {
494
 
                        for credentialName = range cloudCredentials.AuthCredentials {
495
 
                        }
496
 
                }
497
 
        }
498
 
        credential, ok := cloudCredentials.AuthCredentials[credentialName]
499
 
        if !ok {
500
 
                return nil, "", "", errors.NotFoundf(
501
 
                        "%q credential for cloud %q", credentialName, cloudName,
502
 
                )
503
 
        }
504
 
        return &credential, credentialName, cloudCredentials.DefaultRegion, nil
505
 
}
506
 
 
507
 
func (c *bootstrapCommand) getCredentials(
508
 
        ctx *cmd.Context,
509
 
        cloudName string,
510
 
        cloud *jujucloud.Cloud,
511
 
) (_ *jujucloud.Credential, region string, _ error) {
512
 
 
513
 
        credential, credentialName, defaultRegion, err := credentialByName(
514
 
                c.CredentialStore, cloudName, c.CredentialName,
515
 
        )
516
 
        if err != nil {
517
 
                return nil, "", errors.Trace(err)
518
 
        }
519
 
 
520
 
        regionName := c.Region
521
 
        if regionName == "" {
522
 
                regionName = defaultRegion
523
 
        }
524
 
 
525
 
        readFile := func(f string) ([]byte, error) {
526
 
                f, err := utils.NormalizePath(f)
527
 
                if err != nil {
528
 
                        return nil, errors.Trace(err)
529
 
                }
530
 
                return ioutil.ReadFile(ctx.AbsPath(f))
531
 
        }
532
 
 
533
 
        // Finalize credential against schemas supported by the provider.
534
 
        provider, err := environs.Provider(cloud.Type)
535
 
        if err != nil {
536
 
                return nil, "", errors.Trace(err)
537
 
        }
538
 
        credential, err = jujucloud.FinalizeCredential(
539
 
                *credential, provider.CredentialSchemas(), readFile,
540
 
        )
541
 
        if err != nil {
542
 
                return nil, "", errors.Annotatef(
543
 
                        err, "validating %q credential for cloud %q",
544
 
                        credentialName, cloudName,
545
 
                )
546
 
        }
547
 
        return credential, regionName, nil
548
 
}
549
 
 
550
539
// getRegion returns the cloud.Region to use, based on the specified
551
540
// region name, and the region name selected if none was specified.
552
541
//
610
599
// waitForAgentInitialisation polls the bootstrapped controller with a read-only
611
600
// command which will fail until the controller is fully initialised.
612
601
// TODO(wallyworld) - add a bespoke command to maybe the admin facade for this purpose.
613
 
func (c *bootstrapCommand) waitForAgentInitialisation(ctx *cmd.Context) (err error) {
 
602
func (c *bootstrapCommand) waitForAgentInitialisation(ctx *cmd.Context) error {
614
603
        attempts := utils.AttemptStrategy{
615
604
                Min:   bootstrapReadyPollCount,
616
605
                Delay: bootstrapReadyPollDelay,
617
606
        }
618
 
        var client block.BlockListAPI
619
 
        for attempt := attempts.Start(); attempt.Next(); {
 
607
        var (
 
608
                retries int
 
609
                client  block.BlockListAPI
 
610
                err     error
 
611
        )
 
612
 
 
613
        for attempt := attempts.Start(); attempt.Next(); retries++ {
620
614
                client, err = blockAPI(&c.ModelCommandBase)
621
615
                if err != nil {
622
616
                        // Logins are prevented whilst space discovery is ongoing.
623
 
                        errorMessage := err.Error()
 
617
                        errorMessage := errors.Cause(err).Error()
624
618
                        if strings.Contains(errorMessage, "space discovery still in progress") {
625
619
                                continue
626
620
                        }
627
 
                        return err
 
621
                        break
628
622
                }
629
623
                _, err = client.List()
630
624
                client.Close()
631
625
                if err == nil {
632
626
                        ctx.Infof("Bootstrap complete, %s now available.", c.controllerName)
633
 
                        return nil
 
627
                        break
634
628
                }
635
629
                // As the API server is coming up, it goes through a number of steps.
636
630
                // Initially the upgrade steps run, but the api server allows some
640
634
                // lead to EOF or "connection is shut down" error messages. We skip
641
635
                // these too, hoping that things come back up before the end of the
642
636
                // retry poll count.
643
 
                errorMessage := err.Error()
644
 
                if strings.Contains(errorMessage, apiserver.UpgradeInProgressError.Error()) ||
645
 
                        strings.HasSuffix(errorMessage, "EOF") ||
646
 
                        strings.HasSuffix(errorMessage, "connection is shut down") {
 
637
                switch {
 
638
                case errors.Cause(err) == io.EOF,
 
639
                        strings.HasSuffix(errors.Cause(err).Error(), "connection is shut down"):
647
640
                        ctx.Infof("Waiting for API to become available")
648
641
                        continue
 
642
                case params.ErrCode(err) == params.CodeUpgradeInProgress:
 
643
                        ctx.Infof("Waiting for API to become available: %v", err)
 
644
                        continue
649
645
                }
650
 
                return err
 
646
                break
651
647
        }
652
 
        return err
 
648
        return errors.Annotatef(err, "unable to contact api server after %d attempts", retries)
653
649
}
654
650
 
655
651
// checkProviderType ensures the provider type is okay.
687
683
// bootstrap server into the connection information. This should only be run
688
684
// once directly after Bootstrap. It assumes that there is just one instance
689
685
// in the environment - the bootstrap instance.
690
 
func (c *bootstrapCommand) setBootstrapEndpointAddress(
691
 
        legacyStore configstore.Storage,
692
 
        environ environs.Environ,
693
 
) error {
 
686
func (c *bootstrapCommand) setBootstrapEndpointAddress(environ environs.Environ) error {
694
687
        instances, err := allInstances(environ)
695
688
        if err != nil {
696
689
                return errors.Trace(err)
713
706
        cfg := environ.Config()
714
707
        apiPort := cfg.APIPort()
715
708
        apiHostPorts := network.AddressesWithPort(netAddrs, apiPort)
716
 
        return juju.UpdateControllerAddresses(c.ClientStore(), legacyStore, c.controllerName, nil, apiHostPorts...)
 
709
        return juju.UpdateControllerAddresses(c.ClientStore(), c.controllerName, nil, apiHostPorts...)
717
710
}