github.com/verrazzano/verrazzano@v1.7.1/tests/e2e/backup/mysql/mysql_backup_test.go (about)

     1  // Copyright (c) 2022, 2023, 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 mysql
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"fmt"
    10  	"github.com/google/uuid"
    11  	"github.com/verrazzano/verrazzano/platform-operator/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  	k8serror "k8s.io/apimachinery/pkg/api/errors"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"os"
    18  	"strings"
    19  	"text/template"
    20  	"time"
    21  
    22  	. "github.com/onsi/ginkgo/v2"
    23  	. "github.com/onsi/gomega"
    24  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    25  	"github.com/verrazzano/verrazzano/tests/e2e/pkg"
    26  	"github.com/verrazzano/verrazzano/tests/e2e/pkg/test/framework"
    27  )
    28  
    29  const (
    30  	shortWaitTimeout       = 10 * time.Minute
    31  	shortPollingInterval   = 10 * time.Second
    32  	waitTimeout            = 20 * time.Minute
    33  	pollingInterval        = 30 * time.Second
    34  	mysqlPvcPrefix         = "datadir-mysql"
    35  	mysqlChartName         = "mysql"
    36  	mysqlInnoDBClusterName = "mysql"
    37  	vzMySQLChartPath       = "../../../../platform-operator/thirdparty/charts/mysql"
    38  )
    39  
    40  var keycloakNamespacePods = []string{"keycloak", "mysql"}
    41  var mysqlPods = []string{"mysql"}
    42  var keycloakPods = []string{"keycloak"}
    43  
    44  var beforeSuite = t.BeforeSuiteFunc(func() {
    45  	start := time.Now()
    46  	common.GatherInfo()
    47  	file, err := os.CreateTemp("", "mysql-values-")
    48  	if err != nil {
    49  		t.Logs.Fatal(err)
    50  	}
    51  	defer file.Close()
    52  	common.MySQLBackupHelmFileName = file.Name()
    53  	backupPrerequisites()
    54  	metrics.Emit(t.Metrics.With("deployment_elapsed_time", time.Since(start).Milliseconds()))
    55  })
    56  
    57  var _ = BeforeSuite(beforeSuite)
    58  
    59  var afterSuite = t.AfterSuiteFunc(func() {
    60  	start := time.Now()
    61  	cleanUpSuite()
    62  	os.Remove(common.MySQLBackupHelmFileName)
    63  	metrics.Emit(t.Metrics.With("undeployment_elapsed_time", time.Since(start).Milliseconds()))
    64  })
    65  
    66  var _ = AfterSuite(afterSuite)
    67  
    68  var t = framework.NewTestFramework("mysql-backup")
    69  
    70  // func CreateInnoDBBackupObjectWithOci() creates mysql operator backup resource to start the backup.
    71  func CreateInnoDBBackupObjectWithOci() error {
    72  	var b bytes.Buffer
    73  	template, _ := template.New("mysql-backup").Parse(common.InnoDBBackupOci)
    74  	data := common.InnoDBBackupObject{
    75  		InnoDBBackupName:                  common.BackupMySQLName,
    76  		InnoDBNamespaceName:               constants.KeycloakNamespace,
    77  		InnoDBClusterName:                 common.InnoDBClusterName,
    78  		InnoDBBackupProfileName:           common.BackupResourceName,
    79  		InnoDBBackupObjectStoreBucketName: common.OciBucketName,
    80  		InnoDBBackupCredentialsName:       common.VeleroMySQLSecretName,
    81  		InnoDBBackupStorageName:           common.BackupMySQLStorageName,
    82  	}
    83  	template.Execute(&b, data)
    84  	err := common.DynamicSSA(context.TODO(), b.String(), t.Logs)
    85  	if err != nil {
    86  		t.Logs.Errorf("Error creating innodb backup object", zap.Error(err))
    87  		return err
    88  	}
    89  
    90  	return nil
    91  }
    92  
    93  // func CreateInnoDBBackupObjectWithS3() creates mysql operator backup resource to start the backup.
    94  func CreateInnoDBBackupObjectWithS3() error {
    95  	t.Logs.Infof("Starting MySQL backup with S3")
    96  	var b bytes.Buffer
    97  	template, _ := template.New("mysql-backup").Parse(common.InnoDBBackupS3)
    98  	data := common.InnoDBBackupObject{
    99  		InnoDBBackupName:                  common.BackupMySQLName,
   100  		InnoDBNamespaceName:               constants.KeycloakNamespace,
   101  		InnoDBClusterName:                 common.InnoDBClusterName,
   102  		InnoDBBackupProfileName:           common.BackupResourceName,
   103  		InnoDBBackupObjectStoreBucketName: common.OciBucketName,
   104  		InnoDBBackupCredentialsName:       common.VeleroMySQLSecretName,
   105  		InnoDBBackupStorageName:           common.BackupMySQLStorageName,
   106  		InnoDBObjectStorageNamespaceName:  common.OciNamespaceName,
   107  		InnoDBBackupRegion:                common.BackupRegion,
   108  	}
   109  	template.Execute(&b, data)
   110  	err := common.DynamicSSA(context.TODO(), b.String(), t.Logs)
   111  	if err != nil {
   112  		t.Logs.Errorf("Error creating innodb backup object", zap.Error(err))
   113  		return err
   114  	}
   115  
   116  	return nil
   117  }
   118  
   119  func BackupMySQLValues() error {
   120  	t.Logs.Infof("Backing up mysql values to file '%s'", common.MySQLBackupHelmFileName)
   121  	var cmd common.BashCommand
   122  	var cmdArgs []string
   123  	cmdArgs = append(cmdArgs, "/bin/sh", "-c", fmt.Sprintf("helm get values %s -n %s > %s", mysqlChartName, constants.KeycloakNamespace, common.MySQLBackupHelmFileName))
   124  	cmd.CommandArgs = cmdArgs
   125  
   126  	response := common.Runner(&cmd, t.Logs)
   127  	if response.CommandError != nil {
   128  		t.Logs.Error("Unable to get mysql helm values due to ", zap.Error(response.CommandError))
   129  		return response.CommandError
   130  	}
   131  	return nil
   132  }
   133  
   134  func NukeMySQL() error {
   135  	var cmd common.BashCommand
   136  	var cmdArgs []string
   137  	cmdArgs = append(cmdArgs, "helm")
   138  	cmdArgs = append(cmdArgs, "delete")
   139  	cmdArgs = append(cmdArgs, mysqlChartName)
   140  	cmdArgs = append(cmdArgs, "-n")
   141  	cmdArgs = append(cmdArgs, constants.KeycloakNamespace)
   142  
   143  	cmd.CommandArgs = cmdArgs
   144  
   145  	response := common.Runner(&cmd, t.Logs)
   146  	if response.CommandError != nil {
   147  		t.Logs.Error("Unable to cleanup mysql due to ", zap.Error(response.CommandError))
   148  		return response.CommandError
   149  	}
   150  
   151  	clientset, err := k8sutil.GetKubernetesClientset()
   152  	if err != nil {
   153  		t.Logs.Errorf("Failed to get clientset with error: %v", err)
   154  		return err
   155  	}
   156  
   157  	t.Logs.Infof("Deleting mysql pvc(s) from namespace '%s'", constants.KeycloakNamespace)
   158  	for i := 0; i < 3; i++ {
   159  		err := clientset.CoreV1().PersistentVolumeClaims(constants.KeycloakNamespace).Delete(context.TODO(), fmt.Sprintf("%s-%v", mysqlPvcPrefix, i), metav1.DeleteOptions{})
   160  		if err != nil {
   161  			if !k8serror.IsNotFound(err) {
   162  				t.Logs.Errorf("Unable to delete opensearch master pvc due to '%v'", zap.Error(err))
   163  				return err
   164  			}
   165  		}
   166  	}
   167  
   168  	return nil
   169  }
   170  
   171  func MySQLRestore() error {
   172  	t.Logs.Info("Start mysql restore")
   173  
   174  	// Get the backup folder name
   175  	backupInfo, err := common.GetMySQLBackup(constants.KeycloakNamespace, common.BackupMySQLName, t.Logs)
   176  	if err != nil {
   177  		t.Logs.Errorf("Unable to fetch backup '%s' due to '%v'", common.BackupMySQLName, zap.Error(err))
   178  		return err
   179  	}
   180  	backupFolderName := backupInfo.Status.Output
   181  
   182  	var cmd common.BashCommand
   183  	var cmdArgs []string
   184  
   185  	if strings.ToLower(common.MySQLBackupMode) == "s3" {
   186  		t.Logs.Infof("Starting MySQL restore with S3")
   187  		s3EndPoint := fmt.Sprintf("https://%s.compat.objectstorage.%s.oraclecloud.com", common.OciNamespaceName, common.BackupRegion)
   188  		cmdArgs = append(cmdArgs, "helm", "install", mysqlChartName, vzMySQLChartPath)
   189  		cmdArgs = append(cmdArgs, "--namespace", constants.KeycloakNamespace)
   190  		cmdArgs = append(cmdArgs, "--set", "initDB.dump.name=alpha")
   191  		cmdArgs = append(cmdArgs, "--set", fmt.Sprintf("initDB.dump.s3.prefix=%s/%s", common.BackupMySQLStorageName, backupFolderName))
   192  		cmdArgs = append(cmdArgs, "--set", fmt.Sprintf("initDB.dump.s3.bucketName=%s", common.OciBucketName))
   193  		cmdArgs = append(cmdArgs, "--set", fmt.Sprintf("initDB.dump.s3.config=%s", common.VeleroMySQLSecretName))
   194  		cmdArgs = append(cmdArgs, "--set", fmt.Sprintf("initDB.dump.s3.endpoint=%s", s3EndPoint))
   195  		cmdArgs = append(cmdArgs, "--set", "initDB.dump.s3.profile=default")
   196  		cmdArgs = append(cmdArgs, "--values", common.MySQLBackupHelmFileName)
   197  	} else {
   198  		cmdArgs = append(cmdArgs, "helm", "install", mysqlChartName, vzMySQLChartPath)
   199  		cmdArgs = append(cmdArgs, "--namespace", constants.KeycloakNamespace)
   200  		cmdArgs = append(cmdArgs, "--set", "initDB.dump.name=alpha")
   201  		cmdArgs = append(cmdArgs, "--set", fmt.Sprintf("initDB.dump.ociObjectStorage.prefix=%s/%s", common.BackupMySQLStorageName, backupFolderName))
   202  		cmdArgs = append(cmdArgs, "--set", fmt.Sprintf("initDB.dump.ociObjectStorage.bucketName=%s", common.OciBucketName))
   203  		cmdArgs = append(cmdArgs, "--set", fmt.Sprintf("initDB.dump.ociObjectStorage.credentials=%s", common.VeleroMySQLSecretName))
   204  		cmdArgs = append(cmdArgs, "--values", common.MySQLBackupHelmFileName)
   205  	}
   206  	cmd.CommandArgs = cmdArgs
   207  
   208  	response := common.Runner(&cmd, t.Logs)
   209  	if response.CommandError != nil {
   210  		t.Logs.Error("Unable to restore mysql due to ", zap.Error(response.CommandError))
   211  		return response.CommandError
   212  	}
   213  	return nil
   214  }
   215  
   216  func KeycloakDown() error {
   217  	clientset, err := k8sutil.GetKubernetesClientset()
   218  	if err != nil {
   219  		t.Logs.Errorf("Failed to get clientset with error: %v", err)
   220  		return err
   221  	}
   222  
   223  	t.Logs.Infof("Scaling down keycloak sts")
   224  	getScale, err := clientset.AppsV1().StatefulSets(constants.KeycloakNamespace).GetScale(context.TODO(), constants.KeycloakNamespace, metav1.GetOptions{})
   225  	if err != nil {
   226  		return err
   227  	}
   228  	common.KeyCloakReplicaCount = getScale.Spec.Replicas
   229  	scaleDown := *getScale
   230  	scaleDown.Spec.Replicas = 0
   231  
   232  	_, err = clientset.AppsV1().StatefulSets(constants.KeycloakNamespace).UpdateScale(context.TODO(), constants.KeycloakNamespace, &scaleDown, metav1.UpdateOptions{})
   233  	if err != nil {
   234  		t.Logs.Infof("Error = %v", zap.Error(err))
   235  		return err
   236  	}
   237  	return nil
   238  }
   239  
   240  func KeyCloakUp() error {
   241  	clientset, err := k8sutil.GetKubernetesClientset()
   242  	if err != nil {
   243  		t.Logs.Errorf("Failed to get clientset with error: %v", err)
   244  		return err
   245  	}
   246  
   247  	getKeycloak, err := clientset.AppsV1().StatefulSets(constants.KeycloakNamespace).GetScale(context.TODO(), constants.KeycloakNamespace, metav1.GetOptions{})
   248  	if err != nil {
   249  		return err
   250  	}
   251  
   252  	scaleUp := *getKeycloak
   253  	scaleUp.Spec.Replicas = common.KeyCloakReplicaCount
   254  
   255  	t.Logs.Infof("Scaling up keycloak sts")
   256  	_, err = clientset.AppsV1().StatefulSets(constants.KeycloakNamespace).UpdateScale(context.TODO(), constants.KeycloakNamespace, &scaleUp, metav1.UpdateOptions{})
   257  	if err != nil {
   258  		t.Logs.Infof("Error = %v", zap.Error(err))
   259  		return err
   260  	}
   261  	return nil
   262  }
   263  
   264  // KeycloakDeleteUsers helps in cleaning up test users at the end of the run
   265  func KeycloakDeleteUsers() error {
   266  	keycloakClient, err := pkg.NewKeycloakAdminRESTClient()
   267  	if err != nil {
   268  		t.Logs.Errorf("Unable to get keycloak client due to ", zap.Error(err))
   269  		return err
   270  	}
   271  
   272  	for i := 0; i < len(common.KeyCloakUserIDList); i++ {
   273  		t.Logs.Infof("Deleting user with username '%s'", common.KeyCloakUserIDList[i])
   274  		_ = keycloakClient.DeleteUser(constants.VerrazzanoSystemNamespace, common.KeyCloakUserIDList[i])
   275  	}
   276  
   277  	return nil
   278  
   279  }
   280  
   281  // KeycloakCreateUsers helps in creating test users to populate data
   282  func KeycloakCreateUsers(n int) error {
   283  
   284  	keycloakClient, err := pkg.NewKeycloakAdminRESTClient()
   285  	if err != nil {
   286  		t.Logs.Errorf("Unable to get keycloak client due to ", zap.Error(err))
   287  		return err
   288  	}
   289  
   290  	for i := 0; i < n; i++ {
   291  		id := uuid.New().String()
   292  		uniqueID := strings.Split(id, "-")[len(strings.Split(id, "-"))-1]
   293  		userID := fmt.Sprintf("mysql-user-%s", uniqueID)
   294  		t.Logs.Infof("Creating user with username '%s'", userID)
   295  		firstName := fmt.Sprintf("john-%v", i+1)
   296  		lastName := "doe"
   297  		location, err := keycloakClient.CreateUser(constants.VerrazzanoSystemNamespace, userID, firstName, lastName, "hello@mysql!")
   298  		if err != nil {
   299  			t.Logs.Errorf("Unable to get create keycloak user due to ", zap.Error(err))
   300  			return err
   301  		}
   302  		sqlUserID := strings.Split(location, "/")[len(strings.Split(location, "/"))-1]
   303  		common.KeyCloakUserIDList = append(common.KeyCloakUserIDList, sqlUserID)
   304  	}
   305  
   306  	return nil
   307  
   308  }
   309  
   310  // KeycloakVerifyUsers helps in verifying if the user exists
   311  func KeycloakVerifyUsers() bool {
   312  	keycloakClient, err := pkg.NewKeycloakAdminRESTClient()
   313  	if err != nil {
   314  		t.Logs.Errorf("Unable to get keycloak client due to ", zap.Error(err))
   315  		return false
   316  	}
   317  
   318  	for i := 0; i < len(common.KeyCloakUserIDList); i++ {
   319  		t.Logs.Infof("Verifying user with username '%s' exists after mysql restore", common.KeyCloakUserIDList[i])
   320  		ok, err := keycloakClient.VerifyUserExists(constants.VerrazzanoSystemNamespace, common.KeyCloakUserIDList[i])
   321  		if err != nil {
   322  			t.Logs.Errorf("Unable to verify keycloak user due to ", zap.Error(err))
   323  			return false
   324  		}
   325  		if !ok {
   326  			t.Logs.Errorf("User '%s' does not exist or could not be verified.", common.KeyCloakUserIDList[i])
   327  			return false
   328  		}
   329  		t.Logs.Infof("User '%s' found after mysql restore!", common.KeyCloakUserIDList[i])
   330  	}
   331  	return true
   332  }
   333  
   334  // 'It' Wrapper to only run spec if the Velero is supported on the current Verrazzano version
   335  func WhenMySQLOpInstalledIt(description string, f func()) {
   336  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
   337  	if err != nil {
   338  		t.It(description, func() {
   339  			Fail(fmt.Sprintf("Failed to get default kubeconfig path: %s", err.Error()))
   340  		})
   341  	}
   342  	supported, err := pkg.IsVerrazzanoMinVersion("1.4.0", kubeconfigPath)
   343  	if err != nil {
   344  		t.It(description, func() {
   345  			Fail(fmt.Sprintf("Failed to check Verrazzano version 1.4.0: %s", err.Error()))
   346  		})
   347  	}
   348  	if !pkg.IsMySQLOperatorEnabled(kubeconfigPath) {
   349  		supported = false
   350  	}
   351  
   352  	if supported {
   353  		t.It(description, f)
   354  	} else {
   355  		t.Logs.Infof("Skipping check '%v', the MySQL operator not enabled or minimum version detection failed", description)
   356  	}
   357  }
   358  
   359  // checkPodsRunning checks whether the pods are ready in a given namespace
   360  func checkPodsRunning(namespace string, expectedPods []string) bool {
   361  	result, err := pkg.PodsRunning(namespace, expectedPods)
   362  	if err != nil {
   363  		AbortSuite(fmt.Sprintf("One or more pods are not running in the namespace: %v, error: %v", namespace, err))
   364  	}
   365  	return result
   366  }
   367  
   368  // checkPodsNotRunning checks whether the pods are not ready in a given namespace
   369  func checkPodsNotRunning(namespace string, expectedPods []string) bool {
   370  	result, err := pkg.PodsNotRunning(namespace, expectedPods)
   371  	if err != nil {
   372  		AbortSuite(fmt.Sprintf("One or more pods are running in the namespace: %v, error: %v", namespace, err))
   373  	}
   374  	return result
   375  }
   376  
   377  func backupPrerequisites() {
   378  	t.Logs.Info("Setup backup pre-requisites")
   379  	t.Logs.Info("Create backup secret for innodb backup objects")
   380  
   381  	Eventually(func() error {
   382  		return BackupMySQLValues()
   383  	}, shortWaitTimeout, shortPollingInterval).Should(BeNil())
   384  
   385  	if strings.ToLower(common.MySQLBackupMode) == "s3" {
   386  		Eventually(func() error {
   387  			return common.CreateMySQLCredentialsSecretFromFile(constants.KeycloakNamespace, common.VeleroMySQLSecretName, t.Logs)
   388  		}, shortWaitTimeout, shortPollingInterval).Should(BeNil())
   389  	} else {
   390  		Eventually(func() error {
   391  			return common.CreateMySQLCredentialsSecretFromUserPrincipal(constants.KeycloakNamespace, common.VeleroMySQLSecretName, t.Logs)
   392  		}, shortWaitTimeout, shortPollingInterval).Should(BeNil())
   393  	}
   394  
   395  	t.Logs.Info("Create a sample keycloak user")
   396  	Eventually(func() error {
   397  		return KeycloakCreateUsers(common.KeycloakUserCount)
   398  	}, shortWaitTimeout, shortPollingInterval).Should(BeNil())
   399  
   400  }
   401  
   402  func cleanUpSuite() {
   403  	t.Logs.Info("Cleanup backup and restore objects")
   404  
   405  	t.Logs.Info("Cleanup backup object")
   406  	Eventually(func() error {
   407  		return common.CrdPruner("mysql.oracle.com", "v2", "mysqlbackups", common.BackupMySQLName, constants.KeycloakNamespace, t.Logs)
   408  	}, shortWaitTimeout, shortPollingInterval).Should(BeNil())
   409  
   410  	t.Logs.Info("Cleanup mysql backup secrets")
   411  	Eventually(func() error {
   412  		return common.DeleteSecret(constants.KeycloakNamespace, common.VeleroMySQLSecretName, t.Logs)
   413  	}, shortWaitTimeout, shortPollingInterval).Should(BeNil())
   414  
   415  	t.Logs.Info("Delete keycloak user")
   416  	Eventually(func() error {
   417  		return KeycloakDeleteUsers()
   418  	}, shortWaitTimeout, shortPollingInterval).Should(BeNil())
   419  }
   420  
   421  var _ = t.Describe("MySQL Backup and Restore,", Label("f:platform-verrazzano.mysql-backup"), Serial, func() {
   422  
   423  	t.Context("MySQL backup operator", func() {
   424  		WhenMySQLOpInstalledIt("MySQL backup triggered", func() {
   425  			if strings.ToLower(common.MySQLBackupMode) == "s3" {
   426  				Eventually(func() error {
   427  					return CreateInnoDBBackupObjectWithS3()
   428  				}, waitTimeout, pollingInterval).Should(BeNil())
   429  			} else {
   430  				Eventually(func() error {
   431  					return CreateInnoDBBackupObjectWithOci()
   432  				}, waitTimeout, pollingInterval).Should(BeNil())
   433  			}
   434  		})
   435  
   436  		WhenMySQLOpInstalledIt("Check backup progress after mysql backup object was created", func() {
   437  			Eventually(func() error {
   438  				return common.TrackOperationProgress("mysql", common.BackupResource, common.BackupMySQLName, constants.KeycloakNamespace, t.Logs)
   439  			}, waitTimeout, pollingInterval).Should(BeNil())
   440  		})
   441  	})
   442  
   443  	t.Context("Disaster simulation", func() {
   444  		WhenMySQLOpInstalledIt("Delete users created as part of pre-suite", func() {
   445  			Eventually(func() error {
   446  				return KeycloakDeleteUsers()
   447  			}, waitTimeout, pollingInterval).Should(BeNil())
   448  		})
   449  
   450  		WhenMySQLOpInstalledIt("Delete innodb cluster", func() {
   451  			Eventually(func() error {
   452  				return NukeMySQL()
   453  			}, waitTimeout, pollingInterval).Should(BeNil())
   454  		})
   455  
   456  		WhenMySQLOpInstalledIt("Ensure the pods are not running before starting a restore", func() {
   457  			Eventually(func() bool {
   458  				return checkPodsNotRunning(constants.KeycloakNamespace, mysqlPods)
   459  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Check if pods are down")
   460  		})
   461  	})
   462  
   463  	t.Context("MySQL restore", func() {
   464  		WhenMySQLOpInstalledIt(fmt.Sprintf("Start restore of mysql from backup '%s'", common.BackupMySQLName), func() {
   465  			Eventually(func() error {
   466  				return MySQLRestore()
   467  			}, waitTimeout, pollingInterval).Should(BeNil())
   468  		})
   469  		WhenMySQLOpInstalledIt("Check MySQL restore progress", func() {
   470  			Eventually(func() error {
   471  				return common.TrackOperationProgress("mysql", common.RestoreResource, mysqlInnoDBClusterName, constants.KeycloakNamespace, t.Logs)
   472  			}, waitTimeout, pollingInterval).Should(BeNil())
   473  		})
   474  
   475  	})
   476  
   477  	t.Context("MySQL Data and Infra verification", func() {
   478  		WhenMySQLOpInstalledIt("After restore is complete scale down keycloak", func() {
   479  			Eventually(func() error {
   480  				return KeycloakDown()
   481  			}, waitTimeout, pollingInterval).Should(BeNil())
   482  		})
   483  
   484  		WhenMySQLOpInstalledIt("After scaling down keycloak, wait for all keycloak pods to go down", func() {
   485  			Eventually(func() bool {
   486  				return checkPodsNotRunning(constants.KeycloakNamespace, keycloakPods)
   487  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Check if keycloak pods are down")
   488  		})
   489  
   490  		WhenMySQLOpInstalledIt("Scale up keycloak", func() {
   491  			Eventually(func() error {
   492  				return KeyCloakUp()
   493  			}, waitTimeout, pollingInterval).Should(BeNil())
   494  		})
   495  
   496  		WhenMySQLOpInstalledIt("After scaling up keycloak, wait for all keycloak pods to be up", func() {
   497  			Eventually(func() bool {
   498  				return checkPodsRunning(constants.KeycloakNamespace, keycloakPods)
   499  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Check if keycloak pods are up")
   500  		})
   501  
   502  		WhenMySQLOpInstalledIt("After restore is complete wait for keycloak and mysql pods to come up", func() {
   503  			Eventually(func() bool {
   504  				return checkPodsRunning(constants.KeycloakNamespace, keycloakNamespacePods)
   505  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Check if keycloak and mysql infra is up")
   506  		})
   507  
   508  		WhenMySQLOpInstalledIt("Is Restore good? Verify restore", func() {
   509  			Eventually(func() bool {
   510  				return KeycloakVerifyUsers()
   511  			}, waitTimeout, pollingInterval).Should(BeTrue())
   512  		})
   513  
   514  	})
   515  })