move IPv6DualStack feature to stable. (#104691)

* kube-proxy

* endpoints controller

* app: kube-controller-manager

* app: cloud-controller-manager

* kubelet

* app: api-server

* node utils + registry/strategy

* api: validation (comment removal)

* api:pod strategy (util pkg)

* api: docs

* core: integration testing

* kubeadm: change feature gate to GA

* service registry and rest stack

* move feature to GA

* generated
This commit is contained in:
Khaled Henidak (Kal) 2021-09-24 16:30:22 -07:00 committed by GitHub
parent c74d799677
commit a53e2eaeab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 455 additions and 1373 deletions

View File

@ -9317,7 +9317,7 @@
"type": "string" "type": "string"
}, },
"clusterIPs": { "clusterIPs": {
"description": "ClusterIPs is a list of IP addresses assigned to this service, and are usually assigned randomly. If an address is specified manually, is in-range (as per system configuration), and is not in use, it will be allocated to the service; otherwise creation of the service will fail. This field may not be changed through updates unless the type field is also being changed to ExternalName (which requires this field to be empty) or the type field is being changed from ExternalName (in which case this field may optionally be specified, as describe above). Valid values are \"None\", empty string (\"\"), or a valid IP address. Setting this to \"None\" makes a \"headless service\" (no virtual IP), which is useful when direct endpoint connections are preferred and proxying is not required. Only applies to types ClusterIP, NodePort, and LoadBalancer. If this field is specified when creating a Service of type ExternalName, creation will fail. This field will be wiped when updating a Service to type ExternalName. If this field is not specified, it will be initialized from the clusterIP field. If this field is specified, clients must ensure that clusterIPs[0] and clusterIP have the same value.\n\nUnless the \"IPv6DualStack\" feature gate is enabled, this field is limited to one value, which must be the same as the clusterIP field. If the feature gate is enabled, this field may hold a maximum of two entries (dual-stack IPs, in either order). These IPs must correspond to the values of the ipFamilies field. Both clusterIPs and ipFamilies are governed by the ipFamilyPolicy field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies", "description": "ClusterIPs is a list of IP addresses assigned to this service, and are usually assigned randomly. If an address is specified manually, is in-range (as per system configuration), and is not in use, it will be allocated to the service; otherwise creation of the service will fail. This field may not be changed through updates unless the type field is also being changed to ExternalName (which requires this field to be empty) or the type field is being changed from ExternalName (in which case this field may optionally be specified, as describe above). Valid values are \"None\", empty string (\"\"), or a valid IP address. Setting this to \"None\" makes a \"headless service\" (no virtual IP), which is useful when direct endpoint connections are preferred and proxying is not required. Only applies to types ClusterIP, NodePort, and LoadBalancer. If this field is specified when creating a Service of type ExternalName, creation will fail. This field will be wiped when updating a Service to type ExternalName. If this field is not specified, it will be initialized from the clusterIP field. If this field is specified, clients must ensure that clusterIPs[0] and clusterIP have the same value.\n\nThis field may hold a maximum of two entries (dual-stack IPs, in either order). These IPs must correspond to the values of the ipFamilies field. Both clusterIPs and ipFamilies are governed by the ipFamilyPolicy field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies",
"items": { "items": {
"type": "string" "type": "string"
}, },
@ -9349,7 +9349,7 @@
"type": "string" "type": "string"
}, },
"ipFamilies": { "ipFamilies": {
"description": "IPFamilies is a list of IP families (e.g. IPv4, IPv6) assigned to this service, and is gated by the \"IPv6DualStack\" feature gate. This field is usually assigned automatically based on cluster configuration and the ipFamilyPolicy field. If this field is specified manually, the requested family is available in the cluster, and ipFamilyPolicy allows it, it will be used; otherwise creation of the service will fail. This field is conditionally mutable: it allows for adding or removing a secondary IP family, but it does not allow changing the primary IP family of the Service. Valid values are \"IPv4\" and \"IPv6\". This field only applies to Services of types ClusterIP, NodePort, and LoadBalancer, and does apply to \"headless\" services. This field will be wiped when updating a Service to type ExternalName.\n\nThis field may hold a maximum of two entries (dual-stack families, in either order). These families must correspond to the values of the clusterIPs field, if specified. Both clusterIPs and ipFamilies are governed by the ipFamilyPolicy field.", "description": "IPFamilies is a list of IP families (e.g. IPv4, IPv6) assigned to this service. This field is usually assigned automatically based on cluster configuration and the ipFamilyPolicy field. If this field is specified manually, the requested family is available in the cluster, and ipFamilyPolicy allows it, it will be used; otherwise creation of the service will fail. This field is conditionally mutable: it allows for adding or removing a secondary IP family, but it does not allow changing the primary IP family of the Service. Valid values are \"IPv4\" and \"IPv6\". This field only applies to Services of types ClusterIP, NodePort, and LoadBalancer, and does apply to \"headless\" services. This field will be wiped when updating a Service to type ExternalName.\n\nThis field may hold a maximum of two entries (dual-stack families, in either order). These families must correspond to the values of the clusterIPs field, if specified. Both clusterIPs and ipFamilies are governed by the ipFamilyPolicy field.",
"items": { "items": {
"type": "string" "type": "string"
}, },
@ -9357,7 +9357,7 @@
"x-kubernetes-list-type": "atomic" "x-kubernetes-list-type": "atomic"
}, },
"ipFamilyPolicy": { "ipFamilyPolicy": {
"description": "IPFamilyPolicy represents the dual-stack-ness requested or required by this Service, and is gated by the \"IPv6DualStack\" feature gate. If there is no value provided, then this field will be set to SingleStack. Services can be \"SingleStack\" (a single IP family), \"PreferDualStack\" (two IP families on dual-stack configured clusters or a single IP family on single-stack clusters), or \"RequireDualStack\" (two IP families on dual-stack configured clusters, otherwise fail). The ipFamilies and clusterIPs fields depend on the value of this field. This field will be wiped when updating a service to type ExternalName.", "description": "IPFamilyPolicy represents the dual-stack-ness requested or required by this Service. If there is no value provided, then this field will be set to SingleStack. Services can be \"SingleStack\" (a single IP family), \"PreferDualStack\" (two IP families on dual-stack configured clusters or a single IP family on single-stack clusters), or \"RequireDualStack\" (two IP families on dual-stack configured clusters, otherwise fail). The ipFamilies and clusterIPs fields depend on the value of this field. This field will be wiped when updating a service to type ExternalName.",
"type": "string" "type": "string"
}, },
"loadBalancerClass": { "loadBalancerClass": {

View File

@ -25,13 +25,11 @@ import (
"net" "net"
"strings" "strings"
utilfeature "k8s.io/apiserver/pkg/util/feature"
cloudprovider "k8s.io/cloud-provider" cloudprovider "k8s.io/cloud-provider"
"k8s.io/cloud-provider/app" "k8s.io/cloud-provider/app"
cloudcontrollerconfig "k8s.io/cloud-provider/app/config" cloudcontrollerconfig "k8s.io/cloud-provider/app/config"
genericcontrollermanager "k8s.io/controller-manager/app" genericcontrollermanager "k8s.io/controller-manager/app"
"k8s.io/controller-manager/controller" "k8s.io/controller-manager/controller"
"k8s.io/controller-manager/pkg/features"
"k8s.io/klog/v2" "k8s.io/klog/v2"
nodeipamcontrolleroptions "k8s.io/kubernetes/cmd/kube-controller-manager/app/options" nodeipamcontrolleroptions "k8s.io/kubernetes/cmd/kube-controller-manager/app/options"
nodeipamcontroller "k8s.io/kubernetes/pkg/controller/nodeipam" nodeipamcontroller "k8s.io/kubernetes/pkg/controller/nodeipam"
@ -79,11 +77,6 @@ func startNodeIpamController(initContext app.ControllerInitContext, ccmConfig *c
return nil, false, err return nil, false, err
} }
// failure: more than one cidr and dual stack is not enabled
if len(clusterCIDRs) > 1 && !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
return nil, false, fmt.Errorf("len of ClusterCIDRs==%v and dualstack feature is not enabled", len(clusterCIDRs))
}
// failure: more than one cidr but they are not configured as dual stack // failure: more than one cidr but they are not configured as dual stack
if len(clusterCIDRs) > 1 && !dualStack { if len(clusterCIDRs) > 1 && !dualStack {
return nil, false, fmt.Errorf("len of ClusterCIDRs==%v and they are not configured as dual stack (at least one from each IPFamily", len(clusterCIDRs)) return nil, false, fmt.Errorf("len of ClusterCIDRs==%v and they are not configured as dual stack (at least one from each IPFamily", len(clusterCIDRs))
@ -111,11 +104,6 @@ func startNodeIpamController(initContext app.ControllerInitContext, ccmConfig *c
// the following checks are triggered if both serviceCIDR and secondaryServiceCIDR are provided // the following checks are triggered if both serviceCIDR and secondaryServiceCIDR are provided
if serviceCIDR != nil && secondaryServiceCIDR != nil { if serviceCIDR != nil && secondaryServiceCIDR != nil {
// should have dual stack flag enabled
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
return nil, false, fmt.Errorf("secondary service cidr is provided and IPv6DualStack feature is not enabled")
}
// should be dual stack (from different IPFamilies) // should be dual stack (from different IPFamilies)
dualstackServiceCIDR, err := netutils.IsDualStackCIDRs([]*net.IPNet{serviceCIDR, secondaryServiceCIDR}) dualstackServiceCIDR, err := netutils.IsDualStackCIDRs([]*net.IPNet{serviceCIDR, secondaryServiceCIDR})
if err != nil { if err != nil {
@ -126,24 +114,11 @@ func startNodeIpamController(initContext app.ControllerInitContext, ccmConfig *c
} }
} }
var nodeCIDRMaskSizeIPv4, nodeCIDRMaskSizeIPv6 int nodeCIDRMaskSizes, err := setNodeCIDRMaskSizes(nodeIPAMConfig, clusterCIDRs)
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
// only --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 supported with dual stack clusters.
// --node-cidr-mask-size flag is incompatible with dual stack clusters.
nodeCIDRMaskSizeIPv4, nodeCIDRMaskSizeIPv6, err = setNodeCIDRMaskSizesDualStack(nodeIPAMConfig)
} else {
// only --node-cidr-mask-size supported with single stack clusters.
// --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 flags are incompatible with dual stack clusters.
nodeCIDRMaskSizeIPv4, nodeCIDRMaskSizeIPv6, err = setNodeCIDRMaskSizes(nodeIPAMConfig)
}
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
// get list of node cidr mask sizes
nodeCIDRMaskSizes := getNodeCIDRMaskSizes(clusterCIDRs, nodeCIDRMaskSizeIPv4, nodeCIDRMaskSizeIPv6)
nodeIpamController, err := nodeipamcontroller.NewNodeIpamController( nodeIpamController, err := nodeipamcontroller.NewNodeIpamController(
ctx.InformerFactory.Core().V1().Nodes(), ctx.InformerFactory.Core().V1().Nodes(),
cloud, cloud,
@ -180,42 +155,12 @@ func processCIDRs(cidrsList string) ([]*net.IPNet, bool, error) {
return cidrs, dualstack, nil return cidrs, dualstack, nil
} }
// setNodeCIDRMaskSizes returns the IPv4 and IPv6 node cidr mask sizes. // setNodeCIDRMaskSizes returns the IPv4 and IPv6 node cidr mask sizes to the value provided
// If --node-cidr-mask-size not set, then it will return default IPv4 and IPv6 cidr mask sizes.
func setNodeCIDRMaskSizes(cfg nodeipamconfig.NodeIPAMControllerConfiguration) (int, int, error) {
ipv4Mask, ipv6Mask := defaultNodeMaskCIDRIPv4, defaultNodeMaskCIDRIPv6
// NodeCIDRMaskSizeIPv4 and NodeCIDRMaskSizeIPv6 can be used only for dual-stack clusters
if cfg.NodeCIDRMaskSizeIPv4 != 0 || cfg.NodeCIDRMaskSizeIPv6 != 0 {
return ipv4Mask, ipv6Mask, errors.New("usage of --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 are not allowed with non dual-stack clusters")
}
if cfg.NodeCIDRMaskSize != 0 {
ipv4Mask = int(cfg.NodeCIDRMaskSize)
ipv6Mask = int(cfg.NodeCIDRMaskSize)
}
return ipv4Mask, ipv6Mask, nil
}
// setNodeCIDRMaskSizesDualStack returns the IPv4 and IPv6 node cidr mask sizes to the value provided
// for --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 respectively. If value not provided, // for --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 respectively. If value not provided,
// then it will return default IPv4 and IPv6 cidr mask sizes. // then it will return default IPv4 and IPv6 cidr mask sizes.
func setNodeCIDRMaskSizesDualStack(cfg nodeipamconfig.NodeIPAMControllerConfiguration) (int, int, error) { func setNodeCIDRMaskSizes(cfg nodeipamconfig.NodeIPAMControllerConfiguration, clusterCIDRs []*net.IPNet) ([]int, error) {
ipv4Mask, ipv6Mask := defaultNodeMaskCIDRIPv4, defaultNodeMaskCIDRIPv6
// NodeCIDRMaskSize can be used only for single stack clusters
if cfg.NodeCIDRMaskSize != 0 {
return ipv4Mask, ipv6Mask, errors.New("usage of --node-cidr-mask-size is not allowed with dual-stack clusters")
}
if cfg.NodeCIDRMaskSizeIPv4 != 0 {
ipv4Mask = int(cfg.NodeCIDRMaskSizeIPv4)
}
if cfg.NodeCIDRMaskSizeIPv6 != 0 {
ipv6Mask = int(cfg.NodeCIDRMaskSizeIPv6)
}
return ipv4Mask, ipv6Mask, nil
}
// getNodeCIDRMaskSizes is a helper function that helps the generate the node cidr mask sortedSizes := func(maskSizeIPv4, maskSizeIPv6 int) []int {
// sizes slice based on the cluster cidr slice
func getNodeCIDRMaskSizes(clusterCIDRs []*net.IPNet, maskSizeIPv4, maskSizeIPv6 int) []int {
nodeMaskCIDRs := make([]int, len(clusterCIDRs)) nodeMaskCIDRs := make([]int, len(clusterCIDRs))
for idx, clusterCIDR := range clusterCIDRs { for idx, clusterCIDR := range clusterCIDRs {
@ -227,3 +172,58 @@ func getNodeCIDRMaskSizes(clusterCIDRs []*net.IPNet, maskSizeIPv4, maskSizeIPv6
} }
return nodeMaskCIDRs return nodeMaskCIDRs
} }
// --node-cidr-mask-size flag is incompatible with dual stack clusters.
ipv4Mask, ipv6Mask := defaultNodeMaskCIDRIPv4, defaultNodeMaskCIDRIPv6
isDualstack := len(clusterCIDRs) > 1
// case one: cluster is dualstack (i.e, more than one cidr)
if isDualstack {
// if --node-cidr-mask-size then fail, user must configure the correct dual-stack mask sizes (or use default)
if cfg.NodeCIDRMaskSize != 0 {
return nil, errors.New("usage of --node-cidr-mask-size is not allowed with dual-stack clusters")
}
if cfg.NodeCIDRMaskSizeIPv4 != 0 {
ipv4Mask = int(cfg.NodeCIDRMaskSizeIPv4)
}
if cfg.NodeCIDRMaskSizeIPv6 != 0 {
ipv6Mask = int(cfg.NodeCIDRMaskSizeIPv6)
}
return sortedSizes(ipv4Mask, ipv6Mask), nil
}
maskConfigured := cfg.NodeCIDRMaskSize != 0
maskV4Configured := cfg.NodeCIDRMaskSizeIPv4 != 0
maskV6Configured := cfg.NodeCIDRMaskSizeIPv6 != 0
isSingleStackIPv6 := netutils.IsIPv6CIDR(clusterCIDRs[0])
// original flag is set
if maskConfigured {
// original mask flag is still the main reference.
if maskV4Configured || maskV6Configured {
return nil, errors.New("usage of --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 is not allowed if --node-cidr-mask-size is set. For dual-stack clusters please unset it and use IPFamily specific flags")
}
mask := int(cfg.NodeCIDRMaskSize)
return sortedSizes(mask, mask), nil
}
if maskV4Configured {
if isSingleStackIPv6 {
return nil, errors.New("usage of --node-cidr-mask-size-ipv4 is not allowed for a single-stack IPv6 cluster")
}
ipv4Mask = int(cfg.NodeCIDRMaskSizeIPv4)
}
// !maskV4Configured && !maskConfigured && maskV6Configured
if maskV6Configured {
if !isSingleStackIPv6 {
return nil, errors.New("usage of --node-cidr-mask-size-ipv6 is not allowed for a single-stack IPv4 cluster")
}
ipv6Mask = int(cfg.NodeCIDRMaskSizeIPv6)
}
return sortedSizes(ipv4Mask, ipv6Mask), nil
}

View File

@ -27,7 +27,6 @@ import (
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme" aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme"
"k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/features"
netutils "k8s.io/utils/net" netutils "k8s.io/utils/net"
) )
@ -54,13 +53,7 @@ func validateClusterIPFlags(options *ServerRunOptions) []error {
errs = append(errs, err) errs = append(errs, err)
} }
// Secondary IP validation
// ControllerManager needs DualStack feature flags
secondaryServiceClusterIPRangeUsed := (options.SecondaryServiceClusterIPRange.IP != nil) secondaryServiceClusterIPRangeUsed := (options.SecondaryServiceClusterIPRange.IP != nil)
if secondaryServiceClusterIPRangeUsed && !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
errs = append(errs, fmt.Errorf("secondary service cluster-ip range(--service-cluster-ip-range[1]) can only be used if %v feature is enabled", string(features.IPv6DualStack)))
}
// note: While the cluster might be dualstack (i.e. pods with multiple IPs), the user may choose // note: While the cluster might be dualstack (i.e. pods with multiple IPs), the user may choose
// to only ingress traffic within and into the cluster on one IP family only. this family is decided // to only ingress traffic within and into the cluster on one IP family only. this family is decided
// by the range set on --service-cluster-ip-range. If/when the user decides to use dual stack services // by the range set on --service-cluster-ip-range. If/when the user decides to use dual stack services

View File

@ -20,9 +20,6 @@ import (
"net" "net"
"testing" "testing"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/features"
netutils "k8s.io/utils/net" netutils "k8s.io/utils/net"
) )
@ -57,62 +54,37 @@ func TestClusterServiceIPRange(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
options *ServerRunOptions options *ServerRunOptions
enableDualStack bool
expectErrors bool expectErrors bool
}{ }{
{ {
name: "no service cidr", name: "no service cidr",
expectErrors: true, expectErrors: true,
options: makeOptionsWithCIDRs("", ""), options: makeOptionsWithCIDRs("", ""),
enableDualStack: false,
}, },
{ {
name: "only secondary service cidr, dual stack gate on", name: "only secondary service cidr",
expectErrors: true, expectErrors: true,
options: makeOptionsWithCIDRs("", "10.0.0.0/16"), options: makeOptionsWithCIDRs("", "10.0.0.0/16"),
enableDualStack: true,
},
{
name: "only secondary service cidr, dual stack gate off",
expectErrors: true,
options: makeOptionsWithCIDRs("", "10.0.0.0/16"),
enableDualStack: false,
}, },
{ {
name: "primary and secondary are provided but not dual stack v4-v4", name: "primary and secondary are provided but not dual stack v4-v4",
expectErrors: true, expectErrors: true,
options: makeOptionsWithCIDRs("10.0.0.0/16", "11.0.0.0/16"), options: makeOptionsWithCIDRs("10.0.0.0/16", "11.0.0.0/16"),
enableDualStack: true,
}, },
{ {
name: "primary and secondary are provided but not dual stack v6-v6", name: "primary and secondary are provided but not dual stack v6-v6",
expectErrors: true, expectErrors: true,
options: makeOptionsWithCIDRs("2000::/108", "3000::/108"), options: makeOptionsWithCIDRs("2000::/108", "3000::/108"),
enableDualStack: true,
},
{
name: "valid dual stack with gate disabled",
expectErrors: true,
options: makeOptionsWithCIDRs("10.0.0.0/16", "3000::/108"),
enableDualStack: false,
}, },
{ {
name: "service cidr is too big", name: "service cidr is too big",
expectErrors: true, expectErrors: true,
options: makeOptionsWithCIDRs("10.0.0.0/8", ""), options: makeOptionsWithCIDRs("10.0.0.0/8", ""),
enableDualStack: true,
}, },
{ {
name: "dual-stack secondary cidr too big", name: "dual-stack secondary cidr too big",
expectErrors: true, expectErrors: true,
options: makeOptionsWithCIDRs("10.0.0.0/16", "3000::/64"), options: makeOptionsWithCIDRs("10.0.0.0/16", "3000::/64"),
enableDualStack: true,
},
{
name: "valid v6-v4 dual stack + gate on + endpointSlice gate is on",
expectErrors: false,
options: makeOptionsWithCIDRs("3000::/108", "10.0.0.0/16"),
enableDualStack: true,
}, },
/* success cases */ /* success cases */
@ -120,25 +92,21 @@ func TestClusterServiceIPRange(t *testing.T) {
name: "valid primary", name: "valid primary",
expectErrors: false, expectErrors: false,
options: makeOptionsWithCIDRs("10.0.0.0/16", ""), options: makeOptionsWithCIDRs("10.0.0.0/16", ""),
enableDualStack: false,
}, },
{ {
name: "valid v4-v6 dual stack + gate on", name: "valid v4-v6 dual stack",
expectErrors: false, expectErrors: false,
options: makeOptionsWithCIDRs("10.0.0.0/16", "3000::/108"), options: makeOptionsWithCIDRs("10.0.0.0/16", "3000::/108"),
enableDualStack: true,
}, },
{ {
name: "valid v6-v4 dual stack + gate on", name: "valid v6-v4 dual stack",
expectErrors: false, expectErrors: false,
options: makeOptionsWithCIDRs("3000::/108", "10.0.0.0/16"), options: makeOptionsWithCIDRs("3000::/108", "10.0.0.0/16"),
enableDualStack: true,
}, },
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.enableDualStack)()
errs := validateClusterIPFlags(tc.options) errs := validateClusterIPFlags(tc.options)
if len(errs) > 0 && !tc.expectErrors { if len(errs) > 0 && !tc.expectErrors {
t.Errorf("expected no errors, errors found %+v", errs) t.Errorf("expected no errors, errors found %+v", errs)

View File

@ -110,11 +110,6 @@ func startNodeIpamController(ctx ControllerContext) (controller.Interface, bool,
return nil, false, err return nil, false, err
} }
// failure: more than one cidr and dual stack is not enabled
if len(clusterCIDRs) > 1 && !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
return nil, false, fmt.Errorf("len of ClusterCIDRs==%v and dualstack or EndpointSlice feature is not enabled", len(clusterCIDRs))
}
// failure: more than one cidr but they are not configured as dual stack // failure: more than one cidr but they are not configured as dual stack
if len(clusterCIDRs) > 1 && !dualStack { if len(clusterCIDRs) > 1 && !dualStack {
return nil, false, fmt.Errorf("len of ClusterCIDRs==%v and they are not configured as dual stack (at least one from each IPFamily)", len(clusterCIDRs)) return nil, false, fmt.Errorf("len of ClusterCIDRs==%v and they are not configured as dual stack (at least one from each IPFamily)", len(clusterCIDRs))
@ -142,11 +137,6 @@ func startNodeIpamController(ctx ControllerContext) (controller.Interface, bool,
// the following checks are triggered if both serviceCIDR and secondaryServiceCIDR are provided // the following checks are triggered if both serviceCIDR and secondaryServiceCIDR are provided
if serviceCIDR != nil && secondaryServiceCIDR != nil { if serviceCIDR != nil && secondaryServiceCIDR != nil {
// should have dual stack flag enabled
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
return nil, false, fmt.Errorf("secondary service cidr is provided and IPv6DualStack feature is not enabled")
}
// should be dual stack (from different IPFamilies) // should be dual stack (from different IPFamilies)
dualstackServiceCIDR, err := netutils.IsDualStackCIDRs([]*net.IPNet{serviceCIDR, secondaryServiceCIDR}) dualstackServiceCIDR, err := netutils.IsDualStackCIDRs([]*net.IPNet{serviceCIDR, secondaryServiceCIDR})
if err != nil { if err != nil {
@ -157,24 +147,13 @@ func startNodeIpamController(ctx ControllerContext) (controller.Interface, bool,
} }
} }
var nodeCIDRMaskSizeIPv4, nodeCIDRMaskSizeIPv6 int
if dualStack {
// only --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 supported with dual stack clusters. // only --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 supported with dual stack clusters.
// --node-cidr-mask-size flag is incompatible with dual stack clusters. // --node-cidr-mask-size flag is incompatible with dual stack clusters.
nodeCIDRMaskSizeIPv4, nodeCIDRMaskSizeIPv6, err = setNodeCIDRMaskSizesDualStack(ctx.ComponentConfig.NodeIPAMController) nodeCIDRMaskSizes, err := setNodeCIDRMaskSizes(ctx.ComponentConfig.NodeIPAMController, clusterCIDRs)
} else {
// only --node-cidr-mask-size supported with single stack clusters.
// --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 flags are incompatible with single stack clusters.
nodeCIDRMaskSizeIPv4, nodeCIDRMaskSizeIPv6, err = setNodeCIDRMaskSizes(ctx.ComponentConfig.NodeIPAMController)
}
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
// get list of node cidr mask sizes
nodeCIDRMaskSizes := getNodeCIDRMaskSizes(clusterCIDRs, nodeCIDRMaskSizeIPv4, nodeCIDRMaskSizeIPv6)
nodeIpamController, err := nodeipamcontroller.NewNodeIpamController( nodeIpamController, err := nodeipamcontroller.NewNodeIpamController(
ctx.InformerFactory.Core().V1().Nodes(), ctx.InformerFactory.Core().V1().Nodes(),
ctx.Cloud, ctx.Cloud,
@ -257,11 +236,6 @@ func startRouteController(ctx ControllerContext) (controller.Interface, bool, er
return nil, false, err return nil, false, err
} }
// failure: more than one cidr and dual stack is not enabled
if len(clusterCIDRs) > 1 && !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
return nil, false, fmt.Errorf("len of ClusterCIDRs==%v and dualstack feature is not enabled", len(clusterCIDRs))
}
// failure: more than one cidr but they are not configured as dual stack // failure: more than one cidr but they are not configured as dual stack
if len(clusterCIDRs) > 1 && !dualStack { if len(clusterCIDRs) > 1 && !dualStack {
return nil, false, fmt.Errorf("len of ClusterCIDRs==%v and they are not configured as dual stack (at least one from each IPFamily", len(clusterCIDRs)) return nil, false, fmt.Errorf("len of ClusterCIDRs==%v and they are not configured as dual stack (at least one from each IPFamily", len(clusterCIDRs))
@ -627,42 +601,12 @@ func processCIDRs(cidrsList string) ([]*net.IPNet, bool, error) {
return cidrs, dualstack, nil return cidrs, dualstack, nil
} }
// setNodeCIDRMaskSizes returns the IPv4 and IPv6 node cidr mask sizes. // setNodeCIDRMaskSizes returns the IPv4 and IPv6 node cidr mask sizes to the value provided
// If --node-cidr-mask-size not set, then it will return default IPv4 and IPv6 cidr mask sizes.
func setNodeCIDRMaskSizes(cfg nodeipamconfig.NodeIPAMControllerConfiguration) (int, int, error) {
ipv4Mask, ipv6Mask := defaultNodeMaskCIDRIPv4, defaultNodeMaskCIDRIPv6
// NodeCIDRMaskSizeIPv4 and NodeCIDRMaskSizeIPv6 can be used only for dual-stack clusters
if cfg.NodeCIDRMaskSizeIPv4 != 0 || cfg.NodeCIDRMaskSizeIPv6 != 0 {
return ipv4Mask, ipv6Mask, errors.New("usage of --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 are not allowed with non dual-stack clusters")
}
if cfg.NodeCIDRMaskSize != 0 {
ipv4Mask = int(cfg.NodeCIDRMaskSize)
ipv6Mask = int(cfg.NodeCIDRMaskSize)
}
return ipv4Mask, ipv6Mask, nil
}
// setNodeCIDRMaskSizesDualStack returns the IPv4 and IPv6 node cidr mask sizes to the value provided
// for --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 respectively. If value not provided, // for --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 respectively. If value not provided,
// then it will return default IPv4 and IPv6 cidr mask sizes. // then it will return default IPv4 and IPv6 cidr mask sizes.
func setNodeCIDRMaskSizesDualStack(cfg nodeipamconfig.NodeIPAMControllerConfiguration) (int, int, error) { func setNodeCIDRMaskSizes(cfg nodeipamconfig.NodeIPAMControllerConfiguration, clusterCIDRs []*net.IPNet) ([]int, error) {
ipv4Mask, ipv6Mask := defaultNodeMaskCIDRIPv4, defaultNodeMaskCIDRIPv6
// NodeCIDRMaskSize can be used only for single stack clusters
if cfg.NodeCIDRMaskSize != 0 {
return ipv4Mask, ipv6Mask, errors.New("usage of --node-cidr-mask-size is not allowed with dual-stack clusters")
}
if cfg.NodeCIDRMaskSizeIPv4 != 0 {
ipv4Mask = int(cfg.NodeCIDRMaskSizeIPv4)
}
if cfg.NodeCIDRMaskSizeIPv6 != 0 {
ipv6Mask = int(cfg.NodeCIDRMaskSizeIPv6)
}
return ipv4Mask, ipv6Mask, nil
}
// getNodeCIDRMaskSizes is a helper function that helps the generate the node cidr mask sortedSizes := func(maskSizeIPv4, maskSizeIPv6 int) []int {
// sizes slice based on the cluster cidr slice
func getNodeCIDRMaskSizes(clusterCIDRs []*net.IPNet, maskSizeIPv4, maskSizeIPv6 int) []int {
nodeMaskCIDRs := make([]int, len(clusterCIDRs)) nodeMaskCIDRs := make([]int, len(clusterCIDRs))
for idx, clusterCIDR := range clusterCIDRs { for idx, clusterCIDR := range clusterCIDRs {
@ -675,6 +619,61 @@ func getNodeCIDRMaskSizes(clusterCIDRs []*net.IPNet, maskSizeIPv4, maskSizeIPv6
return nodeMaskCIDRs return nodeMaskCIDRs
} }
// --node-cidr-mask-size flag is incompatible with dual stack clusters.
ipv4Mask, ipv6Mask := defaultNodeMaskCIDRIPv4, defaultNodeMaskCIDRIPv6
isDualstack := len(clusterCIDRs) > 1
// case one: cluster is dualstack (i.e, more than one cidr)
if isDualstack {
// if --node-cidr-mask-size then fail, user must configure the correct dual-stack mask sizes (or use default)
if cfg.NodeCIDRMaskSize != 0 {
return nil, errors.New("usage of --node-cidr-mask-size is not allowed with dual-stack clusters")
}
if cfg.NodeCIDRMaskSizeIPv4 != 0 {
ipv4Mask = int(cfg.NodeCIDRMaskSizeIPv4)
}
if cfg.NodeCIDRMaskSizeIPv6 != 0 {
ipv6Mask = int(cfg.NodeCIDRMaskSizeIPv6)
}
return sortedSizes(ipv4Mask, ipv6Mask), nil
}
maskConfigured := cfg.NodeCIDRMaskSize != 0
maskV4Configured := cfg.NodeCIDRMaskSizeIPv4 != 0
maskV6Configured := cfg.NodeCIDRMaskSizeIPv6 != 0
isSingleStackIPv6 := netutils.IsIPv6CIDR(clusterCIDRs[0])
// original flag is set
if maskConfigured {
// original mask flag is still the main reference.
if maskV4Configured || maskV6Configured {
return nil, errors.New("usage of --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 is not allowed if --node-cidr-mask-size is set. For dual-stack clusters please unset it and use IPFamily specific flags")
}
mask := int(cfg.NodeCIDRMaskSize)
return sortedSizes(mask, mask), nil
}
if maskV4Configured {
if isSingleStackIPv6 {
return nil, errors.New("usage of --node-cidr-mask-size-ipv4 is not allowed for a single-stack IPv6 cluster")
}
ipv4Mask = int(cfg.NodeCIDRMaskSizeIPv4)
}
// !maskV4Configured && !maskConfigured && maskV6Configured
if maskV6Configured {
if !isSingleStackIPv6 {
return nil, errors.New("usage of --node-cidr-mask-size-ipv6 is not allowed for a single-stack IPv4 cluster")
}
ipv6Mask = int(cfg.NodeCIDRMaskSizeIPv6)
}
return sortedSizes(ipv4Mask, ipv6Mask), nil
}
func startStorageVersionGCController(ctx ControllerContext) (controller.Interface, bool, error) { func startStorageVersionGCController(ctx ControllerContext) (controller.Interface, bool, error) {
go storageversiongc.NewStorageVersionGC( go storageversiongc.NewStorageVersionGC(
ctx.ClientBuilder.ClientOrDie("storage-version-garbage-collector"), ctx.ClientBuilder.ClientOrDie("storage-version-garbage-collector"),

View File

@ -44,13 +44,11 @@ import (
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
utilnet "k8s.io/apimachinery/pkg/util/net" utilnet "k8s.io/apimachinery/pkg/util/net"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
utilfeature "k8s.io/apiserver/pkg/util/feature"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
toolswatch "k8s.io/client-go/tools/watch" toolswatch "k8s.io/client-go/tools/watch"
"k8s.io/component-base/configz" "k8s.io/component-base/configz"
"k8s.io/component-base/metrics" "k8s.io/component-base/metrics"
utilsysctl "k8s.io/component-helpers/node/utils/sysctl" utilsysctl "k8s.io/component-helpers/node/utils/sysctl"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/proxy" "k8s.io/kubernetes/pkg/proxy"
proxyconfigapi "k8s.io/kubernetes/pkg/proxy/apis/config" proxyconfigapi "k8s.io/kubernetes/pkg/proxy/apis/config"
"k8s.io/kubernetes/pkg/proxy/apis/config/scheme" "k8s.io/kubernetes/pkg/proxy/apis/config/scheme"
@ -183,29 +181,25 @@ func newProxyServer(
iptInterface = utiliptables.New(execer, primaryProtocol) iptInterface = utiliptables.New(execer, primaryProtocol)
var ipt [2]utiliptables.Interface var ipt [2]utiliptables.Interface
dualStack := utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) && proxyMode != proxyModeUserspace dualStack := true // While we assume that node supports, we do further checks below
if dualStack {
if proxyMode != proxyModeUserspace {
// Create iptables handlers for both families, one is already created // Create iptables handlers for both families, one is already created
// Always ordered as IPv4, IPv6 // Always ordered as IPv4, IPv6
if primaryProtocol == utiliptables.ProtocolIPv4 { if primaryProtocol == utiliptables.ProtocolIPv4 {
ipt[0] = iptInterface ipt[0] = iptInterface
ipt[1] = utiliptables.New(execer, utiliptables.ProtocolIPv6) ipt[1] = utiliptables.New(execer, utiliptables.ProtocolIPv6)
// Just because the feature gate is enabled doesn't mean the node
// actually supports dual-stack
if _, err := ipt[1].ChainExists(utiliptables.TableNAT, utiliptables.ChainPostrouting); err != nil {
klog.Warningf("No iptables support for IPv6: %v", err)
dualStack = false
}
} else { } else {
ipt[0] = utiliptables.New(execer, utiliptables.ProtocolIPv4) ipt[0] = utiliptables.New(execer, utiliptables.ProtocolIPv4)
ipt[1] = iptInterface ipt[1] = iptInterface
} }
for _, perFamilyIpt := range ipt {
if !perFamilyIpt.Present() {
klog.V(0).InfoS("kube-proxy running in single-stack mode, this ipFamily is not supported", "ipFamily", perFamilyIpt.Protocol())
dualStack = false
}
} }
if dualStack {
klog.V(0).InfoS("kube-proxy running in dual-stack mode", "ipFamily", iptInterface.Protocol())
} else {
klog.V(0).InfoS("kube-proxy running in single-stack mode", "ipFamily", iptInterface.Protocol())
} }
if proxyMode == proxyModeIPTables { if proxyMode == proxyModeIPTables {
@ -216,8 +210,8 @@ func newProxyServer(
} }
if dualStack { if dualStack {
klog.V(0).InfoS("kube-proxy running in dual-stack mode", "ipFamily", iptInterface.Protocol())
klog.V(0).InfoS("Creating dualStackProxier for iptables") klog.V(0).InfoS("Creating dualStackProxier for iptables")
// Always ordered to match []ipt // Always ordered to match []ipt
var localDetectors [2]proxyutiliptables.LocalTrafficDetector var localDetectors [2]proxyutiliptables.LocalTrafficDetector
localDetectors, err = getDualStackLocalDetectorTuple(detectLocalMode, config, ipt, nodeInfo) localDetectors, err = getDualStackLocalDetectorTuple(detectLocalMode, config, ipt, nodeInfo)
@ -241,7 +235,8 @@ func newProxyServer(
healthzServer, healthzServer,
config.NodePortAddresses, config.NodePortAddresses,
) )
} else { // Create a single-stack proxier. } else {
// Create a single-stack proxier if and only if the node does not support dual-stack (i.e, no iptables support).
var localDetector proxyutiliptables.LocalTrafficDetector var localDetector proxyutiliptables.LocalTrafficDetector
localDetector, err = getLocalDetector(detectLocalMode, config, iptInterface, nodeInfo) localDetector, err = getLocalDetector(detectLocalMode, config, iptInterface, nodeInfo)
if err != nil { if err != nil {

View File

@ -39,7 +39,7 @@ const (
// InitFeatureGates are the default feature gates for the init command // InitFeatureGates are the default feature gates for the init command
var InitFeatureGates = FeatureList{ var InitFeatureGates = FeatureList{
IPv6DualStack: {FeatureSpec: featuregate.FeatureSpec{Default: true, PreRelease: featuregate.Beta}}, IPv6DualStack: {FeatureSpec: featuregate.FeatureSpec{Default: true, LockToDefault: true, PreRelease: featuregate.GA}, HiddenInHelpText: true},
PublicKeysECDSA: {FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Alpha}}, PublicKeysECDSA: {FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Alpha}},
RootlessControlPlane: {FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Alpha}}, RootlessControlPlane: {FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Alpha}},
} }

View File

@ -1130,9 +1130,8 @@ func RunKubelet(kubeServer *options.KubeletServer, kubeDeps *kubelet.Dependencie
} }
} }
} }
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) && len(nodeIPs) > 1 {
return fmt.Errorf("dual-stack --node-ip %q not supported in a single-stack cluster", kubeServer.NodeIP) if len(nodeIPs) > 2 || (len(nodeIPs) == 2 && netutils.IsIPv6(nodeIPs[0]) == netutils.IsIPv6(nodeIPs[1])) {
} else if len(nodeIPs) > 2 || (len(nodeIPs) == 2 && netutils.IsIPv6(nodeIPs[0]) == netutils.IsIPv6(nodeIPs[1])) {
return fmt.Errorf("bad --node-ip %q; must contain either a single IP or a dual-stack pair of IPs", kubeServer.NodeIP) return fmt.Errorf("bad --node-ip %q; must contain either a single IP or a dual-stack pair of IPs", kubeServer.NodeIP)
} else if len(nodeIPs) == 2 && kubeServer.CloudProvider != "" { } else if len(nodeIPs) == 2 && kubeServer.CloudProvider != "" {
return fmt.Errorf("dual-stack --node-ip %q not supported when using a cloud provider", kubeServer.NodeIP) return fmt.Errorf("dual-stack --node-ip %q not supported when using a cloud provider", kubeServer.NodeIP)

View File

@ -490,32 +490,16 @@ func DropDisabledPodFields(pod, oldPod *api.Pod) {
podAnnotations map[string]string podAnnotations map[string]string
oldPodSpec *api.PodSpec oldPodSpec *api.PodSpec
oldPodAnnotations map[string]string oldPodAnnotations map[string]string
podStatus *api.PodStatus
oldPodStatus *api.PodStatus
) )
if pod != nil { if pod != nil {
podSpec = &pod.Spec podSpec = &pod.Spec
podAnnotations = pod.Annotations podAnnotations = pod.Annotations
podStatus = &pod.Status
} }
if oldPod != nil { if oldPod != nil {
oldPodSpec = &oldPod.Spec oldPodSpec = &oldPod.Spec
oldPodAnnotations = oldPod.Annotations oldPodAnnotations = oldPod.Annotations
oldPodStatus = &oldPod.Status
} }
dropDisabledFields(podSpec, podAnnotations, oldPodSpec, oldPodAnnotations) dropDisabledFields(podSpec, podAnnotations, oldPodSpec, oldPodAnnotations)
dropPodStatusDisabledFields(podStatus, oldPodStatus)
}
// dropPodStatusDisabledFields removes disabled fields from the pod status
func dropPodStatusDisabledFields(podStatus *api.PodStatus, oldPodStatus *api.PodStatus) {
// trim PodIPs down to only one entry (non dual stack).
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) &&
!multiplePodIPsInUse(oldPodStatus) {
if len(podStatus.PodIPs) != 0 {
podStatus.PodIPs = podStatus.PodIPs[0:1]
}
}
} }
// dropDisabledFields removes disabled fields from the pod metadata and spec. // dropDisabledFields removes disabled fields from the pod metadata and spec.
@ -824,17 +808,6 @@ func ephemeralInUse(podSpec *api.PodSpec) bool {
return false return false
} }
// podPriorityInUse returns true if status is not nil and number of PodIPs is greater than one
func multiplePodIPsInUse(podStatus *api.PodStatus) bool {
if podStatus == nil {
return false
}
if len(podStatus.PodIPs) > 1 {
return true
}
return false
}
// SeccompAnnotationForField takes a pod seccomp profile field and returns the // SeccompAnnotationForField takes a pod seccomp profile field and returns the
// converted annotation value // converted annotation value
func SeccompAnnotationForField(field *api.SeccompProfile) string { func SeccompAnnotationForField(field *api.SeccompProfile) string {

View File

@ -1053,107 +1053,6 @@ func TestDropProbeGracePeriod(t *testing.T) {
} }
} }
// helper creates a podStatus with list of PodIPs
func makePodStatus(podIPs []api.PodIP) *api.PodStatus {
return &api.PodStatus{
PodIPs: podIPs,
}
}
func TestDropStatusPodIPs(t *testing.T) {
testCases := []struct {
name string
podStatus *api.PodStatus
oldPodStatus *api.PodStatus
comparePodStatus *api.PodStatus
enableDualStack bool
}{
{
name: "nil pod ips",
enableDualStack: false,
podStatus: makePodStatus(nil),
oldPodStatus: nil,
comparePodStatus: makePodStatus(nil),
},
{
name: "empty pod ips",
enableDualStack: false,
podStatus: makePodStatus([]api.PodIP{}),
oldPodStatus: nil,
comparePodStatus: makePodStatus([]api.PodIP{}),
},
{
name: "single family ipv6",
enableDualStack: false,
podStatus: makePodStatus([]api.PodIP{{IP: "::1"}}),
comparePodStatus: makePodStatus([]api.PodIP{{IP: "::1"}}),
},
{
name: "single family ipv4",
enableDualStack: false,
podStatus: makePodStatus([]api.PodIP{{IP: "1.1.1.1"}}),
comparePodStatus: makePodStatus([]api.PodIP{{IP: "1.1.1.1"}}),
},
{
name: "dualstack 4-6",
enableDualStack: true,
podStatus: makePodStatus([]api.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}}),
comparePodStatus: makePodStatus([]api.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}}),
},
{
name: "dualstack 6-4",
enableDualStack: true,
podStatus: makePodStatus([]api.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
comparePodStatus: makePodStatus([]api.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
},
{
name: "not dualstack 6-4=>4only",
enableDualStack: false,
podStatus: makePodStatus([]api.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
oldPodStatus: nil,
comparePodStatus: makePodStatus([]api.PodIP{{IP: "::1"}}),
},
{
name: "not dualstack 6-4=>as is (used in old)",
enableDualStack: false,
podStatus: makePodStatus([]api.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
oldPodStatus: makePodStatus([]api.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
comparePodStatus: makePodStatus([]api.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
},
{
name: "not dualstack 6-4=>6only",
enableDualStack: false,
podStatus: makePodStatus([]api.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
oldPodStatus: nil,
comparePodStatus: makePodStatus([]api.PodIP{{IP: "::1"}}),
},
{
name: "not dualstack 6-4=>as is (used in old)",
enableDualStack: false,
podStatus: makePodStatus([]api.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
oldPodStatus: makePodStatus([]api.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
comparePodStatus: makePodStatus([]api.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
},
}
for _, tc := range testCases {
func() {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.enableDualStack)()
dropPodStatusDisabledFields(tc.podStatus, tc.oldPodStatus)
old := tc.oldPodStatus.DeepCopy()
// old pod status should never be changed
if !reflect.DeepEqual(tc.oldPodStatus, old) {
t.Errorf("%v: old pod status changed: %v", tc.name, cmp.Diff(tc.oldPodStatus, old))
}
if !reflect.DeepEqual(tc.podStatus, tc.comparePodStatus) {
t.Errorf("%v: unexpected pod status: %v", tc.name, cmp.Diff(tc.podStatus, tc.comparePodStatus))
}
}()
}
}
func TestDropEphemeralContainers(t *testing.T) { func TestDropEphemeralContainers(t *testing.T) {
podWithEphemeralContainers := func() *api.Pod { podWithEphemeralContainers := func() *api.Pod {
return &api.Pod{ return &api.Pod{

View File

@ -2286,8 +2286,6 @@ var validEnvDownwardAPIFieldPathExpressions = sets.NewString(
"spec.serviceAccountName", "spec.serviceAccountName",
"status.hostIP", "status.hostIP",
"status.podIP", "status.podIP",
// status.podIPs is populated even if IPv6DualStack feature gate
// is not enabled. This will work for single stack and dual stack.
"status.podIPs") "status.podIPs")
var validContainerResourceFieldPathExpressions = sets.NewString("limits.cpu", "limits.memory", "limits.ephemeral-storage", "requests.cpu", "requests.memory", "requests.ephemeral-storage") var validContainerResourceFieldPathExpressions = sets.NewString("limits.cpu", "limits.memory", "limits.ephemeral-storage", "requests.cpu", "requests.memory", "requests.ephemeral-storage")

View File

@ -30,7 +30,6 @@ import (
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
coreinformers "k8s.io/client-go/informers/core/v1" coreinformers "k8s.io/client-go/informers/core/v1"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/kubernetes/scheme"
@ -48,7 +47,6 @@ import (
helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
"k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller"
endpointutil "k8s.io/kubernetes/pkg/controller/util/endpoint" endpointutil "k8s.io/kubernetes/pkg/controller/util/endpoint"
"k8s.io/kubernetes/pkg/features"
utillabels "k8s.io/kubernetes/pkg/util/labels" utillabels "k8s.io/kubernetes/pkg/util/labels"
utilnet "k8s.io/utils/net" utilnet "k8s.io/utils/net"
) )
@ -229,17 +227,14 @@ func podToEndpointAddressForService(svc *v1.Service, pod *v1.Pod) (*v1.EndpointA
var endpointIP string var endpointIP string
ipFamily := v1.IPv4Protocol ipFamily := v1.IPv4Protocol
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
// In a legacy cluster, the pod IP is guaranteed to be usable
endpointIP = pod.Status.PodIP
} else {
//feature flag enabled and pods may have multiple IPs
if len(svc.Spec.IPFamilies) > 0 { if len(svc.Spec.IPFamilies) > 0 {
// controller is connected to an api-server that correctly sets IPFamilies // controller is connected to an api-server that correctly sets IPFamilies
ipFamily = svc.Spec.IPFamilies[0] // this works for headful and headless ipFamily = svc.Spec.IPFamilies[0] // this works for headful and headless
} else { } else {
// controller is connected to an api server that does not correctly // controller is connected to an api server that does not correctly
// set IPFamilies (e.g. old api-server during an upgrade) // set IPFamilies (e.g. old api-server during an upgrade)
// TODO (khenidak): remove by when the possibility of upgrading
// from a cluster that does not support dual stack is nil
if len(svc.Spec.ClusterIP) > 0 && svc.Spec.ClusterIP != v1.ClusterIPNone { if len(svc.Spec.ClusterIP) > 0 && svc.Spec.ClusterIP != v1.ClusterIPNone {
// headful service. detect via service clusterIP // headful service. detect via service clusterIP
if utilnet.IsIPv6String(svc.Spec.ClusterIP) { if utilnet.IsIPv6String(svc.Spec.ClusterIP) {
@ -266,7 +261,6 @@ func podToEndpointAddressForService(svc *v1.Service, pod *v1.Pod) (*v1.EndpointA
break break
} }
} }
}
if endpointIP == "" { if endpointIP == "" {
return nil, fmt.Errorf("failed to find a matching endpoint for service %v", svc.Name) return nil, fmt.Errorf("failed to find a matching endpoint for service %v", svc.Name)

View File

@ -33,7 +33,6 @@ import (
"k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/fake"
@ -41,11 +40,9 @@ import (
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
utiltesting "k8s.io/client-go/util/testing" utiltesting "k8s.io/client-go/util/testing"
featuregatetesting "k8s.io/component-base/featuregate/testing"
endptspkg "k8s.io/kubernetes/pkg/api/v1/endpoints" endptspkg "k8s.io/kubernetes/pkg/api/v1/endpoints"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
controllerpkg "k8s.io/kubernetes/pkg/controller" controllerpkg "k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/features"
utilnet "k8s.io/utils/net" utilnet "k8s.io/utils/net"
utilpointer "k8s.io/utils/pointer" utilpointer "k8s.io/utils/pointer"
) )
@ -1208,208 +1205,153 @@ func TestPodToEndpointAddressForService(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
enableDualStack bool
ipFamilies []v1.IPFamily ipFamilies []v1.IPFamily
service v1.Service service v1.Service
expectedEndpointFamily v1.IPFamily expectedEndpointFamily v1.IPFamily
expectError bool expectError bool
}{ }{
{ {
name: "v4 service, in a single stack cluster", name: "v4 service, in a single stack cluster",
enableDualStack: false,
ipFamilies: ipv4only, ipFamilies: ipv4only,
service: v1.Service{ service: v1.Service{
Spec: v1.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.1", ClusterIP: "10.0.0.1",
}, },
}, },
expectedEndpointFamily: ipv4, expectedEndpointFamily: ipv4,
}, },
{ {
name: "v4 service, in a dual stack cluster", name: "v4 service, in a dual stack cluster",
enableDualStack: true,
ipFamilies: ipv4ipv6, ipFamilies: ipv4ipv6,
service: v1.Service{ service: v1.Service{
Spec: v1.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.1", ClusterIP: "10.0.0.1",
}, },
}, },
expectedEndpointFamily: ipv4, expectedEndpointFamily: ipv4,
}, },
{ {
name: "v4 service, in a dual stack ipv6-primary cluster", name: "v4 service, in a dual stack ipv6-primary cluster",
enableDualStack: true,
ipFamilies: ipv6ipv4, ipFamilies: ipv6ipv4,
service: v1.Service{ service: v1.Service{
Spec: v1.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.1", ClusterIP: "10.0.0.1",
}, },
}, },
expectedEndpointFamily: ipv4, expectedEndpointFamily: ipv4,
}, },
{ {
name: "v4 headless service, in a single stack cluster", name: "v4 headless service, in a single stack cluster",
enableDualStack: false,
ipFamilies: ipv4only, ipFamilies: ipv4only,
service: v1.Service{ service: v1.Service{
Spec: v1.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: v1.ClusterIPNone, ClusterIP: v1.ClusterIPNone,
}, },
}, },
expectedEndpointFamily: ipv4, expectedEndpointFamily: ipv4,
}, },
{ {
name: "v4 headless service, in a dual stack cluster", name: "v4 headless service, in a dual stack cluster",
enableDualStack: false,
ipFamilies: ipv4ipv6, ipFamilies: ipv4ipv6,
service: v1.Service{ service: v1.Service{
Spec: v1.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: v1.ClusterIPNone, ClusterIP: v1.ClusterIPNone,
IPFamilies: []v1.IPFamily{v1.IPv4Protocol}, IPFamilies: []v1.IPFamily{v1.IPv4Protocol},
}, },
}, },
expectedEndpointFamily: ipv4, expectedEndpointFamily: ipv4,
}, },
{ {
name: "v4 legacy headless service, in a dual stack cluster", name: "v4 legacy headless service, in a dual stack cluster",
enableDualStack: false,
ipFamilies: ipv4ipv6, ipFamilies: ipv4ipv6,
service: v1.Service{ service: v1.Service{
Spec: v1.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: v1.ClusterIPNone, ClusterIP: v1.ClusterIPNone,
}, },
}, },
expectedEndpointFamily: ipv4, expectedEndpointFamily: ipv4,
}, },
{ {
name: "v4 legacy headless service, in a dual stack ipv6-primary cluster", name: "v4 legacy headless service, in a dual stack ipv6-primary cluster",
enableDualStack: true,
ipFamilies: ipv6ipv4, ipFamilies: ipv6ipv4,
service: v1.Service{ service: v1.Service{
Spec: v1.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: v1.ClusterIPNone, ClusterIP: v1.ClusterIPNone,
}, },
}, },
expectedEndpointFamily: ipv6, expectedEndpointFamily: ipv6,
}, },
{ {
name: "v6 service, in a dual stack cluster", name: "v6 service, in a dual stack cluster",
enableDualStack: true,
ipFamilies: ipv4ipv6, ipFamilies: ipv4ipv6,
service: v1.Service{ service: v1.Service{
Spec: v1.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "3000::1", ClusterIP: "3000::1",
}, },
}, },
expectedEndpointFamily: ipv6, expectedEndpointFamily: ipv6,
}, },
{ {
name: "v6 headless service, in a single stack cluster", name: "v6 headless service, in a single stack cluster",
enableDualStack: false,
ipFamilies: ipv6only, ipFamilies: ipv6only,
service: v1.Service{ service: v1.Service{
Spec: v1.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: v1.ClusterIPNone, ClusterIP: v1.ClusterIPNone,
}, },
}, },
expectedEndpointFamily: ipv6, expectedEndpointFamily: ipv6,
}, },
{ {
name: "v6 headless service, in a dual stack cluster (connected to a new api-server)", name: "v6 headless service, in a dual stack cluster (connected to a new api-server)",
enableDualStack: true,
ipFamilies: ipv4ipv6, ipFamilies: ipv4ipv6,
service: v1.Service{ service: v1.Service{
Spec: v1.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: v1.ClusterIPNone, ClusterIP: v1.ClusterIPNone,
IPFamilies: []v1.IPFamily{v1.IPv6Protocol}, // <- set by a api-server defaulting logic IPFamilies: []v1.IPFamily{v1.IPv6Protocol}, // <- set by a api-server defaulting logic
}, },
}, },
expectedEndpointFamily: ipv6, expectedEndpointFamily: ipv6,
}, },
{ {
name: "v6 legacy headless service, in a dual stack cluster (connected to a old api-server)", name: "v6 legacy headless service, in a dual stack cluster (connected to a old api-server)",
enableDualStack: false,
ipFamilies: ipv4ipv6, ipFamilies: ipv4ipv6,
service: v1.Service{ service: v1.Service{
Spec: v1.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: v1.ClusterIPNone, // <- families are not set by api-server ClusterIP: v1.ClusterIPNone, // <- families are not set by api-server
}, },
}, },
expectedEndpointFamily: ipv4, expectedEndpointFamily: ipv4,
}, },
// in reality this is a misconfigured cluster // in reality this is a misconfigured cluster
// i.e user is not using dual stack and have PodIP == v4 and ServiceIP==v6 // i.e user is not using dual stack and have PodIP == v4 and ServiceIP==v6
// we are testing that we will keep producing the expected behavior // previously controller could assign wrong ip to endpoint address
// with gate removed. this is no longer the case. this is *not* behavior change
// because previously things would have failed in kube-proxy anyway (due to editing wrong iptables).
{ {
name: "v6 service, in a v4 only cluster. dual stack disabled", name: "v6 service, in a v4 only cluster.",
enableDualStack: false,
ipFamilies: ipv4only, ipFamilies: ipv4only,
service: v1.Service{ service: v1.Service{
Spec: v1.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "3000::1", ClusterIP: "3000::1",
}, },
}, },
expectError: true,
expectedEndpointFamily: ipv4, expectedEndpointFamily: ipv4,
}, },
// but this will actually give an error // but this will actually give an error
{ {
name: "v6 service, in a v4 only cluster - dual stack enabled", name: "v6 service, in a v4 only cluster",
enableDualStack: true,
ipFamilies: ipv4only, ipFamilies: ipv4only,
service: v1.Service{ service: v1.Service{
Spec: v1.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "3000::1", ClusterIP: "3000::1",
}, },
}, },
expectError: true, expectError: true,
}, },
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.enableDualStack)()
podStore := cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc) podStore := cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc)
ns := "test" ns := "test"
addPods(podStore, ns, 1, 1, 0, tc.ipFamilies) addPods(podStore, ns, 1, 1, 0, tc.ipFamilies)

