github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/cmd/juju/commands/enableha.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package commands
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"strings"
    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/highavailability"
    17  	"github.com/juju/juju/apiserver/params"
    18  	"github.com/juju/juju/cmd/juju/block"
    19  	"github.com/juju/juju/cmd/juju/common"
    20  	"github.com/juju/juju/cmd/modelcmd"
    21  	"github.com/juju/juju/constraints"
    22  	"github.com/juju/juju/instance"
    23  )
    24  
    25  func newEnableHACommand() cmd.Command {
    26  	haCommand := &enableHACommand{}
    27  	haCommand.newHAClientFunc = func() (MakeHAClient, error) {
    28  		root, err := haCommand.NewAPIRoot()
    29  		if err != nil {
    30  			return nil, errors.Annotate(err, "cannot get API connection")
    31  		}
    32  
    33  		// NewClient does not return an error, so we'll return nil
    34  		return highavailability.NewClient(root), nil
    35  	}
    36  	return modelcmd.WrapController(haCommand)
    37  }
    38  
    39  // enableHACommand makes the controller highly available.
    40  type enableHACommand struct {
    41  	modelcmd.ControllerCommandBase
    42  	out cmd.Output
    43  
    44  	// newHAClientFunc returns HA Client to be used by the command.
    45  	newHAClientFunc func() (MakeHAClient, error)
    46  
    47  	// NumControllers specifies the number of controllers to make available.
    48  	NumControllers int
    49  
    50  	// Constraints, if specified, will be merged with those already
    51  	// in the environment when creating new machines.
    52  	Constraints constraints.Value
    53  
    54  	// ConstraintsStr contains the stringified version of the constraints.
    55  	ConstraintsStr string
    56  
    57  	// Placement specifies specific machine(s) which will be used to host
    58  	// new controllers. If there are more controllers required than
    59  	// machines specified, new machines will be created.
    60  	// Placement is passed verbatim to the API, to be evaluated and used server-side.
    61  	Placement []string
    62  
    63  	// PlacementSpec holds the unparsed placement directives argument (--to).
    64  	PlacementSpec string
    65  }
    66  
    67  const enableHADoc = `
    68  To ensure availability of deployed applications, the Juju infrastructure
    69  must itself be highly available. The enable-ha command will ensure
    70  that the specified number of controller machines are used to make up the
    71  controller.
    72  
    73  An odd number of controllers is required.
    74  
    75  Examples:
    76      # Ensure that the controller is still in highly available mode. If
    77      # there is only 1 controller running, this will ensure there
    78      # are 3 running. If you have previously requested more than 3,
    79      # then that number will be ensured.
    80      juju enable-ha
    81  
    82      # Ensure that 5 controllers are available.
    83      juju enable-ha -n 5 
    84  
    85      # Ensure that 7 controllers are available, with newly created
    86      # controller machines having at least 8GB RAM.
    87      juju enable-ha -n 7 --constraints mem=8G
    88  
    89      # Ensure that 7 controllers are available, with machines server1 and
    90      # server2 used first, and if necessary, newly created controller
    91      # machines having at least 8GB RAM.
    92      juju enable-ha -n 7 --to server1,server2 --constraints mem=8G
    93  `
    94  
    95  // formatSimple marshals value to a yaml-formatted []byte, unless value is nil.
    96  func formatSimple(writer io.Writer, value interface{}) error {
    97  	enableHAResult, ok := value.(availabilityInfo)
    98  	if !ok {
    99  		return errors.Errorf("unexpected result type for enable-ha call: %T", value)
   100  	}
   101  
   102  	for _, machineList := range []struct {
   103  		message string
   104  		list    []string
   105  	}{
   106  		{
   107  			"maintaining machines: %s\n",
   108  			enableHAResult.Maintained,
   109  		},
   110  		{
   111  			"adding machines: %s\n",
   112  			enableHAResult.Added,
   113  		},
   114  		{
   115  			"removing machines: %s\n",
   116  			enableHAResult.Removed,
   117  		},
   118  		{
   119  			"promoting machines: %s\n",
   120  			enableHAResult.Promoted,
   121  		},
   122  		{
   123  			"demoting machines: %s\n",
   124  			enableHAResult.Demoted,
   125  		},
   126  		{
   127  			"converting machines: %s\n",
   128  			enableHAResult.Converted,
   129  		},
   130  	} {
   131  		if len(machineList.list) == 0 {
   132  			continue
   133  		}
   134  		_, err := fmt.Fprintf(writer, machineList.message, strings.Join(machineList.list, ", "))
   135  		if err != nil {
   136  			return err
   137  		}
   138  	}
   139  
   140  	return nil
   141  }
   142  
   143  func (c *enableHACommand) Info() *cmd.Info {
   144  	return &cmd.Info{
   145  		Name:    "enable-ha",
   146  		Purpose: "Ensure that sufficient controllers exist to provide redundancy.",
   147  		Doc:     enableHADoc,
   148  	}
   149  }
   150  
   151  func (c *enableHACommand) SetFlags(f *gnuflag.FlagSet) {
   152  	c.ControllerCommandBase.SetFlags(f)
   153  	f.IntVar(&c.NumControllers, "n", 0, "Number of controllers to make available")
   154  	f.StringVar(&c.PlacementSpec, "to", "", "The machine(s) to become controllers, bypasses constraints")
   155  	f.StringVar(&c.ConstraintsStr, "constraints", "", "Additional machine constraints")
   156  	c.out.AddFlags(f, "simple", map[string]cmd.Formatter{
   157  		"yaml":   cmd.FormatYaml,
   158  		"json":   cmd.FormatJson,
   159  		"simple": formatSimple,
   160  	})
   161  
   162  }
   163  
   164  func (c *enableHACommand) Init(args []string) error {
   165  	if c.NumControllers < 0 || (c.NumControllers%2 != 1 && c.NumControllers != 0) {
   166  		return errors.Errorf("must specify a number of controllers odd and non-negative")
   167  	}
   168  	if c.PlacementSpec != "" {
   169  		placementSpecs := strings.Split(c.PlacementSpec, ",")
   170  		c.Placement = make([]string, len(placementSpecs))
   171  		for i, spec := range placementSpecs {
   172  			p, err := instance.ParsePlacement(strings.TrimSpace(spec))
   173  			if err == nil && names.IsContainerMachine(p.Directive) {
   174  				return errors.New("enable-ha cannot be used with container placement directives")
   175  			}
   176  			if err == nil && p.Scope == instance.MachineScope {
   177  				// Targeting machines is ok.
   178  				c.Placement[i] = p.String()
   179  				continue
   180  			}
   181  			if err != instance.ErrPlacementScopeMissing {
   182  				return errors.Errorf("unsupported enable-ha placement directive %q", spec)
   183  			}
   184  			c.Placement[i] = spec
   185  		}
   186  	}
   187  	return cmd.CheckEmpty(args)
   188  }
   189  
   190  type availabilityInfo struct {
   191  	Maintained []string `json:"maintained,omitempty" yaml:"maintained,flow,omitempty"`
   192  	Removed    []string `json:"removed,omitempty" yaml:"removed,flow,omitempty"`
   193  	Added      []string `json:"added,omitempty" yaml:"added,flow,omitempty"`
   194  	Promoted   []string `json:"promoted,omitempty" yaml:"promoted,flow,omitempty"`
   195  	Demoted    []string `json:"demoted,omitempty" yaml:"demoted,flow,omitempty"`
   196  	Converted  []string `json:"converted,omitempty" yaml:"converted,flow,omitempty"`
   197  }
   198  
   199  // MakeHAClient defines the methods
   200  // on the client api that the ensure availability
   201  // command calls.
   202  type MakeHAClient interface {
   203  	Close() error
   204  	EnableHA(
   205  		numControllers int, cons constraints.Value,
   206  		placement []string) (params.ControllersChanges, error)
   207  }
   208  
   209  // Run connects to the environment specified on the command line
   210  // and calls EnableHA.
   211  func (c *enableHACommand) Run(ctx *cmd.Context) error {
   212  	var err error
   213  	c.Constraints, err = common.ParseConstraints(ctx, c.ConstraintsStr)
   214  	if err != nil {
   215  		return err
   216  	}
   217  	haClient, err := c.newHAClientFunc()
   218  	if err != nil {
   219  		return err
   220  	}
   221  
   222  	defer haClient.Close()
   223  	enableHAResult, err := haClient.EnableHA(
   224  		c.NumControllers,
   225  		c.Constraints,
   226  		c.Placement,
   227  	)
   228  	if err != nil {
   229  		return block.ProcessBlockedError(err, block.BlockChange)
   230  	}
   231  
   232  	result := availabilityInfo{
   233  		Added:      machineTagsToIds(enableHAResult.Added...),
   234  		Removed:    machineTagsToIds(enableHAResult.Removed...),
   235  		Maintained: machineTagsToIds(enableHAResult.Maintained...),
   236  		Promoted:   machineTagsToIds(enableHAResult.Promoted...),
   237  		Demoted:    machineTagsToIds(enableHAResult.Demoted...),
   238  		Converted:  machineTagsToIds(enableHAResult.Converted...),
   239  	}
   240  	return c.out.Write(ctx, result)
   241  }
   242  
   243  // Convert machine tags to ids, skipping any non-machine tags.
   244  func machineTagsToIds(tags ...string) []string {
   245  	var result []string
   246  
   247  	for _, rawTag := range tags {
   248  		tag, err := names.ParseTag(rawTag)
   249  		if err != nil {
   250  			continue
   251  		}
   252  		result = append(result, tag.Id())
   253  	}
   254  	return result
   255  }