Skip to content

Commit

Permalink
Add ability to override max_in_flight for bosh create-recovery-plan
Browse files Browse the repository at this point in the history
The current value of `max_in_flight` is fetched from the director and
used as a default.  If the value does not change from the default, no
`max_in_flight_override` is written to the recovery plan.

[#185483613]

Authored-by: Chris Selzo <[email protected]>
  • Loading branch information
selzoc committed Jul 3, 2023
1 parent cecc52c commit c13b006
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 5 deletions.
77 changes: 72 additions & 5 deletions cmd/create_recovery_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"fmt"
"sort"
"strconv"

"gopkg.in/yaml.v2"

Expand All @@ -17,9 +18,9 @@ import (
)

type InstanceGroupPlan struct {
Name string `yaml:"name"`
MaxInFlight string `yaml:"max_in_flight,omitempty"`
PlannedResolutions map[string]string `yaml:"planned_resolutions"`
Name string `yaml:"name"`
MaxInFlightOverride string `yaml:"max_in_flight_override,omitempty"`
PlannedResolutions map[string]string `yaml:"planned_resolutions"`
}

type RecoveryPlan struct {
Expand Down Expand Up @@ -47,6 +48,11 @@ func (c CreateRecoveryPlanCmd) Run(opts CreateRecoveryPlanOpts) error {
return nil
}

maxInFlightByInstanceGroup, err := c.getMaxInFlightByInstanceGroup()
if err != nil {
return err
}

var plan RecoveryPlan
for _, instanceGroup := range sortedInstanceGroups(problemsByInstanceGroup) {
c.ui.PrintLinef("Instance Group '%s'\n", instanceGroup)
Expand All @@ -56,9 +62,24 @@ func (c CreateRecoveryPlanCmd) Run(opts CreateRecoveryPlanOpts) error {
return err
}

instanceGroupCurrentMaxInFlight := maxInFlightByInstanceGroup[instanceGroup]
var instanceGroupMaxInFlightOverride string
if c.ui.AskForConfirmationWithLabel(
fmt.Sprintf("Override current max_in_flight value of '%s'?", instanceGroupCurrentMaxInFlight),
) == nil {
instanceGroupMaxInFlightOverride, err = c.ui.AskForTextWithDefaultValue(
fmt.Sprintf("max_in_flight override for '%s'", instanceGroup),
instanceGroupCurrentMaxInFlight,
)
if err != nil {
return err
}
}

plan.InstanceGroupsPlan = append(plan.InstanceGroupsPlan, InstanceGroupPlan{
Name: instanceGroup,
PlannedResolutions: instanceGroupResolutions,
Name: instanceGroup,
MaxInFlightOverride: instanceGroupMaxInFlightOverride,
PlannedResolutions: instanceGroupResolutions,
})
}

Expand All @@ -70,6 +91,52 @@ func (c CreateRecoveryPlanCmd) Run(opts CreateRecoveryPlanOpts) error {
return c.fs.WriteFile(opts.Args.RecoveryPlan.ExpandedPath, bytes)
}

type updateInstanceGroup struct {
Name string `yaml:"name"`
Update map[string]interface{} `yaml:"update"`
}

type updateManifest struct {
InstanceGroups []updateInstanceGroup `yaml:"instance_groups"`
Update map[string]interface{} `yaml:"update"`
}

func (c CreateRecoveryPlanCmd) getMaxInFlightByInstanceGroup() (map[string]string, error) {
rawManifest, err := c.deployment.Manifest()
if err != nil {
return nil, err
}

var updateManifest updateManifest
err = yaml.Unmarshal([]byte(rawManifest), &updateManifest)
if err != nil {
return nil, err
}

globalMaxInFlight := updateManifest.Update["max_in_flight"]
flightMap := make(map[string]string)
for _, instanceGroup := range updateManifest.InstanceGroups {
groupMaxInFlight := instanceGroup.Update["max_in_flight"]
if groupMaxInFlight == nil {
groupMaxInFlight = globalMaxInFlight
}
flightMap[instanceGroup.Name] = ensureString(groupMaxInFlight)
}

return flightMap, nil
}

func ensureString(i interface{}) string {
switch v := i.(type) {
case int:
return strconv.Itoa(v)
case string:
return v
}

return i.(string)
}

func sortedInstanceGroups(problemsByInstanceGroup map[string][]boshdir.Problem) []string {
var instanceGroups []string
for k := range problemsByInstanceGroup {
Expand Down
30 changes: 30 additions & 0 deletions cmd/create_recovery_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ var _ = Describe("CreateRecoveryPlanCmd", func() {
deployment.ScanForProblemsReturns(severalProbs, nil)
ui.AskedChoiceChosens = []int{0, 1, 2}
ui.AskedChoiceErrs = []error{nil, nil, nil}
ui.AskedConfirmationErr = nil
ui.AskedText = []fakeui.Answer{
{Text: "10", Error: nil},
{Text: "50%", Error: nil},
}
})

It("shows problems by instance group and type", func() {
Expand Down Expand Up @@ -239,10 +244,12 @@ var _ = Describe("CreateRecoveryPlanCmd", func() {
Expect(actualPlan.InstanceGroupsPlan).To(HaveLen(2))

Expect(actualPlan.InstanceGroupsPlan[0].Name).To(Equal("diego_cell"))
Expect(actualPlan.InstanceGroupsPlan[0].MaxInFlightOverride).To(Equal("10"))
Expect(actualPlan.InstanceGroupsPlan[0].PlannedResolutions).To(HaveLen(1))
Expect(actualPlan.InstanceGroupsPlan[0].PlannedResolutions).To(HaveKeyWithValue("unresponsive_agent", *skipResolution.Name))

Expect(actualPlan.InstanceGroupsPlan[1].Name).To(Equal("router"))
Expect(actualPlan.InstanceGroupsPlan[1].MaxInFlightOverride).To(Equal("50%"))
Expect(actualPlan.InstanceGroupsPlan[1].PlannedResolutions).To(HaveLen(2))
Expect(actualPlan.InstanceGroupsPlan[1].PlannedResolutions).To(HaveKeyWithValue("missing_vm", *recreateResolution.Name))
Expect(actualPlan.InstanceGroupsPlan[1].PlannedResolutions).To(HaveKeyWithValue("mount_info_mismatch", *reattachDiskAndRebootResolution.Name))
Expand All @@ -256,6 +263,29 @@ var _ = Describe("CreateRecoveryPlanCmd", func() {
Expect(err.Error()).To(ContainSubstring("fake-err"))
})

It("does not override max_in_flight if not confirmed", func() {
ui.AskedConfirmationErr = errors.New("fake-err")

err := act()
Expect(err).ToNot(HaveOccurred())

Expect(fakeFS.WriteFileCallCount).To(Equal(1))
Expect(fakeFS.FileExists("/tmp/foo.yml")).To(BeTrue())
bytes, err := fakeFS.ReadFile("/tmp/foo.yml")
Expect(err).ToNot(HaveOccurred())

var actualPlan RecoveryPlan
Expect(yaml.Unmarshal(bytes, &actualPlan)).ToNot(HaveOccurred())

Expect(actualPlan.InstanceGroupsPlan).To(HaveLen(2))

Expect(actualPlan.InstanceGroupsPlan[0].Name).To(Equal("diego_cell"))
Expect(actualPlan.InstanceGroupsPlan[0].MaxInFlightOverride).To(BeEmpty())

Expect(actualPlan.InstanceGroupsPlan[1].Name).To(Equal("router"))
Expect(actualPlan.InstanceGroupsPlan[1].MaxInFlightOverride).To(BeEmpty())
})

Context("director does not return instance group", func() {
BeforeEach(func() {
grouplessProbs := severalProbs
Expand Down

0 comments on commit c13b006

Please sign in to comment.