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

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package main
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"regexp"
    10  
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/names"
    13  	"launchpad.net/gnuflag"
    14  
    15  	"github.com/juju/juju/api"
    16  	"github.com/juju/juju/cmd/envcmd"
    17  	"github.com/juju/juju/cmd/juju/block"
    18  	"github.com/juju/juju/environs/config"
    19  	"github.com/juju/juju/provider"
    20  )
    21  
    22  // UnitCommandBase provides support for commands which deploy units. It handles the parsing
    23  // and validation of --to and --num-units arguments.
    24  type UnitCommandBase struct {
    25  	ToMachineSpec string
    26  	NumUnits      int
    27  }
    28  
    29  func (c *UnitCommandBase) SetFlags(f *gnuflag.FlagSet) {
    30  	f.IntVar(&c.NumUnits, "num-units", 1, "")
    31  	f.StringVar(&c.ToMachineSpec, "to", "", "the machine or container to deploy the unit in, bypasses constraints")
    32  }
    33  
    34  func (c *UnitCommandBase) Init(args []string) error {
    35  	if c.NumUnits < 1 {
    36  		return errors.New("--num-units must be a positive integer")
    37  	}
    38  	if c.ToMachineSpec != "" {
    39  		if c.NumUnits > 1 {
    40  			return errors.New("cannot use --num-units > 1 with --to")
    41  		}
    42  		if !isMachineOrNewContainer(c.ToMachineSpec) {
    43  			return fmt.Errorf("invalid --to parameter %q", c.ToMachineSpec)
    44  		}
    45  
    46  	}
    47  	return nil
    48  }
    49  
    50  // TODO(anastasiamac) 2014-10-20 Bug#1383116
    51  // This exists to provide more context to the user about
    52  // why they cannot allocate units to machine 0. Remove
    53  // this when the local provider's machine 0 is a container.
    54  func (c *UnitCommandBase) checkProvider(conf *config.Config) error {
    55  	if conf.Type() == provider.Local && c.ToMachineSpec == "0" {
    56  		return errors.New("machine 0 is the state server for a local environment and cannot host units")
    57  	}
    58  	return nil
    59  }
    60  
    61  var getClientConfig = func(client *api.Client) (*config.Config, error) {
    62  	// Separated into a variable for easy overrides
    63  	attrs, err := client.EnvironmentGet()
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	return config.New(config.NoDefaults, attrs)
    69  }
    70  
    71  // AddUnitCommand is responsible adding additional units to a service.
    72  type AddUnitCommand struct {
    73  	envcmd.EnvCommandBase
    74  	UnitCommandBase
    75  	ServiceName string
    76  }
    77  
    78  const addUnitDoc = `
    79  Adding units to an existing service is a way to scale out an environment by
    80  deploying more instances of a service.  Add-unit must be called on services that
    81  have already been deployed via juju deploy.  
    82  
    83  By default, services are deployed to newly provisioned machines.  Alternatively,
    84  service units can be added to a specific existing machine using the --to
    85  argument.
    86  
    87  Examples:
    88   juju add-unit mysql -n 5          (Add 5 mysql units on 5 new machines)
    89   juju add-unit mysql --to 23       (Add a mysql unit to machine 23)
    90   juju add-unit mysql --to 24/lxc/3 (Add unit to lxc container 3 on host machine 24)
    91   juju add-unit mysql --to lxc:25   (Add unit to a new lxc container on host machine 25)
    92  `
    93  
    94  func (c *AddUnitCommand) Info() *cmd.Info {
    95  	return &cmd.Info{
    96  		Name:    "add-unit",
    97  		Args:    "<service name>",
    98  		Purpose: "add one or more units of an already-deployed service",
    99  		Doc:     addUnitDoc,
   100  	}
   101  }
   102  
   103  func (c *AddUnitCommand) SetFlags(f *gnuflag.FlagSet) {
   104  	c.UnitCommandBase.SetFlags(f)
   105  	f.IntVar(&c.NumUnits, "n", 1, "number of service units to add")
   106  }
   107  
   108  func (c *AddUnitCommand) Init(args []string) error {
   109  	switch len(args) {
   110  	case 1:
   111  		c.ServiceName = args[0]
   112  	case 0:
   113  		return errors.New("no service specified")
   114  	}
   115  	if err := cmd.CheckEmpty(args[1:]); err != nil {
   116  		return err
   117  	}
   118  	return c.UnitCommandBase.Init(args)
   119  }
   120  
   121  // Run connects to the environment specified on the command line
   122  // and calls AddServiceUnits for the given service.
   123  func (c *AddUnitCommand) Run(_ *cmd.Context) error {
   124  	apiclient, err := c.NewAPIClient()
   125  	if err != nil {
   126  		return err
   127  	}
   128  	defer apiclient.Close()
   129  
   130  	conf, err := getClientConfig(apiclient)
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	if err := c.checkProvider(conf); err != nil {
   136  		return err
   137  	}
   138  
   139  	_, err = apiclient.AddServiceUnits(c.ServiceName, c.NumUnits, c.ToMachineSpec)
   140  	return block.ProcessBlockedError(err, block.BlockChange)
   141  }
   142  
   143  const (
   144  	deployTarget = "^(" + names.ContainerTypeSnippet + ":)?" + names.MachineSnippet + "$"
   145  )
   146  
   147  var (
   148  	validMachineOrNewContainer = regexp.MustCompile(deployTarget)
   149  )
   150  
   151  // isMachineOrNewContainer returns whether spec is a valid machine id
   152  // or new container definition.
   153  func isMachineOrNewContainer(spec string) bool {
   154  	return validMachineOrNewContainer.MatchString(spec)
   155  }