github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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 "github.com/juju/juju/apiserver/params" 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/instance" 20 "github.com/juju/juju/provider" 21 ) 22 23 // UnitCommandBase provides support for commands which deploy units. It handles the parsing 24 // and validation of --to and --num-units arguments. 25 type UnitCommandBase struct { 26 // PlacementSpec is the raw string command arg value used to specify placement directives. 27 PlacementSpec string 28 // Placement is the result of parsing the PlacementSpec arg value. 29 Placement []*instance.Placement 30 NumUnits int 31 } 32 33 func (c *UnitCommandBase) SetFlags(f *gnuflag.FlagSet) { 34 f.IntVar(&c.NumUnits, "num-units", 1, "") 35 f.StringVar(&c.PlacementSpec, "to", "", "the machine, container or placement directive to deploy the unit in, bypasses constraints") 36 } 37 38 func (c *UnitCommandBase) Init(args []string) error { 39 if c.NumUnits < 1 { 40 return errors.New("--num-units must be a positive integer") 41 } 42 if c.PlacementSpec != "" { 43 // Older Juju versions just accept a single machine or container. 44 if IsMachineOrNewContainer(c.PlacementSpec) { 45 return nil 46 } 47 // Newer Juju versions accept a comma separated list of placement directives. 48 placementSpecs := strings.Split(c.PlacementSpec, ",") 49 c.Placement = make([]*instance.Placement, len(placementSpecs)) 50 for i, spec := range placementSpecs { 51 placement, err := parsePlacement(spec) 52 if err != nil { 53 return errors.Errorf("invalid --to parameter %q", spec) 54 } 55 c.Placement[i] = placement 56 } 57 } 58 if len(c.Placement) > c.NumUnits { 59 logger.Warningf("%d unit(s) will be deployed, extra placement directives will be ignored", c.NumUnits) 60 } 61 return nil 62 } 63 64 func parsePlacement(spec string) (*instance.Placement, error) { 65 placement, err := instance.ParsePlacement(spec) 66 if err == instance.ErrPlacementScopeMissing { 67 spec = "env-uuid" + ":" + spec 68 placement, err = instance.ParsePlacement(spec) 69 } 70 if err != nil { 71 return nil, errors.Errorf("invalid --to parameter %q", spec) 72 } 73 return placement, nil 74 } 75 76 // TODO(anastasiamac) 2014-10-20 Bug#1383116 77 // This exists to provide more context to the user about 78 // why they cannot allocate units to machine 0. Remove 79 // this when the local provider's machine 0 is a container. 80 // TODO(cherylj) Unexport CheckProvider once deploy is moved under service 81 func (c *UnitCommandBase) CheckProvider(conf *config.Config) error { 82 isMachineZero := c.PlacementSpec == "0" 83 for _, p := range c.Placement { 84 isMachineZero = isMachineZero || (p.Scope == instance.MachineScope && p.Directive == "0") 85 } 86 if conf.Type() == provider.Local && isMachineZero { 87 return errors.New("machine 0 is the state server for a local environment and cannot host units") 88 } 89 return nil 90 } 91 92 // TODO(cherylj) Unexport GetClientConfig and make it a standard function 93 // once deploy is moved under service 94 var GetClientConfig = func(client ServiceAddUnitAPI) (*config.Config, error) { 95 // Separated into a variable for easy overrides 96 attrs, err := client.EnvironmentGet() 97 if err != nil { 98 return nil, err 99 } 100 101 return config.New(config.NoDefaults, attrs) 102 } 103 104 // AddUnitCommand is responsible adding additional units to a service. 105 type AddUnitCommand struct { 106 envcmd.EnvCommandBase 107 UnitCommandBase 108 ServiceName string 109 api ServiceAddUnitAPI 110 } 111 112 const addUnitDoc = ` 113 Adding units to an existing service is a way to scale out an environment by 114 deploying more instances of a service. Add-unit must be called on services that 115 have already been deployed via juju deploy. 116 117 By default, services are deployed to newly provisioned machines. Alternatively, 118 service units can be added to a specific existing machine using the --to 119 argument. 120 121 Examples: 122 juju service add-unit mysql -n 5 (Add 5 mysql units on 5 new machines) 123 juju service add-unit mysql --to 23 (Add a mysql unit to machine 23) 124 juju service add-unit mysql --to 24/lxc/3 (Add unit to lxc container 3 on host machine 24) 125 juju service add-unit mysql --to lxc:25 (Add unit to a new lxc container on host machine 25) 126 ` 127 128 func (c *AddUnitCommand) Info() *cmd.Info { 129 return &cmd.Info{ 130 Name: "add-unit", 131 Args: "<service name>", 132 Purpose: "add one or more units of an already-deployed service", 133 Doc: addUnitDoc, 134 } 135 } 136 137 func (c *AddUnitCommand) SetFlags(f *gnuflag.FlagSet) { 138 c.UnitCommandBase.SetFlags(f) 139 f.IntVar(&c.NumUnits, "n", 1, "number of service units to add") 140 } 141 142 func (c *AddUnitCommand) Init(args []string) error { 143 switch len(args) { 144 case 1: 145 c.ServiceName = args[0] 146 case 0: 147 return errors.New("no service specified") 148 } 149 if err := cmd.CheckEmpty(args[1:]); err != nil { 150 return err 151 } 152 return c.UnitCommandBase.Init(args) 153 } 154 155 // ServiceAddUnitAPI defines the methods on the client API 156 // that the service add-unit command calls. 157 type ServiceAddUnitAPI interface { 158 Close() error 159 EnvironmentUUID() string 160 AddServiceUnits(service string, numUnits int, machineSpec string) ([]string, error) 161 AddServiceUnitsWithPlacement(service string, numUnits int, placement []*instance.Placement) ([]string, error) 162 EnvironmentGet() (map[string]interface{}, error) 163 } 164 165 func (c *AddUnitCommand) getAPI() (ServiceAddUnitAPI, error) { 166 if c.api != nil { 167 return c.api, nil 168 } 169 return c.NewAPIClient() 170 } 171 172 // Run connects to the environment specified on the command line 173 // and calls AddServiceUnits for the given service. 174 func (c *AddUnitCommand) Run(_ *cmd.Context) error { 175 apiclient, err := c.getAPI() 176 if err != nil { 177 return err 178 } 179 defer apiclient.Close() 180 181 conf, err := GetClientConfig(apiclient) 182 if err != nil { 183 return err 184 } 185 186 if err := c.CheckProvider(conf); err != nil { 187 return err 188 } 189 190 for i, p := range c.Placement { 191 if p.Scope == "env-uuid" { 192 p.Scope = apiclient.EnvironmentUUID() 193 } 194 c.Placement[i] = p 195 } 196 if len(c.Placement) > 0 { 197 _, err = apiclient.AddServiceUnitsWithPlacement(c.ServiceName, c.NumUnits, c.Placement) 198 if err == nil { 199 return nil 200 } 201 if !params.IsCodeNotImplemented(err) { 202 return block.ProcessBlockedError(err, block.BlockChange) 203 } 204 } 205 if c.PlacementSpec != "" && !IsMachineOrNewContainer(c.PlacementSpec) { 206 return errors.Errorf("unsupported --to parameter %q", c.PlacementSpec) 207 } 208 if c.PlacementSpec != "" && c.NumUnits > 1 { 209 return errors.New("this version of Juju does not support --num-units > 1 with --to") 210 } 211 _, err = apiclient.AddServiceUnits(c.ServiceName, c.NumUnits, c.PlacementSpec) 212 return block.ProcessBlockedError(err, block.BlockChange) 213 } 214 215 // deployTarget describes the format a machine or container target must match to be valid. 216 const deployTarget = "^(" + names.ContainerTypeSnippet + ":)?" + names.MachineSnippet + "$" 217 218 var validMachineOrNewContainer = regexp.MustCompile(deployTarget) 219 220 // IsMachineOrNewContainer returns whether spec is a valid machine id 221 // or new container definition. 222 func IsMachineOrNewContainer(spec string) bool { 223 return validMachineOrNewContainer.MatchString(spec) 224 }