github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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.Wrap(haCommand) 37 } 38 39 // enableHACommand makes the controller highly available. 40 type enableHACommand struct { 41 modelcmd.ModelCommandBase 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. enable-ha must be called 70 to ensure that the specified number of controllers are made available. 71 72 An odd number of controllers is required. 73 74 Examples: 75 # Ensure that the controller is still in highly available mode. If 76 # there is only 1 controller running, this will ensure there 77 # are 3 running. If you have previously requested more than 3, 78 # then that number will be ensured. 79 juju enable-ha 80 81 # Ensure that 5 controllers are available. 82 juju enable-ha -n 5 83 84 # Ensure that 7 controllers are available, with newly created 85 # controller machines having at least 8GB RAM. 86 juju enable-ha -n 7 --constraints mem=8G 87 88 # Ensure that 7 controllers are available, with machines server1 and 89 # server2 used first, and if necessary, newly created controller 90 # machines having at least 8GB RAM. 91 juju enable-ha -n 7 --to server1,server2 --constraints mem=8G 92 ` 93 94 // formatSimple marshals value to a yaml-formatted []byte, unless value is nil. 95 func formatSimple(writer io.Writer, value interface{}) error { 96 enableHAResult, ok := value.(availabilityInfo) 97 if !ok { 98 return errors.Errorf("unexpected result type for enable-ha call: %T", value) 99 } 100 101 for _, machineList := range []struct { 102 message string 103 list []string 104 }{ 105 { 106 "maintaining machines: %s\n", 107 enableHAResult.Maintained, 108 }, 109 { 110 "adding machines: %s\n", 111 enableHAResult.Added, 112 }, 113 { 114 "removing machines: %s\n", 115 enableHAResult.Removed, 116 }, 117 { 118 "promoting machines: %s\n", 119 enableHAResult.Promoted, 120 }, 121 { 122 "demoting machines: %s\n", 123 enableHAResult.Demoted, 124 }, 125 { 126 "converting machines: %s\n", 127 enableHAResult.Converted, 128 }, 129 } { 130 if len(machineList.list) == 0 { 131 continue 132 } 133 _, err := fmt.Fprintf(writer, machineList.message, strings.Join(machineList.list, ", ")) 134 if err != nil { 135 return err 136 } 137 } 138 139 return nil 140 } 141 142 func (c *enableHACommand) Info() *cmd.Info { 143 return &cmd.Info{ 144 Name: "enable-ha", 145 Purpose: "Ensure that sufficient controllers exist to provide redundancy.", 146 Doc: enableHADoc, 147 } 148 } 149 150 func (c *enableHACommand) SetFlags(f *gnuflag.FlagSet) { 151 c.ModelCommandBase.SetFlags(f) 152 f.IntVar(&c.NumControllers, "n", 0, "Number of controllers to make available") 153 f.StringVar(&c.PlacementSpec, "to", "", "The machine(s) to become controllers, bypasses constraints") 154 f.StringVar(&c.ConstraintsStr, "constraints", "", "Additional machine constraints") 155 c.out.AddFlags(f, "simple", map[string]cmd.Formatter{ 156 "yaml": cmd.FormatYaml, 157 "json": cmd.FormatJson, 158 "simple": formatSimple, 159 }) 160 161 } 162 163 func (c *enableHACommand) Init(args []string) error { 164 if c.NumControllers < 0 || (c.NumControllers%2 != 1 && c.NumControllers != 0) { 165 return errors.Errorf("must specify a number of controllers odd and non-negative") 166 } 167 if c.PlacementSpec != "" { 168 placementSpecs := strings.Split(c.PlacementSpec, ",") 169 c.Placement = make([]string, len(placementSpecs)) 170 for i, spec := range placementSpecs { 171 p, err := instance.ParsePlacement(strings.TrimSpace(spec)) 172 if err == nil && names.IsContainerMachine(p.Directive) { 173 return errors.New("enable-ha cannot be used with container placement directives") 174 } 175 if err == nil && p.Scope == instance.MachineScope { 176 // Targeting machines is ok. 177 c.Placement[i] = p.String() 178 continue 179 } 180 if err != instance.ErrPlacementScopeMissing { 181 return errors.Errorf("unsupported enable-ha placement directive %q", spec) 182 } 183 c.Placement[i] = spec 184 } 185 } 186 return cmd.CheckEmpty(args) 187 } 188 189 type availabilityInfo struct { 190 Maintained []string `json:"maintained,omitempty" yaml:"maintained,flow,omitempty"` 191 Removed []string `json:"removed,omitempty" yaml:"removed,flow,omitempty"` 192 Added []string `json:"added,omitempty" yaml:"added,flow,omitempty"` 193 Promoted []string `json:"promoted,omitempty" yaml:"promoted,flow,omitempty"` 194 Demoted []string `json:"demoted,omitempty" yaml:"demoted,flow,omitempty"` 195 Converted []string `json:"converted,omitempty" yaml:"converted,flow,omitempty"` 196 } 197 198 // MakeHAClient defines the methods 199 // on the client api that the ensure availability 200 // command calls. 201 type MakeHAClient interface { 202 Close() error 203 EnableHA( 204 numControllers int, cons constraints.Value, 205 placement []string) (params.ControllersChanges, error) 206 } 207 208 // Run connects to the environment specified on the command line 209 // and calls EnableHA. 210 func (c *enableHACommand) Run(ctx *cmd.Context) error { 211 var err error 212 c.Constraints, err = common.ParseConstraints(ctx, c.ConstraintsStr) 213 if err != nil { 214 return err 215 } 216 haClient, err := c.newHAClientFunc() 217 if err != nil { 218 return err 219 } 220 221 defer haClient.Close() 222 enableHAResult, err := haClient.EnableHA( 223 c.NumControllers, 224 c.Constraints, 225 c.Placement, 226 ) 227 if err != nil { 228 return block.ProcessBlockedError(err, block.BlockChange) 229 } 230 231 result := availabilityInfo{ 232 Added: machineTagsToIds(enableHAResult.Added...), 233 Removed: machineTagsToIds(enableHAResult.Removed...), 234 Maintained: machineTagsToIds(enableHAResult.Maintained...), 235 Promoted: machineTagsToIds(enableHAResult.Promoted...), 236 Demoted: machineTagsToIds(enableHAResult.Demoted...), 237 Converted: machineTagsToIds(enableHAResult.Converted...), 238 } 239 return c.out.Write(ctx, result) 240 } 241 242 // Convert machine tags to ids, skipping any non-machine tags. 243 func machineTagsToIds(tags ...string) []string { 244 var result []string 245 246 for _, rawTag := range tags { 247 tag, err := names.ParseTag(rawTag) 248 if err != nil { 249 continue 250 } 251 result = append(result, tag.Id()) 252 } 253 return result 254 }