~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/cmd/juju/commands/enableha.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2015 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package commands
 
5
 
 
6
import (
 
7
        "bytes"
 
8
        "fmt"
 
9
        "strings"
 
10
 
 
11
        "github.com/juju/cmd"
 
12
        "github.com/juju/errors"
 
13
        "gopkg.in/juju/names.v2"
 
14
        "launchpad.net/gnuflag"
 
15
 
 
16
        "github.com/juju/juju/api/highavailability"
 
17
        "github.com/juju/juju/apiserver/params"
 
18
        "github.com/juju/juju/cmd/juju/block"
 
19
        "github.com/juju/juju/cmd/modelcmd"
 
20
        "github.com/juju/juju/constraints"
 
21
        "github.com/juju/juju/instance"
 
22
)
 
23
 
 
24
func newEnableHACommand() cmd.Command {
 
25
        haCommand := &enableHACommand{}
 
26
        haCommand.newHAClientFunc = func() (MakeHAClient, error) {
 
27
                root, err := haCommand.NewAPIRoot()
 
28
                if err != nil {
 
29
                        return nil, errors.Annotate(err, "cannot get API connection")
 
30
                }
 
31
 
 
32
                // NewClient does not return an error, so we'll return nil
 
33
                return highavailability.NewClient(root), nil
 
34
        }
 
35
        return modelcmd.Wrap(haCommand)
 
36
}
 
37
 
 
38
// enableHACommand makes the controller highly available.
 
39
type enableHACommand struct {
 
40
        modelcmd.ModelCommandBase
 
41
        out cmd.Output
 
42
 
 
43
        // newHAClientFunc returns HA Client to be used by the command.
 
44
        newHAClientFunc func() (MakeHAClient, error)
 
45
 
 
46
        // NumControllers specifies the number of controllers to make available.
 
47
        NumControllers int
 
48
 
 
49
        // Constraints, if specified, will be merged with those already
 
50
        // in the environment when creating new machines.
 
51
        Constraints constraints.Value
 
52
 
 
53
        // Placement specifies specific machine(s) which will be used to host
 
54
        // new controllers. If there are more controllers required than
 
55
        // machines specified, new machines will be created.
 
56
        // Placement is passed verbatim to the API, to be evaluated and used server-side.
 
57
        Placement []string
 
58
 
 
59
        // PlacementSpec holds the unparsed placement directives argument (--to).
 
60
        PlacementSpec string
 
61
}
 
62
 
 
63
const enableHADoc = `
 
64
To ensure availability of deployed applications, the Juju infrastructure
 
65
must itself be highly available.  enable-ha must be called
 
66
to ensure that the specified number of controllers are made available.
 
67
 
 
68
An odd number of controllers is required.
 
69
 
 
70
Examples:
 
71
    # Ensure that the controller is still in highly available mode. If
 
72
    # there is only 1 controller running, this will ensure there
 
73
    # are 3 running. If you have previously requested more than 3,
 
74
    # then that number will be ensured.
 
75
    juju enable-ha
 
76
 
 
77
    # Ensure that 5 controllers are available.
 
78
    juju enable-ha -n 5 
 
79
 
 
80
    # Ensure that 7 controllers are available, with newly created
 
81
    # controller machines having at least 8GB RAM.
 
82
    juju enable-ha -n 7 --constraints mem=8G
 
83
 
 
84
    # Ensure that 7 controllers are available, with machines server1 and
 
85
    # server2 used first, and if necessary, newly created controller
 
86
    # machines having at least 8GB RAM.
 
87
    juju enable-ha -n 7 --to server1,server2 --constraints mem=8G
 
88
`
 
89
 
 
90
// formatSimple marshals value to a yaml-formatted []byte, unless value is nil.
 
91
func formatSimple(value interface{}) ([]byte, error) {
 
92
        enableHAResult, ok := value.(availabilityInfo)
 
93
        if !ok {
 
94
                return nil, fmt.Errorf("unexpected result type for enable-ha call: %T", value)
 
95
        }
 
96
 
 
97
        var buf bytes.Buffer
 
98
 
 
99
        for _, machineList := range []struct {
 
100
                message string
 
101
                list    []string
 
102
        }{
 
103
                {
 
104
                        "maintaining machines: %s\n",
 
105
                        enableHAResult.Maintained,
 
106
                },
 
107
                {
 
108
                        "adding machines: %s\n",
 
109
                        enableHAResult.Added,
 
110
                },
 
111
                {
 
112
                        "removing machines: %s\n",
 
113
                        enableHAResult.Removed,
 
114
                },
 
115
                {
 
116
                        "promoting machines: %s\n",
 
117
                        enableHAResult.Promoted,
 
118
                },
 
119
                {
 
120
                        "demoting machines: %s\n",
 
121
                        enableHAResult.Demoted,
 
122
                },
 
123
                {
 
124
                        "converting machines: %s\n",
 
125
                        enableHAResult.Converted,
 
126
                },
 
127
        } {
 
128
                if len(machineList.list) == 0 {
 
129
                        continue
 
130
                }
 
131
                _, err := fmt.Fprintf(&buf, machineList.message, strings.Join(machineList.list, ", "))
 
132
                if err != nil {
 
133
                        return nil, err
 
134
                }
 
135
        }
 
136
 
 
137
        return buf.Bytes(), nil
 
138
}
 
