Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add ipv6 support to capd #4558

Merged
merged 1 commit into from
May 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions api/v1alpha4/cluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"net"
"strings"

"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
Expand Down Expand Up @@ -215,6 +216,87 @@ func (c *Cluster) SetConditions(conditions Conditions) {
c.Status.Conditions = conditions
}

mcwumbly marked this conversation as resolved.
Show resolved Hide resolved
func (c *Cluster) GetIPFamily() (ClusterIPFamily, error) {
var podCIDRs, serviceCIDRs []string
if c.Spec.ClusterNetwork != nil {
if c.Spec.ClusterNetwork.Pods != nil {
podCIDRs = c.Spec.ClusterNetwork.Pods.CIDRBlocks
}
if c.Spec.ClusterNetwork.Services != nil {
serviceCIDRs = c.Spec.ClusterNetwork.Services.CIDRBlocks
}
}
if len(podCIDRs) == 0 && len(serviceCIDRs) == 0 {
return IPv4IPFamily, nil
}

podsIPFamily, err := ipFamilyForCIDRStrings(podCIDRs)
if err != nil {
return InvalidIPFamily, fmt.Errorf("pods: %s", err)
}
if len(serviceCIDRs) == 0 {
return podsIPFamily, nil
}

servicesIPFamily, err := ipFamilyForCIDRStrings(serviceCIDRs)
if err != nil {
return InvalidIPFamily, fmt.Errorf("services: %s", err)
}
if len(podCIDRs) == 0 {
return servicesIPFamily, nil
}

if podsIPFamily == DualStackIPFamily {
return DualStackIPFamily, nil
} else if podsIPFamily != servicesIPFamily {
return InvalidIPFamily, errors.New("pods and services IP family mismatch")
}

return podsIPFamily, nil
}

func ipFamilyForCIDRStrings(cidrs []string) (ClusterIPFamily, error) {
if len(cidrs) > 2 {
return InvalidIPFamily, errors.New("too many CIDRs specified")
}
var foundIPv4 bool
var foundIPv6 bool
for _, cidr := range cidrs {
ip, _, err := net.ParseCIDR(cidr)
if err != nil {
return InvalidIPFamily, fmt.Errorf("could not parse CIDR: %s", err)
}
if ip.To4() != nil {
foundIPv4 = true
} else {
foundIPv6 = true
}
}
switch {
case foundIPv4 && foundIPv6:
return DualStackIPFamily, nil
case foundIPv4:
return IPv4IPFamily, nil
case foundIPv6:
return IPv6IPFamily, nil
default:
return InvalidIPFamily, nil
}
}

type ClusterIPFamily int

const (
InvalidIPFamily ClusterIPFamily = iota
IPv4IPFamily
IPv6IPFamily
DualStackIPFamily
)

func (f ClusterIPFamily) String() string {
return [...]string{"InvalidIPFamily", "IPv4IPFamily", "IPv6IPFamily", "DualStackIPFamily"}[f]
}

// +kubebuilder:object:root=true

