Skip to content

Commit 58b4b2a

Browse files
author
Brian Akins
committed
Add minimum age check for pod candidates
1 parent 8c5333e commit 58b4b2a

File tree

3 files changed

+144
-3
lines changed

3 files changed

+144
-3
lines changed

chaoskube/chaoskube.go

+26-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ type Chaoskube struct {
3535
ExcludedDaysOfYear []time.Time
3636
// the timezone to apply when detecting the current weekday
3737
Timezone *time.Location
38+
// minimum age of pods to consider
39+
MinimumAge time.Duration
3840
// an instance of logrus.StdLogger to write log messages to
3941
Logger log.FieldLogger
4042
// dry run will not allow any pod terminations
@@ -63,7 +65,7 @@ var (
6365
// * a time zone to apply to the aforementioned time-based filters
6466
// * a logger implementing logrus.FieldLogger to send log output to
6567
// * whether to enable/disable dry-run mode
66-
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 {
68+
func New(client kubernetes.Interface, labels, annotations, namespaces labels.Selector, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, excludedDaysOfYear []time.Time, timezone *time.Location, minimumAge time.Duration, logger log.FieldLogger, dryRun bool) *Chaoskube {
6769
return &Chaoskube{
6870
Client: client,
6971
Labels: labels,
@@ -73,6 +75,7 @@ func New(client kubernetes.Interface, labels, annotations, namespaces labels.Sel
7375
ExcludedTimesOfDay: excludedTimesOfDay,
7476
ExcludedDaysOfYear: excludedDaysOfYear,
7577
Timezone: timezone,
78+
MinimumAge: minimumAge,
7679
Logger: logger,
7780
DryRun: dryRun,
7881
Now: time.Now,
@@ -156,6 +159,8 @@ func (c *Chaoskube) Candidates() ([]v1.Pod, error) {
156159
return nil, err
157160
}
158161

162+
pods = filterByMinimumAge(pods, c.MinimumAge, c.Now())
163+
159164
return pods, nil
160165
}
161166

@@ -251,3 +256,23 @@ func filterByAnnotations(pods []v1.Pod, annotations labels.Selector) ([]v1.Pod,
251256

252257
return filteredList, nil
253258
}
259+
260+
// filterByMinimumAge filters pods by creation time. Only pods
261+
// older than minimumAge are returned
262+
func filterByMinimumAge(pods []v1.Pod, minimumAge time.Duration, now time.Time) []v1.Pod {
263+
if minimumAge <= time.Duration(0) {
264+
return pods
265+
}
266+
267+
creationTime := now.Add(-minimumAge)
268+
269+
filteredList := []v1.Pod{}
270+
271+
for _, pod := range pods {
272+
if pod.ObjectMeta.CreationTimestamp.Time.Before(creationTime) {
273+
filteredList = append(filteredList, pod)
274+
}
275+
}
276+
277+
return filteredList
278+
}

chaoskube/chaoskube_test.go

+114-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/sirupsen/logrus/hooks/test"
1010

1111
"k8s.io/api/core/v1"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1213
"k8s.io/apimachinery/pkg/labels"
1314
"k8s.io/client-go/kubernetes/fake"
1415

@@ -40,6 +41,7 @@ func (suite *Suite) TestNew() {
4041
excludedWeekdays = []time.Weekday{time.Friday}
4142
excludedTimesOfDay = []util.TimePeriod{util.TimePeriod{}}
4243
excludedDaysOfYear = []time.Time{time.Now()}
44+
minimumAge = time.Duration(42)
4345
)
4446

4547
chaoskube := New(
@@ -51,6 +53,7 @@ func (suite *Suite) TestNew() {
5153
excludedTimesOfDay,
5254
excludedDaysOfYear,
5355
time.UTC,
56+
minimumAge,
5457
logger,
5558
false,
5659
)
@@ -66,6 +69,7 @@ func (suite *Suite) TestNew() {
6669
suite.Equal(time.UTC, chaoskube.Timezone)
6770
suite.Equal(logger, chaoskube.Logger)
6871
suite.Equal(false, chaoskube.DryRun)
72+
suite.Equal(minimumAge, chaoskube.MinimumAge)
6973
}
7074

7175
func (suite *Suite) TestCandidates() {
@@ -108,6 +112,7 @@ func (suite *Suite) TestCandidates() {
108112
[]time.Time{},
109113
time.UTC,
110114
false,
115+
time.Duration(42),
111116
)
112117

113118
suite.assertCandidates(chaoskube, tt.pods)
@@ -141,6 +146,7 @@ func (suite *Suite) TestVictim() {
141146
[]time.Time{},
142147
time.UTC,
143148
false,
149+
time.Duration(42),
144150
)
145151

146152
suite.assertVictim(chaoskube, tt.victim)
@@ -157,6 +163,7 @@ func (suite *Suite) TestNoVictimReturnsError() {
157163
[]util.TimePeriod{},
158164
[]time.Time{},
159165
time.UTC,
166+
time.Duration(42),
160167
false,
161168
)
162169

@@ -185,6 +192,7 @@ func (suite *Suite) TestDeletePod() {
185192
[]time.Time{},
186193
time.UTC,
187194
tt.dryRun,
195+
time.Duration(42),
188196
)
189197

190198
victim := util.NewPod("default", "foo")
@@ -414,6 +422,7 @@ func (suite *Suite) TestTerminateVictim() {
414422
tt.excludedDaysOfYear,
415423
tt.timezone,
416424
false,
425+
time.Duration(42),
417426
)
418427
chaoskube.Now = tt.now
419428

@@ -437,6 +446,7 @@ func (suite *Suite) TestTerminateNoVictimLogsInfo() {
437446
[]util.TimePeriod{},
438447
[]time.Time{},
439448
time.UTC,
449+
time.Duration(42),
440450
false,
441451
)
442452

@@ -486,7 +496,7 @@ func (suite *Suite) assertLog(level log.Level, msg string, fields log.Fields) {
486496
}
487497
}
488498

489-
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 {
499+
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, minimumAge time.Duration) *Chaoskube {
490500
chaoskube := suite.setup(
491501
labelSelector,
492502
annotations,
@@ -495,6 +505,7 @@ func (suite *Suite) setupWithPods(labelSelector labels.Selector, annotations lab
495505
excludedTimesOfDay,
496506
excludedDaysOfYear,
497507
timezone,
508+
minimumAge,
498509
dryRun,
499510
)
500511

@@ -511,7 +522,7 @@ func (suite *Suite) setupWithPods(labelSelector labels.Selector, annotations lab
511522
return chaoskube
512523
}
513524

514-
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 {
525+
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, minimumAge time.Duration, dryRun bool) *Chaoskube {
515526
logOutput.Reset()
516527

517528
return New(
@@ -523,6 +534,7 @@ func (suite *Suite) setup(labelSelector labels.Selector, annotations labels.Sele
523534
excludedTimesOfDay,
524535
excludedDaysOfYear,
525536
timezone,
537+
minimumAge,
526538
logger,
527539
dryRun,
528540
)
@@ -540,3 +552,103 @@ func (t ThankGodItsFriday) Now() time.Time {
540552
blackFriday, _ := time.Parse(time.RFC1123, "Fri, 24 Sep 1869 15:04:05 UTC")
541553
return blackFriday
542554
}
555+
556+
func (suite *Suite) TestMinimumAge() {
557+
type pod struct {
558+
name string
559+
namespace string
560+
creationTime time.Time
561+
}
562+
563+
for _, tt := range []struct {
564+
minimumAge time.Duration
565+
now func() time.Time
566+
pods []pod
567+
candidates int
568+
}{
569+
// no minimum age set
570+
{
571+
time.Duration(0),
572+
func() time.Time { return time.Date(0, 10, 24, 10, 00, 00, 00, time.UTC) },
573+
[]pod{
574+
{
575+
name: "test1",
576+
namespace: "test",
577+
creationTime: time.Date(0, 10, 24, 9, 00, 00, 00, time.UTC),
578+
},
579+
},
580+
1,
581+
},
582+
// minimum age set, but pod is too young
583+
{
584+
time.Hour * 1,
585+
func() time.Time { return time.Date(0, 10, 24, 10, 00, 00, 00, time.UTC) },
586+
[]pod{
587+
{
588+
name: "test1",
589+
namespace: "test",
590+
creationTime: time.Date(0, 10, 24, 9, 30, 00, 00, time.UTC),
591+
},
592+
},
593+
0,
594+
},
595+
// one pod is too young, one matches
596+
{
597+
time.Hour * 1,
598+
func() time.Time { return time.Date(0, 10, 24, 10, 00, 00, 00, time.UTC) },
599+
[]pod{
600+
// too young
601+
{
602+
name: "test1",
603+
namespace: "test",
604+
creationTime: time.Date(0, 10, 24, 9, 30, 00, 00, time.UTC),
605+
},
606+
// matches
607+
{
608+
name: "test2",
609+
namespace: "test",
610+
creationTime: time.Date(0, 10, 23, 8, 00, 00, 00, time.UTC),
611+
},
612+
},
613+
1,
614+
},
615+
// exact time - should not match
616+
{
617+
time.Hour * 1,
618+
func() time.Time { return time.Date(0, 10, 24, 10, 00, 00, 00, time.UTC) },
619+
[]pod{
620+
{
621+
name: "test1",
622+
namespace: "test",
623+
creationTime: time.Date(0, 10, 24, 10, 00, 00, 00, time.UTC),
624+
},
625+
},
626+
0,
627+
},
628+
} {
629+
chaoskube := suite.setup(
630+
labels.Everything(),
631+
labels.Everything(),
632+
labels.Everything(),
633+
[]time.Weekday{},
634+
[]util.TimePeriod{},
635+
[]time.Time{},
636+
time.UTC,
637+
tt.minimumAge,
638+
false,
639+
)
640+
chaoskube.Now = tt.now
641+
642+
for _, p := range tt.pods {
643+
pod := util.NewPod(p.namespace, p.name)
644+
pod.ObjectMeta.CreationTimestamp = metav1.Time{Time: p.creationTime}
645+
_, err := chaoskube.Client.Core().Pods(pod.Namespace).Create(&pod)
646+
suite.Require().NoError(err)
647+
}
648+
649+
pods, err := chaoskube.Candidates()
650+
suite.Require().NoError(err)
651+
652+
suite.Len(pods, tt.candidates)
653+
}
654+
}

main.go

+4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ var (
2929
excludedTimesOfDay string
3030
excludedDaysOfYear string
3131
timezone string
32+
minimumAge time.Duration
3233
master string
3334
kubeconfig string
3435
interval time.Duration
@@ -46,6 +47,7 @@ func init() {
4647
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)
4748
kingpin.Flag("excluded-days-of-year", "A list of days of a year when termination is suspended, e.g. Apr1,Dec24").StringVar(&excludedDaysOfYear)
4849
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)
50+
kingpin.Flag("minimum-age", "Minimum age of pods to consider for termination").Default("0s").DurationVar(&minimumAge)
4951
kingpin.Flag("master", "The address of the Kubernetes cluster to target").StringVar(&master)
5052
kingpin.Flag("kubeconfig", "Path to a kubeconfig file").StringVar(&kubeconfig)
5153
kingpin.Flag("interval", "Interval between Pod terminations").Default("10m").DurationVar(&interval)
@@ -69,6 +71,7 @@ func main() {
6971
"excludedTimesOfDay": excludedTimesOfDay,
7072
"excludedDaysOfYear": excludedDaysOfYear,
7173
"timezone": timezone,
74+
"minimumAge": minimumAge,
7275
"master": master,
7376
"kubeconfig": kubeconfig,
7477
"interval": interval,
@@ -145,6 +148,7 @@ func main() {
145148
parsedTimesOfDay,
146149
parsedDaysOfYear,
147150
parsedTimezone,
151+
minimumAge,
148152
log.StandardLogger(),
149153
dryRun,
150154
)

0 commit comments

Comments
 (0)