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