Skip to content

Commit f242fad

Browse files
author
Brian Akins
committed
Add minimum age check for pod candidates
1 parent d5a34a9 commit f242fad

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,
@@ -154,6 +157,8 @@ func (c *Chaoskube) Candidates() ([]v1.Pod, error) {
154157
pods = filterByAnnotations(pods, c.Annotations)
155158
pods = filterByPhase(pods, v1.PodRunning)
156159

160+
pods = filterByMinimumAge(pods, c.MinimumAge, c.Now())
161+
157162
return pods, nil
158163
}
159164

@@ -262,3 +267,23 @@ func filterByPhase(pods []v1.Pod, phase v1.PodPhase) []v1.Pod {
262267

263268
return filteredList
264269
}
270+
271+
// filterByMinimumAge filters pods by creation time. Only pods
272+
// older than minimumAge are returned
273+
func filterByMinimumAge(pods []v1.Pod, minimumAge time.Duration, now time.Time) []v1.Pod {
274+
if minimumAge <= time.Duration(0) {
275+
return pods
276+
}
277+
278+
creationTime := now.Add(-minimumAge)
279+
280+
filteredList := []v1.Pod{}
281+
282+
for _, pod := range pods {
283+
if pod.ObjectMeta.CreationTimestamp.Time.Before(creationTime) {
284+
filteredList = append(filteredList, pod)
285+
}
286+
}
287+
288+
return filteredList
289+
}

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", v1.PodRunning)
@@ -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

@@ -512,7 +523,7 @@ func (suite *Suite) setupWithPods(labelSelector labels.Selector, annotations lab
512523
return chaoskube
513524
}
514525

515-
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 {
526+
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 {
516527
logOutput.Reset()
517528

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

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)