numam-spdk/scripts/config_converter.py
Tomasz Zawadzki a9a0761bcc scripts/config_converter: skip sections not present in legacy config
Sections 'Nvme', 'Nvmf', 'Bdev' and 'iSCSI' were always present in
the output of config_converter. Even when they were not present in
the legacy config.

This posed an issue with iSCSI section. Support for iscsi_set_options
is part of lib/iscsi, that is not compiled with all applications
(such as nvmf_tgt).

Fixes #1741

This patch checks if there is corresponding section in the legacy
config, and only then presents the JSON output.

Added test to verify output from empty legacy config.

Signed-off-by: Tomasz Zawadzki <tomasz.zawadzki@intel.com>
Change-Id: Ief94e92115dbd80c890dcb434b7c6d5376421c9e
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/5879
Community-CI: Broadcom CI
Community-CI: Mellanox Build Bot
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: Jim Harris <james.r.harris@intel.com>
Reviewed-by: Shuhei Matsumoto <shuhei.matsumoto.xt@hitachi.com>
Reviewed-by: <dongx.yi@intel.com>
2021-01-15 08:32:10 +00:00

685 lines
23 KiB
Python
Executable File

#!/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("")