Skip to content

Commit c831c16

Browse files
authored
fix: sync exception caused by ingress endpoint 0 (#2538)
1 parent efc29bf commit c831c16

File tree

6 files changed

+138
-34
lines changed

6 files changed

+138
-34
lines changed

api/adc/types.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,20 @@ func (n *UpstreamNodes) UnmarshalJSON(p []byte) error {
505505
return nil
506506
}
507507

508+
// MarshalJSON implements the json.Marshaler interface for UpstreamNodes.
509+
// By default, Go serializes a nil slice as JSON null. However, for compatibility
510+
// with APISIX semantics, we want a nil UpstreamNodes to be encoded as an empty
511+
// array ([]) instead of null. Non-nil slices are marshaled as usual.
512+
//
513+
// See APISIX upstream nodes schema definition for details:
514+
// https://github.com/apache/apisix/blob/77dacda31277a31d6014b4970e36bae2a5c30907/apisix/schema_def.lua#L295-L338
515+
func (n UpstreamNodes) MarshalJSON() ([]byte, error) {
516+
if n == nil {
517+
return []byte("[]"), nil
518+
}
519+
return json.Marshal([]UpstreamNode(n))
520+
}
521+
508522
// ComposeRouteName uses namespace, name and rule name to compose
509523
// the route name.
510524
func ComposeRouteName(namespace, name string, rule string) string {

internal/adc/translator/httproute.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ func (t *Translator) fillHTTPRoutePolicies(routes []*adctypes.Route, policies []
285285
}
286286

287287
func (t *Translator) translateEndpointSlice(portName *string, weight int, endpointSlices []discoveryv1.EndpointSlice, endpointFilter func(*discoveryv1.Endpoint) bool) adctypes.UpstreamNodes {
288-
var nodes adctypes.UpstreamNodes
288+
nodes := adctypes.UpstreamNodes{}
289289
if len(endpointSlices) == 0 {
290290
return nodes
291291
}

internal/adc/translator/ingress.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ func (t *Translator) TranslateIngress(tctx *provider.TranslateContext, obj *netw
222222

223223
// translateEndpointSliceForIngress create upstream nodes from EndpointSlice
224224
func (t *Translator) translateEndpointSliceForIngress(weight int, endpointSlices []discoveryv1.EndpointSlice, servicePort *corev1.ServicePort) adctypes.UpstreamNodes {
225-
var nodes adctypes.UpstreamNodes
225+
nodes := adctypes.UpstreamNodes{}
226226
if len(endpointSlices) == 0 {
227227
return nodes
228228
}

test/e2e/crds/v2/route.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,61 @@ spec:
471471
}).WithTimeout(30 * time.Second).ProbeEvery(1 * time.Second).Should(Equal(http.StatusOK))
472472
}
473473
})
474+
475+
It("Service Endpoints Changed", func() {
476+
const apisixRouteSpec = `
477+
apiVersion: apisix.apache.org/v2
478+
kind: ApisixRoute
479+
metadata:
480+
name: default
481+
spec:
482+
ingressClassName: %s
483+
http:
484+
- name: rule0
485+
match:
486+
hosts:
487+
- httpbin
488+
paths:
489+
- /*
490+
backends:
491+
- serviceName: httpbin-service-e2e-test
492+
servicePort: 80
493+
`
494+
495+
By("apply ApisixRoute")
496+
var apisixRoute apiv2.ApisixRoute
497+
applier.MustApplyAPIv2(types.NamespacedName{Namespace: s.Namespace(), Name: "default"},
498+
&apisixRoute, fmt.Sprintf(apisixRouteSpec, s.Namespace()))
499+
500+
s.RequestAssert(&scaffold.RequestAssert{
501+
Method: "GET",
502+
Path: "/get",
503+
Host: "httpbin",
504+
Check: scaffold.WithExpectedStatus(http.StatusOK),
505+
})
506+
507+
By("scale httpbin deployment to 0")
508+
err := s.ScaleHTTPBIN(0)
509+
Expect(err).NotTo(HaveOccurred(), "scaling httpbin deployment to 0")
510+
511+
s.RequestAssert(&scaffold.RequestAssert{
512+
Method: "GET",
513+
Path: "/get",
514+
Host: "httpbin",
515+
Check: scaffold.WithExpectedStatus(http.StatusServiceUnavailable),
516+
})
517+
518+
By("scale httpbin deployment to 1")
519+
err = s.ScaleHTTPBIN(1)
520+
Expect(err).NotTo(HaveOccurred(), "scaling httpbin deployment to 1")
521+
522+
s.RequestAssert(&scaffold.RequestAssert{
523+
Method: "GET",
524+
Path: "/get",
525+
Host: "httpbin",
526+
Check: scaffold.WithExpectedStatus(http.StatusOK),
527+
})
528+
})
474529
})
475530

