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