Example #1
0
    def _prepare_rhel_node_conf(self):
        """
        Merge default RHEL node config with the user provided
        config

        """
        conf = self.read_default_config(constants.RHEL_WORKERS_CONF)
        default_conf = conf.get('ENV_DATA')
        merge_dict(default_conf, self.node_conf)
        logger.info(f"Merged dict is {default_conf}")
        return default_conf
Example #2
0
 def test_merge_dict(self):
     objA = dict(
         a_dict=dict(
             another_list=[],
             another_string="greetings",
         ),
         a_list=[0, 1, 2],
     )
     objB = dict(
         a_dict=dict(a_third_string="salutations", ),
         a_list=[1, 2, 3],
         a_string="string",
     )
     expected = dict(
         a_dict=dict(
             another_list=[],
             another_string="greetings",
             a_third_string="salutations",
         ),
         a_list=[1, 2, 3],
         a_string="string",
     )
     result = framework.merge_dict(objA, objB)
     assert objA is result
     assert result == expected
Example #3
0
    def merge_flexy_env(self):
        """
        Update the ocs-osp.env file with the user supplied values.
        This function assumes that flexy-ocs-private repo has been
        already cloned

        """
        config_parser = configparser.ConfigParser()
        # without this,config parser would convert all
        # uppercase keys to lowercase
        config_parser.optionxform = str

        with open(self.flexy_env_file, "r") as fp:
            # we need to add section to make
            # config parser happy, this will be removed
            # while writing back to file
            file_content = "[root]\n" + fp.read()

        config_parser.read_string(file_content)
        # Iterate over config_parser keys, if same key is present
        # in user supplied dict update config_parser
        for ele in config_parser.items('root'):
            if ele[0] in config.FLEXY:
                # For LAUNCHER_VARS we need to merge the
                # user provided dict with default obtained
                # from env file
                if ele[0] == 'LAUNCHER_VARS':
                    config_parser.set(
                        'root', ele[0],
                        str(
                            merge_dict(
                                yaml.safe_load(config_parser['root'][ele[0]]),
                                config.FLEXY[ele[0]])))
                else:
                    config_parser.set('root', ele[0], config.FLEXY[ele[0]])
                logger.info(f"env updated {ele[0]}:{config.FLEXY[ele[0]]}")

        # write the updated config_parser content back to the env file
        tmp_file = os.path.join(self.flexy_host_private_conf_dir_path,
                                f"{constants.FLEXY_DEFAULT_ENV_FILE}.tmp")
        with open(tmp_file, "w") as fp:
            src = io.StringIO()
            dst = io.StringIO()
            config_parser.write(src)
            src.seek(0)
            # config_parser introduces spaces around '='
            # sign, we need to remove that else flexy podman fails
            for line in src.readlines():
                dst.write(line.replace(' = ', '=', 1))
            dst.seek(0)
            # eliminate first line which has section [root]
            # last line ll have a '\n' so that needs to be
            # removed
            fp.write("".join(dst.readlines()[1:-1]))

        # Move this tempfile to original file
        os.rename(tmp_file, self.flexy_env_file)
Example #4
0
    def merge_flexy_env(self):
        """
        Update the Flexy env file with the user supplied values.
        This function assumes that the flexy_env_file is available
        (e.g. flexy-ocs-private repo has been already cloned).

        """
        config_parser = configparser.ConfigParser()
        # without this,config parser would convert all
        # uppercase keys to lowercase
        config_parser.optionxform = str

        with open(self.flexy_env_file, "r") as fp:
            # we need to add section to make
            # config parser happy, this will be removed
            # while writing back to file
            file_content = "[root]\n" + fp.read()

        config_parser.read_string(file_content)
        # add or update all values from config.FLEXY section into Flexy env
        # configuration file
        for key in config.FLEXY:
            logger.info(
                f"Flexy env file - updating: {key}={config.FLEXY[key]}")
            # For LAUNCHER_VARS we need to merge the
            # user provided dict with default obtained
            # from env file
            if key in ("LAUNCHER_VARS", "BUSHSLICER_CONFIG"):
                config_parser.set(
                    "root",
                    key,
                    str(
                        merge_dict(
                            yaml.safe_load(config_parser["root"][key]),
                            config.FLEXY[key],
                        )),
                )
            else:
                config_parser.set("root", key, f"{config.FLEXY[key]}")

        # write the updated config_parser content to updated env file
        with open(constants.FLEXY_ENV_FILE_UPDATED_PATH, "w") as fp:
            src = io.StringIO()
            dst = io.StringIO()
            config_parser.write(src)
            src.seek(0)
            # config_parser introduces spaces around '='
            # sign, we need to remove that else flexy podman fails
            for line in src.readlines():
                dst.write(line.replace(" = ", "=", 1))
            dst.seek(0)
            # eliminate first line which has section [root]
            # last line ll have a '\n' so that needs to be
            # removed
            fp.write("".join(dst.readlines()[1:-1]))
