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