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 }