Skip to content

Commit

Permalink
feat: specify days of a year without chaos
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin Linkhorst committed Mar 7, 2018
1 parent 1c7a239 commit 9020a87
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 8 deletions.
26 changes: 20 additions & 6 deletions chaoskube/chaoskube.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type Chaoskube struct {
ExcludedWeekdays []time.Weekday
// a list of time periods of a day when termination is suspended
ExcludedTimesOfDay []util.TimePeriod
// a list of days of a year when termination is suspended
ExcludedDaysOfYear []time.Time
// the timezone to apply when detecting the current weekday
Timezone *time.Location
// an instance of logrus.StdLogger to write log messages to
Expand All @@ -50,21 +52,26 @@ var (
msgWeekdayExcluded = "weekday excluded"
// msgTimeOfDayExcluded is the log message when termination is suspended due to the time of day filter
msgTimeOfDayExcluded = "time of day excluded"
// msgDayOfYearExcluded is the log message when termination is suspended due to the day of year filter
msgDayOfYearExcluded = "day of year excluded"
)

// New returns a new instance of Chaoskube. It expects a kubernetes client, a
// label, annotation and/or namespace selector to reduce the amount of affected
// pods as well as whether to enable dryRun mode and a seed to seed the randomizer
// with. You can also provide a list of weekdays and corresponding time zone when
// chaoskube should be inactive.
func New(client kubernetes.Interface, labels, annotations, namespaces labels.Selector, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, timezone *time.Location, logger log.FieldLogger, dryRun bool) *Chaoskube {
// New returns a new instance of Chaoskube. It expects:
// * a Kubernetes client to connect to a Kubernetes API
// * label, annotation and/or namespace selectors to reduce the amount of possible target pods
// * a list of weekdays, times of day and/or days of a year when chaos mode is disabled
// * a time zone to apply to the aforementioned time-based filters
// * a logger implementing logrus.FieldLogger to send log output to
// * whether to enable/disable dry-run mode
func New(client kubernetes.Interface, labels, annotations, namespaces labels.Selector, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, excludedDaysOfYear []time.Time, timezone *time.Location, logger log.FieldLogger, dryRun bool) *Chaoskube {
return &Chaoskube{
Client: client,
Labels: labels,
Annotations: annotations,
Namespaces: namespaces,
ExcludedWeekdays: excludedWeekdays,
ExcludedTimesOfDay: excludedTimesOfDay,
ExcludedDaysOfYear: excludedDaysOfYear,
Timezone: timezone,
Logger: logger,
DryRun: dryRun,
Expand Down Expand Up @@ -146,6 +153,13 @@ func (c *Chaoskube) TerminateVictim() error {
}
}

for _, d := range c.ExcludedDaysOfYear {
if d.Day() == now.Day() && d.Month() == now.Month() {
c.Logger.WithField("dayOfYear", now.Format(util.YearDay)).Debug(msgDayOfYearExcluded)
return nil
}
}

victim, err := c.Victim()
if err == errPodNotFound {
c.Logger.Debug(msgVictimNotFound)
Expand Down
86 changes: 84 additions & 2 deletions chaoskube/chaoskube_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func (suite *Suite) TestNew() {
namespaces, _ = labels.Parse("qux")
excludedWeekdays = []time.Weekday{time.Friday}
excludedTimesOfDay = []util.TimePeriod{util.TimePeriod{}}
excludedDaysOfYear = []time.Time{time.Now()}
)

chaoskube := New(
Expand All @@ -48,6 +49,7 @@ func (suite *Suite) TestNew() {
namespaces,
excludedWeekdays,
excludedTimesOfDay,
excludedDaysOfYear,
time.UTC,
logger,
false,
Expand All @@ -60,6 +62,7 @@ func (suite *Suite) TestNew() {
suite.Equal("qux", chaoskube.Namespaces.String())
suite.Equal(excludedWeekdays, chaoskube.ExcludedWeekdays)
suite.Equal(excludedTimesOfDay, chaoskube.ExcludedTimesOfDay)
suite.Equal(excludedDaysOfYear, chaoskube.ExcludedDaysOfYear)
suite.Equal(time.UTC, chaoskube.Timezone)
suite.Equal(logger, chaoskube.Logger)
suite.Equal(false, chaoskube.DryRun)
Expand Down Expand Up @@ -102,6 +105,7 @@ func (suite *Suite) TestCandidates() {
namespaceSelector,
[]time.Weekday{},
[]util.TimePeriod{},
[]time.Time{},
time.UTC,
false,
)
Expand Down Expand Up @@ -134,6 +138,7 @@ func (suite *Suite) TestVictim() {
labels.Everything(),
[]time.Weekday{},
[]util.TimePeriod{},
[]time.Time{},
time.UTC,
false,
)
Expand All @@ -150,6 +155,7 @@ func (suite *Suite) TestNoVictimReturnsError() {
labels.Everything(),
[]time.Weekday{},
[]util.TimePeriod{},
[]time.Time{},
time.UTC,
false,
)
Expand All @@ -176,6 +182,7 @@ func (suite *Suite) TestDeletePod() {
labels.Everything(),
[]time.Weekday{},
[]util.TimePeriod{},
[]time.Time{},
time.UTC,
tt.dryRun,
)
Expand Down Expand Up @@ -210,6 +217,7 @@ func (suite *Suite) TestTerminateVictim() {
for _, tt := range []struct {
excludedWeekdays []time.Weekday
excludedTimesOfDay []util.TimePeriod
excludedDaysOfYear []time.Time
now func() time.Time
timezone *time.Location
remainingPodCount int
Expand All @@ -218,6 +226,7 @@ func (suite *Suite) TestTerminateVictim() {
{
[]time.Weekday{},
[]util.TimePeriod{},
[]time.Time{},
ThankGodItsFriday{}.Now,
time.UTC,
1,
Expand All @@ -226,6 +235,7 @@ func (suite *Suite) TestTerminateVictim() {
{
[]time.Weekday{time.Friday},
[]util.TimePeriod{},
[]time.Time{},
ThankGodItsFriday{}.Now,
time.UTC,
2,
Expand All @@ -234,6 +244,7 @@ func (suite *Suite) TestTerminateVictim() {
{
[]time.Weekday{},
[]util.TimePeriod{afternoon},
[]time.Time{},
ThankGodItsFriday{}.Now,
time.UTC,
2,
Expand All @@ -242,6 +253,7 @@ func (suite *Suite) TestTerminateVictim() {
{
[]time.Weekday{time.Friday},
[]util.TimePeriod{},
[]time.Time{},
func() time.Time { return ThankGodItsFriday{}.Now().Add(24 * time.Hour) },
time.UTC,
1,
Expand All @@ -250,6 +262,7 @@ func (suite *Suite) TestTerminateVictim() {
{
[]time.Weekday{time.Friday},
[]util.TimePeriod{},
[]time.Time{},
func() time.Time { return ThankGodItsFriday{}.Now().Add(7 * 24 * time.Hour) },
time.UTC,
2,
Expand All @@ -258,6 +271,7 @@ func (suite *Suite) TestTerminateVictim() {
{
[]time.Weekday{},
[]util.TimePeriod{afternoon},
[]time.Time{},
func() time.Time { return ThankGodItsFriday{}.Now().Add(+2 * time.Hour) },
time.UTC,
1,
Expand All @@ -266,6 +280,7 @@ func (suite *Suite) TestTerminateVictim() {
{
[]time.Weekday{},
[]util.TimePeriod{afternoon},
[]time.Time{},
func() time.Time { return ThankGodItsFriday{}.Now().Add(+24 * time.Hour) },
time.UTC,
2,
Expand All @@ -274,6 +289,7 @@ func (suite *Suite) TestTerminateVictim() {
{
[]time.Weekday{time.Friday},
[]util.TimePeriod{},
[]time.Time{},
ThankGodItsFriday{}.Now,
australia,
1,
Expand All @@ -282,6 +298,7 @@ func (suite *Suite) TestTerminateVictim() {
{
[]time.Weekday{},
[]util.TimePeriod{afternoon},
[]time.Time{},
ThankGodItsFriday{}.Now,
australia,
1,
Expand All @@ -290,6 +307,7 @@ func (suite *Suite) TestTerminateVictim() {
{
[]time.Weekday{time.Monday, time.Friday},
[]util.TimePeriod{},
[]time.Time{},
ThankGodItsFriday{}.Now,
time.UTC,
2,
Expand All @@ -298,6 +316,7 @@ func (suite *Suite) TestTerminateVictim() {
{
[]time.Weekday{},
[]util.TimePeriod{morning, afternoon},
[]time.Time{},
ThankGodItsFriday{}.Now,
time.UTC,
2,
Expand All @@ -306,6 +325,7 @@ func (suite *Suite) TestTerminateVictim() {
{
[]time.Weekday{},
[]util.TimePeriod{midnight},
[]time.Time{},
func() time.Time { return ThankGodItsFriday{}.Now().Add(-15 * time.Hour) },
time.UTC,
2,
Expand All @@ -314,6 +334,7 @@ func (suite *Suite) TestTerminateVictim() {
{
[]time.Weekday{},
[]util.TimePeriod{midnight},
[]time.Time{},
func() time.Time { return ThankGodItsFriday{}.Now().Add(-17 * time.Hour) },
time.UTC,
1,
Expand All @@ -322,17 +343,75 @@ func (suite *Suite) TestTerminateVictim() {
{
[]time.Weekday{},
[]util.TimePeriod{midnight},
[]time.Time{},
func() time.Time { return ThankGodItsFriday{}.Now().Add(-13 * time.Hour) },
time.UTC,
1,
},
// this day of year is excluded, no pod should be killed
{
[]time.Weekday{},
[]util.TimePeriod{},
[]time.Time{
ThankGodItsFriday{}.Now(), // today
},
func() time.Time { return ThankGodItsFriday{}.Now() },
time.UTC,
2,
},
// this day of year in year 0 is excluded, no pod should be killed
{
[]time.Weekday{},
[]util.TimePeriod{},
[]time.Time{
time.Date(0, 9, 24, 0, 00, 00, 00, time.UTC), // same year day
},
func() time.Time { return ThankGodItsFriday{}.Now() },
time.UTC,
2,
},
// matching works fine even when multiple days-of-year are provided, no pod should be killed
{
[]time.Weekday{},
[]util.TimePeriod{},
[]time.Time{
time.Date(0, 9, 25, 10, 00, 00, 00, time.UTC), // different year day
time.Date(0, 9, 24, 10, 00, 00, 00, time.UTC), // same year day
},
func() time.Time { return ThankGodItsFriday{}.Now() },
time.UTC,
2,
},
// there is an excluded day of year but it's not today, one pod should be killed
{
[]time.Weekday{},
[]util.TimePeriod{},
[]time.Time{
time.Date(0, 9, 25, 10, 00, 00, 00, time.UTC), // different year day
},
func() time.Time { return ThankGodItsFriday{}.Now() },
time.UTC,
1,
},
// there is an excluded day of year but the month is different, one pod should be killed
{
[]time.Weekday{},
[]util.TimePeriod{},
[]time.Time{
time.Date(0, 10, 24, 10, 00, 00, 00, time.UTC), // different year day
},
func() time.Time { return ThankGodItsFriday{}.Now() },
time.UTC,
1,
},
} {
chaoskube := suite.setupWithPods(
labels.Everything(),
labels.Everything(),
labels.Everything(),
tt.excludedWeekdays,
tt.excludedTimesOfDay,
tt.excludedDaysOfYear,
tt.timezone,
false,
)
Expand All @@ -356,6 +435,7 @@ func (suite *Suite) TestTerminateNoVictimLogsInfo() {
labels.Everything(),
[]time.Weekday{},
[]util.TimePeriod{},
[]time.Time{},
time.UTC,
false,
)
Expand Down Expand Up @@ -406,13 +486,14 @@ func (suite *Suite) assertLog(level log.Level, msg string, fields log.Fields) {
}
}

func (suite *Suite) setupWithPods(labelSelector labels.Selector, annotations labels.Selector, namespaces labels.Selector, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, timezone *time.Location, dryRun bool) *Chaoskube {
func (suite *Suite) setupWithPods(labelSelector labels.Selector, annotations labels.Selector, namespaces labels.Selector, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, excludedDaysOfYear []time.Time, timezone *time.Location, dryRun bool) *Chaoskube {
chaoskube := suite.setup(
labelSelector,
annotations,
namespaces,
excludedWeekdays,
excludedTimesOfDay,
excludedDaysOfYear,
timezone,
dryRun,
)
Expand All @@ -430,7 +511,7 @@ func (suite *Suite) setupWithPods(labelSelector labels.Selector, annotations lab
return chaoskube
}

func (suite *Suite) setup(labelSelector labels.Selector, annotations labels.Selector, namespaces labels.Selector, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, timezone *time.Location, dryRun bool) *Chaoskube {
func (suite *Suite) setup(labelSelector labels.Selector, annotations labels.Selector, namespaces labels.Selector, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, excludedDaysOfYear []time.Time, timezone *time.Location, dryRun bool) *Chaoskube {
logOutput.Reset()

return New(
Expand All @@ -440,6 +521,7 @@ func (suite *Suite) setup(labelSelector labels.Selector, annotations labels.Sele
namespaces,
excludedWeekdays,
excludedTimesOfDay,
excludedDaysOfYear,
timezone,
logger,
dryRun,
Expand Down
14 changes: 14 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var (
nsString string
excludedWeekdays string
excludedTimesOfDay string
excludedDaysOfYear string
timezone string
master string
kubeconfig string
Expand All @@ -40,6 +41,7 @@ func init() {
kingpin.Flag("namespaces", "A set of namespaces to restrict the list of affected pods. Defaults to everything.").StringVar(&nsString)
kingpin.Flag("excluded-weekdays", "A list of weekdays when termination is suspended, e.g. sat,sun").StringVar(&excludedWeekdays)
kingpin.Flag("excluded-times-of-day", "A list of time periods of a day when termination is suspended, e.g. 22:00-08:00").StringVar(&excludedTimesOfDay)
kingpin.Flag("excluded-days-of-year", "A list of days of a year when termination is suspended, e.g. Apr1,Dec24").StringVar(&excludedDaysOfYear)
kingpin.Flag("timezone", "The timezone by which to interpret the excluded weekdays and times of day, e.g. UTC, Local, Europe/Berlin. Defaults to UTC.").Default("UTC").StringVar(&timezone)
kingpin.Flag("master", "The address of the Kubernetes cluster to target").StringVar(&master)
kingpin.Flag("kubeconfig", "Path to a kubeconfig file").StringVar(&kubeconfig)
Expand All @@ -62,6 +64,7 @@ func main() {
"namespaces": nsString,
"excludedWeekdays": excludedWeekdays,
"excludedTimesOfDay": excludedTimesOfDay,
"excludedDaysOfYear": excludedDaysOfYear,
"timezone": timezone,
"master": master,
"kubeconfig": kubeconfig,
Expand Down Expand Up @@ -100,10 +103,12 @@ func main() {
if err != nil {
log.Fatal(err)
}
parsedDaysOfYear := util.ParseDays(excludedDaysOfYear)

log.WithFields(log.Fields{
"weekdays": parsedWeekdays,
"timesOfDay": parsedTimesOfDay,
"daysOfYear": formatDays(parsedDaysOfYear),
}).Info("setting quiet times")

parsedTimezone, err := time.LoadLocation(timezone)
Expand All @@ -125,6 +130,7 @@ func main() {
namespaces,
parsedWeekdays,
parsedTimesOfDay,
parsedDaysOfYear,
parsedTimezone,
log.StandardLogger(),
dryRun,
Expand Down Expand Up @@ -169,3 +175,11 @@ func newClient() (*kubernetes.Clientset, error) {

return client, nil
}

func formatDays(days []time.Time) []string {
formattedDays := make([]string, 0, len(days))
for _, d := range days {
formattedDays = append(formattedDays, d.Format(util.YearDay))
}
return formattedDays
}
Loading

0 comments on commit 9020a87

Please sign in to comment.