github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 apiservice "github.com/juju/juju/api/service" 16 "github.com/juju/juju/cmd/juju/block" 17 "github.com/juju/juju/cmd/modelcmd" 18 "github.com/juju/juju/instance" 19 ) 20 21 var usageAddUnitSummary = ` 22 Adds one or more units to a deployed service.`[1:] 23 24 var usageAddUnitDetails = ` 25 Adding units to an existing service is a way to scale out that service. 26 Many charms will seamlessly support horizontal scaling, others may need an 27 additional service to facilitate load-balancing (check the individual 28 charm documentation). 29 This command is applied to services that have already been deployed. 30 By default, services are deployed to newly provisioned machines in 31 accordance with any service or model constraints. Alternatively, this 32 command also supports the placement directive ("--to") for targeting 33 specific machines or containers, which will bypass any existing 34 constraints. 35 36 Examples: 37 Add five units of wordpress on five new machines: 38 39 juju add-unit wordpress -n 5 40 41 Add one unit of mysql to the existing machine 23: 42 43 juju add-unit mysql --to 23 44 45 Create a new LXC container on machine 7 and add one unit of mysql: 46 47 juju add-unit mysql --to lxc:7 48 49 Add a unit of mariadb to LXC container number 3 on machine 24: 50 51 juju add-unit mariadb --to 24/lxc/3 52 53 See also: 54 remove-unit`[1:] 55 56 // UnitCommandBase provides support for commands which deploy units. It handles the parsing 57 // and validation of --to and --num-units arguments. 58 type UnitCommandBase struct { 59 // PlacementSpec is the raw string command arg value used to specify placement directives. 60 PlacementSpec string 61 // Placement is the result of parsing the PlacementSpec arg value. 62 Placement []*instance.Placement 63 NumUnits int 64 } 65 66 func (c *UnitCommandBase) SetFlags(f *gnuflag.FlagSet) { 67 f.IntVar(&c.NumUnits, "num-units", 1, "") 68 f.StringVar(&c.PlacementSpec, "to", "", "The machine and/or container to deploy the unit in (bypasses constraints)") 69 } 70 71 func (c *UnitCommandBase) Init(args []string) error { 72 if c.NumUnits < 1 { 73 return errors.New("--num-units must be a positive integer") 74 } 75 if c.PlacementSpec != "" { 76 placementSpecs := strings.Split(c.PlacementSpec, ",") 77 c.Placement = make([]*instance.Placement, len(placementSpecs)) 78 for i, spec := range placementSpecs { 79 placement, err := parsePlacement(spec) 80 if err != nil { 81 return errors.Errorf("invalid --to parameter %q", spec) 82 } 83 c.Placement[i] = placement 84 } 85 } 86 if len(c.Placement) > c.NumUnits { 87 logger.Warningf("%d unit(s) will be deployed, extra placement directives will be ignored", c.NumUnits) 88 } 89 return nil 90 } 91 92 func parsePlacement(spec string) (*instance.Placement, error) { 93 if spec == "" { 94 return nil, nil 95 } 96 placement, err := instance.ParsePlacement(spec) 97 if err == instance.ErrPlacementScopeMissing { 98 spec = "model-uuid" + ":" + spec 99 placement, err = instance.ParsePlacement(spec) 100 } 101 if err != nil { 102 return nil, errors.Errorf("invalid --to parameter %q", spec) 103 } 104 return placement, nil 105 } 106 107 // NewAddUnitCommand returns a command that adds a unit[s] to a service. 108 func NewAddUnitCommand() cmd.Command { 109 return modelcmd.Wrap(&addUnitCommand{}) 110 } 111 112 // addUnitCommand is responsible adding additional units to a service. 113 type addUnitCommand struct { 114 modelcmd.ModelCommandBase 115 UnitCommandBase 116 ServiceName string 117 api serviceAddUnitAPI 118 } 119 120 func (c *addUnitCommand) Info() *cmd.Info { 121 return &cmd.Info{ 122 Name: "add-unit", 123 Args: "<service name>", 124 Purpose: usageAddUnitSummary, 125 Doc: usageAddUnitDetails, 126 Aliases: []string{"add-units"}, 127 } 128 } 129 130 func (c *addUnitCommand) SetFlags(f *gnuflag.FlagSet) { 131 c.UnitCommandBase.SetFlags(f) 132 f.IntVar(&c.NumUnits, "n", 1, "Number of units to add") 133 } 134 135 func (c *addUnitCommand) Init(args []string) error { 136 switch len(args) { 137 case 1: 138 c.ServiceName = args[0] 139 case 0: 140 return errors.New("no service specified") 141 } 142 if err := cmd.CheckEmpty(args[1:]); err != nil { 143 return err 144 } 145 return c.UnitCommandBase.Init(args) 146 } 147 148 // serviceAddUnitAPI defines the methods on the client API 149 // that the service add-unit command calls. 150 type serviceAddUnitAPI interface { 151 Close() error 152 ModelUUID() string 153 AddUnits(service string, numUnits int, placement []*instance.Placement) ([]string, error) 154 } 155 156 func (c *addUnitCommand) getAPI() (serviceAddUnitAPI, error) { 157 if c.api != nil { 158 return c.api, nil 159 } 160 root, err := c.NewAPIRoot() 161 if err != nil { 162 return nil, errors.Trace(err) 163 } 164 return apiservice.NewClient(root), nil 165 } 166 167 // Run connects to the environment specified on the command line 168 // and calls AddUnits for the given service. 169 func (c *addUnitCommand) Run(_ *cmd.Context) error { 170 apiclient, err := c.getAPI() 171 if err != nil { 172 return err 173 } 174 defer apiclient.Close() 175 176 for i, p := range c.Placement { 177 if p.Scope == "model-uuid" { 178 p.Scope = apiclient.ModelUUID() 179 } 180 c.Placement[i] = p 181 } 182 _, err = apiclient.AddUnits(c.ServiceName, c.NumUnits, c.Placement) 183 return block.ProcessBlockedError(err, block.BlockChange) 184 } 185 186 // deployTarget describes the format a machine or container target must match to be valid. 187 const deployTarget = "^(" + names.ContainerTypeSnippet + ":)?" + names.MachineSnippet + "$" 188 189 var validMachineOrNewContainer = regexp.MustCompile(deployTarget) 190 191 // IsMachineOrNewContainer returns whether spec is a valid machine id 192 // or new container definition. 193 func IsMachineOrNewContainer(spec string) bool { 194 return validMachineOrNewContainer.MatchString(spec) 195 }