Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Implement event creation after terminating a pod #105

Merged
merged 2 commits into from
Sep 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ Use `UTC`, `Local` or pick a timezone name from the [(IANA) tz database](https:/
| `--timezone` | timezone from tz database, e.g. "America/New_York", "UTC" or "Local" | (UTC) |
| `--minimum-age` | Minimum age to filter pods by | 0s (matches every pod) |
| `--dry-run` | don't kill pods, only log what would have been done | true |
| `--create-events` | If true, create an event in victims namespace after termination | true |

## Related work

Expand Down
46 changes: 44 additions & 2 deletions chaoskube/chaoskube.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/reference"

"github.com/linki/chaoskube/util"
)
Expand Down Expand Up @@ -42,6 +44,8 @@ type Chaoskube struct {
Logger log.FieldLogger
// dry run will not allow any pod terminations
DryRun bool
// create event with deletion message in victims namespace
CreateEvent bool
// a function to retrieve the current time
Now func() time.Time
}
Expand All @@ -66,7 +70,8 @@ var (
// * 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, minimumAge time.Duration, logger log.FieldLogger, dryRun bool) *Chaoskube {
// * whether to enable/disable event creation
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, createEvent bool) *Chaoskube {
return &Chaoskube{
Client: client,
Labels: labels,
Expand All @@ -79,6 +84,7 @@ func New(client kubernetes.Interface, labels, annotations, namespaces labels.Sel
MinimumAge: minimumAge,
Logger: logger,
DryRun: dryRun,
CreateEvent: createEvent,
Now: time.Now,
}
}
Expand Down Expand Up @@ -191,7 +197,43 @@ func (c *Chaoskube) DeletePod(victim v1.Pod) error {
return nil
}

return c.Client.CoreV1().Pods(victim.Namespace).Delete(victim.Name, nil)
err := c.Client.CoreV1().Pods(victim.Namespace).Delete(victim.Name, nil)
if err != nil {
return err
}

err = c.CreateDeleteEvent(victim)
if err != nil {
c.Logger.WithField("err", err).Error("failed to create deletion event")
}

return nil
}

// CreateDeleteEvent creates an event in victims namespace with an deletion message.
func (c *Chaoskube) CreateDeleteEvent(victim v1.Pod) error {
ref, err := reference.GetReference(scheme.Scheme, victim.DeepCopyObject())
if err != nil {
return err
}

t := metav1.Time{Time: c.Now()}
_, err = c.Client.CoreV1().Events(victim.Namespace).Create(&v1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s.chaos.%x", victim.Name, t.UnixNano()),
Namespace: victim.Namespace,
},
InvolvedObject: *ref,
Reason: "Chaos",
Message: fmt.Sprintf("Deleted pod %s", victim.Name),
FirstTimestamp: t,
LastTimestamp: t,
Count: 1,
Action: "Deleted",
Type: v1.EventTypeNormal,
})

return err
}

// filterByNamespaces filters a list of pods by a given namespace selector.
Expand Down
43 changes: 41 additions & 2 deletions chaoskube/chaoskube_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func (suite *Suite) TestNew() {
minimumAge,
logger,
false,
true,
)
suite.Require().NotNil(chaoskube)

Expand Down Expand Up @@ -85,6 +86,7 @@ func (suite *Suite) TestRunContextCanceled() {
time.UTC,
time.Duration(0),
false,
true,
)

ctx, cancel := context.WithCancel(context.Background())
Expand Down Expand Up @@ -134,6 +136,7 @@ func (suite *Suite) TestCandidates() {
time.UTC,
time.Duration(0),
false,
true,
)

suite.assertCandidates(chaoskube, tt.pods)
Expand Down Expand Up @@ -168,6 +171,7 @@ func (suite *Suite) TestVictim() {
time.UTC,
time.Duration(0),
false,
true,
)

suite.assertVictim(chaoskube, tt.victim)
Expand All @@ -186,6 +190,7 @@ func (suite *Suite) TestNoVictimReturnsError() {
time.UTC,
time.Duration(0),
false,
true,
)

_, err := chaoskube.Victim()
Expand Down Expand Up @@ -214,6 +219,7 @@ func (suite *Suite) TestDeletePod() {
time.UTC,
time.Duration(0),
tt.dryRun,
true,
)

victim := util.NewPod("default", "foo", v1.PodRunning)
Expand Down Expand Up @@ -444,6 +450,7 @@ func (suite *Suite) TestTerminateVictim() {
tt.timezone,
time.Duration(0),
false,
true,
)
chaoskube.Now = tt.now

Expand All @@ -457,6 +464,34 @@ func (suite *Suite) TestTerminateVictim() {
}
}