View File

@ -796,7 +796,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
LocalStorageCapacityIsolationFSQuotaMonitoring: {Default: false, PreRelease: featuregate.Alpha}, LocalStorageCapacityIsolationFSQuotaMonitoring: {Default: false, PreRelease: featuregate.Alpha},
NonPreemptingPriority: {Default: true, PreRelease: featuregate.Beta}, NonPreemptingPriority: {Default: true, PreRelease: featuregate.Beta},
PodOverhead: {Default: true, PreRelease: featuregate.Beta}, PodOverhead: {Default: true, PreRelease: featuregate.Beta},
IPv6DualStack: {Default: true, PreRelease: featuregate.Beta}, IPv6DualStack: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25
EndpointSlice: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25 EndpointSlice: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25
EndpointSliceProxying: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25 EndpointSliceProxying: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25
EndpointSliceTerminatingCondition: {Default: true, PreRelease: featuregate.Beta}, EndpointSliceTerminatingCondition: {Default: true, PreRelease: featuregate.Beta},

View File

@ -369,3 +369,7 @@ func (f *fakeIPTables) isBuiltinChain(tableName utiliptables.Table, chainName ut
func (f *fakeIPTables) HasRandomFully() bool { func (f *fakeIPTables) HasRandomFully() bool {
return false return false
} }
func (f *fakeIPTables) Present() bool {
return true
}

View File

@ -47,8 +47,6 @@ import (
utilexec "k8s.io/utils/exec" utilexec "k8s.io/utils/exec"
utilebtables "k8s.io/utils/net/ebtables" utilebtables "k8s.io/utils/net/ebtables"
utilfeature "k8s.io/apiserver/pkg/util/feature"
kubefeatures "k8s.io/kubernetes/pkg/features"
netutils "k8s.io/utils/net" netutils "k8s.io/utils/net"
) )
@ -253,12 +251,6 @@ func (plugin *kubenetNetworkPlugin) Event(name string, details map[string]interf
klog.V(4).InfoS("Kubenet: PodCIDR is set to new value", "podCIDR", podCIDR) klog.V(4).InfoS("Kubenet: PodCIDR is set to new value", "podCIDR", podCIDR)
podCIDRs := strings.Split(podCIDR, ",") podCIDRs := strings.Split(podCIDR, ",")
// reset to one cidr if dual stack is not enabled
if !utilfeature.DefaultFeatureGate.Enabled(kubefeatures.IPv6DualStack) && len(podCIDRs) > 1 {
klog.V(2).InfoS("This node has multiple pod cidrs assigned and dual stack is not enabled. ignoring all except first cidr")
podCIDRs = podCIDRs[0:1]
}
for idx, currentPodCIDR := range podCIDRs { for idx, currentPodCIDR := range podCIDRs {
_, cidr, err := netutils.ParseCIDRSloppy(currentPodCIDR) _, cidr, err := netutils.ParseCIDRSloppy(currentPodCIDR)
if nil != err { if nil != err {

View File

@ -39,9 +39,6 @@ import (
"k8s.io/kubernetes/pkg/kubelet/dockershim/network/metrics" "k8s.io/kubernetes/pkg/kubelet/dockershim/network/metrics"
utilexec "k8s.io/utils/exec" utilexec "k8s.io/utils/exec"
netutils "k8s.io/utils/net" netutils "k8s.io/utils/net"
utilfeature "k8s.io/apiserver/pkg/util/feature"
kubefeatures "k8s.io/kubernetes/pkg/features"
) )
const ( const (
@ -263,19 +260,6 @@ func getOnePodIP(execer utilexec.Interface, nsenterPath, netnsPath, interfaceNam
// TODO (khenidak). The "primary ip" in dual stack world does not really exist. For now // TODO (khenidak). The "primary ip" in dual stack world does not really exist. For now
// we are defaulting to v4 as primary // we are defaulting to v4 as primary
func GetPodIPs(execer utilexec.Interface, nsenterPath, netnsPath, interfaceName string) ([]net.IP, error) { func GetPodIPs(execer utilexec.Interface, nsenterPath, netnsPath, interfaceName string) ([]net.IP, error) {
if !utilfeature.DefaultFeatureGate.Enabled(kubefeatures.IPv6DualStack) {
ip, err := getOnePodIP(execer, nsenterPath, netnsPath, interfaceName, "-4")
if err != nil {
// Fall back to IPv6 address if no IPv4 address is present
ip, err = getOnePodIP(execer, nsenterPath, netnsPath, interfaceName, "-6")
}
if err != nil {
return nil, err
}
return []net.IP{ip}, nil
}
var ( var (
list []net.IP list []net.IP
errs []error errs []error

View File

@ -24,9 +24,7 @@ import (
"time" "time"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/klog/v2" "k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/features"
utiliptables "k8s.io/kubernetes/pkg/util/iptables" utiliptables "k8s.io/kubernetes/pkg/util/iptables"
utilexec "k8s.io/utils/exec" utilexec "k8s.io/utils/exec"
utilnet "k8s.io/utils/net" utilnet "k8s.io/utils/net"
@ -34,21 +32,23 @@ import (
func (kl *Kubelet) initNetworkUtil() { func (kl *Kubelet) initNetworkUtil() {
exec := utilexec.New() exec := utilexec.New()
// TODO: @khenidak review when there is no IPv6 iptables exec what should happen here (note: no error returned from this func)
// At this point in startup we don't know the actual node IPs, so we configure dual stack iptables
// rules if the node _might_ be dual-stack, and single-stack based on requested nodeIPs[0] otherwise.
maybeDualStack := utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack)
ipv6Primary := kl.nodeIPs != nil && utilnet.IsIPv6(kl.nodeIPs[0]) ipv6Primary := kl.nodeIPs != nil && utilnet.IsIPv6(kl.nodeIPs[0])
var iptClients []utiliptables.Interface var iptClients []utiliptables.Interface
var protocols []utiliptables.Protocol var protocols []utiliptables.Protocol
if maybeDualStack || !ipv6Primary {
// assume 4,6
protocols = append(protocols, utiliptables.ProtocolIPv4) protocols = append(protocols, utiliptables.ProtocolIPv4)
iptClients = append(iptClients, utiliptables.New(exec, utiliptables.ProtocolIPv4)) iptClients = append(iptClients, utiliptables.New(exec, utiliptables.ProtocolIPv4))
}
if maybeDualStack || ipv6Primary {
protocols = append(protocols, utiliptables.ProtocolIPv6) protocols = append(protocols, utiliptables.ProtocolIPv6)
iptClients = append(iptClients, utiliptables.New(exec, utiliptables.ProtocolIPv6)) iptClients = append(iptClients, utiliptables.New(exec, utiliptables.ProtocolIPv6))
// and if they are not
if ipv6Primary {
protocols[0], protocols[1] = protocols[1], protocols[0]
iptClients[0], iptClients[1] = iptClients[1], iptClients[0]
} }
for i := range iptClients { for i := range iptClients {

View File

@ -1508,7 +1508,7 @@ func (kl *Kubelet) generateAPIPodStatus(pod *v1.Pod, podStatus *kubecontainer.Po
if kubecontainer.IsHostNetworkPod(pod) && s.PodIP == "" { if kubecontainer.IsHostNetworkPod(pod) && s.PodIP == "" {
s.PodIP = hostIPs[0].String() s.PodIP = hostIPs[0].String()
s.PodIPs = []v1.PodIP{{IP: s.PodIP}} s.PodIPs = []v1.PodIP{{IP: s.PodIP}}
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) && len(hostIPs) == 2 { if len(hostIPs) == 2 {
s.PodIPs = append(s.PodIPs, v1.PodIP{IP: hostIPs[1].String()}) s.PodIPs = append(s.PodIPs, v1.PodIP{IP: hostIPs[1].String()})
} }
} }

View File

@ -38,10 +38,8 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/diff"
utilfeature "k8s.io/apiserver/pkg/util/feature"
core "k8s.io/client-go/testing" core "k8s.io/client-go/testing"
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
featuregatetesting "k8s.io/component-base/featuregate/testing"
netutils "k8s.io/utils/net" netutils "k8s.io/utils/net"
// TODO: remove this import if // TODO: remove this import if
@ -49,7 +47,6 @@ import (
// to "v1"? // to "v1"?
_ "k8s.io/kubernetes/pkg/apis/core/install" _ "k8s.io/kubernetes/pkg/apis/core/install"
"k8s.io/kubernetes/pkg/features"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" containertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
"k8s.io/kubernetes/pkg/kubelet/cri/streaming/portforward" "k8s.io/kubernetes/pkg/kubelet/cri/streaming/portforward"
@ -3113,7 +3110,6 @@ func TestTruncatePodHostname(t *testing.T) {
func TestGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) { func TestGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) {
testcases := []struct { testcases := []struct {
name string name string
dualStack bool
nodeAddresses []v1.NodeAddress nodeAddresses []v1.NodeAddress
criPodIPs []string criPodIPs []string
podIPs []v1.PodIP podIPs []v1.PodIP
@ -3137,22 +3133,11 @@ func TestGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) {
{IP: "10.0.0.1"}, {IP: "10.0.0.1"},
}, },
}, },
{
name: "Dual-stack addresses are ignored in single-stack cluster",
nodeAddresses: []v1.NodeAddress{
{Type: v1.NodeInternalIP, Address: "10.0.0.1"},
{Type: v1.NodeInternalIP, Address: "fd01::1234"},
},
podIPs: []v1.PodIP{
{IP: "10.0.0.1"},
},
},
{ {
name: "Single-stack addresses in dual-stack cluster", name: "Single-stack addresses in dual-stack cluster",
nodeAddresses: []v1.NodeAddress{ nodeAddresses: []v1.NodeAddress{
{Type: v1.NodeInternalIP, Address: "10.0.0.1"}, {Type: v1.NodeInternalIP, Address: "10.0.0.1"},
}, },
dualStack: true,
podIPs: []v1.PodIP{ podIPs: []v1.PodIP{
{IP: "10.0.0.1"}, {IP: "10.0.0.1"},
}, },
@ -3164,7 +3149,6 @@ func TestGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) {
{Type: v1.NodeInternalIP, Address: "10.0.0.2"}, {Type: v1.NodeInternalIP, Address: "10.0.0.2"},
{Type: v1.NodeExternalIP, Address: "192.168.0.1"}, {Type: v1.NodeExternalIP, Address: "192.168.0.1"},
}, },
dualStack: true,
podIPs: []v1.PodIP{ podIPs: []v1.PodIP{
{IP: "10.0.0.1"}, {IP: "10.0.0.1"},
}, },
@ -3175,7 +3159,6 @@ func TestGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) {
{Type: v1.NodeInternalIP, Address: "10.0.0.1"}, {Type: v1.NodeInternalIP, Address: "10.0.0.1"},
{Type: v1.NodeInternalIP, Address: "fd01::1234"}, {Type: v1.NodeInternalIP, Address: "fd01::1234"},
}, },
dualStack: true,
podIPs: []v1.PodIP{ podIPs: []v1.PodIP{
{IP: "10.0.0.1"}, {IP: "10.0.0.1"},
{IP: "fd01::1234"}, {IP: "fd01::1234"},
@ -3187,7 +3170,6 @@ func TestGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) {
{Type: v1.NodeInternalIP, Address: "10.0.0.1"}, {Type: v1.NodeInternalIP, Address: "10.0.0.1"},
{Type: v1.NodeInternalIP, Address: "fd01::1234"}, {Type: v1.NodeInternalIP, Address: "fd01::1234"},
}, },
dualStack: true,
criPodIPs: []string{"192.168.0.1"}, criPodIPs: []string{"192.168.0.1"},
podIPs: []v1.PodIP{ podIPs: []v1.PodIP{
{IP: "192.168.0.1"}, {IP: "192.168.0.1"},
@ -3199,7 +3181,6 @@ func TestGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) {
{Type: v1.NodeInternalIP, Address: "10.0.0.1"}, {Type: v1.NodeInternalIP, Address: "10.0.0.1"},
{Type: v1.NodeInternalIP, Address: "fd01::1234"}, {Type: v1.NodeInternalIP, Address: "fd01::1234"},
}, },
dualStack: true,
criPodIPs: []string{"192.168.0.1", "2001:db8::2"}, criPodIPs: []string{"192.168.0.1", "2001:db8::2"},
podIPs: []v1.PodIP{ podIPs: []v1.PodIP{
{IP: "192.168.0.1"}, {IP: "192.168.0.1"},
@ -3213,7 +3194,6 @@ func TestGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) {
{Type: v1.NodeInternalIP, Address: "10.0.0.1"}, {Type: v1.NodeInternalIP, Address: "10.0.0.1"},
{Type: v1.NodeInternalIP, Address: "fd01::1234"}, {Type: v1.NodeInternalIP, Address: "fd01::1234"},
}, },
dualStack: true,
criPodIPs: []string{"2001:db8::2", "192.168.0.1"}, criPodIPs: []string{"2001:db8::2", "192.168.0.1"},
podIPs: []v1.PodIP{ podIPs: []v1.PodIP{
{IP: "192.168.0.1"}, {IP: "192.168.0.1"},
@ -3228,8 +3208,6 @@ func TestGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) {
defer testKubelet.Cleanup() defer testKubelet.Cleanup()
kl := testKubelet.kubelet kl := testKubelet.kubelet
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.dualStack)()
kl.nodeLister = testNodeLister{nodes: []*v1.Node{ kl.nodeLister = testNodeLister{nodes: []*v1.Node{
{ {
ObjectMeta: metav1.ObjectMeta{Name: string(kl.nodeName)}, ObjectMeta: metav1.ObjectMeta{Name: string(kl.nodeName)},

View File

@ -30,7 +30,6 @@ import (
componentbaseconfig "k8s.io/component-base/config" componentbaseconfig "k8s.io/component-base/config"
"k8s.io/component-base/metrics" "k8s.io/component-base/metrics"
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
kubefeatures "k8s.io/kubernetes/pkg/features"
kubeproxyconfig "k8s.io/kubernetes/pkg/proxy/apis/config" kubeproxyconfig "k8s.io/kubernetes/pkg/proxy/apis/config"
netutils "k8s.io/utils/net" netutils "k8s.io/utils/net"
) )
@ -75,22 +74,19 @@ func Validate(config *kubeproxyconfig.KubeProxyConfiguration) field.ErrorList {
} }
allErrs = append(allErrs, validateHostPort(config.MetricsBindAddress, newPath.Child("MetricsBindAddress"))...) allErrs = append(allErrs, validateHostPort(config.MetricsBindAddress, newPath.Child("MetricsBindAddress"))...)
dualStackEnabled := effectiveFeatures.Enabled(kubefeatures.IPv6DualStack)
if config.ClusterCIDR != "" { if config.ClusterCIDR != "" {
cidrs := strings.Split(config.ClusterCIDR, ",") cidrs := strings.Split(config.ClusterCIDR, ",")
switch { switch {
// if DualStack only valid one cidr or two cidrs with one of each IP family case len(cidrs) > 2:
case dualStackEnabled && len(cidrs) > 2:
allErrs = append(allErrs, field.Invalid(newPath.Child("ClusterCIDR"), config.ClusterCIDR, "only one CIDR allowed or a valid DualStack CIDR (e.g. 10.100.0.0/16,fde4:8dba:82e1::/48)")) allErrs = append(allErrs, field.Invalid(newPath.Child("ClusterCIDR"), config.ClusterCIDR, "only one CIDR allowed or a valid DualStack CIDR (e.g. 10.100.0.0/16,fde4:8dba:82e1::/48)"))
// if DualStack and two cidrs validate if there is at least one of each IP family // if DualStack and two cidrs validate if there is at least one of each IP family
case dualStackEnabled && len(cidrs) == 2: case len(cidrs) == 2:
isDual, err := netutils.IsDualStackCIDRStrings(cidrs) isDual, err := netutils.IsDualStackCIDRStrings(cidrs)
if err != nil || !isDual { if err != nil || !isDual {
allErrs = append(allErrs, field.Invalid(newPath.Child("ClusterCIDR"), config.ClusterCIDR, "must be a valid DualStack CIDR (e.g. 10.100.0.0/16,fde4:8dba:82e1::/48)")) allErrs = append(allErrs, field.Invalid(newPath.Child("ClusterCIDR"), config.ClusterCIDR, "must be a valid DualStack CIDR (e.g. 10.100.0.0/16,fde4:8dba:82e1::/48)"))
} }
// if not DualStack only one CIDR allowed // if not DualStack only one CIDR allowed
case !dualStackEnabled && len(cidrs) > 1: case len(cidrs) > 1:
allErrs = append(allErrs, field.Invalid(newPath.Child("ClusterCIDR"), config.ClusterCIDR, "only one CIDR allowed (e.g. 10.100.0.0/16 or fde4:8dba:82e1::/48)")) allErrs = append(allErrs, field.Invalid(newPath.Child("ClusterCIDR"), config.ClusterCIDR, "only one CIDR allowed (e.g. 10.100.0.0/16 or fde4:8dba:82e1::/48)"))
// if we are here means that len(cidrs) == 1, we need to validate it // if we are here means that len(cidrs) == 1, we need to validate it
default: default:

View File

@ -122,7 +122,6 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
BindAddress: "10.10.12.11", BindAddress: "10.10.12.11",
HealthzBindAddress: "0.0.0.0:12345", HealthzBindAddress: "0.0.0.0:12345",
MetricsBindAddress: "127.0.0.1:10249", MetricsBindAddress: "127.0.0.1:10249",
FeatureGates: map[string]bool{"IPv6DualStack": true},
ClusterCIDR: "192.168.59.0/24", ClusterCIDR: "192.168.59.0/24",
UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
@ -142,7 +141,6 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
BindAddress: "10.10.12.11", BindAddress: "10.10.12.11",
HealthzBindAddress: "0.0.0.0:12345", HealthzBindAddress: "0.0.0.0:12345",
MetricsBindAddress: "127.0.0.1:10249", MetricsBindAddress: "127.0.0.1:10249",
FeatureGates: map[string]bool{"IPv6DualStack": true},
ClusterCIDR: "fd00:192:168::/64", ClusterCIDR: "fd00:192:168::/64",
UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
@ -162,7 +160,6 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
BindAddress: "10.10.12.11", BindAddress: "10.10.12.11",
HealthzBindAddress: "0.0.0.0:12345", HealthzBindAddress: "0.0.0.0:12345",
MetricsBindAddress: "127.0.0.1:10249", MetricsBindAddress: "127.0.0.1:10249",
FeatureGates: map[string]bool{"IPv6DualStack": true},
ClusterCIDR: "192.168.59.0/24,fd00:192:168::/64", ClusterCIDR: "192.168.59.0/24,fd00:192:168::/64",
UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
@ -279,36 +276,11 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
}, },
expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ClusterCIDR"), "192.168.59.0", "must be a valid CIDR block (e.g. 10.100.0.0/16 or fde4:8dba:82e1::/48)")}, expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ClusterCIDR"), "192.168.59.0", "must be a valid CIDR block (e.g. 10.100.0.0/16 or fde4:8dba:82e1::/48)")},
}, },
"Two ClusterCIDR addresses provided without DualStack feature-enabled": {
config: kubeproxyconfig.KubeProxyConfiguration{
BindAddress: "10.10.12.11",
HealthzBindAddress: "0.0.0.0:12345",
MetricsBindAddress: "127.0.0.1:10249",
// DualStack ClusterCIDR without feature flag enabled
FeatureGates: map[string]bool{"IPv6DualStack": false},
ClusterCIDR: "192.168.59.0/24,fd00:192:168::/64",
UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
MasqueradeAll: true,
SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
},
Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
MaxPerCore: pointer.Int32Ptr(1),
Min: pointer.Int32Ptr(1),
TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
},
},
expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ClusterCIDR"), "192.168.59.0/24,fd00:192:168::/64", "only one CIDR allowed (e.g. 10.100.0.0/16 or fde4:8dba:82e1::/48)")},
},
"Invalid number of ClusterCIDRs": { "Invalid number of ClusterCIDRs": {
config: kubeproxyconfig.KubeProxyConfiguration{ config: kubeproxyconfig.KubeProxyConfiguration{
BindAddress: "10.10.12.11", BindAddress: "10.10.12.11",
HealthzBindAddress: "0.0.0.0:12345", HealthzBindAddress: "0.0.0.0:12345",
MetricsBindAddress: "127.0.0.1:10249", MetricsBindAddress: "127.0.0.1:10249",
FeatureGates: map[string]bool{"IPv6DualStack": true},
ClusterCIDR: "192.168.59.0/24,fd00:192:168::/64,10.0.0.0/16", ClusterCIDR: "192.168.59.0/24,fd00:192:168::/64,10.0.0.0/16",
UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
@ -396,7 +368,8 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
}, },
} }
for _, testCase := range testCases { for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
errs := Validate(&testCase.config) errs := Validate(&testCase.config)
if len(testCase.expectedErrs) != len(errs) { if len(testCase.expectedErrs) != len(errs) {
t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs) t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs)
@ -406,6 +379,7 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
t.Fatalf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error()) t.Fatalf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error())
} }
} }
})
} }
} }

View File

@ -180,11 +180,6 @@ type StackCompatTester interface {
type DualStackCompatTester struct{} type DualStackCompatTester struct{}
func (t DualStackCompatTester) DualStackCompatible(networkName string) bool { func (t DualStackCompatTester) DualStackCompatible(networkName string) bool {
dualStackFeatureEnabled := utilfeature.DefaultFeatureGate.Enabled(kubefeatures.IPv6DualStack)
if !dualStackFeatureEnabled {
return false
}
// First tag of hcsshim that has a proper check for dual stack support is v0.8.22 due to a bug. // First tag of hcsshim that has a proper check for dual stack support is v0.8.22 due to a bug.
if err := hcn.IPv6DualStackSupported(); err != nil { if err := hcn.IPv6DualStackSupported(); err != nil {
// Hcn *can* fail the query to grab the version of hcn itself (which this call will do internally before parsing // Hcn *can* fail the query to grab the version of hcn itself (which this call will do internally before parsing

View File

@ -104,23 +104,6 @@ func dropDisabledFields(node *api.Node, oldNode *api.Node) {
node.Spec.ConfigSource = nil node.Spec.ConfigSource = nil
} }
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) && !multiNodeCIDRsInUse(oldNode) {
if len(node.Spec.PodCIDRs) > 1 {
node.Spec.PodCIDRs = node.Spec.PodCIDRs[0:1]
}
}
}
// multiNodeCIDRsInUse returns true if Node.Spec.PodCIDRs is greater than one
func multiNodeCIDRsInUse(node *api.Node) bool {
if node == nil {
return false
}
if len(node.Spec.PodCIDRs) > 1 {
return true
}
return false
} }
// nodeConfigSourceInUse returns true if node's Spec ConfigSource is set(used) // nodeConfigSourceInUse returns true if node's Spec ConfigSource is set(used)

View File

@ -92,78 +92,42 @@ func TestDropFields(t *testing.T) {
node *api.Node node *api.Node
oldNode *api.Node oldNode *api.Node
compareNode *api.Node compareNode *api.Node
enableDualStack bool
enableNodeDynamicConfig bool enableNodeDynamicConfig bool
}{ }{
{ {
name: "nil pod cidrs", name: "nil pod cidrs",
enableDualStack: false,
node: makeNode(nil, false, false), node: makeNode(nil, false, false),
oldNode: nil, oldNode: nil,
compareNode: makeNode(nil, false, false), compareNode: makeNode(nil, false, false),
}, },
{ {
name: "empty pod ips", name: "empty pod ips",
enableDualStack: false,
node: makeNode([]string{}, false, false), node: makeNode([]string{}, false, false),
oldNode: nil, oldNode: nil,
compareNode: makeNode([]string{}, false, false), compareNode: makeNode([]string{}, false, false),
}, },
{ {
name: "single family ipv6", name: "single family ipv6",
enableDualStack: false,
node: makeNode([]string{"2000::/10"}, false, false), node: makeNode([]string{"2000::/10"}, false, false),
compareNode: makeNode([]string{"2000::/10"}, false, false), compareNode: makeNode([]string{"2000::/10"}, false, false),
}, },
{ {
name: "single family ipv4", name: "single family ipv4",
enableDualStack: false,
node: makeNode([]string{"10.0.0.0/8"}, false, false), node: makeNode([]string{"10.0.0.0/8"}, false, false),
compareNode: makeNode([]string{"10.0.0.0/8"}, false, false), compareNode: makeNode([]string{"10.0.0.0/8"}, false, false),
}, },
{ {
name: "dualstack 4-6", name: "dualstack 4-6",
enableDualStack: true,
node: makeNode([]string{"10.0.0.0/8", "2000::/10"}, false, false), node: makeNode([]string{"10.0.0.0/8", "2000::/10"}, false, false),
compareNode: makeNode([]string{"10.0.0.0/8", "2000::/10"}, false, false), compareNode: makeNode([]string{"10.0.0.0/8", "2000::/10"}, false, false),
}, },
{ {
name: "dualstack 6-4", name: "dualstack 6-4",
enableDualStack: true,
node: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false), node: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
compareNode: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false), compareNode: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
}, },
{
name: "not dualstack 6-4=>4only",
enableDualStack: false,
node: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
oldNode: nil,
compareNode: makeNode([]string{"2000::/10"}, false, false),
},
{
name: "not dualstack 6-4=>as is (used in old)",
enableDualStack: false,
node: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
oldNode: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
compareNode: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
},
{
name: "not dualstack 6-4=>6only",
enableDualStack: false,
node: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
oldNode: nil,
compareNode: makeNode([]string{"2000::/10"}, false, false),
},
{
name: "not dualstack 6-4=>as is (used in old)",
enableDualStack: false,
node: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
oldNode: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
compareNode: makeNode([]string{"2000::/10", "10.0.0.0/8"}, false, false),
},
{ {
name: "new with no Spec.ConfigSource and no Status.Config , enableNodeDynamicConfig disabled", name: "new with no Spec.ConfigSource and no Status.Config , enableNodeDynamicConfig disabled",
enableDualStack: false,
enableNodeDynamicConfig: false, enableNodeDynamicConfig: false,
node: makeNode(nil, false, false), node: makeNode(nil, false, false),
oldNode: nil, oldNode: nil,
@ -171,7 +135,6 @@ func TestDropFields(t *testing.T) {
}, },
{ {
name: "new with Spec.ConfigSource and no Status.Config, enableNodeDynamicConfig disabled", name: "new with Spec.ConfigSource and no Status.Config, enableNodeDynamicConfig disabled",
enableDualStack: false,
enableNodeDynamicConfig: false, enableNodeDynamicConfig: false,
node: makeNode(nil, true, false), node: makeNode(nil, true, false),
oldNode: nil, oldNode: nil,
@ -179,7 +142,6 @@ func TestDropFields(t *testing.T) {
}, },
{ {
name: "new with Spec.ConfigSource and Status.Config, enableNodeDynamicConfig disabled", name: "new with Spec.ConfigSource and Status.Config, enableNodeDynamicConfig disabled",
enableDualStack: false,
enableNodeDynamicConfig: false, enableNodeDynamicConfig: false,
node: makeNode(nil, true, true), node: makeNode(nil, true, true),
oldNode: nil, oldNode: nil,
@ -187,7 +149,6 @@ func TestDropFields(t *testing.T) {
}, },
{ {
name: "update with Spec.ConfigSource and Status.Config (old has none), enableNodeDynamicConfig disabled", name: "update with Spec.ConfigSource and Status.Config (old has none), enableNodeDynamicConfig disabled",
enableDualStack: false,
enableNodeDynamicConfig: false, enableNodeDynamicConfig: false,
node: makeNode(nil, true, true), node: makeNode(nil, true, true),
oldNode: makeNode(nil, false, false), oldNode: makeNode(nil, false, false),
@ -195,7 +156,6 @@ func TestDropFields(t *testing.T) {
}, },
{ {
name: "update with Spec.ConfigSource and Status.Config (old has them), enableNodeDynamicConfig disabled", name: "update with Spec.ConfigSource and Status.Config (old has them), enableNodeDynamicConfig disabled",
enableDualStack: false,
enableNodeDynamicConfig: false, enableNodeDynamicConfig: false,
node: makeNode(nil, true, true), node: makeNode(nil, true, true),
oldNode: makeNode(nil, true, true), oldNode: makeNode(nil, true, true),
@ -203,7 +163,6 @@ func TestDropFields(t *testing.T) {
}, },
{ {
name: "update with Spec.ConfigSource and Status.Config (old has Status.Config), enableNodeDynamicConfig disabled", name: "update with Spec.ConfigSource and Status.Config (old has Status.Config), enableNodeDynamicConfig disabled",
enableDualStack: false,
enableNodeDynamicConfig: false, enableNodeDynamicConfig: false,
node: makeNode(nil, true, true), node: makeNode(nil, true, true),
oldNode: makeNode(nil, false, true), oldNode: makeNode(nil, false, true),
@ -211,7 +170,6 @@ func TestDropFields(t *testing.T) {
}, },
{ {
name: "new with Spec.ConfigSource and Status.Config, enableNodeDynamicConfig enabled", name: "new with Spec.ConfigSource and Status.Config, enableNodeDynamicConfig enabled",
enableDualStack: false,
enableNodeDynamicConfig: true, enableNodeDynamicConfig: true,
node: makeNode(nil, true, true), node: makeNode(nil, true, true),
oldNode: nil, oldNode: nil,
@ -221,7 +179,6 @@ func TestDropFields(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
func() { func() {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.enableDualStack)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DynamicKubeletConfig, tc.enableNodeDynamicConfig)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DynamicKubeletConfig, tc.enableNodeDynamicConfig)()
dropDisabledFields(tc.node, tc.oldNode) dropDisabledFields(tc.node, tc.oldNode)

View File

@ -215,7 +215,7 @@ func (c LegacyRESTStorageProvider) NewLegacyRESTStorage(restOptionsGetter generi
// allocator for secondary service ip range // allocator for secondary service ip range
var secondaryServiceClusterIPAllocator ipallocator.Interface var secondaryServiceClusterIPAllocator ipallocator.Interface
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) && c.SecondaryServiceIPRange.IP != nil { if c.SecondaryServiceIPRange.IP != nil {
var secondaryServiceClusterIPRegistry rangeallocation.RangeRegistry var secondaryServiceClusterIPRegistry rangeallocation.RangeRegistry
secondaryServiceClusterIPAllocator, err = ipallocator.New(&c.SecondaryServiceIPRange, func(max int, rangeSpec string) (allocator.Interface, error) { secondaryServiceClusterIPAllocator, err = ipallocator.New(&c.SecondaryServiceIPRange, func(max int, rangeSpec string) (allocator.Interface, error) {
mem := allocator.NewAllocationMap(max, rangeSpec) mem := allocator.NewAllocationMap(max, rangeSpec)

View File

@ -29,10 +29,6 @@ import (
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator" "k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
netutils "k8s.io/utils/net" netutils "k8s.io/utils/net"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/features"
) )
type mockRangeRegistry struct { type mockRangeRegistry struct {
@ -328,8 +324,6 @@ func TestShouldWorkOnSecondary(t *testing.T) {
} }
func TestRepairDualStack(t *testing.T) { func TestRepairDualStack(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
fakeClient := fake.NewSimpleClientset() fakeClient := fake.NewSimpleClientset()
ipregistry := &mockRangeRegistry{ ipregistry := &mockRangeRegistry{
item: &api.RangeAllocation{Range: "192.168.1.0/24"}, item: &api.RangeAllocation{Range: "192.168.1.0/24"},
@ -368,8 +362,6 @@ func TestRepairDualStack(t *testing.T) {
} }
func TestRepairLeakDualStack(t *testing.T) { func TestRepairLeakDualStack(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
_, cidr, _ := netutils.ParseCIDRSloppy("192.168.1.0/24") _, cidr, _ := netutils.ParseCIDRSloppy("192.168.1.0/24")
previous, err := ipallocator.NewInMemory(cidr) previous, err := ipallocator.NewInMemory(cidr)
if err != nil { if err != nil {
@ -466,7 +458,6 @@ func TestRepairWithExistingDualStack(t *testing.T) {
// we can saftly create tests that has ipFamilyPolicy:nil // we can saftly create tests that has ipFamilyPolicy:nil
// this will work every where except alloc & validation // this will work every where except alloc & validation
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
_, cidr, _ := netutils.ParseCIDRSloppy("192.168.1.0/24") _, cidr, _ := netutils.ParseCIDRSloppy("192.168.1.0/24")
previous, err := ipallocator.NewInMemory(cidr) previous, err := ipallocator.NewInMemory(cidr)
if err != nil { if err != nil {

View File

@ -107,12 +107,6 @@ func (al *Allocators) initIPFamilyFields(after After, before Before) error {
return nil return nil
} }
// gate off. We don't need to validate or default new fields
// we totally depend on existing validation in apis/validation
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
return nil
}
// We don't want to auto-upgrade (add an IP) or downgrade (remove an IP) // We don't want to auto-upgrade (add an IP) or downgrade (remove an IP)
// PreferDualStack services following a cluster change to/from // PreferDualStack services following a cluster change to/from
// dual-stackness. // dual-stackness.
@ -343,10 +337,6 @@ func (al *Allocators) allocClusterIPs(service *api.Service, dryRun bool) (map[ap
return nil, nil return nil, nil
} }
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
return al.allocClusterIP(service, dryRun)
}
toAlloc := make(map[api.IPFamily]string) toAlloc := make(map[api.IPFamily]string)
// at this stage, the only fact we know is that service has correct ip families // at this stage, the only fact we know is that service has correct ip families
// assigned to it. It may have partial assigned ClusterIPs (Upgrade to dual stack) // assigned to it. It may have partial assigned ClusterIPs (Upgrade to dual stack)
@ -391,25 +381,6 @@ func (al *Allocators) allocClusterIPs(service *api.Service, dryRun bool) (map[ap
return allocated, err return allocated, err
} }
// standard allocator for dualstackgate==Off, hard wired dependency
// and ignores policy, families and clusterIPs
func (al *Allocators) allocClusterIP(service *api.Service, dryRun bool) (map[api.IPFamily]string, error) {
toAlloc := make(map[api.IPFamily]string)
// get clusterIP.. empty string if user did not specify an ip
toAlloc[al.defaultServiceIPFamily] = service.Spec.ClusterIP
// alloc
allocated, err := al.allocIPs(service, toAlloc, dryRun)
// set
if err == nil {
service.Spec.ClusterIP = allocated[al.defaultServiceIPFamily]
service.Spec.ClusterIPs = []string{allocated[al.defaultServiceIPFamily]}
}
return allocated, err
}
func (al *Allocators) allocIPs(service *api.Service, toAlloc map[api.IPFamily]string, dryRun bool) (map[api.IPFamily]string, error) { func (al *Allocators) allocIPs(service *api.Service, toAlloc map[api.IPFamily]string, dryRun bool) (map[api.IPFamily]string, error) {
allocated := make(map[api.IPFamily]string) allocated := make(map[api.IPFamily]string)
@ -687,24 +658,12 @@ func (al *Allocators) updateClusterIPs(after After, before Before, dryRun bool)
// Update service from non-ExternalName to ExternalName, should release ClusterIP if exists. // Update service from non-ExternalName to ExternalName, should release ClusterIP if exists.
if oldService.Spec.Type != api.ServiceTypeExternalName && service.Spec.Type == api.ServiceTypeExternalName { if oldService.Spec.Type != api.ServiceTypeExternalName && service.Spec.Type == api.ServiceTypeExternalName {
toRelease = make(map[api.IPFamily]string) toRelease = make(map[api.IPFamily]string)
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
// for non dual stack enabled cluster we use clusterIPs
toRelease[al.defaultServiceIPFamily] = oldService.Spec.ClusterIP
} else {
// dual stack is enabled, collect ClusterIPs by families
for i, family := range oldService.Spec.IPFamilies { for i, family := range oldService.Spec.IPFamilies {
toRelease[family] = oldService.Spec.ClusterIPs[i] toRelease[family] = oldService.Spec.ClusterIPs[i]
} }
}
return nil, toRelease, nil return nil, toRelease, nil
} }
// upgrade and downgrade are specific to dualstack
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
return nil, nil, nil
}
upgraded := len(oldService.Spec.IPFamilies) == 1 && len(service.Spec.IPFamilies) == 2 upgraded := len(oldService.Spec.IPFamilies) == 1 && len(service.Spec.IPFamilies) == 2
downgraded := len(oldService.Spec.IPFamilies) == 2 && len(service.Spec.IPFamilies) == 1 downgraded := len(oldService.Spec.IPFamilies) == 2 && len(service.Spec.IPFamilies) == 1
@ -909,10 +868,6 @@ func (al *Allocators) releaseClusterIPs(service *api.Service) (released map[api.
return nil, nil return nil, nil
} }
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
return al.releaseClusterIP(service)
}
toRelease := make(map[api.IPFamily]string) toRelease := make(map[api.IPFamily]string)
for _, ip := range service.Spec.ClusterIPs { for _, ip := range service.Spec.ClusterIPs {
if netutils.IsIPv6String(ip) { if netutils.IsIPv6String(ip) {
@ -924,21 +879,6 @@ func (al *Allocators) releaseClusterIPs(service *api.Service) (released map[api.
return al.releaseIPs(toRelease) return al.releaseIPs(toRelease)
} }
// for pre dual stack (gate == off). Hardwired to ClusterIP and ignores all new fields
func (al *Allocators) releaseClusterIP(service *api.Service) (released map[api.IPFamily]string, err error) {
toRelease := make(map[api.IPFamily]string)
// we need to do that to handle cases where allocator is no longer configured on
// cluster
if netutils.IsIPv6String(service.Spec.ClusterIP) {
toRelease[api.IPv6Protocol] = service.Spec.ClusterIP
} else {
toRelease[api.IPv4Protocol] = service.Spec.ClusterIP
}
return al.releaseIPs(toRelease)
}
// This is O(N), but we expect haystack to be small; // This is O(N), but we expect haystack to be small;
// so small that we expect a linear search to be faster // so small that we expect a linear search to be faster
func containsNumber(haystack []int, needle int) bool { func containsNumber(haystack []int, needle int) bool {

View File

@ -36,10 +36,8 @@ import (
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/util/dryrun" "k8s.io/apiserver/pkg/util/dryrun"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/klog/v2" "k8s.io/klog/v2"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/printers" "k8s.io/kubernetes/pkg/printers"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage" printerstorage "k8s.io/kubernetes/pkg/printers/storage"
@ -242,11 +240,6 @@ func (r *REST) defaultOnReadService(service *api.Service) {
// We still want to present a consistent view of them. // We still want to present a consistent view of them.
normalizeClusterIPs(After{service}, Before{nil}) normalizeClusterIPs(After{service}, Before{nil})
// The rest of this does not apply unless dual-stack is enabled.
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
return
}
// Set ipFamilies and ipFamilyPolicy if needed. // Set ipFamilies and ipFamilyPolicy if needed.
r.defaultOnReadIPFamilies(service) r.defaultOnReadIPFamilies(service)
} }

View File

@ -639,8 +639,6 @@ func TestServiceDefaultOnRead(t *testing.T) {
input: &api.Pod{}, input: &api.Pod{},
}} }}
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
storage, _, server := newStorage(t, []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol}) storage, _, server := newStorage(t, []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol})
@ -727,9 +725,6 @@ func helpTestCreateUpdateDeleteWithFamilies(t *testing.T, testCases []cudTestCas
// NOTE: do not call t.Helper() here. It's more useful for errors to be // NOTE: do not call t.Helper() here. It's more useful for errors to be
// attributed to lines in this function than the caller of it. // attributed to lines in this function than the caller of it.
// This test is ONLY with the gate enabled.
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
storage, _, server := newStorage(t, ipFamilies) storage, _, server := newStorage(t, ipFamilies)
defer server.Terminate(t) defer server.Terminate(t)
defer storage.Store.DestroyFunc() defer storage.Store.DestroyFunc()
@ -850,7 +845,6 @@ func verifyEquiv(t testingTInterface, call string, tc *svcTestCase, got *api.Ser
if want.Spec.ClusterIP == "" { if want.Spec.ClusterIP == "" {
want.Spec.ClusterIP = got.Spec.ClusterIP want.Spec.ClusterIP = got.Spec.ClusterIP
} }
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
if want.Spec.IPFamilyPolicy == nil { if want.Spec.IPFamilyPolicy == nil {
want.Spec.IPFamilyPolicy = got.Spec.IPFamilyPolicy want.Spec.IPFamilyPolicy = got.Spec.IPFamilyPolicy
} }
@ -865,7 +859,6 @@ func verifyEquiv(t testingTInterface, call string, tc *svcTestCase, got *api.Ser
want.Spec.IPFamilies = append(want.Spec.IPFamilies, got.Spec.IPFamilies[len(want.Spec.IPFamilies):]...) want.Spec.IPFamilies = append(want.Spec.IPFamilies, got.Spec.IPFamilies[len(want.Spec.IPFamilies):]...)
} }
} }
}
if tc.expectNodePorts { if tc.expectNodePorts {
for i := range want.Spec.Ports { for i := range want.Spec.Ports {
@ -1021,8 +1014,6 @@ func TestVerifyEquiv(t *testing.T) {
expect: false, expect: false,
}} }}
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
result := verifyEquiv(fakeTestingT{t}, "test", &tc.input, tc.output) result := verifyEquiv(fakeTestingT{t}, "test", &tc.input, tc.output)
@ -1098,23 +1089,15 @@ func proveClusterIPsAllocated(t *testing.T, storage *wrapperRESTForTests, before
t.Errorf("%s: expected clusterIP == clusterIPs[0]: %q != %q", callName(before, after), sing, plur) t.Errorf("%s: expected clusterIP == clusterIPs[0]: %q != %q", callName(before, after), sing, plur)
} }
clips := []string{} for _, clip := range after.Spec.ClusterIPs {
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
clips = after.Spec.ClusterIPs
} else {
clips = append(clips, after.Spec.ClusterIP)
}
for _, clip := range clips {
if !ipIsAllocated(t, storage.alloc.serviceIPAllocatorsByFamily[familyOf(clip)], clip) { if !ipIsAllocated(t, storage.alloc.serviceIPAllocatorsByFamily[familyOf(clip)], clip) {
t.Errorf("%s: expected clusterIP to be allocated: %q", callName(before, after), clip) t.Errorf("%s: expected clusterIP to be allocated: %q", callName(before, after), clip)
} }
} }
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
if lc, lf := len(after.Spec.ClusterIPs), len(after.Spec.IPFamilies); lc != lf { if lc, lf := len(after.Spec.ClusterIPs), len(after.Spec.IPFamilies); lc != lf {
t.Errorf("%s: expected same number of clusterIPs and ipFamilies: %d != %d", callName(before, after), lc, lf) t.Errorf("%s: expected same number of clusterIPs and ipFamilies: %d != %d", callName(before, after), lc, lf)
} }
}
for i, fam := range after.Spec.IPFamilies { for i, fam := range after.Spec.IPFamilies {
if want, got := fam, familyOf(after.Spec.ClusterIPs[i]); want != got { if want, got := fam, familyOf(after.Spec.ClusterIPs[i]); want != got {
@ -1122,7 +1105,6 @@ func proveClusterIPsAllocated(t *testing.T, storage *wrapperRESTForTests, before
} }
} }
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
if after.Spec.IPFamilyPolicy == nil { if after.Spec.IPFamilyPolicy == nil {
t.Errorf("%s: expected ipFamilyPolicy to be set", callName(before, after)) t.Errorf("%s: expected ipFamilyPolicy to be set", callName(before, after))
} else { } else {
@ -1140,7 +1122,6 @@ func proveClusterIPsAllocated(t *testing.T, storage *wrapperRESTForTests, before
t.Errorf("%s: expected %d ipFamilies, got %d", callName(before, after), clus, fams) t.Errorf("%s: expected %d ipFamilies, got %d", callName(before, after), clus, fams)
} }
} }
}
if before != nil { if before != nil {
if before.Spec.ClusterIP != "" { if before.Spec.ClusterIP != "" {
@ -1180,13 +1161,7 @@ func proveClusterIPsDeallocated(t *testing.T, storage *wrapperRESTForTests, befo
} }
if before != nil && before.Spec.ClusterIP != api.ClusterIPNone { if before != nil && before.Spec.ClusterIP != api.ClusterIPNone {
clips := []string{} for _, clip := range before.Spec.ClusterIPs {
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
clips = before.Spec.ClusterIPs
} else {
clips = append(clips, before.Spec.ClusterIP)
}
for _, clip := range clips {
if ipIsAllocated(t, storage.alloc.serviceIPAllocatorsByFamily[familyOf(clip)], clip) { if ipIsAllocated(t, storage.alloc.serviceIPAllocatorsByFamily[familyOf(clip)], clip) {
t.Errorf("%s: expected clusterIP to be deallocated: %q", callName(before, after), clip) t.Errorf("%s: expected clusterIP to be deallocated: %q", callName(before, after), clip)
} }
@ -1289,35 +1264,10 @@ func TestCreateIgnoresIPsForExternalName(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
clusterFamilies []api.IPFamily clusterFamilies []api.IPFamily
enableDualStack bool
cases []testCase cases []testCase
}{{ }{{
name: "singlestack:v4_gate:off", name: "singlestack:v6",
clusterFamilies: []api.IPFamily{api.IPv4Protocol},
enableDualStack: false,
cases: []testCase{{
name: "Policy:unset_Families:unset",
svc: svctest.MakeService("foo"),
}, {
name: "Policy:SingleStack_Families:v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicySingleStack),
svctest.SetIPFamilies(api.IPv4Protocol)),
}, {
name: "Policy:PreferDualStack_Families:v4v6",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyPreferDualStack),
svctest.SetIPFamilies(api.IPv4Protocol, api.IPv6Protocol)),
}, {
name: "Policy:RequireDualStack_Families:v6v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyRequireDualStack),
svctest.SetIPFamilies(api.IPv6Protocol, api.IPv4Protocol)),
}},
}, {
name: "singlestack:v6_gate:on",
clusterFamilies: []api.IPFamily{api.IPv6Protocol}, clusterFamilies: []api.IPFamily{api.IPv6Protocol},
enableDualStack: true,
cases: []testCase{{ cases: []testCase{{
name: "Policy:unset_Families:unset", name: "Policy:unset_Families:unset",
svc: svctest.MakeService("foo"), svc: svctest.MakeService("foo"),
@ -1341,32 +1291,8 @@ func TestCreateIgnoresIPsForExternalName(t *testing.T) {
expectError: true, expectError: true,
}}, }},
}, { }, {
name: "dualstack:v4v6_gate:off", name: "dualstack:v6v4",
clusterFamilies: []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol},
enableDualStack: false,
cases: []testCase{{
name: "Policy:unset_Families:unset",
svc: svctest.MakeService("foo"),
}, {
name: "Policy:SingleStack_Families:v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicySingleStack),
svctest.SetIPFamilies(api.IPv4Protocol)),
}, {
name: "Policy:PreferDualStack_Families:v4v6",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyPreferDualStack),
svctest.SetIPFamilies(api.IPv4Protocol, api.IPv6Protocol)),
}, {
name: "Policy:RequireDualStack_Families:v6v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyRequireDualStack),
svctest.SetIPFamilies(api.IPv6Protocol, api.IPv4Protocol)),
}},
}, {
name: "dualstack:v6v4_gate:on",
clusterFamilies: []api.IPFamily{api.IPv6Protocol, api.IPv4Protocol}, clusterFamilies: []api.IPFamily{api.IPv6Protocol, api.IPv4Protocol},
enableDualStack: true,
cases: []testCase{{ cases: []testCase{{
name: "Policy:unset_Families:unset", name: "Policy:unset_Families:unset",
svc: svctest.MakeService("foo"), svc: svctest.MakeService("foo"),
@ -1393,8 +1319,6 @@ func TestCreateIgnoresIPsForExternalName(t *testing.T) {
for _, otc := range testCases { for _, otc := range testCases {
t.Run(otc.name, func(t *testing.T) { t.Run(otc.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, otc.enableDualStack)()
storage, _, server := newStorage(t, otc.clusterFamilies) storage, _, server := newStorage(t, otc.clusterFamilies)
defer server.Terminate(t) defer server.Terminate(t)
defer storage.Store.DestroyFunc() defer storage.Store.DestroyFunc()
@ -1437,159 +1361,6 @@ func TestCreateIgnoresIPsForExternalName(t *testing.T) {
} }
} }
// Prove that create ignores IPFamily stuff when dual-stack is disabled.
func TestCreateIgnoresIPFamilyWithoutDualStack(t *testing.T) {
// These cases were chosen from the full gamut to ensure all "interesting"
// cases are covered.
testCases := []struct {
name string
svc *api.Service
}{
//----------------------------------------
// ClusterIP:unset
//----------------------------------------
{
name: "ClusterIP:unset_Policy:unset_Families:unset",
svc: svctest.MakeService("foo"),
}, {
name: "ClusterIP:unset_Policy:unset_Families:v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilies(api.IPv4Protocol)),
}, {
name: "ClusterIP:unset_Policy:unset_Families:v6v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilies(api.IPv6Protocol, api.IPv4Protocol)),
}, {
name: "ClusterIP:unset_Policy:SingleStack_Families:unset",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicySingleStack)),
}, {
name: "ClusterIP:unset_Policy:SingleStack_Families:v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicySingleStack),
svctest.SetIPFamilies(api.IPv4Protocol)),
}, {
name: "ClusterIP:unset_Policy:SingleStack_Families:v6v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicySingleStack),
svctest.SetIPFamilies(api.IPv6Protocol, api.IPv4Protocol)),
}, {
name: "ClusterIP:unset_Policy:PreferDualStack_Families:unset",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyPreferDualStack)),
}, {
name: "ClusterIP:unset_Policy:PreferDualStack_Families:v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyPreferDualStack),
svctest.SetIPFamilies(api.IPv4Protocol)),
}, {
name: "ClusterIP:unset_Policy:PreferDualStack_Families:v6v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyPreferDualStack),
svctest.SetIPFamilies(api.IPv6Protocol, api.IPv4Protocol)),
}, {
name: "ClusterIP:unset_Policy:RequireDualStack_Families:unset",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyRequireDualStack)),
}, {
name: "ClusterIP:unset_Policy:RequireDualStack_Families:v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyRequireDualStack),
svctest.SetIPFamilies(api.IPv4Protocol)),
}, {
name: "ClusterIP:unset_Policy:RequireDualStack_Families:v6v4",
svc: svctest.MakeService("foo",
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyRequireDualStack),
svctest.SetIPFamilies(api.IPv6Protocol, api.IPv4Protocol)),
},
//----------------------------------------
// ClusterIPs:v4v6
//----------------------------------------
{
name: "ClusterIPs:v4v6_Policy:unset_Families:unset",
svc: svctest.MakeService("foo",
svctest.SetClusterIPs("10.0.0.1", "2000::1")),
}, {
name: "ClusterIPs:v4v6_Policy:unset_Families:v4",
svc: svctest.MakeService("foo",
svctest.SetClusterIPs("10.0.0.1", "2000::1"),
svctest.SetIPFamilies(api.IPv4Protocol)),
}, {
name: "ClusterIPs:v4v6_Policy:RequireDualStack_Families:unset",
svc: svctest.MakeService("foo",
svctest.SetClusterIPs("10.0.0.1", "2000::1"),
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyRequireDualStack)),
},
//----------------------------------------
// Headless
//----------------------------------------
{
name: "Headless_Policy:unset_Families:unset",
svc: svctest.MakeService("foo",
svctest.SetHeadless),
}, {
name: "Headless_Policy:unset_Families:v4",
svc: svctest.MakeService("foo",
svctest.SetHeadless,
svctest.SetIPFamilies(api.IPv4Protocol)),
}, {
name: "Headless_Policy:RequireDualStack_Families:unset",
svc: svctest.MakeService("foo",
svctest.SetHeadless,
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyRequireDualStack)),
},
//----------------------------------------
// HeadlessSelectorless
//----------------------------------------
{
name: "HeadlessSelectorless_Policy:unset_Families:unset",
svc: svctest.MakeService("foo",
svctest.SetHeadless,
svctest.SetSelector(nil)),
}, {
name: "HeadlessSelectorless_Policy:unset_Families:v4",
svc: svctest.MakeService("foo",
svctest.SetHeadless,
svctest.SetSelector(nil),
svctest.SetIPFamilies(api.IPv4Protocol)),
}, {
name: "HeadlessSelectorless_Policy:RequireDualStack_Families:unset",
svc: svctest.MakeService("foo",
svctest.SetHeadless,
svctest.SetSelector(nil),
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyRequireDualStack)),
},
}
// This test is ONLY with the gate off.
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, false)()
// Do this in the outer scope for performance.
storage, _, server := newStorage(t, []api.IPFamily{api.IPv4Protocol})
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx := genericapirequest.NewDefaultContext()
createdObj, err := storage.Create(ctx, tc.svc, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
if err != nil {
t.Fatalf("unexpected error creating service: %v", err)
}
defer storage.Delete(ctx, tc.svc.Name, rest.ValidateAllObjectFunc, &metav1.DeleteOptions{})
createdSvc := createdObj.(*api.Service)
// The gate is off - these should always be empty.
if want, got := fmtIPFamilyPolicy(nil), fmtIPFamilyPolicy(createdSvc.Spec.IPFamilyPolicy); want != got {
t.Errorf("wrong IPFamilyPolicy: want %s, got %s", want, got)
}
if want, got := fmtIPFamilies(nil), fmtIPFamilies(createdSvc.Spec.IPFamilies); want != got {
t.Errorf("wrong IPFamilies: want %s, got %s", want, got)
}
})
}
}
// Prove that create initializes clusterIPs from clusterIP. This simplifies // Prove that create initializes clusterIPs from clusterIP. This simplifies
// later tests to not need to re-prove this. // later tests to not need to re-prove this.
func TestCreateInitClusterIPsFromClusterIP(t *testing.T) { func TestCreateInitClusterIPsFromClusterIP(t *testing.T) {
@ -1635,9 +1406,6 @@ func TestCreateInitClusterIPsFromClusterIP(t *testing.T) {
svctest.SetClusterIP("2000::1")), svctest.SetClusterIP("2000::1")),
}} }}
// This test is ONLY with the gate enabled.
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
storage, _, server := newStorage(t, tc.clusterFamilies) storage, _, server := newStorage(t, tc.clusterFamilies)
@ -6093,9 +5861,6 @@ func TestCreateInitIPFields(t *testing.T) {
}, },
} }
// This test is ONLY with the gate enabled.
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
for _, otc := range testCases { for _, otc := range testCases {
t.Run(otc.name, func(t *testing.T) { t.Run(otc.name, func(t *testing.T) {
@ -6250,8 +6015,6 @@ func TestCreateInvalidClusterIPInputs(t *testing.T) {
expect: []string{"must be a valid IP"}, expect: []string{"must be a valid IP"},
}} }}
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
storage, _, server := newStorage(t, tc.families) storage, _, server := newStorage(t, tc.families)
@ -6291,9 +6054,6 @@ func TestCreateDeleteReuse(t *testing.T) {
svctest.SetIPFamilies(api.IPv4Protocol, api.IPv6Protocol)), svctest.SetIPFamilies(api.IPv4Protocol, api.IPv6Protocol)),
}} }}
// This test is ONLY with the gate enabled.
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
storage, _, server := newStorage(t, []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol}) storage, _, server := newStorage(t, []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol})
@ -6729,47 +6489,38 @@ func TestCreateSkipsAllocationsForHeadless(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
clusterFamilies []api.IPFamily clusterFamilies []api.IPFamily
enableDualStack bool
svc *api.Service svc *api.Service
expectError bool expectError bool
}{{ }{{
name: "singlestack:v4_gate:off", name: "singlestack:v4",
clusterFamilies: []api.IPFamily{api.IPv4Protocol}, clusterFamilies: []api.IPFamily{api.IPv4Protocol},
enableDualStack: false,
svc: svctest.MakeService("foo"), svc: svctest.MakeService("foo"),
}, { }, {
name: "singlestack:v6_gate:on", name: "singlestack:v6",
clusterFamilies: []api.IPFamily{api.IPv6Protocol}, clusterFamilies: []api.IPFamily{api.IPv6Protocol},
enableDualStack: true,
svc: svctest.MakeService("foo"), svc: svctest.MakeService("foo"),
}, { }, {
name: "dualstack:v4v6_gate:off", name: "dualstack:v4v6",
clusterFamilies: []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol}, clusterFamilies: []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol},
enableDualStack: false,
svc: svctest.MakeService("foo"), svc: svctest.MakeService("foo"),
}, { }, {
name: "dualstack:v6v4_gate:on", name: "dualstack:v6v4",
clusterFamilies: []api.IPFamily{api.IPv6Protocol, api.IPv4Protocol}, clusterFamilies: []api.IPFamily{api.IPv6Protocol, api.IPv4Protocol},
enableDualStack: true,
svc: svctest.MakeService("foo"), svc: svctest.MakeService("foo"),
}, { }, {
name: "singlestack:v4_gate:off_type:NodePort", name: "singlestack:v4_type:NodePort",
clusterFamilies: []api.IPFamily{api.IPv4Protocol}, clusterFamilies: []api.IPFamily{api.IPv4Protocol},
enableDualStack: false,
svc: svctest.MakeService("foo", svctest.SetTypeNodePort), svc: svctest.MakeService("foo", svctest.SetTypeNodePort),
expectError: true, expectError: true,
}, { }, {
name: "singlestack:v6_gate:on_type:LoadBalancer", name: "singlestack:v6_type:LoadBalancer",
clusterFamilies: []api.IPFamily{api.IPv6Protocol}, clusterFamilies: []api.IPFamily{api.IPv6Protocol},
enableDualStack: true,
svc: svctest.MakeService("foo", svctest.SetTypeLoadBalancer), svc: svctest.MakeService("foo", svctest.SetTypeLoadBalancer),
expectError: true, expectError: true,
}} }}
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.enableDualStack)()
storage, _, server := newStorage(t, tc.clusterFamilies) storage, _, server := newStorage(t, tc.clusterFamilies)
defer server.Terminate(t) defer server.Terminate(t)
defer storage.Store.DestroyFunc() defer storage.Store.DestroyFunc()
@ -6805,54 +6556,43 @@ func TestCreateDryRun(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
clusterFamilies []api.IPFamily clusterFamilies []api.IPFamily
enableDualStack bool
svc *api.Service svc *api.Service
}{{ }{{
name: "singlestack:v4_gate:off_clusterip:unset", name: "singlestack:v4_clusterip:unset",
clusterFamilies: []api.IPFamily{api.IPv4Protocol}, clusterFamilies: []api.IPFamily{api.IPv4Protocol},
enableDualStack: false,
svc: svctest.MakeService("foo"), svc: svctest.MakeService("foo"),
}, { }, {
name: "singlestack:v4_gate:off_clusterip:set", name: "singlestack:v4_clusterip:set",
clusterFamilies: []api.IPFamily{api.IPv4Protocol}, clusterFamilies: []api.IPFamily{api.IPv4Protocol},
enableDualStack: false,
svc: svctest.MakeService("foo", svctest.SetClusterIPs("10.0.0.1")), svc: svctest.MakeService("foo", svctest.SetClusterIPs("10.0.0.1")),
}, { }, {
name: "singlestack:v6_gate:on_clusterip:unset", name: "singlestack:v6_clusterip:unset",
clusterFamilies: []api.IPFamily{api.IPv6Protocol}, clusterFamilies: []api.IPFamily{api.IPv6Protocol},
enableDualStack: true,
svc: svctest.MakeService("foo"), svc: svctest.MakeService("foo"),
}, { }, {
name: "singlestack:v6_gate:on_clusterip:set", name: "singlestack:v6_clusterip:set",
clusterFamilies: []api.IPFamily{api.IPv6Protocol}, clusterFamilies: []api.IPFamily{api.IPv6Protocol},
enableDualStack: true,
svc: svctest.MakeService("foo", svctest.SetClusterIPs("2000::1")), svc: svctest.MakeService("foo", svctest.SetClusterIPs("2000::1")),
}, { }, {
name: "dualstack:v4v6_gate:on_clusterip:unset", name: "dualstack:v4v6_clusterip:unset",
clusterFamilies: []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol}, clusterFamilies: []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol},
enableDualStack: true,
svc: svctest.MakeService("foo", svctest.SetIPFamilyPolicy(api.IPFamilyPolicyPreferDualStack)), svc: svctest.MakeService("foo", svctest.SetIPFamilyPolicy(api.IPFamilyPolicyPreferDualStack)),
}, { }, {
name: "dualstack:v4v6_gate:on_clusterip:set", name: "dualstack:v4v6_clusterip:set",
clusterFamilies: []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol}, clusterFamilies: []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol},
enableDualStack: true,
svc: svctest.MakeService("foo", svctest.SetIPFamilyPolicy(api.IPFamilyPolicyPreferDualStack), svctest.SetClusterIPs("10.0.0.1", "2000::1")), svc: svctest.MakeService("foo", svctest.SetIPFamilyPolicy(api.IPFamilyPolicyPreferDualStack), svctest.SetClusterIPs("10.0.0.1", "2000::1")),
}, { }, {
name: "singlestack:v4_gate:off_type:NodePort_nodeport:unset", name: "singlestack:v4_type:NodePort_nodeport:unset",
clusterFamilies: []api.IPFamily{api.IPv4Protocol}, clusterFamilies: []api.IPFamily{api.IPv4Protocol},
enableDualStack: false,
svc: svctest.MakeService("foo", svctest.SetTypeNodePort), svc: svctest.MakeService("foo", svctest.SetTypeNodePort),
}, { }, {
name: "singlestack:v4_gate:on_type:LoadBalancer_nodePort:set", name: "singlestack:v4_type:LoadBalancer_nodePort:set",
clusterFamilies: []api.IPFamily{api.IPv4Protocol}, clusterFamilies: []api.IPFamily{api.IPv4Protocol},
enableDualStack: true,
svc: svctest.MakeService("foo", svctest.SetTypeLoadBalancer, svctest.SetUniqueNodePorts), svc: svctest.MakeService("foo", svctest.SetTypeLoadBalancer, svctest.SetUniqueNodePorts),
}} }}
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.enableDualStack)()
storage, _, server := newStorage(t, tc.clusterFamilies) storage, _, server := newStorage(t, tc.clusterFamilies)
defer server.Terminate(t) defer server.Terminate(t)
defer storage.Store.DestroyFunc() defer storage.Store.DestroyFunc()
@ -6886,9 +6626,6 @@ func TestCreateDryRun(t *testing.T) {
func TestDeleteWithFinalizer(t *testing.T) { func TestDeleteWithFinalizer(t *testing.T) {
svcName := "foo" svcName := "foo"
// This test is ONLY with the gate enabled.
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
storage, _, server := newStorage(t, []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol}) storage, _, server := newStorage(t, []api.IPFamily{api.IPv4Protocol, api.IPv6Protocol})
defer server.Terminate(t) defer server.Terminate(t)
defer storage.Store.DestroyFunc() defer storage.Store.DestroyFunc()
@ -6963,32 +6700,28 @@ func TestDeleteWithFinalizer(t *testing.T) {
func TestDeleteDryRun(t *testing.T) { func TestDeleteDryRun(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
enableDualStack bool
svc *api.Service svc *api.Service
}{{ }{
name: "gate:off", {
enableDualStack: false, name: "v4",
svc: svctest.MakeService("foo", svc: svctest.MakeService("foo",
svctest.SetTypeLoadBalancer, svctest.SetTypeLoadBalancer,
svctest.SetIPFamilies(api.IPv4Protocol),
svctest.SetExternalTrafficPolicy(api.ServiceExternalTrafficPolicyTypeLocal)), svctest.SetExternalTrafficPolicy(api.ServiceExternalTrafficPolicyTypeLocal)),
}, { },
name: "gate:on", {
enableDualStack: true, name: "v4v6",
svc: svctest.MakeService("foo", svc: svctest.MakeService("foo",
svctest.SetTypeLoadBalancer, svctest.SetTypeLoadBalancer,
svctest.SetIPFamilyPolicy(api.IPFamilyPolicyRequireDualStack),
svctest.SetIPFamilies(api.IPv4Protocol, api.IPv6Protocol),
svctest.SetExternalTrafficPolicy(api.ServiceExternalTrafficPolicyTypeLocal)), svctest.SetExternalTrafficPolicy(api.ServiceExternalTrafficPolicyTypeLocal)),
}} }}
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.enableDualStack)()
families := []api.IPFamily{api.IPv4Protocol} storage, _, server := newStorage(t, tc.svc.Spec.IPFamilies)
if tc.enableDualStack {
families = append(families, api.IPv6Protocol)
}
storage, _, server := newStorage(t, families)
defer server.Terminate(t) defer server.Terminate(t)
defer storage.Store.DestroyFunc() defer storage.Store.DestroyFunc()

View File

@ -162,14 +162,6 @@ func (svcStrategy) AllowUnconditionalUpdate() bool {
// newSvc.Spec.MyFeature = nil // newSvc.Spec.MyFeature = nil
// } // }
func dropServiceDisabledFields(newSvc *api.Service, oldSvc *api.Service) { func dropServiceDisabledFields(newSvc *api.Service, oldSvc *api.Service) {
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) && !serviceDualStackFieldsInUse(oldSvc) {
newSvc.Spec.IPFamilies = nil
newSvc.Spec.IPFamilyPolicy = nil
if len(newSvc.Spec.ClusterIPs) > 1 {
newSvc.Spec.ClusterIPs = newSvc.Spec.ClusterIPs[0:1]
}
}
// Clear AllocateLoadBalancerNodePorts if ServiceLBNodePortControl is not enabled // Clear AllocateLoadBalancerNodePorts if ServiceLBNodePortControl is not enabled
if !utilfeature.DefaultFeatureGate.Enabled(features.ServiceLBNodePortControl) { if !utilfeature.DefaultFeatureGate.Enabled(features.ServiceLBNodePortControl) {
if !allocateLoadBalancerNodePortsInUse(oldSvc) { if !allocateLoadBalancerNodePortsInUse(oldSvc) {
@ -211,19 +203,6 @@ func allocateLoadBalancerNodePortsInUse(svc *api.Service) bool {
return svc.Spec.AllocateLoadBalancerNodePorts != nil return svc.Spec.AllocateLoadBalancerNodePorts != nil
} }
// returns true if svc.Spec.ServiceIPFamily field is in use
func serviceDualStackFieldsInUse(svc *api.Service) bool {
if svc == nil {
return false
}
ipFamilyPolicyInUse := svc.Spec.IPFamilyPolicy != nil
ipFamiliesInUse := len(svc.Spec.IPFamilies) > 0
ClusterIPsInUse := len(svc.Spec.ClusterIPs) > 1
return ipFamilyPolicyInUse || ipFamiliesInUse || ClusterIPsInUse
}
// returns true when the svc.Status.Conditions field is in use. // returns true when the svc.Status.Conditions field is in use.
func serviceConditionsInUse(svc *api.Service) bool { func serviceConditionsInUse(svc *api.Service) bool {
if svc == nil { if svc == nil {

View File

@ -144,15 +144,6 @@ func TestServiceStatusStrategy(t *testing.T) {
} }
} }
func makeServiceWithIPFamilies(ipfamilies []api.IPFamily, ipFamilyPolicy *api.IPFamilyPolicyType) *api.Service {
return &api.Service{
Spec: api.ServiceSpec{
IPFamilies: ipfamilies,
IPFamilyPolicy: ipFamilyPolicy,
},
}
}
func makeServiceWithConditions(conditions []metav1.Condition) *api.Service { func makeServiceWithConditions(conditions []metav1.Condition) *api.Service {
return &api.Service{ return &api.Service{
Status: api.ServiceStatus{ Status: api.ServiceStatus{
@ -192,15 +183,10 @@ func makeServiceWithInternalTrafficPolicy(policy *api.ServiceInternalTrafficPoli
} }
func TestDropDisabledField(t *testing.T) { func TestDropDisabledField(t *testing.T) {
requireDualStack := api.IPFamilyPolicyRequireDualStack
preferDualStack := api.IPFamilyPolicyPreferDualStack
singleStack := api.IPFamilyPolicySingleStack
localInternalTrafficPolicy := api.ServiceInternalTrafficPolicyLocal localInternalTrafficPolicy := api.ServiceInternalTrafficPolicyLocal
testCases := []struct { testCases := []struct {
name string name string
enableDualStack bool
enableMixedProtocol bool enableMixedProtocol bool
enableLoadBalancerClass bool enableLoadBalancerClass bool
enableInternalTrafficPolicy bool enableInternalTrafficPolicy bool
@ -208,64 +194,6 @@ func TestDropDisabledField(t *testing.T) {
oldSvc *api.Service oldSvc *api.Service
compareSvc *api.Service compareSvc *api.Service
}{ }{
{
name: "not dual stack, field not used",
enableDualStack: false,
svc: makeServiceWithIPFamilies(nil, nil),
oldSvc: nil,
compareSvc: makeServiceWithIPFamilies(nil, nil),
},
{
name: "not dual stack, field used in old and new",
enableDualStack: false,
svc: makeServiceWithIPFamilies([]api.IPFamily{api.IPv4Protocol}, nil),
oldSvc: makeServiceWithIPFamilies([]api.IPFamily{api.IPv4Protocol}, nil),
compareSvc: makeServiceWithIPFamilies([]api.IPFamily{api.IPv4Protocol}, nil),
},
{
name: "dualstack, field used",
enableDualStack: true,
svc: makeServiceWithIPFamilies([]api.IPFamily{api.IPv6Protocol}, nil),
oldSvc: nil,
compareSvc: makeServiceWithIPFamilies([]api.IPFamily{api.IPv6Protocol}, nil),
},
/* preferDualStack field */
{
name: "not dual stack, fields is not use",
enableDualStack: false,
svc: makeServiceWithIPFamilies(nil, nil),
oldSvc: nil,
compareSvc: makeServiceWithIPFamilies(nil, nil),
},
{
name: "not dual stack, fields used in new, not in old",
enableDualStack: false,
svc: makeServiceWithIPFamilies(nil, &preferDualStack),
oldSvc: nil,
compareSvc: makeServiceWithIPFamilies(nil, nil),
},
{
name: "not dual stack, fields used in new, not in old",
enableDualStack: false,
svc: makeServiceWithIPFamilies(nil, &requireDualStack),
oldSvc: nil,
compareSvc: makeServiceWithIPFamilies(nil, nil),
},
{
name: "not dual stack, fields not used in old (single stack)",
enableDualStack: false,
svc: makeServiceWithIPFamilies(nil, nil),
oldSvc: makeServiceWithIPFamilies(nil, &singleStack),
compareSvc: makeServiceWithIPFamilies(nil, nil),
},
{
name: "dualstack, field used",
enableDualStack: true,
svc: makeServiceWithIPFamilies(nil, &singleStack),
oldSvc: nil,
compareSvc: makeServiceWithIPFamilies(nil, &singleStack),
},
/* svc.Status.Conditions */ /* svc.Status.Conditions */
{ {
name: "mixed protocol not enabled, field not used in old, not used in new", name: "mixed protocol not enabled, field not used in old, not used in new",
@ -463,7 +391,6 @@ func TestDropDisabledField(t *testing.T) {
} }
for _, tc := range testCases { for _, tc := range testCases {
func() { func() {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.enableDualStack)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MixedProtocolLBService, tc.enableMixedProtocol)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MixedProtocolLBService, tc.enableMixedProtocol)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceLoadBalancerClass, tc.enableLoadBalancerClass)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceLoadBalancerClass, tc.enableLoadBalancerClass)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceInternalTrafficPolicy, tc.enableInternalTrafficPolicy)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceInternalTrafficPolicy, tc.enableInternalTrafficPolicy)()

View File

@ -89,6 +89,9 @@ type Interface interface {
// mapped to the same IP:PORT and consequently some suffer packet // mapped to the same IP:PORT and consequently some suffer packet
// drops. // drops.
HasRandomFully() bool HasRandomFully() bool
// Present checks if the kernel supports the iptable interface
Present() bool
} }
// Protocol defines the ip protocol either ipv4 or ipv6 // Protocol defines the ip protocol either ipv4 or ipv6
@ -723,6 +726,16 @@ func (runner *runner) HasRandomFully() bool {
return runner.hasRandomFully return runner.hasRandomFully
} }
// Present tests if iptable is supported on current kernel by checking the existence
// of default table and chain
func (runner *runner) Present() bool {
if _, err := runner.ChainExists(TableNAT, ChainPostrouting); err != nil {
return false
}
return true
}
var iptablesNotFoundStrings = []string{ var iptablesNotFoundStrings = []string{
// iptables-legacy [-A|-I] BAD-CHAIN [...] // iptables-legacy [-A|-I] BAD-CHAIN [...]
// iptables-legacy [-C|-D] GOOD-CHAIN [...non-matching rule...] // iptables-legacy [-C|-D] GOOD-CHAIN [...non-matching rule...]

View File

@ -176,4 +176,8 @@ func (f *FakeIPTables) HasRandomFully() bool {
return f.hasRandomFully return f.hasRandomFully
} }
func (f *FakeIPTables) Present() bool {
return true
}
var _ = iptables.Interface(&FakeIPTables{}) var _ = iptables.Interface(&FakeIPTables{})

View File

@ -33,10 +33,8 @@ import (
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
v1core "k8s.io/client-go/kubernetes/typed/core/v1" v1core "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/kubernetes/pkg/features"
netutils "k8s.io/utils/net" netutils "k8s.io/utils/net"
) )
@ -122,14 +120,12 @@ func GetNodeHostIPs(node *v1.Node) ([]net.IP, error) {
} }
nodeIPs := []net.IP{allIPs[0]} nodeIPs := []net.IP{allIPs[0]}
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
for _, ip := range allIPs { for _, ip := range allIPs {
if netutils.IsIPv6(ip) != netutils.IsIPv6(nodeIPs[0]) { if netutils.IsIPv6(ip) != netutils.IsIPv6(nodeIPs[0]) {
nodeIPs = append(nodeIPs, ip) nodeIPs = append(nodeIPs, ip)
break break
} }
} }
}
return nodeIPs, nil return nodeIPs, nil
} }

View File

@ -23,9 +23,6 @@ import (
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/features"
netutils "k8s.io/utils/net" netutils "k8s.io/utils/net"
) )
@ -99,7 +96,6 @@ func TestGetNodeHostIPs(t *testing.T) {
testcases := []struct { testcases := []struct {
name string name string
addresses []v1.NodeAddress addresses []v1.NodeAddress
dualStack bool
expectIPs []net.IP expectIPs []net.IP
}{ }{
@ -141,7 +137,7 @@ func TestGetNodeHostIPs(t *testing.T) {
expectIPs: []net.IP{netutils.ParseIPSloppy("4.3.2.1")}, expectIPs: []net.IP{netutils.ParseIPSloppy("4.3.2.1")},
}, },
{ {
name: "dual-stack node, single-stack cluster", name: "dual-stack node",
addresses: []v1.NodeAddress{ addresses: []v1.NodeAddress{
{Type: v1.NodeInternalIP, Address: "1.2.3.4"}, {Type: v1.NodeInternalIP, Address: "1.2.3.4"},
{Type: v1.NodeExternalIP, Address: "4.3.2.1"}, {Type: v1.NodeExternalIP, Address: "4.3.2.1"},
@ -149,52 +145,50 @@ func TestGetNodeHostIPs(t *testing.T) {
{Type: v1.NodeInternalIP, Address: "a:b::c:d"}, {Type: v1.NodeInternalIP, Address: "a:b::c:d"},
{Type: v1.NodeExternalIP, Address: "d:c::b:a"}, {Type: v1.NodeExternalIP, Address: "d:c::b:a"},
}, },
expectIPs: []net.IP{netutils.ParseIPSloppy("1.2.3.4")},
},
{
name: "dual-stack node, dual-stack cluster",
addresses: []v1.NodeAddress{
{Type: v1.NodeInternalIP, Address: "1.2.3.4"},
{Type: v1.NodeExternalIP, Address: "4.3.2.1"},
{Type: v1.NodeExternalIP, Address: "4.3.2.2"},
{Type: v1.NodeInternalIP, Address: "a:b::c:d"},
{Type: v1.NodeExternalIP, Address: "d:c::b:a"},
},
dualStack: true,
expectIPs: []net.IP{netutils.ParseIPSloppy("1.2.3.4"), netutils.ParseIPSloppy("a:b::c:d")}, expectIPs: []net.IP{netutils.ParseIPSloppy("1.2.3.4"), netutils.ParseIPSloppy("a:b::c:d")},
}, },
{ {
name: "dual-stack node, different order, single-stack cluster", name: "dual-stack node",
addresses: []v1.NodeAddress{ addresses: []v1.NodeAddress{
{Type: v1.NodeInternalIP, Address: "1.2.3.4"}, {Type: v1.NodeInternalIP, Address: "1.2.3.4"},
{Type: v1.NodeInternalIP, Address: "a:b::c:d"},
{Type: v1.NodeExternalIP, Address: "4.3.2.1"}, {Type: v1.NodeExternalIP, Address: "4.3.2.1"},
{Type: v1.NodeExternalIP, Address: "4.3.2.2"}, {Type: v1.NodeExternalIP, Address: "4.3.2.2"},
{Type: v1.NodeExternalIP, Address: "d:c::b:a"},
},
expectIPs: []net.IP{netutils.ParseIPSloppy("1.2.3.4")},
},
{
name: "dual-stack node, different order, dual-stack cluster",
addresses: []v1.NodeAddress{
{Type: v1.NodeInternalIP, Address: "1.2.3.4"},
{Type: v1.NodeInternalIP, Address: "a:b::c:d"}, {Type: v1.NodeInternalIP, Address: "a:b::c:d"},
{Type: v1.NodeExternalIP, Address: "4.3.2.1"},
{Type: v1.NodeExternalIP, Address: "4.3.2.2"},
{Type: v1.NodeExternalIP, Address: "d:c::b:a"}, {Type: v1.NodeExternalIP, Address: "d:c::b:a"},
}, },
dualStack: true,
expectIPs: []net.IP{netutils.ParseIPSloppy("1.2.3.4"), netutils.ParseIPSloppy("a:b::c:d")}, expectIPs: []net.IP{netutils.ParseIPSloppy("1.2.3.4"), netutils.ParseIPSloppy("a:b::c:d")},
}, },
{ {
name: "dual-stack node, IPv6-first, no internal IPv4, single-stack cluster", name: "dual-stack node, different order",
addresses: []v1.NodeAddress{
{Type: v1.NodeInternalIP, Address: "1.2.3.4"},
{Type: v1.NodeInternalIP, Address: "a:b::c:d"},
{Type: v1.NodeExternalIP, Address: "4.3.2.1"},
{Type: v1.NodeExternalIP, Address: "4.3.2.2"},
{Type: v1.NodeExternalIP, Address: "d:c::b:a"},
},
expectIPs: []net.IP{netutils.ParseIPSloppy("1.2.3.4"), netutils.ParseIPSloppy("a:b::c:d")},
},
{
name: "dual-stack node, different order",
addresses: []v1.NodeAddress{
{Type: v1.NodeInternalIP, Address: "1.2.3.4"},
{Type: v1.NodeInternalIP, Address: "a:b::c:d"},
{Type: v1.NodeExternalIP, Address: "4.3.2.1"},
{Type: v1.NodeExternalIP, Address: "4.3.2.2"},
{Type: v1.NodeExternalIP, Address: "d:c::b:a"},
},
expectIPs: []net.IP{netutils.ParseIPSloppy("1.2.3.4"), netutils.ParseIPSloppy("a:b::c:d")},
},
{
name: "dual-stack node, IPv6-first, no internal IPv4",
addresses: []v1.NodeAddress{ addresses: []v1.NodeAddress{
{Type: v1.NodeInternalIP, Address: "a:b::c:d"}, {Type: v1.NodeInternalIP, Address: "a:b::c:d"},
{Type: v1.NodeExternalIP, Address: "d:c::b:a"}, {Type: v1.NodeExternalIP, Address: "d:c::b:a"},
{Type: v1.NodeExternalIP, Address: "4.3.2.1"}, {Type: v1.NodeExternalIP, Address: "4.3.2.1"},
{Type: v1.NodeExternalIP, Address: "4.3.2.2"}, {Type: v1.NodeExternalIP, Address: "4.3.2.2"},
}, },
expectIPs: []net.IP{netutils.ParseIPSloppy("a:b::c:d")}, expectIPs: []net.IP{netutils.ParseIPSloppy("a:b::c:d"), netutils.ParseIPSloppy("4.3.2.1")},
}, },
{ {
name: "dual-stack node, IPv6-first, no internal IPv4, dual-stack cluster", name: "dual-stack node, IPv6-first, no internal IPv4, dual-stack cluster",
@ -204,14 +198,12 @@ func TestGetNodeHostIPs(t *testing.T) {
{Type: v1.NodeExternalIP, Address: "4.3.2.1"}, {Type: v1.NodeExternalIP, Address: "4.3.2.1"},
{Type: v1.NodeExternalIP, Address: "4.3.2.2"}, {Type: v1.NodeExternalIP, Address: "4.3.2.2"},
}, },
dualStack: true,
expectIPs: []net.IP{netutils.ParseIPSloppy("a:b::c:d"), netutils.ParseIPSloppy("4.3.2.1")}, expectIPs: []net.IP{netutils.ParseIPSloppy("a:b::c:d"), netutils.ParseIPSloppy("4.3.2.1")},
}, },
} }
for _, tc := range testcases { for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.dualStack)()
node := &v1.Node{ node := &v1.Node{
Status: v1.NodeStatus{Addresses: tc.addresses}, Status: v1.NodeStatus{Addresses: tc.addresses},
} }

View File

@ -4900,12 +4900,9 @@ message ServiceSpec {
// clients must ensure that clusterIPs[0] and clusterIP have the same // clients must ensure that clusterIPs[0] and clusterIP have the same
// value. // value.
// //
// Unless the "IPv6DualStack" feature gate is enabled, this field is // This field may hold a maximum of two entries (dual-stack IPs, in either order).
// limited to one value, which must be the same as the clusterIP field. If // These IPs must correspond to the values of the ipFamilies field. Both
// the feature gate is enabled, this field may hold a maximum of two // clusterIPs and ipFamilies are governed by the ipFamilyPolicy field.
// entries (dual-stack IPs, in either order). These IPs must correspond to
// the values of the ipFamilies field. Both clusterIPs and ipFamilies are
// governed by the ipFamilyPolicy field.
// More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies // More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies
// +listType=atomic // +listType=atomic
// +optional // +optional
@ -5005,17 +5002,16 @@ message ServiceSpec {
optional SessionAffinityConfig sessionAffinityConfig = 14; optional SessionAffinityConfig sessionAffinityConfig = 14;
// IPFamilies is a list of IP families (e.g. IPv4, IPv6) assigned to this // IPFamilies is a list of IP families (e.g. IPv4, IPv6) assigned to this
// service, and is gated by the "IPv6DualStack" feature gate. This field // service. This field is usually assigned automatically based on cluster
// is usually assigned automatically based on cluster configuration and the // configuration and the ipFamilyPolicy field. If this field is specified
// ipFamilyPolicy field. If this field is specified manually, the requested // manually, the requested family is available in the cluster,
// family is available in the cluster, and ipFamilyPolicy allows it, it // and ipFamilyPolicy allows it, it will be used; otherwise creation of
// will be used; otherwise creation of the service will fail. This field // the service will fail. This field is conditionally mutable: it allows
// is conditionally mutable: it allows for adding or removing a secondary // for adding or removing a secondary IP family, but it does not allow
// IP family, but it does not allow changing the primary IP family of the // changing the primary IP family of the Service. Valid values are "IPv4"
// Service. Valid values are "IPv4" and "IPv6". This field only applies // and "IPv6". This field only applies to Services of types ClusterIP,
// to Services of types ClusterIP, NodePort, and LoadBalancer, and does // NodePort, and LoadBalancer, and does apply to "headless" services.
// apply to "headless" services. This field will be wiped when updating a // This field will be wiped when updating a Service to type ExternalName.
// Service to type ExternalName.
// //
// This field may hold a maximum of two entries (dual-stack families, in // This field may hold a maximum of two entries (dual-stack families, in
// either order). These families must correspond to the values of the // either order). These families must correspond to the values of the
@ -5026,14 +5022,13 @@ message ServiceSpec {
repeated string ipFamilies = 19; repeated string ipFamilies = 19;
// IPFamilyPolicy represents the dual-stack-ness requested or required by // IPFamilyPolicy represents the dual-stack-ness requested or required by
// this Service, and is gated by the "IPv6DualStack" feature gate. If // this Service. If there is no value provided, then this field will be set
// there is no value provided, then this field will be set to SingleStack. // to SingleStack. Services can be "SingleStack" (a single IP family),
// Services can be "SingleStack" (a single IP family), "PreferDualStack" // "PreferDualStack" (two IP families on dual-stack configured clusters or
// (two IP families on dual-stack configured clusters or a single IP family // a single IP family on single-stack clusters), or "RequireDualStack"
// on single-stack clusters), or "RequireDualStack" (two IP families on // (two IP families on dual-stack configured clusters, otherwise fail). The
// dual-stack configured clusters, otherwise fail). The ipFamilies and // ipFamilies and clusterIPs fields depend on the value of this field. This
// clusterIPs fields depend on the value of this field. This field will be // field will be wiped when updating a service to type ExternalName.
// wiped when updating a service to type ExternalName.
// +optional // +optional
optional string ipFamilyPolicy = 17; optional string ipFamilyPolicy = 17;

View File

@ -4147,12 +4147,9 @@ type ServiceSpec struct {
// clients must ensure that clusterIPs[0] and clusterIP have the same // clients must ensure that clusterIPs[0] and clusterIP have the same
// value. // value.
// //
// Unless the "IPv6DualStack" feature gate is enabled, this field is // This field may hold a maximum of two entries (dual-stack IPs, in either order).
// limited to one value, which must be the same as the clusterIP field. If // These IPs must correspond to the values of the ipFamilies field. Both
// the feature gate is enabled, this field may hold a maximum of two // clusterIPs and ipFamilies are governed by the ipFamilyPolicy field.
// entries (dual-stack IPs, in either order). These IPs must correspond to
// the values of the ipFamilies field. Both clusterIPs and ipFamilies are
// governed by the ipFamilyPolicy field.
// More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies // More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies
// +listType=atomic // +listType=atomic
// +optional // +optional
@ -4258,17 +4255,16 @@ type ServiceSpec struct {
// IPFamily *IPFamily `json:"ipFamily,omitempty" protobuf:"bytes,15,opt,name=ipFamily,Configcasttype=IPFamily"` // IPFamily *IPFamily `json:"ipFamily,omitempty" protobuf:"bytes,15,opt,name=ipFamily,Configcasttype=IPFamily"`
// IPFamilies is a list of IP families (e.g. IPv4, IPv6) assigned to this // IPFamilies is a list of IP families (e.g. IPv4, IPv6) assigned to this
// service, and is gated by the "IPv6DualStack" feature gate. This field // service. This field is usually assigned automatically based on cluster
// is usually assigned automatically based on cluster configuration and the // configuration and the ipFamilyPolicy field. If this field is specified
// ipFamilyPolicy field. If this field is specified manually, the requested // manually, the requested family is available in the cluster,
// family is available in the cluster, and ipFamilyPolicy allows it, it // and ipFamilyPolicy allows it, it will be used; otherwise creation of
// will be used; otherwise creation of the service will fail. This field // the service will fail. This field is conditionally mutable: it allows
// is conditionally mutable: it allows for adding or removing a secondary // for adding or removing a secondary IP family, but it does not allow
// IP family, but it does not allow changing the primary IP family of the // changing the primary IP family of the Service. Valid values are "IPv4"
// Service. Valid values are "IPv4" and "IPv6". This field only applies // and "IPv6". This field only applies to Services of types ClusterIP,
// to Services of types ClusterIP, NodePort, and LoadBalancer, and does // NodePort, and LoadBalancer, and does apply to "headless" services.
// apply to "headless" services. This field will be wiped when updating a // This field will be wiped when updating a Service to type ExternalName.
// Service to type ExternalName.
// //
// This field may hold a maximum of two entries (dual-stack families, in // This field may hold a maximum of two entries (dual-stack families, in
// either order). These families must correspond to the values of the // either order). These families must correspond to the values of the
@ -4279,14 +4275,13 @@ type ServiceSpec struct {
IPFamilies []IPFamily `json:"ipFamilies,omitempty" protobuf:"bytes,19,opt,name=ipFamilies,casttype=IPFamily"` IPFamilies []IPFamily `json:"ipFamilies,omitempty" protobuf:"bytes,19,opt,name=ipFamilies,casttype=IPFamily"`
// IPFamilyPolicy represents the dual-stack-ness requested or required by // IPFamilyPolicy represents the dual-stack-ness requested or required by
// this Service, and is gated by the "IPv6DualStack" feature gate. If // this Service. If there is no value provided, then this field will be set
// there is no value provided, then this field will be set to SingleStack. // to SingleStack. Services can be "SingleStack" (a single IP family),
// Services can be "SingleStack" (a single IP family), "PreferDualStack" // "PreferDualStack" (two IP families on dual-stack configured clusters or
// (two IP families on dual-stack configured clusters or a single IP family // a single IP family on single-stack clusters), or "RequireDualStack"
// on single-stack clusters), or "RequireDualStack" (two IP families on // (two IP families on dual-stack configured clusters, otherwise fail). The
// dual-stack configured clusters, otherwise fail). The ipFamilies and // ipFamilies and clusterIPs fields depend on the value of this field. This
// clusterIPs fields depend on the value of this field. This field will be // field will be wiped when updating a service to type ExternalName.
// wiped when updating a service to type ExternalName.
// +optional // +optional
IPFamilyPolicy *IPFamilyPolicyType `json:"ipFamilyPolicy,omitempty" protobuf:"bytes,17,opt,name=ipFamilyPolicy,casttype=IPFamilyPolicyType"` IPFamilyPolicy *IPFamilyPolicyType `json:"ipFamilyPolicy,omitempty" protobuf:"bytes,17,opt,name=ipFamilyPolicy,casttype=IPFamilyPolicyType"`

View File

@ -2234,7 +2234,7 @@ var map_ServiceSpec = map[string]string{
"ports": "The list of ports that are exposed by this service. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies", "ports": "The list of ports that are exposed by this service. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies",
"selector": "Route service traffic to pods with label keys and values matching this selector. If empty or not present, the service is assumed to have an external process managing its endpoints, which Kubernetes will not modify. Only applies to types ClusterIP, NodePort, and LoadBalancer. Ignored if type is ExternalName. More info: https://kubernetes.io/docs/concepts/services-networking/service/", "selector": "Route service traffic to pods with label keys and values matching this selector. If empty or not present, the service is assumed to have an external process managing its endpoints, which Kubernetes will not modify. Only applies to types ClusterIP, NodePort, and LoadBalancer. Ignored if type is ExternalName. More info: https://kubernetes.io/docs/concepts/services-networking/service/",
"clusterIP": "clusterIP is the IP address of the service and is usually assigned randomly. If an address is specified manually, is in-range (as per system configuration), and is not in use, it will be allocated to the service; otherwise creation of the service will fail. This field may not be changed through updates unless the type field is also being changed to ExternalName (which requires this field to be blank) or the type field is being changed from ExternalName (in which case this field may optionally be specified, as describe above). Valid values are \"None\", empty string (\"\"), or a valid IP address. Setting this to \"None\" makes a \"headless service\" (no virtual IP), which is useful when direct endpoint connections are preferred and proxying is not required. Only applies to types ClusterIP, NodePort, and LoadBalancer. If this field is specified when creating a Service of type ExternalName, creation will fail. This field will be wiped when updating a Service to type ExternalName. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies", "clusterIP": "clusterIP is the IP address of the service and is usually assigned randomly. If an address is specified manually, is in-range (as per system configuration), and is not in use, it will be allocated to the service; otherwise creation of the service will fail. This field may not be changed through updates unless the type field is also being changed to ExternalName (which requires this field to be blank) or the type field is being changed from ExternalName (in which case this field may optionally be specified, as describe above). Valid values are \"None\", empty string (\"\"), or a valid IP address. Setting this to \"None\" makes a \"headless service\" (no virtual IP), which is useful when direct endpoint connections are preferred and proxying is not required. Only applies to types ClusterIP, NodePort, and LoadBalancer. If this field is specified when creating a Service of type ExternalName, creation will fail. This field will be wiped when updating a Service to type ExternalName. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies",
"clusterIPs": "ClusterIPs is a list of IP addresses assigned to this service, and are usually assigned randomly. If an address is specified manually, is in-range (as per system configuration), and is not in use, it will be allocated to the service; otherwise creation of the service will fail. This field may not be changed through updates unless the type field is also being changed to ExternalName (which requires this field to be empty) or the type field is being changed from ExternalName (in which case this field may optionally be specified, as describe above). Valid values are \"None\", empty string (\"\"), or a valid IP address. Setting this to \"None\" makes a \"headless service\" (no virtual IP), which is useful when direct endpoint connections are preferred and proxying is not required. Only applies to types ClusterIP, NodePort, and LoadBalancer. If this field is specified when creating a Service of type ExternalName, creation will fail. This field will be wiped when updating a Service to type ExternalName. If this field is not specified, it will be initialized from the clusterIP field. If this field is specified, clients must ensure that clusterIPs[0] and clusterIP have the same value.\n\nUnless the \"IPv6DualStack\" feature gate is enabled, this field is limited to one value, which must be the same as the clusterIP field. If the feature gate is enabled, this field may hold a maximum of two entries (dual-stack IPs, in either order). These IPs must correspond to the values of the ipFamilies field. Both clusterIPs and ipFamilies are governed by the ipFamilyPolicy field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies", "clusterIPs": "ClusterIPs is a list of IP addresses assigned to this service, and are usually assigned randomly. If an address is specified manually, is in-range (as per system configuration), and is not in use, it will be allocated to the service; otherwise creation of the service will fail. This field may not be changed through updates unless the type field is also being changed to ExternalName (which requires this field to be empty) or the type field is being changed from ExternalName (in which case this field may optionally be specified, as describe above). Valid values are \"None\", empty string (\"\"), or a valid IP address. Setting this to \"None\" makes a \"headless service\" (no virtual IP), which is useful when direct endpoint connections are preferred and proxying is not required. Only applies to types ClusterIP, NodePort, and LoadBalancer. If this field is specified when creating a Service of type ExternalName, creation will fail. This field will be wiped when updating a Service to type ExternalName. If this field is not specified, it will be initialized from the clusterIP field. If this field is specified, clients must ensure that clusterIPs[0] and clusterIP have the same value.\n\nThis field may hold a maximum of two entries (dual-stack IPs, in either order). These IPs must correspond to the values of the ipFamilies field. Both clusterIPs and ipFamilies are governed by the ipFamilyPolicy field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies",
"type": "type determines how the Service is exposed. Defaults to ClusterIP. Valid options are ExternalName, ClusterIP, NodePort, and LoadBalancer. \"ClusterIP\" allocates a cluster-internal IP address for load-balancing to endpoints. Endpoints are determined by the selector or if that is not specified, by manual construction of an Endpoints object or EndpointSlice objects. If clusterIP is \"None\", no virtual IP is allocated and the endpoints are published as a set of endpoints rather than a virtual IP. \"NodePort\" builds on ClusterIP and allocates a port on every node which routes to the same endpoints as the clusterIP. \"LoadBalancer\" builds on NodePort and creates an external load-balancer (if supported in the current cloud) which routes to the same endpoints as the clusterIP. \"ExternalName\" aliases this service to the specified externalName. Several other fields do not apply to ExternalName services. More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types", "type": "type determines how the Service is exposed. Defaults to ClusterIP. Valid options are ExternalName, ClusterIP, NodePort, and LoadBalancer. \"ClusterIP\" allocates a cluster-internal IP address for load-balancing to endpoints. Endpoints are determined by the selector or if that is not specified, by manual construction of an Endpoints object or EndpointSlice objects. If clusterIP is \"None\", no virtual IP is allocated and the endpoints are published as a set of endpoints rather than a virtual IP. \"NodePort\" builds on ClusterIP and allocates a port on every node which routes to the same endpoints as the clusterIP. \"LoadBalancer\" builds on NodePort and creates an external load-balancer (if supported in the current cloud) which routes to the same endpoints as the clusterIP. \"ExternalName\" aliases this service to the specified externalName. Several other fields do not apply to ExternalName services. More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types",
"externalIPs": "externalIPs is a list of IP addresses for which nodes in the cluster will also accept traffic for this service. These IPs are not managed by Kubernetes. The user is responsible for ensuring that traffic arrives at a node with this IP. A common example is external load-balancers that are not part of the Kubernetes system.", "externalIPs": "externalIPs is a list of IP addresses for which nodes in the cluster will also accept traffic for this service. These IPs are not managed by Kubernetes. The user is responsible for ensuring that traffic arrives at a node with this IP. A common example is external load-balancers that are not part of the Kubernetes system.",
"sessionAffinity": "Supports \"ClientIP\" and \"None\". Used to maintain session affinity. Enable client IP based session affinity. Must be ClientIP or None. Defaults to None. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies", "sessionAffinity": "Supports \"ClientIP\" and \"None\". Used to maintain session affinity. Enable client IP based session affinity. Must be ClientIP or None. Defaults to None. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies",
@ -2245,8 +2245,8 @@ var map_ServiceSpec = map[string]string{
"healthCheckNodePort": "healthCheckNodePort specifies the healthcheck nodePort for the service. This only applies when type is set to LoadBalancer and externalTrafficPolicy is set to Local. If a value is specified, is in-range, and is not in use, it will be used. If not specified, a value will be automatically allocated. External systems (e.g. load-balancers) can use this port to determine if a given node holds endpoints for this service or not. If this field is specified when creating a Service which does not need it, creation will fail. This field will be wiped when updating a Service to no longer need it (e.g. changing type).", "healthCheckNodePort": "healthCheckNodePort specifies the healthcheck nodePort for the service. This only applies when type is set to LoadBalancer and externalTrafficPolicy is set to Local. If a value is specified, is in-range, and is not in use, it will be used. If not specified, a value will be automatically allocated. External systems (e.g. load-balancers) can use this port to determine if a given node holds endpoints for this service or not. If this field is specified when creating a Service which does not need it, creation will fail. This field will be wiped when updating a Service to no longer need it (e.g. changing type).",
"publishNotReadyAddresses": "publishNotReadyAddresses indicates that any agent which deals with endpoints for this Service should disregard any indications of ready/not-ready. The primary use case for setting this field is for a StatefulSet's Headless Service to propagate SRV DNS records for its Pods for the purpose of peer discovery. The Kubernetes controllers that generate Endpoints and EndpointSlice resources for Services interpret this to mean that all endpoints are considered \"ready\" even if the Pods themselves are not. Agents which consume only Kubernetes generated endpoints through the Endpoints or EndpointSlice resources can safely assume this behavior.", "publishNotReadyAddresses": "publishNotReadyAddresses indicates that any agent which deals with endpoints for this Service should disregard any indications of ready/not-ready. The primary use case for setting this field is for a StatefulSet's Headless Service to propagate SRV DNS records for its Pods for the purpose of peer discovery. The Kubernetes controllers that generate Endpoints and EndpointSlice resources for Services interpret this to mean that all endpoints are considered \"ready\" even if the Pods themselves are not. Agents which consume only Kubernetes generated endpoints through the Endpoints or EndpointSlice resources can safely assume this behavior.",
"sessionAffinityConfig": "sessionAffinityConfig contains the configurations of session affinity.", "sessionAffinityConfig": "sessionAffinityConfig contains the configurations of session affinity.",
"ipFamilies": "IPFamilies is a list of IP families (e.g. IPv4, IPv6) assigned to this service, and is gated by the \"IPv6DualStack\" feature gate. This field is usually assigned automatically based on cluster configuration and the ipFamilyPolicy field. If this field is specified manually, the requested family is available in the cluster, and ipFamilyPolicy allows it, it will be used; otherwise creation of the service will fail. This field is conditionally mutable: it allows for adding or removing a secondary IP family, but it does not allow changing the primary IP family of the Service. Valid values are \"IPv4\" and \"IPv6\". This field only applies to Services of types ClusterIP, NodePort, and LoadBalancer, and does apply to \"headless\" services. This field will be wiped when updating a Service to type ExternalName.\n\nThis field may hold a maximum of two entries (dual-stack families, in either order). These families must correspond to the values of the clusterIPs field, if specified. Both clusterIPs and ipFamilies are governed by the ipFamilyPolicy field.", "ipFamilies": "IPFamilies is a list of IP families (e.g. IPv4, IPv6) assigned to this service. This field is usually assigned automatically based on cluster configuration and the ipFamilyPolicy field. If this field is specified manually, the requested family is available in the cluster, and ipFamilyPolicy allows it, it will be used; otherwise creation of the service will fail. This field is conditionally mutable: it allows for adding or removing a secondary IP family, but it does not allow changing the primary IP family of the Service. Valid values are \"IPv4\" and \"IPv6\". This field only applies to Services of types ClusterIP, NodePort, and LoadBalancer, and does apply to \"headless\" services. This field will be wiped when updating a Service to type ExternalName.\n\nThis field may hold a maximum of two entries (dual-stack families, in either order). These families must correspond to the values of the clusterIPs field, if specified. Both clusterIPs and ipFamilies are governed by the ipFamilyPolicy field.",
"ipFamilyPolicy": "IPFamilyPolicy represents the dual-stack-ness requested or required by this Service, and is gated by the \"IPv6DualStack\" feature gate. If there is no value provided, then this field will be set to SingleStack. Services can be \"SingleStack\" (a single IP family), \"PreferDualStack\" (two IP families on dual-stack configured clusters or a single IP family on single-stack clusters), or \"RequireDualStack\" (two IP families on dual-stack configured clusters, otherwise fail). The ipFamilies and clusterIPs fields depend on the value of this field. This field will be wiped when updating a service to type ExternalName.", "ipFamilyPolicy": "IPFamilyPolicy represents the dual-stack-ness requested or required by this Service. If there is no value provided, then this field will be set to SingleStack. Services can be \"SingleStack\" (a single IP family), \"PreferDualStack\" (two IP families on dual-stack configured clusters or a single IP family on single-stack clusters), or \"RequireDualStack\" (two IP families on dual-stack configured clusters, otherwise fail). The ipFamilies and clusterIPs fields depend on the value of this field. This field will be wiped when updating a service to type ExternalName.",
"allocateLoadBalancerNodePorts": "allocateLoadBalancerNodePorts defines if NodePorts will be automatically allocated for services with type LoadBalancer. Default is \"true\". It may be set to \"false\" if the cluster load-balancer does not rely on NodePorts. If the caller requests specific NodePorts (by specifying a value), those requests will be respected, regardless of this field. This field may only be set for services with type LoadBalancer and will be cleared if the type is changed to any other type. This field is beta-level and is only honored by servers that enable the ServiceLBNodePortControl feature.", "allocateLoadBalancerNodePorts": "allocateLoadBalancerNodePorts defines if NodePorts will be automatically allocated for services with type LoadBalancer. Default is \"true\". It may be set to \"false\" if the cluster load-balancer does not rely on NodePorts. If the caller requests specific NodePorts (by specifying a value), those requests will be respected, regardless of this field. This field may only be set for services with type LoadBalancer and will be cleared if the type is changed to any other type. This field is beta-level and is only honored by servers that enable the ServiceLBNodePortControl feature.",
"loadBalancerClass": "loadBalancerClass is the class of the load balancer implementation this Service belongs to. If specified, the value of this field must be a label-style identifier, with an optional prefix, e.g. \"internal-vip\" or \"example.com/internal-vip\". Unprefixed names are reserved for end-users. This field can only be set when the Service type is 'LoadBalancer'. If not set, the default load balancer implementation is used, today this is typically done through the cloud provider integration, but should apply for any default implementation. If set, it is assumed that a load balancer implementation is watching for Services with a matching class. Any default load balancer implementation (e.g. cloud providers) should ignore Services that set this field. This field can only be set when creating or updating a Service to type 'LoadBalancer'. Once set, it can not be changed. This field will be wiped when a service is updated to a non 'LoadBalancer' type.", "loadBalancerClass": "loadBalancerClass is the class of the load balancer implementation this Service belongs to. If specified, the value of this field must be a label-style identifier, with an optional prefix, e.g. \"internal-vip\" or \"example.com/internal-vip\". Unprefixed names are reserved for end-users. This field can only be set when the Service type is 'LoadBalancer'. If not set, the default load balancer implementation is used, today this is typically done through the cloud provider integration, but should apply for any default implementation. If set, it is assumed that a load balancer implementation is watching for Services with a matching class. Any default load balancer implementation (e.g. cloud providers) should ignore Services that set this field. This field can only be set when creating or updating a Service to type 'LoadBalancer'. Once set, it can not be changed. This field will be wiped when a service is updated to a non 'LoadBalancer' type.",
"internalTrafficPolicy": "InternalTrafficPolicy specifies if the cluster internal traffic should be routed to all endpoints or node-local endpoints only. \"Cluster\" routes internal traffic to a Service to all endpoints. \"Local\" routes traffic to node-local endpoints only, traffic is dropped if no node-local endpoints are ready. The default value is \"Cluster\".", "internalTrafficPolicy": "InternalTrafficPolicy specifies if the cluster internal traffic should be routed to all endpoints or node-local endpoints only. \"Cluster\" routes internal traffic to a Service to all endpoints. \"Local\" routes traffic to node-local endpoints only, traffic is dropped if no node-local endpoints are ready. The default value is \"Cluster\".",

View File

@ -50,6 +50,6 @@ func SetupCurrentKubernetesSpecificFeatureGates(featuregates featuregate.Mutable
// cloudPublicFeatureGates consists of cloud-specific feature keys. // cloudPublicFeatureGates consists of cloud-specific feature keys.
// To add a new feature, define a key for it at k8s.io/api/pkg/features and add it here. // To add a new feature, define a key for it at k8s.io/api/pkg/features and add it here.
var cloudPublicFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ var cloudPublicFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
IPv6DualStack: {Default: true, PreRelease: featuregate.Beta}, IPv6DualStack: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
ControllerManagerLeaderMigration: {Default: true, PreRelease: featuregate.Beta}, ControllerManagerLeaderMigration: {Default: true, PreRelease: featuregate.Beta},
} }

View File

@ -28,14 +28,11 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/controller/endpoint" "k8s.io/kubernetes/pkg/controller/endpoint"
"k8s.io/kubernetes/pkg/controller/endpointslice" "k8s.io/kubernetes/pkg/controller/endpointslice"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/test/integration/framework" "k8s.io/kubernetes/test/integration/framework"
netutils "k8s.io/utils/net" netutils "k8s.io/utils/net"
) )
@ -48,9 +45,6 @@ func TestDualStackEndpoints(t *testing.T) {
return map[string]string{"foo": "bar"} return map[string]string{"foo": "bar"}
} }
dualStack := true
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, dualStack)()
cfg := framework.NewIntegrationTestControlPlaneConfig() cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR) _, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
if err != nil { if err != nil {

View File

@ -39,10 +39,6 @@ import (
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/test/integration/framework" "k8s.io/kubernetes/test/integration/framework"
netutils "k8s.io/utils/net" netutils "k8s.io/utils/net"
) )
@ -51,7 +47,6 @@ import (
func TestCreateServiceSingleStackIPv4(t *testing.T) { func TestCreateServiceSingleStackIPv4(t *testing.T) {
// Create an IPv4 single stack control-plane // Create an IPv4 single stack control-plane
serviceCIDR := "10.0.0.0/16" serviceCIDR := "10.0.0.0/16"
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
cfg := framework.NewIntegrationTestControlPlaneConfig() cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR) _, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
@ -268,7 +263,6 @@ func TestCreateServiceSingleStackIPv4(t *testing.T) {
func TestCreateServiceDualStackIPv6(t *testing.T) { func TestCreateServiceDualStackIPv6(t *testing.T) {
// Create an IPv6 only dual stack control-plane // Create an IPv6 only dual stack control-plane
serviceCIDR := "2001:db8:1::/48" serviceCIDR := "2001:db8:1::/48"
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
cfg := framework.NewIntegrationTestControlPlaneConfig() cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR) _, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
@ -488,7 +482,6 @@ func TestCreateServiceDualStackIPv4IPv6(t *testing.T) {
// Create an IPv4IPv6 dual stack control-plane // Create an IPv4IPv6 dual stack control-plane
serviceCIDR := "10.0.0.0/16" serviceCIDR := "10.0.0.0/16"
secondaryServiceCIDR := "2001:db8:1::/48" secondaryServiceCIDR := "2001:db8:1::/48"
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
cfg := framework.NewIntegrationTestControlPlaneConfig() cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR) _, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
@ -715,7 +708,6 @@ func TestCreateServiceDualStackIPv6IPv4(t *testing.T) {
// Create an IPv6IPv4 dual stack control-plane // Create an IPv6IPv4 dual stack control-plane
serviceCIDR := "2001:db8:1::/48" serviceCIDR := "2001:db8:1::/48"
secondaryServiceCIDR := "10.0.0.0/16" secondaryServiceCIDR := "10.0.0.0/16"
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
cfg := framework.NewIntegrationTestControlPlaneConfig() cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR) _, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
@ -945,7 +937,6 @@ func TestUpgradeDowngrade(t *testing.T) {
// Create an IPv4IPv6 dual stack control-plane // Create an IPv4IPv6 dual stack control-plane
serviceCIDR := "10.0.0.0/16" serviceCIDR := "10.0.0.0/16"
secondaryServiceCIDR := "2001:db8:1::/48" secondaryServiceCIDR := "2001:db8:1::/48"
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
cfg := framework.NewIntegrationTestControlPlaneConfig() cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR) _, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
@ -1060,7 +1051,6 @@ func TestConvertToFromExternalName(t *testing.T) {
// Create an IPv4IPv6 dual stack control-plane // Create an IPv4IPv6 dual stack control-plane
serviceCIDR := "10.0.0.0/16" serviceCIDR := "10.0.0.0/16"
secondaryServiceCIDR := "2001:db8:1::/48" secondaryServiceCIDR := "2001:db8:1::/48"
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
cfg := framework.NewIntegrationTestControlPlaneConfig() cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR) _, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
@ -1148,92 +1138,11 @@ func TestConvertToFromExternalName(t *testing.T) {
} }
} }
// TestExistingServiceDefaulting tests that existing services are defaulted correctly after an upgrade
func TestExistingServiceDefaulting(t *testing.T) {
// Create an IPv4IPv6 dual stack control-plane
serviceCIDR := "10.0.0.0/16"
cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
if err != nil {
t.Fatalf("bad cidr: %v", err)
}
cfg.ExtraConfig.ServiceIPRange = *cidr
_, s, closeFn := framework.RunAnAPIServer(cfg)
defer closeFn()
client := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL})
// Wait until the default "kubernetes" service is created.
if err = wait.Poll(250*time.Millisecond, time.Minute, func() (bool, error) {
_, err := client.CoreV1().Services(metav1.NamespaceDefault).Get(context.TODO(), "kubernetes", metav1.GetOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return false, err
}
return !apierrors.IsNotFound(err), nil
}); err != nil {
t.Fatalf("creating kubernetes service timed out")
}
serviceName := "svc-ext-name"
svc := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: serviceName,
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeClusterIP,
Ports: []v1.ServicePort{
{
Port: 443,
TargetPort: intstr.FromInt(443),
},
},
},
}
// make sure gate is off (cluster is not running as dual stack)
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, false)()
// create a service
_, err = client.CoreV1().Services(metav1.NamespaceDefault).Create(context.TODO(), svc, metav1.CreateOptions{})
if err != nil {
t.Fatalf("unexpected error while creating service:%v", err)
}
// validate the service was created correctly if it was not expected to fail
svc, err = client.CoreV1().Services(metav1.NamespaceDefault).Get(context.TODO(), svc.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("Unexpected error to get the service %s %v", svc.Name, err)
}
// turn gate on
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
// validate the service was created correctly if it was not expected to fail
defaultedSvc, err := client.CoreV1().Services(metav1.NamespaceDefault).Get(context.TODO(), svc.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("Unexpected error to get the service %s %v", svc.Name, err)
}
if defaultedSvc.Spec.IPFamilyPolicy == nil {
t.Fatalf("service should have an IPFamily Policy")
} else {
// must be equal to single stack
if *(defaultedSvc.Spec.IPFamilyPolicy) != v1.IPFamilyPolicySingleStack {
t.Fatalf("service should have a SingleStack as IPFamilyPolicy instead we got:%v", defaultedSvc.Spec.IPFamilyPolicy)
}
}
if err := validateServiceAndClusterIPFamily(defaultedSvc, []v1.IPFamily{v1.IPv4Protocol}); err != nil {
t.Fatalf("Unexpected error validating the service %s %v", svc.Name, err)
}
}
// TestPreferDualStack preferDualstack on create and update // TestPreferDualStack preferDualstack on create and update
func TestPreferDualStack(t *testing.T) { func TestPreferDualStack(t *testing.T) {
// Create an IPv4IPv6 dual stack control-plane // Create an IPv4IPv6 dual stack control-plane
serviceCIDR := "10.0.0.0/16" serviceCIDR := "10.0.0.0/16"
secondaryServiceCIDR := "2001:db8:1::/48" secondaryServiceCIDR := "2001:db8:1::/48"
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
cfg := framework.NewIntegrationTestControlPlaneConfig() cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR) _, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
@ -1318,7 +1227,6 @@ type labelsForMergePatch struct {
func TestServiceUpdate(t *testing.T) { func TestServiceUpdate(t *testing.T) {
// Create an IPv4 single stack control-plane // Create an IPv4 single stack control-plane
serviceCIDR := "10.0.0.0/16" serviceCIDR := "10.0.0.0/16"
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, false)()
cfg := framework.NewIntegrationTestControlPlaneConfig() cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR) _, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
@ -1483,7 +1391,6 @@ func validateServiceAndClusterIPFamily(svc *v1.Service, expectedIPFamilies []v1.
func TestUpgradeServicePreferToDualStack(t *testing.T) { func TestUpgradeServicePreferToDualStack(t *testing.T) {
// Create an IPv4 only dual stack control-plane // Create an IPv4 only dual stack control-plane
serviceCIDR := "192.168.0.0/24" serviceCIDR := "192.168.0.0/24"
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
cfg := framework.NewIntegrationTestControlPlaneConfig() cfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR) _, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
@ -1580,7 +1487,7 @@ func TestDowngradeServicePreferToDualStack(t *testing.T) {
// Create a dual stack control-plane // Create a dual stack control-plane
serviceCIDR := "192.168.0.0/24" serviceCIDR := "192.168.0.0/24"
secondaryServiceCIDR := "2001:db8:1::/48" secondaryServiceCIDR := "2001:db8:1::/48"
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, true)()
dualStackCfg := framework.NewIntegrationTestControlPlaneConfig() dualStackCfg := framework.NewIntegrationTestControlPlaneConfig()
_, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR) _, cidr, err := netutils.ParseCIDRSloppy(serviceCIDR)
if err != nil { if err != nil {