diff --git a/tests/e2e/mixedosbgp/Vagrantfile b/tests/e2e/mixedosbgp/Vagrantfile new file mode 100644 index 0000000000..61ba3da434 --- /dev/null +++ b/tests/e2e/mixedosbgp/Vagrantfile @@ -0,0 +1,144 @@ +ENV['VAGRANT_NO_PARALLEL'] = 'no' +NODE_ROLES = (ENV['E2E_NODE_ROLES'] || +["server-0", "linux-agent-0", "windows-agent-0" ]) +NODE_BOXES = (ENV['E2E_NODE_BOXES'] || +['generic/ubuntu2004', 'generic/ubuntu2004', 'jborean93/WindowsServer2022']) +GITHUB_BRANCH = (ENV['E2E_GITHUB_BRANCH'] || "master") +RELEASE_VERSION = (ENV['E2E_RELEASE_VERSION'] || "") +NODE_CPUS = (ENV['E2E_NODE_CPUS'] || 2).to_i +NODE_MEMORY = (ENV['E2E_NODE_MEMORY'] || 3072).to_i +# Virtualbox >= 6.1.28 require `/etc/vbox/network.conf` for expanded private networks +NETWORK_PREFIX = "10.10.10" +install_type = "" + +def provision(vm, role, role_num, node_num) + vm.box = NODE_BOXES[node_num] + vm.hostname = role + # An expanded netmask is required to allow VM<-->VM communication, virtualbox defaults to /32 + node_ip = "#{NETWORK_PREFIX}.#{100+node_num}" + vm.network "private_network", ip: node_ip, netmask: "255.255.255.0" + + vagrant_defaults = '../vagrantdefaults.rb' + load vagrant_defaults if File.exists?(vagrant_defaults) + + defaultOSConfigure(vm) + + if role.include?("windows") + vm.provision "shell", path: "../scripts/install-bgp.ps1" + if !RELEASE_VERSION.empty? + install_type = "Version=#{RELEASE_VERSION}" + else + vm.provision "shell", path: "../scripts/latest_commit.ps1", args: [GITHUB_BRANCH, "./rke2_commits.txt"] + install_type = "Commit=(Get-Content -TotalCount 1 ./rke2_commits.txt)" + end + else + if !RELEASE_VERSION.empty? + install_type = "INSTALL_RKE2_VERSION=#{RELEASE_VERSION}" + else + # Grabs the last 5 commit SHA's from the given branch, then purges any commits that do not have a passing CI build + vm.provision "shell", path: "../scripts/latest_commit.sh", args: [GITHUB_BRANCH, "/tmp/rke2_commits"] + install_type = "INSTALL_RKE2_COMMIT=$(head\ -n\ 1\ /tmp/rke2_commits)" + end + vm.provision "shell", inline: "ping -c 2 rke2.io" + vm.provision "Create Calico Manifest", type: "shell", path: "../scripts/calico_manifestbgp.sh", args: [ "#{NETWORK_PREFIX}.1" ] + end + + if role.include?("server") && role_num == 0 + vm.provision :rke2, run: 'once' do |rke2| + rke2.env = %W[INSTALL_RKE2_TYPE=server #{install_type}] + rke2.config_mode = '0644' # side-step https://github.com/k3s-io/k3s/issues/4321 + rke2.config = <<~YAML + write-kubeconfig-mode: '0644' + node-external-ip: #{NETWORK_PREFIX}.100 + node-ip: #{NETWORK_PREFIX}.100 + token: vagrant-rke2 + cni: calico + YAML + end + vm.provision "Install sonobuoy", type: "shell", path: "../scripts/install_sonobuoy.sh" + elsif role.include?("server") && role_num != 0 + vm.provision :rke2, run: 'once' do |rke2| + rke2.env = %W[INSTALL_RKE2_TYPE=server #{install_type}] + rke2.config_mode = '0644' # side-step https://github.com/k3s-io/k3s/issues/4321 + rke2.config = <<~YAML + write-kubeconfig-mode: '0644' + node-external-ip: #{node_ip} + node-ip: #{node_ip} + server: https://#{NETWORK_PREFIX}.100:9345 + token: vagrant-rke2 + cni: calico + YAML + end + end + if role.include?("linux-agent") + vm.provision :rke2, run: 'once' do |rke2| + rke2.env = %W[INSTALL_RKE2_TYPE=agent #{install_type}] + rke2.config_mode = '0644' # side-step https://github.com/k3s-io/k3s/issues/4321 + rke2.install_path = false + rke2.config = <<~YAML + write-kubeconfig-mode: '0644' + node-external-ip: #{node_ip} + node-ip: #{node_ip} + server: https://#{NETWORK_PREFIX}.100:9345 + token: vagrant-rke2 + YAML + end + end + if role.include?("windows-agent") + if !vm.box.match?(/Windows.*2022/) && !vm.box.match?(/Windows.*2019/) + puts "invalid box: " + vm.box + " found for windows agent" + abort + end + + # For Windows GUI on virtualbox + # vm.provider "virtualbox" do |v| + # v.gui = true + # v.customize ["modifyvm", :id, "--vram", 128] + # v.customize ["modifyvm", :id, "--clipboard", "bidirectional"] + # v.customize ["modifyvm", :id, "--accelerate3d", "on"] + # v.customize ["modifyvm", :id, "--accelerate2dvideo", "on"] + # end + # If using libvirt, use virt-viewer for GUI after bring up the node + vm.provision :rke2, run: 'once' do |rke2| + rke2.env = [install_type] + rke2.config_mode = '0644' # side-step https://github.com/k3s-io/k3s/issues/4321 + # rke2.skip_start = true + rke2.config = <<~YAML + node-external-ip: #{node_ip} + node-ip: #{node_ip} + server: https://#{NETWORK_PREFIX}.100:9345 + token: vagrant-rke2 + YAML + end + end +end + +Vagrant.configure("2") do |config| + config.vagrant.plugins = ["vagrant-rke2", "vagrant-reload"] + # For windows, just use the password not the private key + config.ssh.password = "vagrant" + # Default provider is libvirt, virtualbox is only provided as a backup + config.vm.provider "libvirt" do |v| + v.cpus = NODE_CPUS + v.memory = NODE_MEMORY + end + config.vm.provider "virtualbox" do |v| + v.cpus = NODE_CPUS + v.memory = NODE_MEMORY + v.linked_clone = false + end + + if NODE_ROLES.kind_of?(String) + NODE_ROLES = NODE_ROLES.split(" ", -1) + end + if NODE_BOXES.kind_of?(String) + NODE_BOXES = NODE_BOXES.split(" ", -1) + end + + NODE_ROLES.each_with_index do |name, i| + config.vm.define name do |node| + role_num = name.split("-", -1).pop.to_i + provision(node.vm, name, role_num, i) + end + end +end diff --git a/tests/e2e/mixedosbgp/mixedosbgp_test.go b/tests/e2e/mixedosbgp/mixedosbgp_test.go new file mode 100644 index 0000000000..4bf9c223dd --- /dev/null +++ b/tests/e2e/mixedosbgp/mixedosbgp_test.go @@ -0,0 +1,185 @@ +package mixedosbgp + +import ( + "flag" + "fmt" + "os" + "strconv" + "strings" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/rancher/rke2/tests/e2e" +) + +// Valid nodeOS: generic/ubuntu2004, opensuse/Leap-15.3.x86_64 +var nodeOS = flag.String("nodeOS", "generic/ubuntu2004", "operating system for linux nodes") +var serverCount = flag.Int("serverCount", 1, "number of server nodes") +var linuxAgentCount = flag.Int("linuxAgentCount", 1, "number of linux agent nodes") +var windowsAgentCount = flag.Int("windowsAgentCount", 1, "number of windows agent nodes") +var ci = flag.Bool("ci", false, "running on CI") + +const defaultWindowsOS = "jborean93/WindowsServer2022" + +func Test_E2EMixedOSBGPValidation(t *testing.T) { + flag.Parse() + RegisterFailHandler(Fail) + suiteConfig, reporterConfig := GinkgoConfiguration() + RunSpecs(t, "Validate MixedOS BGP Test Suite", suiteConfig, reporterConfig) +} + +func createMixedCluster(nodeOS string, serverCount, linuxAgentCount, windowsAgentCount int) ([]string, []string, []string, error) { + serverNodeNames := []string{} + for i := 0; i < serverCount; i++ { + serverNodeNames = append(serverNodeNames, "server-"+strconv.Itoa(i)) + } + linuxAgentNames := []string{} + for i := 0; i < linuxAgentCount; i++ { + linuxAgentNames = append(linuxAgentNames, "linux-agent-"+strconv.Itoa(i)) + } + windowsAgentNames := []string{} + for i := 0; i < linuxAgentCount; i++ { + windowsAgentNames = append(windowsAgentNames, "linux-agent-"+strconv.Itoa(i)) + } + nodeRoles := strings.Join(serverNodeNames, " ") + " " + strings.Join(linuxAgentNames, " ") + " " + strings.Join(windowsAgentNames, " ") + nodeRoles = strings.TrimSpace(nodeRoles) + nodeBoxes := strings.Repeat(nodeOS+" ", serverCount+linuxAgentCount) + nodeBoxes += strings.Repeat(defaultWindowsOS+" ", windowsAgentCount) + nodeBoxes = strings.TrimSpace(nodeBoxes) + + var testOptions string + for _, env := range os.Environ() { + if strings.HasPrefix(env, "E2E_") { + testOptions += " " + env + } + } + + cmd := fmt.Sprintf("NODE_ROLES=\"%s\" NODE_BOXES=\"%s\" %s vagrant up &> vagrant.log", nodeRoles, nodeBoxes, testOptions) + fmt.Println(cmd) + if _, err := e2e.RunCommand(cmd); err != nil { + fmt.Println("Error Creating Cluster", err) + return nil, nil, nil, err + } + return serverNodeNames, linuxAgentNames, windowsAgentNames, nil +} + +var ( + kubeConfigFile string + serverNodeNames []string + linuxAgentNames []string + windowsAgentNames []string +) + +var _ = ReportAfterEach(e2e.GenReport) +var _ = Describe("Verify Basic Cluster Creation", Ordered, func() { + + It("Starts up with no issues", func() { + var err error + serverNodeNames, linuxAgentNames, windowsAgentNames, err = createMixedCluster(*nodeOS, *serverCount, *linuxAgentCount, *windowsAgentCount) + Expect(err).NotTo(HaveOccurred(), e2e.GetVagrantLog(err)) + fmt.Println("CLUSTER CONFIG") + fmt.Println("OS:", *nodeOS) + fmt.Println("Server Nodes:", serverNodeNames) + fmt.Println("Agent Nodes:", linuxAgentNames) + kubeConfigFile, err = e2e.GenKubeConfigFile(serverNodeNames[0]) + Expect(err).NotTo(HaveOccurred()) + }) + + It("Checks Node Status", func() { + Eventually(func(g Gomega) { + nodes, err := e2e.ParseNodes(kubeConfigFile, false) + g.Expect(err).NotTo(HaveOccurred()) + for _, node := range nodes { + g.Expect(node.Status).Should(Equal("Ready")) + } + }, "420s", "5s").Should(Succeed()) + _, err := e2e.ParseNodes(kubeConfigFile, true) + Expect(err).NotTo(HaveOccurred()) + }) + + It("Checks Pod Status", func() { + Eventually(func(g Gomega) { + pods, err := e2e.ParsePods(kubeConfigFile, false) + g.Expect(err).NotTo(HaveOccurred()) + for _, pod := range pods { + if strings.Contains(pod.Name, "helm-install") { + g.Expect(pod.Status).Should(Equal("Completed"), pod.Name) + } else { + g.Expect(pod.Status).Should(Equal("Running"), pod.Name) + } + } + }, "420s", "5s").Should(Succeed()) + _, err := e2e.ParsePods(kubeConfigFile, true) + Expect(err).NotTo(HaveOccurred()) + }) + It("Verifies internode connectivity over BGP", func() { + _, err := e2e.DeployWorkload("pod_client.yaml", kubeConfigFile) + Expect(err).NotTo(HaveOccurred()) + + _, err = e2e.DeployWorkload("windows_app_deployment.yaml", kubeConfigFile) + Expect(err).NotTo(HaveOccurred()) + + // Wait for the pod_client pods to have an IP + Eventually(func() string { + ips, _ := e2e.PodIPsUsingLabel(kubeConfigFile, "app=client") + return ips[0] + }, "120s", "10s").Should(ContainSubstring("10.42"), "failed getClientIPs") + + // Wait for the windows_app_deployment pods to have an IP (We must wait 250s because it takes time) + Eventually(func() string { + ips, _ := e2e.PodIPsUsingLabel(kubeConfigFile, "app=windows-app") + return ips[0] + }, "620s", "10s").Should(ContainSubstring("10.42"), "failed getClientIPs") + + // Verify there are BGP routes + cmdRoute := "ip route" + for _, node := range append(serverNodeNames, linuxAgentNames...) { + output, err := e2e.RunCmdOnNode(cmdRoute, node) + Expect(err).NotTo(HaveOccurred()) + Expect(output).Should(ContainSubstring("bird")) + } +x + // Test Linux -> Windows communication + fmt.Println("Testing Linux -> Windows communication") + cmd := "kubectl exec svc/client-curl --kubeconfig=" + kubeConfigFile + " -- curl -m7 windows-app-svc:3000" + Eventually(func() (string, error) { + return e2e.RunCommand(cmd) + }, "120s", "3s").Should(ContainSubstring("Welcome to PSTools for K8s Debugging"), "failed cmd: "+cmd) + + // Test Windows -> Linux communication + fmt.Println("Testing Windows -> Linux communication") + cmd = "kubectl exec svc/windows-app-svc --kubeconfig=" + kubeConfigFile + " -- curl -m7 client-curl:8080" + Eventually(func() (string, error) { + return e2e.RunCommand(cmd) + }, "20s", "3s").Should(ContainSubstring("Welcome to nginx!"), "failed cmd: "+cmd) + + }) + It("Runs the mixed os sonobuoy plugin", func() { + cmd := "sonobuoy run --kubeconfig=/etc/rancher/rke2/rke2.yaml --plugin my-sonobuoy-plugins/mixed-workload-e2e/mixed-workload-e2e.yaml --aggregator-node-selector kubernetes.io/os:linux --wait" + res, err := e2e.RunCmdOnNode(cmd, serverNodeNames[0]) + Expect(err).NotTo(HaveOccurred(), "failed output:"+res) + cmd = "sonobuoy retrieve --kubeconfig=/etc/rancher/rke2/rke2.yaml" + testResultTar, err := e2e.RunCmdOnNode(cmd, serverNodeNames[0]) + Expect(err).NotTo(HaveOccurred(), "failed cmd: "+cmd) + cmd = "sonobuoy results " + testResultTar + res, err = e2e.RunCmdOnNode(cmd, serverNodeNames[0]) + Expect(err).NotTo(HaveOccurred(), "failed cmd: "+cmd) + Expect(res).Should(ContainSubstring("Plugin: mixed-workload-e2e\nStatus: passed\n")) + }) + +}) + +var failed bool +var _ = AfterEach(func() { + failed = failed || CurrentSpecReport().Failed() +}) + +var _ = AfterSuite(func() { + if failed && !*ci { + fmt.Println("FAILED!") + } else { + Expect(e2e.DestroyCluster()).To(Succeed()) + Expect(os.Remove(kubeConfigFile)).To(Succeed()) + } +}) diff --git a/tests/e2e/scripts/calico_manifestbgp.sh b/tests/e2e/scripts/calico_manifestbgp.sh new file mode 100644 index 0000000000..b326faad22 --- /dev/null +++ b/tests/e2e/scripts/calico_manifestbgp.sh @@ -0,0 +1,23 @@ +#!/bin/bash +ip4_addr=$1 + +# Override default calico and specify the interface for windows-agent +# by default, the windows-agent use a different interface name than the linux-agent +mkdir -p /var/lib/rancher/rke2/server/manifests + +echo "Creating calico chart" +echo "apiVersion: helm.cattle.io/v1 +kind: HelmChartConfig +metadata: + name: rke2-calico + namespace: kube-system +spec: + valuesContent: |- + installation: + calicoNetwork: + bgp: Enabled + ipPools: + - cidr: 10.42.0.0/16 + encapsulation: None + nodeAddressAutodetectionV4: + canReach: $ip4_addr" >> /var/lib/rancher/rke2/server/manifests/e2e-calico.yaml