Skip to content

Commit 04ddced

Browse files
authored
Merge pull request #134 from linki/namespace-labels
Allow to filter pods by namespace label selector
2 parents 5fc7b8c + 0fecf60 commit 04ddced

File tree

6 files changed

+159
-6
lines changed

6 files changed

+159
-6
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,16 @@ INFO[0000] setting pod filter namespaces="default,staging,testing"
128128

129129
This will filter for pods in the three namespaces `default`, `staging` and `testing`.
130130

131+
Namespaces can additionally be filtered by a namespace label selector.
132+
133+
```console
134+
$ chaoskube --namespace-labels='!integration'
135+
...
136+
INFO[0000] setting pod filter namespaceLabels="!integration"
137+
```
138+
139+
This will exclude all pods from namespaces with the label `integration`.
140+
131141
You can filter pods by name:
132142

133143
```console
@@ -213,6 +223,7 @@ Use `UTC`, `Local` or pick a timezone name from the [(IANA) tz database](https:/
213223
| `--labels` | label selector to filter pods by | (matches everything) |
214224
| `--annotations` | annotation selector to filter pods by | (matches everything) |
215225
| `--namespaces` | namespace selector to filter pods by | (all namespaces) |
226+
| `--namespace-labels` | label selector to filter namespaces and its pods by | (all namespaces) |
216227
| `--included-pod-names` | regular expression pattern for pod names to include | (all included) |
217228
| `--excluded-pod-names` | regular expression pattern for pod names to exclude | (none excluded) |
218229
| `--excluded-weekdays` | weekdays when chaos is to be suspended, e.g. "Sat,Sun" | (no weekday excluded) |

chaoskube/chaoskube.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ type Chaoskube struct {
3535
Annotations labels.Selector
3636
// a namespace selector which restricts the pods to choose from
3737
Namespaces labels.Selector
38+
// a namespace label selector which restricts the namespaces to choose from
39+
NamespaceLabels labels.Selector
3840
// a regular expression for pod names to include
3941
IncludedPodNames *regexp.Regexp
4042
// a regular expression for pod names to exclude
@@ -84,7 +86,7 @@ var (
8486
// * a logger implementing logrus.FieldLogger to send log output to
8587
// * what specific terminator to use to imbue chaos on victim pods
8688
// * whether to enable/disable dry-run mode
87-
func New(client kubernetes.Interface, labels, annotations, namespaces labels.Selector, includedPodNames, excludedPodNames *regexp.Regexp, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, excludedDaysOfYear []time.Time, timezone *time.Location, minimumAge time.Duration, logger log.FieldLogger, dryRun bool, terminator terminator.Terminator) *Chaoskube {
89+
func New(client kubernetes.Interface, labels, annotations, namespaces, namespaceLabels labels.Selector, includedPodNames, excludedPodNames *regexp.Regexp, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, excludedDaysOfYear []time.Time, timezone *time.Location, minimumAge time.Duration, logger log.FieldLogger, dryRun bool, terminator terminator.Terminator) *Chaoskube {
8890
broadcaster := record.NewBroadcaster()
8991
broadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: client.CoreV1().Events(v1.NamespaceAll)})
9092
recorder := broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "chaoskube"})
@@ -94,6 +96,7 @@ func New(client kubernetes.Interface, labels, annotations, namespaces labels.Sel
9496
Labels: labels,
9597
Annotations: annotations,
9698
Namespaces: namespaces,
99+
NamespaceLabels: namespaceLabels,
97100
IncludedPodNames: includedPodNames,
98101
ExcludedPodNames: excludedPodNames,
99102
ExcludedWeekdays: excludedWeekdays,
@@ -200,6 +203,11 @@ func (c *Chaoskube) Candidates() ([]v1.Pod, error) {
200203
return nil, err
201204
}
202205

206+
pods, err = filterPodsByNamespaceLabels(pods, c.NamespaceLabels, c.Client)
207+
if err != nil {
208+
return nil, err
209+
}
210+
203211
pods = filterByAnnotations(pods, c.Annotations)
204212
pods = filterByPhase(pods, v1.PodRunning)
205213
pods = filterByMinimumAge(pods, c.MinimumAge, c.Now())
@@ -296,6 +304,35 @@ func filterByNamespaces(pods []v1.Pod, namespaces labels.Selector) ([]v1.Pod, er
296304
return filteredList, nil
297305
}
298306

307+
// filterPodsByNamespaceLabels filters a list of pods by a given label selector on their namespace.
308+
func filterPodsByNamespaceLabels(pods []v1.Pod, labels labels.Selector, client kubernetes.Interface) ([]v1.Pod, error) {
309+
// empty filter returns original list
310+
if labels.Empty() {
311+
return pods, nil
312+
}
313+
314+
// find all namespaces matching the label selector
315+
listOptions := metav1.ListOptions{LabelSelector: labels.String()}
316+
317+
namespaces, err := client.CoreV1().Namespaces().List(listOptions)
318+
if err != nil {
319+
return nil, err
320+
}
321+
322+
filteredList := []v1.Pod{}
323+
324+
for _, pod := range pods {
325+
for _, namespace := range namespaces.Items {
326+
// include pod if its in one of the matched namespaces
327+
if pod.Namespace == namespace.Name {
328+
filteredList = append(filteredList, pod)
329+
}
330+
}
331+
}
332+
333+
return filteredList, nil
334+
}
335+
299336
// filterByAnnotations filters a list of pods by a given annotation selector.
300337
func filterByAnnotations(pods []v1.Pod, annotations labels.Selector) []v1.Pod {
301338
// empty filter returns original list

chaoskube/chaoskube_test.go

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ func (suite *Suite) TestNew() {
4242
labelSelector, _ = labels.Parse("foo=bar")
4343
annotations, _ = labels.Parse("baz=waldo")
4444
namespaces, _ = labels.Parse("qux")
45+
namespaceLabels, _ = labels.Parse("taz=wubble")
4546
includedPodNames = regexp.MustCompile("foo")
4647
excludedPodNames = regexp.MustCompile("bar")
4748
excludedWeekdays = []time.Weekday{time.Friday}
@@ -57,6 +58,7 @@ func (suite *Suite) TestNew() {
5758
labelSelector,
5859
annotations,
5960
namespaces,
61+
namespaceLabels,
6062
includedPodNames,
6163
excludedPodNames,
6264
excludedWeekdays,
@@ -74,6 +76,7 @@ func (suite *Suite) TestNew() {
7476
suite.Equal("foo=bar", chaoskube.Labels.String())
7577
suite.Equal("baz=waldo", chaoskube.Annotations.String())
7678
suite.Equal("qux", chaoskube.Namespaces.String())
79+
suite.Equal("taz=wubble", chaoskube.NamespaceLabels.String())
7780
suite.Equal("foo", chaoskube.IncludedPodNames.String())
7881
suite.Equal("bar", chaoskube.ExcludedPodNames.String())
7982
suite.Equal(excludedWeekdays, chaoskube.ExcludedWeekdays)
@@ -92,6 +95,7 @@ func (suite *Suite) TestRunContextCanceled() {
9295
labels.Everything(),
9396
labels.Everything(),
9497
labels.Everything(),
98+
labels.Everything(),
9599
&regexp.Regexp{},
96100
&regexp.Regexp{},
97101
[]time.Weekday{},
@@ -145,6 +149,51 @@ func (suite *Suite) TestCandidates() {
145149
labelSelector,
146150
annotationSelector,
147151
namespaceSelector,
152+
labels.Everything(),
153+
nil,
154+
nil,
155+
[]time.Weekday{},
156+
[]util.TimePeriod{},
157+
[]time.Time{},
158+
time.UTC,
159+
time.Duration(0),
160+
false,
161+
10,
162+
)
163+
164+
suite.assertCandidates(chaoskube, tt.pods)
165+
}
166+
}
167+
168+
// TestCandidatesNamespaceLabels tests that the label selector for namespaces works correctly.
169+
func (suite *Suite) TestCandidatesNamespaceLabels() {
170+
foo := map[string]string{"namespace": "default", "name": "foo"}
171+
bar := map[string]string{"namespace": "testing", "name": "bar"}
172+
173+
for _, tt := range []struct {
174+
labels string
175+
pods []map[string]string
176+
}{
177+
{"", []map[string]string{foo, bar}},
178+
{"env", []map[string]string{foo, bar}},
179+
{"!env", []map[string]string{}},
180+
{"env=default", []map[string]string{foo}},
181+
{"env=testing", []map[string]string{bar}},
182+
{"env!=default", []map[string]string{bar}},
183+
{"env!=testing", []map[string]string{foo}},
184+
{"env!=default,env!=testing", []map[string]string{}},
185+
{"env=default,env!=testing", []map[string]string{foo}},
186+
{"env=default,env!=default", []map[string]string{}},
187+
{"nomatch", []map[string]string{}},
188+
} {
189+
namespaceLabels, err := labels.Parse(tt.labels)
190+
suite.Require().NoError(err)
191+
192+
chaoskube := suite.setupWithPods(
193+
labels.Everything(),
194+
labels.Everything(),
195+
labels.Everything(),
196+
namespaceLabels,
148197
nil,
149198
nil,
150199
[]time.Weekday{},
@@ -186,6 +235,7 @@ func (suite *Suite) TestCandidatesPodNameRegexp() {
186235
labels.Everything(),
187236
labels.Everything(),
188237
labels.Everything(),
238+
labels.Everything(),
189239
tt.includedPodNames,
190240
tt.excludedPodNames,
191241
[]time.Weekday{},
@@ -224,6 +274,7 @@ func (suite *Suite) TestVictim() {
224274
labelSelector,
225275
labels.Everything(),
226276
labels.Everything(),
277+
labels.Everything(),
227278
&regexp.Regexp{},
228279
&regexp.Regexp{},
229280
[]time.Weekday{},
@@ -245,6 +296,7 @@ func (suite *Suite) TestNoVictimReturnsError() {
245296
labels.Everything(),
246297
labels.Everything(),
247298
labels.Everything(),
299+
labels.Everything(),
248300
&regexp.Regexp{},
249301
&regexp.Regexp{},
250302
[]time.Weekday{},
@@ -277,6 +329,7 @@ func (suite *Suite) TestDeletePod() {
277329
labels.Everything(),
278330
labels.Everything(),
279331
labels.Everything(),
332+
labels.Everything(),
280333
&regexp.Regexp{},
281334
&regexp.Regexp{},
282335
[]time.Weekday{},
@@ -304,6 +357,7 @@ func (suite *Suite) TestDeletePodNotFound() {
304357
labels.Everything(),
305358
labels.Everything(),
306359
labels.Everything(),
360+
labels.Everything(),
307361
&regexp.Regexp{},
308362
&regexp.Regexp{},
309363
[]time.Weekday{},
@@ -533,6 +587,7 @@ func (suite *Suite) TestTerminateVictim() {
533587
labels.Everything(),
534588
labels.Everything(),
535589
labels.Everything(),
590+
labels.Everything(),
536591
&regexp.Regexp{},
537592
&regexp.Regexp{},
538593
tt.excludedWeekdays,
@@ -561,6 +616,7 @@ func (suite *Suite) TestTerminateNoVictimLogsInfo() {
561616
labels.Everything(),
562617
labels.Everything(),
563618
labels.Everything(),
619+
labels.Everything(),
564620
&regexp.Regexp{},
565621
&regexp.Regexp{},
566622
[]time.Weekday{},
@@ -594,11 +650,12 @@ func (suite *Suite) assertVictim(chaoskube *Chaoskube, expected map[string]strin
594650
suite.AssertPod(victim, expected)
595651
}
596652

597-
func (suite *Suite) setupWithPods(labelSelector labels.Selector, annotations labels.Selector, namespaces labels.Selector, includedPodNames *regexp.Regexp, excludedPodNames *regexp.Regexp, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, excludedDaysOfYear []time.Time, timezone *time.Location, minimumAge time.Duration, dryRun bool, gracePeriod time.Duration) *Chaoskube {
653+
func (suite *Suite) setupWithPods(labelSelector labels.Selector, annotations labels.Selector, namespaces labels.Selector, namespaceLabels labels.Selector, includedPodNames *regexp.Regexp, excludedPodNames *regexp.Regexp, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, excludedDaysOfYear []time.Time, timezone *time.Location, minimumAge time.Duration, dryRun bool, gracePeriod time.Duration) *Chaoskube {
598654
chaoskube := suite.setup(
599655
labelSelector,
600656
annotations,
601657
namespaces,
658+
namespaceLabels,
602659
includedPodNames,
603660
excludedPodNames,
604661
excludedWeekdays,
@@ -610,6 +667,14 @@ func (suite *Suite) setupWithPods(labelSelector labels.Selector, annotations lab
610667
gracePeriod,
611668
)
612669

670+
for _, namespace := range []v1.Namespace{
671+
util.NewNamespace("default"),
672+
util.NewNamespace("testing"),
673+
} {
674+
_, err := chaoskube.Client.CoreV1().Namespaces().Create(&namespace)
675+
suite.Require().NoError(err)
676+
}
677+
613678
pods := []v1.Pod{
614679
util.NewPod("default", "foo", v1.PodRunning),
615680
util.NewPod("testing", "bar", v1.PodRunning),
@@ -624,7 +689,7 @@ func (suite *Suite) setupWithPods(labelSelector labels.Selector, annotations lab
624689
return chaoskube
625690
}
626691

627-
func (suite *Suite) setup(labelSelector labels.Selector, annotations labels.Selector, namespaces labels.Selector, includedPodNames *regexp.Regexp, excludedPodNames *regexp.Regexp, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, excludedDaysOfYear []time.Time, timezone *time.Location, minimumAge time.Duration, dryRun bool, gracePeriod time.Duration) *Chaoskube {
692+
func (suite *Suite) setup(labelSelector labels.Selector, annotations labels.Selector, namespaces labels.Selector, namespaceLabels labels.Selector, includedPodNames *regexp.Regexp, excludedPodNames *regexp.Regexp, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, excludedDaysOfYear []time.Time, timezone *time.Location, minimumAge time.Duration, dryRun bool, gracePeriod time.Duration) *Chaoskube {
628693
logOutput.Reset()
629694

630695
client := fake.NewSimpleClientset()
@@ -635,6 +700,7 @@ func (suite *Suite) setup(labelSelector labels.Selector, annotations labels.Sele
635700
labelSelector,
636701
annotations,
637702
namespaces,
703+
namespaceLabels,
638704
includedPodNames,
639705
excludedPodNames,
640706
excludedWeekdays,
@@ -738,6 +804,7 @@ func (suite *Suite) TestMinimumAge() {
738804
labels.Everything(),
739805
labels.Everything(),
740806
labels.Everything(),
807+
labels.Everything(),
741808
&regexp.Regexp{},
742809
&regexp.Regexp{},
743810
[]time.Weekday{},

main.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ var (
3535
labelString string
3636
annString string
3737
nsString string
38+
nsLabelString string
3839
includedPodNames *regexp.Regexp
3940
excludedPodNames *regexp.Regexp
4041
excludedWeekdays string
@@ -59,6 +60,7 @@ func init() {
5960
kingpin.Flag("labels", "A set of labels to restrict the list of affected pods. Defaults to everything.").StringVar(&labelString)
6061
kingpin.Flag("annotations", "A set of annotations to restrict the list of affected pods. Defaults to everything.").StringVar(&annString)
6162
kingpin.Flag("namespaces", "A set of namespaces to restrict the list of affected pods. Defaults to everything.").StringVar(&nsString)
63+
kingpin.Flag("namespace-labels", "A set of labels to restrict the list of affected namespaces. Defaults to everything.").StringVar(&nsLabelString)
6264
kingpin.Flag("included-pod-names", "Regular expression that defines which pods to include. All included by default.").RegexpVar(&includedPodNames)
6365
kingpin.Flag("excluded-pod-names", "Regular expression that defines which pods to exclude. None excluded by default.").RegexpVar(&excludedPodNames)
6466
kingpin.Flag("excluded-weekdays", "A list of weekdays when termination is suspended, e.g. Sat,Sun").StringVar(&excludedWeekdays)
@@ -98,6 +100,7 @@ func main() {
98100
"labels": labelString,
99101
"annotations": annString,
100102
"namespaces": nsString,
103+
"namespaceLabels": nsLabelString,
101104
"includedPodNames": includedPodNames,
102105
"excludedPodNames": excludedPodNames,
103106
"excludedWeekdays": excludedWeekdays,
@@ -127,15 +130,17 @@ func main() {
127130
}
128131

129132
var (
130-
labelSelector = parseSelector(labelString)
131-
annotations = parseSelector(annString)
132-
namespaces = parseSelector(nsString)
133+
labelSelector = parseSelector(labelString)
134+
annotations = parseSelector(annString)
135+
namespaces = parseSelector(nsString)
136+
namespaceLabels = parseSelector(nsLabelString)
133137
)
134138

135139
log.WithFields(log.Fields{
136140
"labels": labelSelector,
137141
"annotations": annotations,
138142
"namespaces": namespaces,
143+
"namespaceLabels": namespaceLabels,
139144
"includedPodNames": includedPodNames,
140145
"excludedPodNames": excludedPodNames,
141146
"minimumAge": minimumAge,
@@ -183,6 +188,7 @@ func main() {
183188
labelSelector,
184189
annotations,
185190
namespaces,
191+
namespaceLabels,
186192
includedPodNames,
187193
excludedPodNames,
188194
parsedWeekdays,

util/util.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,15 @@ func NewPod(namespace, name string, phase v1.PodPhase) v1.Pod {
156156
},
157157
}
158158
}
159+
160+
// NewNamespace returns a new namespace instance for testing purposes.
161+
func NewNamespace(name string) v1.Namespace {
162+
return v1.Namespace{
163+
ObjectMeta: metav1.ObjectMeta{
164+
Name: name,
165+
Labels: map[string]string{
166+
"env": name,
167+
},
168+
},
169+
}
170+
}

util/util_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,26 @@ func (suite *Suite) TestFormatDays() {
387387
}
388388
}
389389

390+
func (suite *Suite) TestNewPod() {
391+
pod := NewPod("namespace", "name", "phase")
392+
393+
suite.Equal("v1", pod.APIVersion)
394+
suite.Equal("Pod", pod.Kind)
395+
suite.Equal("namespace", pod.Namespace)
396+
suite.Equal("name", pod.Name)
397+
suite.Equal("name", pod.Labels["app"])
398+
suite.Equal("name", pod.Annotations["chaos"])
399+
suite.Equal("/api/v1/namespaces/namespace/pods/name", pod.SelfLink)
400+
suite.EqualValues("phase", pod.Status.Phase)
401+
}
402+
403+
func (suite *Suite) TestNewNamespace() {
404+
namespace := NewNamespace("name")
405+
406+
suite.Equal("name", namespace.Name)
407+
suite.Equal("name", namespace.Labels["env"])
408+
}
409+
390410
func TestSuite(t *testing.T) {
391411
suite.Run(t, new(Suite))
392412
}

0 commit comments

Comments
 (0)