Skip to content

Commit

Permalink
Merge pull request #224 from qlikcoe/filter-by-kind
Browse files Browse the repository at this point in the history
Filter by OwnerReference's Kind
  • Loading branch information
linki authored Sep 10, 2020
2 parents 7becea1 + 06af502 commit b1c3600
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 5 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,26 @@ INFO[0000] setting pod filter namespaceLabels="!integration"

This will exclude all pods from namespaces with the label `integration`.

You can filter target pods by [OwnerReference's](https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#OwnerReference) kind selector.

```console
$ chaoskube --kinds '!DaemonSet,!StatefulSet'
...
INFO[0000] setting pod filter kinds="!DaemonSet,!StatefulSet"
```

This will exclude any `DaemonSet` and `StatefulSet` pods.

```console
$ chaoskube --kinds 'DaemonSet'
...
INFO[0000] setting pod filter kinds="DaemonSet"
```

This will only include any `DaemonSet` pods.

Please note: any `include` filter will automatically exclude all the pods with no OwnerReference defined.

You can filter pods by name:

```console
Expand Down Expand Up @@ -185,6 +205,7 @@ Use `UTC`, `Local` or pick a timezone name from the [(IANA) tz database](https:/
| `--interval` | interval between pod terminations | 10m |
| `--labels` | label selector to filter pods by | (matches everything) |
| `--annotations` | annotation selector to filter pods by | (matches everything) |
| `--kinds` | owner's kind selector to filter pods by | (all kinds) |
| `--namespaces` | namespace selector to filter pods by | (all namespaces) |
| `--namespace-labels` | label selector to filter namespaces and its pods by | (all namespaces) |
| `--included-pod-names` | regular expression pattern for pod names to include | (all included) |
Expand Down
71 changes: 69 additions & 2 deletions chaoskube/chaoskube.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ type Chaoskube struct {
Labels labels.Selector
// an annotation selector which restricts the pods to choose from
Annotations labels.Selector
// a kind label selector which restricts the kinds to choose from
Kinds labels.Selector
// a namespace selector which restricts the pods to choose from
Namespaces labels.Selector
// a namespace label selector which restricts the namespaces to choose from
Expand All @@ -56,7 +58,7 @@ type Chaoskube struct {
MinimumAge time.Duration
// an instance of logrus.StdLogger to write log messages to
Logger log.FieldLogger
// a terminator that termiantes victim pods
// a terminator that terminates victim pods
Terminator terminator.Terminator
// dry run will not allow any pod terminations
DryRun bool
Expand Down Expand Up @@ -93,7 +95,7 @@ var (
// * a logger implementing logrus.FieldLogger to send log output to
// * what specific terminator to use to imbue chaos on victim pods
// * whether to enable/disable dry-run mode
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, maxKill int, notifier notifier.Notifier) *Chaoskube {
func New(client kubernetes.Interface, labels, annotations, kinds, 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, maxKill int, notifier notifier.Notifier) *Chaoskube {
broadcaster := record.NewBroadcaster()
broadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: client.CoreV1().Events(v1.NamespaceAll)})
recorder := broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "chaoskube"})
Expand All @@ -102,6 +104,7 @@ func New(client kubernetes.Interface, labels, annotations, namespaces, namespace
Client: client,
Labels: labels,
Annotations: annotations,
Kinds: kinds,
Namespaces: namespaces,
NamespaceLabels: namespaceLabels,
IncludedPodNames: includedPodNames,
Expand Down Expand Up @@ -223,6 +226,11 @@ func (c *Chaoskube) Candidates(ctx context.Context) ([]v1.Pod, error) {
return nil, err
}

pods, err = filterByKinds(pods, c.Kinds)
if err != nil {
return nil, err
}

pods = filterByAnnotations(pods, c.Annotations)
pods = filterByPhase(pods, v1.PodRunning)
pods = filterTerminatingPods(pods)
Expand Down Expand Up @@ -269,6 +277,65 @@ func (c *Chaoskube) DeletePod(ctx context.Context, victim v1.Pod) error {
return nil
}

// filterByKinds filters a list of pods by a given kind selector.
func filterByKinds(pods []v1.Pod, kinds labels.Selector) ([]v1.Pod, error) {
// empty filter returns original list
if kinds.Empty() {
return pods, nil
}

// split requirements into including and excluding groups
reqs, _ := kinds.Requirements()
reqIncl := []labels.Requirement{}
reqExcl := []labels.Requirement{}

for _, req := range reqs {
switch req.Operator() {
case selection.Exists:
reqIncl = append(reqIncl, req)
case selection.DoesNotExist:
reqExcl = append(reqExcl, req)
default:
return nil, fmt.Errorf("unsupported operator: %s", req.Operator())
}
}

filteredList := []v1.Pod{}

for _, pod := range pods {
// if there aren't any including requirements, we're in by default
included := len(reqIncl) == 0

// Check owner reference
for _, ref := range pod.GetOwnerReferences() {
// convert the pod's owner kind to an equivalent label selector
selector := labels.Set{ref.Kind: ""}

// include pod if one including requirement matches
for _, req := range reqIncl {
if req.Matches(selector) {
included = true
break
}
}

// exclude pod if it is filtered out by at least one excluding requirement
for _, req := range reqExcl {
if !req.Matches(selector) {
included = false
break
}
}
}

if included {
filteredList = append(filteredList, pod)
}
}

return filteredList, nil
}

// filterByNamespaces filters a list of pods by a given namespace selector.
func filterByNamespaces(pods []v1.Pod, namespaces labels.Selector) ([]v1.Pod, error) {
// empty filter returns original list
Expand Down
97 changes: 95 additions & 2 deletions chaoskube/chaoskube_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func (suite *Suite) TestNew() {
client = fake.NewSimpleClientset()
labelSelector, _ = labels.Parse("foo=bar")
annotations, _ = labels.Parse("baz=waldo")
kinds, _ = labels.Parse("job")
namespaces, _ = labels.Parse("qux")
namespaceLabels, _ = labels.Parse("taz=wubble")
includedPodNames = regexp.MustCompile("foo")
Expand All @@ -69,6 +70,7 @@ func (suite *Suite) TestNew() {
client,
labelSelector,
annotations,
kinds,
namespaces,
namespaceLabels,
includedPodNames,
Expand All @@ -89,6 +91,7 @@ func (suite *Suite) TestNew() {
suite.Equal(client, chaoskube.Client)
suite.Equal("foo=bar", chaoskube.Labels.String())
suite.Equal("baz=waldo", chaoskube.Annotations.String())
suite.Equal("job", chaoskube.Kinds.String())
suite.Equal("qux", chaoskube.Namespaces.String())
suite.Equal("taz=wubble", chaoskube.NamespaceLabels.String())
suite.Equal("foo", chaoskube.IncludedPodNames.String())
Expand All @@ -110,6 +113,7 @@ func (suite *Suite) TestRunContextCanceled() {
labels.Everything(),
labels.Everything(),
labels.Everything(),
labels.Everything(),
&regexp.Regexp{},
&regexp.Regexp{},
[]time.Weekday{},
Expand Down Expand Up @@ -163,6 +167,7 @@ func (suite *Suite) TestCandidates() {
chaoskube := suite.setupWithPods(
labelSelector,
annotationSelector,
labels.Everything(),
namespaceSelector,
labels.Everything(),
nil,
Expand Down Expand Up @@ -208,6 +213,7 @@ func (suite *Suite) TestCandidatesNamespaceLabels() {
labels.Everything(),
labels.Everything(),
labels.Everything(),
labels.Everything(),
namespaceLabels,
nil,
nil,
Expand Down Expand Up @@ -251,6 +257,7 @@ func (suite *Suite) TestCandidatesPodNameRegexp() {
labels.Everything(),
labels.Everything(),
labels.Everything(),
labels.Everything(),
tt.includedPodNames,
tt.excludedPodNames,
[]time.Weekday{},
Expand Down Expand Up @@ -290,6 +297,7 @@ func (suite *Suite) TestVictim() {
labels.Everything(),
labels.Everything(),
labels.Everything(),
labels.Everything(),
&regexp.Regexp{},
&regexp.Regexp{},
[]time.Weekday{},
Expand Down Expand Up @@ -342,6 +350,7 @@ func (suite *Suite) TestVictims() {
labels.Everything(),
labels.Everything(),
labels.Everything(),
labels.Everything(),
&regexp.Regexp{},
&regexp.Regexp{},
[]time.Weekday{},
Expand All @@ -366,6 +375,7 @@ func (suite *Suite) TestNoVictimReturnsError() {
labels.Everything(),
labels.Everything(),
labels.Everything(),
labels.Everything(),
&regexp.Regexp{},
&regexp.Regexp{},
[]time.Weekday{},
Expand Down Expand Up @@ -400,6 +410,7 @@ func (suite *Suite) TestDeletePod() {
labels.Everything(),
labels.Everything(),
labels.Everything(),
labels.Everything(),
&regexp.Regexp{},
&regexp.Regexp{},
[]time.Weekday{},
Expand Down Expand Up @@ -428,6 +439,7 @@ func (suite *Suite) TestDeletePodNotFound() {
labels.Everything(),
labels.Everything(),
labels.Everything(),
labels.Everything(),
&regexp.Regexp{},
&regexp.Regexp{},
[]time.Weekday{},
Expand Down Expand Up @@ -659,6 +671,7 @@ func (suite *Suite) TestTerminateVictim() {
labels.Everything(),
labels.Everything(),
labels.Everything(),
labels.Everything(),
&regexp.Regexp{},
&regexp.Regexp{},
tt.excludedWeekdays,
Expand Down Expand Up @@ -688,6 +701,7 @@ func (suite *Suite) TestTerminateNoVictimLogsInfo() {
labels.Everything(),
labels.Everything(),
labels.Everything(),
labels.Everything(),
&regexp.Regexp{},
&regexp.Regexp{},
[]time.Weekday{},
Expand Down Expand Up @@ -732,10 +746,11 @@ func (suite *Suite) assertNotified(notifier *notifier.Noop) {
suite.Assert().Greater(notifier.Calls, 0)
}

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 {
func (suite *Suite) setupWithPods(labelSelector labels.Selector, annotations labels.Selector, kinds 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 {
chaoskube := suite.setup(
labelSelector,
annotations,
kinds,
namespaces,
namespaceLabels,
includedPodNames,
Expand Down Expand Up @@ -783,7 +798,7 @@ func (suite *Suite) createPods(client kubernetes.Interface, podsInfo []podInfo)
}
}

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, maxKill int) *Chaoskube {
func (suite *Suite) setup(labelSelector labels.Selector, annotations labels.Selector, kinds 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, maxKill int) *Chaoskube {
logOutput.Reset()

client := fake.NewSimpleClientset()
Expand All @@ -793,6 +808,7 @@ func (suite *Suite) setup(labelSelector labels.Selector, annotations labels.Sele
client,
labelSelector,
annotations,
kinds,
namespaces,
namespaceLabels,
includedPodNames,
Expand Down Expand Up @@ -901,6 +917,7 @@ func (suite *Suite) TestMinimumAge() {
labels.Everything(),
labels.Everything(),
labels.Everything(),
labels.Everything(),
&regexp.Regexp{},
&regexp.Regexp{},
[]time.Weekday{},
Expand Down Expand Up @@ -942,6 +959,81 @@ func (suite *Suite) TestFilterDeletedPods() {
suite.Equal(pods[0].Name, "running")
}

func (suite *Suite) TestFilterByKinds() {
foo := util.NewPodWithOwner("default", "foo", v1.PodRunning, "parent-1")
foo1 := util.NewPodWithOwner("default", "foo-1", v1.PodRunning, "parent-2")
bar := util.NewPodWithOwner("default", "bar", v1.PodRunning, "other-parent")
baz := util.NewPod("default", "baz", v1.PodRunning)
baz1 := util.NewPod("default", "baz-1", v1.PodRunning)

for _, tt := range []struct {
name string
kinds string
pods []v1.Pod
expected []v1.Pod
}{
{
name: "2 pods, one with owner ref",
kinds: "testkind",
pods: []v1.Pod{foo, baz},
expected: []v1.Pod{foo},
},
{
name: "5 pods, 3 with owner ref",
kinds: "!testkind",
pods: []v1.Pod{foo, foo1, baz, bar, baz1},
expected: []v1.Pod{baz, baz1},
},
{
name: "3 pods with owner ref, different kind",
kinds: "!testkind",
pods: []v1.Pod{foo, foo1, bar},
expected: []v1.Pod{},
},
{
name: "3 pods with owner ref, different kind",
kinds: "!testkind,!job",
pods: []v1.Pod{foo, baz},
expected: []v1.Pod{baz},
},
{
name: "3 pods with owner ref, different kind",
kinds: "testkind,job",
pods: []v1.Pod{foo, foo1, bar, baz},
expected: []v1.Pod{foo, foo1, bar},
},
{
name: "3 pods with owner ref, different kind",
kinds: "!testkind,job",
pods: []v1.Pod{foo, foo1, bar, baz},
expected: []v1.Pod{},
},
{
name: "3 pods with owner ref, different kind",
kinds: "testkind,!job",
pods: []v1.Pod{foo, foo1, bar, baz},
expected: []v1.Pod{foo, foo1, bar},
},
{
name: "3 pods with owner ref, different kind",
kinds: "job",
pods: []v1.Pod{foo, foo1, bar, baz},
expected: []v1.Pod{},
},
} {
kindsSelector, err := labels.Parse(tt.kinds)
suite.Require().NoError(err)

results, err := filterByKinds(tt.pods, kindsSelector)
suite.Require().Len(results, len(tt.expected))
suite.Require().NoError(err)

for i, result := range results {
suite.Assert().Equal(tt.expected[i], result, tt.name)
}
}
}

func (suite *Suite) TestFilterByOwnerReference() {
foo := util.NewPodWithOwner("default", "foo", v1.PodRunning, "parent")
foo1 := util.NewPodWithOwner("default", "foo-1", v1.PodRunning, "parent")
Expand Down Expand Up @@ -1008,6 +1100,7 @@ func (suite *Suite) TestNotifierCall() {
labels.Everything(),
labels.Everything(),
labels.Everything(),
labels.Everything(),
&regexp.Regexp{},
&regexp.Regexp{},
[]time.Weekday{},
Expand Down
2 changes: 2 additions & 0 deletions examples/chaoskube.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ spec:
- --labels=environment=test
# only consider pods with this annotation
- --annotations=chaos.alpha.kubernetes.io/enabled=true
# exclude all DaemonSet pods
- --kinds=!DaemonSet
# exclude all pods in the kube-system namespace
- --namespaces=!kube-system
# don't kill anything on weekends
Expand Down
Loading

0 comments on commit b1c3600

Please sign in to comment.