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