Skip to content

Commit 4f362ea

Browse files
ivanmatmatiGopher Bot
authored andcommitted
TEST/MINOR: service: add unit tests for getRuntimeBackend
Cover five scenarios: - ExternalName service with orphan EndpointSlices in HAProxyRuntime (regression test for the fix) - ExternalName service with no HAProxyRuntime entry - ClusterIP service with matching port name - ClusterIP service with no HAProxyRuntime entry - ClusterIP service with port name mismatch
1 parent cd94a73 commit 4f362ea

1 file changed

Lines changed: 198 additions & 0 deletions

File tree

pkg/service/endpoints_test.go

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
// Copyright 2019 HAProxy Technologies LLC
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+
// http://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+
package service
16+
17+
import (
18+
"testing"
19+
20+
"github.com/haproxytech/kubernetes-ingress/pkg/store"
21+
"github.com/stretchr/testify/assert"
22+
"github.com/stretchr/testify/require"
23+
)
24+
25+
func newServiceForTest(svcName, namespace, dns string, ports []store.ServicePort, path *store.IngressPath) *Service {
26+
return &Service{
27+
resource: &store.Service{
28+
Name: svcName,
29+
Namespace: namespace,
30+
DNS: dns,
31+
Ports: ports,
32+
},
33+
path: path,
34+
}
35+
}
36+
37+
func newK8sWithRuntime(namespace, svcName, portName string, backend *store.RuntimeBackend) store.K8s {
38+
k8s := store.K8s{
39+
Namespaces: map[string]*store.Namespace{
40+
namespace: {
41+
HAProxyRuntime: map[string]map[string]*store.RuntimeBackend{
42+
svcName: {
43+
portName: backend,
44+
},
45+
},
46+
},
47+
},
48+
}
49+
return k8s
50+
}
51+
52+
// TestGetRuntimeBackend_ExternalName_IgnoresOrphanEndpointSlices verifies that an
53+
// ExternalName service always resolves via DNS even when orphan EndpointSlices
54+
// (left over from a prior ClusterIP state) populate HAProxyRuntime.
55+
func TestGetRuntimeBackend_ExternalName_IgnoresOrphanEndpointSlices(t *testing.T) {
56+
path := &store.IngressPath{
57+
SvcName: "nginx",
58+
SvcNamespace: "haproxy-controller",
59+
SvcPortInt: 80,
60+
SvcPortString: "",
61+
SvcPortResolved: &store.ServicePort{
62+
Name: "",
63+
Port: 80,
64+
},
65+
}
66+
67+
svc := newServiceForTest(
68+
"nginx", "haproxy-controller", "api1.example.com",
69+
[]store.ServicePort{{Port: 80}},
70+
path,
71+
)
72+
73+
// Simulate orphan EndpointSlice: HAProxyRuntime is populated with a named port
74+
// "http" from the previous ClusterIP state.
75+
orphanBackend := &store.RuntimeBackend{
76+
Endpoints: store.RuntimeEndpoints{store.RuntimeEndpoint{Port: 80}: {}},
77+
}
78+
k8s := newK8sWithRuntime("haproxy-controller", "nginx", "http", orphanBackend)
79+
80+
backend, err := svc.getRuntimeBackend(k8s)
81+
82+
require.NoError(t, err)
83+
require.Len(t, backend.HAProxySrvs, 1)
84+
assert.Equal(t, "api1.example.com", backend.HAProxySrvs[0].Address)
85+
assert.Equal(t, "SRV_1", backend.HAProxySrvs[0].Name)
86+
assert.Equal(t, int64(80), backend.HAProxySrvs[0].Port)
87+
}
88+
89+
// TestGetRuntimeBackend_ExternalName_NoRuntime verifies that an ExternalName
90+
// service resolves correctly even when HAProxyRuntime has no entry for it.
91+
func TestGetRuntimeBackend_ExternalName_NoRuntime(t *testing.T) {
92+
path := &store.IngressPath{
93+
SvcName: "my-ext-svc",
94+
SvcNamespace: "default",
95+
SvcPortInt: 443,
96+
SvcPortResolved: &store.ServicePort{
97+
Port: 443,
98+
},
99+
}
100+
101+
svc := newServiceForTest(
102+
"my-ext-svc", "default", "api.example.com",
103+
[]store.ServicePort{{Port: 443}},
104+
path,
105+
)
106+
107+
k8s := store.K8s{
108+
Namespaces: map[string]*store.Namespace{
109+
"default": {
110+
HAProxyRuntime: map[string]map[string]*store.RuntimeBackend{},
111+
},
112+
},
113+
}
114+
115+
backend, err := svc.getRuntimeBackend(k8s)
116+
117+
require.NoError(t, err)
118+
require.Len(t, backend.HAProxySrvs, 1)
119+
assert.Equal(t, "api.example.com", backend.HAProxySrvs[0].Address)
120+
assert.Equal(t, int64(443), backend.HAProxySrvs[0].Port)
121+
}
122+
123+
// TestGetRuntimeBackend_ClusterIP_MatchingPort verifies that a regular ClusterIP
124+
// service resolves correctly from HAProxyRuntime when the port name matches.
125+
func TestGetRuntimeBackend_ClusterIP_MatchingPort(t *testing.T) {
126+
path := &store.IngressPath{
127+
SvcName: "my-svc",
128+
SvcNamespace: "default",
129+
SvcPortResolved: &store.ServicePort{
130+
Name: "http",
131+
Port: 80,
132+
},
133+
}
134+
135+
svc := newServiceForTest("my-svc", "default", "", nil, path)
136+
137+
expectedBackend := &store.RuntimeBackend{
138+
Endpoints: store.RuntimeEndpoints{store.RuntimeEndpoint{Port: 80}: {}},
139+
}
140+
k8s := newK8sWithRuntime("default", "my-svc", "http", expectedBackend)
141+
142+
backend, err := svc.getRuntimeBackend(k8s)
143+
144+
require.NoError(t, err)
145+
assert.Equal(t, expectedBackend, backend)
146+
}
147+
148+
// TestGetRuntimeBackend_ClusterIP_NoRuntime verifies that a ClusterIP service
149+
// with no HAProxyRuntime entry returns an error.
150+
func TestGetRuntimeBackend_ClusterIP_NoRuntime(t *testing.T) {
151+
path := &store.IngressPath{
152+
SvcName: "my-svc",
153+
SvcNamespace: "default",
154+
SvcPortResolved: &store.ServicePort{
155+
Name: "http",
156+
Port: 80,
157+
},
158+
}
159+
160+
svc := newServiceForTest("my-svc", "default", "", nil, path)
161+
162+
k8s := store.K8s{
163+
Namespaces: map[string]*store.Namespace{
164+
"default": {
165+
HAProxyRuntime: map[string]map[string]*store.RuntimeBackend{},
166+
},
167+
},
168+
}
169+
170+
_, err := svc.getRuntimeBackend(k8s)
171+
172+
assert.Error(t, err)
173+
assert.Contains(t, err.Error(), "no available endpoints")
174+
}
175+
176+
// TestGetRuntimeBackend_ClusterIP_PortMismatch verifies that a ClusterIP service
177+
// returns an error when the resolved port name does not match any runtime entry.
178+
func TestGetRuntimeBackend_ClusterIP_PortMismatch(t *testing.T) {
179+
path := &store.IngressPath{
180+
SvcName: "my-svc",
181+
SvcNamespace: "default",
182+
SvcPortString: "web",
183+
SvcPortResolved: &store.ServicePort{
184+
Name: "web",
185+
Port: 8080,
186+
},
187+
}
188+
189+
svc := newServiceForTest("my-svc", "default", "", nil, path)
190+
191+
// HAProxyRuntime has "http" port but path expects "web"
192+
k8s := newK8sWithRuntime("default", "my-svc", "http", &store.RuntimeBackend{})
193+
194+
_, err := svc.getRuntimeBackend(k8s)
195+
196+
assert.Error(t, err)
197+
assert.Contains(t, err.Error(), "web")
198+
}

0 commit comments

Comments
 (0)