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
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
netutils "k8s.io/utils/net"
|
||||
)
|
||||
|
||||
// ValidateConditionalService validates conditionally valid fields. allowedIPFamilies is an ordered
|
||||
// list of the valid IP families (IPv4 or IPv6) that are supported. The first family in the slice
|
||||
// is the cluster default, although the clusterIP here dictates the family defaulting.
|
||||
func ValidateConditionalService(service, oldService *api.Service, allowedIPFamilies []api.IPFamily) field.ErrorList {
|
||||
var errs field.ErrorList
|
||||
|
||||
errs = append(errs, validateIPFamily(service, oldService, allowedIPFamilies)...)
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// validateIPFamily checks the IPFamily field.
|
||||
func validateIPFamily(service, oldService *api.Service, allowedIPFamilies []api.IPFamily) field.ErrorList {
|
||||
var errs field.ErrorList
|
||||
|
||||
// specifically allow an invalid value to remain in storage as long as the user isn't changing it, regardless of gate
|
||||
if oldService != nil && oldService.Spec.IPFamily != nil && service.Spec.IPFamily != nil && *oldService.Spec.IPFamily == *service.Spec.IPFamily {
|
||||
return errs
|
||||
}
|
||||
|
||||
// If the gate is off, setting or changing IPFamily is not allowed, but clearing it is
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
|
||||
if service.Spec.IPFamily != nil {
|
||||
if oldService != nil {
|
||||
errs = append(errs, ValidateImmutableField(service.Spec.IPFamily, oldService.Spec.IPFamily, field.NewPath("spec", "ipFamily"))...)
|
||||
} else {
|
||||
errs = append(errs, field.Forbidden(field.NewPath("spec", "ipFamily"), "programmer error, must be cleared when the dual-stack feature gate is off"))
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// PrepareCreate, PrepareUpdate, and test cases must all set IPFamily when the gate is on
|
||||
if service.Spec.IPFamily == nil {
|
||||
errs = append(errs, field.Required(field.NewPath("spec", "ipFamily"), "programmer error, must be set or defaulted by other fields"))
|
||||
return errs
|
||||
}
|
||||
|
||||
// A user is not allowed to change the IPFamily field, except for ExternalName services
|
||||
if oldService != nil && oldService.Spec.IPFamily != nil && service.Spec.Type != api.ServiceTypeExternalName {
|
||||
errs = append(errs, ValidateImmutableField(service.Spec.IPFamily, oldService.Spec.IPFamily, field.NewPath("spec", "ipFamily"))...)
|
||||
}
|
||||
|
||||
// Verify the IPFamily is one of the allowed families
|
||||
desiredFamily := *service.Spec.IPFamily
|
||||
if hasIPFamily(allowedIPFamilies, desiredFamily) {
|
||||
// the IP family is one of the allowed families, verify that it matches cluster IP
|
||||
switch ip := net.ParseIP(service.Spec.ClusterIP); {
|
||||
case ip == nil:
|
||||
// do not need to check anything
|
||||
case netutils.IsIPv6(ip) && desiredFamily != api.IPv6Protocol:
|
||||
errs = append(errs, field.Invalid(field.NewPath("spec", "ipFamily"), *service.Spec.IPFamily, "does not match IPv6 cluster IP"))
|
||||
case !netutils.IsIPv6(ip) && desiredFamily != api.IPv4Protocol:
|
||||
errs = append(errs, field.Invalid(field.NewPath("spec", "ipFamily"), *service.Spec.IPFamily, "does not match IPv4 cluster IP"))
|
||||
}
|
||||
} else {
|
||||
errs = append(errs, field.Invalid(field.NewPath("spec", "ipFamily"), desiredFamily, fmt.Sprintf("only the following families are allowed: %s", joinIPFamilies(allowedIPFamilies, ", "))))
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func hasIPFamily(families []api.IPFamily, family api.IPFamily) bool {
|
||||
for _, allow := range families {
|
||||
if allow == family {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func joinIPFamilies(families []api.IPFamily, separator string) string {
|
||||
var b strings.Builder
|
||||
for i, family := range families {
|
||||
if i != 0 {
|
||||
b.WriteString(separator)
|
||||
}
|
||||
b.WriteString(string(family))
|
||||
}
|
||||
return b.String()
|
||||
}
|
Reference in New Issue
Block a user