Skip to content

Commit f85abce

Browse files
committed
test(e2e): verify experimental delivery retry limits
Exercise BackoffMax and RetryAfterMax through both the NATS broker and channel in the experimental Eventing suite. Signed-off-by: kahirokunn <okinakahiro@gmail.com>
1 parent 4be5a3c commit f85abce

11 files changed

Lines changed: 771 additions & 3 deletions

File tree

.github/workflows/kind-e2e.yaml

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ permissions:
3838
jobs:
3939

4040
e2e-tests:
41-
name: e2e tests (${{ matrix.k8s-version }}, ${{ matrix.eventing-version }})
41+
name: e2e tests (${{ matrix.k8s-version }}, ${{ matrix.eventing-version }}, ${{ matrix.test-suite }})
4242
runs-on: ubuntu-latest
4343
timeout-minutes: 120
4444
strategy:
@@ -54,6 +54,15 @@ jobs:
5454
# Build the Eventing revision selected by go.mod.
5555
- go.mod
5656

57+
test-suite:
58+
- ./test/e2e
59+
- ./test/experimental
60+
61+
exclude:
62+
# Experimental tests exercise the API selected by go.mod.
63+
- eventing-version: knative-v1.23.0
64+
test-suite: ./test/experimental
65+
5766
env:
5867
KO_DOCKER_REPO: kind.local
5968
SYSTEM_NAMESPACE: knative-eventing
@@ -141,6 +150,11 @@ jobs:
141150
ko apply --platform=linux/amd64 -R -f ./config/core/
142151
ko apply --platform=linux/amd64 -f ./config/brokers/mt-channel-broker/
143152
153+
- name: Apply experimental features config
154+
if: matrix.test-suite == './test/experimental'
155+
run: |
156+
kubectl apply -f ./test/experimental/config
157+
144158
- name: Install NATS Eventing components
145159
run: |
146160
set -x
@@ -163,7 +177,7 @@ jobs:
163177
run: |
164178
# Run the tests tagged as e2e on the KinD cluster.
165179
go test -race -count=1 -parallel=12 -timeout=90m -tags=e2e \
166-
./test/e2e
180+
${{ matrix.test-suite }}
167181
168182
- name: Collect system diagnostics
169183
if: ${{ failure() }}
@@ -214,6 +228,6 @@ jobs:
214228
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
215229
SLACK_CHANNEL: 'eventing-delivery'
216230
MSG_MINIMAL: 'true'
217-
SLACK_TITLE: Periodic e2e for NATS on KinD on (${{ matrix.k8s-version }}, ${{ matrix.eventing-version }}) failed.
231+
SLACK_TITLE: Periodic e2e for NATS on KinD on (${{ matrix.k8s-version }}, ${{ matrix.eventing-version }}, ${{ matrix.test-suite }}) failed.
218232
SLACK_MESSAGE: |
219233
For detailed logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//go:build e2e
2+
// +build e2e
3+
4+
/*
5+
Copyright 2026 The Knative Authors
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
*/
19+
20+
package experimental
21+
22+
import (
23+
"testing"
24+
25+
"knative.dev/pkg/system"
26+
"knative.dev/reconciler-test/pkg/environment"
27+
"knative.dev/reconciler-test/pkg/k8s"
28+
"knative.dev/reconciler-test/pkg/knative"
29+
30+
"knative.dev/eventing-natss/test/experimental/features/delivery"
31+
)
32+
33+
func TestBackoffMax(t *testing.T) {
34+
t.Parallel()
35+
36+
ctx, env := global.Environment(
37+
knative.WithKnativeNamespace(system.Namespace()),
38+
knative.WithLoggingConfig,
39+
knative.WithObservabilityConfig,
40+
k8s.WithEventListener,
41+
environment.Managed(t),
42+
)
43+
44+
env.Test(ctx, t, delivery.BackoffMaxBrokerToSink())
45+
env.Test(ctx, t, delivery.BackoffMaxChannelToSink())
46+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Copyright 2026 The Knative Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
apiVersion: v1
16+
kind: ConfigMap
17+
metadata:
18+
name: config-features
19+
namespace: knative-eventing
20+
labels:
21+
knative.dev/config-propagation: original
22+
knative.dev/config-category: eventing
23+
data:
24+
kreference-group: "enabled"
25+
delivery-retryafter: "enabled"
26+
delivery-backoff-max: "enabled"
27+
delivery-timeout: "enabled"
28+
eventtype-auto-create: "enabled"
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
Copyright 2026 The Knative Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package delivery
18+
19+
import (
20+
"net/http"
21+
"time"
22+
23+
"k8s.io/utils/ptr"
24+
"knative.dev/reconciler-test/pkg/feature"
25+
26+
eventingduckv1 "knative.dev/eventing/pkg/apis/duck/v1"
27+
)
28+
29+
// BackoffMaxBrokerToSink verifies that BackoffMax caps Broker retries.
30+
func BackoffMaxBrokerToSink() *feature.Feature {
31+
return backoffMax("Broker delivery backoff maximum", brokerRoute)
32+
}
33+
34+
// BackoffMaxChannelToSink verifies that a Channel default BackoffMax caps Subscription retries.
35+
func BackoffMaxChannelToSink() *feature.Feature {
36+
return backoffMax("Channel delivery backoff maximum", channelRoute)
37+
}
38+
39+
func backoffMax(name string, route deliveryRoute) *feature.Feature {
40+
backoffPolicy := eventingduckv1.BackoffPolicyExponential
41+
return newDeliveryFeature(name, "backoff-max", route, retryBehavior{
42+
retries: 4,
43+
responseCode: http.StatusServiceUnavailable,
44+
delivery: &eventingduckv1.DeliverySpec{
45+
Retry: ptr.To(int32(4)),
46+
BackoffPolicy: &backoffPolicy,
47+
BackoffDelay: ptr.To("PT1S"),
48+
BackoffMax: ptr.To("PT2S"),
49+
},
50+
expectedIntervals: []time.Duration{time.Second, 2 * time.Second, 2 * time.Second, 2 * time.Second},
51+
rejectedStep: "receiver rejects the first four deliveries",
52+
receivedStep: "receiver accepts the fifth delivery",
53+
timingStep: "retry delay stops growing at two seconds",
54+
})
55+
}
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/*
2+
Copyright 2026 The Knative Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package delivery
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"time"
23+
24+
cetest "github.com/cloudevents/sdk-go/v2/test"
25+
"github.com/stretchr/testify/require"
26+
corev1 "k8s.io/api/core/v1"
27+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
"k8s.io/apimachinery/pkg/runtime/schema"
29+
duckv1 "knative.dev/pkg/apis/duck/v1"
30+
"knative.dev/reconciler-test/pkg/environment"
31+
"knative.dev/reconciler-test/pkg/eventshub"
32+
"knative.dev/reconciler-test/pkg/feature"
33+
"knative.dev/reconciler-test/pkg/k8s"
34+
35+
natssv1alpha1 "knative.dev/eventing-natss/pkg/apis/messaging/v1alpha1"
36+
brokerconstants "knative.dev/eventing-natss/pkg/broker/constants"
37+
natssclient "knative.dev/eventing-natss/pkg/client/injection/client"
38+
eventingduckv1 "knative.dev/eventing/pkg/apis/duck/v1"
39+
eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1"
40+
messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1"
41+
eventingclient "knative.dev/eventing/pkg/client/injection/client"
42+
)
43+
44+
var (
45+
brokerGVR = eventingv1.SchemeGroupVersion.WithResource("brokers")
46+
triggerGVR = eventingv1.SchemeGroupVersion.WithResource("triggers")
47+
subscriptionGVR = messagingv1.SchemeGroupVersion.WithResource("subscriptions")
48+
natsChannelGVR = natssv1alpha1.SchemeGroupVersion.WithResource("natsjetstreamchannels")
49+
serviceGVR = corev1.SchemeGroupVersion.WithResource("services")
50+
)
51+
52+
type deliveryRoute int
53+
54+
const (
55+
brokerRoute deliveryRoute = iota
56+
channelRoute
57+
)
58+
59+
type retryBehavior struct {
60+
retries int32
61+
responseCode int
62+
responseHeaders map[string]string
63+
delivery *eventingduckv1.DeliverySpec
64+
expectedIntervals []time.Duration
65+
rejectedStep string
66+
receivedStep string
67+
timingStep string
68+
}
69+
70+
func newDeliveryFeature(name, resourcePrefix string, route deliveryRoute, behavior retryBehavior) *feature.Feature {
71+
receiverName := feature.MakeRandomK8sName(resourcePrefix + "-receiver")
72+
senderName := feature.MakeRandomK8sName(resourcePrefix + "-sender")
73+
event := cetest.FullEvent()
74+
75+
receiverOptions := []eventshub.EventsHubOption{
76+
eventshub.StartReceiver,
77+
eventshub.DropFirstN(uint(behavior.retries)),
78+
eventshub.DropEventsResponseCode(behavior.responseCode),
79+
}
80+
if len(behavior.responseHeaders) > 0 {
81+
receiverOptions = append(receiverOptions, eventshub.DropEventsResponseHeaders(behavior.responseHeaders))
82+
}
83+
84+
f := feature.NewFeatureNamed(name)
85+
f.Setup("install receiver", eventshub.Install(receiverName, receiverOptions...))
86+
87+
var target schema.GroupVersionResource
88+
var targetName string
89+
switch route {
90+
case brokerRoute:
91+
target = brokerGVR
92+
targetName = feature.MakeRandomK8sName(resourcePrefix + "-broker")
93+
triggerName := feature.MakeRandomK8sName(resourcePrefix + "-trigger")
94+
f.Setup("install broker", installBroker(targetName))
95+
f.Setup("install trigger", installTrigger(targetName, triggerName, receiverName, behavior.delivery))
96+
f.Requirement("receiver is addressable", k8s.IsAddressable(serviceGVR, receiverName, time.Second, time.Minute))
97+
f.Requirement("broker is ready", k8s.IsReady(brokerGVR, targetName, time.Second, 3*time.Minute))
98+
f.Requirement("trigger is ready", k8s.IsReady(triggerGVR, triggerName, time.Second, 3*time.Minute))
99+
case channelRoute:
100+
target = natsChannelGVR
101+
targetName = feature.MakeRandomK8sName(resourcePrefix + "-channel")
102+
subscriptionName := feature.MakeRandomK8sName(resourcePrefix + "-subscription")
103+
f.Setup("install channel", installChannel(targetName, behavior.delivery))
104+
f.Setup("install subscription", installSubscription(targetName, subscriptionName, receiverName))
105+
f.Requirement("receiver is addressable", k8s.IsAddressable(serviceGVR, receiverName, time.Second, time.Minute))
106+
f.Requirement("channel is ready", k8s.IsReady(natsChannelGVR, targetName, time.Second, 3*time.Minute))
107+
f.Requirement("subscription is ready", k8s.IsReady(subscriptionGVR, subscriptionName, time.Second, 3*time.Minute))
108+
default:
109+
panic(fmt.Sprintf("unsupported delivery route %d", route))
110+
}
111+
112+
f.Assert("send event", eventshub.Install(
113+
senderName,
114+
eventshub.StartSenderToResource(target, targetName),
115+
eventshub.InputEvent(event),
116+
))
117+
f.Assert(behavior.rejectedStep, assertExact(receiverName, int(behavior.retries), eventWithKind(event.ID(), eventshub.EventRejected)))
118+
f.Assert(behavior.receivedStep, assertExact(receiverName, 1, eventWithKind(event.ID(), eventshub.EventReceived)))
119+
f.Assert(behavior.timingStep, assertExact(receiverName, int(behavior.retries)+1, deliveriesFollowIntervals(event.ID(), behavior.expectedIntervals)))
120+
121+
return f
122+
}
123+
124+
func assertExact(receiverName string, count int, matcher eventshub.EventInfoMatcher) feature.StepFn {
125+
return func(ctx context.Context, t feature.T) {
126+
eventshub.StoreFromContext(ctx, receiverName).AssertExact(ctx, t, count, matcher)
127+
}
128+
}
129+
130+
func installBroker(name string) feature.StepFn {
131+
return func(ctx context.Context, t feature.T) {
132+
namespace := environment.FromContext(ctx).Namespace()
133+
_, err := eventingclient.Get(ctx).EventingV1().Brokers(namespace).Create(ctx, &eventingv1.Broker{
134+
ObjectMeta: metav1.ObjectMeta{
135+
Name: name,
136+
Namespace: namespace,
137+
Annotations: map[string]string{
138+
eventingv1.BrokerClassAnnotationKey: brokerconstants.BrokerClassName,
139+
},
140+
},
141+
}, metav1.CreateOptions{})
142+
require.NoError(t, err)
143+
}
144+
}
145+
146+
func installTrigger(brokerName, triggerName, receiverName string, delivery *eventingduckv1.DeliverySpec) feature.StepFn {
147+
return func(ctx context.Context, t feature.T) {
148+
namespace := environment.FromContext(ctx).Namespace()
149+
_, err := eventingclient.Get(ctx).EventingV1().Triggers(namespace).Create(ctx, &eventingv1.Trigger{
150+
ObjectMeta: metav1.ObjectMeta{Name: triggerName, Namespace: namespace},
151+
Spec: eventingv1.TriggerSpec{
152+
Broker: brokerName,
153+
Subscriber: duckv1.Destination{Ref: &duckv1.KReference{
154+
APIVersion: corev1.SchemeGroupVersion.String(),
155+
Kind: "Service",
156+
Name: receiverName,
157+
}},
158+
Delivery: delivery,
159+
},
160+
}, metav1.CreateOptions{})
161+
require.NoError(t, err)
162+
}
163+
}
164+
165+
func installChannel(name string, delivery *eventingduckv1.DeliverySpec) feature.StepFn {
166+
return func(ctx context.Context, t feature.T) {
167+
namespace := environment.FromContext(ctx).Namespace()
168+
_, err := natssclient.Get(ctx).MessagingV1alpha1().NatsJetStreamChannels(namespace).Create(ctx, &natssv1alpha1.NatsJetStreamChannel{
169+
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
170+
Spec: natssv1alpha1.NatsJetStreamChannelSpec{
171+
ChannelableSpec: eventingduckv1.ChannelableSpec{Delivery: delivery},
172+
},
173+
}, metav1.CreateOptions{})
174+
require.NoError(t, err)
175+
}
176+
}
177+
178+
func installSubscription(channelName, subscriptionName, receiverName string) feature.StepFn {
179+
return func(ctx context.Context, t feature.T) {
180+
namespace := environment.FromContext(ctx).Namespace()
181+
_, err := eventingclient.Get(ctx).MessagingV1().Subscriptions(namespace).Create(ctx, &messagingv1.Subscription{
182+
ObjectMeta: metav1.ObjectMeta{Name: subscriptionName, Namespace: namespace},
183+
Spec: messagingv1.SubscriptionSpec{
184+
Channel: duckv1.KReference{
185+
APIVersion: natssv1alpha1.SchemeGroupVersion.String(),
186+
Kind: "NatsJetStreamChannel",
187+
Name: channelName,
188+
},
189+
Subscriber: &duckv1.Destination{Ref: &duckv1.KReference{
190+
APIVersion: corev1.SchemeGroupVersion.String(),
191+
Kind: "Service",
192+
Name: receiverName,
193+
}},
194+
},
195+
}, metav1.CreateOptions{})
196+
require.NoError(t, err)
197+
}
198+
}

0 commit comments

Comments
 (0)