github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/controller/listcontrollers.go (about)

     1  // Copyright 2015,2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package controller
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  	"sync"
    10  
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/gnuflag"
    14  	"gopkg.in/juju/names.v2"
    15  
    16  	"github.com/juju/juju/api/base"
    17  	"github.com/juju/juju/api/controller"
    18  	jujucmd "github.com/juju/juju/cmd"
    19  	"github.com/juju/juju/cmd/modelcmd"
    20  	"github.com/juju/juju/core/status"
    21  	"github.com/juju/juju/environs/bootstrap"
    22  	"github.com/juju/juju/jujuclient"
    23  )
    24  
    25  var helpControllersSummary = `
    26  Lists all controllers.`[1:]
    27  
    28  var helpControllersDetails = `
    29  The output format may be selected with the '--format' option. In the
    30  default tabular output, the current controller is marked with an asterisk.
    31  
    32  Examples:
    33      juju controllers
    34      juju controllers --format json --output ~/tmp/controllers.json
    35  
    36  See also:
    37      models
    38      show-controller`[1:]
    39  
    40  // NewListControllersCommand returns a command to list registered controllers.
    41  func NewListControllersCommand() cmd.Command {
    42  	cmd := &listControllersCommand{
    43  		store: jujuclient.NewFileClientStore(),
    44  	}
    45  	return modelcmd.WrapBase(cmd)
    46  }
    47  
    48  // Info implements Command.Info
    49  func (c *listControllersCommand) Info() *cmd.Info {
    50  	return jujucmd.Info(&cmd.Info{
    51  		Name:    "controllers",
    52  		Purpose: helpControllersSummary,
    53  		Doc:     helpControllersDetails,
    54  		Aliases: []string{"list-controllers"},
    55  	})
    56  }
    57  
    58  // SetFlags implements Command.SetFlags.
    59  func (c *listControllersCommand) SetFlags(f *gnuflag.FlagSet) {
    60  	c.CommandBase.SetFlags(f)
    61  	f.BoolVar(&c.refresh, "refresh", false, "Connect to each controller to download the latest details")
    62  	c.out.AddFlags(f, "tabular", map[string]cmd.Formatter{
    63  		"yaml":    cmd.FormatYaml,
    64  		"json":    cmd.FormatJson,
    65  		"tabular": c.formatControllersListTabular,
    66  	})
    67  }
    68  
    69  func (c *listControllersCommand) getAPI(controllerName string) (ControllerAccessAPI, error) {
    70  	if c.api != nil {
    71  		return c.api(controllerName), nil
    72  	}
    73  	api, err := c.NewAPIRoot(c.store, controllerName, "")
    74  	if err != nil {
    75  		return nil, errors.Annotate(err, "opening API connection")
    76  	}
    77  	return controller.NewClient(api), nil
    78  }
    79  
    80  // Run implements Command.Run
    81  func (c *listControllersCommand) Run(ctx *cmd.Context) error {
    82  	controllers, err := c.store.AllControllers()
    83  	if err != nil {
    84  		return errors.Annotate(err, "failed to list controllers")
    85  	}
    86  	if len(controllers) == 0 && c.out.Name() == "tabular" {
    87  		return errors.Trace(modelcmd.ErrNoControllersDefined)
    88  	}
    89  	if c.refresh && len(controllers) > 0 {
    90  		var wg sync.WaitGroup
    91  		wg.Add(len(controllers))
    92  		for controllerName := range controllers {
    93  			name := controllerName
    94  			go func() {
    95  				defer wg.Done()
    96  				client, err := c.getAPI(name)
    97  				if err != nil {
    98  					fmt.Fprintf(ctx.GetStderr(), "error connecting to api for %q: %v\n", name, err)
    99  					return
   100  				}
   101  				defer client.Close()
   102  				if err := c.refreshControllerDetails(client, name); err != nil {
   103  					fmt.Fprintf(ctx.GetStderr(), "error updating cached details for %q: %v\n", name, err)
   104  				}
   105  			}()
   106  		}
   107  		wg.Wait()
   108  		// Reload controller details
   109  		controllers, err = c.store.AllControllers()
   110  		if err != nil {
   111  			return errors.Annotate(err, "failed to list controllers")
   112  		}
   113  	}
   114  	details, errs := c.convertControllerDetails(controllers)
   115  	if len(errs) > 0 {
   116  		fmt.Fprintln(ctx.Stderr, strings.Join(errs, "\n"))
   117  	}
   118  	currentController, err := c.store.CurrentController()
   119  	if errors.IsNotFound(err) {
   120  		currentController = ""
   121  	} else if err != nil {
   122  		return errors.Annotate(err, "getting current controller")
   123  	}
   124  	controllerSet := ControllerSet{
   125  		Controllers:       details,
   126  		CurrentController: currentController,
   127  	}
   128  	return c.out.Write(ctx, controllerSet)
   129  }
   130  
   131  func (c *listControllersCommand) refreshControllerDetails(client ControllerAccessAPI, controllerName string) error {
   132  	// First, get all the models the user can see, and their details.
   133  	allModels, err := client.AllModels()
   134  	if err != nil {
   135  		return err
   136  	}
   137  	// Update client store.
   138  	if err := c.SetControllerModels(c.store, controllerName, allModels); err != nil {
   139  		return errors.Trace(err)
   140  	}
   141  
   142  	var controllerModelUUID string
   143  	modelTags := make([]names.ModelTag, len(allModels))
   144  	for i, m := range allModels {
   145  		modelTags[i] = names.NewModelTag(m.UUID)
   146  		if m.Name == bootstrap.ControllerModelName {
   147  			controllerModelUUID = m.UUID
   148  		}
   149  	}
   150  	modelStatus, err := client.ModelStatus(modelTags...)
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	c.mu.Lock()
   156  	defer c.mu.Unlock()
   157  	// Use the model information to update the cached controller details.
   158  	details, err := c.store.ControllerByName(controllerName)
   159  	if err != nil {
   160  		return err
   161  	}
   162  
   163  	machineCount := 0
   164  	for _, s := range modelStatus {
   165  		if s.Error != nil {
   166  			if errors.IsNotFound(s.Error) {
   167  				// This most likely occurred because a model was
   168  				// destroyed half-way through the call.
   169  				continue
   170  			}
   171  			return errors.Trace(s.Error)
   172  		}
   173  		machineCount += s.TotalMachineCount
   174  	}
   175  	details.MachineCount = &machineCount
   176  	details.ActiveControllerMachineCount, details.ControllerMachineCount = ControllerMachineCounts(controllerModelUUID, modelStatus)
   177  	return c.store.UpdateController(controllerName, *details)
   178  }
   179  
   180  func ControllerMachineCounts(controllerModelUUID string, modelStatusResults []base.ModelStatus) (activeCount, totalCount int) {
   181  	for _, s := range modelStatusResults {
   182  		if s.Error != nil {
   183  			// This most likely occurred because a model was
   184  			// destroyed half-way through the call.
   185  			continue
   186  		}
   187  		if s.UUID != controllerModelUUID {
   188  			continue
   189  		}
   190  		for _, m := range s.Machines {
   191  			if !m.WantsVote {
   192  				continue
   193  			}
   194  			totalCount++
   195  			if m.Status != string(status.Down) && m.HasVote {
   196  				activeCount++
   197  			}
   198  		}
   199  	}
   200  	return activeCount, totalCount
   201  }
   202  
   203  type listControllersCommand struct {
   204  	modelcmd.CommandBase
   205  
   206  	out     cmd.Output
   207  	store   jujuclient.ClientStore
   208  	api     func(controllerName string) ControllerAccessAPI
   209  	refresh bool
   210  	mu      sync.Mutex
   211  }