Skip to content

Commit f188b4c

Browse files
authored
Ensure uniqueness and validity of generated names and labels (#716)
Problem: Provisioner could generate an invalid name or a label for NKG static mode deployment. As a result, the provisioner would fail with an error like below: ``` {“level”:“info”,“ts”:“2023-06-05T17:26:04Z”,“logger”:“eventLoop”,“msg”:“added an event to the next batch”,“type”:“*events.UpsertEvent”,“total”:1} panic: failed to create deployment: Deployment.apps “nginx-gateway-gateway-conformance-infra-same-namespace-with-http-listener-on-8080" is invalid: [spec.selector.matchLabels: Invalid value: “nginx-gateway-gateway-conformance-infra-same-namespace-with-http-listener-on-8080”: must be no more than 63 characters, spec.selector: Invalid value: v1.LabelSelector{MatchLabels:map[string]string{“app”:“nginx-gateway-gateway-conformance-infra-same-namespace-with-http-listener-on-8080”}, MatchExpressions:[]v1.LabelSelectorRequirement(nil)}: invalid label selector] ``` Solution: Provisioner will use the following format for names and labels of Deployments: nginx-gateway-<number>, where the number if an interger >= 1. For every new Gateway resource, the provisioner will increment the number by one. This approach will break if the provisioner is restarted, but we don't support provisioner for production yet, so it is acceptable. Note: correlation between a Gateway resource and the corresponding Deployment can be found in the provisioner log. For example: ``` {"level":"info","ts":"2023-06-05T21:23:33Z","logger":"eventHandler","msg":"Created deployment","deployment":{"name":"nginx-gateway-1","namespace":"nginx-gateway"},"gateway":{"name":"same-namespace","namespace":"gateway-conformance-infra"}} ```
1 parent edaa5ff commit f188b4c

File tree

2 files changed

+98
-46
lines changed

2 files changed

+98
-46
lines changed

internal/provisioner/handler.go

+19-8
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ type eventHandler struct {
3030
logger logr.Logger
3131

3232
staticModeDeploymentYAML []byte
33+
34+
gatewayNextID int64
3335
}
3436

3537
func newEventHandler(
@@ -47,6 +49,7 @@ func newEventHandler(
4749
k8sClient: k8sClient,
4850
logger: logger,
4951
staticModeDeploymentYAML: staticModeDeploymentYAML,
52+
gatewayNextID: 1,
5053
}
5154
}
5255

@@ -91,7 +94,7 @@ func (h *eventHandler) ensureDeploymentsMatchGateways(ctx context.Context) {
9194
// Create new deployments
9295

9396
for _, nsname := range gwsWithoutDeps {
94-
deployment, err := prepareDeployment(h.staticModeDeploymentYAML, generateDeploymentID(nsname), nsname)
97+
deployment, err := prepareDeployment(h.staticModeDeploymentYAML, h.generateDeploymentID(), nsname)
9598
if err != nil {
9699
panic(fmt.Errorf("failed to prepare deployment: %w", err))
97100
}
@@ -103,7 +106,10 @@ func (h *eventHandler) ensureDeploymentsMatchGateways(ctx context.Context) {
103106

104107
h.provisions[nsname] = deployment
105108

106-
h.logger.Info("Created deployment", "deployment", client.ObjectKeyFromObject(deployment))
109+
h.logger.Info("Created deployment",
110+
"deployment", client.ObjectKeyFromObject(deployment),
111+
"gateway", nsname,
112+
)
107113
}
108114

109115
// Remove unnecessary deployments
@@ -118,7 +124,10 @@ func (h *eventHandler) ensureDeploymentsMatchGateways(ctx context.Context) {
118124

119125
delete(h.provisions, nsname)
120126

121-
h.logger.Info("Deleted deployment", "deployment", client.ObjectKeyFromObject(deployment))
127+
h.logger.Info("Deleted deployment",
128+
"deployment", client.ObjectKeyFromObject(deployment),
129+
"gateway", nsname,
130+
)
122131
}
123132
}
124133

@@ -128,9 +137,11 @@ func (h *eventHandler) HandleEventBatch(ctx context.Context, batch events.EventB
128137
h.ensureDeploymentsMatchGateways(ctx)
129138
}
130139

131-
func generateDeploymentID(gatewayNsName types.NamespacedName) string {
132-
// for production, make sure the ID is:
133-
// - a valid resource name (ex. can't be too long);
134-
// - unique among all Gateway resources (Gateways test-test/test and test/test-test should not have the same ID)
135-
return fmt.Sprintf("nginx-gateway-%s-%s", gatewayNsName.Namespace, gatewayNsName.Name)
140+
func (h *eventHandler) generateDeploymentID() string {
141+
// This approach will break if the provisioner is restarted, because the existing Gateways might get
142+
// IDs different from the previous replica of the provisioner.
143+
id := h.gatewayNextID
144+
h.gatewayNextID++
145+
146+
return fmt.Sprintf("nginx-gateway-%d", id)
136147
}

internal/provisioner/handler_test.go

+79-38
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package provisioner
22

33
import (
44
"context"
5+
"fmt"
56

67
. "github.com/onsi/ginkgo/v2"
78
v1 "k8s.io/api/apps/v1"
@@ -32,9 +33,6 @@ var _ = Describe("handler", func() {
3233

3334
statusUpdater status.Updater
3435
k8sclient client.Client
35-
36-
gwNsName, depNsName types.NamespacedName
37-
gw *v1beta1.Gateway
3836
)
3937

4038
BeforeEach(OncePerOrdered, func() {
@@ -64,12 +62,10 @@ var _ = Describe("handler", func() {
6462
PodIP: "1.2.3.4",
6563
UpdateGatewayClassStatus: true,
6664
})
65+
})
6766

68-
gwNsName = types.NamespacedName{
69-
Namespace: "test-ns",
70-
Name: "test-gw",
71-
}
72-
gw = &v1beta1.Gateway{
67+
createGateway := func(gwNsName types.NamespacedName) *v1beta1.Gateway {
68+
return &v1beta1.Gateway{
7369
ObjectMeta: metav1.ObjectMeta{
7470
Namespace: gwNsName.Namespace,
7571
Name: gwNsName.Name,
@@ -78,12 +74,7 @@ var _ = Describe("handler", func() {
7874
GatewayClassName: gcName,
7975
},
8076
}
81-
82-
depNsName = types.NamespacedName{
83-
Namespace: "nginx-gateway",
84-
Name: "nginx-gateway-test-ns-test-gw",
85-
}
86-
})
77+
}
8778

8879
itShouldUpsertGatewayClass := func() {
8980
// Add GatewayClass to the cluster
@@ -127,31 +118,37 @@ var _ = Describe("handler", func() {
127118
Expect(clusterGc.Status.Conditions).To(Equal(expectedConditions))
128119
}
129120

130-
itShouldUpsertGateway := func() {
121+
itShouldUpsertGateway := func(gwNsName types.NamespacedName, seqNumber int64) {
131122
batch := []interface{}{
132123
&events.UpsertEvent{
133-
Resource: gw,
124+
Resource: createGateway(gwNsName),
134125
},
135126
}
136127

137128
handler.HandleEventBatch(context.Background(), batch)
138129

130+
depNsName := types.NamespacedName{
131+
Namespace: "nginx-gateway",
132+
Name: fmt.Sprintf("nginx-gateway-%d", seqNumber),
133+
}
134+
139135
dep := &v1.Deployment{}
140136
err := k8sclient.Get(context.Background(), depNsName, dep)
141137

142138
Expect(err).ShouldNot(HaveOccurred())
143139

144140
Expect(dep.ObjectMeta.Namespace).To(Equal("nginx-gateway"))
145-
Expect(dep.ObjectMeta.Name).To(Equal("nginx-gateway-test-ns-test-gw"))
141+
Expect(dep.ObjectMeta.Name).To(Equal(depNsName.Name))
146142
Expect(dep.Spec.Template.Spec.Containers[0].Args).To(ContainElement("static-mode"))
147-
Expect(dep.Spec.Template.Spec.Containers[0].Args).To(ContainElement("--gateway=test-ns/test-gw"))
143+
expectedGwFlag := fmt.Sprintf("--gateway=%s", gwNsName.String())
144+
Expect(dep.Spec.Template.Spec.Containers[0].Args).To(ContainElement(expectedGwFlag))
148145
Expect(dep.Spec.Template.Spec.Containers[0].Args).To(ContainElement("--update-gatewayclass-status=false"))
149146
}
150147

151-
itShouldPanicWhenUpsertingGateway := func() {
148+
itShouldPanicWhenUpsertingGateway := func(gwNsName types.NamespacedName) {
152149
batch := []interface{}{
153150
&events.UpsertEvent{
154-
Resource: gw,
151+
Resource: createGateway(gwNsName),
155152
},
156153
}
157154

@@ -163,7 +160,18 @@ var _ = Describe("handler", func() {
163160
}
164161

165162
Describe("Core cases", Ordered, func() {
163+
var gwNsName1, gwNsName2 types.NamespacedName
164+
166165
BeforeAll(func() {
166+
gwNsName1 = types.NamespacedName{
167+
Namespace: "test-ns-1",
168+
Name: "test-gw-1",
169+
}
170+
gwNsName2 = types.NamespacedName{
171+
Namespace: "test-ns-2",
172+
Name: "test-gw-2",
173+
}
174+
167175
handler = newEventHandler(
168176
gcName,
169177
statusUpdater,
@@ -179,24 +187,50 @@ var _ = Describe("handler", func() {
179187
})
180188
})
181189

182-
When("upserting Gateway", func() {
183-
It("should create Deployment", func() {
184-
itShouldUpsertGateway()
190+
When("upserting first Gateway", func() {
191+
It("should create first Deployment", func() {
192+
itShouldUpsertGateway(gwNsName1, 1)
185193
})
186194
})
187195

188-
When("upserting Gateway again", func() {
196+
When("upserting first Gateway again", func() {
189197
It("must retain Deployment", func() {
190-
itShouldUpsertGateway()
198+
itShouldUpsertGateway(gwNsName1, 1)
191199
})
192200
})
193201

194-
When("deleting Gateway", func() {
195-
It("should remove Deployment", func() {
202+
When("upserting second Gateway", func() {
203+
It("should create second Deployment", func() {
204+
itShouldUpsertGateway(gwNsName2, 2)
205+
})
206+
})
207+
208+
When("deleting first Gateway", func() {
209+
It("should remove first Deployment", func() {
196210
batch := []interface{}{
197211
&events.DeleteEvent{
198212
Type: &v1beta1.Gateway{},
199-
NamespacedName: gwNsName,
213+
NamespacedName: gwNsName1,
214+
},
215+
}
216+
217+
handler.HandleEventBatch(context.Background(), batch)
218+
deps := &v1.DeploymentList{}
219+
220+
err := k8sclient.List(context.Background(), deps)
221+
222+
Expect(err).ShouldNot(HaveOccurred())
223+
Expect(deps.Items).To(HaveLen(1))
224+
Expect(deps.Items[0].ObjectMeta.Name).To(Equal("nginx-gateway-2"))
225+
})
226+
})
227+
228+
When("deleting second Gateway", func() {
229+
It("should remove second Deployment", func() {
230+
batch := []interface{}{
231+
&events.DeleteEvent{
232+
Type: &v1beta1.Gateway{},
233+
NamespacedName: gwNsName2,
200234
},
201235
}
202236

@@ -215,8 +249,8 @@ var _ = Describe("handler", func() {
215249
It("should not create Deployment", func() {
216250
gw := &v1beta1.Gateway{
217251
ObjectMeta: metav1.ObjectMeta{
218-
Name: "test-gw-2",
219-
Namespace: "test-ns-2",
252+
Name: "test-gw-3",
253+
Namespace: "test-ns-3",
220254
},
221255
Spec: v1beta1.GatewaySpec{
222256
GatewayClassName: "some-class",
@@ -241,7 +275,14 @@ var _ = Describe("handler", func() {
241275
})
242276

243277
Describe("Edge cases", func() {
278+
var gwNsName types.NamespacedName
279+
244280
BeforeEach(func() {
281+
gwNsName = types.NamespacedName{
282+
Namespace: "test-ns",
283+
Name: "test-gw",
284+
}
285+
245286
handler = newEventHandler(
246287
gcName,
247288
statusUpdater,
@@ -275,7 +316,7 @@ var _ = Describe("handler", func() {
275316

276317
When("upserting Gateway when GatewayClass doesn't exist", func() {
277318
It("should panic", func() {
278-
itShouldPanicWhenUpsertingGateway()
319+
itShouldPanicWhenUpsertingGateway(gwNsName)
279320
})
280321
})
281322

@@ -287,29 +328,29 @@ var _ = Describe("handler", func() {
287328

288329
dep := &v1.Deployment{
289330
ObjectMeta: metav1.ObjectMeta{
290-
Namespace: depNsName.Namespace,
291-
Name: depNsName.Name,
331+
Namespace: "nginx-gateway",
332+
Name: "nginx-gateway-1",
292333
},
293334
}
294335

295336
err := k8sclient.Create(context.Background(), dep)
296337
Expect(err).ShouldNot(HaveOccurred())
297338

298-
itShouldPanicWhenUpsertingGateway()
339+
itShouldPanicWhenUpsertingGateway(gwNsName)
299340
})
300341
})
301342

302343
When("deleting Gateway when Deployment can't be deleted", func() {
303344
It("should panic", func() {
304345
itShouldUpsertGatewayClass()
305-
itShouldUpsertGateway()
346+
itShouldUpsertGateway(gwNsName, 1)
306347

307348
// Delete the deployment so that the Handler will fail to delete it because it doesn't exist.
308349

309350
dep := &v1.Deployment{
310351
ObjectMeta: metav1.ObjectMeta{
311-
Namespace: depNsName.Namespace,
312-
Name: depNsName.Name,
352+
Namespace: "nginx-gateway",
353+
Name: "nginx-gateway-1",
313354
},
314355
}
315356

@@ -364,7 +405,7 @@ var _ = Describe("handler", func() {
364405
)
365406

366407
itShouldUpsertGatewayClass()
367-
itShouldPanicWhenUpsertingGateway()
408+
itShouldPanicWhenUpsertingGateway(types.NamespacedName{Namespace: "test-ns", Name: "test-gw"})
368409
})
369410
})
370411
})

0 commit comments

Comments
 (0)