139
 
 
140
func (c *enableHACommand) Info() *cmd.Info {
 
141
        return &cmd.Info{
 
142
                Name:    "enable-ha",
 
143
                Purpose: "Ensure that sufficient controllers exist to provide redundancy.",
 
144
                Doc:     enableHADoc,
 
145
        }
 
146
}
 
147
 
 
148
func (c *enableHACommand) SetFlags(f *gnuflag.FlagSet) {
 
149
        f.IntVar(&c.NumControllers, "n", 0, "Number of controllers to make available")
 
150
        f.StringVar(&c.PlacementSpec, "to", "", "The machine(s) to become controllers, bypasses constraints")
 
151
        f.Var(constraints.ConstraintsValue{&c.Constraints}, "constraints", "Additional machine constraints")
 
152
        c.out.AddFlags(f, "simple", map[string]cmd.Formatter{
 
153
                "yaml":   cmd.FormatYaml,
 
154
                "json":   cmd.FormatJson,
 
155
                "simple": formatSimple,
 
156
        })
 
157
 
 
158
}
 
159
 
 
160
func (c *enableHACommand) Init(args []string) error {
 
161
        if c.NumControllers < 0 || (c.NumControllers%2 != 1 && c.NumControllers != 0) {
 
162
                return fmt.Errorf("must specify a number of controllers odd and non-negative")
 
163
        }
 
164
        if c.PlacementSpec != "" {
 
165
                placementSpecs := strings.Split(c.PlacementSpec, ",")
 
166
                c.Placement = make([]string, len(placementSpecs))
 
167
                for i, spec := range placementSpecs {
 
168
                        p, err := instance.ParsePlacement(strings.TrimSpace(spec))
 
169
                        if err == nil && names.IsContainerMachine(p.Directive) {
 
170
                                return errors.New("enable-ha cannot be used with container placement directives")
 
171
                        }
 
172
                        if err == nil && p.Scope == instance.MachineScope {
 
173
                                // Targeting machines is ok.
 
174
                                c.Placement[i] = p.String()
 
175
                                continue
 
176
                        }
 
177
                        if err != instance.ErrPlacementScopeMissing {
 
178
                                return fmt.Errorf("unsupported enable-ha placement directive %q", spec)
 
179
                        }
 
180
                        c.Placement[i] = spec
 
181
                }
 
182
        }
 
183
        return cmd.CheckEmpty(args)
 
184
}
 
185
 
 
186
type availabilityInfo struct {
 
187
        Maintained []string `json:"maintained,omitempty" yaml:"maintained,flow,omitempty"`
 
188
        Removed    []string `json:"removed,omitempty" yaml:"removed,flow,omitempty"`
 
189
        Added      []string `json:"added,omitempty" yaml:"added,flow,omitempty"`
 
190
        Promoted   []string `json:"promoted,omitempty" yaml:"promoted,flow,omitempty"`
 
191
        Demoted    []string `json:"demoted,omitempty" yaml:"demoted,flow,omitempty"`
 
192
        Converted  []string `json:"converted,omitempty" yaml:"converted,flow,omitempty"`
 
193
}
 
194
 
 
195
// MakeHAClient defines the methods
 
196
// on the client api that the ensure availability
 
197
// command calls.
 
198
type MakeHAClient interface {
 
199
        Close() error
 
200
        EnableHA(
 
201
                numControllers int, cons constraints.Value,
 
202
                placement []string) (params.ControllersChanges, error)
 
203
}
 
204
 
 
205
// Run connects to the environment specified on the command line
 
206
// and calls EnableHA.
 
207
func (c *enableHACommand) Run(ctx *cmd.Context) error {
 
208
        haClient, err := c.newHAClientFunc()
 
209
        if err != nil {
 
210
                return err
 
211
        }
 
212
 
 
213
        defer haClient.Close()
 
214
        enableHAResult, err := haClient.EnableHA(
 
215
                c.NumControllers,
 
216
                c.Constraints,
 
217
                c.Placement,
 
218
        )
 
219
        if err != nil {
 
220
                return block.ProcessBlockedError(err, block.BlockChange)
 
221
        }
 
222
 
 
223
        result := availabilityInfo{
 
224
                Added:      machineTagsToIds(enableHAResult.Added...),
 
225
                Removed:    machineTagsToIds(enableHAResult.Removed...),
 
226
                Maintained: machineTagsToIds(enableHAResult.Maintained...),
 
227
                Promoted:   machineTagsToIds(enableHAResult.Promoted...),
 
228
                Demoted:    machineTagsToIds(enableHAResult.Demoted...),
 
229
                Converted:  machineTagsToIds(enableHAResult.Converted...),
 
230
        }
 
231
        return c.out.Write(ctx, result)
 
232
}
 
233
 
 
234
// Convert machine tags to ids, skipping any non-machine tags.
 
235
func machineTagsToIds(tags ...string) []string {
 
236
        var result []string
 
237
 
 
238
        for _, rawTag := range tags {
 
239
                tag, err := names.ParseTag(rawTag)
 
240
                if err != nil {
 
241
                        continue
 
242
                }
 
243
                result = append(result, tag.Id())
 
244
        }
 
245
        return result
 
246
}