-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Kevin Logan <[email protected]>
- Loading branch information
1 parent
a93b405
commit cf141fc
Showing
8 changed files
with
237 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// Copyright 2017 Palantir Technologies, Inc. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package cmd | ||
|
||
import ( | ||
log "github.com/Sirupsen/logrus" | ||
"github.com/palantir/bouncer/bouncer" | ||
"github.com/palantir/bouncer/rolling" | ||
"github.com/pkg/errors" | ||
"github.com/spf13/cobra" | ||
"github.com/spf13/viper" | ||
) | ||
|
||
var rollingCmd = &cobra.Command{ | ||
Use: "rolling", | ||
Short: "Run bouncer in rolling", | ||
Long: `Run bouncer in rolling mode, where we bounce one node at a time from the list of ASGs.`, | ||
Run: func(cmd *cobra.Command, args []string) { | ||
log.SetLevel(logLevelFromViper()) | ||
|
||
log.Debug("rolling called") | ||
if log.GetLevel() == log.DebugLevel { | ||
cmd.DebugFlags() | ||
viper.Debug() | ||
} | ||
|
||
asgsString := viper.GetString("rolling.asgs") | ||
if asgsString == "" { | ||
log.Fatal("You must specify ASGs to cycle nodes from (in a comma-delimited list)") | ||
} | ||
|
||
commandString := viper.GetString("rolling.command") | ||
noop := viper.GetBool("rolling.noop") | ||
force := viper.GetBool("rolling.force") | ||
termHook := viper.GetString("terminate-hook") | ||
pendHook := viper.GetString("pending-hook") | ||
timeout := timeoutFromViper() | ||
|
||
log.Debugf("Binding vars, got %+v %+v %+v %+v", asgsString, noop, version, commandString) | ||
|
||
log.Info("Beginning bouncer rolling run") | ||
|
||
var defCap int64 | ||
defCap = 1 | ||
opts := bouncer.RunnerOpts{ | ||
Noop: noop, | ||
Force: force, | ||
AsgString: asgsString, | ||
CommandString: commandString, | ||
DefaultCapacity: &defCap, | ||
TerminateHook: termHook, | ||
PendingHook: pendHook, | ||
ItemTimeout: timeout, | ||
} | ||
|
||
r, err := rolling.NewRunner(&opts) | ||
if err != nil { | ||
log.Fatal(errors.Wrap(err, "error initializing runner")) | ||
} | ||
|
||
r.MustValidatePrereqs() | ||
|
||
err = r.Run() | ||
if err != nil { | ||
log.Fatal(errors.Wrap(err, "error in run")) | ||
} | ||
}, | ||
} | ||
|
||
func init() { | ||
RootCmd.AddCommand(rollingCmd) | ||
|
||
rollingCmd.Flags().BoolP("noop", "n", false, "Run this in noop mode, and only print what you would do") | ||
err := viper.BindPFlag("rolling.noop", rollingCmd.Flags().Lookup("noop")) | ||
if err != nil { | ||
log.Fatal(errors.Wrap(err, "Binding PFlag 'noop' to viper var 'rolling.noop' failed: %s")) | ||
} | ||
|
||
rollingCmd.Flags().StringP("asgs", "a", "", "ASGs to check for nodes to cycle in") | ||
err = viper.BindPFlag("rolling.asgs", rollingCmd.Flags().Lookup("asgs")) | ||
if err != nil { | ||
log.Fatal(errors.Wrap(err, "Binding PFlag 'asgs' to viper var 'rolling.asgs' failed: %s")) | ||
} | ||
|
||
rollingCmd.Flags().StringP("preterminatecall", "p", "", "External command to run before host is removed from its ELB & terminate process begins") | ||
err = viper.BindPFlag("rolling.command", rollingCmd.Flags().Lookup("preterminatecall")) | ||
if err != nil { | ||
log.Fatal(errors.Wrap(err, "Binding PFlag 'command' to viper var 'rolling.command' failed: %s")) | ||
} | ||
|
||
rollingCmd.Flags().BoolP("force", "f", false, "Force all nodes to be recycled, even if they're running the latest launch config") | ||
err = viper.BindPFlag("rolling.force", rollingCmd.Flags().Lookup("force")) | ||
if err != nil { | ||
log.Fatal(errors.Wrap(err, "Binding PFlag 'force' to viper var 'rolling.force' failed: %s")) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
// Copyright 2017 Palantir Technologies, Inc. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package rolling | ||
|
||
import ( | ||
"os" | ||
|
||
log "github.com/Sirupsen/logrus" | ||
"github.com/palantir/bouncer/bouncer" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
// Runner holds data for a particular rolling run | ||
type Runner struct { | ||
bouncer.BaseRunner | ||
} | ||
|
||
// NewRunner instantiates a new rolling runner | ||
func NewRunner(opts *bouncer.RunnerOpts) (*Runner, error) { | ||
br, err := bouncer.NewBaseRunner(opts) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "error getting base runner") | ||
} | ||
|
||
r := Runner{ | ||
*br, | ||
} | ||
return &r, nil | ||
} | ||
|
||
func (r *Runner) killBestOldInstance(asgSet *bouncer.ASGSet) error { | ||
bestOld := asgSet.GetBestOldInstance() | ||
decrement := false | ||
err := r.KillInstance(bestOld, &decrement) | ||
return errors.Wrap(err, "error killing instance") | ||
} | ||
|
||
// MustValidatePrereqs checks that the batch runner is safe to proceed | ||
func (r *Runner) MustValidatePrereqs() { | ||
asgSet, err := r.NewASGSet() | ||
if err != nil { | ||
log.Fatal(errors.Wrap(err, "error building ASGSet")) | ||
} | ||
|
||
divergedASGs := asgSet.GetDivergedASGs() | ||
if len(divergedASGs) != 0 { | ||
for _, badASG := range divergedASGs { | ||
log.WithFields(log.Fields{ | ||
"ASG": *badASG.ASG.AutoScalingGroupName, | ||
"desired_capacity actual": *badASG.ASG.DesiredCapacity, | ||
"desired_capacity given": badASG.DesiredASG.DesiredCapacity, | ||
}).Error("ASG desired capacity doesn't match expected starting value") | ||
} | ||
os.Exit(1) | ||
} | ||
|
||
for _, asg := range asgSet.ASGs { | ||
if *asg.ASG.DesiredCapacity == 0 { | ||
log.WithFields(log.Fields{ | ||
"ASG": *asg.ASG.AutoScalingGroupName, | ||
}).Warn("ASG desired capacity is 0 - nothing to do here") | ||
os.Exit(0) | ||
} | ||
} | ||
} | ||
|
||
// Run has the meat of the batch job | ||
func (r *Runner) Run() error { | ||
for { | ||
if r.TimedOut() { | ||
return errors.Errorf("timeout exceeded, something is probably wrong with rollout") | ||
} | ||
|
||
// Rebuild the state of the world every iteration of the loop because instance and ASG statuses are changing | ||
log.Debug("Beginning new rolling run check") | ||
asgSet, err := r.NewASGSet() | ||
if err != nil { | ||
return errors.Wrap(err, "error building ASGSet") | ||
} | ||
|
||
// See if we're still waiting on a change we made previously to finish or settle | ||
if asgSet.IsNewUnhealthy() || asgSet.IsTerminating() || asgSet.IsImmutableAutoscalingEvent() || asgSet.IsCountMismatch() { | ||
r.Sleep() | ||
continue | ||
} | ||
|
||
// If there are any old instances which are now ready to be terminated, let's do it | ||
if asgSet.IsOldInstance() { | ||
err = r.killBestOldInstance(asgSet) | ||
if err != nil { | ||
return errors.Wrap(err, "error finding or killing best old instance") | ||
} | ||
|
||
r.Sleep() | ||
continue | ||
} | ||
|
||
log.Info("Didn't find any old instances or ASGs - we're done here!") | ||
return nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters