github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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/instance" 22 "github.com/juju/juju/juju" 23 "github.com/juju/juju/jujuclient" 24 "github.com/juju/juju/network" 25 "github.com/juju/version" 26 ) 27 28 var allInstances = func(environ environs.Environ) ([]instance.Instance, error) { 29 return environ.AllInstances() 30 } 31 32 // SetBootstrapEndpointAddress writes the API endpoint address of the 33 // bootstrap server, plus the agent version, into the connection information. 34 // This should only be run once directly after Bootstrap. It assumes that 35 // there is just one instance in the environment - the bootstrap instance. 36 func SetBootstrapEndpointAddress( 37 store jujuclient.ControllerStore, 38 controllerName string, agentVersion version.Number, 39 apiPort int, environ environs.Environ, 40 ) error { 41 instances, err := allInstances(environ) 42 if err != nil { 43 return errors.Trace(err) 44 } 45 length := len(instances) 46 if length == 0 { 47 return errors.Errorf("found no instances, expected at least one") 48 } 49 if length > 1 { 50 return errors.Errorf("expected one instance, got %d", length) 51 } 52 bootstrapInstance := instances[0] 53 54 // Don't use c.ConnectionEndpoint as it attempts to contact the state 55 // server if no addresses are found in connection info. 56 netAddrs, err := bootstrapInstance.Addresses() 57 if err != nil { 58 return errors.Annotate(err, "failed to get bootstrap instance addresses") 59 } 60 apiHostPorts := network.AddressesWithPort(netAddrs, apiPort) 61 // At bootstrap we have 2 models, the controller model and the default. 62 two := 2 63 params := juju.UpdateControllerParams{ 64 AgentVersion: agentVersion.String(), 65 AddrConnectedTo: apiHostPorts, 66 MachineCount: &length, 67 ControllerMachineCount: &length, 68 ModelCount: &two, 69 } 70 return juju.UpdateControllerDetailsFromLogin(store, controllerName, params) 71 } 72 73 var ( 74 bootstrapReadyPollDelay = 1 * time.Second 75 bootstrapReadyPollCount = 60 76 blockAPI = getBlockAPI 77 ) 78 79 type listBlocksAPI interface { 80 List() ([]params.Block, error) 81 Close() error 82 } 83 84 // getBlockAPI returns a block api for listing blocks. 85 func getBlockAPI(c *modelcmd.ModelCommandBase) (listBlocksAPI, error) { 86 root, err := c.NewAPIRoot() 87 if err != nil { 88 return nil, errors.Trace(err) 89 } 90 return block.NewClient(root), nil 91 } 92 93 // tryAPI attempts to open the API and makes a trivial call 94 // to check if the API is available yet. 95 func tryAPI(c *modelcmd.ModelCommandBase) error { 96 client, err := blockAPI(c) 97 if err == nil { 98 _, err = client.List() 99 closeErr := client.Close() 100 if closeErr != nil { 101 logger.Debugf("Error closing client: %v", closeErr) 102 } 103 } 104 return err 105 } 106 107 // WaitForAgentInitialisation polls the bootstrapped controller with a read-only 108 // command which will fail until the controller is fully initialised. 109 // TODO(wallyworld) - add a bespoke command to maybe the admin facade for this purpose. 110 func WaitForAgentInitialisation(ctx *cmd.Context, c *modelcmd.ModelCommandBase, controllerName, hostedModelName string) error { 111 // TODO(katco): 2016-08-09: lp:1611427 112 attempts := utils.AttemptStrategy{ 113 Min: bootstrapReadyPollCount, 114 Delay: bootstrapReadyPollDelay, 115 } 116 var ( 117 apiAttempts int 118 err error 119 ) 120 121 // Make a best effort to find the new controller address so we can print it. 122 addressInfo := "" 123 controller, err := c.ClientStore().ControllerByName(controllerName) 124 if err == nil && len(controller.APIEndpoints) > 0 { 125 addr, err := network.ParseHostPort(controller.APIEndpoints[0]) 126 if err == nil { 127 addressInfo = fmt.Sprintf(" at %s", addr.Address.Value) 128 } 129 } 130 131 ctx.Infof("Contacting Juju controller%s to verify accessibility...", addressInfo) 132 apiAttempts = 1 133 for attempt := attempts.Start(); attempt.Next(); apiAttempts++ { 134 err = tryAPI(c) 135 if err == nil { 136 ctx.Infof("Bootstrap complete, %q controller now available.", controllerName) 137 ctx.Infof("Controller machines are in the %q model.", bootstrap.ControllerModelName) 138 ctx.Infof("Initial model %q added.", hostedModelName) 139 break 140 } 141 // As the API server is coming up, it goes through a number of steps. 142 // Initially the upgrade steps run, but the api server allows some 143 // calls to be processed during the upgrade, but not the list blocks. 144 // Logins are also blocked during space discovery. 145 // It is also possible that the underlying database causes connections 146 // to be dropped as it is initialising, or reconfiguring. These can 147 // lead to EOF or "connection is shut down" error messages. We skip 148 // these too, hoping that things come back up before the end of the 149 // retry poll count. 150 errorMessage := errors.Cause(err).Error() 151 switch { 152 case errors.Cause(err) == io.EOF, 153 strings.HasSuffix(errorMessage, "connection is shut down"), 154 strings.HasSuffix(errorMessage, "no api connection available"), 155 strings.Contains(errorMessage, "spaces are still being discovered"): 156 ctx.Verbosef("Still waiting for API to become available") 157 continue 158 case params.ErrCode(err) == params.CodeUpgradeInProgress: 159 ctx.Verbosef("Still waiting for API to become available: %v", err) 160 continue 161 } 162 break 163 } 164 return errors.Annotatef(err, "unable to contact api server after %d attempts", apiAttempts) 165 }