mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-14 23:17:29 +00:00
cmd/k8s-operator, k8s-operator: support Static Endpoints on ProxyGroups (#16115)
updates: #14674 Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
This commit is contained in:
277
cmd/k8s-operator/nodeport-services-ports_test.go
Normal file
277
cmd/k8s-operator/nodeport-services-ports_test.go
Normal file
@@ -0,0 +1,277 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !plan9
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
tsapi "tailscale.com/k8s-operator/apis/v1alpha1"
|
||||
"tailscale.com/tstest"
|
||||
)
|
||||
|
||||
func TestGetServicesNodePortRangeFromErr(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
errStr string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "valid_error_string",
|
||||
errStr: "NodePort 777777 is not in the allowed range 30000-32767",
|
||||
want: "30000-32767",
|
||||
},
|
||||
{
|
||||
name: "error_string_with_different_message",
|
||||
errStr: "some other error without a port range",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "error_string_with_multiple_port_ranges",
|
||||
errStr: "range 1000-2000 and another range 3000-4000",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "empty_error_string",
|
||||
errStr: "",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "error_string_with_range_at_start",
|
||||
errStr: "30000-32767 is the range",
|
||||
want: "30000-32767",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := getServicesNodePortRangeFromErr(tt.errStr); got != tt.want {
|
||||
t.Errorf("got %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseServicesNodePortRange(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
p string
|
||||
want *tsapi.PortRange
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid_range",
|
||||
p: "30000-32767",
|
||||
want: &tsapi.PortRange{Port: 30000, EndPort: 32767},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "single_port_range",
|
||||
p: "30000",
|
||||
want: &tsapi.PortRange{Port: 30000, EndPort: 30000},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid_format_non_numeric_end",
|
||||
p: "30000-abc",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid_format_non_numeric_start",
|
||||
p: "abc-32767",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty_string",
|
||||
p: "",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "too_many_parts",
|
||||
p: "1-2-3",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "port_too_large_start",
|
||||
p: "65536-65537",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "port_too_large_end",
|
||||
p: "30000-65536",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "inverted_range",
|
||||
p: "32767-30000",
|
||||
want: nil,
|
||||
wantErr: true, // IsValid() will fail
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
portRange, err := parseServicesNodePortRange(tt.p)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if tt.wantErr {
|
||||
return
|
||||
}
|
||||
|
||||
if portRange == nil {
|
||||
t.Fatalf("got nil port range, expected %v", tt.want)
|
||||
}
|
||||
|
||||
if portRange.Port != tt.want.Port || portRange.EndPort != tt.want.EndPort {
|
||||
t.Errorf("got = %v, want %v", portRange, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateNodePortRanges(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
portRanges []tsapi.PortRange
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid_ranges_with_unknown_kube_range",
|
||||
portRanges: []tsapi.PortRange{
|
||||
{Port: 30003, EndPort: 30005},
|
||||
{Port: 30006, EndPort: 30007},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "overlapping_ranges",
|
||||
portRanges: []tsapi.PortRange{
|
||||
{Port: 30000, EndPort: 30010},
|
||||
{Port: 30005, EndPort: 30015},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "adjacent_ranges_no_overlap",
|
||||
portRanges: []tsapi.PortRange{
|
||||
{Port: 30010, EndPort: 30020},
|
||||
{Port: 30021, EndPort: 30022},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "identical_ranges_are_overlapping",
|
||||
portRanges: []tsapi.PortRange{
|
||||
{Port: 30005, EndPort: 30010},
|
||||
{Port: 30005, EndPort: 30010},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "range_clashes_with_existing_proxyclass",
|
||||
portRanges: []tsapi.PortRange{
|
||||
{Port: 31005, EndPort: 32070},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
// as part of this test, we want to create an adjacent ProxyClass in order to ensure that if it clashes with the one created in this test
|
||||
// that we get an error
|
||||
cl := tstest.NewClock(tstest.ClockOpts{})
|
||||
opc := &tsapi.ProxyClass{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "other-pc",
|
||||
},
|
||||
Spec: tsapi.ProxyClassSpec{
|
||||
StatefulSet: &tsapi.StatefulSet{
|
||||
Annotations: defaultProxyClassAnnotations,
|
||||
},
|
||||
StaticEndpoints: &tsapi.StaticEndpointsConfig{
|
||||
NodePort: &tsapi.NodePortConfig{
|
||||
Ports: []tsapi.PortRange{
|
||||
{Port: 31000}, {Port: 32000},
|
||||
},
|
||||
Selector: map[string]string{
|
||||
"foo/bar": "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: tsapi.ProxyClassStatus{
|
||||
Conditions: []metav1.Condition{{
|
||||
Type: string(tsapi.ProxyClassReady),
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: reasonProxyClassValid,
|
||||
Message: reasonProxyClassValid,
|
||||
LastTransitionTime: metav1.Time{Time: cl.Now().Truncate(time.Second)},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
fc := fake.NewClientBuilder().
|
||||
WithObjects(opc).
|
||||
WithStatusSubresource(opc).
|
||||
WithScheme(tsapi.GlobalScheme).
|
||||
Build()
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pc := &tsapi.ProxyClass{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pc",
|
||||
},
|
||||
Spec: tsapi.ProxyClassSpec{
|
||||
StatefulSet: &tsapi.StatefulSet{
|
||||
Annotations: defaultProxyClassAnnotations,
|
||||
},
|
||||
StaticEndpoints: &tsapi.StaticEndpointsConfig{
|
||||
NodePort: &tsapi.NodePortConfig{
|
||||
Ports: tt.portRanges,
|
||||
Selector: map[string]string{
|
||||
"foo/bar": "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: tsapi.ProxyClassStatus{
|
||||
Conditions: []metav1.Condition{{
|
||||
Type: string(tsapi.ProxyClassReady),
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: reasonProxyClassValid,
|
||||
Message: reasonProxyClassValid,
|
||||
LastTransitionTime: metav1.Time{Time: cl.Now().Truncate(time.Second)},
|
||||
}},
|
||||
},
|
||||
}
|
||||
err := validateNodePortRanges(context.Background(), fc, &tsapi.PortRange{Port: 30000, EndPort: 32767}, pc)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRandomPort(t *testing.T) {
|
||||
for range 100 {
|
||||
port := getRandomPort()
|
||||
if port < tailscaledPortMin || port > tailscaledPortMax {
|
||||
t.Errorf("generated port %d which is out of range [%d, %d]", port, tailscaledPortMin, tailscaledPortMax)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user