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:
Khaled Henidak (Kal)
2020-10-26 13:15:59 -07:00
committed by GitHub
parent d0e06cf3e0
commit 6675eba3ef
84 changed files with 11170 additions and 3514 deletions

View File

@@ -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)
}
}
})
}
}