github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/common/controller.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package common 5 6 import ( 7 "fmt" 8 "io" 9 "strings" 10 "time" 11 12 "github.com/juju/cmd" 13 "github.com/juju/errors" 14 "github.com/juju/utils" 15 16 "github.com/juju/juju/api/block" 17 "github.com/juju/juju/apiserver/params" 18 "github.com/juju/juju/cmd/modelcmd" 19 "github.com/juju/juju/environs" 20 "github.com/juju/juju/environs/bootstrap" 21 "github.com/juju/juju/environs/context" 22 "github.com/juju/juju/network" 23 ) 24 25 var ( 26 bootstrapReadyPollDelay = 1 * time.Second 27 bootstrapReadyPollCount = 60 28 blockAPI = getBlockAPI 29 ) 30 31 type listBlocksAPI interface { 32 List() ([]params.Block, error) 33 Close() error 34 } 35 36 // getBlockAPI returns a block api for listing blocks. 37 func getBlockAPI(c *modelcmd.ModelCommandBase) (listBlocksAPI, error) { 38 root, err := c.NewAPIRoot() 39 if err != nil { 40 return nil, errors.Trace(err) 41 } 42 return block.NewClient(root), nil 43 } 44 45 // tryAPI attempts to open the API and makes a trivial call 46 // to check if the API is available yet. 47 func tryAPI(c *modelcmd.ModelCommandBase) error { 48 client, err := blockAPI(c) 49 if err == nil { 50 _, err = client.List() 51 closeErr := client.Close() 52 if closeErr != nil { 53 logger.Debugf("Error closing client: %v", closeErr) 54 } 55 } 56 return err 57 } 58 59 // WaitForAgentInitialisation polls the bootstrapped controller with a read-only 60 // command which will fail until the controller is fully initialised. 61 // TODO(wallyworld) - add a bespoke command to maybe the admin facade for this purpose. 62 func WaitForAgentInitialisation(ctx *cmd.Context, c *modelcmd.ModelCommandBase, controllerName, hostedModelName string) error { 63 // TODO(katco): 2016-08-09: lp:1611427 64 attempts := utils.AttemptStrategy{ 65 Min: bootstrapReadyPollCount, 66 Delay: bootstrapReadyPollDelay, 67 } 68 var ( 69 apiAttempts int 70 err error 71 ) 72 73 // Make a best effort to find the new controller address so we can print it. 74 addressInfo := "" 75 controller, err := c.ClientStore().ControllerByName(controllerName) 76 if err == nil && len(controller.APIEndpoints) > 0 { 77 addr, err := network.ParseHostPort(controller.APIEndpoints[0]) 78 if err == nil { 79 addressInfo = fmt.Sprintf(" at %s", addr.Address.Value) 80 } 81 } 82 83 ctx.Infof("Contacting Juju controller%s to verify accessibility...", addressInfo) 84 apiAttempts = 1 85 for attempt := attempts.Start(); attempt.Next(); apiAttempts++ { 86 err = tryAPI(c) 87 if err == nil { 88 ctx.Infof("Bootstrap complete, %q controller now available", controllerName) 89 ctx.Infof("Controller machines are in the %q model", bootstrap.ControllerModelName) 90 ctx.Infof("Initial model %q added", hostedModelName) 91 break 92 } 93 // As the API server is coming up, it goes through a number of steps. 94 // Initially the upgrade steps run, but the api server allows some 95 // calls to be processed during the upgrade, but not the list blocks. 96 // Logins are also blocked during space discovery. 97 // It is also possible that the underlying database causes connections 98 // to be dropped as it is initialising, or reconfiguring. These can 99 // lead to EOF or "connection is shut down" error messages. We skip 100 // these too, hoping that things come back up before the end of the 101 // retry poll count. 102 errorMessage := errors.Cause(err).Error() 103 switch { 104 case errors.Cause(err) == io.EOF, 105 strings.HasSuffix(errorMessage, "connection is shut down"), 106 strings.HasSuffix(errorMessage, "no api connection available"), 107 strings.Contains(errorMessage, "spaces are still being discovered"): 108 ctx.Verbosef("Still waiting for API to become available") 109 continue 110 case params.ErrCode(err) == params.CodeUpgradeInProgress: 111 ctx.Verbosef("Still waiting for API to become available: %v", err) 112 continue 113 } 114 break 115 } 116 return errors.Annotatef(err, "unable to contact api server after %d attempts", apiAttempts) 117 } 118 119 // BootstrapEndpointAddresses returns the addresses of the bootstrapped instance. 120 func BootstrapEndpointAddresses(environ environs.InstanceBroker, callContext context.ProviderCallContext) ([]network.Address, error) { 121 instances, err := environ.AllInstances(callContext) 122 if err != nil { 123 return nil, errors.Trace(err) 124 } 125 if n := len(instances); n != 1 { 126 return nil, errors.Errorf("expected one instance, got %d", n) 127 } 128 netAddrs, err := instances[0].Addresses(callContext) 129 if err != nil { 130 return nil, errors.Annotate(err, "failed to get bootstrap instance addresses") 131 } 132 return netAddrs, nil 133 }