1
// Copyright 2015 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
12
"github.com/juju/errors"
13
"gopkg.in/juju/names.v2"
14
"launchpad.net/gnuflag"
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"
24
func newEnableHACommand() cmd.Command {
25
haCommand := &enableHACommand{}
26
haCommand.newHAClientFunc = func() (MakeHAClient, error) {
27
root, err := haCommand.NewAPIRoot()
29
return nil, errors.Annotate(err, "cannot get API connection")
32
// NewClient does not return an error, so we'll return nil
33
return highavailability.NewClient(root), nil
35
return modelcmd.Wrap(haCommand)
38
// enableHACommand makes the controller highly available.
39
type enableHACommand struct {
40
modelcmd.ModelCommandBase
43
// newHAClientFunc returns HA Client to be used by the command.
44
newHAClientFunc func() (MakeHAClient, error)
46
// NumControllers specifies the number of controllers to make available.
49
// Constraints, if specified, will be merged with those already
50
// in the environment when creating new machines.
51
Constraints constraints.Value
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.
59
// PlacementSpec holds the unparsed placement directives argument (--to).
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.
68
An odd number of controllers is required.
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.
77
# Ensure that 5 controllers are available.
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
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
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)
94
return nil, fmt.Errorf("unexpected result type for enable-ha call: %T", value)
99
for _, machineList := range []struct {
104
"maintaining machines: %s\n",
105
enableHAResult.Maintained,
108
"adding machines: %s\n",
109
enableHAResult.Added,
112
"removing machines: %s\n",
113
enableHAResult.Removed,
116
"promoting machines: %s\n",
117
enableHAResult.Promoted,
120
"demoting machines: %s\n",
121
enableHAResult.Demoted,
124
"converting machines: %s\n",
125
enableHAResult.Converted,
128
if len(machineList.list) == 0 {
131
_, err := fmt.Fprintf(&buf, machineList.message, strings.Join(machineList.list, ", "))
137
return buf.Bytes(), nil
140
func (c *enableHACommand) Info() *cmd.Info {
143
Purpose: "Ensure that sufficient controllers exist to provide redundancy.",
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,
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")
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")
172
if err == nil && p.Scope == instance.MachineScope {
173
// Targeting machines is ok.
174
c.Placement[i] = p.String()
177
if err != instance.ErrPlacementScopeMissing {
178
return fmt.Errorf("unsupported enable-ha placement directive %q", spec)
180
c.Placement[i] = spec
183
return cmd.CheckEmpty(args)
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"`
195
// MakeHAClient defines the methods
196
// on the client api that the ensure availability
198
type MakeHAClient interface {
201
numControllers int, cons constraints.Value,
202
placement []string) (params.ControllersChanges, error)
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()
213
defer haClient.Close()
214
enableHAResult, err := haClient.EnableHA(
220
return block.ProcessBlockedError(err, block.BlockChange)
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...),
231
return c.out.Write(ctx, result)
234
// Convert machine tags to ids, skipping any non-machine tags.
235
func machineTagsToIds(tags ...string) []string {
238
for _, rawTag := range tags {
239
tag, err := names.ParseTag(rawTag)
243
result = append(result, tag.Id())