Example #5
0
    def deploy_ocs_via_operator(self, image=None):
        """
        Method for deploy OCS via OCS operator

        Args:
            image (str): Image of ocs registry.

        """
        ui_deployment = config.DEPLOYMENT.get("ui_deployment")
        live_deployment = config.DEPLOYMENT.get("live_deployment")
        arbiter_deployment = config.DEPLOYMENT.get("arbiter_deployment")

        if ui_deployment:
            self.deployment_with_ui()
            # Skip the rest of the deployment when deploy via UI
            return
        else:
            logger.info("Deployment of OCS via OCS operator")
            self.label_and_taint_nodes()

        if config.DEPLOYMENT.get("local_storage"):
            setup_local_storage(storageclass=self.DEFAULT_STORAGECLASS_LSO)

        logger.info("Creating namespace and operator group.")
        run_cmd(f"oc create -f {constants.OLM_YAML}")
        if config.ENV_DATA["platform"] == constants.IBMCLOUD_PLATFORM:
            ibmcloud.add_deployment_dependencies()
            if not live_deployment:
                create_ocs_secret(self.namespace)
        if not live_deployment:
            create_catalog_source(image)
        self.subscribe_ocs()
        operator_selector = get_selector_for_ocs_operator()
        subscription_plan_approval = config.DEPLOYMENT.get(
            "subscription_plan_approval")
        package_manifest = PackageManifest(
            resource_name=defaults.OCS_OPERATOR_NAME,
            selector=operator_selector,
            subscription_plan_approval=subscription_plan_approval,
        )
        package_manifest.wait_for_resource(timeout=300)
        channel = config.DEPLOYMENT.get("ocs_csv_channel")
        csv_name = package_manifest.get_current_csv(channel=channel)
        csv = CSV(resource_name=csv_name, namespace=self.namespace)
        if (config.ENV_DATA["platform"] == constants.IBMCLOUD_PLATFORM
                and not live_deployment):
            csv.wait_for_phase("Installing", timeout=720)
            logger.info("Sleeping for 30 seconds before applying SA")
            time.sleep(30)
            link_all_sa_and_secret(constants.OCS_SECRET, self.namespace)
            logger.info("Deleting all pods in openshift-storage namespace")
            exec_cmd(f"oc delete pod --all -n {self.namespace}")
        csv.wait_for_phase("Succeeded", timeout=720)
        ocp_version = float(get_ocp_version())
        if config.ENV_DATA["platform"] == constants.IBMCLOUD_PLATFORM:
            config_map = ocp.OCP(
                kind="configmap",
                namespace=self.namespace,
                resource_name=constants.ROOK_OPERATOR_CONFIGMAP,
            )
            config_map.get(retry=10, wait=5)
            config_map_patch = (
                '\'{"data": {"ROOK_CSI_KUBELET_DIR_PATH": "/var/data/kubelet"}}\''
            )
            logger.info("Patching config map to change KUBLET DIR PATH")
            exec_cmd(
                f"oc patch configmap -n {self.namespace} "
                f"{constants.ROOK_OPERATOR_CONFIGMAP} -p {config_map_patch}")
            if config.DEPLOYMENT.get("create_ibm_cos_secret", True):
                logger.info("Creating secret for IBM Cloud Object Storage")
                with open(constants.IBM_COS_SECRET_YAML, "r") as cos_secret_fd:
                    cos_secret_data = yaml.load(cos_secret_fd,
                                                Loader=yaml.SafeLoader)
                key_id = config.AUTH["ibmcloud"]["ibm_cos_access_key_id"]
                key_secret = config.AUTH["ibmcloud"][
                    "ibm_cos_secret_access_key"]
                cos_secret_data["data"]["IBM_COS_ACCESS_KEY_ID"] = key_id
                cos_secret_data["data"][
                    "IBM_COS_SECRET_ACCESS_KEY"] = key_secret
                cos_secret_data_yaml = tempfile.NamedTemporaryFile(
                    mode="w+", prefix="cos_secret", delete=False)
                templating.dump_data_to_temp_yaml(cos_secret_data,
                                                  cos_secret_data_yaml.name)
                exec_cmd(f"oc create -f {cos_secret_data_yaml.name}")

        # Modify the CSV with custom values if required
        if all(key in config.DEPLOYMENT
               for key in ("csv_change_from", "csv_change_to")):
            modify_csv(
                csv=csv_name,
                replace_from=config.DEPLOYMENT["csv_change_from"],
                replace_to=config.DEPLOYMENT["csv_change_to"],
            )

        # create custom storage class for StorageCluster CR if necessary
        if self.CUSTOM_STORAGE_CLASS_PATH is not None:
            with open(self.CUSTOM_STORAGE_CLASS_PATH, "r") as custom_sc_fo:
                custom_sc = yaml.load(custom_sc_fo, Loader=yaml.SafeLoader)
            # set value of DEFAULT_STORAGECLASS to mach the custom storage cls
            self.DEFAULT_STORAGECLASS = custom_sc["metadata"]["name"]
            run_cmd(f"oc create -f {self.CUSTOM_STORAGE_CLASS_PATH}")

        # creating StorageCluster
        if config.DEPLOYMENT.get("kms_deployment"):
            kms = KMS.get_kms_deployment()
            kms.deploy()
        cluster_data = templating.load_yaml(constants.STORAGE_CLUSTER_YAML)

        # Figure out all the OCS modules enabled/disabled
        # CLI parameter --disable-components takes the precedence over
        # anything which comes from config file
        if config.ENV_DATA.get("disable_components"):
            for component in config.ENV_DATA["disable_components"]:
                config.COMPONENTS[f"disable_{component}"] = True
                logger.warning(f"disabling: {component}")

        # Update cluster_data with respective component enable/disable
        for key in config.COMPONENTS.keys():
            comp_name = constants.OCS_COMPONENTS_MAP[key.split("_")[1]]
            if config.COMPONENTS[key]:
                if "noobaa" in key:
                    merge_dict(
                        cluster_data,
                        {
                            "spec": {
                                "multiCloudGateway": {
                                    "reconcileStrategy": "ignore"
                                }
                            }
                        },
                    )
                else:
                    merge_dict(
                        cluster_data,
                        {
                            "spec": {
                                "managedResources": {
                                    f"{comp_name}": {
                                        "reconcileStrategy": "ignore"
                                    }
                                }
                            }
                        },
                    )

        if arbiter_deployment:
            cluster_data["spec"]["arbiter"] = {}
            cluster_data["spec"]["nodeTopologies"] = {}
            cluster_data["spec"]["arbiter"]["enable"] = True
            cluster_data["spec"]["nodeTopologies"][
                "arbiterLocation"] = self.get_arbiter_location()
            cluster_data["spec"]["storageDeviceSets"][0][
                "replica"] = config.DEPLOYMENT.get(
                    "ocs_operator_nodes_to_label", 4)

        cluster_data["metadata"]["name"] = config.ENV_DATA[
            "storage_cluster_name"]

        deviceset_data = cluster_data["spec"]["storageDeviceSets"][0]
        device_size = int(
            config.ENV_DATA.get("device_size", defaults.DEVICE_SIZE))

        logger.info(
            "Flexible scaling is available from version 4.7 on LSO cluster with less than 3 zones"
        )
        ocs_version = config.ENV_DATA["ocs_version"]
        zone_num = get_az_count()
        if (config.DEPLOYMENT.get("local_storage")
                and Version.coerce(ocs_version) >= Version.coerce("4.7")
                and zone_num < 3):
            cluster_data["spec"]["flexibleScaling"] = True
            # https://bugzilla.redhat.com/show_bug.cgi?id=1921023
            cluster_data["spec"]["storageDeviceSets"][0]["count"] = 3
            cluster_data["spec"]["storageDeviceSets"][0]["replica"] = 1

        # set size of request for storage
        if self.platform.lower() == constants.BAREMETAL_PLATFORM:
            pv_size_list = helpers.get_pv_size(
                storageclass=self.DEFAULT_STORAGECLASS_LSO)
            pv_size_list.sort()
            deviceset_data["dataPVCTemplate"]["spec"]["resources"]["requests"][
                "storage"] = f"{pv_size_list[0]}"
        else:
            deviceset_data["dataPVCTemplate"]["spec"]["resources"]["requests"][
                "storage"] = f"{device_size}Gi"

        # set storage class to OCS default on current platform
        if self.DEFAULT_STORAGECLASS:
            deviceset_data["dataPVCTemplate"]["spec"][
                "storageClassName"] = self.DEFAULT_STORAGECLASS

        ocs_version = float(config.ENV_DATA["ocs_version"])

        # StorageCluster tweaks for LSO
        if config.DEPLOYMENT.get("local_storage"):
            cluster_data["spec"]["manageNodes"] = False
            cluster_data["spec"]["monDataDirHostPath"] = "/var/lib/rook"
            deviceset_data["name"] = constants.DEFAULT_DEVICESET_LSO_PVC_NAME
            deviceset_data["portable"] = False
            deviceset_data["dataPVCTemplate"]["spec"][
                "storageClassName"] = self.DEFAULT_STORAGECLASS_LSO
            lso_type = config.DEPLOYMENT.get("type")
            if (self.platform.lower() == constants.AWS_PLATFORM
                    and not lso_type == constants.AWS_EBS):
                deviceset_data["count"] = 2
            if (ocp_version >= 4.6) and (ocs_version >= 4.6):
                cluster_data["metadata"]["annotations"] = {
                    "cluster.ocs.openshift.io/local-devices": "true"
                }

        # Allow lower instance requests and limits for OCS deployment
        # The resources we need to change can be found here:
        # https://github.com/openshift/ocs-operator/blob/release-4.5/pkg/deploy-manager/storagecluster.go#L88-L116
        if config.DEPLOYMENT.get("allow_lower_instance_requirements"):
            none_resources = {"Requests": None, "Limits": None}
            deviceset_data["resources"] = deepcopy(none_resources)
            resources = [
                "mon",
                "mds",
                "rgw",
                "mgr",
                "noobaa-core",
                "noobaa-db",
            ]
            if ocs_version >= 4.5:
                resources.append("noobaa-endpoint")
            cluster_data["spec"]["resources"] = {
                resource: deepcopy(none_resources)
                for resource in resources
            }
            if ocs_version >= 4.5:
                cluster_data["spec"]["resources"]["noobaa-endpoint"] = {
                    "limits": {
                        "cpu": 1,
                        "memory": "500Mi"
                    },
                    "requests": {
                        "cpu": 1,
                        "memory": "500Mi"
                    },
                }
        else:
            local_storage = config.DEPLOYMENT.get("local_storage")
            platform = config.ENV_DATA.get("platform", "").lower()
            if local_storage and platform == "aws":
                resources = {
                    "mds": {
                        "limits": {
                            "cpu": 3,
                            "memory": "8Gi"
                        },
                        "requests": {
                            "cpu": 1,
                            "memory": "8Gi"
                        },
                    }
                }
                if ocs_version < 4.5:
                    resources["noobaa-core"] = {
                        "limits": {
                            "cpu": 2,
                            "memory": "8Gi"
                        },
                        "requests": {
                            "cpu": 1,
                            "memory": "8Gi"
                        },
                    }
                    resources["noobaa-db"] = {
                        "limits": {
                            "cpu": 2,
                            "memory": "8Gi"
                        },
                        "requests": {
                            "cpu": 1,
                            "memory": "8Gi"
                        },
                    }
                cluster_data["spec"]["resources"] = resources

        # Enable host network if enabled in config (this require all the
        # rules to be enabled on underlaying platform).
        if config.DEPLOYMENT.get("host_network"):
            cluster_data["spec"]["hostNetwork"] = True

        cluster_data["spec"]["storageDeviceSets"] = [deviceset_data]

        if self.platform == constants.IBMCLOUD_PLATFORM:
            mon_pvc_template = {
                "spec": {
                    "accessModes": ["ReadWriteOnce"],
                    "resources": {
                        "requests": {
                            "storage": "20Gi"
                        }
                    },
                    "storageClassName": self.DEFAULT_STORAGECLASS,
                    "volumeMode": "Filesystem",
                }
            }
            cluster_data["spec"]["monPVCTemplate"] = mon_pvc_template
            # Need to check if it's needed for ibm cloud to set manageNodes
            cluster_data["spec"]["manageNodes"] = False

        if config.ENV_DATA.get("encryption_at_rest"):
            if ocs_version < 4.6:
                error_message = "Encryption at REST can be enabled only on OCS >= 4.6!"
                logger.error(error_message)
                raise UnsupportedFeatureError(error_message)
            logger.info("Enabling encryption at REST!")
            cluster_data["spec"]["encryption"] = {
                "enable": True,
            }
            if config.DEPLOYMENT.get("kms_deployment"):
                cluster_data["spec"]["encryption"]["kms"] = {
                    "enable": True,
                }

        if config.DEPLOYMENT.get("ceph_debug"):
            setup_ceph_debug()
            cluster_data["spec"]["managedResources"] = {
                "cephConfig": {
                    "reconcileStrategy": "ignore"
                }
            }

        cluster_data_yaml = tempfile.NamedTemporaryFile(
            mode="w+", prefix="cluster_storage", delete=False)
        templating.dump_data_to_temp_yaml(cluster_data, cluster_data_yaml.name)
        run_cmd(f"oc create -f {cluster_data_yaml.name}", timeout=1200)
        if config.DEPLOYMENT["infra_nodes"]:
            _ocp = ocp.OCP(kind="node")
            _ocp.exec_oc_cmd(
                command=f"annotate namespace {defaults.ROOK_CLUSTER_NAMESPACE} "
                f"{constants.NODE_SELECTOR_ANNOTATION}")