github.com/verrazzano/verrazzano@v1.7.1/tests/e2e/backup/rancher/rancher_backup_test.go (about)

     1  // Copyright (c) 2022, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package rancher
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"fmt"
    10  	"github.com/google/uuid"
    11  	"github.com/verrazzano/verrazzano/pkg/constants"
    12  	common "github.com/verrazzano/verrazzano/tests/e2e/backup/helpers"
    13  	"github.com/verrazzano/verrazzano/tests/e2e/pkg/test/framework/metrics"
    14  	"go.uber.org/zap"
    15  	corev1 "k8s.io/api/core/v1"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"net/http"
    18  	"strconv"
    19  	"strings"
    20  	"text/template"
    21  	"time"
    22  
    23  	. "github.com/onsi/ginkgo/v2"
    24  	. "github.com/onsi/gomega"
    25  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    26  	"github.com/verrazzano/verrazzano/tests/e2e/pkg"
    27  	"github.com/verrazzano/verrazzano/tests/e2e/pkg/test/framework"
    28  )
    29  
    30  const (
    31  	shortWaitTimeout     = 10 * time.Minute
    32  	shortPollingInterval = 10 * time.Second
    33  	waitTimeout          = 20 * time.Minute
    34  	pollingInterval      = 30 * time.Second
    35  	rancherPassword      = "rancher@newstack"
    36  	rancherUserPrefix    = "thor"
    37  )
    38  
    39  var rancherPods = []string{"rancher"}
    40  
    41  var beforeSuite = t.BeforeSuiteFunc(func() {
    42  	start := time.Now()
    43  	common.GatherInfo()
    44  	backupPrerequisites()
    45  	metrics.Emit(t.Metrics.With("deployment_elapsed_time", time.Since(start).Milliseconds()))
    46  })
    47  
    48  var _ = BeforeSuite(beforeSuite)
    49  
    50  var afterSuite = t.AfterSuiteFunc(func() {
    51  	start := time.Now()
    52  	cleanUpRancher()
    53  	metrics.Emit(t.Metrics.With("undeployment_elapsed_time", time.Since(start).Milliseconds()))
    54  })
    55  
    56  var _ = AfterSuite(afterSuite)
    57  
    58  var t = framework.NewTestFramework("rancher-backup-operator")
    59  
    60  // CreateSecretFromMap creates opaque rancher secret required for backup/restore
    61  func CreateSecretFromMap(namespace string, name string) error {
    62  	clientset, err := k8sutil.GetKubernetesClientset()
    63  	if err != nil {
    64  		t.Logs.Errorf("Failed to get clientset with error: %v", err)
    65  		return err
    66  	}
    67  
    68  	secretData := make(map[string]string)
    69  	secretData["accessKey"] = common.OciOsAccessKey
    70  	secretData["secretKey"] = common.OciOsAccessSecretKey
    71  
    72  	secret := &corev1.Secret{
    73  		ObjectMeta: metav1.ObjectMeta{
    74  			Name:      name,
    75  			Namespace: namespace,
    76  		},
    77  		Type:       corev1.SecretTypeOpaque,
    78  		StringData: secretData,
    79  	}
    80  
    81  	_, err = clientset.CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{})
    82  	if err != nil {
    83  		t.Logs.Errorf("Error creating secret ", zap.Error(err))
    84  		return err
    85  	}
    86  	return nil
    87  }
    88  
    89  // CreateRancherBackupObject creates rancher backup object to start the backup process
    90  func CreateRancherBackupObject() error {
    91  	var b bytes.Buffer
    92  	template, _ := template.New("rancher-backup").Parse(common.RancherBackup)
    93  	data := common.RancherBackupData{
    94  		RancherBackupName: common.BackupRancherName,
    95  		RancherSecretData: common.RancherObjectStoreData{
    96  			RancherSecretName:                 common.RancherSecretName,
    97  			RancherSecretNamespaceName:        common.VeleroNameSpace,
    98  			RancherObjectStoreBucketName:      common.OciBucketName,
    99  			RancherBackupRegion:               common.BackupRegion,
   100  			RancherObjectStorageNamespaceName: common.OciNamespaceName,
   101  		},
   102  	}
   103  	template.Execute(&b, data)
   104  	err := common.DynamicSSA(context.TODO(), b.String(), t.Logs)
   105  	if err != nil {
   106  		t.Logs.Errorf("Error creating rancher backup object", zap.Error(err))
   107  		return err
   108  	}
   109  	return nil
   110  }
   111  
   112  // CreateRancherRestoreObject creates rancher restore object to start the restore process
   113  func CreateRancherRestoreObject() error {
   114  
   115  	rancherFileName, err := common.GetRancherBackupFileName(common.BackupRancherName, t.Logs)
   116  	if err != nil {
   117  		return err
   118  	}
   119  
   120  	common.RancherBackupFileName = rancherFileName
   121  
   122  	var b bytes.Buffer
   123  	template, _ := template.New("rancher-backup").Parse(common.RancherRestore)
   124  	data := common.RancherRestoreData{
   125  		RancherRestoreName: common.RestoreRancherName,
   126  		BackupFileName:     common.RancherBackupFileName,
   127  		RancherSecretData: common.RancherObjectStoreData{
   128  			RancherSecretName:                 common.RancherSecretName,
   129  			RancherSecretNamespaceName:        common.VeleroNameSpace,
   130  			RancherObjectStoreBucketName:      common.OciBucketName,
   131  			RancherBackupRegion:               common.BackupRegion,
   132  			RancherObjectStorageNamespaceName: common.OciNamespaceName,
   133  		},
   134  	}
   135  	template.Execute(&b, data)
   136  	err = common.DynamicSSA(context.TODO(), b.String(), t.Logs)
   137  	if err != nil {
   138  		t.Logs.Errorf("Error creating rancher backup object", zap.Error(err))
   139  		return err
   140  	}
   141  	t.Logs.Infof("Rancher backup filename = %s", common.RancherBackupFileName)
   142  	return nil
   143  }
   144  
   145  // PopulateRancherUsers is used to populate test users on Rancher
   146  func PopulateRancherUsers(rancherURL string, n int) error {
   147  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
   148  	if err != nil {
   149  		t.Logs.Errorf("Unable to fetch kubeconfig url due to %v", zap.Error(err))
   150  		return err
   151  	}
   152  
   153  	httpClient, err := pkg.GetVerrazzanoHTTPClient(kubeconfigPath)
   154  	if err != nil {
   155  		t.Logs.Errorf("Unable to fetch httpClient due to %v", zap.Error(err))
   156  		return err
   157  	}
   158  
   159  	apiPath := "v3/users"
   160  	rancherUserCreateURL := fmt.Sprintf("%s/%s", rancherURL, apiPath)
   161  	token := common.GetRancherLoginToken(t.Logs)
   162  	if token == "" {
   163  		t.Logs.Errorf("rancher login token is empty")
   164  		return fmt.Errorf("rancher login token is empty")
   165  	}
   166  
   167  	for i := 0; i < n; i++ {
   168  		id := uuid.New().String()
   169  		uniqueID := strings.Split(id, "-")[len(strings.Split(id, "-"))-1]
   170  		fullName := fmt.Sprintf("john-smith-%v", i+1)
   171  		userName := fmt.Sprintf("%s-%v", rancherUserPrefix, uniqueID)
   172  
   173  		var b bytes.Buffer
   174  		template, templateErr := template.New("rancher-user").Parse(common.RancherUserTemplate)
   175  		if templateErr != nil {
   176  			t.Logs.Errorf("Unable to convert template '%v'", templateErr)
   177  			return templateErr
   178  		}
   179  		data := common.RancherUser{
   180  			FullName: strconv.Quote(fullName),
   181  			Username: strconv.Quote(userName),
   182  			Password: strconv.Quote(rancherPassword),
   183  		}
   184  		template.Execute(&b, data)
   185  
   186  		_, err = common.HTTPHelper(httpClient, "POST", rancherUserCreateURL, token, "Bearer", http.StatusCreated, b.Bytes(), t.Logs)
   187  		if err != nil {
   188  			t.Logs.Errorf("Error while retrieving http data %v", zap.Error(err))
   189  			return err
   190  		}
   191  		common.RancherUserNameList = append(common.RancherUserNameList, userName)
   192  		t.Logs.Infof("Successfully created rancher user %v", userName)
   193  	}
   194  
   195  	return nil
   196  }
   197  
   198  // DeleteRancherUsers deletes rancher users
   199  func DeleteRancherUsers(rancherURL string) bool {
   200  	token := common.GetRancherLoginToken(t.Logs)
   201  	if token == "" {
   202  		t.Logs.Errorf("rancher login token is empty")
   203  		return false
   204  	}
   205  	httpClient := pkg.EventuallyVerrazzanoRetryableHTTPClient()
   206  	rancherDeletedIds := make([]int, 0)
   207  	for i := 0; i < len(common.RancherUserNameList); i++ {
   208  		rancherUserDeleteURL := fmt.Sprintf("%s/v3/users/%s", rancherURL, common.RancherUserIDList[i])
   209  		_, err := common.HTTPHelper(httpClient, "DELETE", rancherUserDeleteURL, token, "Bearer", http.StatusOK, nil, t.Logs)
   210  		if err != nil {
   211  			t.Logs.Errorf("Error while retrieving http data %v", zap.Error(err))
   212  			updateUserLists(rancherDeletedIds)
   213  			return false
   214  		}
   215  		t.Logs.Infof("Successfully deleted rancher user '%v' with id '%v' ", common.RancherUserNameList[i], common.RancherUserIDList[i])
   216  		rancherDeletedIds = append(rancherDeletedIds, i)
   217  	}
   218  	return true
   219  }
   220  
   221  // updateUserLists updates the rancher user lists by removing those users that have already been deleted
   222  func updateUserLists(ids []int) {
   223  	for _, id := range ids {
   224  		common.RancherUserNameList = removeItem(common.RancherUserNameList, id)
   225  		common.RancherUserIDList = removeItem(common.RancherUserIDList, id)
   226  	}
   227  }
   228  
   229  // removeItem returns a slice with the item specified by the index removed
   230  func removeItem(s []string, index int) []string {
   231  	ret := make([]string, 0)
   232  	ret = append(ret, s[:index]...)
   233  	return append(ret, s[index+1:]...)
   234  }
   235  
   236  // VerifyRancherUsers gets an existing rancher user
   237  func VerifyRancherUsers(rancherURL string) bool {
   238  	token := common.GetRancherLoginToken(t.Logs)
   239  	if token == "" {
   240  		t.Logs.Errorf("rancher login token is empty")
   241  		return false
   242  	}
   243  	httpClient := pkg.EventuallyVerrazzanoRetryableHTTPClient()
   244  	for i := 0; i < len(common.RancherUserNameList); i++ {
   245  		rancherGetURL := fmt.Sprintf("%s/v3/users?username=%s", rancherURL, common.RancherUserNameList[i])
   246  		parsedJSON, err := common.HTTPHelper(httpClient, "GET", rancherGetURL, token, "Bearer", http.StatusOK, nil, t.Logs)
   247  		if err != nil {
   248  			t.Logs.Errorf("Error while retrieving http data %v", zap.Error(err))
   249  			return false
   250  		}
   251  		if common.RancherUserNameList[i] != fmt.Sprintf("%s", parsedJSON.Path("data.0.username").Data()) {
   252  			t.Logs.Errorf("Fetched Name = '%s', Expected Name = '%s'", common.RancherUserNameList[i], fmt.Sprintf("%s", parsedJSON.Path("data.0.username").Data()))
   253  			return false
   254  		}
   255  		t.Logs.Infof("'%s' found in rancher after restore", common.RancherUserNameList[i])
   256  	}
   257  	return true
   258  }
   259  
   260  // BuildRancherUserIDList gets an existing rancher user
   261  func BuildRancherUserIDList(rancherURL string) bool {
   262  	token := common.GetRancherLoginToken(t.Logs)
   263  	if token == "" {
   264  		t.Logs.Errorf("rancher login token is empty")
   265  		return false
   266  	}
   267  	httpClient := pkg.EventuallyVerrazzanoRetryableHTTPClient()
   268  	for i := 0; i < len(common.RancherUserNameList); i++ {
   269  		rancherGetURL := fmt.Sprintf("%s/v3/users?username=%s", rancherURL, common.RancherUserNameList[i])
   270  		parsedJSON, err := common.HTTPHelper(httpClient, "GET", rancherGetURL, token, "Bearer", http.StatusOK, nil, t.Logs)
   271  		if err != nil {
   272  			t.Logs.Errorf("Error while retrieving http data %v", zap.Error(err))
   273  			return false
   274  		}
   275  		common.RancherUserIDList = append(common.RancherUserIDList, fmt.Sprintf("%s", parsedJSON.Path("data.0.id").Data()))
   276  		t.Logs.Infof("'%s' found in rancher", common.RancherUserNameList[i])
   277  	}
   278  	return true
   279  }
   280  
   281  // 'It' Wrapper to only run spec if the Velero is supported on the current Verrazzano version
   282  func WhenRancherBackupInstalledIt(description string, f func()) {
   283  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
   284  	if err != nil {
   285  		t.It(description, func() {
   286  			Fail(fmt.Sprintf("Failed to get default kubeconfig path: %s", err.Error()))
   287  		})
   288  	}
   289  	supported, err := pkg.IsVerrazzanoMinVersion("1.4.0", kubeconfigPath)
   290  	if err != nil {
   291  		t.It(description, func() {
   292  			Fail(fmt.Sprintf("Failed to check Verrazzano version 1.4.0: %s", err.Error()))
   293  		})
   294  	}
   295  	if supported {
   296  		t.It(description, f)
   297  	} else {
   298  		t.Logs.Infof("Skipping check '%v', the Velero is not supported", description)
   299  	}
   300  }
   301  
   302  // checkPodsRunning checks whether the pods are ready in a given namespace
   303  func checkPodsRunning(namespace string, expectedPods []string) bool {
   304  	result, err := pkg.SpecificPodsRunning(namespace, "app=rancher")
   305  	if err != nil {
   306  		AbortSuite(fmt.Sprintf("One or more pods are not running in the namespace: %v, error: %v", namespace, err))
   307  	}
   308  	return result
   309  }
   310  
   311  // Run as part of BeforeSuite
   312  func backupPrerequisites() {
   313  	t.Logs.Info("Setup backup pre-requisites")
   314  
   315  	var err error
   316  
   317  	t.Logs.Info("Create backup secret for rancher backup objects")
   318  	Eventually(func() error {
   319  		return CreateSecretFromMap(common.VeleroNameSpace, common.RancherSecretName)
   320  	}, shortWaitTimeout, shortPollingInterval).Should(BeNil())
   321  
   322  	t.Logs.Info("Get rancher URL")
   323  	Eventually(func() (string, error) {
   324  		common.RancherURL, err = common.GetRancherURL(t.Logs)
   325  		return common.RancherURL, err
   326  	}, shortWaitTimeout, shortPollingInterval).ShouldNot(BeNil())
   327  
   328  	t.Logs.Info("Creating multiple Rancher users")
   329  	Eventually(func() error {
   330  		return PopulateRancherUsers(common.RancherURL, common.RancherUserCount)
   331  	}, waitTimeout, pollingInterval).Should(BeNil())
   332  
   333  	t.Logs.Info("Build user id list for rancher users")
   334  	Eventually(func() bool {
   335  		return BuildRancherUserIDList(common.RancherURL)
   336  	}, waitTimeout, pollingInterval).Should(BeTrue())
   337  
   338  	time.Sleep(60 * time.Second)
   339  }
   340  
   341  // Run as part of AfterSuite
   342  func cleanUpRancher() {
   343  	t.Logs.Info("Cleanup backup and restore objects")
   344  
   345  	t.Logs.Info("Cleanup restore object")
   346  	Eventually(func() error {
   347  		return common.CrdPruner("resources.cattle.io", "v1", common.RestoreResource, common.RestoreRancherName, "", t.Logs)
   348  	}, shortWaitTimeout, shortPollingInterval).Should(BeNil())
   349  
   350  	t.Logs.Info("Cleanup backup object")
   351  	Eventually(func() error {
   352  		return common.CrdPruner("resources.cattle.io", "v1", common.BackupResource, common.BackupRancherName, "", t.Logs)
   353  	}, shortWaitTimeout, shortPollingInterval).Should(BeNil())
   354  
   355  	t.Logs.Info("Cleanup rancher secrets")
   356  	Eventually(func() error {
   357  		return common.DeleteSecret(common.VeleroNameSpace, common.RancherSecretName, t.Logs)
   358  	}, shortWaitTimeout, shortPollingInterval).Should(BeNil())
   359  
   360  	t.Logs.Info("Cleanup rancher users")
   361  	Eventually(func() bool {
   362  		return DeleteRancherUsers(common.RancherURL)
   363  	}, waitTimeout, pollingInterval).Should(BeTrue())
   364  
   365  }
   366  
   367  var _ = t.Describe("Rancher Backup and Restore,", Label("f:platform-verrazzano.rancher-backup"), Serial, func() {
   368  
   369  	t.Context("Rancher backup", func() {
   370  		WhenRancherBackupInstalledIt("Start rancher backup", func() {
   371  			Eventually(func() error {
   372  				return CreateRancherBackupObject()
   373  			}, waitTimeout, pollingInterval).Should(BeNil(), "Create rancher backup CRD")
   374  		})
   375  
   376  		WhenRancherBackupInstalledIt("Check backup progress after rancher backup object was created", func() {
   377  			Eventually(func() error {
   378  				return common.TrackOperationProgress("rancher", common.BackupResource, common.BackupRancherName, common.VeleroNameSpace, t.Logs)
   379  			}, waitTimeout, pollingInterval).Should(BeNil(), "Check if rancher backup operation completed successfully")
   380  		})
   381  
   382  	})
   383  
   384  	t.Context("Disaster simulation", func() {
   385  		WhenRancherBackupInstalledIt("Delete all users that were created as part of pre-suite", func() {
   386  			Eventually(func() bool {
   387  				return DeleteRancherUsers(common.RancherURL)
   388  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Delete rancher user")
   389  		})
   390  	})
   391  
   392  	t.Context("Rancher restore", func() {
   393  		WhenRancherBackupInstalledIt("Start restore after rancher backup is completed", func() {
   394  			Eventually(func() error {
   395  				return CreateRancherRestoreObject()
   396  			}, waitTimeout, pollingInterval).Should(BeNil(), "Create rancher restore CRD")
   397  		})
   398  		WhenRancherBackupInstalledIt("Check rancher restore progress", func() {
   399  			Eventually(func() error {
   400  				return common.TrackOperationProgress("rancher", common.RestoreResource, common.RestoreRancherName, common.VeleroNameSpace, t.Logs)
   401  			}, waitTimeout, pollingInterval).Should(BeNil(), "Check if rancher restore operation completed successfully")
   402  		})
   403  	})
   404  
   405  	t.Context("Rancher Data and Infra verification", func() {
   406  		WhenRancherBackupInstalledIt("After restore is complete wait for rancher pods to come up", func() {
   407  			Eventually(func() bool {
   408  				return checkPodsRunning(constants.RancherSystemNamespace, rancherPods)
   409  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Check if rancher infra is up")
   410  		})
   411  		WhenRancherBackupInstalledIt("Verify users are present rancher restore is complete", func() {
   412  			Eventually(func() bool {
   413  				return VerifyRancherUsers(common.RancherURL)
   414  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Check if rancher user has been restored successfully")
   415  		})
   416  	})
   417  
   418  })