github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/cmd/juju/environment/ensureavailability.go (about)

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