Skip to content

Commit 3eb8416

Browse files
authored
chore(testing): add eventloop tests, improve test coverage (#61)
* chore(testing): add eventloop tests, improve test coverage * chore(testing): reduce flaky testing count, supress logs
1 parent e3158bb commit 3eb8416

9 files changed

Lines changed: 898 additions & 150 deletions

File tree

Makefile

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ benchmark: tmp/coverage ## Run the benchmarks
4444
.PHONY: test-flakiness
4545
test-flakiness: tmp/coverage ## Run the unit tests with a high count to ensure they are not flaky
4646
@echo "🧪 running the unit tests with a high count to ensure they are not flaky …"
47-
# Yes, we really can run the tests 10000 times in just a few seconds
48-
go test -timeout 2m -count 10000 -failfast -skip '^Example' ./...
47+
go test -timeout 2m -count 100 -failfast -skip '^Example' ./...
4948

5049
.PHONY: lint
5150
lint: ## Run the linters

doc/ARCHITECTURE.md

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ The Events API is also monitored to gather the `image_pull` metric type.
1111
### Data-flow
1212

1313
The [`main`](../cmd/kube-transition-metrics/main.go) function starts the
14-
[`PodCollector`](../internal/statistics/pod_collector.go), the
15-
[`PodStatisticEventLoop`](../internal/statistics/event_loop.go), the
16-
[`ImagePullStatisticEventLoop`](../internal/statistics/event_loop.go), and the HTTP server.
14+
[`PodCollector`](../internal/statistics/types/types.go), the
15+
[`PodStatisticEventLoop`](../internal/statistics/types/types.go), the
16+
[`ImagePullStatisticEventLoop`](../internal/statistics/types/types.go), and the HTTP server.
17+
`PodCollector` is an interface defined in [`internal/statistics/types`](../internal/statistics/types/types.go),
18+
implemented by the private `podCollector` struct in [`internal/statistics`](../internal/statistics/pod_collector.go).
1719
The `PodCollector` gets the list of currently existing pods.
1820
Any pods created before the `PodCollector` starts up cannot be tracked, as we missed crucial milestones in the Pod's
1921
life-cycle.
@@ -41,9 +43,9 @@ title: "kube-transition-metrics Data-flow"
4143
---
4244
flowchart TD
4345
main["./cmd/kube-transition-metrics.main()"]
44-
PodCollector["./internal/statistics.PodCollector"]
45-
PodStatisticEventLoop["./internal/statistics.PodStatisticEventLoop"]
46-
ImagePullStatisticEventLoop["./internal/statistics.ImagePullStatisticEventLoop"]
46+
PodCollector["./internal/statistics/types.PodCollector"]
47+
PodStatisticEventLoop["./internal/statistics/types.PodStatisticEventLoop"]
48+
ImagePullStatisticEventLoop["./internal/statistics/types.ImagePullStatisticEventLoop"]
4749
imagePullCollector["./internal/statistics.imagePullCollector"]
4850
Stdout["/dev/stdout"]
4951
@@ -63,24 +65,24 @@ flowchart TD
6365

6466
### Pod Collector zoom-in
6567

66-
The `PodCollector` goroutine receives added, modified, and deleted Pod events from the Kubernetes API.
67-
When Pods are added, the `PodCollector` sends an event to the `PodStatisticEventLoop` to create a new tracked
68+
The `podCollector` goroutine receives added, modified, and deleted Pod events from the Kubernetes API.
69+
When Pods are added, the `podCollector` sends an event to the `PodStatisticEventLoop` to create a new tracked
6870
[`PodStatistic`](../internal/statistics/state/pod.go), and launches a new `imagePullCollector` routine to track Events
6971
involving the Pod UID.
70-
When Pods are modified, the `PodStatisticEventLoop` receives an event to update the `PodStatistic`.
71-
When Pods are deleted , the `PodStatisticEventLoop` receives an event to remove the `PodStatistic` from tracking,
72-
then the `imagePullCollector` routine is canceled for this Pod.
72+
When Pods are modified, the `podCollector` sends an event to the `PodStatisticEventLoop` to update the `PodStatistic`.
73+
When Pods are deleted, the `podCollector` sends an event to the `PodStatisticEventLoop` to remove the `PodStatistic`
74+
from tracking, then the `imagePullCollector` routine is canceled for this Pod.
7375

7476
```mermaid
7577
---
7678
title: "kube-transition-metrics Pod Collector"
7779
---
7880
flowchart TD
79-
PodCollector["./internal/statistics.PodCollector"]
81+
PodCollector["./internal/statistics/types.PodCollector"]
8082
imagePullCollector["./internal/statistics.imagePullCollector"]
8183
PodsWatch["k8s.io/api/core/v1.PodInterface"]
8284
Pod["k8s.io/api/core/v1.Pod"]
83-
PodStatisticEventLoop["./internal/statistics.PodStatisticEventLoop"]
85+
PodStatisticEventLoop["./internal/statistics/types.PodStatisticEventLoop"]
8486
8587
PodCollector
8688
-->|"Watch(...)"| PodsWatch
@@ -108,7 +110,7 @@ flowchart TD
108110
imagePullCollector["./internal/statistics.imagePullCollector"]
109111
EventsWatch["k8s.io/api/core/v1.EventInterface"]
110112
Event["k8s.io/api/core/v1.Event"]
111-
ImagePullStatisticEventLoop["./internal/statistics.ImagePulltatisticEventLoop"]
113+
ImagePullStatisticEventLoop["./internal/statistics/types.ImagePullStatisticEventLoop"]
112114
113115
imagePullCollector
114116
-->|"Watch(...)"| EventsWatch
@@ -122,7 +124,9 @@ flowchart TD
122124

123125
The data model for the pod statistics is composed of the following main components:
124126

125-
- `PodStatisticEventLoop`: The main event loop that handles the [`PodStatistics`](../internal/statistics/state/pod.go)
127+
- [`PodStatisticEventLoop`](../internal/statistics/types/types.go): An interface defined in
128+
`internal/statistics/types`, implemented by the private `podStatisticEventLoop` in `internal/statistics`.
129+
The main event loop that handles the [`PodStatistics`](../internal/statistics/state/pod.go)
126130
and has exclusive access to modify the statistics through dispatched events.
127131
- `PodStatistics`: The statistics for all tracked pods, which contains a map of pod UIDs to their respective
128132
`PodStatistic` as well as a list of UIDs that should not be tracked.
@@ -141,7 +145,7 @@ The data model for the pod statistics is composed of the following main componen
141145
title: "kube-transition-metrics Pod Statistic Data Model"
142146
---
143147
flowchart TD
144-
PodStatisticEventLoop["./internal/statistics.PodStatisticEventLoop"]
148+
PodStatisticEventLoop["./internal/statistics/types.PodStatisticEventLoop"]
145149
PodStatistics["./internal/statistics/state.PodStatistics"]
146150
PodStatistic["./internal/statistics/state.PodStatistic"]
147151
blacklistUIDs["[]k8s.io/apimachinery/pkg/types.UID"]
@@ -163,8 +167,10 @@ flowchart TD
163167

164168
The data model for the image pull statistics is composed of the following main components:
165169

166-
- [`ImagePullStatisticEventLoop`](../internal/statistics/event_loop.go): The main event loop that handles the
167-
`PodImagePullStatistic` and has exclusive access to modify the statistics through dispatched events.
170+
- [`ImagePullStatisticEventLoop`](../internal/statistics/types/types.go): An interface defined in
171+
`internal/statistics/types`, implemented by the private `imagePullStatisticEventLoop` in `internal/statistics`.
172+
The main event loop that handles the `PodImagePullStatistic` and has exclusive access to modify the statistics
173+
through dispatched events.
168174
- [`ImagePullStatistics`](../internal/statistics/state/image_pull.go): The statistics for all tracked pods, which
169175
contains a map of pod UIDs to their respective `PodImagePullStatistic`.
170176
- `PodImagePullStatistic`: A collection of the
@@ -176,7 +182,7 @@ The data model for the image pull statistics is composed of the following main c
176182
title: "kube-transition-metrics Image Pull Statistic Data Model"
177183
---
178184
flowchart TD
179-
ImagePullStatisticEventLoop["./internal/statistics.ImagePullStatisticEventLoop"]
185+
ImagePullStatisticEventLoop["./internal/statistics/types.ImagePullStatisticEventLoop"]
180186
ImagePullStatistics["./internal/statistics/state.ImagePullStatistics"]
181187
PodImagePullStatistic["./internal/statistics/state.PodImagePullStatistic"]
182188
ContainerImagePullStatistic["./internal/statistics/state.ContainerImagePullStatistic"]

internal/statistics/event_loop.go

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,21 @@ import (
2121
apimachinerytypes "k8s.io/apimachinery/pkg/types"
2222
)
2323

24-
// PodStatisticEventLoop loops over pod statistic events sent by collectors to track and update metrics.
25-
type PodStatisticEventLoop struct {
24+
// podStatisticEventLoop loops over pod statistic events sent by collectors to track and update metrics.
25+
type podStatisticEventLoop struct {
2626
safeconcurrencytypes.EventLoop[*state.PodStatistics]
2727

2828
options *options.Options
2929
watcherChan <-chan struct{}
3030
metricOutput io.Writer
3131
}
3232

33-
// NewStatisticEventLoop creates a new StatisticEventHandler which filters out events for the provided
33+
// NewStatisticEventLoop creates a new podStatisticEventLoop which filters out events for the provided
3434
// initialSyncBlacklist Pod UIDs.
35-
func NewStatisticEventLoop(options *options.Options, metricOutput io.Writer) *PodStatisticEventLoop {
35+
//
36+
// The returned *podStatisticEventLoop implements
37+
// [github.com/BackMarket-oss/kube-transition-metrics/internal/statistics/types.PodStatisticEventLoop].
38+
func NewStatisticEventLoop(options *options.Options, metricOutput io.Writer) *podStatisticEventLoop {
3639
s := state.NewPodStatistics([]apimachinerytypes.UID{})
3740
snapshot := snapshot.NewCopyable[*state.PodStatistics](s)
3841

@@ -43,33 +46,33 @@ func NewStatisticEventLoop(options *options.Options, metricOutput io.Writer) *Po
4346
//nolint:gosec // We already check that options.StatisticEventQueueLength is less than 0, and panic otherwise.
4447
buffer := uint(options.StatisticEventQueueLength)
4548

46-
return &PodStatisticEventLoop{
49+
return &podStatisticEventLoop{
4750
options: options,
4851
EventLoop: eventloop.NewBuffered[*state.PodStatistics](snapshot, buffer),
4952
metricOutput: metricOutput,
5053
}
5154
}
5255

5356
// Start starts the event loop and begins watching the state of the event loop.
54-
// Start implements [eventloop.EventLoop.Start].
55-
func (el *PodStatisticEventLoop) Start() {
57+
// Start implements [safeconcurrencytypes.EventLoop.Start].
58+
func (el *podStatisticEventLoop) Start() {
5659
el.EventLoop.Start()
5760
// EventLoop.Start() will panic if the event loop is already started, so we can be sure to do this assignment only
5861
// once.
5962
el.watcherChan = eventloop.WatchState(context.TODO(), el.EventLoop, el.watcher)
6063
}
6164

6265
// Close closes the event loop and waits for the watcher to finish.
63-
// Close implements [eventloop.EventLoop.Close].
64-
func (el *PodStatisticEventLoop) Close() {
66+
// Close implements [safeconcurrencytypes.EventLoop.Close].
67+
func (el *podStatisticEventLoop) Close() {
6568
el.EventLoop.Close()
6669
// Wait for the watcher to finish too.
6770
<-el.watcherChan
6871
}
6972

7073
// Send sends an event to the event loop and tracks the time it took to process the event.
71-
// Send implements [eventloop.EventLoop.Send].
72-
func (el *PodStatisticEventLoop) Send(
74+
// Send implements [safeconcurrencytypes.EventLoop.Send].
75+
func (el *podStatisticEventLoop) Send(
7376
ctx context.Context,
7477
event safeconcurrencytypes.Event[*state.PodStatistics],
7578
) (safeconcurrencytypes.GenerationID, error) {
@@ -94,7 +97,8 @@ func (el *PodStatisticEventLoop) Send(
9497
}
9598

9699
// PodUpdate sends an event to update the pod statistic for a pod based on the latest Kubernetes Pod.
97-
func (el *PodStatisticEventLoop) PodUpdate(
100+
// PodUpdate implements [types.PodStatisticEventLoop.PodUpdate].
101+
func (el *podStatisticEventLoop) PodUpdate(
98102
ctx context.Context,
99103
pod *corev1.Pod,
100104
) (safeconcurrencytypes.GenerationID, error) {
@@ -108,7 +112,8 @@ func (el *PodStatisticEventLoop) PodUpdate(
108112

109113
// PodDelete sends an event to stop tracking the pod statistic for a pod after it has been deleted from the Kubernetes
110114
// API.
111-
func (el *PodStatisticEventLoop) PodDelete(
115+
// PodDelete implements [types.PodStatisticEventLoop.PodDelete].
116+
func (el *podStatisticEventLoop) PodDelete(
112117
ctx context.Context,
113118
pod *corev1.Pod,
114119
) (safeconcurrencytypes.GenerationID, error) {
@@ -120,7 +125,8 @@ func (el *PodStatisticEventLoop) PodDelete(
120125
}
121126

122127
// PodResync sends an event to resync the event loop if the Kubernetes Watch API times out and events are lost.
123-
func (el *PodStatisticEventLoop) PodResync(
128+
// PodResync implements [types.PodStatisticEventLoop.PodResync].
129+
func (el *podStatisticEventLoop) PodResync(
124130
ctx context.Context,
125131
blacklistUIDs []apimachinerytypes.UID,
126132
) (safeconcurrencytypes.GenerationID, error) {
@@ -131,7 +137,7 @@ func (el *PodStatisticEventLoop) PodResync(
131137
}
132138

133139
// watcher watches the state of the event loop and updates the prometheus metrics.
134-
func (el *PodStatisticEventLoop) watcher(
140+
func (el *podStatisticEventLoop) watcher(
135141
ctx context.Context,
136142
s safeconcurrencytypes.StateSnapshot[*state.PodStatistics],
137143
) bool {
@@ -140,9 +146,9 @@ func (el *PodStatisticEventLoop) watcher(
140146
return true
141147
}
142148

143-
// ImagePullStatisticEventLoop loops over image pull statistic events sent by collectors to track and update metrics for
149+
// imagePullStatisticEventLoop loops over image pull statistic events sent by collectors to track and update metrics for
144150
// image pulls.
145-
type ImagePullStatisticEventLoop struct {
151+
type imagePullStatisticEventLoop struct {
146152
safeconcurrencytypes.EventLoop[*state.ImagePullStatistics]
147153

148154
options *options.Options
@@ -151,7 +157,10 @@ type ImagePullStatisticEventLoop struct {
151157
}
152158

153159
// NewImagePullStatisticEventLoop creates a new ImagePullStatisticEventLoop.
154-
func NewImagePullStatisticEventLoop(options *options.Options, metricOutput io.Writer) *ImagePullStatisticEventLoop {
160+
//
161+
// The returned *imagePullStatisticEventLoop implements
162+
// [github.com/BackMarket-oss/kube-transition-metrics/internal/statistics/types.ImagePullStatisticEventLoop].
163+
func NewImagePullStatisticEventLoop(options *options.Options, metricOutput io.Writer) *imagePullStatisticEventLoop {
155164
s := state.NewImagePullStatistics()
156165
snapshot := snapshot.NewCopyable[*state.ImagePullStatistics](s)
157166

@@ -162,30 +171,33 @@ func NewImagePullStatisticEventLoop(options *options.Options, metricOutput io.Wr
162171
//nolint:gosec // We already check that options.StatisticEventQueueLength is less than 0, and panic otherwise.
163172
buffer := uint(options.StatisticEventQueueLength)
164173

165-
return &ImagePullStatisticEventLoop{
174+
return &imagePullStatisticEventLoop{
166175
EventLoop: eventloop.NewBuffered[*state.ImagePullStatistics](snapshot, buffer),
167176
options: options,
168177
metricOutput: metricOutput,
169178
}
170179
}
171180

172181
// Start starts the event loop and begins watching the state of the event loop.
173-
func (el *ImagePullStatisticEventLoop) Start() {
182+
// Start implements [safeconcurrencytypes.EventLoop.Start].
183+
func (el *imagePullStatisticEventLoop) Start() {
174184
el.EventLoop.Start()
175185
// EventLoop.Start() will panic if the event loop is already started, so we can be sure to do this assignment only
176186
// once.
177187
el.watcherChan = eventloop.WatchState(context.TODO(), el.EventLoop, el.watcher)
178188
}
179189

180190
// Close closes the event loop and waits for the watcher to finish.
181-
func (el *ImagePullStatisticEventLoop) Close() {
191+
// Close implements [safeconcurrencytypes.EventLoop.Close].
192+
func (el *imagePullStatisticEventLoop) Close() {
182193
el.EventLoop.Close()
183194
// Wait for the watcher to finish too.
184195
<-el.watcherChan
185196
}
186197

187198
// Send sends an event to the event loop and tracks the time it took to process the event.
188-
func (el *ImagePullStatisticEventLoop) Send(
199+
// Send implements [safeconcurrencytypes.EventLoop.Send].
200+
func (el *imagePullStatisticEventLoop) Send(
189201
ctx context.Context,
190202
event safeconcurrencytypes.Event[*state.ImagePullStatistics],
191203
) (safeconcurrencytypes.GenerationID, error) {
@@ -211,7 +223,8 @@ func (el *ImagePullStatisticEventLoop) Send(
211223

212224
// ImagePullUpdate sends an event to update the image pull statistic for a pod from the latest Kubernetes Event for
213225
// image pulling related events.
214-
func (el *ImagePullStatisticEventLoop) ImagePullUpdate(
226+
// ImagePullUpdate implements [types.ImagePullStatisticEventLoop.ImagePullUpdate].
227+
func (el *imagePullStatisticEventLoop) ImagePullUpdate(
215228
ctx context.Context,
216229
pod *corev1.Pod,
217230
k8sEvent *corev1.Event,
@@ -226,7 +239,8 @@ func (el *ImagePullStatisticEventLoop) ImagePullUpdate(
226239

227240
// ImagePullDelete sends an event to delete the image pull statistic for a pod after the image pull is no longer being
228241
// tracked.
229-
func (el *ImagePullStatisticEventLoop) ImagePullDelete(
242+
// ImagePullDelete implements [types.ImagePullStatisticEventLoop.ImagePullDelete].
243+
func (el *imagePullStatisticEventLoop) ImagePullDelete(
230244
ctx context.Context,
231245
pod *corev1.Pod,
232246
) (safeconcurrencytypes.GenerationID, error) {
@@ -238,7 +252,7 @@ func (el *ImagePullStatisticEventLoop) ImagePullDelete(
238252
}
239253

240254
// watcher watches the state of the event loop and updates the prometheus metrics.
241-
func (el *ImagePullStatisticEventLoop) watcher(
255+
func (el *imagePullStatisticEventLoop) watcher(
242256
ctx context.Context,
243257
s safeconcurrencytypes.StateSnapshot[*state.ImagePullStatistics],
244258
) bool {

0 commit comments

Comments
 (0)