476531
Context("Test ApisixRoute reference ApisixUpstream", func() {

test/e2e/gatewayapi/httproute.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,40 @@ spec:
781781
applyHTTPRouteAndAssert(s, route, asserts)
782782
})
783783

784+
It("Service Endpoints changed", func() {
785+
gatewayName := s.Namespace()
786+
By("create HTTPRoute")
787+
s.ResourceApplied("HTTPRoute", "httpbin", fmt.Sprintf(exactRouteByGet, gatewayName), 1)
788+
789+
s.RequestAssert(&scaffold.RequestAssert{
790+
Method: "GET",
791+
Path: "/get",
792+
Host: "httpbin.example",
793+
Check: scaffold.WithExpectedStatus(http.StatusOK),
794+
})
795+
796+
By("scale httpbin deployment to 0")
797+
err := s.ScaleHTTPBIN(0)
798+
Expect(err).NotTo(HaveOccurred(), "scaling httpbin deployment to 0")
799+
800+
s.RequestAssert(&scaffold.RequestAssert{
801+
Method: "GET",
802+
Path: "/get",
803+
Host: "httpbin.example",
804+
Check: scaffold.WithExpectedStatus(http.StatusServiceUnavailable),
805+
})
806+
807+
By("scale httpbin deployment to 1")
808+
err = s.ScaleHTTPBIN(1)
809+
Expect(err).NotTo(HaveOccurred(), "scaling httpbin deployment to 1")
810+
811+
s.RequestAssert(&scaffold.RequestAssert{
812+
Method: "GET",
813+
Path: "/get",
814+
Host: "httpbin.example",
815+
Check: scaffold.WithExpectedStatus(http.StatusOK),
816+
})
817+
})
784818
})
785819

