func parsePlacement(spec string) (*instance.Placement, error) { placement, err := instance.ParsePlacement(spec) if err == instance.ErrPlacementScopeMissing { spec = "env-uuid" + ":" + spec placement, err = instance.ParsePlacement(spec) } if err != nil { return nil, errors.Errorf("invalid --to parameter %q", spec) } return placement, nil }
func (c *ensureAvailabilityCommand) Init(args []string) error { if c.NumStateServers < 0 || (c.NumStateServers%2 != 1 && c.NumStateServers != 0) { return fmt.Errorf("must specify a number of state servers odd and non-negative") } if c.PlacementSpec != "" { placementSpecs := strings.Split(c.PlacementSpec, ",") c.Placement = make([]string, len(placementSpecs)) for i, spec := range placementSpecs { p, err := instance.ParsePlacement(strings.TrimSpace(spec)) if err == nil && names.IsContainerMachine(p.Directive) { return errors.New("ensure-availability cannot be used with container placement directives") } if err == nil && p.Scope == instance.MachineScope { // Targeting machines is ok. c.Placement[i] = p.String() continue } if err != instance.ErrPlacementScopeMissing { return fmt.Errorf("unsupported ensure-availability placement directive %q", spec) } c.Placement[i] = spec } } return cmd.CheckEmpty(args) }
func (c *BootstrapCommand) Init(args []string) (err error) { if len(c.Series) > 0 && !c.UploadTools { return fmt.Errorf("--upload-series requires --upload-tools") } if len(c.seriesOld) > 0 && !c.UploadTools { return fmt.Errorf("--series requires --upload-tools") } if len(c.Series) > 0 && len(c.seriesOld) > 0 { return fmt.Errorf("--upload-series and --series can't be used together") } if len(c.seriesOld) > 0 { c.Series = c.seriesOld } // Parse the placement directive. Bootstrap currently only // supports provider-specific placement directives. if c.Placement != "" { _, err = instance.ParsePlacement(c.Placement) if err != instance.ErrPlacementScopeMissing { // We only support unscoped placement directives for bootstrap. return fmt.Errorf("unsupported bootstrap placement directive %q", c.Placement) } } return cmd.CheckEmpty(args) }
func (c *bootstrapCommand) Init(args []string) (err error) { if c.showClouds && c.showRegionsForCloud != "" { return errors.New("--clouds and --regions can't be used together") } if c.showClouds { return cmd.CheckEmpty(args) } if c.showRegionsForCloud != "" { return cmd.CheckEmpty(args) } if c.AgentVersionParam != "" && c.BuildAgent { return errors.New("--agent-version and --build-agent can't be used together") } if c.BootstrapSeries != "" && !charm.IsValidSeries(c.BootstrapSeries) { return errors.NotValidf("series %q", c.BootstrapSeries) } // Parse the placement directive. Bootstrap currently only // supports provider-specific placement directives. if c.Placement != "" { _, err = instance.ParsePlacement(c.Placement) if err != instance.ErrPlacementScopeMissing { // We only support unscoped placement directives for bootstrap. return errors.Errorf("unsupported bootstrap placement directive %q", c.Placement) } } if !c.AutoUpgrade { // With no auto upgrade chosen, we default to the version matching the bootstrap client. vers := jujuversion.Current c.AgentVersion = &vers } if c.AgentVersionParam != "" { if vers, err := version.ParseBinary(c.AgentVersionParam); err == nil { c.AgentVersion = &vers.Number } else if vers, err := version.Parse(c.AgentVersionParam); err == nil { c.AgentVersion = &vers } else { return err } } if c.AgentVersion != nil && (c.AgentVersion.Major != jujuversion.Current.Major || c.AgentVersion.Minor != jujuversion.Current.Minor) { return errors.New("requested agent version major.minor mismatch") } switch len(args) { case 0: // no args or flags, go interactive. c.interactive = true return nil } c.Cloud = args[0] if i := strings.IndexRune(c.Cloud, '/'); i > 0 { c.Cloud, c.Region = c.Cloud[:i], c.Cloud[i+1:] } if len(args) > 1 { c.controllerName = args[1] return cmd.CheckEmpty(args[2:]) } return nil }
func (c *bootstrapCommand) Init(args []string) (err error) { if c.AgentVersionParam != "" && c.UploadTools { return fmt.Errorf("--agent-version and --upload-tools can't be used together") } if c.BootstrapSeries != "" && !charm.IsValidSeries(c.BootstrapSeries) { return errors.NotValidf("series %q", c.BootstrapSeries) } if c.BootstrapImage != "" { if c.BootstrapSeries == "" { return errors.Errorf("--bootstrap-image must be used with --bootstrap-series") } cons, err := constraints.Merge(c.Constraints, c.BootstrapConstraints) if err != nil { return errors.Trace(err) } if !cons.HasArch() { return errors.Errorf("--bootstrap-image must be used with --bootstrap-constraints, specifying architecture") } } // Parse the placement directive. Bootstrap currently only // supports provider-specific placement directives. if c.Placement != "" { _, err = instance.ParsePlacement(c.Placement) if err != instance.ErrPlacementScopeMissing { // We only support unscoped placement directives for bootstrap. return fmt.Errorf("unsupported bootstrap placement directive %q", c.Placement) } } if !c.AutoUpgrade { // With no auto upgrade chosen, we default to the version matching the bootstrap client. vers := jujuversion.Current c.AgentVersion = &vers } if c.AgentVersionParam != "" { if vers, err := version.ParseBinary(c.AgentVersionParam); err == nil { c.AgentVersion = &vers.Number } else if vers, err := version.Parse(c.AgentVersionParam); err == nil { c.AgentVersion = &vers } else { return err } } if c.AgentVersion != nil && (c.AgentVersion.Major != jujuversion.Current.Major || c.AgentVersion.Minor != jujuversion.Current.Minor) { return fmt.Errorf("requested agent version major.minor mismatch") } // The user must specify two positional arguments: the controller name, // and the cloud name (optionally with region specified). if len(args) < 2 { return errors.New("controller name and cloud name are required") } c.controllerName = bootstrappedControllerName(args[0]) c.Cloud = args[1] if i := strings.IndexRune(c.Cloud, '/'); i > 0 { c.Cloud, c.Region = c.Cloud[:i], c.Cloud[i+1:] } return cmd.CheckEmpty(args[2:]) }
func (c *AddMachineCommand) Init(args []string) error { if c.Constraints.Container != nil { return fmt.Errorf("container constraint %q not allowed when adding a machine", *c.Constraints.Container) } placement, err := cmd.ZeroOrOneArgs(args) if err != nil { return err } c.Placement, err = instance.ParsePlacement(placement) if err == instance.ErrPlacementScopeMissing { placement = c.EnvName + ":" + placement c.Placement, err = instance.ParsePlacement(placement) } if err != nil { return err } return nil }
func (c *addCommand) Init(args []string) error { if c.Constraints.Container != nil { return fmt.Errorf("container constraint %q not allowed when adding a machine", *c.Constraints.Container) } placement, err := cmd.ZeroOrOneArgs(args) if err != nil { return err } c.Placement, err = instance.ParsePlacement(placement) if err == instance.ErrPlacementScopeMissing { placement = "model-uuid" + ":" + placement c.Placement, err = instance.ParsePlacement(placement) } if err != nil { return err } if c.NumMachines > 1 && c.Placement != nil && c.Placement.Directive != "" { return fmt.Errorf("cannot use -n when specifying a placement directive") } return nil }
func (c *bootstrapCommand) Init(args []string) (err error) { if c.AgentVersionParam != "" && c.UploadTools { return fmt.Errorf("--agent-version and --upload-tools can't be used together") } if c.BootstrapSeries != "" && !charm.IsValidSeries(c.BootstrapSeries) { return errors.NotValidf("series %q", c.BootstrapSeries) } if c.BootstrapImage != "" { if c.BootstrapSeries == "" { return errors.Errorf("--bootstrap-image must be used with --bootstrap-series") } cons, err := constraints.Merge(c.Constraints, c.BootstrapConstraints) if err != nil { return errors.Trace(err) } if !cons.HasArch() { return errors.Errorf("--bootstrap-image must be used with --bootstrap-constraints, specifying architecture") } } // Parse the placement directive. Bootstrap currently only // supports provider-specific placement directives. if c.Placement != "" { _, err = instance.ParsePlacement(c.Placement) if err != instance.ErrPlacementScopeMissing { // We only support unscoped placement directives for bootstrap. return fmt.Errorf("unsupported bootstrap placement directive %q", c.Placement) } } if !c.AutoUpgrade { // With no auto upgrade chosen, we default to the version matching the bootstrap client. vers := version.Current c.AgentVersion = &vers } if c.AgentVersionParam != "" { if vers, err := version.ParseBinary(c.AgentVersionParam); err == nil { c.AgentVersion = &vers.Number } else if vers, err := version.Parse(c.AgentVersionParam); err == nil { c.AgentVersion = &vers } else { return err } } if c.AgentVersion != nil && (c.AgentVersion.Major != version.Current.Major || c.AgentVersion.Minor != version.Current.Minor) { return fmt.Errorf("requested agent version major.minor mismatch") } return cmd.CheckEmpty(args) }
func (c *BootstrapCommand) Init(args []string) (err error) { if len(c.Series) > 0 && !c.UploadTools { return fmt.Errorf("--upload-series requires --upload-tools") } if len(c.seriesOld) > 0 && !c.UploadTools { return fmt.Errorf("--series requires --upload-tools") } if len(c.Series) > 0 && len(c.seriesOld) > 0 { return fmt.Errorf("--upload-series and --series can't be used together") } if c.AgentVersionParam != "" && c.UploadTools { return fmt.Errorf("--agent-version and --upload-tools can't be used together") } if c.AgentVersionParam != "" && c.NoAutoUpgrade { return fmt.Errorf("--agent-version and --no-auto-upgrade can't be used together") } // Parse the placement directive. Bootstrap currently only // supports provider-specific placement directives. if c.Placement != "" { _, err = instance.ParsePlacement(c.Placement) if err != instance.ErrPlacementScopeMissing { // We only support unscoped placement directives for bootstrap. return fmt.Errorf("unsupported bootstrap placement directive %q", c.Placement) } } if c.NoAutoUpgrade { vers := version.Current.Number c.AgentVersion = &vers } else if c.AgentVersionParam != "" { if vers, err := version.ParseBinary(c.AgentVersionParam); err == nil { c.AgentVersion = &vers.Number } else if vers, err := version.Parse(c.AgentVersionParam); err == nil { c.AgentVersion = &vers } else { return err } } if c.AgentVersion != nil && (c.AgentVersion.Major != version.Current.Major || c.AgentVersion.Minor != version.Current.Minor) { return fmt.Errorf("requested agent version major.minor mismatch") } return cmd.CheckEmpty(args) }
func (f *fakeHAClient) EnsureAvailability(numStateServers int, cons constraints.Value, series string, placement []string) (params.StateServersChanges, error) { f.numStateServers = numStateServers f.cons = cons f.series = series f.placement = placement if f.err != nil { return f.result, f.err } if numStateServers == 1 { return f.result, nil } // In the real HAClient, specifying a numStateServers value of 0 // indicates that the default value (3) should be used if numStateServers == 0 { numStateServers = 3 } f.result.Maintained = append(f.result.Maintained, "machine-0") for _, p := range placement { m, err := instance.ParsePlacement(p) if err == nil && m.Scope == instance.MachineScope { f.result.Converted = append(f.result.Converted, "machine-"+m.Directive) } } // We may need to pretend that we added some machines. for i := len(f.result.Converted) + 1; i < numStateServers; i++ { f.result.Added = append(f.result.Added, fmt.Sprintf("machine-%d", i)) } return f.result, nil }
// ensureAvailabilityIntentions returns what we would like // to do to maintain the availability of the existing servers // mentioned in the given info, including: // demoting unavailable, voting machines; // removing unavailable, non-voting, non-vote-holding machines; // gathering available, non-voting machines that may be promoted; func (st *State) ensureAvailabilityIntentions(info *StateServerInfo, placement []string) (*ensureAvailabilityIntent, error) { var intent ensureAvailabilityIntent for _, s := range placement { // TODO(natefinch): unscoped placements shouldn't ever get here (though // they do currently). We should fix up the CLI to always add a scope // to placements and then we can remove the need to deal with unscoped // placements. p, err := instance.ParsePlacement(s) if err == instance.ErrPlacementScopeMissing { intent.placement = append(intent.placement, s) continue } if err == nil && p.Scope == instance.MachineScope { // TODO(natefinch) add env provider policy to check if conversion is // possible (e.g. cannot be supported by Azure in HA mode). if names.IsContainerMachine(p.Directive) { return nil, errors.New("container placement directives not supported") } m, err := st.Machine(p.Directive) if err != nil { return nil, errors.Annotatef(err, "can't find machine for placement directive %q", s) } if m.IsManager() { return nil, errors.Errorf("machine for placement directive %q is already a state server", s) } intent.convert = append(intent.convert, m) intent.placement = append(intent.placement, s) continue } return nil, errors.Errorf("unsupported placement directive %q", s) } for _, mid := range info.MachineIds { m, err := st.Machine(mid) if err != nil { return nil, err } available, err := stateServerAvailable(m) if err != nil { return nil, err } logger.Infof("machine %q, available %v, wants vote %v, has vote %v", m, available, m.WantsVote(), m.HasVote()) if available { if m.WantsVote() { intent.maintain = append(intent.maintain, m) } else { intent.promote = append(intent.promote, m) } continue } if m.WantsVote() { // The machine wants to vote, so we simply set novote and allow it // to run its course to have its vote removed by the worker that // maintains the replicaset. We will replace it with an existing // non-voting state server if there is one, starting a new one if // not. intent.demote = append(intent.demote, m) } else if m.HasVote() { // The machine still has a vote, so keep it around for now. intent.maintain = append(intent.maintain, m) } else { // The machine neither wants to nor has a vote, so remove its // JobManageEnviron job immediately. intent.remove = append(intent.remove, m) } } logger.Infof("initial intentions: promote %v; maintain %v; demote %v; remove %v; convert: %v", intent.promote, intent.maintain, intent.demote, intent.remove, intent.convert) return &intent, nil }
func (s *PlacementSuite) TestParsePlacement(c *gc.C) { parsePlacementTests := []struct { arg string expectScope, expectDirective string err string }{{ arg: "", }, { arg: "0", expectScope: instance.MachineScope, expectDirective: "0", }, { arg: "0/lxc/0", expectScope: instance.MachineScope, expectDirective: "0/lxc/0", }, { arg: "#:x", err: `invalid value "x" for "#" scope: expected machine-id`, }, { arg: "lxc:x", err: `invalid value "x" for "lxc" scope: expected machine-id`, }, { arg: "kvm:x", err: `invalid value "x" for "kvm" scope: expected machine-id`, }, { arg: "kvm:123", expectScope: string(instance.KVM), expectDirective: "123", }, { arg: "lxc", expectScope: string(instance.LXC), }, { arg: "non-standard", err: "placement scope missing", }, { arg: ":non-standard", err: "placement scope missing", }, { arg: "non:standard", expectScope: "non", expectDirective: "standard", }} for i, t := range parsePlacementTests { c.Logf("test %d: %s", i, t.arg) p, err := instance.ParsePlacement(t.arg) if t.err != "" { c.Assert(err, gc.ErrorMatches, t.err) } else { c.Assert(err, jc.ErrorIsNil) } if t.expectScope == "" && t.expectDirective == "" { c.Assert(p, gc.IsNil) } else { c.Assert(p, gc.DeepEquals, &instance.Placement{ Scope: t.expectScope, Directive: t.expectDirective, }) } } }