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  }