github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/cmd/juju/application/addunit.go (about)

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