github.com/rancher/elemental/tests@v0.0.0-20240517125144-ae048c615b3f/e2e/multi-cluster_test.go (about)

     1  /*
     2  Copyright © 2022 - 2024 SUSE LLC
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7      http://www.apache.org/licenses/LICENSE-2.0
     8  Unless required by applicable law or agreed to in writing, software
     9  distributed under the License is distributed on an "AS IS" BASIS,
    10  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  See the License for the specific language governing permissions and
    12  limitations under the License.
    13  */
    14  
    15  package e2e_test
    16  
    17  import (
    18  	"os"
    19  	"os/exec"
    20  	"strconv"
    21  	"sync"
    22  	"time"
    23  
    24  	. "github.com/onsi/ginkgo/v2"
    25  	. "github.com/onsi/gomega"
    26  	"github.com/rancher-sandbox/ele-testhelpers/kubectl"
    27  	"github.com/rancher-sandbox/ele-testhelpers/rancher"
    28  	"github.com/rancher-sandbox/ele-testhelpers/tools"
    29  	"github.com/rancher/elemental/tests/e2e/helpers/elemental"
    30  )
    31  
    32  var _ = Describe("E2E - Bootstrapping nodes", Label("multi-cluster"), func() {
    33  	// Define some variables
    34  	const seedImageName = "seed-image-multi"
    35  	const machineRegName = "machine-registration-multi"
    36  
    37  	var (
    38  		basePatterns []YamlPattern
    39  		globalNodeID int
    40  		wg           sync.WaitGroup
    41  	)
    42  
    43  	BeforeEach(func() {
    44  
    45  		// Patterns to replace
    46  		basePatterns = []YamlPattern{
    47  			{
    48  				key:   "%K8S_VERSION%",
    49  				value: k8sDownstreamVersion,
    50  			},
    51  			{
    52  				key:   "%SNAP_TYPE%",
    53  				value: snapType,
    54  			},
    55  			{
    56  				key:   "%PASSWORD%",
    57  				value: userPassword,
    58  			},
    59  			{
    60  				key:   "%USER%",
    61  				value: userName,
    62  			},
    63  			{
    64  				key:   "%VM_NAME%",
    65  				value: vmNameRoot,
    66  			},
    67  		}
    68  	})
    69  
    70  	It("Configure Libvirt", func() {
    71  		// Report to Qase
    72  		testCaseID = 68
    73  
    74  		By("Starting default network", func() {
    75  			// Don't check return code, as the default network could be already removed
    76  			for _, c := range []string{"net-destroy", "net-undefine"} {
    77  				_ = exec.Command("sudo", "virsh", c, "default").Run()
    78  			}
    79  
    80  			// Wait a bit between virsh commands
    81  			time.Sleep(1 * time.Minute)
    82  			err := exec.Command("sudo", "virsh", "net-create", netDefaultFileName).Run()
    83  			Expect(err).To(Not(HaveOccurred()))
    84  		})
    85  	})
    86  
    87  	It("Configure and create ISO image", func() {
    88  		// Report to Qase
    89  		testCaseID = 38
    90  
    91  		By("Adding MachineRegistration", func() {
    92  			// Set temporary file
    93  			registrationTmp, err := tools.CreateTemp("machineRegistration")
    94  			Expect(err).To(Not(HaveOccurred()))
    95  			defer os.Remove(registrationTmp)
    96  
    97  			// Save original file as it may have to be modified twice
    98  			err = tools.CopyFile(registrationYaml, registrationTmp)
    99  			Expect(err).To(Not(HaveOccurred()))
   100  
   101  			// Create Yaml file
   102  			for _, p := range basePatterns {
   103  				err := tools.Sed(p.key, p.value, registrationTmp)
   104  				Expect(err).To(Not(HaveOccurred()))
   105  			}
   106  
   107  			// Apply to k8s
   108  			Eventually(func() error {
   109  				return kubectl.Apply(clusterNS, registrationTmp)
   110  			}, tools.SetTimeout(2*time.Minute), 10*time.Second).ShouldNot(HaveOccurred())
   111  
   112  			// Check that the machine registration is correctly created
   113  			CheckCreatedRegistration(clusterNS, "machine-registration-multi")
   114  		})
   115  
   116  		By("Downloading MachineRegistration file", func() {
   117  			// Download the new YAML installation config file
   118  			tokenURL, err := kubectl.RunWithoutErr("get", "MachineRegistration",
   119  				"--namespace", clusterNS, machineRegName,
   120  				"-o", "jsonpath={.status.registrationURL}")
   121  			Expect(err).To(Not(HaveOccurred()))
   122  
   123  			Eventually(func() error {
   124  				return tools.GetFileFromURL(tokenURL, installConfigYaml, false)
   125  			}, tools.SetTimeout(2*time.Minute), 10*time.Second).ShouldNot(HaveOccurred())
   126  		})
   127  
   128  		By("Creating ISO from SeedImage", func() {
   129  			// Wait for list of OS versions to be populated
   130  			WaitForOSVersion(clusterNS)
   131  
   132  			// Get OSVersion name
   133  			OSVersion, err := exec.Command(getOSScript, os2Test, "true").Output()
   134  			Expect(err).To(Not(HaveOccurred()))
   135  			Expect(OSVersion).To(Not(BeEmpty()))
   136  
   137  			// Extract container image URL
   138  			baseImageURL, err := elemental.GetImageURI(clusterNS, string(OSVersion))
   139  			Expect(err).To(Not(HaveOccurred()))
   140  			Expect(baseImageURL).To(Not(BeEmpty()))
   141  
   142  			// Set temporary file
   143  			seedImageTmp, err := tools.CreateTemp("seedimage")
   144  			Expect(err).To(Not(HaveOccurred()))
   145  			defer os.Remove(seedImageTmp)
   146  
   147  			// Save original file as it may have to be modified twice
   148  			err = tools.CopyFile(seedImageYaml, seedImageTmp)
   149  			Expect(err).To(Not(HaveOccurred()))
   150  
   151  			seedImagePatterns := []YamlPattern{
   152  				{
   153  					key:   "%BASE_IMAGE%",
   154  					value: baseImageURL,
   155  				},
   156  			}
   157  			patterns := append(basePatterns, seedImagePatterns...)
   158  
   159  			// Create Yaml file
   160  			for _, p := range patterns {
   161  				err := tools.Sed(p.key, p.value, seedImageTmp)
   162  				Expect(err).To(Not(HaveOccurred()))
   163  			}
   164  
   165  			// Apply to k8s
   166  			err = kubectl.Apply(clusterNS, seedImageTmp)
   167  			Expect(err).To(Not(HaveOccurred()))
   168  		})
   169  	})
   170  
   171  	It("Downloading ISO built by SeedImage", func() {
   172  		// Report to Qase
   173  		testCaseID = 38
   174  
   175  		DownloadBuiltISO(clusterNS, seedImageName, "../../elemental-multi.iso")
   176  	})
   177  
   178  	It("Create clusters and deploy nodes", func() {
   179  		// Report to Qase
   180  		testCaseID = 9
   181  
   182  		// Loop on all clusters to create
   183  		for clusterIndex := 1; clusterIndex <= numberOfClusters; clusterIndex++ {
   184  			createdClusterName := clusterName + "-" + strconv.Itoa(clusterIndex)
   185  
   186  			// Patterns to replace
   187  			addPatterns := []YamlPattern{
   188  				{
   189  					key:   "%CLUSTER_NAME%",
   190  					value: createdClusterName,
   191  				},
   192  			}
   193  			patterns := append(basePatterns, addPatterns...)
   194  
   195  			By("Creating cluster "+createdClusterName, func() {
   196  				// Set temporary file
   197  				clusterTmp, err := tools.CreateTemp(createdClusterName)
   198  				Expect(err).To(Not(HaveOccurred()))
   199  				defer os.Remove(clusterTmp)
   200  
   201  				// Save original file as it may have to be modified twice
   202  				err = tools.CopyFile(clusterYaml, clusterTmp)
   203  				Expect(err).To(Not(HaveOccurred()))
   204  
   205  				// Create Yaml file
   206  				for _, p := range patterns {
   207  					err := tools.Sed(p.key, p.value, clusterTmp)
   208  					Expect(err).To(Not(HaveOccurred()))
   209  				}
   210  
   211  				// Apply to k8s
   212  				err = kubectl.Apply(clusterNS, clusterTmp)
   213  				Expect(err).To(Not(HaveOccurred()))
   214  
   215  				// Check that the cluster is correctly created
   216  				CheckCreatedCluster(clusterNS, createdClusterName)
   217  			})
   218  
   219  			By("Creating cluster selector for cluster "+createdClusterName, func() {
   220  				// Set temporary file
   221  				selectorTmp, err := tools.CreateTemp("selector")
   222  				Expect(err).To(Not(HaveOccurred()))
   223  				defer os.Remove(selectorTmp)
   224  
   225  				// Save original file as it may have to be modified twice
   226  				err = tools.CopyFile(selectorYaml, selectorTmp)
   227  				Expect(err).To(Not(HaveOccurred()))
   228  
   229  				// Create Yaml file
   230  				for _, p := range patterns {
   231  					err := tools.Sed(p.key, p.value, selectorTmp)
   232  					Expect(err).To(Not(HaveOccurred()))
   233  				}
   234  
   235  				// Apply to k8s
   236  				err = kubectl.Apply(clusterNS, selectorTmp)
   237  				Expect(err).To(Not(HaveOccurred()))
   238  
   239  				// Check that the selector template is correctly created
   240  				CheckCreatedSelectorTemplate(clusterNS, "selector-"+createdClusterName)
   241  			})
   242  
   243  			// Loop on node provisionning
   244  			for nodeIndex := 1; nodeIndex <= 3; nodeIndex++ {
   245  				// Incremente global node index
   246  				globalNodeID++
   247  
   248  				// Set node hostname
   249  				hostName := elemental.SetHostname(vmNameRoot+"-"+createdClusterName, nodeIndex)
   250  				Expect(hostName).To(Not(BeNil()))
   251  
   252  				// Add node in network configuration
   253  				err := rancher.AddNode(netDefaultFileName, hostName, globalNodeID)
   254  				Expect(err).To(Not(HaveOccurred()))
   255  
   256  				// Get generated MAC address
   257  				_, macAdrs := GetNodeInfo(hostName)
   258  				Expect(macAdrs).To(Not(BeNil()))
   259  
   260  				wg.Add(1)
   261  				go func(s, h, m string) {
   262  					defer wg.Done()
   263  					defer GinkgoRecover()
   264  
   265  					By("Installing node "+h+" on cluster "+createdClusterName, func() {
   266  						// Execute node deployment in parallel
   267  						err := exec.Command(s, h, m).Run()
   268  						Expect(err).To(Not(HaveOccurred()))
   269  					})
   270  				}(installVMScript, hostName, macAdrs)
   271  			}
   272  
   273  			// Wait for all parallel jobs
   274  			wg.Wait()
   275  
   276  			// Add needed label on provisionned nodes
   277  			for nodeIndex := 1; nodeIndex <= 3; nodeIndex++ {
   278  				// Set node hostname
   279  				hostName := elemental.SetHostname(vmNameRoot+"-"+createdClusterName, nodeIndex)
   280  				Expect(hostName).To(Not(BeNil()))
   281  
   282  				// Get node's IP
   283  				ip := GetNodeIP(hostName)
   284  
   285  				// Get MachineInventory name
   286  				nodeName, err := kubectl.RunWithoutErr("get", "MachineInventory",
   287  					"--namespace", clusterNS,
   288  					"-o", "jsonpath={.items[?(@.metadata.annotations.elemental\\.cattle\\.io/registration-ip==\""+ip+"\")].metadata.name}")
   289  				Expect(err).To(Not(HaveOccurred()))
   290  
   291  				// Add label
   292  				elemental.SetMachineInventoryLabel(clusterNS, nodeName, "clusterName", createdClusterName)
   293  
   294  				// Get node information
   295  				client, _ := GetNodeInfo(hostName)
   296  
   297  				// Restart node(s)
   298  				wg.Add(1)
   299  				go func(h string, cl *tools.Client) {
   300  					defer wg.Done()
   301  					defer GinkgoRecover()
   302  
   303  					By("Restarting "+h+" to add it in cluster "+createdClusterName, func() {
   304  						err := exec.Command("sudo", "virsh", "start", h).Run()
   305  						Expect(err).To(Not(HaveOccurred()))
   306  					})
   307  
   308  					By("Checking "+h+" SSH connection", func() {
   309  						CheckSSH(cl)
   310  					})
   311  				}(hostName, client)
   312  			}
   313  
   314  			// Wait for all parallel jobs
   315  			wg.Wait()
   316  
   317  			By("Waiting for cluster "+createdClusterName+" to be Active", func() {
   318  				WaitCluster(clusterNS, createdClusterName)
   319  			})
   320  		}
   321  
   322  		// Loop on all clusters to check
   323  		for clusterIndex := 1; clusterIndex <= numberOfClusters; clusterIndex++ {
   324  			createdClusterName := clusterName + "-" + strconv.Itoa(clusterIndex)
   325  
   326  			// Do a final check on all created clusters to validate them
   327  			// NOTE: do it in parallel to speed-up the checking process
   328  			wg.Add(1)
   329  			go func(ns, c string) {
   330  				defer wg.Done()
   331  				defer GinkgoRecover()
   332  				By("Waiting for cluster "+c+" to be Active", func() {
   333  					WaitCluster(ns, c)
   334  				})
   335  			}(clusterNS, createdClusterName)
   336  
   337  			// Wait for all parallel jobs
   338  			wg.Wait()
   339  		}
   340  	})
   341  })