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