From 773aa20a4ac0364a2219877863017224c7f1089f Mon Sep 17 00:00:00 2001 From: Lin Tan Date: Thu, 19 Nov 2015 14:50:38 +0800 Subject: [PATCH] Make use of oslo-config-generator oslo_config provide a utility for generating sample config files, which provide more detail about opts, like Minimum/Maximum value and Allowed values. So drop Ironic's "generate_sample.sh" which was copied from oslo-incubator long time ago. Add a new entry point "ironic" under oslo.config.opts namespace to explore config options to oslo-config-generator. After this patch, new config options of Ironic code should register with ironic/conf/opts.py. New external libraries should register with tools/config/ironic-config-generator.conf There is a bug #1554657 with oslo-config about deprecated_group. This bug have impact of some configs from keystonemiddleware and oslo.messaging in ironic.conf.sample So currently, deprecated option should always add the deprecated_group even it didn't alter the group, otherwise the deprecated group value will be 'DEFAULT'. Update etc/ironic/ironic.conf.sample via running 'tox -egenconfig'. Closes-Bug: #1564195 Change-Id: If7721e98e69b6f54f1ee04a07477396b86583371 --- etc/ironic/ironic.conf.sample | 215 +++------- ironic/common/config_generator/generator.py | 375 ------------------ ironic/common/paths.py | 1 + ironic/common/service.py | 1 + ironic/common/utils.py | 1 + .../config_generator => conf}/__init__.py | 0 ironic/conf/opts.py | 137 +++++++ ironic/netconf.py | 1 + ...slo-config-generator-15afd2e7c2f008b4.yaml | 9 + setup.cfg | 3 + tools/config/check_uptodate.sh | 5 +- tools/config/generate_sample.sh | 138 ------- tools/config/ironic-config-generator.conf | 17 + tools/config/oslo.config.generator.rc | 2 - tox.ini | 3 +- 15 files changed, 231 insertions(+), 677 deletions(-) delete mode 100644 ironic/common/config_generator/generator.py rename ironic/{common/config_generator => conf}/__init__.py (100%) create mode 100644 ironic/conf/opts.py create mode 100644 releasenotes/notes/adopt-oslo-config-generator-15afd2e7c2f008b4.yaml delete mode 100755 tools/config/generate_sample.sh create mode 100644 tools/config/ironic-config-generator.conf delete mode 100644 tools/config/oslo.config.generator.rc diff --git a/etc/ironic/ironic.conf.sample b/etc/ironic/ironic.conf.sample index e0b9b3d6ae..788e45b78f 100644 --- a/etc/ironic/ironic.conf.sample +++ b/etc/ironic/ironic.conf.sample @@ -1,7 +1,7 @@ [DEFAULT] # -# Options defined in ironic.api.app +# From ironic # # Authentication strategy used by ironic-api. "noauth" should @@ -20,11 +20,6 @@ # value) #pecan_debug = false - -# -# Options defined in ironic.common.driver_factory -# - # Specify the list of drivers to load during service # initialization. Missing drivers, or drivers which fail to # initialize, will prevent the conductor service from @@ -35,22 +30,12 @@ # developer documentation online. (list value) #enabled_drivers = pxe_ipmitool - -# -# Options defined in ironic.common.exception -# - # Used if there is a formatting error when generating an # exception message (a programming error). If True, raise an # exception; if False, use the unformatted message. (boolean # value) #fatal_exception_format_errors = false - -# -# Options defined in ironic.common.hash_ring -# - # Exponent to determine number of hash partitions to use when # distributing load across conductors. Larger values will # result in more even distribution of load and less load when @@ -76,11 +61,6 @@ # value) #hash_ring_reset_interval = 180 - -# -# Options defined in ironic.common.images -# - # If True, convert backing images to "raw" disk image format. # (boolean value) #force_raw_images = true @@ -95,11 +75,6 @@ # Template file for grub configuration file. (string value) #grub_config_template = $pybasedir/common/grub_conf.template - -# -# Options defined in ironic.common.paths -# - # Directory where the ironic python module is installed. # (string value) #pybasedir = /usr/lib/python/site-packages/ironic/ironic @@ -112,11 +87,6 @@ # value) #state_path = $pybasedir - -# -# Options defined in ironic.common.service -# - # Name of this node. This can be an opaque identifier. It is # not necessarily a hostname, FQDN, or IP address. However, # the node name must be valid within an AMQP key, and if using @@ -124,11 +94,6 @@ # value) #host = localhost - -# -# Options defined in ironic.common.utils -# - # Path to the rootwrap configuration file to use for running # commands as root. (string value) #rootwrap_config = /etc/ironic/rootwrap.conf @@ -137,32 +102,22 @@ # (string value) #tempdir = /tmp - -# -# Options defined in ironic.drivers.modules.image_cache -# - # Run image downloads and raw format conversions in parallel. # (boolean value) #parallel_image_downloads = false - -# -# Options defined in ironic.netconf -# - # IP address of this host. If unset, will determine the IP # programmatically. If unable to do so, will use "127.0.0.1". # (string value) -#my_ip = 10.0.0.1 - +#my_ip = 127.0.0.1 # -# Options defined in oslo.log +# From oslo.log # # If set to true, the logging level will be set to DEBUG # instead of the default INFO level. (boolean value) +# Note: This option can be changed without restarting. #debug = false # If set to false, the logging level will be set to WARNING @@ -261,9 +216,8 @@ # value) #fatal_deprecations = false - # -# Options defined in oslo.messaging +# From oslo.messaging # # Size of RPC connection pool. (integer value) @@ -357,18 +311,16 @@ # transport_url option. (string value) #control_exchange = openstack - # -# Options defined in oslo.service.periodic_task +# From oslo.service.periodic_task # # Some periodic tasks can be run in a separate process. Should # we run them here? (boolean value) #run_external_periodic_tasks = true - # -# Options defined in oslo.service.service +# From oslo.service.service # # Enable eventlet backdoor. Acceptable values are 0, , @@ -400,7 +352,7 @@ [agent] # -# Options defined in ironic.drivers.modules.agent +# From ironic # # Whether Ironic will manage booting of the agent ramdisk. If @@ -424,11 +376,6 @@ # be set to True. Defaults to True. (boolean value) #stream_raw_images = true - -# -# Options defined in ironic.drivers.modules.agent_base_vendor -# - # Maximum interval (in seconds) for agent heartbeats. (integer # value) #heartbeat_timeout = 300 @@ -442,11 +389,6 @@ # state after trigger soft poweroff. (integer value) #post_deploy_get_power_state_retry_interval = 5 - -# -# Options defined in ironic.drivers.modules.agent_client -# - # API version to use for communicating with the ramdisk agent. # (string value) #agent_api_version = v1 @@ -455,7 +397,7 @@ [amt] # -# Options defined in ironic.drivers.modules.amt.common +# From ironic # # Protocol used for AMT endpoint (string value) @@ -471,11 +413,6 @@ # Minimum value: 0 #awake_interval = 60 - -# -# Options defined in ironic.drivers.modules.amt.power -# - # Maximum number of times to attempt an AMT operation, before # failing (integer value) #max_attempts = 3 @@ -488,7 +425,7 @@ [api] # -# Options defined in ironic.api +# From ironic # # The IP address on which ironic-api listens. (string value) @@ -529,7 +466,7 @@ [cimc] # -# Options defined in ironic.drivers.modules.cimc.power +# From ironic # # Number of times a power operation needs to be retried @@ -544,7 +481,7 @@ [cisco_ucs] # -# Options defined in ironic.drivers.modules.ucs.power +# From ironic # # Number of times a power operation needs to be retried @@ -559,7 +496,7 @@ [conductor] # -# Options defined in ironic.conductor.base_manager +# From ironic # # The size of the workers greenthread pool. Note that 2 @@ -571,11 +508,6 @@ # Seconds between conductor heart beats. (integer value) #heartbeat_interval = 10 - -# -# Options defined in ironic.conductor.manager -# - # URL of Ironic API service. If not set ironic can get the # current value from the keystone service catalog. (string # value) @@ -677,7 +609,7 @@ [console] # -# Options defined in ironic.drivers.modules.console_utils +# From ironic # # Path to serial console terminal program (string value) @@ -703,7 +635,7 @@ [cors] # -# Options defined in oslo.middleware.cors +# From oslo.middleware.cors # # Indicate whether this resource may be shared with the domain @@ -718,7 +650,7 @@ # Indicate which headers are safe to expose to the API. # Defaults to HTTP Simple Headers. (list value) -#expose_headers = +#expose_headers = # Maximum cache age of CORS preflight requests. (integer # value) @@ -730,13 +662,13 @@ # Indicate which header field names may be used during the # actual request. (list value) -#allow_headers = +#allow_headers = [cors.subdomain] # -# Options defined in oslo.middleware.cors +# From oslo.middleware.cors # # Indicate whether this resource may be shared with the domain @@ -751,7 +683,7 @@ # Indicate which headers are safe to expose to the API. # Defaults to HTTP Simple Headers. (list value) -#expose_headers = +#expose_headers = # Maximum cache age of CORS preflight requests. (integer # value) @@ -763,21 +695,20 @@ # Indicate which header field names may be used during the # actual request. (list value) -#allow_headers = +#allow_headers = [database] # -# Options defined in ironic.db.sqlalchemy.models +# From ironic # # MySQL engine to use. (string value) #mysql_engine = InnoDB - # -# Options defined in oslo.db +# From oslo.db # # The file name to use with SQLite. (string value) @@ -889,7 +820,7 @@ [deploy] # -# Options defined in ironic.drivers.modules.deploy_utils +# From ironic # # ironic-conductor node's HTTP server URL. Example: @@ -927,7 +858,7 @@ [dhcp] # -# Options defined in ironic.common.dhcp_factory +# From ironic # # DHCP provider to use. "neutron" uses Neutron, and "none" @@ -938,7 +869,7 @@ [disk_partitioner] # -# Options defined in ironic_lib.disk_partitioner +# From ironic_lib.disk_partitioner # # After Ironic has completed creating the partition table, it @@ -957,7 +888,7 @@ [disk_utils] # -# Options defined in ironic_lib.disk_utils +# From ironic_lib.disk_utils # # Size of EFI system partition in MiB when configuring UEFI @@ -979,13 +910,13 @@ [glance] # -# Options defined in ironic.common.glance_service.v2.image_service +# From ironic # # A list of URL schemes that can be downloaded directly via # the direct_url. Currently supported schemes: [file]. (list # value) -#allowed_direct_url_schemes = +#allowed_direct_url_schemes = # The secret token given to Swift to allow temporary URL # downloads. Required for temporary URLs. (string value) @@ -1065,11 +996,6 @@ # Allowed values: swift, radosgw #temp_url_endpoint_type = swift - -# -# Options defined in ironic.common.image_service -# - # Default glance hostname or IP address. (string value) #glance_host = $my_ip @@ -1110,7 +1036,7 @@ [iboot] # -# Options defined in ironic.drivers.modules.iboot +# From ironic # # Maximum retries for iBoot operations (integer value) @@ -1129,7 +1055,7 @@ [ilo] # -# Options defined in ironic.drivers.modules.ilo.common +# From ironic # # Timeout (in seconds) for iLO operations (integer value) @@ -1155,21 +1081,11 @@ # (boolean value) #use_web_server_for_images = false - -# -# Options defined in ironic.drivers.modules.ilo.deploy -# - # Priority for erase devices clean step. If unset, it defaults # to 10. If set to 0, the step will be disabled and will not # run during cleaning. (integer value) #clean_priority_erase_devices = - -# -# Options defined in ironic.drivers.modules.ilo.management -# - # Priority for reset_ilo clean step. (integer value) #clean_priority_reset_ilo = 0 @@ -1192,11 +1108,6 @@ # nodes's driver_info with the new password. (integer value) #clean_priority_reset_ilo_credential = 30 - -# -# Options defined in ironic.drivers.modules.ilo.power -# - # Number of times a power operation needs to be retried # (integer value) #power_retry = 6 @@ -1209,7 +1120,7 @@ [inspector] # -# Options defined in ironic.drivers.modules.inspector +# From ironic # # whether to enable inspection using ironic-inspector (boolean @@ -1229,7 +1140,7 @@ [ipmi] # -# Options defined in ironic.drivers.modules.ipminative +# From ironic # # Maximum time in seconds to retry IPMI operations. There is a @@ -1250,7 +1161,7 @@ [irmc] # -# Options defined in ironic.drivers.modules.irmc.boot +# From ironic # # Ironic conductor node's "NFS" or "CIFS" root path (string @@ -1276,11 +1187,6 @@ # Domain name of remote_image_user_name (string value) #remote_image_user_domain = - -# -# Options defined in ironic.drivers.modules.irmc.common -# - # Port to be used for iRMC operations (port value) # Allowed values: 443, 80 #port = 443 @@ -1317,7 +1223,7 @@ [ironic_lib] # -# Options defined in ironic_lib.utils +# From ironic_lib.utils # # Command that is prefixed to commands that are run as root. @@ -1329,7 +1235,7 @@ [iscsi] # -# Options defined in ironic.drivers.modules.iscsi_deploy +# From ironic # # The port number on which the iSCSI portal listens for @@ -1342,7 +1248,7 @@ [keystone] # -# Options defined in ironic.common.keystone +# From ironic # # The region used for getting endpoints of OpenStack services. @@ -1353,7 +1259,7 @@ [keystone_authtoken] # -# Options defined in keystonemiddleware.auth_token +# From keystonemiddleware.auth_token # # Complete public Identity API endpoint. (string value) @@ -1405,7 +1311,7 @@ # Optionally specify a list of memcached server(s) to use for # caching. If left undefined, tokens will instead be cached # in-process. (list value) -# Deprecated group/name - [keystone_authtoken]/memcache_servers +# Deprecated group/name - [DEFAULT]/memcache_servers #memcached_servers = # In order to prevent excessive effort spent validating @@ -1496,7 +1402,7 @@ #hash_algorithms = md5 # Authentication type to load (unknown value) -# Deprecated group/name - [keystone_authtoken]/auth_plugin +# Deprecated group/name - [DEFAULT]/auth_plugin #auth_type = # Config Section from which to load plugin specific options @@ -1507,7 +1413,7 @@ [matchmaker_redis] # -# Options defined in oslo.messaging +# From oslo.messaging # # Host to locate redis. (string value) @@ -1523,7 +1429,7 @@ # List of Redis Sentinel hosts (fault tolerance mode) e.g. # [host:port, host1:port ... ] (list value) -#sentinel_hosts = +#sentinel_hosts = # Redis replica set name. (string value) #sentinel_group_name = oslo-messaging-zeromq @@ -1543,7 +1449,7 @@ [neutron] # -# Options defined in ironic.dhcp.neutron +# From ironic # # URL for connecting to neutron. (string value) @@ -1577,7 +1483,7 @@ [oneview] # -# Options defined in ironic.drivers.modules.oneview.common +# From ironic # # URL where OneView is available (string value) @@ -1604,7 +1510,7 @@ [oslo_concurrency] # -# Options defined in oslo.concurrency +# From oslo.concurrency # # Enables or disables inter-process locks. (boolean value) @@ -1623,7 +1529,7 @@ [oslo_messaging_amqp] # -# Options defined in oslo.messaging +# From oslo.messaging # # address prefix used when sending to a specific server @@ -1705,14 +1611,14 @@ [oslo_messaging_notifications] # -# Options defined in oslo.messaging +# From oslo.messaging # # The Drivers(s) to handle sending notifications. Possible # values are messaging, messagingv2, routing, log, test, noop # (multi valued) # Deprecated group/name - [DEFAULT]/notification_driver -#driver = +#driver = # A URL representing the messaging driver to use for # notifications. If not set, we fall back to the same @@ -1729,7 +1635,7 @@ [oslo_messaging_rabbit] # -# Options defined in oslo.messaging +# From oslo.messaging # # Use durable queues in AMQP. (boolean value) @@ -1773,7 +1679,7 @@ # How long to wait a missing client beforce abandoning to send # it its replies. This value should not be longer than # rpc_response_timeout. (integer value) -# Deprecated group/name - [oslo_messaging_rabbit]/kombu_reconnect_timeout +# Deprecated group/name - [DEFAULT]/kombu_reconnect_timeout #kombu_missing_consumer_retry_timeout = 60 # Determines how the next RabbitMQ node is chosen in case the @@ -1983,7 +1889,7 @@ [oslo_policy] # -# Options defined in oslo.policy +# From oslo.policy # # The JSON file that defines policies. (string value) @@ -2008,7 +1914,7 @@ [pxe] # -# Options defined in ironic.drivers.modules.iscsi_deploy +# From ironic # # Additional append parameters for baremetal PXE boot. (string @@ -2040,11 +1946,6 @@ # value) #disk_devices = cciss/c0d0,sda,hda,vda - -# -# Options defined in ironic.drivers.modules.pxe -# - # On ironic-conductor node, template file for PXE # configuration. (string value) #pxe_config_template = $pybasedir/drivers/modules/pxe_config.template @@ -2092,7 +1993,7 @@ [seamicro] # -# Options defined in ironic.drivers.modules.seamicro +# From ironic # # Maximum retries for SeaMicro operations (integer value) @@ -2106,7 +2007,7 @@ [snmp] # -# Options defined in ironic.drivers.modules.snmp +# From ironic # # Seconds to wait for power action to be completed (integer @@ -2122,7 +2023,7 @@ [ssh] # -# Options defined in ironic.drivers.modules.ssh +# From ironic # # libvirt URI. (string value) @@ -2141,7 +2042,7 @@ [ssl] # -# Options defined in oslo.service.sslutils +# From oslo.service.sslutils # # CA certificate file to use to verify connecting clients. @@ -2172,7 +2073,7 @@ [swift] # -# Options defined in ironic.common.swift +# From ironic # # Maximum number of times to retry a Swift request, before @@ -2183,7 +2084,7 @@ [virtualbox] # -# Options defined in ironic.drivers.modules.virtualbox +# From ironic # # Port on which VirtualBox web service is listening. (port @@ -2191,5 +2092,3 @@ # Minimum value: 0 # Maximum value: 65535 #port = 18083 - - diff --git a/ironic/common/config_generator/generator.py b/ironic/common/config_generator/generator.py deleted file mode 100644 index 274ff746b3..0000000000 --- a/ironic/common/config_generator/generator.py +++ /dev/null @@ -1,375 +0,0 @@ -# Copyright 2012 SINA Corporation -# Copyright 2014 Cisco Systems, Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -"""Extracts OpenStack config option info from module(s).""" - -# NOTE(GheRivero): Copied from oslo_incubator before getting removed in -# Change-Id: If15b77d31a8c615aad8fca30f6dd9928da2d08bb - -from __future__ import print_function - -import argparse -import imp -import os -import re -import socket -import sys -import tempfile -import textwrap - -import mock -from oslo_config import cfg -import oslo_i18n -from oslo_utils import importutils -import six -import stevedore.named - - -oslo_i18n.install('ironic') - -OPT = "Opt" -STROPT = "StrOpt" -BOOLOPT = "BoolOpt" -INTOPT = "IntOpt" -FLOATOPT = "FloatOpt" -LISTOPT = "ListOpt" -DICTOPT = "DictOpt" -MULTISTROPT = "MultiStrOpt" -PORTOPT = "PortOpt" - -OPT_TYPES = { - OPT: 'unknown value', - STROPT: 'string value', - BOOLOPT: 'boolean value', - INTOPT: 'integer value', - FLOATOPT: 'floating point value', - LISTOPT: 'list value', - DICTOPT: 'dict value', - MULTISTROPT: 'multi valued', - PORTOPT: 'port value', -} - -OPTION_REGEX = re.compile(r"(%s)" % "|".join(OPT_TYPES)) - -PY_EXT = ".py" -BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), - "../../../../")) -WORDWRAP_WIDTH = 60 - - -def raise_extension_exception(extmanager, ep, err): - raise - - -# Don't let the system hostname or FQDN affect config file values. Certain 3rd -# party libraries use either 'gethostbyname' or 'getfqdn' to set the default -# value. -@mock.patch.object(socket, 'gethostname', lambda: 'localhost') -@mock.patch.object(socket, 'getfqdn', lambda: 'localhost') -@mock.patch.object(tempfile, 'gettempdir', lambda: '/tmp') -def generate(argv): - parser = argparse.ArgumentParser( - description='generate sample configuration file', - ) - parser.add_argument('-m', dest='modules', action='append') - parser.add_argument('-l', dest='libraries', action='append') - parser.add_argument('srcfiles', nargs='*') - parsed_args = parser.parse_args(argv) - - mods_by_pkg = dict() - for filepath in parsed_args.srcfiles: - pkg_name = filepath.split(os.sep)[1] - mod_str = '.'.join(['.'.join(filepath.split(os.sep)[:-1]), - os.path.basename(filepath).split('.')[0]]) - mods_by_pkg.setdefault(pkg_name, list()).append(mod_str) - # NOTE(lzyeval): place top level modules before packages - pkg_names = sorted(pkg for pkg in mods_by_pkg if pkg.endswith(PY_EXT)) - ext_names = sorted(pkg for pkg in mods_by_pkg if pkg not in pkg_names) - pkg_names.extend(ext_names) - - # opts_by_group is a mapping of group name to an options list - # The options list is a list of (module, options) tuples - opts_by_group = {'DEFAULT': []} - - if parsed_args.modules: - for module_name in parsed_args.modules: - module = _import_module(module_name) - if module: - for group, opts in _list_opts(module): - opts_by_group.setdefault(group, []).append((module_name, - opts)) - - # Look for entry points defined in libraries (or applications) for - # option discovery, and include their return values in the output. - # - # Each entry point should be a function returning an iterable - # of pairs with the group name (or None for the default group) - # and the list of Opt instances for that group. - if parsed_args.libraries: - loader = stevedore.named.NamedExtensionManager( - 'oslo.config.opts', - names=list(set(parsed_args.libraries)), - invoke_on_load=False, - on_load_failure_callback=raise_extension_exception - ) - for ext in loader: - for group, opts in ext.plugin(): - opt_list = opts_by_group.setdefault(group or 'DEFAULT', []) - opt_list.append((ext.name, opts)) - - for pkg_name in pkg_names: - mods = mods_by_pkg.get(pkg_name) - mods.sort() - for mod_str in mods: - if mod_str.endswith('.__init__'): - mod_str = mod_str[:mod_str.rfind(".")] - - mod_obj = _import_module(mod_str) - if not mod_obj: - raise RuntimeError("Unable to import module %s" % mod_str) - - for group, opts in _list_opts(mod_obj): - opts_by_group.setdefault(group, []).append((mod_str, opts)) - - print_group_opts('DEFAULT', opts_by_group.pop('DEFAULT', [])) - for group in sorted(opts_by_group.keys()): - print_group_opts(group, opts_by_group[group]) - - -def _import_module(mod_str): - try: - if mod_str.startswith('bin.'): - imp.load_source(mod_str[4:], os.path.join('bin', mod_str[4:])) - return sys.modules[mod_str[4:]] - else: - return importutils.import_module(mod_str) - except Exception as e: - sys.stderr.write("Error importing module %s: %s\n" % (mod_str, e)) - return None - - -def _is_in_group(opt, group): - """Check if opt is in group.""" - for value in group._opts.values(): - if value['opt'] is opt: - return True - return False - - -def _guess_groups(opt): - # is it in the DEFAULT group? - if _is_in_group(opt, cfg.CONF): - return 'DEFAULT' - - # what other groups is it in? - for value in cfg.CONF.values(): - if isinstance(value, cfg.CONF.GroupAttr): - if _is_in_group(opt, value._group): - return value._group.name - - raise RuntimeError( - "Unable to find group for option %s, " - "maybe it's defined twice in the same group?" - % opt.name - ) - - -def _list_opts(obj): - def is_opt(o): - return (isinstance(o, cfg.Opt) and - not isinstance(o, cfg.SubCommandOpt)) - - opts = list() - - if 'list_opts' in dir(obj): - group_opts = getattr(obj, 'list_opts')() - # NOTE(GheRivero): Options without a defined group, - # must be registered to the DEFAULT section - fixed_list = [] - for section, opts in group_opts: - if not section: - section = 'DEFAULT' - fixed_list.append((section, opts)) - return fixed_list - - for attr_str in dir(obj): - attr_obj = getattr(obj, attr_str) - if is_opt(attr_obj): - opts.append(attr_obj) - elif (isinstance(attr_obj, list) and - all(map(lambda x: is_opt(x), attr_obj))): - opts.extend(attr_obj) - - ret = {} - for opt in opts: - ret.setdefault(_guess_groups(opt), []).append(opt) - return ret.items() - - -def print_group_opts(group, opts_by_module): - print("[%s]" % group) - print('') - for mod, opts in sorted(opts_by_module, key=lambda x: x[0]): - print('#') - print('# Options defined in %s' % mod) - print('#') - print('') - for opt in opts: - _print_opt(opt, group) - print('') - - -def _get_choice_text(choice): - if choice is None: - return '' - elif choice == '': - return "''" - return six.text_type(choice) - - -def _get_my_ip(): - try: - csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - csock.connect(('8.8.8.8', 80)) - (addr, port) = csock.getsockname() - csock.close() - return addr - except socket.error: - return None - - -def _sanitize_default(name, value): - """Set up a reasonably sensible default for pybasedir, my_ip and host.""" - if value.startswith(sys.prefix): - # NOTE(jd) Don't use os.path.join, because it is likely to think the - # second part is an absolute pathname and therefore drop the first - # part. - value = os.path.normpath("/usr/" + value[len(sys.prefix):]) - elif value.startswith(BASEDIR): - return value.replace(BASEDIR, '/usr/lib/python/site-packages') - elif BASEDIR in value: - return value.replace(BASEDIR, '') - elif value == _get_my_ip(): - return '10.0.0.1' - elif value.strip() != value: - return '"%s"' % value - return value - - -def _print_opt(opt, group): - opt_name, opt_default, opt_help = opt.dest, opt.default, opt.help - if not opt_help: - sys.stderr.write('WARNING: "%s" is missing help string.\n' % opt_name) - opt_help = "" - result = OPTION_REGEX.search(str(type(opt))) - if not result: - raise ValueError( - "Config option: {!r} Unknown option type: {}\n".format( - opt_name, type(opt))) - opt_type = result.group(0) - opt_help = u'%s (%s)' % (opt_help, - OPT_TYPES[opt_type]) - print('#', "\n# ".join(textwrap.wrap(opt_help, WORDWRAP_WIDTH))) - - min_value = getattr(opt.type, 'min', None) - max_value = getattr(opt.type, 'max', None) - choices = getattr(opt.type, 'choices', None) - - # NOTE(lintan): choices are mutually exclusive with 'min/max', - # see oslo.config for more details. - if min_value is not None: - print('# Minimum value: %d' % min_value) - if max_value is not None: - print('# Maximum value: %d' % max_value) - if choices is not None: - if choices == []: - print('# No possible values.') - else: - choices_text = ', '.join([_get_choice_text(choice) - for choice in choices]) - print('# Allowed values: %s' % choices_text) - - if opt.deprecated_opts: - for deprecated_opt in opt.deprecated_opts: - deprecated_name = (deprecated_opt.name if - deprecated_opt.name else opt_name) - deprecated_group = (deprecated_opt.group if - deprecated_opt.group else group) - print('# Deprecated group/name - [%s]/%s' % - (deprecated_group, - deprecated_name)) - if opt.deprecated_for_removal: - print('# This option is deprecated for removal.') - print('# Its value may be silently ignored in the future.') - try: - if opt_default is None: - print('#%s = ' % opt_name) - else: - _print_type(opt_type, opt_name, opt_default) - print('') - except Exception as e: - sys.stderr.write('Error in option "%s": %s\n' % (opt_name, e)) - sys.exit(1) - - -def _print_type(opt_type, opt_name, opt_default): - if opt_type == OPT: - print('#%s = %s' % (opt_name, opt_default)) - elif opt_type == STROPT: - assert(isinstance(opt_default, six.string_types)) - value = _sanitize_default(opt_name, opt_default) - if value: - print('#%s = %s' % (opt_name, value)) - else: - print('#%s =' % (opt_name)) - elif opt_type == BOOLOPT: - assert(isinstance(opt_default, bool)) - print('#%s = %s' % (opt_name, str(opt_default).lower())) - elif opt_type == INTOPT: - assert(isinstance(opt_default, int) and - not isinstance(opt_default, bool)) - print('#%s = %s' % (opt_name, opt_default)) - elif opt_type == PORTOPT: - assert(isinstance(opt_default, int) and - not isinstance(opt_default, bool)) - print('#%s = %s' % (opt_name, opt_default)) - elif opt_type == FLOATOPT: - assert(isinstance(opt_default, float)) - print('#%s = %s' % (opt_name, opt_default)) - elif opt_type == LISTOPT: - assert(isinstance(opt_default, list)) - print('#%s = %s' % (opt_name, ','.join(opt_default))) - elif opt_type == DICTOPT: - assert(isinstance(opt_default, dict)) - opt_default_strlist = [str(key) + ':' + str(value) - for (key, value) in opt_default.items()] - print('#%s = %s' % (opt_name, ','.join(opt_default_strlist))) - elif opt_type == MULTISTROPT: - assert(isinstance(opt_default, list)) - if not opt_default: - opt_default = [''] - for default in opt_default: - print('#%s = %s' % (opt_name, default)) - else: - raise ValueError("unknown oslo_config type %s" % opt_type) - - -def main(): - generate(sys.argv[1:]) - -if __name__ == '__main__': - main() diff --git a/ironic/common/paths.py b/ironic/common/paths.py index d11c3682f7..c35d3f495e 100644 --- a/ironic/common/paths.py +++ b/ironic/common/paths.py @@ -25,6 +25,7 @@ path_opts = [ cfg.StrOpt('pybasedir', default=os.path.abspath(os.path.join(os.path.dirname(__file__), '../')), + sample_default='/usr/lib/python/site-packages/ironic/ironic', help=_('Directory where the ironic python module is ' 'installed.')), cfg.StrOpt('bindir', diff --git a/ironic/common/service.py b/ironic/common/service.py index 2cbcf6b755..18fabccc7c 100644 --- a/ironic/common/service.py +++ b/ironic/common/service.py @@ -40,6 +40,7 @@ from ironic.objects import base as objects_base service_opts = [ cfg.StrOpt('host', default=socket.getfqdn(), + sample_default='localhost', help=_('Name of this node. This can be an opaque identifier. ' 'It is not necessarily a hostname, FQDN, or IP address. ' 'However, the node name must be valid within ' diff --git a/ironic/common/utils.py b/ironic/common/utils.py index 530e875468..65acb5dd4f 100644 --- a/ironic/common/utils.py +++ b/ironic/common/utils.py @@ -49,6 +49,7 @@ utils_opts = [ 'running commands as root.')), cfg.StrOpt('tempdir', default=tempfile.gettempdir(), + sample_default='/tmp', help=_('Temporary working directory, default is Python temp ' 'dir.')), ] diff --git a/ironic/common/config_generator/__init__.py b/ironic/conf/__init__.py similarity index 100% rename from ironic/common/config_generator/__init__.py rename to ironic/conf/__init__.py diff --git a/ironic/conf/opts.py b/ironic/conf/opts.py new file mode 100644 index 0000000000..d567c3eda0 --- /dev/null +++ b/ironic/conf/opts.py @@ -0,0 +1,137 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy +# of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import itertools + +import ironic.api +import ironic.api.app +import ironic.common.dhcp_factory +import ironic.common.driver_factory +import ironic.common.exception +import ironic.common.glance_service.v2.image_service +import ironic.common.hash_ring +import ironic.common.image_service +import ironic.common.images +import ironic.common.keystone +import ironic.common.paths +import ironic.common.service +import ironic.common.swift +import ironic.common.utils +import ironic.conductor.base_manager +import ironic.conductor.manager +import ironic.db.sqlalchemy.models +import ironic.dhcp.neutron +import ironic.drivers.modules.agent +import ironic.drivers.modules.agent_base_vendor +import ironic.drivers.modules.agent_client +import ironic.drivers.modules.amt.common +import ironic.drivers.modules.amt.power +import ironic.drivers.modules.cimc.power +import ironic.drivers.modules.console_utils +import ironic.drivers.modules.deploy_utils +import ironic.drivers.modules.iboot +import ironic.drivers.modules.ilo.common +import ironic.drivers.modules.ilo.deploy +import ironic.drivers.modules.ilo.management +import ironic.drivers.modules.ilo.power +import ironic.drivers.modules.image_cache +import ironic.drivers.modules.inspector +import ironic.drivers.modules.ipminative +import ironic.drivers.modules.irmc.boot +import ironic.drivers.modules.irmc.common +import ironic.drivers.modules.iscsi_deploy +import ironic.drivers.modules.oneview.common +import ironic.drivers.modules.pxe +import ironic.drivers.modules.seamicro +import ironic.drivers.modules.snmp +import ironic.drivers.modules.ssh +import ironic.drivers.modules.ucs.power +import ironic.drivers.modules.virtualbox +import ironic.netconf + +_default_opt_lists = [ + ironic.api.app.api_opts, + ironic.common.driver_factory.driver_opts, + ironic.common.exception.exc_log_opts, + ironic.common.hash_ring.hash_opts, + ironic.common.images.image_opts, + ironic.common.paths.path_opts, + ironic.common.service.service_opts, + ironic.common.utils.utils_opts, + ironic.drivers.modules.image_cache.img_cache_opts, + ironic.netconf.netconf_opts, +] + +_opts = [ + ('DEFAULT', itertools.chain(*_default_opt_lists)), + ('agent', itertools.chain( + ironic.drivers.modules.agent.agent_opts, + ironic.drivers.modules.agent_base_vendor.agent_opts, + ironic.drivers.modules.agent_client.agent_opts)), + ('amt', itertools.chain( + ironic.drivers.modules.amt.common.opts, + ironic.drivers.modules.amt.power.opts)), + ('api', ironic.api.API_SERVICE_OPTS), + ('cimc', ironic.drivers.modules.cimc.power.opts), + ('cisco_ucs', ironic.drivers.modules.ucs.power.opts), + ('conductor', itertools.chain( + ironic.conductor.base_manager.conductor_opts, + ironic.conductor.manager.conductor_opts)), + ('console', ironic.drivers.modules.console_utils.opts), + ('database', ironic.db.sqlalchemy.models.sql_opts), + ('deploy', ironic.drivers.modules.deploy_utils.deploy_opts), + ('dhcp', ironic.common.dhcp_factory.dhcp_provider_opts), + ('glance', itertools.chain( + ironic.common.glance_service.v2.image_service.glance_opts, + ironic.common.image_service.glance_opts)), + ('iboot', ironic.drivers.modules.iboot.opts), + ('ilo', itertools.chain( + ironic.drivers.modules.ilo.common.opts, + ironic.drivers.modules.ilo.deploy.clean_opts, + ironic.drivers.modules.ilo.management.clean_step_opts, + ironic.drivers.modules.ilo.power.opts)), + ('inspector', ironic.drivers.modules.inspector.inspector_opts), + ('ipmi', ironic.drivers.modules.ipminative.opts), + ('irmc', itertools.chain( + ironic.drivers.modules.irmc.boot.opts, + ironic.drivers.modules.irmc.common.opts)), + ('iscsi', ironic.drivers.modules.iscsi_deploy.iscsi_opts), + ('keystone', ironic.common.keystone.keystone_opts), + ('neutron', ironic.dhcp.neutron.neutron_opts), + ('oneview', ironic.drivers.modules.oneview.common.opts), + ('pxe', itertools.chain( + ironic.drivers.modules.iscsi_deploy.pxe_opts, + ironic.drivers.modules.pxe.pxe_opts)), + ('seamicro', ironic.drivers.modules.seamicro.opts), + ('snmp', ironic.drivers.modules.snmp.opts), + ('ssh', ironic.drivers.modules.ssh.libvirt_opts), + ('swift', ironic.common.swift.swift_opts), + ('virtualbox', ironic.drivers.modules.virtualbox.opts), +] + + +def list_opts(): + """Return a list of oslo.config options available in Ironic code. + + The returned list includes all oslo.config options. Each element of + the list is a tuple. The first element is the name of the group, the + second element is the options. + + The function is discoverable via the 'ironic' entry point under the + 'oslo.config.opts' namespace. + + The function is used by Oslo sample config file generator to discover the + options. + + :returns: a list of (group, options) tuples + """ + return _opts diff --git a/ironic/netconf.py b/ironic/netconf.py index eaae7c8900..bfebd1a55e 100644 --- a/ironic/netconf.py +++ b/ironic/netconf.py @@ -25,6 +25,7 @@ CONF = cfg.CONF netconf_opts = [ cfg.StrOpt('my_ip', default=netutils.get_my_ipv4(), + sample_default='127.0.0.1', help=_('IP address of this host. If unset, will determine the ' 'IP programmatically. If unable to do so, will use ' '"127.0.0.1".')), diff --git a/releasenotes/notes/adopt-oslo-config-generator-15afd2e7c2f008b4.yaml b/releasenotes/notes/adopt-oslo-config-generator-15afd2e7c2f008b4.yaml new file mode 100644 index 0000000000..4ee23f3c9b --- /dev/null +++ b/releasenotes/notes/adopt-oslo-config-generator-15afd2e7c2f008b4.yaml @@ -0,0 +1,9 @@ +--- +other: + Adopt oslo-config-generator to generate sample config files. + New config options from Ironic code should register with + ironic/conf/opts.py. New external libraries should register + with tools/config/ironic-config-generator.conf. + A deprecated option should add a deprecated group even if it + didn't alter its group, otherwise the deprecated group will + use 'DEFAULT' by default. diff --git a/setup.cfg b/setup.cfg index 0bafa1abdf..d7e5196aad 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,6 +22,9 @@ packages = ironic_tempest_plugin [entry_points] +oslo.config.opts = + ironic = ironic.conf.opts:list_opts + console_scripts = ironic-api = ironic.cmd.api:main ironic-dbsync = ironic.cmd.dbsync:main diff --git a/tools/config/check_uptodate.sh b/tools/config/check_uptodate.sh index 392a08b771..d762c92540 100755 --- a/tools/config/check_uptodate.sh +++ b/tools/config/check_uptodate.sh @@ -2,6 +2,7 @@ PROJECT_NAME=${PROJECT_NAME:-ironic} CFGFILE_NAME=${PROJECT_NAME}.conf.sample +OSLO_CFGFILE_OPTION=${OSLO_CFGFILE_OPTION:-tools/config/ironic-config-generator.conf} if [ -e etc/${PROJECT_NAME}/${CFGFILE_NAME} ]; then CFGFILE=etc/${PROJECT_NAME}/${CFGFILE_NAME} @@ -15,7 +16,7 @@ fi TEMPDIR=`mktemp -d /tmp/${PROJECT_NAME}.XXXXXX` trap "rm -rf $TEMPDIR" EXIT -tools/config/generate_sample.sh -b ./ -p ${PROJECT_NAME} -o ${TEMPDIR} +oslo-config-generator --config-file=${OSLO_CFGFILE_OPTION} --output-file ${TEMPDIR}/${CFGFILE_NAME} if [ $? != 0 ] then exit 1 @@ -24,6 +25,6 @@ fi if ! diff -u ${TEMPDIR}/${CFGFILE_NAME} ${CFGFILE} then echo "${0##*/}: ${PROJECT_NAME}.conf.sample is not up to date." - echo "${0##*/}: Please run ${0%%${0##*/}}generate_sample.sh." + echo "${0##*/}: Please run oslo-config-generator --config-file=${OSLO_CFGFILE_OPTION}" exit 1 fi diff --git a/tools/config/generate_sample.sh b/tools/config/generate_sample.sh deleted file mode 100755 index 5363fbf472..0000000000 --- a/tools/config/generate_sample.sh +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env bash - -# Generate sample configuration for your project. -# -# Aside from the command line flags, it also respects a config file which -# should be named oslo.config.generator.rc and be placed in the same directory. -# -# You can then export the following variables: -# IRONIC_CONFIG_GENERATOR_EXTRA_MODULES: list of modules to interrogate for options. -# IRONIC_CONFIG_GENERATOR_EXTRA_LIBRARIES: list of libraries to discover. -# IRONIC_CONFIG_GENERATOR_EXCLUDED_FILES: list of files to remove from automatic listing. - -print_hint() { - echo "Try \`${0##*/} --help' for more information." >&2 -} - -PARSED_OPTIONS=$(getopt -n "${0##*/}" -o hb:p:m:l:o: \ - --long help,base-dir:,package-name:,output-dir:,module:,library: -- "$@") - -if [ $? != 0 ] ; then print_hint ; exit 1 ; fi - -eval set -- "$PARSED_OPTIONS" - -while true; do - case "$1" in - -h|--help) - echo "${0##*/} [options]" - echo "" - echo "options:" - echo "-h, --help show brief help" - echo "-b, --base-dir=DIR project base directory" - echo "-p, --package-name=NAME project package name" - echo "-o, --output-dir=DIR file output directory" - echo "-m, --module=MOD extra python module to interrogate for options" - echo "-l, --library=LIB extra library that registers options for discovery" - exit 0 - ;; - -b|--base-dir) - shift - BASEDIR=`echo $1 | sed -e 's/\/*$//g'` - shift - ;; - -p|--package-name) - shift - PACKAGENAME=`echo $1` - shift - ;; - -o|--output-dir) - shift - OUTPUTDIR=`echo $1 | sed -e 's/\/*$//g'` - shift - ;; - -m|--module) - shift - MODULES="$MODULES -m $1" - shift - ;; - -l|--library) - shift - LIBRARIES="$LIBRARIES -l $1" - shift - ;; - --) - break - ;; - esac -done - -BASEDIR=${BASEDIR:-`pwd`} -if ! [ -d $BASEDIR ] -then - echo "${0##*/}: missing project base directory" >&2 ; print_hint ; exit 1 -elif [[ $BASEDIR != /* ]] -then - BASEDIR=$(cd "$BASEDIR" && pwd) -fi - -PACKAGENAME=${PACKAGENAME:-$(python setup.py --name)} -TARGETDIR=$BASEDIR/$PACKAGENAME -if ! [ -d $TARGETDIR ] -then - echo "${0##*/}: invalid project package name" >&2 ; print_hint ; exit 1 -fi - -OUTPUTDIR=${OUTPUTDIR:-$BASEDIR/etc} -# NOTE(bnemec): Some projects put their sample config in etc/, -# some in etc/$PACKAGENAME/ -if [ -d $OUTPUTDIR/$PACKAGENAME ] -then - OUTPUTDIR=$OUTPUTDIR/$PACKAGENAME -elif ! [ -d $OUTPUTDIR ] -then - echo "${0##*/}: cannot access \`$OUTPUTDIR': No such file or directory" >&2 - exit 1 -fi - -BASEDIRESC=`echo $BASEDIR | sed -e 's/\//\\\\\//g'` -find $TARGETDIR -type f -name "*.pyc" -delete -FILES=$(find $TARGETDIR -type f -name "*.py" ! -path "*/tests/*" ! -path "*/nova/*" \ - -exec grep -l "Opt(" {} + | sed -e "s/^$BASEDIRESC\///g" | sort -u) - -RC_FILE="`dirname $0`/oslo.config.generator.rc" -if test -r "$RC_FILE" -then - source "$RC_FILE" -fi - -for filename in ${IRONIC_CONFIG_GENERATOR_EXCLUDED_FILES}; do - FILES="${FILES[@]/$filename/}" -done - -for mod in ${IRONIC_CONFIG_GENERATOR_EXTRA_MODULES}; do - MODULES="$MODULES -m $mod" -done - -for lib in ${IRONIC_CONFIG_GENERATOR_EXTRA_LIBRARIES}; do - LIBRARIES="$LIBRARIES -l $lib" -done - -export EVENTLET_NO_GREENDNS=yes - -OS_VARS=$(set | sed -n '/^OS_/s/=[^=]*$//gp' | xargs) -[ "$OS_VARS" ] && eval "unset \$OS_VARS" -DEFAULT_CONFIG_GENERATOR=ironic.common.config_generator.generator -CONFIG_GENERATOR=${CONFIG_GENERATOR:-$DEFAULT_CONFIG_GENERATOR} -OUTPUTFILE=$OUTPUTDIR/$PACKAGENAME.conf.sample -python -m $CONFIG_GENERATOR $MODULES $LIBRARIES $FILES > $OUTPUTFILE -if [ $? != 0 ] -then - echo "Can not generate $OUTPUTFILE" - exit 1 -fi - -# Hook to allow projects to append custom config file snippets -CONCAT_FILES=$(ls $BASEDIR/tools/config/*.conf.sample 2>/dev/null) -for CONCAT_FILE in $CONCAT_FILES; do - cat $CONCAT_FILE >> $OUTPUTFILE -done diff --git a/tools/config/ironic-config-generator.conf b/tools/config/ironic-config-generator.conf new file mode 100644 index 0000000000..4196cda88a --- /dev/null +++ b/tools/config/ironic-config-generator.conf @@ -0,0 +1,17 @@ +[DEFAULT] +output_file = etc/ironic/ironic.conf.sample +wrap_width = 62 +namespace = ironic +namespace = ironic_lib.disk_utils +namespace = ironic_lib.disk_partitioner +namespace = ironic_lib.utils +namespace = oslo.db +namespace = oslo.messaging +namespace = oslo.middleware.cors +namespace = oslo.concurrency +namespace = oslo.policy +namespace = oslo.log +namespace = oslo.service.service +namespace = oslo.service.periodic_task +namespace = oslo.service.sslutils +namespace = keystonemiddleware.auth_token diff --git a/tools/config/oslo.config.generator.rc b/tools/config/oslo.config.generator.rc deleted file mode 100644 index 447d8144dd..0000000000 --- a/tools/config/oslo.config.generator.rc +++ /dev/null @@ -1,2 +0,0 @@ -export IRONIC_CONFIG_GENERATOR_EXTRA_LIBRARIES='oslo.db oslo.messaging oslo.middleware.cors keystonemiddleware.auth_token oslo.concurrency oslo.policy oslo.log oslo.service.service oslo.service.periodic_task oslo.service.sslutils' -export IRONIC_CONFIG_GENERATOR_EXTRA_MODULES='ironic_lib.disk_utils ironic_lib.disk_partitioner ironic_lib.utils' diff --git a/tox.ini b/tox.ini index 0863a7b198..6339ffeb53 100644 --- a/tox.ini +++ b/tox.ini @@ -53,11 +53,10 @@ commands = {toxinidir}/tools/config/check_uptodate.sh [testenv:genconfig] -whitelist_externals = bash sitepackages = False envdir = {toxworkdir}/venv commands = - bash tools/config/generate_sample.sh -b . -p ironic -o etc/ironic + oslo-config-generator --config-file=tools/config/ironic-config-generator.conf [testenv:docs] setenv = PYTHONHASHSEED=0