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