def get_available_storage(self): """Get the largest available SCM and NVMe storage common to all servers. Raises: ServerFailed: if output from the dmg storage scan is missing or not in the expected format Returns: dict: a dictionary of the largest available storage common to all engines per storage type, "scm"s and "nvme", in bytes """ storage = {} storage_capacity = self.information.get_storage_capacity(self.manager.job.engine_params) self.log.info("Largest storage size available per engine:") for key in sorted(storage_capacity): # Use the same storage size across all engines storage[key] = min(storage_capacity[key]) self.log.info(" %-4s: %s", key.upper(), get_display_size(storage[key])) return storage
def autosize_pool_params(self, size, tier_ratio, scm_size, nvme_size, min_targets=1, quantity=1): """Update any pool size parameter ending in a %. Use the current NVMe and SCM storage sizes to assign values to the size, scm_size, and or nvme_size dmg pool create arguments which end in "%". The numerical part of these arguments will be used to assign a value that is X% of the available storage capacity. The updated size and nvme_size arguments will be assigned values that are multiples of 1GiB times the number of targets assigned to each server engine. If needed the number of targets will be reduced (to not exceed min_targets) in order to support the requested size. An optional number of expected pools (quantity) can also be specified to divide the available storage capacity. Note: depending upon the inputs this method may return dmg pool create parameter combinations that are not supported, e.g. tier_ratio + nvme_size. This is intended to allow testing of these combinations. Args: size (object): the str, int, or None value for the dmg pool create size parameter. tier_ratio (object): the int or None value for the dmg pool create size parameter. scm_size (object): the str, int, or None value for the dmg pool create scm_size parameter. nvme_size (object): the str, int, or None value for the dmg pool create nvme_size parameter. min_targets (int, optional): the minimum number of targets per engine that can be configured. Defaults to 1. quantity (int, optional): Number of pools to account for in the size calculations. The pool size returned is only for a single pool. Defaults to 1. Raises: ServerFailed: if there was a error obtaining auto-sized TestPool parameters. AutosizeCancel: if a valid pool parameter size could not be obtained Returns: dict: the parameters for a TestPool object. """ # Adjust any pool size parameter by the requested percentage params = {"tier_ratio": tier_ratio} adjusted = {"size": size, "scm_size": scm_size, "nvme_size": nvme_size} keys = [ key for key in ("size", "scm_size", "nvme_size") if adjusted[key] is not None and str(adjusted[key]).endswith("%")] if keys: # Verify the minimum number of targets configured per engine targets = min(self.manager.job.get_engine_values("targets")) if targets < min_targets: raise ServerFailed( "Minimum target quantity ({}) exceeds current target " "quantity ({})".format(min_targets, targets)) self.log.info("-" * 100) pool_msg = "{} pool{}".format(quantity, "s" if quantity > 1 else "") self.log.info( "Autosizing TestPool parameters ending with a \"%%\" for %s:", pool_msg) for key in ("size", "scm_size", "nvme_size"): self.log.info(" - %-9s : %s (%s)", key, adjusted[key], key in keys) # Determine the largest SCM and NVMe pool sizes can be used with # this server configuration with an optionally applied ratio. try: available_storage = self.get_available_storage() except ServerFailed as error: raise ServerFailed("Error obtaining available storage") from error # Determine the SCM and NVMe size limits for the size and tier_ratio # arguments for the total number of engines if tier_ratio is None: # Use the default value if not provided tier_ratio = 6 engine_qty = len(self.manager.job.engine_params) * len(self._hosts) available_storage["size"] = min( engine_qty * available_storage["nvme"], (engine_qty * available_storage["scm"]) / float(tier_ratio / 100) ) available_storage["tier_ratio"] = available_storage["size"] * float(tier_ratio / 100) self.log.info( "Largest storage size available for %s engines with a %.2f%% " "tier_ratio:", engine_qty, tier_ratio) self.log.info( " - NVME : %s", get_display_size(available_storage["size"])) self.log.info( " - SCM : %s", get_display_size(available_storage["tier_ratio"])) self.log.info( " - COMBINED : %s", get_display_size(available_storage["size"] + available_storage["tier_ratio"])) # Apply any requested percentages to the pool parameters available = { "size": {"size": available_storage["size"], "type": "NVMe"}, "scm_size": {"size": available_storage["scm"], "type": "SCM"}, "nvme_size": {"size": available_storage["nvme"], "type": "NVMe"} } self.log.info("Adjusted pool sizes for %s:", pool_msg) for key in keys: try: ratio = int(str(adjusted[key]).replace("%", "")) except NameError as error: raise ServerFailed( "Invalid '{}' format: {}".format(key, adjusted[key])) from error adjusted[key] = (available[key]["size"] * float(ratio / 100)) / quantity self.log.info( " - %-9s : %-4s storage adjusted by %.2f%%: %s", key, available[key]["type"], ratio, get_display_size(adjusted[key])) # Display the pool size increment value for each size argument increment = { "size": human_to_bytes("1GiB"), "scm_size": human_to_bytes("16MiB"), "nvme_size": human_to_bytes("1GiB")} self.log.info("Increment sizes per target:") for key in keys: self.log.info(" - %-9s : %s", key, get_display_size(increment[key])) # Adjust the size to use a SCM/NVMe target multiplier self.log.info("Pool sizes adjusted to fit by increment sizes:") adjusted_targets = targets for key in keys: multiplier = math.floor(adjusted[key] / increment[key]) params[key] = multiplier * increment[key] self.log.info( " - %-9s : %s * %s = %s", key, multiplier, increment[key], get_display_size(params[key])) if multiplier < adjusted_targets: adjusted_targets = multiplier if adjusted_targets < min_targets: raise AutosizeCancel( "Unable to autosize the {} pool parameter due to " "exceeding the minimum of {} targets: {}".format( key, min_targets, adjusted_targets)) if key == "size": tier_ratio_size = params[key] * float(tier_ratio / 100) self.log.info( " - %-9s : %.2f%% tier_ratio = %s", key, tier_ratio, get_display_size(tier_ratio_size)) params[key] += tier_ratio_size self.log.info( " - %-9s : NVMe + SCM = %s", key, get_display_size(params[key])) params[key] = bytes_to_human(params[key], binary=True) # Reboot the servers if a reduced number of targets is required if adjusted_targets < targets: self.log.info( "Updating targets per server engine: %s -> %s", targets, adjusted_targets) self.set_config_value("targets", adjusted_targets) self.stop() self.start() self.log.info("-" * 100) return params
def get_storage_capacity(self, engine_params): """Get the configured SCM and NVMe storage per server engine. Only sums up capacities of devices that have been specified in the server configuration file. Args: engine_params (list): a list of configuration parameters for each engine Raises: ServerFailed: if output from the dmg storage scan is missing or not in the expected format Returns: dict: a dictionary of each engine's smallest SCM and NVMe storage capacity in bytes, e.g. { "scm": [3183575302144, 6367150604288], "nvme": [1500312748032, 1500312748032] } """ self._check_information("storage", "HostStorage") device_capacity = {"nvme": {}, "scm": {}} try: for entry in self.storage["response"]["HostStorage"].values(): # Collect a list of sizes for each NVMe device if entry["storage"]["nvme_devices"]: for device in entry["storage"]["nvme_devices"]: if device["pci_addr"] not in device_capacity["nvme"]: device_capacity["nvme"][device["pci_addr"]] = [] device_capacity["nvme"][device["pci_addr"]].append(0) for namespace in device["namespaces"]: device_capacity["nvme"][device["pci_addr"]][-1] += \ namespace["size"] # Collect a list of sizes for each SCM device if entry["storage"]["scm_namespaces"]: for device in entry["storage"]["scm_namespaces"]: if device["blockdev"] not in device_capacity["scm"]: device_capacity["scm"][device["blockdev"]] = [] device_capacity["scm"][device["blockdev"]].append( device["size"]) except KeyError as error: raise ServerFailed( "ServerInformation: Error obtaining storage data") from error self.log.info("Detected device capacities:") for category in sorted(device_capacity): for device in sorted(device_capacity[category]): sizes = [ get_display_size(size) for size in device_capacity[category][device] ] self.log.info(" %-4s for %s : %s", category.upper(), device, sizes) # Determine what storage is currently configured for each engine storage_capacity = {"scm": [], "nvme": []} for engine_param in engine_params: # Get the NVMe storage configuration for this engine bdev_list = engine_param.get_value("bdev_list") storage_capacity["nvme"].append(0) for device in bdev_list: if device in device_capacity["nvme"]: storage_capacity["nvme"][-1] += min( device_capacity["nvme"][device]) # Get the SCM storage configuration for this engine scm_size = engine_param.get_value("scm_size") scm_list = engine_param.get_value("scm_list") if scm_list: storage_capacity["scm"].append(0) for device in scm_list: scm_dev = os.path.basename(device) if scm_dev in device_capacity["scm"]: storage_capacity["scm"][-1] += min( device_capacity["scm"][scm_dev]) else: storage_capacity["scm"].append( human_to_bytes("{}GB".format(scm_size))) self.log.info("Detected engine capacities:") for category in sorted(storage_capacity): sizes = [ get_display_size(size) for size in storage_capacity[category] ] self.log.info(" %-4s : %s", category.upper(), sizes) return storage_capacity