diff --git a/tests/cnf/ran/gitopsztp/internal/tsparams/consts.go b/tests/cnf/ran/gitopsztp/internal/tsparams/consts.go index 75f60d4f8..8181a1b4f 100644 --- a/tests/cnf/ran/gitopsztp/internal/tsparams/consts.go +++ b/tests/cnf/ran/gitopsztp/internal/tsparams/consts.go @@ -25,6 +25,8 @@ const ( LabelMachineConfigTestCases = "ztp-machine-config" // LabelSpokeCheckerTests is the label for a particular set of test cases. LabelSpokeCheckerTests = "ztp-spoke-checker" + // LabelClusterInstanceDeleteTestCases is the label for the siteconfig operator's cluster instance delete test cases. + LabelClusterInstanceDeleteTestCases = "ztp-cluster-instance-delete" // MultiClusterHubOperator is the name of the multi cluster hub operator. MultiClusterHubOperator = "multiclusterhub-operator" @@ -81,6 +83,10 @@ const ( ZtpTestPathCustomSourceNoCrFile = "ztp-test/custom-source-crs/no-cr-file" // ZtpTestPathCustomSourceSearchPath is the git path for the policies app custome source search path test. ZtpTestPathCustomSourceSearchPath = "ztp-test/custom-source-crs/search-path" + // ZtpTestPathDetachAIMNO is the git path for the siteconfig operator detach AI MNO cluster instance test. + ZtpTestPathDetachAIMNO = "ztp-test/siteconfig-operator/detach-ai-mno/" + // ZtpTestPathDetachAISNO is the git path for the siteconfig operator detach AI SNO cluster instance test. + ZtpTestPathDetachAISNO = "ztp-test/siteconfig-operator/detach-ai-sno/" // ZtpKustomizationPath is the path to the kustomization file in the ztp test. ZtpKustomizationPath = "/kustomization.yaml" @@ -121,6 +127,12 @@ const ( ImageRegistryPVC = "image-registry-pvc" // ImageRegistryPath is the path to where the image registry PV will be. ImageRegistryPath = "/var/imageregistry" + // DefaultAIClusterTemplatesConfigMapName is the name of default AI cluster templates config map. + DefaultAIClusterTemplatesConfigMapName = "ai-cluster-templates-v1" + // DefaultAINodeTemplatesConfigMapName is the name of default AI node templates config map. + DefaultAINodeTemplatesConfigMapName = "ai-node-templates-v1" + // SiteconfigOperatorPodLabel is the name of siteconfig operator pod label selector. + SiteconfigOperatorPodLabel = "app.kubernetes.io/name=siteconfig-controller" // LogLevel is the verbosity of glog statements in this test suite. LogLevel glog.Level = 90 diff --git a/tests/cnf/ran/gitopsztp/tests/ztp-cluster-instance-delete.go b/tests/cnf/ran/gitopsztp/tests/ztp-cluster-instance-delete.go new file mode 100644 index 000000000..5ca9154e8 --- /dev/null +++ b/tests/cnf/ran/gitopsztp/tests/ztp-cluster-instance-delete.go @@ -0,0 +1,229 @@ +package tests + +import ( + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/openshift-kni/eco-goinfra/pkg/assisted" + "github.com/openshift-kni/eco-goinfra/pkg/bmh" + "github.com/openshift-kni/eco-goinfra/pkg/configmap" + "github.com/openshift-kni/eco-goinfra/pkg/hive" + "github.com/openshift-kni/eco-goinfra/pkg/ocm" + "github.com/openshift-kni/eco-goinfra/pkg/pod" + "github.com/openshift-kni/eco-goinfra/pkg/reportxml" + "github.com/openshift-kni/eco-gotests/tests/cnf/ran/gitopsztp/internal/gitdetails" + "github.com/openshift-kni/eco-gotests/tests/cnf/ran/gitopsztp/internal/tsparams" + "github.com/openshift-kni/eco-gotests/tests/cnf/ran/internal/rancluster" + "github.com/openshift-kni/eco-gotests/tests/cnf/ran/internal/ranhelper" + "github.com/openshift-kni/eco-gotests/tests/cnf/ran/internal/ranparam" + "github.com/openshift-kni/eco-gotests/tests/cnf/ran/internal/version" + + . "github.com/openshift-kni/eco-gotests/tests/cnf/ran/internal/raninittools" +) + +var _ = Describe("ZTP Siteconfig Operator's Cluster Instance Delete Tests", + Label(tsparams.LabelClusterInstanceDeleteTestCases), func() { + // These tests use the hub and spoke architecture. + BeforeEach(func() { + By("verifying that ZTP meets the minimum version") + versionInRange, err := version.IsVersionStringInRange(RANConfig.ZTPVersion, "4.17", "") + Expect(err).ToNot(HaveOccurred(), "Failed to compare ZTP version string") + + if !versionInRange { + Skip("ZTP Siteconfig operator tests require ZTP 4.17 or later") + } + + }) + + AfterEach(func() { + // Recreate the ClusterInstance custom resource. + By("resetting the clusters app back to the original settings") + err := gitdetails.SetGitDetailsInArgoCd( + tsparams.ArgoCdClustersAppName, tsparams.ArgoCdAppDetails[tsparams.ArgoCdClustersAppName], + true, false) + Expect(err).ToNot(HaveOccurred(), "Failed to reset clusters app git details") + + // Test teardown expected results validation. + By("checking the infra env manifests exists on hub") + _, err = assisted.PullInfraEnvInstall(HubAPIClient, RANConfig.Spoke1Name, RANConfig.Spoke1Name) + Expect(err).ToNot(HaveOccurred(), "Failed to find spoke infra env manifests") + + By("checking the bare metal host manifests exists on hub") + _, err = bmh.Pull(HubAPIClient, RANConfig.Spoke1Name, RANConfig.Spoke1Name) + Expect(err).ToNot(HaveOccurred(), "Failed to find spoke bmh manifests") + + By("checking the cluster deployment manifests exists on hub") + _, err = hive.PullClusterDeployment(HubAPIClient, RANConfig.Spoke1Name, RANConfig.Spoke1Name) + Expect(err).ToNot(HaveOccurred(), "Failed to find spoke cluster deployment manifests") + + By("checking the NM state config manifests exists on hub") + nmStateConfigList, err := assisted.ListNmStateConfigs(HubAPIClient, RANConfig.Spoke1Name) + Expect(err).ToNot(HaveOccurred(), "Failed to list NM state config manifests") + Expect(nmStateConfigList).ToNot(BeEmpty(), "Failed to find NM state config manifests") + + By("checking the klusterlet addon config manifests exists on hub") + _, err = ocm.PullKAC(HubAPIClient, RANConfig.Spoke1Name, RANConfig.Spoke1Name) + Expect(err).ToNot(HaveOccurred(), "Failed to find spoke kac manifests") + + By("checking the agent cluster install manifests exists on hub") + _, err = assisted.PullAgentClusterInstall(HubAPIClient, RANConfig.Spoke1Name, RANConfig.Spoke1Name) + Expect(err).ToNot(HaveOccurred(), "Failed to find spoke agent cluster install manifests") + }) + + // 75374 - Detaching the AI multi-node openshift (MNO) spoke cluster. + It("Validate detaching the AI multi-node openshift spoke cluster", reportxml.ID("75374"), func() { + By("checking spoke cluster type") + spokeClusterType, err := rancluster.CheckSpokeClusterType(RANConfig.Spoke1APIClient) + Expect(err).ToNot(HaveOccurred(), "Failed to fetch spoke cluster type") + + if spokeClusterType == ranparam.SNOCluster { + Skip("This test only applies to standard or multi-node openshift spoke cluster") + } + + // Test step 1-Delete default assisted installer template reference ConfigMap CRs after spoke cluster installed. + By("deleting default assisted installer template reference ConfigMap custom resources", func() { + + By("deleting default assisted installer cluster level templates ConfigMap CR") + clusterTemplateConfigMap, err := configmap.Pull(HubAPIClient, tsparams.DefaultAIClusterTemplatesConfigMapName, + ranparam.AcmOperatorNamespace) + Expect(err).ToNot(HaveOccurred(), "Failed to find default AI cluster level templates config map") + + if err == nil { + err = clusterTemplateConfigMap.Delete() + Expect(err).ToNot(HaveOccurred(), "Failed to delete AI cluster level templates config map") + } + + By("deleting default assisted installer node level templates ConfigMap CR") + nodeTemplateConfigMap, err := configmap.Pull(HubAPIClient, tsparams.DefaultAINodeTemplatesConfigMapName, + ranparam.AcmOperatorNamespace) + Expect(err).ToNot(HaveOccurred(), "Failed to find default AI node level templates config map") + + if err == nil { + err = nodeTemplateConfigMap.Delete() + Expect(err).ToNot(HaveOccurred(), "Failed to delete AI node level templates config map") + } + }) + + // Test step 1 expected result validation. + By("verifying installed spoke cluster should still be functional") + _, err = version.GetOCPVersion(Spoke1APIClient) + Expect(err).ToNot(HaveOccurred(), "Failed to get OCP version from spoke and verify spoke cluster access") + + // Test step 2-Update the ztp-test git path to delete the ClusterInstance CR in root level kustimozation.yaml. + By("updating the Argo CD clusters app with the detach AI MNO cluster instance git path") + exists, err := gitdetails.UpdateArgoCdAppGitPath(tsparams.ArgoCdClustersAppName, + tsparams.ZtpTestPathDetachAIMNO, true) + if !exists { + Skip(err.Error()) + } + + Expect(err).ToNot(HaveOccurred(), "Failed to update Argo CD clusters app with new git path") + + // Test step 2 expected results validation. + ValidateAISpokeClusterInstallCRsRemoved() + + // Test teardown. + // Recreate default assisted installer template reference ConfigMap CRs by deleting siteconfig operator pod. + By("deleting siteconfig operator pod running under rhacm namespace on hub cluster. ", func() { + + By("Get the siteconfig operator pod name with label " + tsparams.SiteconfigOperatorPodLabel) + desiredPodName, err := ranhelper.GetPodNameWithLabel(HubAPIClient, ranparam.AcmOperatorNamespace, + tsparams.SiteconfigOperatorPodLabel) + Expect(err).ToNot(HaveOccurred(), "Failed to get siteconfig operator pod name with label "+ + tsparams.SiteconfigOperatorPodLabel+" from "+ranparam.AcmOperatorNamespace+" namespace") + + By("Pull the siteconfig operator pod name from namespace " + ranparam.AcmOperatorNamespace) + siteconfigOperatorPodName, err := pod.Pull(HubAPIClient, desiredPodName, ranparam.AcmOperatorNamespace) + Expect(err).ToNot(HaveOccurred(), "Failed to pull siteconfig operator pod name") + if err == nil { + _, err = siteconfigOperatorPodName.DeleteAndWait(3 * time.Minute) + Expect(err).ToNot(HaveOccurred(), "Failed to delete siteconfig operator pod") + } + }) + + // Teardown test expected results validation. + // The default assisted installer template reference ConfigMap custom resource should be recreated successfully. + By("checking the default assisted installer template reference ConfigMap CRs recreated successfully.", func() { + + By("checking default assisted installer cluster level templates ConfigMap CR exists") + _, err := configmap.Pull(HubAPIClient, tsparams.DefaultAIClusterTemplatesConfigMapName, + ranparam.AcmOperatorNamespace) + Expect(err).ToNot(HaveOccurred(), "Failed to find default AI cluster level templates config map") + + By("checking default assisted installer node level templates ConfigMap CR exists") + _, err = configmap.Pull(HubAPIClient, tsparams.DefaultAINodeTemplatesConfigMapName, + ranparam.AcmOperatorNamespace) + Expect(err).ToNot(HaveOccurred(), "Failed to find default AI node level templates config map") + + By("verifying installed spoke cluster should still be functional") + _, err = version.GetOCPVersion(Spoke1APIClient) + Expect(err).ToNot(HaveOccurred(), "Failed to get OCP version from spoke and verify spoke cluster access") + }) + }) + + // 75376 - Detaching the AI single-node openshift (SNO) spoke cluster. + It("Validate detaching the AI single-node openshift spoke cluster", reportxml.ID("75376"), func() { + + By("checking spoke cluster type") + spokeClusterType, err := rancluster.CheckSpokeClusterType(RANConfig.Spoke1APIClient) + Expect(err).ToNot(HaveOccurred(), "Failed to fetch spoke cluster type") + + if spokeClusterType == ranparam.StandardCluster { + Skip("This test only applies to single-node openshift spoke cluster") + } + + // Test step 1-Update the ztp-test git path to delete the ClusterInstance CR in root level kustimozation.yaml. + By("updating the Argo CD clusters app with the detach AI SNO cluster instance git path") + exists, err := gitdetails.UpdateArgoCdAppGitPath(tsparams.ArgoCdClustersAppName, + tsparams.ZtpTestPathDetachAISNO, true) + if !exists { + Skip(err.Error()) + } + + Expect(err).ToNot(HaveOccurred(), "Failed to update Argo CD clusters app with new git path") + + // Test step 1 expected results validation. + ValidateAISpokeClusterInstallCRsRemoved() + }) + }) + +// ValidateAISpokeClusterInstallCRsRemoved verifies AI spoke cluster install CRs removed and spoke cluster accessible. +func ValidateAISpokeClusterInstallCRsRemoved() { + By("checking the infra env manifests removed on hub", func() { + _, err := assisted.PullInfraEnvInstall(HubAPIClient, RANConfig.Spoke1Name, RANConfig.Spoke1Name) + Expect(err).To(HaveOccurred(), "Found spoke infra env manifests but expected to be removed") + }) + + By("checking the bare metal host manifests removed on hub", func() { + _, err := bmh.Pull(HubAPIClient, RANConfig.Spoke1Name, RANConfig.Spoke1Name) + Expect(err).To(HaveOccurred(), "Found spoke bmh manifests but expected to be removed") + }) + + By("checking the cluster deployment manifests removed on hub", func() { + _, err := hive.PullClusterDeployment(HubAPIClient, RANConfig.Spoke1Name, RANConfig.Spoke1Name) + Expect(err).To(HaveOccurred(), "Found spoke cluster deployment manifests but expected to be removed") + }) + + By("checking the NM state config manifests removed on hub", func() { + nmStateConfigList, err := assisted.ListNmStateConfigs(HubAPIClient, RANConfig.Spoke1Name) + Expect(err).ToNot(HaveOccurred(), "Failed to list NM state config manifests") + Expect(nmStateConfigList).To(BeEmpty(), "Found spoke NM state config manifests but expected to be removed") + }) + + By("checking the klusterlet addon config manifests removed on hub", func() { + _, err := ocm.PullKAC(HubAPIClient, RANConfig.Spoke1Name, RANConfig.Spoke1Name) + Expect(err).To(HaveOccurred(), "Found spoke kac manifests but expected to be removed") + }) + + By("checking the agent cluster install manifests removed on hub", func() { + _, err := assisted.PullAgentClusterInstall(HubAPIClient, RANConfig.Spoke1Name, RANConfig.Spoke1Name) + Expect(err).To(HaveOccurred(), "Found spoke ACI manifests but expected to be removed") + }) + + By("verifying installed spoke cluster should still be functional", func() { + _, err := version.GetOCPVersion(Spoke1APIClient) + Expect(err).ToNot(HaveOccurred(), "Failed to get OCP version from spoke and verify spoke cluster access") + }) +} diff --git a/tests/cnf/ran/internal/rancluster/rancluster.go b/tests/cnf/ran/internal/rancluster/rancluster.go index 7ba473d7b..0304ca331 100644 --- a/tests/cnf/ran/internal/rancluster/rancluster.go +++ b/tests/cnf/ran/internal/rancluster/rancluster.go @@ -9,6 +9,7 @@ import ( "github.com/openshift-kni/eco-goinfra/pkg/clients" "github.com/openshift-kni/eco-goinfra/pkg/nodes" . "github.com/openshift-kni/eco-gotests/tests/cnf/ran/internal/raninittools" + "github.com/openshift-kni/eco-gotests/tests/cnf/ran/internal/ranparam" "github.com/openshift-kni/eco-gotests/tests/system-tests/diskencryption/tsparams" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -125,3 +126,17 @@ func WaitForNumberOfNodes(client *clients.Settings, expected int, timeout time.D return false, nil }) } + +// CheckSpokeClusterType checks and returns a spoke cluster type based on number of control plane nodes. +func CheckSpokeClusterType(client *clients.Settings) (ranparam.ClusterType, error) { + controlPlaneNodesList, err := ListNodesByLabel(client, RANConfig.ControlPlaneLabelMap) + if err != nil { + return "", err + } + + if len(controlPlaneNodesList) == 1 { + return ranparam.SNOCluster, nil + } + + return ranparam.StandardCluster, fmt.Errorf("could not determine spoke cluster type") +} diff --git a/tests/cnf/ran/internal/ranhelper/ranhelper.go b/tests/cnf/ran/internal/ranhelper/ranhelper.go index 4d441080f..d78bb57ac 100644 --- a/tests/cnf/ran/internal/ranhelper/ranhelper.go +++ b/tests/cnf/ran/internal/ranhelper/ranhelper.go @@ -6,8 +6,11 @@ import ( "time" "github.com/golang/glog" + "github.com/openshift-kni/eco-goinfra/pkg/clients" "github.com/openshift-kni/eco-goinfra/pkg/pod" "github.com/openshift-kni/eco-gotests/tests/cnf/ran/internal/ranparam" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" ) @@ -59,3 +62,13 @@ func ExecLocalCommand(timeout time.Duration, command string, args ...string) (st return string(output), err } + +// GetPodNameWithLabel returns a pod name matching pod label selector in a given namespace. +func GetPodNameWithLabel(client *clients.Settings, podNamespace, podLabelSelector string) (string, error) { + podList, err := pod.List(client, podNamespace, metav1.ListOptions{LabelSelector: podLabelSelector}) + + glog.V(ranparam.LogLevel).Infof("podList[0] matching podLabelSelector is '%v'", + podList[0].Definition.Name) + + return podList[0].Definition.Name, err +} diff --git a/tests/cnf/ran/internal/ranparam/const.go b/tests/cnf/ran/internal/ranparam/const.go index b6b163573..9157ce73c 100644 --- a/tests/cnf/ran/internal/ranparam/const.go +++ b/tests/cnf/ran/internal/ranparam/const.go @@ -50,3 +50,13 @@ const ( // MCE is the name of the multicluster engine operator. MCE HubOperatorName = "multicluster-engine" ) + +// ClusterType represents spoke cluster type. +type ClusterType string + +const ( + // SNOCluster represents spoke cluster type as single-node openshift (SNO) cluster. + SNOCluster ClusterType = "sno" + // StandardCluster represents spoke cluster type as multi-node openshift (MNO) cluster. + StandardCluster ClusterType = "standard" +)