github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/cmd/juju/service/addunit.go (about)

     1  // Copyright 2012-2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package service
     5  
     6  import (
     7  	"regexp"
     8  	"strings"
     9  
    10  	"github.com/juju/cmd"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/names"
    13  	"launchpad.net/gnuflag"
    14  
    15  	apiservice "github.com/juju/juju/api/service"
    16  	"github.com/juju/juju/cmd/juju/block"
    17  	"github.com/juju/juju/cmd/modelcmd"
    18  	"github.com/juju/juju/instance"
    19  )
    20  
    21  var usageAddUnitSummary = `
    22  Adds one or more units to a deployed service.`[1:]
    23  
    24  var usageAddUnitDetails = `
    25  Adding units to an existing service is a way to scale out that service. 
    26  Many charms will seamlessly support horizontal scaling, others may need an
    27  additional service to facilitate load-balancing (check the individual 
    28  charm documentation).
    29  This command is applied to services that have already been deployed.
    30  By default, services are deployed to newly provisioned machines in
    31  accordance with any service or model constraints. Alternatively, this 
    32  command also supports the placement directive ("--to") for targeting
    33  specific machines or containers, which will bypass any existing
    34  constraints.
    35  
    36  Examples:
    37  Add five units of wordpress on five new machines:
    38  
    39      juju add-unit wordpress -n 5
    40  
    41  Add one unit of mysql to the existing machine 23:
    42  
    43      juju add-unit mysql --to 23
    44  
    45  Create a new LXC container on machine 7 and add one unit of mysql:
    46  
    47      juju add-unit mysql --to lxc:7
    48  
    49  Add a unit of mariadb to LXC container number 3 on machine 24:
    50  
    51      juju add-unit mariadb --to 24/lxc/3
    52  
    53  See also: 
    54      remove-unit`[1:]
    55  
    56  // UnitCommandBase provides support for commands which deploy units. It handles the parsing
    57  // and validation of --to and --num-units arguments.
    58  type UnitCommandBase struct {
    59  	// PlacementSpec is the raw string command arg value used to specify placement directives.
    60  	PlacementSpec string
    61  	// Placement is the result of parsing the PlacementSpec arg value.
    62  	Placement []*instance.Placement
    63  	NumUnits  int
    64  }
    65  
    66  func (c *UnitCommandBase) SetFlags(f *gnuflag.FlagSet) {
    67  	f.IntVar(&c.NumUnits, "num-units", 1, "")
    68  	f.StringVar(&c.PlacementSpec, "to", "", "The machine and/or container to deploy the unit in (bypasses constraints)")
    69  }
    70  
    71  func (c *UnitCommandBase) Init(args []string) error {
    72  	if c.NumUnits < 1 {
    73  		return errors.New("--num-units must be a positive integer")
    74  	}
    75  	if c.PlacementSpec != "" {
    76  		placementSpecs := strings.Split(c.PlacementSpec, ",")
    77  		c.Placement = make([]*instance.Placement, len(placementSpecs))
    78  		for i, spec := range placementSpecs {
    79  			placement, err := parsePlacement(spec)
    80  			if err != nil {
    81  				return errors.Errorf("invalid --to parameter %q", spec)
    82  			}
    83  			c.Placement[i] = placement
    84  		}
    85  	}
    86  	if len(c.Placement) > c.NumUnits {
    87  		logger.Warningf("%d unit(s) will be deployed, extra placement directives will be ignored", c.NumUnits)
    88  	}
    89  	return nil
    90  }
    91  
    92  func parsePlacement(spec string) (*instance.Placement, error) {
    93  	if spec == "" {
    94  		return nil, nil
    95  	}
    96  	placement, err := instance.ParsePlacement(spec)
    97  	if err == instance.ErrPlacementScopeMissing {
    98  		spec = "model-uuid" + ":" + spec
    99  		placement, err = instance.ParsePlacement(spec)
   100  	}
   101  	if err != nil {
   102  		return nil, errors.Errorf("invalid --to parameter %q", spec)
   103  	}
   104  	return placement, nil
   105  }
   106  
   107  // NewAddUnitCommand returns a command that adds a unit[s] to a service.
   108  func NewAddUnitCommand() cmd.Command {
   109  	return modelcmd.Wrap(&addUnitCommand{})
   110  }
   111  
   112  // addUnitCommand is responsible adding additional units to a service.
   113  type addUnitCommand struct {
   114  	modelcmd.ModelCommandBase
   115  	UnitCommandBase
   116  	ServiceName string
   117  	api         serviceAddUnitAPI
   118  }
   119  
   120  func (c *addUnitCommand) Info() *cmd.Info {
   121  	return &cmd.Info{
   122  		Name:    "add-unit",
   123  		Args:    "<service name>",
   124  		Purpose: usageAddUnitSummary,
   125  		Doc:     usageAddUnitDetails,
   126  		Aliases: []string{"add-units"},
   127  	}
   128  }
   129  
   130  func (c *addUnitCommand) SetFlags(f *gnuflag.FlagSet) {
   131  	c.UnitCommandBase.SetFlags(f)
   132  	f.IntVar(&c.NumUnits, "n", 1, "Number of units to add")
   133  }
   134  
   135  func (c *addUnitCommand) Init(args []string) error {
   136  	switch len(args) {
   137  	case 1:
   138  		c.ServiceName = args[0]
   139  	case 0:
   140  		return errors.New("no service specified")
   141  	}
   142  	if err := cmd.CheckEmpty(args[1:]); err != nil {
   143  		return err
   144  	}
   145  	return c.UnitCommandBase.Init(args)
   146  }
   147  
   148  // serviceAddUnitAPI defines the methods on the client API
   149  // that the service add-unit command calls.
   150  type serviceAddUnitAPI interface {
   151  	Close() error
   152  	ModelUUID() string
   153  	AddUnits(service string, numUnits int, placement []*instance.Placement) ([]string, error)
   154  }
   155  
   156  func (c *addUnitCommand) getAPI() (serviceAddUnitAPI, error) {
   157  	if c.api != nil {
   158  		return c.api, nil
   159  	}
   160  	root, err := c.NewAPIRoot()
   161  	if err != nil {
   162  		return nil, errors.Trace(err)
   163  	}
   164  	return apiservice.NewClient(root), nil
   165  }
   166  
   167  // Run connects to the environment specified on the command line
   168  // and calls AddUnits for the given service.
   169  func (c *addUnitCommand) Run(_ *cmd.Context) error {
   170  	apiclient, err := c.getAPI()
   171  	if err != nil {
   172  		return err
   173  	}
   174  	defer apiclient.Close()
   175  
   176  	for i, p := range c.Placement {
   177  		if p.Scope == "model-uuid" {
   178  			p.Scope = apiclient.ModelUUID()
   179  		}
   180  		c.Placement[i] = p
   181  	}
   182  	_, err = apiclient.AddUnits(c.ServiceName, c.NumUnits, c.Placement)
   183  	return block.ProcessBlockedError(err, block.BlockChange)
   184  }
   185  
   186  // deployTarget describes the format a machine or container target must match to be valid.
   187  const deployTarget = "^(" + names.ContainerTypeSnippet + ":)?" + names.MachineSnippet + "$"
   188  
   189  var validMachineOrNewContainer = regexp.MustCompile(deployTarget)
   190  
   191  // IsMachineOrNewContainer returns whether spec is a valid machine id
   192  // or new container definition.
   193  func IsMachineOrNewContainer(spec string) bool {
   194  	return validMachineOrNewContainer.MatchString(spec)
   195  }