dual stack services (#91824)
* api: structure change * api: defaulting, conversion, and validation * [FIX] validation: auto remove second ip/family when service changes to SingleStack * [FIX] api: defaulting, conversion, and validation * api-server: clusterIPs alloc, printers, storage and strategy * [FIX] clusterIPs default on read * alloc: auto remove second ip/family when service changes to SingleStack * api-server: repair loop handling for clusterIPs * api-server: force kubernetes default service into single stack * api-server: tie dualstack feature flag with endpoint feature flag * controller-manager: feature flag, endpoint, and endpointSlice controllers handling multi family service * [FIX] controller-manager: feature flag, endpoint, and endpointSlicecontrollers handling multi family service * kube-proxy: feature-flag, utils, proxier, and meta proxier * [FIX] kubeproxy: call both proxier at the same time * kubenet: remove forced pod IP sorting * kubectl: modify describe to include ClusterIPs, IPFamilies, and IPFamilyPolicy * e2e: fix tests that depends on IPFamily field AND add dual stack tests * e2e: fix expected error message for ClusterIP immutability * add integration tests for dualstack the third phase of dual stack is a very complex change in the API, basically it introduces Dual Stack services. Main changes are: - It pluralizes the Service IPFamily field to IPFamilies, and removes the singular field. - It introduces a new field IPFamilyPolicyType that can take 3 values to express the "dual-stack(mad)ness" of the cluster: SingleStack, PreferDualStack and RequireDualStack - It pluralizes ClusterIP to ClusterIPs. The goal is to add coverage to the services API operations, taking into account the 6 different modes a cluster can have: - single stack: IP4 or IPv6 (as of today) - dual stack: IPv4 only, IPv6 only, IPv4 - IPv6, IPv6 - IPv4 * [FIX] add integration tests for dualstack * generated data * generated files Co-authored-by: Antonio Ojea <aojea@redhat.com>
This commit is contained in:

committed by
GitHub

parent
d0e06cf3e0
commit
6675eba3ef
@@ -385,7 +385,7 @@ func TestPodToEndpoint(t *testing.T) {
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
endpoint := podToEndpoint(testCase.pod, testCase.node, testCase.svc)
|
||||
endpoint := podToEndpoint(testCase.pod, testCase.node, testCase.svc, discovery.AddressTypeIPv4)
|
||||
if !reflect.DeepEqual(testCase.expectedEndpoint, endpoint) {
|
||||
t.Errorf("Expected endpoint: %v, got: %v", testCase.expectedEndpoint, endpoint)
|
||||
}
|
||||
@@ -889,7 +889,8 @@ func newServiceAndEndpointMeta(name, namespace string) (v1.Service, endpointMeta
|
||||
Protocol: v1.ProtocolTCP,
|
||||
Name: name,
|
||||
}},
|
||||
Selector: map[string]string{"foo": "bar"},
|
||||
Selector: map[string]string{"foo": "bar"},
|
||||
IPFamilies: []v1.IPFamily{v1.IPv4Protocol},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -918,3 +919,135 @@ func newEmptyEndpointSlice(n int, namespace string, endpointMeta endpointMeta, s
|
||||
Endpoints: []discovery.Endpoint{},
|
||||
}
|
||||
}
|
||||
|
||||
func TestSupportedServiceAddressType(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
service v1.Service
|
||||
expectedAddressTypes []discovery.AddressType
|
||||
}{
|
||||
{
|
||||
name: "v4 service with no ip families (cluster upgrade)",
|
||||
expectedAddressTypes: []discovery.AddressType{discovery.AddressTypeIPv4},
|
||||
service: v1.Service{
|
||||
Spec: v1.ServiceSpec{
|
||||
ClusterIP: "10.0.0.10",
|
||||
IPFamilies: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "v6 service with no ip families (cluster upgrade)",
|
||||
expectedAddressTypes: []discovery.AddressType{discovery.AddressTypeIPv6},
|
||||
service: v1.Service{
|
||||
Spec: v1.ServiceSpec{
|
||||
ClusterIP: "2000::1",
|
||||
IPFamilies: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "v4 service",
|
||||
expectedAddressTypes: []discovery.AddressType{discovery.AddressTypeIPv4},
|
||||
service: v1.Service{
|
||||
Spec: v1.ServiceSpec{
|
||||
IPFamilies: []v1.IPFamily{v1.IPv4Protocol},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "v6 services",
|
||||
expectedAddressTypes: []discovery.AddressType{discovery.AddressTypeIPv6},
|
||||
service: v1.Service{
|
||||
Spec: v1.ServiceSpec{
|
||||
IPFamilies: []v1.IPFamily{v1.IPv6Protocol},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "v4,v6 service",
|
||||
expectedAddressTypes: []discovery.AddressType{discovery.AddressTypeIPv4, discovery.AddressTypeIPv6},
|
||||
service: v1.Service{
|
||||
Spec: v1.ServiceSpec{
|
||||
IPFamilies: []v1.IPFamily{v1.IPv4Protocol, v1.IPv6Protocol},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "v6,v4 service",
|
||||
expectedAddressTypes: []discovery.AddressType{discovery.AddressTypeIPv6, discovery.AddressTypeIPv4},
|
||||
service: v1.Service{
|
||||
Spec: v1.ServiceSpec{
|
||||
IPFamilies: []v1.IPFamily{v1.IPv6Protocol, v1.IPv4Protocol},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "headless with no selector and no families (old api-server)",
|
||||
expectedAddressTypes: []discovery.AddressType{discovery.AddressTypeIPv6, discovery.AddressTypeIPv4},
|
||||
service: v1.Service{
|
||||
Spec: v1.ServiceSpec{
|
||||
ClusterIP: v1.ClusterIPNone,
|
||||
IPFamilies: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "headless with selector and no families (old api-server)",
|
||||
expectedAddressTypes: []discovery.AddressType{discovery.AddressTypeIPv6, discovery.AddressTypeIPv4},
|
||||
service: v1.Service{
|
||||
Spec: v1.ServiceSpec{
|
||||
Selector: map[string]string{"foo": "bar"},
|
||||
ClusterIP: v1.ClusterIPNone,
|
||||
IPFamilies: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "headless with no selector with families",
|
||||
expectedAddressTypes: []discovery.AddressType{discovery.AddressTypeIPv4, discovery.AddressTypeIPv6},
|
||||
service: v1.Service{
|
||||
Spec: v1.ServiceSpec{
|
||||
ClusterIP: v1.ClusterIPNone,
|
||||
IPFamilies: []v1.IPFamily{v1.IPv4Protocol, v1.IPv6Protocol},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "headless with selector with families",
|
||||
expectedAddressTypes: []discovery.AddressType{discovery.AddressTypeIPv4, discovery.AddressTypeIPv6},
|
||||
service: v1.Service{
|
||||
Spec: v1.ServiceSpec{
|
||||
Selector: map[string]string{"foo": "bar"},
|
||||
ClusterIP: v1.ClusterIPNone,
|
||||
IPFamilies: []v1.IPFamily{v1.IPv4Protocol, v1.IPv6Protocol},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
addressTypes := getAddressTypesForService(&testCase.service)
|
||||
if len(addressTypes) != len(testCase.expectedAddressTypes) {
|
||||
t.Fatalf("expected count address types %v got %v", len(testCase.expectedAddressTypes), len(addressTypes))
|
||||
}
|
||||
|
||||
// compare
|
||||
for _, expectedAddressType := range testCase.expectedAddressTypes {
|
||||
found := false
|
||||
for key := range addressTypes {
|
||||
if key == expectedAddressType {
|
||||
found = true
|
||||
break
|
||||
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("expected address type %v was not found in the result", expectedAddressType)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user