#!/usr/bin/env python3 import configparser import re import sys import json from collections import OrderedDict bdev_dict = OrderedDict() bdev_dict["bdev_set_options"] = [] bdev_dict["bdev_split_create"] = [] bdev_dict["bdev_nvme_set_options"] = [] bdev_dict["bdev_nvme_attach_controller"] = [] bdev_dict["bdev_nvme_set_hotplug"] = [] bdev_dict["bdev_malloc_create"] = [] bdev_dict["bdev_aio_create"] = [] bdev_dict["bdev_pmem_create"] = [] bdev_dict["bdev_virtio_attach_controller"] = [] vhost_dict = OrderedDict() vhost_dict["vhost_create_scsi_controller"] = [] vhost_dict["vhost_create_blk_controller"] = [] iscsi_dict = OrderedDict() iscsi_dict["iscsi_set_options"] = [] iscsi_dict["iscsi_create_portal_group"] = [] iscsi_dict["iscsi_create_initiator_group"] = [] iscsi_dict["iscsi_create_target_node"] = [] nvmf_dict = OrderedDict() nvmf_dict["nvmf_set_config"] = [] nvmf_dict["nvmf_set_max_subsystems"] = [] nvmf_dict["subsystems"] = [] # dictionary with new config that will be written to new json config file subsystem = { "bdev": bdev_dict, "nvmf": nvmf_dict, "vhost": vhost_dict, "iscsi": iscsi_dict } class OptionOrderedDict(OrderedDict): def __setitem__(self, option, value): if option in self and isinstance(value, list): self[option].extend(value) return super(OptionOrderedDict, self).__setitem__(option, value) no_yes_map = {"no": False, "No": False, "Yes": True, "yes": True} def generate_new_json_config(): json_subsystems = [] bdev_subsystem = {"subsystem": "bdev", "config": []} for method in subsystem['bdev']: for item in subsystem['bdev'][method]: bdev_subsystem['config'].append(item) if bdev_subsystem['config']: json_subsystems.append(bdev_subsystem) nvmf_subsystem = {"subsystem": "nvmf", "config": []} for method in subsystem['nvmf']: for item in subsystem['nvmf'][method]: nvmf_subsystem['config'].append(item) if nvmf_subsystem['config']: json_subsystems.append(nvmf_subsystem) vhost_subsystem = {"subsystem": "vhost", "config": []} for method in subsystem['vhost']: for item in subsystem['vhost'][method]: vhost_subsystem['config'].append(item) if vhost_subsystem['config']: json_subsystems.append(vhost_subsystem) iscsi_subsystem = {"subsystem": "iscsi", "config": []} for method in subsystem['iscsi']: for item in subsystem['iscsi'][method]: iscsi_subsystem['config'].append(item) if iscsi_subsystem['config']: json_subsystems.append(iscsi_subsystem) return {"subsystems": json_subsystems} section_to_subsystem = { "Bdev": subsystem['bdev'], "AIO": subsystem['bdev'], "Malloc": subsystem['bdev'], "Nvme": subsystem['bdev'], "Pmem": subsystem['bdev'], "Split": subsystem['bdev'], "Nvmf": subsystem['nvmf'], "Subsystem": subsystem['nvmf'], "VhostScsi": subsystem['vhost'], "VhostBlk": subsystem['vhost'], "VhostNvme": subsystem['vhost'], "VirtioUser": subsystem['bdev'], "iSCSI": subsystem['iscsi'], "PortalGroup": subsystem['iscsi'], "InitiatorGroup": subsystem['iscsi'], "TargetNode": subsystem['iscsi'] } def set_param(params, cfg_name, value): for param in params: if param[0] != cfg_name: continue if param[1] == "disable_chap": param[3] = True if value == "None" else False elif param[1] == "require_chap": param[3] = True if value in ["CHAP", "Mutual"] else False elif param[1] == "mutual_chap": param[3] = True if value == "Mutual" else False elif param[1] == "chap_group": param[3] = int(value.replace("AuthGroup", "")) elif param[2] == bool: param[3] = True if value in ("yes", "true", "Yes") else False elif param[2] == "hex": param[3] = str(int(value, 16)) elif param[2] == int: param[3] = int(value) elif param[2] == list: param[3].append(value) elif param[2] == "dev_type": if value.lower() == "blk": param[3] = "blk" else: param[3] = param[2](value.replace("\"", "")) def to_json_params(params): out = {} for param in params: if param[3] is not None: out[param[1]] = param[3] return out def get_bdev_options_json(config, section): params = [ ["BdevIoPoolSize", "bdev_io_pool_size", int, 65536], ["BdevIoCacheSize", "bdev_io_cache_size", int, 256] ] for option in config.options("Bdev"): set_param(params, option, config.get("Bdev", option)) return [{"params": to_json_params(params), "method": "bdev_set_options"}] def get_aio_bdev_json(config, section): aio_json = [] value = None for option in config.options("AIO"): if option == "AIO": value = config.get("AIO", option).split("\n") if value is None: return aio_json for item in value: items = re.findall(r"\S+", item) params = {} params['filename'] = items[0] params['name'] = items[1] if len(items) == 3: params['block_size'] = int(items[2]) aio_json.append({ "params": params, "method": "bdev_aio_create" }) return aio_json def get_malloc_bdev_json(config, section): malloc_json = [] params = [ ['NumberOfLuns', '', int, -1], ['LunSizeInMB', '', int, 20], ['BlockSize', '', int, 512] ] for option in config.options("Malloc"): set_param(params, option, config.get("Malloc", option)) for lun in range(0, params[0][3]): malloc_json.append({ "params": { "block_size": params[2][3], "num_blocks": params[1][3] * 1024 * 1024 / params[2][3], "name": "Malloc%s" % lun }, "method": "bdev_malloc_create" }) return malloc_json def get_nvme_bdev_json(config, section): params = [ ["RetryCount", "retry_count", int, 4], ["TimeoutuSec", "timeout_us", int, 0], ["AdminPollRate", "nvme_adminq_poll_period_us", int, 1000000], ["ActionOnTimeout", "action_on_timeout", str, "none"], ["IOPollRate", "nvme_ioq_poll_period_us", int, 0], ["HotplugEnable", "enable", bool, False], ["AdminPollRate", "period_us", int, 1000] ] nvme_json = [] for option in config.options("Nvme"): value = config.get("Nvme", option) if "TransportID" == option: entry = re.findall(r"\S+", value) nvme_name = entry[-1] trtype = re.findall(r"trtype:\S+", value) if trtype: trtype = trtype[0].replace("trtype:", "").replace("\"", "") traddr = re.findall(r"traddr:\S+", value) if traddr: traddr = traddr[0].replace("traddr:", "").replace("\"", "") nvme_json.append({ "params": { "trtype": trtype, "name": nvme_name, "traddr": traddr }, "method": "bdev_nvme_attach_controller" }) else: set_param(params, option, value) params[3][3] = params[3][3].lower() params[6][3] = params[6][3] * 100 nvme_json.append({ "params": to_json_params(params[5:7]), "method": "bdev_nvme_set_hotplug" }) nvme_json.append({ "params": to_json_params(params[0:5]), "method": "bdev_nvme_set_options" }) return nvme_json def get_pmem_bdev_json(config, section): pmem_json = [] for option in config.options(section): if "Blk" == option: for value in config.get(section, option).split("\n"): items = re.findall(r"\S+", value) pmem_json.append({ "params": { "name": items[1], "pmem_file": items[0] }, "method": "bdev_pmem_create" }) return pmem_json def get_split_bdev_json(config, section): split_json = [] value = [] for option in config.options("Split"): if "Split" == option: value = config.get("Split", option) if value and not isinstance(value, list): value = [value] for split in value: items = re.findall(r"\S+", split) split_size_mb = 0 base_bdev = items[0] split_count = int(items[1]) if len(items) == 3: split_size_mb = items[2] split_json.append({ "params": { "base_bdev": base_bdev, "split_size_mb": split_size_mb, "split_count": split_count }, "method": "bdev_split_create" }) return split_json def get_nvmf_options_json(config, section): params = [ ["AcceptorPollRate", "acceptor_poll_rate", int, 10000], ["MaxSubsystems", "max_subsystems", int, 1024] ] for option in config.options("Nvmf"): set_param(params, option, config.get("Nvmf", option)) nvmf_json = [] nvmf_json.append({ "params": to_json_params([params[0]]), "method": "nvmf_set_config" }) nvmf_json.append({ "params": to_json_params(params[1:7]), "method": "nvmf_set_max_subsystems" }) return nvmf_json def get_nvmf_subsystem_json(config, section): nvmf_subsystem_methods = [] params = [ # Last items are default values if given entry is not set ["Host", "hosts", list, []], ["NQN", "nqn", str, ""], ["AllowAnyHost", "allow_any_host", bool, False], ["SN", "serial_number", str, "00000000000000000000"], ["MN", "model_number", str, "SPDK bdev Controller"], ["MaxNamespaces", "max_namespaces", str, ""], ] listen_address = [] namespaces = [] nsid = 0 searched_items = [param[0] for param in params] for option in config.options(section): value = config.get(section, option) if option in searched_items: set_param(params, option, value) continue if "Listen" == option: items = re.findall(r"\S+", value) adrfam = "IPv4" if len(items[1].split(":")) > 2: adrfam = "IPv6" listen_address.append({ "trtype": items[0], "adrfam": adrfam, "trsvcid": items[1].rsplit(":", 1)[-1], "traddr": items[1].rsplit(":", 1)[0].replace( "]", "").replace("[", "") }) if "Namespace" == option: for item in value.split("\n"): items = re.findall(r"\S+", item) if len(items) == 2: nsid = items[1] else: nsid += 1 namespaces.append({ "nsid": int(nsid), "bdev_name": items[0], }) # Get parameters: nqn, allow_any_host, serial_number # for nvmf_create_subsystem rpc method parameters = to_json_params(params[1:5]) nvmf_subsystem_methods.append({ "params": parameters, "method": "nvmf_create_subsystem" }) for listen in listen_address: nvmf_subsystem_methods.append({ "params": { "listen_address": listen, "nqn": parameters['nqn'] }, "method": "nvmf_subsystem_add_listener" }) for host in to_json_params([params[0]])['hosts']: nvmf_subsystem_methods.append({ "params": { "host": host, "nqn": parameters['nqn'] }, "method": "nvmf_subsystem_add_host" }) for namespace in namespaces: nvmf_subsystem_methods.append({ "params": { "namespace": namespace, "nqn": parameters['nqn'] }, "method": "nvmf_subsystem_add_ns" }) # Define max_namespaces if it is set in old config if params[5][3]: nvmf_subsystem_methods[0]['params']['max_namespaces'] = int(params[5][3]) return nvmf_subsystem_methods def get_vhost_scsi_json(config, section): params = [ ["Name", "ctrlr", str, None], ["Cpumask", "cpumask", "hex", "1"], ] targets = [] vhost_scsi_json = [] for option in config.options(section): value = config.get(section, option) if option in ["Name", "Cpumask"]: set_param(params, option, value) if "Target" == option: for item in value.split("\n"): items = re.findall(r"\S+", item) targets.append({ "scsi_target_num": int(items[0]), "ctrlr": params[0][3], "bdev_name": items[1] }) vhost_scsi_json.append({ "params": to_json_params(params), "method": "vhost_create_scsi_controller" }) for target in targets: vhost_scsi_json.append({ "params": target, "method": "vhost_scsi_controller_add_target" }) return vhost_scsi_json def get_vhost_blk_json(config, section): params = [ ["ReadOnly", "readonly", bool, False], ["Dev", "dev_name", str, ""], ["Name", "ctrlr", str, ""], ["Cpumask", "cpumask", "hex", ""] ] for option in config.options(section): set_param(params, option, config.get(section, option)) return [{"method": "vhost_create_blk_controller", "params": to_json_params(params)}] def get_virtio_user_json(config, section): params = [ ["Path", "traddr", str, ""], ["Queues", "vq_count", int, 1], ["Type", "dev_type", "dev_type", "scsi"], ["Name", "name", str, section], # Define parameters with default values. # These params are set by rpc commands and # do not occur in ini config file. # But they are visible in json config file # with default values even if not set by rpc. [None, "trtype", str, "user"], [None, "vq_size", int, 512] ] for option in config.options(section): set_param(params, option, config.get(section, option)) dev_name = "Scsi" if params[2][3] == "blk": dev_name = "Blk" params[3][3] = params[3][3].replace("User", dev_name) return [{ "params": to_json_params(params), "method": "bdev_virtio_attach_controller" }] def get_iscsi_options_json(config, section): params = [ ['AllowDuplicateIsid', 'allow_duplicated_isid', bool, False], ['DefaultTime2Retain', 'default_time2retain', int, 20], ['DiscoveryAuthMethod', 'mutual_chap', bool, False], ['MaxConnectionsPerSession', 'max_connections_per_session', int, 2], ['Timeout', 'nop_timeout', int, 60], ['DiscoveryAuthMethod', 'disable_chap', bool, False], ['DiscoveryAuthMethod', 'require_chap', bool, False], ['NodeBase', 'node_base', str, "iqn.2016-06.io.spdk"], ['AuthFile', 'auth_file', str, None], ['DiscoveryAuthGroup', 'chap_group', int, 0], ['MaxSessions', 'max_sessions', int, 128], ['ImmediateData', 'immediate_data', bool, True], ['ErrorRecoveryLevel', 'error_recovery_level', int, 0], ['NopInInterval', 'nop_in_interval', int, 30], ['DefaultTime2Wait', 'default_time2wait', int, 2], ['QueueDepth', 'max_queue_depth', int, 64], ['', 'first_burst_length', int, 8192] ] for option in config.options(section): set_param(params, option, config.get(section, option)) return [{"method": "iscsi_set_options", "params": to_json_params(params)}] def get_iscsi_portal_group_json(config, name): portal_group_json = [] portals = [] for option in config.options(name): if "Portal" == option: for value in config.get(name, option).split("\n"): items = re.findall(r"\S+", value) portal = {'host': items[1].rsplit(":", 1)[0]} if "@" in items[1]: portal['port'] =\ items[1].rsplit(":", 1)[1].split("@")[0] else: portal['port'] = items[1].rsplit(":", 1)[1] portals.append(portal) portal_group_json.append({ "params": { "portals": portals, "tag": int(re.findall(r'\d+', name)[0]) }, "method": "iscsi_create_portal_group" }) return portal_group_json def get_iscsi_initiator_group_json(config, name): initiators = [] netmasks = [] for option in config.options(name): if "InitiatorName" == option: initiators.append(config.get(name, option)) if "Netmask" == option: netmasks.append(config.get(name, option)) initiator_group_json = { "params": { "initiators": initiators, "tag": int(re.findall(r'\d+', name)[0]), "netmasks": netmasks }, "method": "iscsi_create_initiator_group" } return [initiator_group_json] def get_iscsi_target_node_json(config, section): luns = [] mutual_chap = False name = "" alias_name = "" require_chap = False chap_group = 1 pg_ig_maps = [] data_digest = False disable_chap = False header_digest = False queue_depth = 64 for option in config.options(section): value = config.get(section, option) if "TargetName" == option: name = value if "TargetAlias" == option: alias_name = value.replace("\"", "") if "Mapping" == option: items = re.findall(r"\S+", value) pg_ig_maps.append({ "ig_tag": int(re.findall(r'\d+', items[1])[0]), "pg_tag": int(re.findall(r'\d+', items[0])[0]) }) if "AuthMethod" == option: items = re.findall(r"\S+", value) for item in items: if "CHAP" == item: require_chap = True elif "Mutual" == item: mutual_chap = True elif "Auto" == item: disable_chap = False require_chap = False mutual_chap = False elif "None" == item: disable_chap = True require_chap = False mutual_chap = False if "AuthGroup" == option: # AuthGroup1 items = re.findall(r"\S+", value) chap_group = int(re.findall(r'\d+', items[0])[0]) if "UseDigest" == option: items = re.findall(r"\S+", value) for item in items: if "Header" == item: header_digest = True elif "Data" == item: data_digest = True elif "Auto" == item: header_digest = False data_digest = False if re.match(r"LUN\d+", option): luns.append({"lun_id": len(luns), "bdev_name": value}) if "QueueDepth" == option: queue_depth = int(value) params = {"alias_name": alias_name} params["name"] = "iqn.2016-06.io.spdk:%s" % name params["luns"] = luns params["pg_ig_maps"] = pg_ig_maps params["queue_depth"] = queue_depth params["chap_group"] = chap_group params["header_digest"] = header_digest params["mutual_chap"] = mutual_chap params["require_chap"] = require_chap params["data_digest"] = data_digest params["disable_chap"] = disable_chap target_json = { "params": params, "method": "iscsi_create_target_node" } return [target_json] if __name__ == "__main__": try: config = configparser.ConfigParser(strict=False, delimiters=(' '), dict_type=OptionOrderedDict, allow_no_value=True) # Do not parse options and values. Capital letters are relevant. config.optionxform = str config.read_file(sys.stdin) except Exception as e: print("Exception while parsing config: %s" % e) exit(1) for section in config.sections(): match = re.match(r'(Bdev|Nvme|Malloc|VirtioUser\d+|Split|Pmem|AIO|' r'iSCSI|PortalGroup\d+|InitiatorGroup\d+|' r'TargetNode\d+|Nvmf|Subsystem\d+|VhostScsi\d+|' r'VhostBlk\d+)', section) if match: match_section = ''.join(letter for letter in match.group(0) if not letter.isdigit()) if match_section == "Bdev": items = get_bdev_options_json(config, section) elif match_section == "AIO": items = get_aio_bdev_json(config, section) elif match_section == "Malloc": items = get_malloc_bdev_json(config, section) elif match_section == "Nvme": items = get_nvme_bdev_json(config, section) elif match_section == "Pmem": items = get_pmem_bdev_json(config, section) elif match_section == "Split": items = get_split_bdev_json(config, section) elif match_section == "Nvmf": items = get_nvmf_options_json(config, section) elif match_section == "Subsystem": items = get_nvmf_subsystem_json(config, section) elif match_section == "VhostScsi": items = get_vhost_scsi_json(config, section) elif match_section == "VhostBlk": items = get_vhost_blk_json(config, section) elif match_section == "VirtioUser": items = get_virtio_user_json(config, section) elif match_section == "iSCSI": items = get_iscsi_options_json(config, section) elif match_section == "PortalGroup": items = get_iscsi_portal_group_json(config, section) elif match_section == "InitiatorGroup": items = get_iscsi_initiator_group_json(config, section) elif match_section == "TargetNode": items = get_iscsi_target_node_json(config, section) for item in items: if match_section == "VhostScsi": section_to_subsystem[match_section]["vhost_create_scsi_controller"].append(item) elif match_section == "Subsystem": section_to_subsystem[match_section]["subsystems"].append(item) else: section_to_subsystem[match_section][ item['method']].append(item) elif section == "Global": pass elif section == "Ioat": # Ioat doesn't support JSON config yet. pass elif section == "VirtioPci": print("Please use spdk target flags.") exit(1) else: print("An invalid section detected: %s.\n" "Please revise your config file." % section) exit(1) json.dump(generate_new_json_config(), sys.stdout, indent=2) print("")