Skip to content

Commit 4aed1a8

Browse files
committed
Emit events for all TaskRun lifecycle events
Emit events for additional TaskRun lifecyle events: - taskrun started - taskrun running - taskrun timeout Fix the logic in events.go to compare semantic equality as opposed to raw pointer equality. Fix broken EmitEvents unit tests and extend them to cover new functionality. Extend reconcile test to verify new events are sent. To do so, get the event recorder from the context when creating the controller - if avaialble. This allows using the fake recorder for testing instead of having to look for event related actions in the fake client go action list. Add documentation on events. Fixes #2328 Work towards #2082
1 parent d35df98 commit 4aed1a8

File tree

10 files changed

+308
-181
lines changed

10 files changed

+308
-181
lines changed

docs/events.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<!--
2+
---
3+
linkTitle: "Events"
4+
weight: 2
5+
---
6+
-->
7+
# Events
8+
9+
Tekton runtime resources, specifically `TaskRuns` and `PipelineRuns`,
10+
emit events when they are executed, so that users can monitor their lifecycle
11+
and react to it. Tekton emits [kubernetes events](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#event-v1-core), that can be retrieve from the resource via
12+
`kubectl describe [resource]`.
13+
14+
No events are emitted for `Conditions` today (https://github.com/tektoncd/pipeline/issues/2461).
15+
16+
## TaskRuns
17+
18+
`TaskRun` events are generated for the following `Reasons`:
19+
- `Started`: this is triggered the first time the `TaskRun` is picked by the
20+
reconciler from its work queue, so it only happens if web-hook validation was
21+
successful. Note that this event does not imply that a step started executing,
22+
as several conditions must be met first:
23+
- task and bound resource validation must be successful
24+
- attached conditions must run successfully
25+
- the `Pod` associated to the `TaskRun` must be successfully scheduled
26+
- `Succeeded`: this is triggered once all steps in the `TaskRun` are executed
27+
successfully, including post-steps injected by Tekton.
28+
- `Failed`: this is triggered if the `TaskRun` is completed, but not successfully.
29+
Causes of failure may be: one the steps failed, the `TaskRun` was cancelled or
30+
the `TaskRun` timed out.
31+
32+
## PipelineRuns
33+
34+
`PipelineRun` events are generated for the following `Reasons`:
35+
- `Succeeded`: this is triggered once all `Tasks` reachable via the DAG are
36+
executed successfully.
37+
- `Failed`: this is triggered if the `PipelineRun` is completed, but not
38+
successfully. Causes of failure may be: one the `Tasks` failed or the
39+
`PipelineRun` was cancelled.

docs/pipelineruns.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ weight: 4
1818
- [Specifying `LimitRange` values](#specifying-limitrange-values)
1919
- [Configuring a failure timeout](#configuring-a-failure-timeout)
2020
- [Cancelling a `PipelineRun`](#cancelling-a-pipelinerun)
21+
- [Events](events.md#pipelineruns)
22+
2123

2224

2325
## Overview

docs/taskruns.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ weight: 2
2222
- [Monitoring `Steps`](#monitoring-steps)
2323
- [Monitoring `Results`](#monitoring-results)
2424
- [Cancelling a `TaskRun`](#cancelling-a-taskrun)
25+
- [Events](events.md#taskruns)
2526
- [Code examples](#code-examples)
2627
- [Example `TaskRun` with a referenced `Task`](#example-taskrun-with-a-referenced-task)
2728
- [Example `TaskRun` with an embedded `Task`](#example-taskrun-with-an-embedded-task)

pkg/reconciler/event.go

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package reconciler
1717

1818
import (
1919
corev1 "k8s.io/api/core/v1"
20+
"k8s.io/apimachinery/pkg/api/equality"
2021
"k8s.io/apimachinery/pkg/runtime"
2122
"k8s.io/client-go/tools/record"
2223
"knative.dev/pkg/apis"
@@ -27,20 +28,38 @@ const (
2728
EventReasonSucceded = "Succeeded"
2829
// EventReasonFailed is the reason set for events about unsuccessful completion of TaskRuns / PipelineRuns
2930
EventReasonFailed = "Failed"
31+
// EventReasonStarted is the reason set for events about the start of TaskRuns / PipelineRuns
32+
EventReasonStarted = "Started"
3033
)
3134

32-
// EmitEvent emits success or failed event for object
33-
// if afterCondition is different from beforeCondition
35+
// EmitEvent emits an event for object if afterCondition is different from beforeCondition
36+
//
37+
// Status "ConditionUnknown":
38+
// beforeCondition == nil, emit EventReasonStarted
39+
// beforeCondition != mil, emit afterCondition.Reason
40+
//
41+
// Status "ConditionTrue": emit EventReasonSucceded
42+
// Status "ConditionFalse": emit EventReasonFailed
43+
//
3444
func EmitEvent(c record.EventRecorder, beforeCondition *apis.Condition, afterCondition *apis.Condition, object runtime.Object) {
35-
if beforeCondition != afterCondition && afterCondition != nil {
36-
// Create events when the obj result is in.
45+
if !equality.Semantic.DeepEqual(beforeCondition, afterCondition) && afterCondition != nil {
46+
// If the condition changed, and the target condition is not empty, we send an event
3747
switch afterCondition.Status {
3848
case corev1.ConditionTrue:
3949
c.Event(object, corev1.EventTypeNormal, EventReasonSucceded, afterCondition.Message)
40-
case corev1.ConditionUnknown:
41-
c.Event(object, corev1.EventTypeNormal, afterCondition.Reason, afterCondition.Message)
4250
case corev1.ConditionFalse:
4351
c.Event(object, corev1.EventTypeWarning, EventReasonFailed, afterCondition.Message)
52+
case corev1.ConditionUnknown:
53+
if beforeCondition == nil {
54+
// If the condition changed, the status is "unknown", and there was no condition before,
55+
// we emit the "Started event". We ignore further updates of the "uknown" status.
56+
c.Event(object, corev1.EventTypeNormal, EventReasonStarted, "")
57+
} else {
58+
// If the condition changed, the status is "unknown", and there was a condition before,
59+
// we emit an event that matches the reason and message of the condition.
60+
// This is used for instance to signal the transition from "started" to "running"
61+
c.Event(object, corev1.EventTypeNormal, afterCondition.Reason, afterCondition.Message)
62+
}
4463
}
4564
}
4665
}

pkg/reconciler/event_test.go

Lines changed: 87 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,38 +21,45 @@ import (
2121
"time"
2222

2323
corev1 "k8s.io/api/core/v1"
24+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2425
"k8s.io/client-go/tools/record"
2526
"knative.dev/pkg/apis"
2627
)
2728

2829
func TestEmitEvent(t *testing.T) {
2930
testcases := []struct {
30-
name string
31-
before *apis.Condition
32-
after *apis.Condition
33-
expectEvent bool
31+
name string
32+
before *apis.Condition
33+
after *apis.Condition
34+
expectEvent bool
35+
expectedEvent string
3436
}{{
35-
name: "unknown to true",
37+
name: "unknown to true with message",
3638
before: &apis.Condition{
3739
Type: apis.ConditionSucceeded,
3840
Status: corev1.ConditionUnknown,
3941
},
4042
after: &apis.Condition{
41-
Type: apis.ConditionSucceeded,
42-
Status: corev1.ConditionTrue,
43+
Type: apis.ConditionSucceeded,
44+
Status: corev1.ConditionTrue,
45+
Message: "all done",
4346
},
44-
expectEvent: true,
47+
expectEvent: true,
48+
expectedEvent: "Normal Succeeded all done",
4549
}, {
4650
name: "true to true",
4751
before: &apis.Condition{
48-
Type: apis.ConditionSucceeded,
49-
Status: corev1.ConditionTrue,
52+
Type: apis.ConditionSucceeded,
53+
Status: corev1.ConditionTrue,
54+
LastTransitionTime: apis.VolatileTime{Inner: metav1.NewTime(time.Now())},
5055
},
5156
after: &apis.Condition{
52-
Type: apis.ConditionSucceeded,
53-
Status: corev1.ConditionTrue,
57+
Type: apis.ConditionSucceeded,
58+
Status: corev1.ConditionTrue,
59+
LastTransitionTime: apis.VolatileTime{Inner: metav1.NewTime(time.Now().Add(5 * time.Minute))},
5460
},
55-
expectEvent: false,
61+
expectEvent: false,
62+
expectedEvent: "",
5663
}, {
5764
name: "false to false",
5865
before: &apis.Condition{
@@ -63,23 +70,74 @@ func TestEmitEvent(t *testing.T) {
6370
Type: apis.ConditionSucceeded,
6471
Status: corev1.ConditionFalse,
6572
},
66-
expectEvent: false,
73+
expectEvent: false,
74+
expectedEvent: "",
75+
}, {
76+
name: "unknown to unknown",
77+
before: &apis.Condition{
78+
Type: apis.ConditionSucceeded,
79+
Status: corev1.ConditionUnknown,
80+
Reason: "",
81+
Message: "",
82+
},
83+
after: &apis.Condition{
84+
Type: apis.ConditionSucceeded,
85+
Status: corev1.ConditionUnknown,
86+
Reason: "foo",
87+
Message: "bar",
88+
},
89+
expectEvent: true,
90+
expectedEvent: "Normal foo bar",
6791
}, {
6892
name: "true to nil",
6993
after: nil,
7094
before: &apis.Condition{
7195
Type: apis.ConditionSucceeded,
7296
Status: corev1.ConditionTrue,
7397
},
74-
expectEvent: true,
98+
expectEvent: false,
99+
expectedEvent: "",
75100
}, {
76101
name: "nil to true",
77102
before: nil,
78103
after: &apis.Condition{
79104
Type: apis.ConditionSucceeded,
80105
Status: corev1.ConditionTrue,
81106
},
82-
expectEvent: true,
107+
expectEvent: true,
108+
expectedEvent: "Normal Succeeded ",
109+
}, {
110+
name: "nil to unknown with message",
111+
before: nil,
112+
after: &apis.Condition{
113+
Type: apis.ConditionSucceeded,
114+
Status: corev1.ConditionUnknown,
115+
Message: "just starting",
116+
},
117+
expectEvent: true,
118+
expectedEvent: "Normal Started ",
119+
}, {
120+
name: "unknown to false with message",
121+
before: &apis.Condition{
122+
Type: apis.ConditionSucceeded,
123+
Status: corev1.ConditionUnknown,
124+
},
125+
after: &apis.Condition{
126+
Type: apis.ConditionSucceeded,
127+
Status: corev1.ConditionFalse,
128+
Message: "really bad",
129+
},
130+
expectEvent: true,
131+
expectedEvent: "Warning Failed really bad",
132+
}, {
133+
name: "nil to false",
134+
before: nil,
135+
after: &apis.Condition{
136+
Type: apis.ConditionSucceeded,
137+
Status: corev1.ConditionFalse,
138+
},
139+
expectEvent: true,
140+
expectedEvent: "Warning Failed ",
83141
}}
84142

85143
for _, ts := range testcases {
@@ -90,12 +148,21 @@ func TestEmitEvent(t *testing.T) {
90148

91149
select {
92150
case event := <-fr.Events:
93-
if ts.expectEvent && event == "" {
94-
t.Errorf("Expected event but got empty for %s", ts.name)
151+
if event == "" {
152+
// The fake recorder reported empty, it should not happen
153+
t.Fatalf("Expected event but got empty for %s", ts.name)
95154
}
96-
case <-timer.C:
97155
if !ts.expectEvent {
98-
t.Errorf("Unexpected event but got for %s", ts.name)
156+
// The fake recorder reported an event which we did not expect
157+
t.Errorf("Unxpected event \"%s\" but got one for %s", event, ts.name)
158+
}
159+
if !(event == ts.expectedEvent) {
160+
t.Errorf("Expected event \"%s\" but got \"%s\" instead for %s", ts.expectedEvent, event, ts.name)
161+
}
162+
case <-timer.C:
163+
if ts.expectEvent {
164+
// The fake recorder did not report, the timer imeout expired
165+
t.Errorf("Expected event but got none for %s", ts.name)
99166
}
100167
}
101168
}

pkg/reconciler/taskrun/cancel.go

Lines changed: 0 additions & 77 deletions
This file was deleted.

pkg/reconciler/taskrun/controller.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const (
4444
resyncPeriod = 10 * time.Hour
4545
)
4646

47+
// NewController instantiates a new controller.Impl from knative.dev/pkg/controller
4748
func NewController(images pipeline.Images) func(context.Context, configmap.Watcher) *controller.Impl {
4849
return func(ctx context.Context, cmw configmap.Watcher) *controller.Impl {
4950
logger := logging.FromContext(ctx)
@@ -66,6 +67,7 @@ func NewController(images pipeline.Images) func(context.Context, configmap.Watch
6667
ConfigMapWatcher: cmw,
6768
ResyncPeriod: resyncPeriod,
6869
Logger: logger,
70+
Recorder: controller.GetEventRecorder(ctx),
6971
}
7072

7173
entrypointCache, err := pod.NewEntrypointCache(kubeclientset)

pkg/reconciler/taskrun/resources/cloudevent/cloud_event_controller.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ func cloudEventDeliveryFromTargets(targets []string) []v1alpha1.CloudEventDelive
6666
}
6767

6868
// SendCloudEvents is used by the TaskRun controller to send cloud events once
69-
// the TaskRun is complete. `tr` is used to obtain the list of targets but also
70-
// to construct the body of the
69+
// the TaskRun is complete. `tr` is used to obtain the list of targets
7170
func SendCloudEvents(tr *v1alpha1.TaskRun, ceclient CEClient, logger *zap.SugaredLogger) error {
7271
logger = logger.With(zap.String("taskrun", tr.Name))
7372

0 commit comments

Comments
 (0)