Skip to content

Commit fbc78f5

Browse files
authored
Merge pull request kubernetes#91590 from knight42/fix/repair-node-port
fix(service::repair): accept same nodePort with different protocols
2 parents dbef5e3 + 1368497 commit fbc78f5

File tree

3 files changed

+131
-7
lines changed

3 files changed

+131
-7
lines changed

pkg/registry/core/service/portallocator/controller/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ go_library(
2020
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
2121
"//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library",
2222
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
23+
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
2324
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
2425
"//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
2526
"//staging/src/k8s.io/client-go/tools/record:go_default_library",

pkg/registry/core/service/portallocator/controller/repair.go

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2727
"k8s.io/apimachinery/pkg/util/net"
2828
"k8s.io/apimachinery/pkg/util/runtime"
29+
"k8s.io/apimachinery/pkg/util/sets"
2930
"k8s.io/apimachinery/pkg/util/wait"
3031
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
3132
"k8s.io/client-go/tools/record"
@@ -200,17 +201,39 @@ func (c *Repair) runOnce() error {
200201
return nil
201202
}
202203

204+
// collectServiceNodePorts returns nodePorts specified in the Service.
205+
// Please note that:
206+
// 1. same nodePort with *same* protocol will be duplicated as it is
207+
// 2. same nodePort with *different* protocol will be deduplicated
203208
func collectServiceNodePorts(service *corev1.Service) []int {
204-
servicePorts := []int{}
205-
for i := range service.Spec.Ports {
206-
servicePort := &service.Spec.Ports[i]
207-
if servicePort.NodePort != 0 {
208-
servicePorts = append(servicePorts, int(servicePort.NodePort))
209+
var servicePorts []int
210+
// map from nodePort to set of protocols
211+
seen := make(map[int]sets.String)
212+
for _, port := range service.Spec.Ports {
213+
nodePort := int(port.NodePort)
214+
if nodePort == 0 {
215+
continue
216+
}
217+
proto := string(port.Protocol)
218+
s := seen[nodePort]
219+
if s == nil { // have not seen this nodePort before
220+
s = sets.NewString(proto)
221+
servicePorts = append(servicePorts, nodePort)
222+
} else if s.Has(proto) { // same nodePort with same protocol
223+
servicePorts = append(servicePorts, nodePort)
224+
} else { // same nodePort with different protocol
225+
s.Insert(proto)
209226
}
227+
seen[nodePort] = s
210228
}
211229

212-
if service.Spec.HealthCheckNodePort != 0 {
213-
servicePorts = append(servicePorts, int(service.Spec.HealthCheckNodePort))
230+
healthPort := int(service.Spec.HealthCheckNodePort)
231+
if healthPort != 0 {
232+
s := seen[healthPort]
233+
// TODO: is it safe to assume the protocol is always TCP?
234+
if s == nil || s.Has(string(corev1.ProtocolTCP)) {
235+
servicePorts = append(servicePorts, healthPort)
236+
}
214237
}
215238

216239
return servicePorts

pkg/registry/core/service/portallocator/controller/repair_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package controller
1818

1919
import (
2020
"fmt"
21+
"reflect"
22+
"sort"
2123
"strings"
2224
"testing"
2325

@@ -203,3 +205,101 @@ func TestRepairWithExisting(t *testing.T) {
203205
t.Errorf("unexpected portallocator state: %d free", free)
204206
}
205207
}
208+
209+
func TestCollectServiceNodePorts(t *testing.T) {
210+
tests := []struct {
211+
name string
212+
serviceSpec corev1.ServiceSpec
213+
expected []int
214+
}{
215+
{
216+
name: "no duplicated nodePorts",
217+
serviceSpec: corev1.ServiceSpec{
218+
Ports: []corev1.ServicePort{
219+
{NodePort: 111, Protocol: corev1.ProtocolTCP},
220+
{NodePort: 112, Protocol: corev1.ProtocolUDP},
221+
{NodePort: 113, Protocol: corev1.ProtocolUDP},
222+
},
223+
},
224+
expected: []int{111, 112, 113},
225+
},
226+
{
227+
name: "duplicated nodePort with TCP protocol",
228+
serviceSpec: corev1.ServiceSpec{
229+
Ports: []corev1.ServicePort{
230+
{NodePort: 111, Protocol: corev1.ProtocolTCP},
231+
{NodePort: 111, Protocol: corev1.ProtocolTCP},
232+
{NodePort: 112, Protocol: corev1.ProtocolUDP},
233+
},
234+
},
235+
expected: []int{111, 111, 112},
236+
},
237+
{
238+
name: "duplicated nodePort with UDP protocol",
239+
serviceSpec: corev1.ServiceSpec{
240+
Ports: []corev1.ServicePort{
241+
{NodePort: 111, Protocol: corev1.ProtocolUDP},
242+
{NodePort: 111, Protocol: corev1.ProtocolUDP},
243+
{NodePort: 112, Protocol: corev1.ProtocolTCP},
244+
},
245+
},
246+
expected: []int{111, 111, 112},
247+
},
248+
{
249+
name: "duplicated nodePort with different protocol",
250+
serviceSpec: corev1.ServiceSpec{
251+
Ports: []corev1.ServicePort{
252+
{NodePort: 111, Protocol: corev1.ProtocolTCP},
253+
{NodePort: 112, Protocol: corev1.ProtocolTCP},
254+
{NodePort: 111, Protocol: corev1.ProtocolUDP},
255+
},
256+
},
257+
expected: []int{111, 112},
258+
},
259+
{
260+
name: "no duplicated port(with health check port)",
261+
serviceSpec: corev1.ServiceSpec{
262+
Ports: []corev1.ServicePort{
263+
{NodePort: 111, Protocol: corev1.ProtocolTCP},
264+
{NodePort: 112, Protocol: corev1.ProtocolUDP},
265+
},
266+
HealthCheckNodePort: 113,
267+
},
268+
expected: []int{111, 112, 113},
269+
},
270+
{
271+
name: "nodePort has different protocol with duplicated health check port",
272+
serviceSpec: corev1.ServiceSpec{
273+
Ports: []corev1.ServicePort{
274+
{NodePort: 111, Protocol: corev1.ProtocolUDP},
275+
{NodePort: 112, Protocol: corev1.ProtocolTCP},
276+
},
277+
HealthCheckNodePort: 111,
278+
},
279+
expected: []int{111, 112},
280+
},
281+
{
282+
name: "nodePort has same protocol as duplicated health check port",
283+
serviceSpec: corev1.ServiceSpec{
284+
Ports: []corev1.ServicePort{
285+
{NodePort: 111, Protocol: corev1.ProtocolUDP},
286+
{NodePort: 112, Protocol: corev1.ProtocolTCP},
287+
},
288+
HealthCheckNodePort: 112,
289+
},
290+
expected: []int{111, 112, 112},
291+
},
292+
}
293+
for _, tc := range tests {
294+
t.Run(tc.name, func(t *testing.T) {
295+
ports := collectServiceNodePorts(&corev1.Service{
296+
ObjectMeta: metav1.ObjectMeta{Namespace: "one", Name: "one"},
297+
Spec: tc.serviceSpec,
298+
})
299+
sort.Ints(ports)
300+
if !reflect.DeepEqual(tc.expected, ports) {
301+
t.Fatalf("Invalid result\nexpected: %v\ngot: %v", tc.expected, ports)
302+
}
303+
})
304+
}
305+
}

0 commit comments

Comments
 (0)