github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/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  	"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/cmd/envcmd"
    16  	"github.com/juju/juju/cmd/juju/block"
    17  	"github.com/juju/juju/environs/config"
    18  	"github.com/juju/juju/provider"
    19  )
    20  
    21  // UnitCommandBase provides support for commands which deploy units. It handles the parsing
    22  // and validation of --to and --num-units arguments.
    23  type UnitCommandBase struct {
    24  	ToMachineSpec string
    25  	NumUnits      int
    26  }
    27  
    28  func (c *UnitCommandBase) SetFlags(f *gnuflag.FlagSet) {
    29  	f.IntVar(&c.NumUnits, "num-units", 1, "")
    30  	f.StringVar(&c.ToMachineSpec, "to", "", "the machine or container to deploy the unit in, bypasses constraints")
    31  }
    32  
    33  func (c *UnitCommandBase) Init(args []string) error {
    34  	if c.NumUnits < 1 {
    35  		return errors.New("--num-units must be a positive integer")
    36  	}
    37  	if c.ToMachineSpec != "" {
    38  		if c.NumUnits > 1 {
    39  			return errors.New("cannot use --num-units > 1 with --to")
    40  		}
    41  		if !IsMachineOrNewContainer(c.ToMachineSpec) {
    42  			return fmt.Errorf("invalid --to parameter %q", c.ToMachineSpec)
    43  		}
    44  
    45  	}
    46  	return nil
    47  }
    48  
    49  // TODO(anastasiamac) 2014-10-20 Bug#1383116
    50  // This exists to provide more context to the user about
    51  // why they cannot allocate units to machine 0. Remove
    52  // this when the local provider's machine 0 is a container.
    53  // TODO(cherylj) Unexport CheckProvider once deploy is moved under service
    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  // TODO(cherylj) Unexport GetClientConfig and make it a standard function
    62  // once deploy is moved under service
    63  var GetClientConfig = func(client ServiceAddUnitAPI) (*config.Config, error) {
    64  	// Separated into a variable for easy overrides
    65  	attrs, err := client.EnvironmentGet()
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  
    70  	return config.New(config.NoDefaults, attrs)
    71  }
    72  
    73  // AddUnitCommand is responsible adding additional units to a service.
    74  type AddUnitCommand struct {
    75  	envcmd.EnvCommandBase
    76  	UnitCommandBase
    77  	ServiceName string
    78  	api         ServiceAddUnitAPI
    79  }
    80  
    81  const addUnitDoc = `
    82  Adding units to an existing service is a way to scale out an environment by
    83  deploying more instances of a service.  Add-unit must be called on services that
    84  have already been deployed via juju deploy.
    85  
    86  By default, services are deployed to newly provisioned machines.  Alternatively,
    87  service units can be added to a specific existing machine using the --to
    88  argument.
    89  
    90  Examples:
    91   juju service add-unit mysql -n 5          (Add 5 mysql units on 5 new machines)
    92   juju service add-unit mysql --to 23       (Add a mysql unit to machine 23)
    93   juju service add-unit mysql --to 24/lxc/3 (Add unit to lxc container 3 on host machine 24)
    94   juju service add-unit mysql --to lxc:25   (Add unit to a new lxc container on host machine 25)
    95  `
    96  
    97  func (c *AddUnitCommand) Info() *cmd.Info {
    98  	return &cmd.Info{
    99  		Name:    "add-unit",
   100  		Args:    "<service name>",
   101  		Purpose: "add one or more units of an already-deployed service",
   102  		Doc:     addUnitDoc,
   103  	}
   104  }
   105  
   106  func (c *AddUnitCommand) SetFlags(f *gnuflag.FlagSet) {
   107  	c.UnitCommandBase.SetFlags(f)
   108  	f.IntVar(&c.NumUnits, "n", 1, "number of service units to add")
   109  }
   110  
   111  func (c *AddUnitCommand) Init(args []string) error {
   112  	switch len(args) {
   113  	case 1:
   114  		c.ServiceName = args[0]
   115  	case 0:
   116  		return errors.New("no service specified")
   117  	}
   118  	if err := cmd.CheckEmpty(args[1:]); err != nil {
   119  		return err
   120  	}
   121  	return c.UnitCommandBase.Init(args)
   122  }
   123  
   124  // ServiceAddUnitAPI defines the methods on the client API
   125  // that the service add-unit command calls.
   126  type ServiceAddUnitAPI interface {
   127  	Close() error
   128  	AddServiceUnits(service string, numUnits int, machineSpec string) ([]string, error)
   129  	EnvironmentGet() (map[string]interface{}, error)
   130  }
   131  
   132  func (c *AddUnitCommand) getAPI() (ServiceAddUnitAPI, error) {
   133  	if c.api != nil {
   134  		return c.api, nil
   135  	}
   136  	return c.NewAPIClient()
   137  }
   138  
   139  // Run connects to the environment specified on the command line
   140  // and calls AddServiceUnits for the given service.
   141  func (c *AddUnitCommand) Run(_ *cmd.Context) error {
   142  	apiclient, err := c.getAPI()
   143  	if err != nil {
   144  		return err
   145  	}
   146  	defer apiclient.Close()
   147  
   148  	conf, err := GetClientConfig(apiclient)
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	if err := c.CheckProvider(conf); err != nil {
   154  		return err
   155  	}
   156  
   157  	_, err = apiclient.AddServiceUnits(c.ServiceName, c.NumUnits, c.ToMachineSpec)
   158  	return block.ProcessBlockedError(err, block.BlockChange)
   159  }
   160  
   161  // deployTarget describes the format a machine or container target must match to be valid.
   162  const deployTarget = "^(" + names.ContainerTypeSnippet + ":)?" + names.MachineSnippet + "$"
   163  
   164  var validMachineOrNewContainer = regexp.MustCompile(deployTarget)
   165  
   166  // IsMachineOrNewContainer returns whether spec is a valid machine id
   167  // or new container definition.
   168  func IsMachineOrNewContainer(spec string) bool {
   169  	return validMachineOrNewContainer.MatchString(spec)
   170  }