github.com/verrazzano/verrazzano@v1.7.1/tests/e2e/pkg/update/updater.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 update
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"time"
    10  
    11  	"github.com/onsi/gomega"
    12  	"github.com/verrazzano/verrazzano/pkg/k8s/verrazzano"
    13  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    14  	vzapi "github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1alpha1"
    15  	"github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1beta1"
    16  	vpoClient "github.com/verrazzano/verrazzano/platform-operator/clientset/versioned"
    17  	"github.com/verrazzano/verrazzano/tests/e2e/pkg"
    18  	corev1 "k8s.io/api/core/v1"
    19  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    20  	"k8s.io/client-go/rest"
    21  	"sigs.k8s.io/controller-runtime/pkg/client"
    22  )
    23  
    24  const (
    25  	waitTimeout     = 5 * time.Minute
    26  	pollingInterval = 5 * time.Second
    27  )
    28  
    29  type CRModifier interface {
    30  	ModifyCR(cr *vzapi.Verrazzano)
    31  }
    32  
    33  type CRModifierV1beta1 interface {
    34  	ModifyCRV1beta1(cr *v1beta1.Verrazzano)
    35  }
    36  
    37  // GetCR gets the CR.  If it is not "Ready", wait for up to 5 minutes for it to be "Ready".
    38  func GetCR() *vzapi.Verrazzano {
    39  	var vz *vzapi.Verrazzano
    40  	// Wait for the CR to be Ready
    41  	gomega.Eventually(func() error {
    42  		cr, err := pkg.GetVerrazzano()
    43  		if err != nil {
    44  			return err
    45  		}
    46  		if cr.Status.State != vzapi.VzStateReady {
    47  			return fmt.Errorf("CR in state %s, not Ready yet", cr.Status.State)
    48  		}
    49  		vz = cr
    50  		return nil
    51  	}, waitTimeout, pollingInterval).Should(gomega.BeNil(), "Expected to get Verrazzano CR with Ready state")
    52  	return vz
    53  }
    54  
    55  // GetCRV1beta1 gets the CR.  If it is not "Ready", wait for up to 5 minutes for it to be "Ready".
    56  func GetCRV1beta1() *v1beta1.Verrazzano {
    57  	// Wait for the CR to be Ready
    58  	var vz *v1beta1.Verrazzano
    59  	gomega.Eventually(func() error {
    60  		cr, err := pkg.GetVerrazzanoV1beta1()
    61  		if err != nil {
    62  			return err
    63  		}
    64  		if cr.Status.State != v1beta1.VzStateReady {
    65  			return fmt.Errorf("v1beta1 CR in state %s, not Ready yet", cr.Status.State)
    66  		}
    67  		vz = cr
    68  		return nil
    69  	}, waitTimeout, pollingInterval).Should(gomega.BeNil(), "Expected to get Verrazzano v1beta1 CR with Ready state")
    70  
    71  	return vz
    72  }
    73  
    74  // UpdateCR updates the CR with the given CRModifier
    75  // - if the CRModifier implements rest.WarningHandler it will be added to the client config
    76  //
    77  // First it waits for CR to be "Ready" before using the specified CRModifier modifies the CR.
    78  // Then, it updates the modified.
    79  // Any error during the process will cause Ginkgo Fail.
    80  func UpdateCR(m CRModifier, updateOpts ...client.UpdateOption) error {
    81  	// Get the CR
    82  	cr := GetCR()
    83  
    84  	// Modify the CR
    85  	m.ModifyCR(cr)
    86  
    87  	// Update the CR
    88  	var err error
    89  	path, err := k8sutil.GetKubeConfigLocation()
    90  	if err != nil {
    91  		return err
    92  	}
    93  	config, err := k8sutil.GetKubeConfigGivenPath(path)
    94  	if err != nil {
    95  		return err
    96  	}
    97  	addWarningHandlerIfNecessary(m, config)
    98  	vzClient, err := pkg.GetV1Beta1ControllerRuntimeClient(config)
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	return verrazzano.UpdateV1Alpha1(context.TODO(), vzClient, cr, updateOpts...)
   104  }
   105  
   106  // UpdateCRV1beta1WithRetries updates the CR with the given CRModifierV1beta1.
   107  // First it waits for CR to be "Ready" before using the specified CRModifierV1beta1 modifies the CR.
   108  // Then, it updates the modified.
   109  // Any error during the process will cause Ginkgo Fail.
   110  func UpdateCRV1beta1WithRetries(m CRModifierV1beta1, pollingInterval, waitTime time.Duration) {
   111  	// Update the CR
   112  	gomega.Eventually(func() bool {
   113  		// GetCRV1beta1 gets the CR using v1beta1 client.
   114  		cr := GetCRV1beta1()
   115  
   116  		// Modify the CR
   117  		m.ModifyCRV1beta1(cr)
   118  
   119  		path, err := k8sutil.GetKubeConfigLocation()
   120  		if err != nil {
   121  			pkg.Log(pkg.Error, err.Error())
   122  			return false
   123  		}
   124  		config, err := k8sutil.GetKubeConfigGivenPath(path)
   125  		if err != nil {
   126  			pkg.Log(pkg.Error, err.Error())
   127  			return false
   128  		}
   129  		addWarningHandlerIfNecessary(m, config)
   130  		client, err := vpoClient.NewForConfig(config)
   131  		if err != nil {
   132  			pkg.Log(pkg.Error, err.Error())
   133  			return false
   134  		}
   135  		vzClient := client.VerrazzanoV1beta1().Verrazzanos(cr.Namespace)
   136  		_, err = vzClient.Update(context.TODO(), cr, metav1.UpdateOptions{})
   137  		if err != nil {
   138  			pkg.Log(pkg.Error, err.Error())
   139  			return false
   140  		}
   141  		return true
   142  	}).WithPolling(pollingInterval).WithTimeout(waitTime).Should(gomega.BeTrue())
   143  }
   144  
   145  func addWarningHandlerIfNecessary(m interface{}, config *rest.Config) {
   146  	warningHandler, implementsWarningHandler := m.(rest.WarningHandler)
   147  	if implementsWarningHandler {
   148  		config.WarningHandler = warningHandler
   149  	}
   150  }
   151  
   152  // UpdateCRV1beta1 updates the CR with the given CRModifierV1beta1.
   153  // - if the modifier implements rest.WarningHandler it will be added to the client config
   154  //
   155  // First it waits for CR to be "Ready" before using the specified CRModifierV1beta1 modifies the CR.
   156  // Then, it updates the modified.
   157  // Any error during the process will cause Ginkgo Fail.
   158  func UpdateCRV1beta1(m CRModifierV1beta1, opts ...client.UpdateOption) error {
   159  	// GetCRV1beta1 gets the CR using v1beta1 client.
   160  	cr := GetCRV1beta1()
   161  
   162  	// Modify the CR
   163  	m.ModifyCRV1beta1(cr)
   164  
   165  	// Update the CR
   166  	var err error
   167  	path, err := k8sutil.GetKubeConfigLocation()
   168  	if err != nil {
   169  		return err
   170  	}
   171  	config, err := k8sutil.GetKubeConfigGivenPath(path)
   172  	if err != nil {
   173  		return err
   174  	}
   175  	addWarningHandlerIfNecessary(m, config)
   176  
   177  	vzClient, err := pkg.GetV1Beta1ControllerRuntimeClient(config)
   178  	if err != nil {
   179  		return err
   180  	}
   181  
   182  	return vzClient.Update(context.TODO(), cr, opts...)
   183  }
   184  
   185  // UpdateCRWithRetries updates the CR with the given CRModifier.
   186  // UpdateCRV1beta1 updates the CR with the given CRModifierV1beta1.
   187  // - if the modifier implements rest.WarningHandler it will be added to the client config
   188  //
   189  // If the update fails, it retries by getting the latest version of CR and applying the same
   190  // update till it succeeds or timesout.
   191  func UpdateCRWithRetries(m CRModifier, pollingInterval, timeout time.Duration) {
   192  	RetryUpdate(m, "", true, pollingInterval, timeout)
   193  }
   194  
   195  // UpdateCRWithPlugins updates the CR with the given CRModifier.
   196  // update till it succeeds or timesout.
   197  func UpdateCRWithPlugins(m CRModifier, pollingInterval, timeout time.Duration, waitForReady bool) {
   198  	UpdatePlugins(m, "", waitForReady, pollingInterval, timeout)
   199  }
   200  
   201  // UpdatePlugins tries update with kubeconfigPath
   202  func UpdatePlugins(m CRModifier, kubeconfigPath string, waitForReady bool, pollingInterval, timeout time.Duration) {
   203  	gomega.Eventually(func() bool {
   204  		var err error
   205  		if kubeconfigPath == "" {
   206  			kubeconfigPath, err = k8sutil.GetKubeConfigLocation()
   207  			if err != nil {
   208  				pkg.Log(pkg.Error, err.Error())
   209  				return false
   210  			}
   211  		}
   212  		cr, err := pkg.GetVerrazzanoInstallResourceInCluster(kubeconfigPath)
   213  		if err != nil {
   214  			pkg.Log(pkg.Error, err.Error())
   215  			return false
   216  		}
   217  		// Modify the CR
   218  		m.ModifyCR(cr)
   219  		config, err := k8sutil.GetKubeConfigGivenPath(kubeconfigPath)
   220  		if err != nil {
   221  			pkg.Log(pkg.Error, err.Error())
   222  			return false
   223  		}
   224  		addWarningHandlerIfNecessary(m, config)
   225  		vzClient, err := pkg.GetV1Beta1ControllerRuntimeClient(config)
   226  		if err != nil {
   227  			pkg.Log(pkg.Error, err.Error())
   228  			return false
   229  		}
   230  		if err = verrazzano.UpdateV1Alpha1(context.TODO(), vzClient, cr); err != nil {
   231  			pkg.Log(pkg.Error, err.Error())
   232  			return false
   233  		}
   234  		if waitForReady {
   235  			WaitForReadyState(kubeconfigPath, time.Time{}, pollingInterval, timeout)
   236  		}
   237  		return true
   238  	}).WithPolling(pollingInterval).WithTimeout(timeout).Should(gomega.BeTrue())
   239  }
   240  
   241  // RetryUpdate tries update with kubeconfigPath
   242  // - if the modifier implements rest.WarningHandler it will be added to the client config
   243  func RetryUpdate(m CRModifier, kubeconfigPath string, waitForReady bool, pollingInterval, timeout time.Duration) {
   244  	gomega.Eventually(func() bool {
   245  		var err error
   246  		if kubeconfigPath == "" {
   247  			kubeconfigPath, err = k8sutil.GetKubeConfigLocation()
   248  			if err != nil {
   249  				pkg.Log(pkg.Error, err.Error())
   250  				return false
   251  			}
   252  		}
   253  		if waitForReady {
   254  			WaitForReadyState(kubeconfigPath, time.Time{}, pollingInterval, timeout)
   255  		}
   256  		cr, err := pkg.GetVerrazzanoInstallResourceInCluster(kubeconfigPath)
   257  		if err != nil {
   258  			pkg.Log(pkg.Error, err.Error())
   259  			return false
   260  		}
   261  		// Modify the CR
   262  		m.ModifyCR(cr)
   263  		config, err := k8sutil.GetKubeConfigGivenPath(kubeconfigPath)
   264  		if err != nil {
   265  			pkg.Log(pkg.Error, err.Error())
   266  			return false
   267  		}
   268  		addWarningHandlerIfNecessary(m, config)
   269  		vzClient, err := pkg.GetV1Beta1ControllerRuntimeClient(config)
   270  		if err != nil {
   271  			pkg.Log(pkg.Error, err.Error())
   272  			return false
   273  		}
   274  		if err = verrazzano.UpdateV1Alpha1(context.TODO(), vzClient, cr); err != nil {
   275  			pkg.Log(pkg.Error, err.Error())
   276  			return false
   277  		}
   278  		if waitForReady {
   279  			// Wait till the resource edit is complete and the verrazzano custom resource comes to ready state
   280  			WaitForReadyState(kubeconfigPath, time.Now(), pollingInterval, timeout)
   281  		}
   282  		return true
   283  	}).WithPolling(pollingInterval).WithTimeout(timeout).Should(gomega.BeTrue())
   284  }
   285  
   286  func UpdateCRExpectError(m CRModifier) error {
   287  	cr, err := pkg.GetVerrazzano()
   288  	if err != nil {
   289  		pkg.Log(pkg.Error, err.Error())
   290  		return err
   291  	}
   292  	// Modify the CR
   293  	m.ModifyCR(cr)
   294  
   295  	// Update the CR
   296  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
   297  	if err != nil {
   298  		pkg.Log(pkg.Error, err.Error())
   299  		return err
   300  	}
   301  	config, err := k8sutil.GetKubeConfigGivenPath(kubeconfigPath)
   302  	if err != nil {
   303  		pkg.Log(pkg.Error, err.Error())
   304  		return err
   305  	}
   306  	addWarningHandlerIfNecessary(m, config)
   307  	vzClient, err := pkg.GetV1Beta1ControllerRuntimeClient(config)
   308  	if err != nil {
   309  		pkg.Log(pkg.Error, err.Error())
   310  		return err
   311  	}
   312  	if verrazzano.UpdateV1Alpha1(context.TODO(), vzClient, cr); err != nil {
   313  		pkg.Log(pkg.Error, err.Error())
   314  		return err
   315  	}
   316  	return nil
   317  }
   318  
   319  // IsCRReady return true if the verrazzano custom resource is in ready state after an update operation false otherwise
   320  func IsCRReadyAfterUpdate(cr *v1beta1.Verrazzano, updatedTime time.Time) bool {
   321  	if cr == nil || cr.Status.State != v1beta1.VzStateReady {
   322  		pkg.Log(pkg.Error, "VZ CR is nil or not in ready state")
   323  		return false
   324  	}
   325  	for _, condition := range cr.Status.Conditions {
   326  		pkg.Log(pkg.Info, fmt.Sprintf("Checking if condition of type '%s', transitioned at '%s' is for the expected update",
   327  			condition.Type, condition.LastTransitionTime))
   328  		if (condition.Type == v1beta1.CondInstallComplete || condition.Type == v1beta1.CondUpgradeComplete) && condition.Status == corev1.ConditionTrue {
   329  			// check if the transition time is post the time of update
   330  			transitionTime, err := time.Parse(time.RFC3339, condition.LastTransitionTime)
   331  			if err != nil {
   332  				pkg.Log(pkg.Error, "Unable to parse the transition time '"+condition.LastTransitionTime+"'")
   333  				return false
   334  			}
   335  			if transitionTime.After(updatedTime) {
   336  				pkg.Log(pkg.Info, "Update operation completed at "+transitionTime.String())
   337  				return true
   338  			}
   339  		}
   340  		pkg.Log(pkg.Error, fmt.Sprintf("Could not find condition of type '%s' or '%s', transitioned after '%s'",
   341  			v1beta1.CondInstallComplete, v1beta1.CondUpgradeComplete, updatedTime.String()))
   342  	}
   343  	// Return true if the state is ready and there are no conditions updated in the status.
   344  	return len(cr.Status.Conditions) == 0
   345  }
   346  
   347  // WaitForReadyState waits till the verrazzano custom resource becomes ready or times out
   348  func WaitForReadyState(kubeconfigPath string, updateTime time.Time, pollingInterval, timeout time.Duration) {
   349  	gomega.Eventually(func() bool {
   350  		cr, err := pkg.GetVerrazzanoInstallResourceInClusterV1beta1(kubeconfigPath)
   351  		if err != nil {
   352  			pkg.Log(pkg.Error, err.Error())
   353  			return false
   354  		}
   355  		return IsCRReadyAfterUpdate(cr, updateTime)
   356  	}).WithPolling(pollingInterval).WithTimeout(timeout).Should(gomega.BeTrue())
   357  }