diff --git a/pkg/agent/flannel/setup_test.go b/pkg/agent/flannel/setup_test.go new file mode 100644 index 00000000000..32a9368f7a2 --- /dev/null +++ b/pkg/agent/flannel/setup_test.go @@ -0,0 +1,83 @@ +package flannel + +import ( + "io/ioutil" + "net" + "regexp" + "strings" + "testing" + + "github.com/rancher/k3s/pkg/daemons/config" +) + +func stringToCIDR(s string) []*net.IPNet { + var netCidrs []*net.IPNet + for _, v := range strings.Split(s, ",") { + _, parsed, _ := net.ParseCIDR(v) + netCidrs = append(netCidrs, parsed) + } + return netCidrs +} + +func Test_findNetMode(t *testing.T) { + tests := []struct { + name string + args string + want int + wantErr bool + }{ + {"dual-stack", "10.42.0.0/16,2001:cafe:22::/56", ipv4 + ipv6, false}, + {"ipv4 only", "10.42.0.0/16", ipv4, false}, + {"ipv6 only", "2001:cafe:42:0::/56", ipv6, false}, + {"wrong input", "wrong", 0, true}, + } + for _, tt := range tests { + + t.Run(tt.name, func(t *testing.T) { + netCidrs := stringToCIDR(tt.args) + got, err := findNetMode(netCidrs) + if (err != nil) != tt.wantErr { + t.Errorf("findNetMode() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("findNetMode() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_createFlannelConf(t *testing.T) { + tests := []struct { + name string + args string + wantConfig []string + wantErr bool + }{ + {"dual-stack", "10.42.0.0/16,2001:cafe:22::/56", []string{"\"Network\": \"10.42.0.0/16\"", "\"IPv6Network\": \"2001:cafe:22::/56\"", "\"EnableIPv6\": true"}, false}, + {"ipv4 only", "10.42.0.0/16", []string{"\"Network\": \"10.42.0.0/16\"", "\"IPv6Network\": \"::/0\"", "\"EnableIPv6\": false"}, false}, + } + var containerd = config.Containerd{} + for _, tt := range tests { + var agent = config.Agent{} + agent.ClusterCIDR = stringToCIDR(tt.args)[0] + agent.ClusterCIDRs = stringToCIDR(tt.args) + var nodeConfig = &config.Node{Docker: false, ContainerRuntimeEndpoint: "", NoFlannel: false, SELinux: false, FlannelBackend: "vxlan", FlannelConf: "test_file", FlannelConfOverride: false, FlannelIface: nil, Containerd: containerd, Images: "", AgentConfig: agent, Token: "", Certificate: nil, ServerHTTPSPort: 0} + + t.Run(tt.name, func(t *testing.T) { + if err := createFlannelConf(nodeConfig); (err != nil) != tt.wantErr { + t.Errorf("createFlannelConf() error = %v, wantErr %v", err, tt.wantErr) + } + data, err := ioutil.ReadFile("test_file") + if err != nil { + t.Errorf("Something went wrong when reading the flannel config file") + } + for _, config := range tt.wantConfig { + isExist, _ := regexp.Match(config, data) + if !isExist { + t.Errorf("Config is wrong, %s is not present", config) + } + } + }) + } +} diff --git a/pkg/cli/server/server.go b/pkg/cli/server/server.go index 9baa5b20aab..c76fe6ee2c3 100644 --- a/pkg/cli/server/server.go +++ b/pkg/cli/server/server.go @@ -467,8 +467,8 @@ func validateNetworkConfiguration(serverConfig server.Config) error { return errors.Wrap(err, "failed to validate cluster-dns") } - if (serverConfig.ControlConfig.FlannelBackend != "none" || serverConfig.ControlConfig.DisableNPC == false) && (dualCluster || dualService) { - return errors.New("flannel CNI and network policy enforcement are not compatible with dual-stack operation; server must be restarted with --flannel-backend=none --disable-network-policy and an alternative CNI plugin deployed") + if (serverConfig.ControlConfig.DisableNPC == false) && (dualCluster || dualService) { + return errors.New("network policy enforcement is not compatible with dual-stack operation; server must be restarted with --disable-network-policy") } if dualDNS == true { return errors.New("dual-stack cluster-dns is not supported") diff --git a/tests/integration/dual_stack_int_test.go b/tests/integration/dual_stack_int_test.go new file mode 100644 index 00000000000..44607707a0d --- /dev/null +++ b/tests/integration/dual_stack_int_test.go @@ -0,0 +1,55 @@ +package integration + +import ( + "strings" + "testing" + + . "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/reporters" + . "github.com/onsi/gomega" + testutil "github.com/rancher/k3s/tests/util" +) + +var dualStackServerArgs = []string{"--cluster-init", "--cluster-cidr 10.42.0.0/16,2001:cafe:42:0::/56", "--service-cidr 10.43.0.0/16,2001:cafe:42:1::/112"} +var _ = BeforeSuite(func() { + if !testutil.IsExistingServer() { + var err error + server, err = testutil.K3sStartServer(dualStackServerArgs...) + Expect(err).ToNot(HaveOccurred()) + } +}) + +var _ = Describe("dual stack", func() { + BeforeEach(func() { + if testutil.IsExistingServer() && !testutil.ServerArgsPresent(dualStackServerArgs) { + Skip("Test needs k3s server with: " + strings.Join(dualStackServerArgs, " ")) + } + }) + When("a ipv4 and ipv6 cidr is present", func() { + It("starts up with no problems", func() { + Eventually(func() (string, error) { + return testutil.K3sCmd("kubectl", "get", "pods", "-A") + }, "90s", "1s").Should(MatchRegexp("kube-system.+traefik.+1\\/1.+Running")) + }) + It("creates pods with two IPs", func() { + podname, err := testutil.K3sCmd("kubectl", "get", "pods", "-nkube-system", "-ojsonpath={.items[?(@.metadata.labels.app\\.kubernetes\\.io/name==\"traefik\")].metadata.name}") + Expect(err).NotTo(HaveOccurred()) + result, err := testutil.K3sCmd("kubectl", "exec", podname, "-nkube-system", "--", "ip", "a") + Expect(result).To(ContainSubstring("2001:cafe:42:")) + Expect(err).NotTo(HaveOccurred()) + }) + }) +}) + +var _ = AfterSuite(func() { + if !testutil.IsExistingServer() { + Expect(testutil.K3sKillServer(server)).To(Succeed()) + } +}) + +func Test_IntegrationDualStack(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecsWithDefaultAndCustomReporters(t, "Dual-Stack Suite", []Reporter{ + reporters.NewJUnitReporter("/tmp/results/junit-ls.xml"), + }) +}