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