github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/juju/commands/bootstrap.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package commands 5 6 import ( 7 "fmt" 8 "io" 9 "os" 10 "strings" 11 "time" 12 13 "github.com/juju/cmd" 14 "github.com/juju/errors" 15 "github.com/juju/utils" 16 "github.com/juju/utils/featureflag" 17 "github.com/juju/version" 18 "gopkg.in/juju/charm.v6-unstable" 19 "launchpad.net/gnuflag" 20 21 apiblock "github.com/juju/juju/api/block" 22 "github.com/juju/juju/apiserver/params" 23 jujucloud "github.com/juju/juju/cloud" 24 "github.com/juju/juju/cmd/juju/block" 25 "github.com/juju/juju/cmd/juju/common" 26 "github.com/juju/juju/cmd/modelcmd" 27 "github.com/juju/juju/constraints" 28 "github.com/juju/juju/environs" 29 "github.com/juju/juju/environs/bootstrap" 30 "github.com/juju/juju/environs/config" 31 "github.com/juju/juju/feature" 32 "github.com/juju/juju/instance" 33 "github.com/juju/juju/juju" 34 "github.com/juju/juju/juju/osenv" 35 "github.com/juju/juju/jujuclient" 36 "github.com/juju/juju/network" 37 jujuversion "github.com/juju/juju/version" 38 ) 39 40 // provisionalProviders is the names of providers that are hidden behind 41 // feature flags. 42 var provisionalProviders = map[string]string{ 43 "vsphere": feature.VSphereProvider, 44 } 45 46 var usageBootstrapSummary = ` 47 Initializes a cloud environment.`[1:] 48 49 var usageBootstrapDetails = ` 50 Initialization consists of creating an 'admin' model and provisioning a 51 machine to act as controller. 52 Credentials are set beforehand and are distinct from any other 53 configuration (see `[1:] + "`juju add-credential`" + `). 54 The 'admin' model typically does not run workloads. It should remain 55 pristine to run and manage Juju's own infrastructure for the corresponding 56 cloud. Additional (hosted) models should be created with ` + "`juju create-\nmodel`" + ` for workload purposes. 57 Note that a 'default' model is also created and becomes the current model 58 of the environment once the command completes. It can be discarded if 59 other models are created. 60 If '--bootstrap-constraints' is used, its values will also apply to any 61 future controllers provisioned for high availability (HA). 62 If '--constraints' is used, its values will be set as the default 63 constraints for all future workload machines in the model, exactly as if 64 the constraints were set with ` + "`juju set-model-constraints`" + `. 65 It is possible to override constraints and the automatic machine selection 66 algorithm by assigning a "placement directive" via the '--to' option. This 67 dictates what machine to use for the controller. This would typically be 68 used with the MAAS provider ('--to <host>.maas'). 69 You can change the default timeout and retry delays used during the 70 bootstrap by changing the following settings in your configuration file 71 (all values represent number of seconds): 72 # How long to wait for a connection to the controller 73 bootstrap-timeout: 600 # default: 10 minutes 74 # How long to wait between connection attempts to a controller 75 address. 76 bootstrap-retry-delay: 5 # default: 5 seconds 77 # How often to refresh controller addresses from the API server. 78 bootstrap-addresses-delay: 10 # default: 10 seconds 79 Private clouds may need to specify their own custom image metadata and 80 tools/agent. Use '--metadata-source' whose value is a local directory. 81 The value of '--agent-version' will become the default tools version to 82 use in all models for this controller. The full binary version is accepted 83 (e.g.: 2.0.1-xenial-amd64) but only the numeric version (e.g.: 2.0.1) is 84 used. Otherwise, by default, the version used is that of the client. 85 86 Examples: 87 juju bootstrap mycontroller google 88 juju bootstrap --config=~/config-rs.yaml mycontroller rackspace 89 juju bootstrap --config agent-version=1.25.3 mycontroller aws 90 juju bootstrap --config bootstrap-timeout=1200 mycontroller azure 91 92 See also: 93 add-credentials 94 create-model 95 set-constraints` 96 97 // defaultHostedModelName is the name of the hosted model created in each 98 // controller for deploying workloads to, in addition to the "admin" model. 99 const defaultHostedModelName = "default" 100 101 func newBootstrapCommand() cmd.Command { 102 return modelcmd.Wrap( 103 &bootstrapCommand{}, 104 modelcmd.ModelSkipFlags, modelcmd.ModelSkipDefault, 105 ) 106 } 107 108 // bootstrapCommand is responsible for launching the first machine in a juju 109 // environment, and setting up everything necessary to continue working. 110 type bootstrapCommand struct { 111 modelcmd.ModelCommandBase 112 113 Constraints constraints.Value 114 BootstrapConstraints constraints.Value 115 BootstrapSeries string 116 BootstrapImage string 117 UploadTools bool 118 MetadataSource string 119 Placement string 120 KeepBrokenEnvironment bool 121 AutoUpgrade bool 122 AgentVersionParam string 123 AgentVersion *version.Number 124 config common.ConfigFlag 125 126 controllerName string 127 hostedModelName string 128 CredentialName string 129 Cloud string 130 Region string 131 noGUI bool 132 } 133 134 func (c *bootstrapCommand) Info() *cmd.Info { 135 return &cmd.Info{ 136 Name: "bootstrap", 137 Args: "<controller name> <cloud name>[/region]", 138 Purpose: usageBootstrapSummary, 139 Doc: usageBootstrapDetails, 140 } 141 } 142 143 func (c *bootstrapCommand) SetFlags(f *gnuflag.FlagSet) { 144 f.Var(constraints.ConstraintsValue{Target: &c.Constraints}, "constraints", "Set model constraints") 145 f.Var(constraints.ConstraintsValue{Target: &c.BootstrapConstraints}, "bootstrap-constraints", "Specify bootstrap machine constraints") 146 f.StringVar(&c.BootstrapSeries, "bootstrap-series", "", "Specify the series of the bootstrap machine") 147 if featureflag.Enabled(feature.ImageMetadata) { 148 f.StringVar(&c.BootstrapImage, "bootstrap-image", "", "Specify the image of the bootstrap machine") 149 } 150 f.BoolVar(&c.UploadTools, "upload-tools", false, "Upload local version of tools before bootstrapping") 151 f.StringVar(&c.MetadataSource, "metadata-source", "", "Local path to use as tools and/or metadata source") 152 f.StringVar(&c.Placement, "to", "", "Placement directive indicating an instance to bootstrap") 153 f.BoolVar(&c.KeepBrokenEnvironment, "keep-broken", false, "Do not destroy the model if bootstrap fails") 154 f.BoolVar(&c.AutoUpgrade, "auto-upgrade", false, "Upgrade to the latest patch release tools on first bootstrap") 155 f.StringVar(&c.AgentVersionParam, "agent-version", "", "Version of tools to use for Juju agents") 156 f.StringVar(&c.CredentialName, "credential", "", "Credentials to use when bootstrapping") 157 f.Var(&c.config, "config", "Specify a controller configuration file, or one or more configuration\n options\n (--config config.yaml [--config key=value ...])") 158 f.StringVar(&c.hostedModelName, "d", defaultHostedModelName, "Name of the default hosted model for the controller") 159 f.StringVar(&c.hostedModelName, "default-model", defaultHostedModelName, "Name of the default hosted model for the controller") 160 f.BoolVar(&c.noGUI, "no-gui", false, "Do not install the Juju GUI in the controller when bootstrapping") 161 } 162 163 func (c *bootstrapCommand) Init(args []string) (err error) { 164 if c.AgentVersionParam != "" && c.UploadTools { 165 return fmt.Errorf("--agent-version and --upload-tools can't be used together") 166 } 167 if c.BootstrapSeries != "" && !charm.IsValidSeries(c.BootstrapSeries) { 168 return errors.NotValidf("series %q", c.BootstrapSeries) 169 } 170 if c.BootstrapImage != "" { 171 if c.BootstrapSeries == "" { 172 return errors.Errorf("--bootstrap-image must be used with --bootstrap-series") 173 } 174 cons, err := constraints.Merge(c.Constraints, c.BootstrapConstraints) 175 if err != nil { 176 return errors.Trace(err) 177 } 178 if !cons.HasArch() { 179 return errors.Errorf("--bootstrap-image must be used with --bootstrap-constraints, specifying architecture") 180 } 181 } 182 183 // Parse the placement directive. Bootstrap currently only 184 // supports provider-specific placement directives. 185 if c.Placement != "" { 186 _, err = instance.ParsePlacement(c.Placement) 187 if err != instance.ErrPlacementScopeMissing { 188 // We only support unscoped placement directives for bootstrap. 189 return fmt.Errorf("unsupported bootstrap placement directive %q", c.Placement) 190 } 191 } 192 if !c.AutoUpgrade { 193 // With no auto upgrade chosen, we default to the version matching the bootstrap client. 194 vers := jujuversion.Current 195 c.AgentVersion = &vers 196 } 197 if c.AgentVersionParam != "" { 198 if vers, err := version.ParseBinary(c.AgentVersionParam); err == nil { 199 c.AgentVersion = &vers.Number 200 } else if vers, err := version.Parse(c.AgentVersionParam); err == nil { 201 c.AgentVersion = &vers 202 } else { 203 return err 204 } 205 } 206 if c.AgentVersion != nil && (c.AgentVersion.Major != jujuversion.Current.Major || c.AgentVersion.Minor != jujuversion.Current.Minor) { 207 return fmt.Errorf("requested agent version major.minor mismatch") 208 } 209 210 // The user must specify two positional arguments: the controller name, 211 // and the cloud name (optionally with region specified). 212 if len(args) < 2 { 213 return errors.New("controller name and cloud name are required") 214 } 215 c.controllerName = bootstrappedControllerName(args[0]) 216 c.Cloud = args[1] 217 if i := strings.IndexRune(c.Cloud, '/'); i > 0 { 218 c.Cloud, c.Region = c.Cloud[:i], c.Cloud[i+1:] 219 } 220 return cmd.CheckEmpty(args[2:]) 221 } 222 223 var bootstrappedControllerName = func(controllerName string) string { 224 return fmt.Sprintf("local.%s", controllerName) 225 } 226 227 // BootstrapInterface provides bootstrap functionality that Run calls to support cleaner testing. 228 type BootstrapInterface interface { 229 Bootstrap(ctx environs.BootstrapContext, environ environs.Environ, args bootstrap.BootstrapParams) error 230 } 231 232 type bootstrapFuncs struct{} 233 234 func (b bootstrapFuncs) Bootstrap(ctx environs.BootstrapContext, env environs.Environ, args bootstrap.BootstrapParams) error { 235 return bootstrap.Bootstrap(ctx, env, args) 236 } 237 238 var getBootstrapFuncs = func() BootstrapInterface { 239 return &bootstrapFuncs{} 240 } 241 242 var ( 243 environsPrepare = environs.Prepare 244 environsDestroy = environs.Destroy 245 ) 246 247 var ambiguousCredentialError = errors.New(` 248 more than one credential detected 249 run juju autoload-credentials and specify a credential using the --credential argument`[1:], 250 ) 251 252 // Run connects to the environment specified on the command line and bootstraps 253 // a juju in that environment if none already exists. If there is as yet no environments.yaml file, 254 // the user is informed how to create one. 255 func (c *bootstrapCommand) Run(ctx *cmd.Context) (resultErr error) { 256 bootstrapFuncs := getBootstrapFuncs() 257 258 // Get the cloud definition identified by c.Cloud. If c.Cloud does not 259 // identify a cloud in clouds.yaml, but is the name of a provider, and 260 // that provider implements environs.CloudRegionDetector, we'll 261 // synthesise a Cloud structure with the detected regions and no auth- 262 // types. 263 cloud, err := jujucloud.CloudByName(c.Cloud) 264 if errors.IsNotFound(err) { 265 ctx.Verbosef("cloud %q not found, trying as a provider name", c.Cloud) 266 provider, err := environs.Provider(c.Cloud) 267 if errors.IsNotFound(err) { 268 return errors.NewNotFound(nil, fmt.Sprintf("unknown cloud %q, please try %q", c.Cloud, "juju update-clouds")) 269 } else if err != nil { 270 return errors.Trace(err) 271 } 272 detector, ok := provider.(environs.CloudRegionDetector) 273 if !ok { 274 ctx.Verbosef( 275 "provider %q does not support detecting regions", 276 c.Cloud, 277 ) 278 return errors.NewNotFound(nil, fmt.Sprintf("unknown cloud %q, please try %q", c.Cloud, "juju update-clouds")) 279 } 280 regions, err := detector.DetectRegions() 281 if err != nil && !errors.IsNotFound(err) { 282 // It's not an error to have no regions. 283 return errors.Annotatef(err, 284 "detecting regions for %q cloud provider", 285 c.Cloud, 286 ) 287 } 288 cloud = &jujucloud.Cloud{ 289 Type: c.Cloud, 290 Regions: regions, 291 } 292 } else if err != nil { 293 return errors.Trace(err) 294 } 295 if err := checkProviderType(cloud.Type); errors.IsNotFound(err) { 296 // This error will get handled later. 297 } else if err != nil { 298 return errors.Trace(err) 299 } 300 301 // Get the credentials and region name. 302 store := c.ClientStore() 303 credential, credentialName, regionName, err := modelcmd.GetCredentials( 304 store, c.Region, c.CredentialName, c.Cloud, cloud.Type, 305 ) 306 if errors.IsNotFound(err) && c.CredentialName == "" { 307 // No credential was explicitly specified, and no credential 308 // was found in credentials.yaml; have the provider detect 309 // credentials from the environment. 310 ctx.Verbosef("no credentials found, checking environment") 311 detected, err := modelcmd.DetectCredential(c.Cloud, cloud.Type) 312 if errors.Cause(err) == modelcmd.ErrMultipleCredentials { 313 return ambiguousCredentialError 314 } else if err != nil { 315 return errors.Trace(err) 316 } 317 // We have one credential so extract it from the map. 318 var oneCredential jujucloud.Credential 319 for _, oneCredential = range detected.AuthCredentials { 320 } 321 credential = &oneCredential 322 regionName = c.Region 323 if regionName == "" { 324 regionName = detected.DefaultRegion 325 } 326 logger.Tracef("authenticating with region %q and %v", regionName, credential) 327 } else if err != nil { 328 return errors.Trace(err) 329 } 330 331 region, err := getRegion(cloud, c.Cloud, regionName) 332 if err != nil { 333 return errors.Trace(err) 334 } 335 336 hostedModelUUID, err := utils.NewUUID() 337 if err != nil { 338 return errors.Trace(err) 339 } 340 controllerUUID, err := utils.NewUUID() 341 if err != nil { 342 return errors.Trace(err) 343 } 344 345 // Create an environment config from the cloud and credentials. 346 configAttrs := map[string]interface{}{ 347 "type": cloud.Type, 348 "name": environs.ControllerModelName, 349 config.UUIDKey: controllerUUID.String(), 350 config.ControllerUUIDKey: controllerUUID.String(), 351 } 352 userConfigAttrs, err := c.config.ReadAttrs(ctx) 353 if err != nil { 354 return errors.Trace(err) 355 } 356 for k, v := range userConfigAttrs { 357 configAttrs[k] = v 358 } 359 logger.Debugf("preparing controller with config: %v", configAttrs) 360 361 // Read existing current controller, account, model so we can clean up on error. 362 var oldCurrentController string 363 oldCurrentController, err = modelcmd.ReadCurrentController() 364 if err != nil { 365 return errors.Annotate(err, "error reading current controller") 366 } 367 368 defer func() { 369 if resultErr == nil || errors.IsAlreadyExists(resultErr) { 370 return 371 } 372 if oldCurrentController != "" { 373 if err := modelcmd.WriteCurrentController(oldCurrentController); err != nil { 374 logger.Warningf( 375 "cannot reset current controller to %q: %v", 376 oldCurrentController, err, 377 ) 378 } 379 } 380 if err := store.RemoveController(c.controllerName); err != nil { 381 logger.Warningf( 382 "cannot destroy newly created controller %q details: %v", 383 c.controllerName, err, 384 ) 385 } 386 }() 387 388 environ, err := environsPrepare( 389 modelcmd.BootstrapContext(ctx), store, 390 environs.PrepareParams{ 391 BaseConfig: configAttrs, 392 ControllerName: c.controllerName, 393 CloudName: c.Cloud, 394 CloudRegion: region.Name, 395 CloudEndpoint: region.Endpoint, 396 CloudStorageEndpoint: region.StorageEndpoint, 397 Credential: *credential, 398 CredentialName: credentialName, 399 }, 400 ) 401 if err != nil { 402 return errors.Trace(err) 403 } 404 405 // Set the current model to the initial hosted model. 406 accountName, err := store.CurrentAccount(c.controllerName) 407 if err != nil { 408 return errors.Trace(err) 409 } 410 if err := store.UpdateModel(c.controllerName, accountName, c.hostedModelName, jujuclient.ModelDetails{ 411 hostedModelUUID.String(), 412 }); err != nil { 413 return errors.Trace(err) 414 } 415 if err := store.SetCurrentModel(c.controllerName, accountName, c.hostedModelName); err != nil { 416 return errors.Trace(err) 417 } 418 419 // Set the current controller so "juju status" can be run while 420 // bootstrapping is underway. 421 if err := modelcmd.WriteCurrentController(c.controllerName); err != nil { 422 return errors.Trace(err) 423 } 424 425 cloudRegion := c.Cloud 426 if region.Name != "" { 427 cloudRegion = fmt.Sprintf("%s/%s", cloudRegion, region.Name) 428 } 429 ctx.Infof( 430 "Creating Juju controller %q on %s", 431 c.controllerName, cloudRegion, 432 ) 433 434 // If we error out for any reason, clean up the environment. 435 defer func() { 436 if resultErr != nil { 437 if c.KeepBrokenEnvironment { 438 logger.Warningf(` 439 bootstrap failed but --keep-broken was specified so model is not being destroyed. 440 When you are finished diagnosing the problem, remember to run juju destroy-model --force 441 to clean up the model.`[1:]) 442 } else { 443 handleBootstrapError(ctx, resultErr, func() error { 444 return environsDestroy( 445 c.controllerName, environ, store, 446 ) 447 }) 448 } 449 } 450 }() 451 452 // Block interruption during bootstrap. Providers may also 453 // register for interrupt notification so they can exit early. 454 interrupted := make(chan os.Signal, 1) 455 defer close(interrupted) 456 ctx.InterruptNotify(interrupted) 457 defer ctx.StopInterruptNotify(interrupted) 458 go func() { 459 for _ = range interrupted { 460 ctx.Infof("Interrupt signalled: waiting for bootstrap to exit") 461 } 462 }() 463 464 // If --metadata-source is specified, override the default tools metadata source so 465 // SyncTools can use it, and also upload any image metadata. 466 var metadataDir string 467 if c.MetadataSource != "" { 468 metadataDir = ctx.AbsPath(c.MetadataSource) 469 } 470 471 // Merge environ and bootstrap-specific constraints. 472 constraintsValidator, err := environ.ConstraintsValidator() 473 if err != nil { 474 return errors.Trace(err) 475 } 476 bootstrapConstraints, err := constraintsValidator.Merge( 477 c.Constraints, c.BootstrapConstraints, 478 ) 479 if err != nil { 480 return errors.Trace(err) 481 } 482 logger.Infof("combined bootstrap constraints: %v", bootstrapConstraints) 483 484 hostedModelConfig := map[string]interface{}{ 485 "name": c.hostedModelName, 486 config.UUIDKey: hostedModelUUID.String(), 487 } 488 489 // We copy across any user supplied attributes to the hosted model config. 490 // But only if the attributes have not been removed from the controller 491 // model config as part of preparing the controller model. 492 controllerConfigAttrs := environ.Config().AllAttrs() 493 for k, v := range userConfigAttrs { 494 if _, ok := controllerConfigAttrs[k]; ok { 495 hostedModelConfig[k] = v 496 } 497 } 498 // Ensure that certain config attributes are not included in the hosted 499 // model config. These attributes may be modified during bootstrap; by 500 // removing them from this map, we ensure the modified values are 501 // inherited. 502 delete(hostedModelConfig, config.AuthKeysConfig) 503 delete(hostedModelConfig, config.AgentVersionKey) 504 505 // Check whether the Juju GUI must be installed in the controller. 506 // Leaving this value empty means no GUI will be installed. 507 var guiDataSourceBaseURL string 508 if !c.noGUI { 509 guiDataSourceBaseURL = common.GUIDataSourceBaseURL() 510 } 511 512 err = bootstrapFuncs.Bootstrap(modelcmd.BootstrapContext(ctx), environ, bootstrap.BootstrapParams{ 513 ModelConstraints: c.Constraints, 514 BootstrapConstraints: bootstrapConstraints, 515 BootstrapSeries: c.BootstrapSeries, 516 BootstrapImage: c.BootstrapImage, 517 Placement: c.Placement, 518 UploadTools: c.UploadTools, 519 AgentVersion: c.AgentVersion, 520 MetadataDir: metadataDir, 521 HostedModelConfig: hostedModelConfig, 522 GUIDataSourceBaseURL: guiDataSourceBaseURL, 523 }) 524 if err != nil { 525 return errors.Annotate(err, "failed to bootstrap model") 526 } 527 528 if err := c.SetModelName(c.hostedModelName); err != nil { 529 return errors.Trace(err) 530 } 531 532 err = c.setBootstrapEndpointAddress(environ) 533 if err != nil { 534 return errors.Annotate(err, "saving bootstrap endpoint address") 535 } 536 537 // To avoid race conditions when running scripted bootstraps, wait 538 // for the controller's machine agent to be ready to accept commands 539 // before exiting this bootstrap command. 540 return c.waitForAgentInitialisation(ctx) 541 } 542 543 // getRegion returns the cloud.Region to use, based on the specified 544 // region name, and the region name selected if none was specified. 545 // 546 // If no region name is specified, and there is at least one region, 547 // we use the first region in the list. 548 // 549 // If no region name is specified, and there are no regions at all, 550 // then we synthesise a region from the cloud's endpoint information 551 // and just pass this on to the provider. 552 func getRegion(cloud *jujucloud.Cloud, cloudName, regionName string) (jujucloud.Region, error) { 553 if len(cloud.Regions) == 0 { 554 // The cloud does not specify regions, so assume 555 // that the cloud provider does not have a concept 556 // of regions, or has no pre-defined regions, and 557 // defer validation to the provider. 558 region := jujucloud.Region{ 559 regionName, 560 cloud.Endpoint, 561 cloud.StorageEndpoint, 562 } 563 return region, nil 564 } 565 if regionName == "" { 566 // No region was specified, use the first region in the list. 567 return cloud.Regions[0], nil 568 } 569 for _, region := range cloud.Regions { 570 // Do a case-insensitive comparison 571 if strings.EqualFold(region.Name, regionName) { 572 return region, nil 573 } 574 } 575 return jujucloud.Region{}, errors.NewNotFound(nil, fmt.Sprintf( 576 "region %q in cloud %q not found (expected one of %q)\nalternatively, try %q", 577 regionName, cloudName, cloudRegionNames(cloud), "juju update-clouds", 578 )) 579 } 580 581 func cloudRegionNames(cloud *jujucloud.Cloud) []string { 582 var regionNames []string 583 for _, region := range cloud.Regions { 584 regionNames = append(regionNames, region.Name) 585 } 586 return regionNames 587 } 588 589 var ( 590 bootstrapReadyPollDelay = 1 * time.Second 591 bootstrapReadyPollCount = 60 592 blockAPI = getBlockAPI 593 ) 594 595 // getBlockAPI returns a block api for listing blocks. 596 func getBlockAPI(c *modelcmd.ModelCommandBase) (block.BlockListAPI, error) { 597 root, err := c.NewAPIRoot() 598 if err != nil { 599 return nil, err 600 } 601 return apiblock.NewClient(root), nil 602 } 603 604 // tryAPI attempts to open the API and makes a trivial call 605 // to check if the API is available yet. 606 func (c *bootstrapCommand) tryAPI() error { 607 client, err := blockAPI(&c.ModelCommandBase) 608 if err == nil { 609 _, err = client.List() 610 closeErr := client.Close() 611 if closeErr != nil { 612 logger.Debugf("Error closing client: %v", closeErr) 613 } 614 } 615 return err 616 } 617 618 // waitForAgentInitialisation polls the bootstrapped controller with a read-only 619 // command which will fail until the controller is fully initialised. 620 // TODO(wallyworld) - add a bespoke command to maybe the admin facade for this purpose. 621 func (c *bootstrapCommand) waitForAgentInitialisation(ctx *cmd.Context) error { 622 attempts := utils.AttemptStrategy{ 623 Min: bootstrapReadyPollCount, 624 Delay: bootstrapReadyPollDelay, 625 } 626 var ( 627 apiAttempts int 628 err error 629 ) 630 631 apiAttempts = 1 632 for attempt := attempts.Start(); attempt.Next(); apiAttempts++ { 633 err = c.tryAPI() 634 if err == nil { 635 ctx.Infof("Bootstrap complete, %s now available.", c.controllerName) 636 break 637 } 638 // As the API server is coming up, it goes through a number of steps. 639 // Initially the upgrade steps run, but the api server allows some 640 // calls to be processed during the upgrade, but not the list blocks. 641 // Logins are also blocked during space discovery. 642 // It is also possible that the underlying database causes connections 643 // to be dropped as it is initialising, or reconfiguring. These can 644 // lead to EOF or "connection is shut down" error messages. We skip 645 // these too, hoping that things come back up before the end of the 646 // retry poll count. 647 errorMessage := errors.Cause(err).Error() 648 switch { 649 case errors.Cause(err) == io.EOF, 650 strings.HasSuffix(errorMessage, "connection is shut down"), 651 strings.Contains(errorMessage, "spaces are still being discovered"): 652 ctx.Infof("Waiting for API to become available") 653 continue 654 case params.ErrCode(err) == params.CodeUpgradeInProgress: 655 ctx.Infof("Waiting for API to become available: %v", err) 656 continue 657 } 658 break 659 } 660 return errors.Annotatef(err, "unable to contact api server after %d attempts", apiAttempts) 661 } 662 663 // checkProviderType ensures the provider type is okay. 664 func checkProviderType(envType string) error { 665 featureflag.SetFlagsFromEnvironment(osenv.JujuFeatureFlagEnvKey) 666 flag, ok := provisionalProviders[envType] 667 if ok && !featureflag.Enabled(flag) { 668 msg := `the %q provider is provisional in this version of Juju. To use it anyway, set JUJU_DEV_FEATURE_FLAGS="%s" in your shell model` 669 return errors.Errorf(msg, envType, flag) 670 } 671 return nil 672 } 673 674 // handleBootstrapError is called to clean up if bootstrap fails. 675 func handleBootstrapError(ctx *cmd.Context, err error, cleanup func() error) { 676 ch := make(chan os.Signal, 1) 677 ctx.InterruptNotify(ch) 678 defer ctx.StopInterruptNotify(ch) 679 defer close(ch) 680 go func() { 681 for _ = range ch { 682 fmt.Fprintln(ctx.GetStderr(), "Cleaning up failed bootstrap") 683 } 684 }() 685 if err := cleanup(); err != nil { 686 logger.Errorf("error cleaning up: %v", err) 687 } 688 } 689 690 var allInstances = func(environ environs.Environ) ([]instance.Instance, error) { 691 return environ.AllInstances() 692 } 693 694 // setBootstrapEndpointAddress writes the API endpoint address of the 695 // bootstrap server into the connection information. This should only be run 696 // once directly after Bootstrap. It assumes that there is just one instance 697 // in the environment - the bootstrap instance. 698 func (c *bootstrapCommand) setBootstrapEndpointAddress(environ environs.Environ) error { 699 instances, err := allInstances(environ) 700 if err != nil { 701 return errors.Trace(err) 702 } 703 length := len(instances) 704 if length == 0 { 705 return errors.Errorf("found no instances, expected at least one") 706 } 707 if length > 1 { 708 logger.Warningf("expected one instance, got %d", length) 709 } 710 bootstrapInstance := instances[0] 711 712 // Don't use c.ConnectionEndpoint as it attempts to contact the state 713 // server if no addresses are found in connection info. 714 netAddrs, err := bootstrapInstance.Addresses() 715 if err != nil { 716 return errors.Annotate(err, "failed to get bootstrap instance addresses") 717 } 718 cfg := environ.Config() 719 apiPort := cfg.APIPort() 720 apiHostPorts := network.AddressesWithPort(netAddrs, apiPort) 721 return juju.UpdateControllerAddresses(c.ClientStore(), c.controllerName, nil, apiHostPorts...) 722 }