Skip to content

Commit 057ede2

Browse files
pmalekrainest
andauthored
fix(plugins) handle CG plugins like consumers (#6132) (#6160)
Use logic similar to consumers for consumer groups. This fixes an issue where plugins assigned to a consumer group and a route (or service) would not actually be associated with the consumer group, only the route (or service). Co-authored-by: Travis Raines <[email protected]>
1 parent b0ce45e commit 057ede2

File tree

3 files changed

+223
-40
lines changed

3 files changed

+223
-40
lines changed

internal/util/relations.go

+42-11
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,67 @@ type Rel struct {
99
}
1010

1111
func (relations *ForeignRelations) GetCombinations() []Rel {
12+
var (
13+
lConsumer = len(relations.Consumer)
14+
lConsumerGroup = len(relations.ConsumerGroup)
15+
lRoutes = len(relations.Route)
16+
lServices = len(relations.Service)
17+
l = lRoutes + lServices
18+
)
19+
1220
var cartesianProduct []Rel
1321

14-
if len(relations.Consumer) > 0 {
15-
consumers := relations.Consumer
16-
if len(relations.Route)+len(relations.Service) > 0 {
17-
for _, service := range relations.Service {
18-
for _, consumer := range consumers {
22+
// gocritic I don't care that you think switch statements are the one true god of readability, the language offers
23+
// multiple options for a reason. go away, gocritic.
24+
if lConsumer > 0 { //nolint:gocritic
25+
if l > 0 {
26+
cartesianProduct = make([]Rel, 0, l*lConsumer)
27+
for _, consumer := range relations.Consumer {
28+
for _, service := range relations.Service {
1929
cartesianProduct = append(cartesianProduct, Rel{
2030
Service: service,
2131
Consumer: consumer,
2232
})
2333
}
24-
}
25-
for _, route := range relations.Route {
26-
for _, consumer := range consumers {
34+
for _, route := range relations.Route {
2735
cartesianProduct = append(cartesianProduct, Rel{
2836
Route: route,
2937
Consumer: consumer,
3038
})
3139
}
3240
}
41+
3342
} else {
43+
cartesianProduct = make([]Rel, 0, len(relations.Consumer))
3444
for _, consumer := range relations.Consumer {
3545
cartesianProduct = append(cartesianProduct, Rel{Consumer: consumer})
3646
}
3747
}
38-
} else {
39-
for _, consumerGroup := range relations.ConsumerGroup {
40-
cartesianProduct = append(cartesianProduct, Rel{ConsumerGroup: consumerGroup})
48+
} else if lConsumerGroup > 0 {
49+
if l > 0 {
50+
cartesianProduct = make([]Rel, 0, l*lConsumerGroup)
51+
for _, group := range relations.ConsumerGroup {
52+
for _, service := range relations.Service {
53+
cartesianProduct = append(cartesianProduct, Rel{
54+
Service: service,
55+
ConsumerGroup: group,
56+
})
57+
}
58+
for _, route := range relations.Route {
59+
cartesianProduct = append(cartesianProduct, Rel{
60+
Route: route,
61+
ConsumerGroup: group,
62+
})
63+
}
64+
}
65+
} else {
66+
cartesianProduct = make([]Rel, 0, lConsumerGroup)
67+
for _, group := range relations.ConsumerGroup {
68+
cartesianProduct = append(cartesianProduct, Rel{ConsumerGroup: group})
69+
}
4170
}
71+
} else if l > 0 {
72+
cartesianProduct = make([]Rel, 0, l)
4273
for _, service := range relations.Service {
4374
cartesianProduct = append(cartesianProduct, Rel{Service: service})
4475
}

internal/util/relations_test.go

+91-21
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package util
22

33
import (
4-
"reflect"
54
"testing"
5+
6+
"github.com/stretchr/testify/require"
67
)
78

89
func TestGetCombinations(t *testing.T) {
@@ -121,14 +122,14 @@ func TestGetCombinations(t *testing.T) {
121122
Consumer: "foo",
122123
Route: "foo",
123124
},
124-
{
125-
Consumer: "bar",
126-
Route: "foo",
127-
},
128125
{
129126
Consumer: "foo",
130127
Route: "bar",
131128
},
129+
{
130+
Consumer: "bar",
131+
Route: "foo",
132+
},
132133
{
133134
Consumer: "bar",
134135
Route: "bar",
@@ -148,14 +149,14 @@ func TestGetCombinations(t *testing.T) {
148149
Consumer: "foo",
149150
Service: "foo",
150151
},
151-
{
152-
Consumer: "bar",
153-
Service: "foo",
154-
},
155152
{
156153
Consumer: "foo",
157154
Service: "bar",
158155
},
156+
{
157+
Consumer: "bar",
158+
Service: "foo",
159+
},
159160
{
160161
Consumer: "bar",
161162
Service: "bar",
@@ -177,41 +178,110 @@ func TestGetCombinations(t *testing.T) {
177178
Service: "s1",
178179
},
179180
{
180-
Consumer: "c2",
181-
Service: "s1",
181+
Consumer: "c1",
182+
Service: "s2",
182183
},
183184
{
184185
Consumer: "c1",
185-
Service: "s2",
186+
Route: "r1",
187+
},
188+
{
189+
Consumer: "c1",
190+
Route: "r2",
186191
},
187192
{
188193
Consumer: "c2",
189-
Service: "s2",
194+
Service: "s1",
190195
},
191196
{
192-
Consumer: "c1",
193-
Route: "r1",
197+
Consumer: "c2",
198+
Service: "s2",
194199
},
195200
{
196201
Consumer: "c2",
197202
Route: "r1",
198203
},
199204
{
200-
Consumer: "c1",
205+
Consumer: "c2",
201206
Route: "r2",
202207
},
208+
},
209+
},
210+
{
211+
name: "plugins on combination of service,route and consumer group",
212+
args: args{
213+
relations: ForeignRelations{
214+
Route: []string{"r1", "r2"},
215+
Service: []string{"s1", "s2"},
216+
ConsumerGroup: []string{"cg1", "cg2"},
217+
},
218+
},
219+
want: []Rel{
203220
{
204-
Consumer: "c2",
205-
Route: "r2",
221+
ConsumerGroup: "cg1",
222+
Service: "s1",
223+
},
224+
{
225+
ConsumerGroup: "cg1",
226+
Service: "s2",
227+
},
228+
{
229+
ConsumerGroup: "cg1",
230+
Route: "r1",
231+
},
232+
{
233+
ConsumerGroup: "cg1",
234+
Route: "r2",
235+
},
236+
{
237+
ConsumerGroup: "cg2",
238+
Service: "s1",
239+
},
240+
{
241+
ConsumerGroup: "cg2",
242+
Service: "s2",
243+
},
244+
{
245+
ConsumerGroup: "cg2",
246+
Route: "r1",
247+
},
248+
{
249+
ConsumerGroup: "cg2",
250+
Route: "r2",
206251
},
207252
},
208253
},
209254
}
210255
for _, tt := range tests {
211256
t.Run(tt.name, func(t *testing.T) {
212-
if got := tt.args.relations.GetCombinations(); !reflect.DeepEqual(got, tt.want) {
213-
t.Errorf("GetCombinations() = %v, want %v", got, tt.want)
214-
}
257+
require.Equal(t, tt.want, tt.args.relations.GetCombinations())
215258
})
216259
}
217260
}
261+
262+
func BenchmarkGetCombinations(b *testing.B) {
263+
b.Run("consumer groups", func(b *testing.B) {
264+
for i := 0; i < b.N; i++ {
265+
relations := ForeignRelations{
266+
Route: []string{"r1", "r2"},
267+
Service: []string{"s1", "s2"},
268+
ConsumerGroup: []string{"cg1", "cg2"},
269+
}
270+
271+
rels := relations.GetCombinations()
272+
_ = rels
273+
}
274+
})
275+
b.Run("consumers", func(b *testing.B) {
276+
for i := 0; i < b.N; i++ {
277+
relations := ForeignRelations{
278+
Route: []string{"r1", "r2"},
279+
Service: []string{"s1", "s2"},
280+
Consumer: []string{"c1", "c2", "c3"},
281+
}
282+
283+
rels := relations.GetCombinations()
284+
_ = rels
285+
}
286+
})
287+
}

test/integration/consumer_group_test.go

+90-8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/kong/go-kong/kong"
1313
"github.com/kong/kubernetes-testing-framework/pkg/clusters"
1414
"github.com/kong/kubernetes-testing-framework/pkg/utils/kubernetes/generators"
15+
"github.com/stretchr/testify/assert"
1516
"github.com/stretchr/testify/require"
1617
appsv1 "k8s.io/api/apps/v1"
1718
corev1 "k8s.io/api/core/v1"
@@ -36,11 +37,16 @@ func TestConsumerGroup(t *testing.T) {
3637
ctx := context.Background()
3738
ns, cleaner := helpers.Setup(ctx, t, env)
3839

39-
d, s, i, p := deployMinimalSvcWithKeyAuth(ctx, t, ns.Name)
40-
cleaner.Add(d)
41-
cleaner.Add(s)
42-
cleaner.Add(i)
43-
cleaner.Add(p)
40+
// path is the basic path used for most of the test
41+
path := "/test-consumer-group/basic"
42+
// multiPath is the path used to test consumer group + route plugins
43+
multiPath := "/test-consumer-group/multi"
44+
45+
deployment, service, ingress, keyauthPlugin := deployMinimalSvcWithKeyAuth(ctx, t, ns.Name, path)
46+
cleaner.Add(deployment)
47+
cleaner.Add(service)
48+
cleaner.Add(ingress)
49+
cleaner.Add(keyauthPlugin)
4450

4551
addedHeader := header{
4652
K: "X-Test-Header",
@@ -89,6 +95,15 @@ func TestConsumerGroup(t *testing.T) {
8995
ctx, t, ns.Name, "test-consumer-group-2", pluginRateLimit.Name,
9096
)
9197
cleaner.Add(rateLimitGroup)
98+
// 3 has consumers but no plugins
99+
nothingGroup := configureConsumerGroupWithPlugins(
100+
ctx, t, ns.Name, "test-consumer-group-3",
101+
)
102+
cleaner.Add(nothingGroup)
103+
addHeaderRouteGroup := configureConsumerGroupWithPlugins(
104+
ctx, t, ns.Name, "test-consumer-group-4", pluginRespTrans.Name,
105+
)
106+
cleaner.Add(addHeaderRouteGroup)
92107

93108
rateLimitHeader := header{
94109
K: "RateLimit-Limit",
@@ -130,7 +145,7 @@ func TestConsumerGroup(t *testing.T) {
130145
t.Log("checking if consumer has plugin configured correctly based on consumer group membership")
131146
for _, consumer := range consumers {
132147
require.Eventually(t, func() bool {
133-
req := helpers.MustHTTPRequest(t, http.MethodGet, proxyURL.Host, "/", map[string]string{
148+
req := helpers.MustHTTPRequest(t, http.MethodGet, proxyURL.Host, path, map[string]string{
134149
"apikey": consumer.Name,
135150
})
136151
resp, err := helpers.DefaultHTTPClientWithProxy(proxyURL).Do(req)
@@ -158,10 +173,77 @@ func TestConsumerGroup(t *testing.T) {
158173
return true
159174
}, ingressWait, waitTick)
160175
}
176+
177+
t.Log("checking plugins attached to a consumer group and route only apply when request matches both")
178+
four, fourSecret := configureConsumerWithAPIKey(ctx, t, ns.Name, "test-consumer-4", "test-consumer-group-4")
179+
cleaner.Add(four)
180+
cleaner.Add(fourSecret)
181+
182+
multiIngress := generators.NewIngressForService(multiPath, map[string]string{
183+
annotations.AnnotationPrefix + annotations.StripPathKey: "true",
184+
annotations.AnnotationPrefix + annotations.PluginsKey: strings.Join([]string{keyauthPlugin.Name, pluginRespTrans.Name}, ","),
185+
}, service)
186+
multiIngress.Spec.IngressClassName = kong.String(consts.IngressClass)
187+
multiIngress.Name = "multi"
188+
require.NoError(t, clusters.DeployIngress(ctx, env.Cluster(), ns.Name, multiIngress))
189+
cleaner.Add(multiIngress)
190+
191+
require.EventuallyWithT(t, func(c *assert.CollectT) {
192+
// this should see the header, it uses a consumer in the group on the associated route
193+
req := helpers.MustHTTPRequest(t, http.MethodGet, proxyURL.Host, multiPath, map[string]string{
194+
"apikey": four.Name,
195+
})
196+
resp, err := helpers.DefaultHTTPClientWithProxy(proxyURL).Do(req)
197+
if !assert.NoError(c, err) {
198+
return
199+
}
200+
defer resp.Body.Close()
201+
if !assert.Equal(c, resp.StatusCode, http.StatusOK) {
202+
return
203+
}
204+
hv := resp.Header.Get(addedHeader.K)
205+
if !assert.Equal(c, addedHeader.V, hv) {
206+
return
207+
}
208+
209+
// this should not see the header, it uses a consumer in the group on another route
210+
clear := helpers.MustHTTPRequest(t, http.MethodGet, proxyURL.Host, path, map[string]string{
211+
"apikey": four.Name,
212+
})
213+
clearResp, err := helpers.DefaultHTTPClientWithProxy(proxyURL).Do(clear)
214+
if !assert.NoError(c, err) {
215+
return
216+
}
217+
defer clearResp.Body.Close()
218+
if !assert.Equal(c, clearResp.StatusCode, http.StatusOK) {
219+
return
220+
}
221+
hv = clearResp.Header.Get(addedHeader.K)
222+
if !assert.NotEqual(c, addedHeader.V, hv) {
223+
return
224+
}
225+
226+
// this should not see the header, it uses a consumer outside the group on the associated route
227+
empty := helpers.MustHTTPRequest(t, http.MethodGet, proxyURL.Host, multiPath, map[string]string{
228+
"apikey": "test-consumer-3",
229+
})
230+
emptyResp, err := helpers.DefaultHTTPClientWithProxy(proxyURL).Do(empty)
231+
if !assert.NoError(c, err) {
232+
return
233+
}
234+
defer emptyResp.Body.Close()
235+
if !assert.Equal(c, emptyResp.StatusCode, http.StatusOK) {
236+
return
237+
}
238+
hv = emptyResp.Header.Get(addedHeader.K)
239+
if !assert.NotEqual(c, addedHeader.V, hv) {
240+
return
241+
}
242+
}, ingressWait, waitTick)
161243
}
162244

163245
func deployMinimalSvcWithKeyAuth(
164-
ctx context.Context, t *testing.T, namespace string,
246+
ctx context.Context, t *testing.T, namespace, path string,
165247
) (*appsv1.Deployment, *corev1.Service, *netv1.Ingress, *kongv1.KongPlugin) {
166248
const pluginKeyAuthName = "key-auth"
167249
t.Logf("configuring plugin %q (to give consumers an identity)", pluginKeyAuthName)
@@ -194,7 +276,7 @@ func deployMinimalSvcWithKeyAuth(
194276
require.NoError(t, err)
195277

196278
t.Logf("creating an ingress for service %q with plugin %q attached", service.Name, pluginKeyAuthName)
197-
ingress := generators.NewIngressForService("/", map[string]string{
279+
ingress := generators.NewIngressForService(path, map[string]string{
198280
annotations.AnnotationPrefix + annotations.StripPathKey: "true",
199281
annotations.AnnotationPrefix + annotations.PluginsKey: pluginKeyAuthName,
200282
}, service)

0 commit comments

Comments
 (0)