Skip to content

Commit

Permalink
feat: add IPv6-only clusters
Browse files Browse the repository at this point in the history
  • Loading branch information
rainest committed Jul 27, 2023
1 parent cd12b01 commit 760bccc
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 0 deletions.
8 changes: 8 additions & 0 deletions internal/cmd/ktf/environments.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func init() { //nolint:gochecknoinits
// cluster configurations
environmentsCreateCmd.PersistentFlags().String("kubernetes-version", "", "which kubernetes version to use (default: latest for driver)")
environmentsCreateCmd.PersistentFlags().Bool("cni-calico", false, "use Calico for cluster CNI instead of the default CNI")
environmentsCreateCmd.PersistentFlags().Bool("ipv6-only", false, "only use IPv6")

// addon configurations
environmentsCreateCmd.PersistentFlags().StringArray("addon", nil, "name of an addon to deploy to the testing environment's cluster")
Expand Down Expand Up @@ -84,6 +85,10 @@ var environmentsCreateCmd = &cobra.Command{
useCalicoCNI, err := cmd.PersistentFlags().GetBool("cni-calico")
cobra.CheckErr(err)

// check if IPv6 was requested
useIPv6Only, err := cmd.PersistentFlags().GetBool("ipv6-only")
cobra.CheckErr(err)

// setup the new environment
builder := environments.NewBuilder()
if !useGeneratedName {
Expand All @@ -92,6 +97,9 @@ var environmentsCreateCmd = &cobra.Command{
if useCalicoCNI {
builder = builder.WithCalicoCNI()
}
if useIPv6Only {
builder = builder.WithIPv6Only()
}
if kubernetesVersion != "" {
version, err := semver.Parse(strings.TrimPrefix(kubernetesVersion, "v"))
cobra.CheckErr(err)
Expand Down
2 changes: 2 additions & 0 deletions pkg/clusters/addons/metallb/metallb.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ func (a *addon) DumpDiagnostics(context.Context, clusters.Cluster) (map[string][
// -----------------------------------------------------------------------------

var (
// TODO these obviously don't work for a v6-only cluster, but handling that requires more stuff in the cluster
// interface to inform about its network stack
defaultStartIP = net.ParseIP("0.0.0.100")
defaultEndIP = net.ParseIP("0.0.0.250")
metalManifest = "https://github.com/metallb/metallb/config/native?ref=v0.13.10&timeout=2m"
Expand Down
13 changes: 13 additions & 0 deletions pkg/clusters/types/kind/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type Builder struct {
configPath *string
configReader io.Reader
calicoCNI bool
ipv6Only bool
}

// NewBuilder provides a new *Builder object.
Expand Down Expand Up @@ -74,6 +75,12 @@ func (b *Builder) WithCalicoCNI() *Builder {
return b
}

// WithIPv6Only configures KIND to only use IPv6.
func (b *Builder) WithIPv6Only() *Builder {
b.ipv6Only = true
return b
}

// Build creates and configures clients for a Kind-based Kubernetes clusters.Cluster.
func (b *Builder) Build(ctx context.Context) (clusters.Cluster, error) {
deployArgs := make([]string, 0)
Expand All @@ -92,6 +99,12 @@ func (b *Builder) Build(ctx context.Context) (clusters.Cluster, error) {
deployArgs = append(deployArgs, "--wait", "1s")
}

if b.ipv6Only {
if err := b.useIPv6Only(); err != nil {
return nil, fmt.Errorf("failed configuring IPv6-only networking: %w", err)
}
}

var stdin io.Reader
if b.configPath != nil {
deployArgs = append(deployArgs, "--config", *b.configPath)
Expand Down
32 changes: 32 additions & 0 deletions pkg/clusters/types/kind/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,38 @@ func (b *Builder) disableDefaultCNI() error {
return nil
}

func (b *Builder) useIPv6Only() error {
if err := b.ensureConfigFile(); err != nil {
return err
}

configYAML, err := os.ReadFile(*b.configPath)
if err != nil {
return fmt.Errorf("failed reading kind config from %s: %w", *b.configPath, err)
}

kindConfig := v1alpha4.Cluster{}
if err := yaml.Unmarshal(configYAML, &kindConfig); err != nil {
return fmt.Errorf("failed unmarshalling kind config: %w", err)
}

kindConfig.Networking.IPFamily = v1alpha4.IPv6Family
// For Windows/OS X Docker compatibility:
// https://kind.sigs.k8s.io/docs/user/configuration/#ip-family
kindConfig.Networking.APIServerAddress = "127.0.0.1"

configYAML, err = yaml.Marshal(kindConfig)
if err != nil {
return fmt.Errorf("failed marshalling kind config: %w", err)
}

err = os.WriteFile(*b.configPath, configYAML, 0o600) //nolint:gomnd
if err != nil {
return fmt.Errorf("failed writing kind config %s: %w", *b.configPath, err)
}
return nil
}

// exportLogs dumps a kind cluster logs to the specified directory
func exportLogs(ctx context.Context, name string, outDir string) error {
args := []string{"export", "logs", outDir, "--name", name}
Expand Down
14 changes: 14 additions & 0 deletions pkg/environments/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type Builder struct {
clusterBuilder clusters.Builder
kubernetesVersion *semver.Version
calicoCNI bool
ipv6Only bool
}

// NewBuilder generates a new empty Builder for creating Environments.
Expand Down Expand Up @@ -78,6 +79,12 @@ func (b *Builder) WithCalicoCNI() *Builder {
return b
}

// WithIPv6Only configures KIND to only use IPv6.
func (b *Builder) WithIPv6Only() *Builder {
b.ipv6Only = true
return b
}

// Build is a blocking call to construct the configured Environment and it's
// underlying Kubernetes cluster. The amount of time that it blocks depends
// entirely on the underlying clusters.Cluster implementation that was requested.
Expand All @@ -88,6 +95,10 @@ func (b *Builder) Build(ctx context.Context) (env Environment, err error) {
return nil, fmt.Errorf("trying to deploy Calico CNI on an existing cluster is not currently supported")
}

if b.ipv6Only && b.existingCluster != nil {
return nil, fmt.Errorf("trying to configure IPv6 only on an existing cluster is not currently supported")
}

if b.existingCluster != nil && b.clusterBuilder != nil {
return nil, fmt.Errorf("Environment cannot specify both existingCluster and clusterBuilder")
}
Expand All @@ -114,6 +125,9 @@ func (b *Builder) Build(ctx context.Context) (env Environment, err error) {
if b.calicoCNI {
builder.WithCalicoCNI()
}
if b.ipv6Only {
builder.WithIPv6Only()
}
cluster, err = builder.Build(ctx)
if err != nil {
return nil, err
Expand Down
39 changes: 39 additions & 0 deletions test/integration/ipv6_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//go:build integration_tests

package integration

import (
"net"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/kong/kubernetes-testing-framework/pkg/clusters/addons/metallb"
"github.com/kong/kubernetes-testing-framework/pkg/environments"
)

func TestKindClusterWithIPv6(t *testing.T) {
t.Parallel()

t.Log("configuring the test environment with IPv6 only enabled")
builder := environments.NewBuilder().WithIPv6Only().WithAddons(metallb.New())

t.Log("building the testing environment and Kubernetes cluster")
env, err := builder.Build(ctx)
require.NoError(t, err)

t.Log("waiting for the testing environment to be ready")
require.NoError(t, <-env.WaitForReady(ctx))
defer func() { assert.NoError(t, env.Cleanup(ctx)) }()

endpoints, err := env.Cluster().Client().CoreV1().Endpoints(corev1.NamespaceDefault).Get(ctx, "kubernetes", metav1.GetOptions{})
for _, subset := range endpoints.Subsets {
for _, addr := range subset.Addresses {
parsed := net.ParseIP(addr.IP)
require.Nil(t, parsed.To4())
}
}
}

0 comments on commit 760bccc

Please sign in to comment.