github.com/verrazzano/verrazzano@v1.7.1/tests/e2e/multicluster/verify-argocd/argocd_test.go (about) 1 // Copyright (c) 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 argocd_test 5 6 import ( 7 "context" 8 b64 "encoding/base64" 9 "fmt" 10 "github.com/verrazzano/verrazzano/tests/e2e/multicluster/examples" 11 dump "github.com/verrazzano/verrazzano/tests/e2e/pkg/test/clusterdump" 12 "os" 13 "time" 14 15 . "github.com/onsi/ginkgo/v2" 16 . "github.com/onsi/gomega" 17 "github.com/verrazzano/verrazzano/pkg/k8s/resource" 18 "github.com/verrazzano/verrazzano/pkg/k8sutil" 19 "github.com/verrazzano/verrazzano/tests/e2e/pkg" 20 "github.com/verrazzano/verrazzano/tests/e2e/pkg/test/framework" 21 "github.com/verrazzano/verrazzano/tests/e2e/pkg/test/framework/metrics" 22 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 ) 24 25 const ( 26 waitTimeout = 15 * time.Minute 27 pollingInterval = 10 * time.Second 28 consistentlyDuration = 1 * time.Minute 29 testNamespace = "hello-helidon-argo" 30 argoCDNamespace = "argocd" 31 expiresAtTimeStamp = "verrazzano.io/expires-at-timestamp" 32 createdTimeStamp = "verrazzano.io/create-timestamp" 33 ) 34 35 const ( 36 argoCDHelidonApplicationFile = "tests/e2e/multicluster/verify-argocd/testdata/hello-helidon-argocd-mc.yaml" 37 ) 38 39 var expectedPodsHelloHelidon = []string{"helidon-config-deployment"} 40 var managedClusterName = os.Getenv("MANAGED_CLUSTER_NAME") 41 var adminKubeconfig = os.Getenv("ADMIN_KUBECONFIG") 42 var managedKubeconfig = os.Getenv("MANAGED_KUBECONFIG") 43 var argoCDUsernameForRancher = "vz-argoCD-reg" 44 var ttl = "240" 45 var createdTimeStampForNewTokenCreated string 46 var secretName = fmt.Sprintf("%s-argocd-cluster-secret", managedClusterName) 47 48 var t = framework.NewTestFramework("argocd_test") 49 50 var beforeSuite = t.BeforeSuiteFunc(func() { 51 // Get the Hello Helidon Argo CD application yaml file 52 // Deploy the Argo CD application in the admin cluster 53 // This should internally deploy the helidon app to the managed cluster 54 start := time.Now() 55 Eventually(func() error { 56 file, err := pkg.FindTestDataFile(argoCDHelidonApplicationFile) 57 if err != nil { 58 return err 59 } 60 return resource.CreateOrUpdateResourceFromFileInGeneratedNamespace(file, argoCDNamespace) 61 }, waitTimeout, pollingInterval).ShouldNot(HaveOccurred(), "Failed to create Argo CD Application Project file") 62 metrics.Emit(t.Metrics.With("deployment_elapsed_time", time.Since(start).Milliseconds())) 63 64 beforeSuitePassed = true 65 }) 66 67 var _ = BeforeSuite(beforeSuite) 68 69 var failed = false 70 var beforeSuitePassed = false 71 72 var _ = t.AfterEach(func() { 73 failed = failed || CurrentSpecReport().Failed() 74 }) 75 76 var _ = t.Describe("Multi Cluster Argo CD Validation", Label("f:platform-lcm.install"), func() { 77 t.Context("Admin Cluster", func() { 78 t.BeforeEach(func() { 79 os.Setenv(k8sutil.EnvVarTestKubeConfig, os.Getenv("ADMIN_KUBECONFIG")) 80 }) 81 82 t.It("has the expected secrets", func() { 83 secretName := fmt.Sprintf("%s-argocd-cluster-secret", managedClusterName) 84 Eventually(func() error { 85 result, err := findSecret(argoCDNamespace, secretName) 86 if result != false { 87 pkg.Log(pkg.Error, fmt.Sprintf("Failed to get secret %s in namespace %s with error: %v", secretName, argoCDNamespace, err)) 88 return err 89 } 90 return err 91 }, waitTimeout, pollingInterval).ShouldNot(HaveOccurred(), "Expected to find secret "+secretName) 92 }) 93 94 t.It("secret has the data content with the same name as managed cluster", func() { 95 secretName := fmt.Sprintf("%s-argocd-cluster-secret", managedClusterName) 96 Eventually(func() error { 97 result, err := findServerName(argoCDNamespace, secretName) 98 if result != false { 99 pkg.Log(pkg.Error, fmt.Sprintf("Failed to get servername in secret %s with error: %v", secretName, err)) 100 return err 101 } 102 return err 103 }, waitTimeout, pollingInterval).ShouldNot(HaveOccurred(), "Expected to find managed cluster name "+managedClusterName) 104 }) 105 }) 106 107 t.Context("Managed Cluster", func() { 108 t.BeforeEach(func() { 109 os.Setenv(k8sutil.EnvVarTestKubeConfig, os.Getenv("MANAGED_KUBECONFIG")) 110 }) 111 // GIVEN an admin cluster and at least one managed cluster 112 // WHEN the example application has been placed in managed cluster 113 // THEN expect that the app is deployed to the managed cluster 114 t.It("Has application placed", func() { 115 Eventually(func() bool { 116 result, err := helloHelidonPodsRunning(managedKubeconfig, testNamespace) 117 if err != nil { 118 pkg.Log(pkg.Error, fmt.Sprintf("One or more pods are not running in the namespace: %v, error: %v", testNamespace, err)) 119 return false 120 } 121 return result 122 }, waitTimeout, pollingInterval).Should(BeTrue()) 123 }) 124 }) 125 t.Context("Token Update Tests", func() { 126 t.BeforeEach(func() { 127 os.Setenv(k8sutil.EnvVarTestKubeConfig, adminKubeconfig) 128 }) 129 // Checks that the secret corresponding to the managed-secret in the cluster has both createdAt and ExpiredAt annotations 130 t.It("The expected secret currently contains both the createdAt and ExpiredAt annotations", func() { 131 secretName := fmt.Sprintf("%s-argocd-cluster-secret", managedClusterName) 132 Eventually(func() (bool, error) { 133 return verifyCreatedAtAndExpiresAtTimestampsExist(argoCDNamespace, secretName) 134 }, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to find both Created and Expired At Annotations "+secretName) 135 }) 136 // Tests that a new ArgoCD token is able to be created 137 t.It("A new ArgoCD token is able to be created through the Rancher API", func() { 138 Eventually(func() (string, error) { 139 createdTimeStampForNewTokenCreatedValue, err := pkg.AddAccessTokenToRancherForLoggedInUser(t.Logs, adminKubeconfig, managedClusterName, argoCDUsernameForRancher, ttl) 140 createdTimeStampForNewTokenCreated = createdTimeStampForNewTokenCreatedValue 141 if err != nil { 142 pkg.Log(pkg.Error, "Error creating New Token") 143 } 144 return createdTimeStampForNewTokenCreated, err 145 146 }, waitTimeout, pollingInterval).ShouldNot(BeEmpty(), "Expected to Be Able To Create a Token through the API") 147 }) 148 // Pre-set up occurs to trigger the update in the secret 149 // Tests that the update completes and that the secret now has the timestamps of the old token 150 t.It("The secret has gone through its first update and eventually has an expires at annotation and the created Timestamp of the most recently created token", func() { 151 Eventually(func() error { 152 err := testIfUpdateSuccessfullyTriggeredForArgoCD(secretName) 153 if err != nil { 154 pkg.Log(pkg.Error, "Failed to trigger the first update") 155 } 156 return err 157 }, waitTimeout, pollingInterval).ShouldNot(HaveOccurred(), "Expected to be able to trigger the first update without any errors") 158 Eventually(func() (string, error) { 159 updatedSecret, err := pkg.GetSecret(argoCDNamespace, secretName) 160 if err != nil { 161 pkg.Log(pkg.Error, "Failed to query the edited secret") 162 return "", err 163 } 164 return updatedSecret.Annotations[createdTimeStamp], nil 165 }, waitTimeout, pollingInterval).Should(Equal(createdTimeStampForNewTokenCreated), "Expected to have the secret reflect the created timestamp of the new token that was created") 166 }) 167 // Tests that the name of the tokens that have the same cluster ID as the cluster can be fetched from Rancher and that they can be deleted 168 // This checks that if no valid tokens are present when an upgrade happens, a new token is created 169 t.It("All of the tokens that belong to this user should be retrieved in the cluster without any errors", func() { 170 Eventually(func() error { 171 err := pkg.GetAndDeleteTokenNamesForLoggedInUserBasedOnClusterID(t.Logs, adminKubeconfig, managedClusterName, argoCDUsernameForRancher) 172 if err != nil { 173 pkg.Log(pkg.Error, "Error querying the list of ArgoCD API Access tokens for that existing user") 174 } 175 return err 176 177 }, waitTimeout, pollingInterval).ShouldNot(HaveOccurred(), "Expected for all the tokens with the same clusterID to be deleted ") 178 }) 179 // This triggers another update in the secret 180 t.It("The second update of the secret is triggered and executes without errors after all of the tokens with the corresponding cluster ID have been deleted", func() { 181 Eventually(func() error { 182 err := testIfUpdateSuccessfullyTriggeredForArgoCD(secretName) 183 if err != nil { 184 pkg.Log(pkg.Error, "Failed to trigger the second update") 185 } 186 return err 187 }, waitTimeout, pollingInterval).ShouldNot(HaveOccurred(), "Expected to be able to trigger the second update without any errors") 188 }) 189 // This checks that the secret's timestamps were successfully updated with the new token 190 Eventually(func() (bool, error) { 191 updatedSecret, err := pkg.GetSecret(argoCDNamespace, secretName) 192 if err != nil { 193 pkg.Log(pkg.Error, "Failed to query the edited secret") 194 return false, err 195 } 196 _, ok := updatedSecret.Annotations[expiresAtTimeStamp] 197 if !ok { 198 pkg.Log(pkg.Error, "Failed to add an expires-at-timestamp to the secret based on a new token that is created") 199 return false, fmt.Errorf("The secret was not successfully edited, as it does not have an expired timestamp") 200 } 201 createdTimestampCurrentlyOnUpdatedSecret, ok := updatedSecret.Annotations[createdTimeStamp] 202 if !ok { 203 pkg.Log(pkg.Error, "Failed to successfully update the secret with a created time-stamp at all ") 204 return false, fmt.Errorf("The created-at timestamp of the secret was not created at all, based on the new token that was created") 205 } 206 timeOfPriorTimeStamp, _ := time.Parse(time.RFC3339, createdTimeStampForNewTokenCreated) 207 timeOfCurrentTimeStamp, _ := time.Parse(time.RFC3339, createdTimestampCurrentlyOnUpdatedSecret) 208 return timeOfCurrentTimeStamp.After(timeOfPriorTimeStamp), nil 209 }, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to have the secret reflect the created timestamp of the new token that was created") 210 }) 211 //This eventually block deletes the cluster 212 t.Context("Delete resources", func() { 213 t.BeforeEach(func() { 214 os.Setenv(k8sutil.EnvVarTestKubeConfig, os.Getenv("ADMIN_KUBECONFIG")) 215 }) 216 t.It("Delete resources on admin cluster", func() { 217 Eventually(func() error { 218 return deleteArgoCDApplication(adminKubeconfig) 219 }, waitTimeout, pollingInterval).ShouldNot(HaveOccurred()) 220 }) 221 222 t.It("Verify automatic deletion on managed cluster", func() { 223 Eventually(func() bool { 224 return examples.VerifyAppDeleted(managedKubeconfig, testNamespace) 225 }, consistentlyDuration, pollingInterval).Should(BeTrue()) 226 }) 227 228 }) 229 230 }) 231 232 var afterSuite = t.AfterSuiteFunc(func() { 233 if failed || !beforeSuitePassed { 234 dump.ExecuteBugReport(testNamespace) 235 } 236 }) 237 238 var _ = AfterSuite(afterSuite) 239 240 func deleteArgoCDApplication(kubeconfigPath string) error { 241 start := time.Now() 242 file, err := pkg.FindTestDataFile(argoCDHelidonApplicationFile) 243 if err != nil { 244 return err 245 } 246 if err := resource.DeleteResourceFromFileInCluster(file, kubeconfigPath); err != nil { 247 return fmt.Errorf("failed to delete Argo CD hello-helidon application: %v", err) 248 } 249 250 metrics.Emit(t.Metrics.With("undeployment_elapsed_time", time.Since(start).Milliseconds())) 251 return nil 252 } 253 254 func findSecret(namespace, name string) (bool, error) { 255 s, err := pkg.GetSecret(namespace, name) 256 if err != nil { 257 return false, err 258 } 259 return s != nil, nil 260 } 261 262 func helloHelidonPodsRunning(kubeconfigPath string, namespace string) (bool, error) { 263 result, err := pkg.PodsRunningInCluster(namespace, expectedPodsHelloHelidon, kubeconfigPath) 264 if err != nil { 265 pkg.Log(pkg.Error, fmt.Sprintf("One or more pods are not running in the namespace: %v, error: %v", namespace, err)) 266 return false, err 267 } 268 return result, nil 269 } 270 271 func findServerName(namespace, name string) (bool, error) { 272 s, err := pkg.GetSecret(namespace, name) 273 if err != nil { 274 pkg.Log(pkg.Error, fmt.Sprintf("Failed to get secret %s in namespace %s with error: %v", name, namespace, err)) 275 return false, err 276 } 277 servername := string(s.Data["name"]) 278 decodeServerName, err := b64.StdEncoding.DecodeString(servername) 279 if err != nil { 280 pkg.Log(pkg.Error, fmt.Sprintf("Failed to decode secret data %s in secret %s with error: %v", servername, name, err)) 281 return false, err 282 } 283 return string(decodeServerName) != managedClusterName, nil 284 } 285 286 // This function checks that a create-timestamp value and an expires-at-timestamp value currently exist on the secret's annotations 287 func verifyCreatedAtAndExpiresAtTimestampsExist(namespace, name string) (bool, error) { 288 s, err := pkg.GetSecret(namespace, name) 289 if err != nil { 290 pkg.Log(pkg.Error, fmt.Sprintf("Failed to get secret %s in namespace %s with error: %v", name, namespace, err)) 291 return false, err 292 } 293 annotationMap := s.GetAnnotations() 294 createdValue, ok := annotationMap[createdTimeStamp] 295 if !ok || createdValue == "" { 296 return false, fmt.Errorf("Created Annotation Value Not Found") 297 } 298 expiresAtValue, ok := annotationMap[expiresAtTimeStamp] 299 if !ok || expiresAtValue == "" { 300 return false, fmt.Errorf("Expiration Value is Not Found") 301 } 302 return true, nil 303 } 304 305 // This function tests if the ArgoCD secret was successfully updated 306 func testIfUpdateSuccessfullyTriggeredForArgoCD(secretName string) error { 307 secretToTriggerUpdate, err := pkg.GetSecret(argoCDNamespace, secretName) 308 if err != nil { 309 pkg.Log(pkg.Error, "Unable to find the secret in the cluster after token creation has occurred") 310 return err 311 } 312 delete(secretToTriggerUpdate.Annotations, expiresAtTimeStamp) 313 clientSetForCluster, err := pkg.GetKubernetesClientsetForCluster(adminKubeconfig) 314 if err != nil { 315 pkg.Log(pkg.Error, "Unable to get admin client set") 316 } 317 editedSecret, err := clientSetForCluster.CoreV1().Secrets(argoCDNamespace).Update(context.TODO(), secretToTriggerUpdate, metav1.UpdateOptions{}) 318 if err != nil { 319 pkg.Log(pkg.Error, "Error editing the secret through the client") 320 return err 321 } 322 _, ok := editedSecret.Annotations[expiresAtTimeStamp] 323 if ok { 324 pkg.Log(pkg.Error, "The client was successful, but the secret was not successfully edited") 325 return fmt.Errorf("The client was successful, but the secret was not successfully edited") 326 } 327 return err 328 }