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