github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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.Wrap(haCommand)
    37  }
    38  
    39  // enableHACommand makes the controller highly available.
    40  type enableHACommand struct {
    41  	modelcmd.ModelCommandBase
    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.  enable-ha must be called
    70  to ensure that the specified number of controllers are made available.
    71  
    72  An odd number of controllers is required.
    73  
    74  Examples:
    75      # Ensure that the controller is still in highly available mode. If
    76      # there is only 1 controller running, this will ensure there
    77      # are 3 running. If you have previously requested more than 3,
    78      # then that number will be ensured.
    79      juju enable-ha
    80  
    81      # Ensure that 5 controllers are available.
    82      juju enable-ha -n 5 
    83  
    84      # Ensure that 7 controllers are available, with newly created
    85      # controller machines having at least 8GB RAM.
    86      juju enable-ha -n 7 --constraints mem=8G
    87  
    88      # Ensure that 7 controllers are available, with machines server1 and
    89      # server2 used first, and if necessary, newly created controller
    90      # machines having at least 8GB RAM.
    91      juju enable-ha -n 7 --to server1,server2 --constraints mem=8G
    92  `
    93  
    94  // formatSimple marshals value to a yaml-formatted []byte, unless value is nil.
    95  func formatSimple(writer io.Writer, value interface{}) error {
    96  	enableHAResult, ok := value.(availabilityInfo)
    97  	if !ok {
    98  		return errors.Errorf("unexpected result type for enable-ha call: %T", value)
    99  	}
   100  
   101  	for _, machineList := range []struct {
   102  		message string
   103  		list    []string
   104  	}{
   105  		{
   106  			"maintaining machines: %s\n",
   107  			enableHAResult.Maintained,
   108  		},
   109  		{
   110  			"adding machines: %s\n",
   111  			enableHAResult.Added,
   112  		},
   113  		{
   114  			"removing machines: %s\n",
   115  			enableHAResult.Removed,
   116  		},
   117  		{
   118  			"promoting machines: %s\n",
   119  			enableHAResult.Promoted,
   120  		},
   121  		{
   122  			"demoting machines: %s\n",
   123  			enableHAResult.Demoted,
   124  		},
   125  		{
   126  			"converting machines: %s\n",
   127  			enableHAResult.Converted,
   128  		},
   129  	} {
   130  		if len(machineList.list) == 0 {
   131  			continue
   132  		}
   133  		_, err := fmt.Fprintf(writer, machineList.message, strings.Join(machineList.list, ", "))
   134  		if err != nil {
   135  			return err
   136  		}
   137  	}
   138  
   139  	return nil
   140  }
   141  
   142  func (c *enableHACommand) Info() *cmd.Info {
   143  	return &cmd.Info{
   144  		Name:    "enable-ha",
   145  		Purpose: "Ensure that sufficient controllers exist to provide redundancy.",
   146  		Doc:     enableHADoc,
   147  	}
   148  }
   149  
   150  func (c *enableHACommand) SetFlags(f *gnuflag.FlagSet) {
   151  	c.ModelCommandBase.SetFlags(f)
   152  	f.IntVar(&c.NumControllers, "n", 0, "Number of controllers to make available")
   153  	f.StringVar(&c.PlacementSpec, "to", "", "The machine(s) to become controllers, bypasses constraints")
   154  	f.StringVar(&c.ConstraintsStr, "constraints", "", "Additional machine constraints")
   155  	c.out.AddFlags(f, "simple", map[string]cmd.Formatter{
   156  		"yaml":   cmd.FormatYaml,
   157  		"json":   cmd.FormatJson,
   158  		"simple": formatSimple,
   159  	})
   160  
   161  }
   162  
   163  func (c *enableHACommand) Init(args []string) error {
   164  	if c.NumControllers < 0 || (c.NumControllers%2 != 1 && c.NumControllers != 0) {
   165  		return errors.Errorf("must specify a number of controllers odd and non-negative")
   166  	}
   167  	if c.PlacementSpec != "" {
   168  		placementSpecs := strings.Split(c.PlacementSpec, ",")
   169  		c.Placement = make([]string, len(placementSpecs))
   170  		for i, spec := range placementSpecs {
   171  			p, err := instance.ParsePlacement(strings.TrimSpace(spec))
   172  			if err == nil && names.IsContainerMachine(p.Directive) {
   173  				return errors.New("enable-ha cannot be used with container placement directives")
   174  			}
   175  			if err == nil && p.Scope == instance.MachineScope {
   176  				// Targeting machines is ok.
   177  				c.Placement[i] = p.String()
   178  				continue
   179  			}
   180  			if err != instance.ErrPlacementScopeMissing {
   181  				return errors.Errorf("unsupported enable-ha placement directive %q", spec)
   182  			}
   183  			c.Placement[i] = spec
   184  		}
   185  	}
   186  	return cmd.CheckEmpty(args)
   187  }
   188  
   189  type availabilityInfo struct {
   190  	Maintained []string `json:"maintained,omitempty" yaml:"maintained,flow,omitempty"`
   191  	Removed    []string `json:"removed,omitempty" yaml:"removed,flow,omitempty"`
   192  	Added      []string `json:"added,omitempty" yaml:"added,flow,omitempty"`
   193  	Promoted   []string `json:"promoted,omitempty" yaml:"promoted,flow,omitempty"`
   194  	Demoted    []string `json:"demoted,omitempty" yaml:"demoted,flow,omitempty"`
   195  	Converted  []string `json:"converted,omitempty" yaml:"converted,flow,omitempty"`
   196  }
   197  
   198  // MakeHAClient defines the methods
   199  // on the client api that the ensure availability
   200  // command calls.
   201  type MakeHAClient interface {
   202  	Close() error
   203  	EnableHA(
   204  		numControllers int, cons constraints.Value,
   205  		placement []string) (params.ControllersChanges, error)
   206  }
   207  
   208  // Run connects to the environment specified on the command line
   209  // and calls EnableHA.
   210  func (c *enableHACommand) Run(ctx *cmd.Context) error {
   211  	var err error
   212  	c.Constraints, err = common.ParseConstraints(ctx, c.ConstraintsStr)
   213  	if err != nil {
   214  		return err
   215  	}
   216  	haClient, err := c.newHAClientFunc()
   217  	if err != nil {
   218  		return err
   219  	}
   220  
   221  	defer haClient.Close()
   222  	enableHAResult, err := haClient.EnableHA(
   223  		c.NumControllers,
   224  		c.Constraints,
   225  		c.Placement,
   226  	)
   227  	if err != nil {
   228  		return block.ProcessBlockedError(err, block.BlockChange)
   229  	}
   230  
   231  	result := availabilityInfo{
   232  		Added:      machineTagsToIds(enableHAResult.Added...),
   233  		Removed:    machineTagsToIds(enableHAResult.Removed...),
   234  		Maintained: machineTagsToIds(enableHAResult.Maintained...),
   235  		Promoted:   machineTagsToIds(enableHAResult.Promoted...),
   236  		Demoted:    machineTagsToIds(enableHAResult.Demoted...),
   237  		Converted:  machineTagsToIds(enableHAResult.Converted...),
   238  	}
   239  	return c.out.Write(ctx, result)
   240  }
   241  
   242  // Convert machine tags to ids, skipping any non-machine tags.
   243  func machineTagsToIds(tags ...string) []string {
   244  	var result []string
   245  
   246  	for _, rawTag := range tags {
   247  		tag, err := names.ParseTag(rawTag)
   248  		if err != nil {
   249  			continue
   250  		}
   251  		result = append(result, tag.Id())
   252  	}
   253  	return result
   254  }