// ClusterList contains a list of Cluster.
Expand Down
197 changes: 197 additions & 0 deletions api/v1alpha4/cluster_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/*
Copyright 2021 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha4

import (
"testing"

. "github.com/onsi/gomega"
)

func TestClusterIPFamily(t *testing.T) {
clusterWithNetwork := func(podCIDRs, serviceCIDRs []string) *Cluster {
return &Cluster{
Spec: ClusterSpec{
ClusterNetwork: &ClusterNetwork{
Pods: &NetworkRanges{
CIDRBlocks: podCIDRs,
},
Services: &NetworkRanges{
CIDRBlocks: serviceCIDRs,
},
},
},
}
}

validAndUnambiguous := []struct {
name string
expectRes ClusterIPFamily
c *Cluster
}{
{
name: "pods: ipv4, services: ipv4",
expectRes: IPv4IPFamily,
c: clusterWithNetwork([]string{"192.168.0.0/16"}, []string{"10.128.0.0/12"}),
},
{
name: "pods: ipv4, services: nil",
expectRes: IPv4IPFamily,
c: clusterWithNetwork([]string{"192.168.0.0/16"}, nil),
},
{
name: "pods: ipv6, services: nil",
expectRes: IPv6IPFamily,
c: clusterWithNetwork([]string{"fd00:100:96::/48"}, nil),
},
{
name: "pods: ipv6, services: ipv6",
expectRes: IPv6IPFamily,
c: clusterWithNetwork([]string{"fd00:100:96::/48"}, []string{"fd00:100:64::/108"}),
},
{
name: "pods: dual-stack, services: nil",
expectRes: DualStackIPFamily,
c: clusterWithNetwork([]string{"192.168.0.0/16", "fd00:100:96::/48"}, nil),
},
{
name: "pods: dual-stack, services: ipv4",
expectRes: DualStackIPFamily,
c: clusterWithNetwork([]string{"192.168.0.0/16", "fd00:100:96::/48"}, []string{"10.128.0.0/12"}),
},
{
name: "pods: dual-stack, services: ipv6",
expectRes: DualStackIPFamily,
c: clusterWithNetwork([]string{"192.168.0.0/16", "fd00:100:96::/48"}, []string{"fd00:100:64::/108"}),
},
{
name: "pods: dual-stack, services: dual-stack",
expectRes: DualStackIPFamily,
c: clusterWithNetwork([]string{"192.168.0.0/16", "fd00:100:96::/48"}, []string{"10.128.0.0/12", "fd00:100:64::/108"}),
},
{
name: "pods: nil, services: dual-stack",
expectRes: DualStackIPFamily,
c: clusterWithNetwork(nil, []string{"10.128.0.0/12", "fd00:100:64::/108"}),
},
}

for _, tt := range validAndUnambiguous {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
ipFamily, err := tt.c.GetIPFamily()
g.Expect(ipFamily).To(Equal(tt.expectRes))
g.Expect(err).NotTo(HaveOccurred())
})
}

validButAmbiguous := []struct {
name string
expectRes ClusterIPFamily
c *Cluster
}{
{
name: "pods: nil, services: nil",
// this could be ipv4, ipv6, or dual-stack; assume ipv4 for now though
expectRes: IPv4IPFamily,
c: clusterWithNetwork(nil, nil),
},
{
name: "pods: nil, services: ipv4",
// this could be a dual-stack; assume ipv4 for now though
expectRes: IPv4IPFamily,
c: clusterWithNetwork(nil, []string{"10.128.0.0/12"}),
},
{
name: "pods: nil, services: ipv6",
// this could be dual-stack; assume ipv6 for now though
expectRes: IPv6IPFamily,
c: clusterWithNetwork(nil, []string{"fd00:100:64::/108"}),
},
}

for _, tt := range validButAmbiguous {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
ipFamily, err := tt.c.GetIPFamily()
g.Expect(ipFamily).To(Equal(tt.expectRes))
g.Expect(err).NotTo(HaveOccurred())
})
}

invalid := []struct {
name string
expectErr string
c *Cluster
}{
{
name: "pods: ipv4, services: ipv6",
expectErr: "pods and services IP family mismatch",
c: clusterWithNetwork([]string{"192.168.0.0/16"}, []string{"fd00:100:64::/108"}),
},
{
name: "pods: ipv6, services: ipv4",
expectErr: "pods and services IP family mismatch",
c: clusterWithNetwork([]string{"fd00:100:96::/48"}, []string{"10.128.0.0/12"}),
},
{
name: "pods: ipv6, services: dual-stack",
expectErr: "pods and services IP family mismatch",
c: clusterWithNetwork([]string{"fd00:100:96::/48"}, []string{"10.128.0.0/12", "fd00:100:64::/108"}),
},
{
name: "pods: ipv4, services: dual-stack",
expectErr: "pods and services IP family mismatch",
c: clusterWithNetwork([]string{"192.168.0.0/16"}, []string{"10.128.0.0/12", "fd00:100:64::/108"}),
},
{
name: "pods: ipv4, services: dual-stack",
expectErr: "pods and services IP family mismatch",
c: clusterWithNetwork([]string{"192.168.0.0/16"}, []string{"10.128.0.0/12", "fd00:100:64::/108"}),
},
{
name: "pods: bad cidr",
expectErr: "pods: could not parse CIDR",
c: clusterWithNetwork([]string{"foo"}, nil),
},
{
name: "services: bad cidr",
expectErr: "services: could not parse CIDR",
c: clusterWithNetwork([]string{"192.168.0.0/16"}, []string{"foo"}),
},
{
name: "pods: too many cidrs",
expectErr: "pods: too many CIDRs specified",
c: clusterWithNetwork([]string{"192.168.0.0/16", "fd00:100:96::/48", "10.128.0.0/12"}, nil),
},
{
name: "services: too many cidrs",
expectErr: "services: too many CIDRs specified",
c: clusterWithNetwork(nil, []string{"192.168.0.0/16", "fd00:100:96::/48", "10.128.0.0/12"}),
},
}

for _, tt := range invalid {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
ipFamily, err := tt.c.GetIPFamily()
g.Expect(err).To(HaveOccurred())
g.Expect(err).To(MatchError(ContainSubstring(tt.expectErr)))
g.Expect(ipFamily).To(Equal(InvalidIPFamily))
})
}
}
1 change: 1 addition & 0 deletions test/e2e/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ cluster-templates-v1alpha4: $(KUSTOMIZE) ## Generate cluster templates for v1alp
$(KUSTOMIZE) build $(DOCKER_TEMPLATES)/v1alpha4/cluster-template-node-drain --load_restrictor none > $(DOCKER_TEMPLATES)/v1alpha4/cluster-template-node-drain.yaml
$(KUSTOMIZE) build $(DOCKER_TEMPLATES)/v1alpha4/cluster-template-upgrades --load_restrictor none > $(DOCKER_TEMPLATES)/v1alpha4/cluster-template-upgrades.yaml
$(KUSTOMIZE) build $(DOCKER_TEMPLATES)/v1alpha4/cluster-template-kcp-scale-in --load_restrictor none > $(DOCKER_TEMPLATES)/v1alpha4/cluster-template-kcp-scale-in.yaml
$(KUSTOMIZE) build $(DOCKER_TEMPLATES)/v1alpha4/cluster-template-ipv6 --load_restrictor none > $(DOCKER_TEMPLATES)/v1alpha4/cluster-template-ipv6.yaml
## --------------------------------------
## Testing
## --------------------------------------
Expand Down
1 change: 1 addition & 0 deletions test/e2e/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const (
KubernetesVersionUpgradeTo = "KUBERNETES_VERSION_UPGRADE_TO"
EtcdVersionUpgradeTo = "ETCD_VERSION_UPGRADE_TO"
CoreDNSVersionUpgradeTo = "COREDNS_VERSION_UPGRADE_TO"
IPFamily = "IP_FAMILY"
)

func Byf(format string, a ...interface{}) {
Expand Down
3 changes: 2 additions & 1 deletion test/e2e/config/docker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ providers:
- sourcePath: "../data/infrastructure-docker/v1alpha4/cluster-template-node-drain.yaml"
- sourcePath: "../data/infrastructure-docker/v1alpha4/cluster-template-upgrades.yaml"
- sourcePath: "../data/infrastructure-docker/v1alpha4/cluster-template-kcp-scale-in.yaml"
- sourcePath: "../data/infrastructure-docker/v1alpha4/cluster-template-ipv6.yaml"
- sourcePath: "../data/shared/v1alpha4/metadata.yaml"

variables:
Expand All @@ -115,8 +116,8 @@ variables:
KUBERNETES_VERSION_UPGRADE_TO: "v1.19.1"
KUBERNETES_VERSION_UPGRADE_FROM: "v1.18.2"
DOCKER_SERVICE_DOMAIN: "cluster.local"
IP_FAMILY: "IPv4"
DOCKER_SERVICE_CIDRS: "10.128.0.0/12"
# IMPORTANT! This values should match the one used by the CNI provider
DOCKER_POD_CIDRS: "192.168.0.0/16"
CNI: "./data/cni/kindnet/kindnet.yaml"
EXP_CLUSTER_RESOURCE_SET: "true"
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/data/cni/kindnet/kindnet.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ spec:
fieldRef:
fieldPath: status.podIP
- name: POD_SUBNET
value: "192.168.0.0/16"
value: '${DOCKER_POD_CIDRS}'
volumeMounts:
- name: cni-cfg
mountPath: /etc/cni/net.d
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
kind: KubeadmControlPlane
apiVersion: controlplane.cluster.x-k8s.io/v1alpha4
metadata:
name: "${CLUSTER_NAME}-control-plane"
spec:
kubeadmConfigSpec:
clusterConfiguration:
apiServer:
# host.docker.internal is required by kubetest when running on MacOS because of the way ports are proxied.
certSANs: [localhost, "::", "::1", host.docker.internal]
initConfiguration:
localAPIEndpoint:
advertiseAddress: '::'
bindPort: 6443
nodeRegistration:
kubeletExtraArgs:
node-ip: "::"
joinConfiguration:
nodeRegistration:
kubeletExtraArgs:
node-ip: "::"
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
bases:
- ../bases/cluster-with-kcp.yaml
- ../bases/md.yaml
- ../bases/crs.yaml

patchesStrategicMerge:
- ./md-ipv6.yaml
- ./kcp-ipv6.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
apiVersion: bootstrap.cluster.x-k8s.io/v1alpha4
kind: KubeadmConfigTemplate
metadata:
name: "${CLUSTER_NAME}-md-0"
spec:
template:
spec:
initConfiguration:
nodeRegistration:
kubeletExtraArgs:
node-ip: "::"
joinConfiguration:
nodeRegistration:
kubeletExtraArgs:
node-ip: "::"
1 change: 1 addition & 0 deletions test/e2e/e2e_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ func setupBootstrapCluster(config *clusterctl.E2EConfig, scheme *runtime.Scheme,
Name: config.ManagementClusterName,
RequiresDockerSock: config.HasDockerProvider(),
Images: config.Images,
IPFamily: config.GetVariable(IPFamily),
})
Expect(clusterProvider).ToNot(BeNil(), "Failed to create a bootstrap cluster")

Expand Down
Loading