786820
Context("HTTPRoute Rule Match", func() {

test/e2e/ingress/ingress.go

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,7 @@ spec:
204204
port:
205205
number: 80
206206
`
207-
208-
It("Test IngressClass Selection", func() {
207+
BeforeEach(func() {
209208
By("create GatewayProxy")
210209
gatewayProxy := fmt.Sprintf(gatewayProxyYaml, s.Namespace(), s.Deployer.GetAdminEndpoint(), s.AdminKey())
211210
err := s.CreateResourceFromStringWithNamespace(gatewayProxy, s.Namespace())
@@ -217,34 +216,47 @@ spec:
217216
Expect(err).NotTo(HaveOccurred(), "creating Default IngressClass")
218217
time.Sleep(5 * time.Second)
219218

219+
})
220+
221+
It("Service Endpoints Changed", func() {
220222
By("create Ingress without IngressClass")
221-
err = s.CreateResourceFromStringWithNamespace(fmt.Sprintf(defaultIngress, s.Namespace()), s.Namespace())
223+
err := s.CreateResourceFromStringWithNamespace(fmt.Sprintf(defaultIngress, s.Namespace()), s.Namespace())
222224
Expect(err).NotTo(HaveOccurred(), "creating Ingress without IngressClass")
223225
time.Sleep(5 * time.Second)
224226

225227
By("verify default ingress")
226-
s.NewAPISIXClient().
227-
GET("/get").
228-
WithHost("default.example.com").
229-
Expect().
230-
Status(200)
231-
})
228+
s.RequestAssert(&scaffold.RequestAssert{
229+
Method: "GET",
230+
Path: "/get",
231+
Host: "default.example.com",
232+
Check: scaffold.WithExpectedStatus(http.StatusOK),
233+
})
232234

233-
It("Proxy External Service", func() {
234-
By("create GatewayProxy")
235-
gatewayProxy := fmt.Sprintf(gatewayProxyYaml, s.Namespace(), s.Deployer.GetAdminEndpoint(), s.AdminKey())
236-
err := s.CreateResourceFromStringWithNamespace(gatewayProxy, s.Namespace())
237-
Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy")
238-
time.Sleep(5 * time.Second)
235+
err = s.ScaleHTTPBIN(0)
236+
Expect(err).NotTo(HaveOccurred(), "scaling httpbin to 0")
239237

240-
By("create Default IngressClass")
241-
err = s.CreateResourceFromStringWithNamespace(fmt.Sprintf(defaultIngressClass, s.GetControllerName(), s.Namespace()), s.Namespace())
242-
Expect(err).NotTo(HaveOccurred(), "creating Default IngressClass")
243-
time.Sleep(5 * time.Second)
238+
s.RequestAssert(&scaffold.RequestAssert{
239+
Method: "GET",
240+
Path: "/get",
241+
Host: "default.example.com",
242+
Check: scaffold.WithExpectedStatus(http.StatusServiceUnavailable),
243+
})
244+
245+
err = s.ScaleHTTPBIN(1)
246+
Expect(err).NotTo(HaveOccurred(), "scaling httpbin to 1")
244247

248+
s.RequestAssert(&scaffold.RequestAssert{
249+
Method: "GET",
250+
Path: "/get",
251+
Host: "default.example.com",
252+
Check: scaffold.WithExpectedStatus(http.StatusOK),
253+
})
254+
})
255+
256+
It("Proxy External Service", func() {
245257
By("create Ingress")
246258
ingressName := s.Namespace() + "-external"
247-
err = s.CreateResourceFromStringWithNamespace(fmt.Sprintf(ingressWithExternalName, ingressName), s.Namespace())
259+
err := s.CreateResourceFromStringWithNamespace(fmt.Sprintf(ingressWithExternalName, ingressName), s.Namespace())
248260
Expect(err).NotTo(HaveOccurred(), "creating Ingress without IngressClass")
249261

250262
By("checking the external service response")
@@ -259,20 +271,9 @@ spec:
259271
})
260272

261273
It("Delete Ingress during restart", func() {
262-
By("create GatewayProxy")
263-
gatewayProxy := fmt.Sprintf(gatewayProxyYaml, s.Namespace(), s.Deployer.GetAdminEndpoint(), s.AdminKey())
264-
err := s.CreateResourceFromStringWithNamespace(gatewayProxy, s.Namespace())
265-
Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy")
266-
time.Sleep(5 * time.Second)
267-
268-
By("create Default IngressClass")
269-
err = s.CreateResourceFromStringWithNamespace(fmt.Sprintf(defaultIngressClass, s.GetControllerName(), s.Namespace()), s.Namespace())
270-
Expect(err).NotTo(HaveOccurred(), "creating Default IngressClass")
271-
time.Sleep(5 * time.Second)
272-
273274
By("create Ingress with ExternalName")
274275
ingressName := s.Namespace() + "-external"
275-
err = s.CreateResourceFromStringWithNamespace(fmt.Sprintf(ingressWithExternalName, ingressName), s.Namespace())
276+
err := s.CreateResourceFromStringWithNamespace(fmt.Sprintf(ingressWithExternalName, ingressName), s.Namespace())
276277
Expect(err).NotTo(HaveOccurred(), "creating Ingress without IngressClass")
277278
time.Sleep(5 * time.Second)
278279

0 commit comments

Comments
 (0)