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  }