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 })