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  }