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 }