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 }