func (suite *Suite) TestTerminateVictimCreatesEvent() {
chaoskube := suite.setupWithPods(
labels.Everything(),
labels.Everything(),
labels.Everything(),
[]time.Weekday{},
[]util.TimePeriod{},
[]time.Time{},
time.UTC,
time.Duration(0),
false,
true,
)
chaoskube.Now = ThankGodItsFriday{}.Now

err := chaoskube.TerminateVictim()
suite.Require().NoError(err)

events, err := chaoskube.Client.CoreV1().Events(v1.NamespaceAll).List(metav1.ListOptions{})
suite.Require().NoError(err)

suite.Require().Len(events.Items, 1)
event := events.Items[0]

suite.Equal("foo.chaos.-2be96689beac4e00", event.Name)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For anyone who cares: the third part of the event name is the timestamp of ThankGodItsFriday{}.Now() (Fri, 24 Sep 1869 15:04:05 UTC, roughly a hundred years before timestamp 0) in nanoseconds and encoded in base64... 🤷‍♂️

suite.Equal("Deleted pod foo", event.Message)
}

// TestTerminateNoVictimLogsInfo tests that missing victim prints a log message
func (suite *Suite) TestTerminateNoVictimLogsInfo() {
chaoskube := suite.setup(
Expand All @@ -469,6 +504,7 @@ func (suite *Suite) TestTerminateNoVictimLogsInfo() {
time.UTC,
time.Duration(0),
false,
true,
)

err := chaoskube.TerminateVictim()
Expand Down Expand Up @@ -517,7 +553,7 @@ 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, excludedDaysOfYear []time.Time, timezone *time.Location, minimumAge time.Duration, 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, minimumAge time.Duration, dryRun bool, createEvent bool) *Chaoskube {
chaoskube := suite.setup(
labelSelector,
annotations,
Expand All @@ -528,6 +564,7 @@ func (suite *Suite) setupWithPods(labelSelector labels.Selector, annotations lab
timezone,
minimumAge,
dryRun,
createEvent,
)

pods := []v1.Pod{
Expand All @@ -544,7 +581,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, excludedDaysOfYear []time.Time, timezone *time.Location, minimumAge time.Duration, 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, minimumAge time.Duration, dryRun bool, createEvent bool) *Chaoskube {
logOutput.Reset()

return New(
Expand All @@ -559,6 +596,7 @@ func (suite *Suite) setup(labelSelector labels.Selector, annotations labels.Sele
minimumAge,
logger,
dryRun,
createEvent,
)
}

Expand Down Expand Up @@ -658,6 +696,7 @@ func (suite *Suite) TestMinimumAge() {
time.UTC,
tt.minimumAge,
false,
true,
)
chaoskube.Now = tt.now

Expand Down
5 changes: 3 additions & 2 deletions examples/rbac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["list", "delete"]

- apiGroups: [""]
resources: ["events"]
verbs: ["create"]
---

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
Expand Down
4 changes: 4 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ var (
kubeconfig string
interval time.Duration
dryRun bool
createEvent bool
debug bool
metricsAddress string
)
Expand All @@ -59,6 +60,7 @@ func init() {
kingpin.Flag("kubeconfig", "Path to a kubeconfig file").StringVar(&kubeconfig)
kingpin.Flag("interval", "Interval between Pod terminations").Default("10m").DurationVar(&interval)
kingpin.Flag("dry-run", "If true, don't actually do anything.").Default("true").BoolVar(&dryRun)
kingpin.Flag("create-events", "If true, create an event in victims namespace after termination.").Default("true").BoolVar(&createEvent)
kingpin.Flag("debug", "Enable debug logging.").BoolVar(&debug)
kingpin.Flag("metrics-address", "Listening address for metrics handler").Default(":8080").StringVar(&metricsAddress)
}
Expand Down Expand Up @@ -86,6 +88,7 @@ func main() {
"dryRun": dryRun,
"debug": debug,
"metricsAddress": metricsAddress,
"createEvent": createEvent,
}).Debug("reading config")

log.WithFields(log.Fields{
Expand Down Expand Up @@ -161,6 +164,7 @@ func main() {
minimumAge,
log.StandardLogger(),
dryRun,
createEvent,
)

if metricsAddress != "" {
Expand Down
1 change: 1 addition & 0 deletions util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ func NewPod(namespace, name string, phase v1.PodPhase) v1.Pod {
Annotations: map[string]string{
"chaos": name,
},
SelfLink: fmt.Sprintf("/api/v1/namespaces/%s/pods/%s", namespace, name),
},
Status: v1.PodStatus{
Phase: phase,
Expand Down