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

[WIP] Add ipv6 support to CAPG #700

Closed
wants to merge 3 commits into from
Closed
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
83 changes: 83 additions & 0 deletions api/v1beta1/gcpcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package v1beta1

import (
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
)
Expand Down Expand Up @@ -94,3 +95,85 @@ type GCPClusterList struct {
func init() {
SchemeBuilder.Register(&GCPCluster{}, &GCPClusterList{})
}

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]
}

192 changes: 192 additions & 0 deletions api/v1beta1/gcpcluster_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/*
Copyright 2022 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 v1beta1

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{"192.168.0.0/16"}, []string{"fd00:100:64::/108"}),
},
{
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",
expectErr: "services: could not parse CIDR",
},
{
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))
})
}
}
4 changes: 4 additions & 0 deletions test/e2e/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ import (
"sigs.k8s.io/cluster-api/util"
)

const (
IPFamily = "IP_FAMILY"
)

func Byf(format string, a ...interface{}) {
By(fmt.Sprintf(format, a...))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
kind: KubeadmControlPlane
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
metadata:
name: "${CLUSTER_NAME}-control-plane"
spec:
kubeadmConfigSpec:
clusterConfiguration:
apiServer:
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/v1beta1
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/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,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