diff --git a/ocs_ci/deployment/hosted_cluster.py b/ocs_ci/deployment/hosted_cluster.py index b855dabaaef..ea2f44429af 100644 --- a/ocs_ci/deployment/hosted_cluster.py +++ b/ocs_ci/deployment/hosted_cluster.py @@ -5,6 +5,7 @@ import time from concurrent.futures import ThreadPoolExecutor from ocs_ci.deployment.cnv import CNVInstaller +from ocs_ci.deployment.mce import MCEInstaller from ocs_ci.deployment.deployment import Deployment from ocs_ci.deployment.helpers.hypershift_base import ( HyperShiftBase, @@ -365,6 +366,7 @@ def __init__(self, name): HyperShiftBase.__init__(self) MetalLBInstaller.__init__(self) CNVInstaller.__init__(self) + MCEInstaller.__init__(self) self.name = name if config.ENV_DATA.get("clusters", {}).get(self.name): cluster_path = ( @@ -450,7 +452,12 @@ def deploy_ocp( ) def deploy_dependencies( - self, deploy_acm_hub, deploy_cnv, deploy_metallb, download_hcp_binary + self, + deploy_acm_hub, + deploy_cnv, + deploy_metallb, + download_hcp_binary, + deploy_mce, ): """ Deploy dependencies for hosted OCP cluster @@ -459,6 +466,7 @@ def deploy_dependencies( deploy_cnv: bool Deploy CNV deploy_metallb: bool Deploy MetalLB download_hcp_binary: bool Download HCP binary + deploy_mce: bool Deploy mce """ initial_default_sc = helpers.get_default_storage_class() @@ -480,6 +488,8 @@ def deploy_dependencies( self.deploy_lb() if download_hcp_binary: self.update_hcp_binary() + if deploy_mce: + self.deploy_mce() provider_ocp_version = str( get_semantic_version(get_ocp_version(), only_major_minor=True) diff --git a/ocs_ci/deployment/mce.py b/ocs_ci/deployment/mce.py new file mode 100644 index 00000000000..b3d001c9a9f --- /dev/null +++ b/ocs_ci/deployment/mce.py @@ -0,0 +1,243 @@ +""" +This module contains functionality required for mce installation. +""" + +import logging +import tempfile + +from ocs_ci.framework import config +from ocs_ci.ocs.resources.ocs import OCS +from ocs_ci.ocs.ocp import OCP +from ocs_ci.utility import templating +from ocs_ci.ocs import constants +from ocs_ci.utility.utils import ( + run_cmd, + exec_cmd, +) +from ocs_ci.ocs import exceptions +from ocs_ci.ocs.resources.catalog_source import CatalogSource +from ocs_ci.ocs import ocp +from ocs_ci.utility.utils import get_running_ocp_version + +logger = logging.getLogger(__name__) + + +class MCEInstaller(object): + """ + mce Installer class for mce deployment + """ + + def __init__(self): + self.namespace = constants.MCE_NAMESPACE + self.ns_obj = ocp.OCP(kind=constants.NAMESPACES) + self.hypershift_override_image_cm = "hypershift-override-images-new" + self.multicluster_engine = ocp.OCP( + kind="MultiClusterEngine", + resource_name=constants.MULTICLUSTER_ENGINE, + ) + + def create_mce_catalog_source(self): + """ + Creates a catalogsource for mce operator. + + """ + logger.info("Adding CatalogSource for MCE") + mce_catalog_source_data = templating.load_yaml(constants.MCE_CATSRC_YAML) + mce_catalog_source_name = mce_catalog_source_data.get("metadata").get("name") + if config.ENV_DATA.get("mce_image"): + mce_image_tag = config.ENV_DATA.get("mce_image") + mce_catalog_source_data["spec"]["image"] = mce_image_tag + mce_catalog_source_manifest = tempfile.NamedTemporaryFile( + mode="w+", prefix="mce_catalog_source_manifest", delete=False + ) + templating.dump_data_to_temp_yaml( + mce_catalog_source_data, mce_catalog_source_manifest.name + ) + run_cmd(f"oc apply -f {mce_catalog_source_manifest.name}", timeout=2400) + mce_catalog_source = CatalogSource( + resource_name=mce_catalog_source_name, + namespace=constants.MARKETPLACE_NAMESPACE, + ) + + # Wait for catalog source is ready + mce_catalog_source.wait_for_state("READY") + + def create_mce_namespace(self): + """ + Creates the namespace for mce resources + + Raises: + CommandFailed: If the 'oc create' command fails. + """ + try: + logger.info(f"Creating namespace {self.namespace} for mce resources") + namespace_yaml_file = templating.load_yaml(constants.MCE_NAMESPACE_YAML) + namespace_yaml = OCS(**namespace_yaml_file) + namespace_yaml.create() + logger.info(f"MCE namespace {self.namespace} was created successfully") + except exceptions.CommandFailed as ef: + if ( + f'project.project.openshift.io "{self.namespace}" already exists' + in str(ef) + ): + logger.info(f"Namespace {self.namespace} already present") + raise ef + + def create_multiclusterengine_operator(self): + """ + Creates multiclusterengine operator + + """ + operatorgroup_yaml_file = templating.load_yaml(constants.MCE_OPERATOR_YAM) + operatorgroup_yaml = OCS(**operatorgroup_yaml_file) + try: + operatorgroup_yaml.create() + logger.info("mce OperatorGroup created successfully") + except exceptions.CommandFailed as ef: + if "multiclusterengine exists" in str(ef): + logger.info("multiclusterengine already exists") + + cmd = "oc get mce multiclusterengine -o jsonpath='{.status}'" + cmd_res = exec_cmd(cmd, shell=True) + if cmd_res.returncode != 0: + logger.error(f"Failed to get multicluster engine status\n{cmd_res.stderr}") + else: + logger.info( + f"Multicluster engine version: {cmd_res.stdout.decode('utf-8')}" + ) + assert ( + cmd_res.stdout.decode("utf-8") == "Available" + ), "multiclusterengine is not is 'Available' status" + + def create_mce_subscription(self): + """ + Creates subscription for mce operator + + """ + mce_subscription_yaml_data = templating.load_yaml( + constants.MCE_SUBSCRIPTION_YAML + ) + + if config.DEPLOYMENT.get("mce_latest_stable"): + mce_subscription_yaml_data["spec"][ + "source" + ] = constants.OPERATOR_CATALOG_SOURCE_NAME + mce_sub_channel = "stable-2.7" + + mce_subscription_yaml_data["spec"]["channel"] = f"{mce_sub_channel}" + mce_subscription_manifest = tempfile.NamedTemporaryFile( + mode="w+", prefix="mce_subscription_manifest", delete=False + ) + templating.dump_data_to_temp_yaml( + mce_subscription_yaml_data, mce_subscription_manifest.name + ) + logger.info("Creating subscription for mce operator") + run_cmd(f"oc create -f {mce_subscription_manifest.name}") + OCP( + kind=constants.SUBSCRIPTION_COREOS, + namespace=self.namespace, + resource_name=constants.MCE_OPERATOR, + ).check_resource_existence( + should_exist=True, resource_name=constants.MCE_OPERATOR + ) + + def check_hypershift_namespace(self): + """ + Check hypershift namespace created + + """ + logger.info(f"hypershift namespace {self.namespace} was created successfully") + is_hypershift_ns_available = self.ns_obj.is_exist( + resource_name=constants.HYPERSHIFT_NAMESPACE, + ) + return is_hypershift_ns_available + + def check_supported_versions(self): + """ + Check supported ocp versions for hcp cluster creation + + """ + configmaps_obj = OCP( + kind=constants.CONFIGMAP, + namespace=constants.HYPERSHIFT_NAMESPACE, + ) + + assert configmaps_obj.is_exist( + resource_name=constants.SUPPORTED_VERSIONS_CONFIGMAP + ), f"Configmap {constants.SUPPORTED_VERSIONS_CONFIGMAP} does not exist in hypershift namespace" + + cmd = "oc get cm -n hypershift supported-versions -o jsonpath='{.data.supported-versions}'" + cmd_res = exec_cmd(cmd, shell=True) + if cmd_res.returncode == 0: + supported_versions = cmd_res.stdout.decode("utf-8") + logger.info(f"Supported versions: {supported_versions}") + + if not get_running_ocp_version() in supported_versions: + self.create_image_override() + + def create_image_override(self): + """ + Create hypershift image override cm + """ + # Create image override configmap using the image override json + cmd = ( + f"oc create cm {self.hypershift_override_image_cm} --from-file={constants.IMAGE_OVERRIDE_JSON}" + "-n {self.namespace}" + ) + cmd_res = exec_cmd(cmd, shell=True) + assert cmd_res.returncode == 0 + + configmaps_obj = OCP( + kind=constants.CONFIGMAP, + namespace=constants.HYPERSHIFT_NAMESPACE, + ) + + assert configmaps_obj.is_exist( + resource_name=self.hypershift_override_image_cm + ), f"Configmap {self.hypershift_override_image_cm} does not exist in hypershift namespace" + + # annotate multicluster engine operator with the override cm + self.multicluster_engine.annotate( + annotation=f"imageOverridesCM={self.hypershift_override_image_cm}" + ) + self.multicluster_engine.wait_until_running() + + def deploy_mce(self, check_mce_deployed=False, check_mce_ready=False): + """ + Installs mce enabling software emulation. + + Args: + check_mce_deployed (bool): If True, check if mce is already deployed. If so, skip the deployment. + check_mce_ready (bool): If True, check if mce is ready. If so, skip the deployment. + """ + if check_mce_deployed: + if self.mce_hyperconverged_installed(): + logger.info("mce operator is already deployed, skipping the deployment") + return + + if check_mce_ready: + if self.post_install_verification(raise_exception=False): + logger.info("mce operator ready, skipping the deployment") + return + + logger.info("Installing mce") + # we create catsrc with nightly builds only if config.DEPLOYMENT does not have mce_latest_stable + if not config.DEPLOYMENT.get("mce_latest_stable"): + # Create mce catalog source + self.create_mce_catalog_source() + # Create multicluster-engine namespace + self.create_mce_namespace() + # create mce subscription + self.create_mce_subscription() + # Deploy the multiclusterengine CR + self.create_multiclusterengine_operator() + # Check hypershift ns created + if not self.check_hypershift_namespace(): + cmd = ( + "oc patch mce multiclusterengine " + '-p \'{"spec":{"overrides":{"components":[' + '{"enabled":true, "name":"hypershift"}' + "]}}}' --type=merge" + ) + cmd_res = exec_cmd(cmd, shell=True) + assert cmd_res.returncode == 0 diff --git a/ocs_ci/ocs/constants.py b/ocs_ci/ocs/constants.py index 2c14f51b679..6580c0575df 100644 --- a/ocs_ci/ocs/constants.py +++ b/ocs_ci/ocs/constants.py @@ -33,6 +33,7 @@ TEMPLATE_DEPLOYMENT_DIR_LVMO = os.path.join(TEMPLATE_DIR, "lvmo-deployment") TEMPLATE_MULTICLUSTER_DIR = os.path.join(TEMPLATE_DEPLOYMENT_DIR, "multicluster") TEMPLATE_DEPLOYMENT_DIR_CNV = os.path.join(TEMPLATE_DIR, "cnv-deployment") +TEMPLATE_DEPLOYMENT_DIR_MCE = os.path.join(TEMPLATE_DIR, "mce-deployment") TEMPLATE_DEPLOYMENT_DIR_METALLB = os.path.join(TEMPLATE_DIR, "metallb-deployment") TEMPLATE_DEPLOYMENT_DIR_NMSTATE = os.path.join(TEMPLATE_DIR, "nmstate-deployment") TEMPLATE_DEPLOYMENT_DIR_INF = os.path.join( @@ -2709,6 +2710,17 @@ SUBCTL_DOWNSTREAM_URL = "registry.redhat.io/rhacm2/" # Multicluster related +MCE_NAMESPACE = "multicluster-engine" +MCE_NAMESPACE_YAML = os.path.join(TEMPLATE_DEPLOYMENT_DIR_MCE, "mce_namespace.yaml") +MCE_CATSRC_YAML = os.path.join(TEMPLATE_DEPLOYMENT_DIR_MCE, "mce_catsrc.yaml") +MCE_SUBSCRIPTION_YAML = os.path.join( + TEMPLATE_DEPLOYMENT_DIR_MCE, "mce_subscription.yaml" +) +MCE_OPERATOR = "multicluster-engine-operator" +MCE_OPERATOR_YAML = os.path.join(TEMPLATE_DEPLOYMENT_DIR_MCE, "mce_operator.yaml") +HYPERSHIFT_NAMESPACE = "hypershift" +SUPPORTED_VERSIONS_CONFIGMAP = "supported-versions" +IMAGE_OVERRIDE_JSON = os.path.join(TEMPLATE_DEPLOYMENT_DIR_MCE, "image-override.json") # OpenSSL Certificate parameters OPENSSL_KEY_SIZE = 2048 diff --git a/ocs_ci/templates/mce-deployment/image-override.json b/ocs_ci/templates/mce-deployment/image-override.json new file mode 100644 index 00000000000..724a4e68445 --- /dev/null +++ b/ocs_ci/templates/mce-deployment/image-override.json @@ -0,0 +1,8 @@ +[ + { + "image-name": "rhtap-hypershift-operator", + "image-tag": "17a958f", + "image-remote": "quay.io/acm-d", + "image-key": "hypershift_operator" + } + ] diff --git a/ocs_ci/templates/mce-deployment/mce_catsrc.yaml b/ocs_ci/templates/mce-deployment/mce_catsrc.yaml new file mode 100644 index 00000000000..98fdae0e33d --- /dev/null +++ b/ocs_ci/templates/mce-deployment/mce_catsrc.yaml @@ -0,0 +1,8 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: CatalogSource +metadata: + name: mce-catalogsource + namespace: openshift-marketplace +spec: + image: PLACE_HOLDER2.13.0-DOWNSTREAM-2024-12-09-22-26-41 + sourceType: grpc diff --git a/ocs_ci/templates/mce-deployment/mce_namespace.yaml b/ocs_ci/templates/mce-deployment/mce_namespace.yaml new file mode 100644 index 00000000000..1c6928bf0a6 --- /dev/null +++ b/ocs_ci/templates/mce-deployment/mce_namespace.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: multicluster-engine + labels: + openshift.io/cluster-monitoring: "true" diff --git a/ocs_ci/templates/mce-deployment/mce_operator.yaml b/ocs_ci/templates/mce-deployment/mce_operator.yaml new file mode 100644 index 00000000000..a3afc5d5e4a --- /dev/null +++ b/ocs_ci/templates/mce-deployment/mce_operator.yaml @@ -0,0 +1,10 @@ +apiVersion: multicluster.openshift.io/v1 +kind: MultiClusterEngine +metadata: + name: multiclusterengine +spec: + overrides: + components: + - name: local-cluster + enabled: true + availabilityConfig: High diff --git a/ocs_ci/templates/mce-deployment/mce_subscription.yaml b/ocs_ci/templates/mce-deployment/mce_subscription.yaml new file mode 100644 index 00000000000..32b15714637 --- /dev/null +++ b/ocs_ci/templates/mce-deployment/mce_subscription.yaml @@ -0,0 +1,11 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: Subscription +metadata: + name: multicluster-engine + namespace: multicluster-engine +spec: + channel: stable + installPlanApproval: Automatic + name: multicluster-engine + source: mce-catalogsource + sourceNamespace: openshift-marketplace diff --git a/tests/libtest/test_provider_create_hosted_cluster.py b/tests/libtest/test_provider_create_hosted_cluster.py index 7e9c564cb09..3203d004d4c 100644 --- a/tests/libtest/test_provider_create_hosted_cluster.py +++ b/tests/libtest/test_provider_create_hosted_cluster.py @@ -312,3 +312,29 @@ def test_verify_native_storage(self): assert ( storage_class in storage_class_classes ), "Storage classes ae not created as expected" + + @runs_on_provider + @hci_provider_required + def test_deploy_mce(self): + """ + Test deploy mce without installting acm + """ + logger.info("Test deploy mce without deploying ACM") + HypershiftHostedOCP("dummy").deploy_dependencies( + deploy_acm_hub=False, + deploy_cnv=False, + deploy_metallb=False, + download_hcp_binary=False, + deploy_mce=True, + ) + assert validate_acm_hub_install(), "ACM not installed or MCE not configured" + + @hci_provider_required + def test_provider_deploy_OCP_hosted_skip_acm(self): + """ + Test deploy hosted OCP on provider platform with cnv ready beforehand + """ + logger.info("Test deploy hosted OCP on provider platform with cnv ready") + cluster_name = list(config.ENV_DATA["clusters"].keys())[-1] + + HypershiftHostedOCP(cluster_name).deploy_ocp(deploy_acm_hub=False)