-
Notifications
You must be signed in to change notification settings - Fork 492
/
addunit.go
298 lines (244 loc) · 8.43 KB
/
addunit.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
// Copyright 2012-2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package application
import (
"regexp"
"strings"
"github.com/juju/cmd/v3"
"github.com/juju/errors"
"github.com/juju/gnuflag"
"github.com/juju/names/v5"
"github.com/juju/juju/api/client/application"
jujucmd "github.com/juju/juju/cmd"
"github.com/juju/juju/cmd/juju/application/utils"
"github.com/juju/juju/cmd/juju/block"
"github.com/juju/juju/cmd/juju/common"
"github.com/juju/juju/cmd/modelcmd"
"github.com/juju/juju/core/instance"
"github.com/juju/juju/core/model"
"github.com/juju/juju/rpc/params"
)
var usageAddUnitSummary = `Adds one or more units to a deployed application.`
var usageAddUnitDetails = `
The add-unit is used to scale out an application for improved performance or
availability.
The usage of this command differs depending on whether it is being used on a
k8s or cloud model.
Many charms will seamlessly support horizontal scaling while others may need
an additional application support (e.g. a separate load balancer). See the
documentation for specific charms to check how scale-out is supported.
For k8s models the only valid argument is -n, --num-units.
Anything additional will result in an error.
Example:
Add five units of mysql:
juju add-unit mysql --num-units 5
For cloud models, by default, units are deployed to newly provisioned machines
in accordance with any application or model constraints.
This command also supports the placement directive ("--to") for targeting
specific machines or containers, which will bypass application and model
constraints. --to accepts a comma-separated list of placement specifications
(see examples below). If the length of this list is less than the number of
units being added, the remaining units will be added in the default way (i.e.
to new machines).
`[1:]
const usageAddUnitExamples = `
Add five units of mysql on five new machines:
juju add-unit mysql -n 5
Add a unit of mysql to machine 23 (which already exists):
juju add-unit mysql --to 23
Add two units of mysql to existing machines 3 and 4:
juju add-unit mysql -n 2 --to 3,4
Add three units of mysql, one to machine 3 and the others to new
machines:
juju add-unit mysql -n 3 --to 3
Add a unit of mysql into a new LXD container on machine 7:
juju add-unit mysql --to lxd:7
Add two units of mysql into two new LXD containers on machine 7:
juju add-unit mysql -n 2 --to lxd:7,lxd:7
Add three units of mysql, one to a new LXD container on machine 7,
and the others to new machines:
juju add-unit mysql -n 3 --to lxd:7
Add a unit of mysql to LXD container number 3 on machine 24:
juju add-unit mysql --to 24/lxd/3
Add a unit of mysql to LXD container on a new machine:
juju add-unit mysql --to lxd
`
// UnitCommandBase provides support for commands which deploy units. It handles the parsing
// and validation of --to and --num-units arguments.
type UnitCommandBase struct {
// PlacementSpec is the raw string command arg value used to specify placement directives.
PlacementSpec string
// Placement is the result of parsing the PlacementSpec arg value.
Placement []*instance.Placement
NumUnits int
// AttachStorage is a list of storage IDs, identifying storage to
// attach to the unit created by deploy.
AttachStorage []string
}
func (c *UnitCommandBase) SetFlags(f *gnuflag.FlagSet) {
f.IntVar(&c.NumUnits, "num-units", 1, "")
f.StringVar(&c.PlacementSpec, "to", "", "The machine and/or container to deploy the unit in (bypasses constraints)")
f.Var(attachStorageFlag{&c.AttachStorage}, "attach-storage", "Existing storage to attach to the deployed unit (not available on k8s models)")
}
func (c *UnitCommandBase) Init(args []string) error {
if c.NumUnits < 1 {
return errors.New("--num-units must be a positive integer")
}
if len(c.AttachStorage) > 0 && c.NumUnits != 1 {
return errors.New("--attach-storage cannot be used with -n")
}
if c.PlacementSpec != "" {
placementSpecs := strings.Split(c.PlacementSpec, ",")
// Ensure that Placement length is accurate, wait for valid placements
// to add.
c.Placement = make([]*instance.Placement, 0)
for _, spec := range placementSpecs {
if spec == "" {
return errors.Errorf("invalid --to parameter %q", c.PlacementSpec)
}
placement, err := utils.ParsePlacement(spec)
if err != nil {
return errors.Errorf("invalid --to parameter %q", spec)
}
c.Placement = append(c.Placement, placement)
}
}
if len(c.Placement) > c.NumUnits {
logger.Warningf("%d unit(s) will be deployed, extra placement directives will be ignored", c.NumUnits)
}
return nil
}
// NewAddUnitCommand returns a command that adds a unit[s] to an application.
func NewAddUnitCommand() cmd.Command {
return modelcmd.Wrap(&addUnitCommand{})
}
// addUnitCommand is responsible adding additional units to an application.
type addUnitCommand struct {
modelcmd.ModelCommandBase
UnitCommandBase
ApplicationName string
api applicationAddUnitAPI
unknownModel bool
}
func (c *addUnitCommand) Info() *cmd.Info {
return jujucmd.Info(&cmd.Info{
Name: "add-unit",
Args: "<application name>",
Purpose: usageAddUnitSummary,
Doc: usageAddUnitDetails,
Examples: usageAddUnitExamples,
SeeAlso: []string{
"remove-unit",
},
})
}
func (c *addUnitCommand) SetFlags(f *gnuflag.FlagSet) {
c.UnitCommandBase.SetFlags(f)
f.IntVar(&c.NumUnits, "n", 1, "Number of units to add")
}
func (c *addUnitCommand) Init(args []string) error {
switch len(args) {
case 1:
c.ApplicationName = args[0]
case 0:
return errors.New("no application specified")
}
if err := cmd.CheckEmpty(args[1:]); err != nil {
return err
}
if err := c.validateArgsByModelType(); err != nil {
if !errors.IsNotFound(err) {
return errors.Trace(err)
}
c.unknownModel = true
}
return c.UnitCommandBase.Init(args)
}
func (c *addUnitCommand) validateArgsByModelType() error {
modelType, err := c.ModelType()
if err != nil {
return err
}
if modelType == model.CAAS {
if c.PlacementSpec != "" || len(c.AttachStorage) != 0 {
return errors.New("k8s models only support --num-units")
}
}
return nil
}
// applicationAddUnitAPI defines the methods on the client API
// that the application add-unit command calls.
type applicationAddUnitAPI interface {
Close() error
ModelUUID() string
AddUnits(application.AddUnitsParams) ([]string, error)
ScaleApplication(application.ScaleApplicationParams) (params.ScaleApplicationResult, error)
}
func (c *addUnitCommand) getAPI() (applicationAddUnitAPI, error) {
if c.api != nil {
return c.api, nil
}
root, err := c.NewAPIRoot()
if err != nil {
return nil, errors.Trace(err)
}
return application.NewClient(root), nil
}
// Run connects to the environment specified on the command line
// and calls AddUnits for the given application.
func (c *addUnitCommand) Run(ctx *cmd.Context) error {
apiclient, err := c.getAPI()
if err != nil {
return err
}
defer apiclient.Close()
if c.unknownModel {
if err := c.validateArgsByModelType(); err != nil {
return errors.Trace(err)
}
}
modelType, err := c.ModelType()
if err != nil {
return err
}
if modelType == model.CAAS {
_, err = apiclient.ScaleApplication(application.ScaleApplicationParams{
ApplicationName: c.ApplicationName,
ScaleChange: c.NumUnits,
})
if err == nil {
return nil
}
if params.IsCodeNotSupported(err) {
return errors.Annotate(err, "can not add unit")
}
if params.IsCodeUnauthorized(err) {
common.PermissionsMessage(ctx.Stderr, "scale an application")
}
return block.ProcessBlockedError(err, block.BlockChange)
}
for i, p := range c.Placement {
if p.Scope == "model-uuid" {
p.Scope = apiclient.ModelUUID()
}
c.Placement[i] = p
}
_, err = apiclient.AddUnits(application.AddUnitsParams{
ApplicationName: c.ApplicationName,
NumUnits: c.NumUnits,
Placement: c.Placement,
AttachStorage: c.AttachStorage,
})
if params.IsCodeUnauthorized(err) {
common.PermissionsMessage(ctx.Stderr, "add a unit")
}
return block.ProcessBlockedError(err, block.BlockChange)
}
// deployTarget describes the format a machine or container target must match to be valid.
const deployTarget = "^(" + names.ContainerTypeSnippet + ":)?" + names.MachineSnippet + "$"
var validMachineOrNewContainer = regexp.MustCompile(deployTarget)
// IsMachineOrNewContainer returns whether spec is a valid machine id
// or new container definition.
func IsMachineOrNewContainer(spec string) bool {
return validMachineOrNewContainer.MatchString(spec)
}