summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnita Kuno <anita.kuno@enovance.com>2013-06-14 16:54:03 -0400
committerAnita Kuno <anita.kuno@enovance.com>2013-06-20 19:14:39 -0400
commitcf84f2bf8a12b82ff91b5918553c61c3c2763787 (patch)
tree1b1c0ad0302eb660e41767f71eeed2d7dbbf0b48
parent0442a0034fbeb275e2a57d87d5a66e48a12ee5e1 (diff)
downloadironic-cf84f2bf8a12b82ff91b5918553c61c3c2763787.tar.gz
Add conf file generator.
Add oslo generator.py and generate_sample.sh similar to nova. These files provide generator functionality for creating a conf file. Fixes: bug #1191032 Change-Id: I5a239e41f822e2f51c95d7a44e8d62c38c62c8ac
-rw-r--r--etc/ironic/ironic.conf.sample526
-rwxr-xr-xironic/openstack/common/config/generator.py254
-rw-r--r--openstack-common.conf1
-rwxr-xr-xtools/conf/generate_sample.sh27
4 files changed, 808 insertions, 0 deletions
diff --git a/etc/ironic/ironic.conf.sample b/etc/ironic/ironic.conf.sample
new file mode 100644
index 000000000..5a2e2be7f
--- /dev/null
+++ b/etc/ironic/ironic.conf.sample
@@ -0,0 +1,526 @@
+[DEFAULT]
+
+#
+# Options defined in ironic.netconf
+#
+
+# ip address of this host (string value)
+#my_ip=10.0.0.1
+
+# use ipv6 (boolean value)
+#use_ipv6=false
+
+
+#
+# Options defined in ironic.api
+#
+
+# IP for the Ironic API server to bind to (string value)
+#ironic_api_bind_ip=0.0.0.0
+
+# The port for the Ironic API server (integer value)
+#ironic_api_port=6385
+
+
+#
+# Options defined in ironic.api.app
+#
+
+# Method to use for auth: noauth or keystone. (string value)
+#auth_strategy=noauth
+
+
+#
+# Options defined in ironic.common.exception
+#
+
+# make exception message format errors fatal (boolean value)
+#fatal_exception_format_errors=false
+
+
+#
+# Options defined in ironic.common.paths
+#
+
+# Directory where the nova python module is installed (string
+# value)
+#pybasedir=/usr/lib/python/site-packages/ironic
+
+# Directory where nova binaries are installed (string value)
+#bindir=$pybasedir/bin
+
+# Top-level directory for maintaining nova's state (string
+# value)
+#state_path=$pybasedir
+
+
+#
+# Options defined in ironic.common.policy
+#
+
+# JSON file representing policy (string value)
+#policy_file=policy.json
+
+# Rule checked when requested rule is not found (string value)
+#policy_default_rule=default
+
+
+#
+# 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
+
+# Explicitly specify the temporary working directory (string
+# value)
+#tempdir=<None>
+
+
+#
+# Options defined in ironic.db.sqlalchemy.models
+#
+
+# MySQL engine (string value)
+#mysql_engine=InnoDB
+
+
+#
+# Options defined in ironic.drivers.modules.ipmi
+#
+
+# path to baremetal terminal program (string value)
+#terminal=shellinaboxd
+
+# path to baremetal terminal SSL cert(PEM) (string value)
+#terminal_cert_dir=<None>
+
+# path to directory stores pidfiles of baremetal_terminal
+# (string value)
+#terminal_pid_dir=$state_path/baremetal/console
+
+# Maximum seconds to retry IPMI operations (integer value)
+#ipmi_power_retry=5
+
+
+#
+# Options defined in ironic.openstack.common.db.sqlalchemy.session
+#
+
+# the filename to use with sqlite (string value)
+#sqlite_db=ironic.sqlite
+
+# If true, use synchronous mode for sqlite (boolean value)
+#sqlite_synchronous=true
+
+
+#
+# Options defined in ironic.openstack.common.eventlet_backdoor
+#
+
+# port for eventlet backdoor to listen (integer value)
+#backdoor_port=<None>
+
+
+#
+# Options defined in ironic.openstack.common.lockutils
+#
+
+# Whether to disable inter-process locks (boolean value)
+#disable_process_locking=false
+
+# Directory to use for lock files. Default to a temp directory
+# (string value)
+#lock_path=<None>
+
+
+#
+# Options defined in ironic.openstack.common.log
+#
+
+# Print debugging output (set logging level to DEBUG instead
+# of default WARNING level). (boolean value)
+#debug=false
+
+# Print more verbose output (set logging level to INFO instead
+# of default WARNING level). (boolean value)
+#verbose=false
+
+# Log output to standard error (boolean value)
+#use_stderr=true
+
+# format string to use for log messages with context (string
+# value)
+#logging_context_format_string=%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [%(request_id)s %(user)s %(tenant)s] %(instance)s%(message)s
+
+# format string to use for log messages without context
+# (string value)
+#logging_default_format_string=%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [-] %(instance)s%(message)s
+
+# data to append to log format when level is DEBUG (string
+# value)
+#logging_debug_format_suffix=%(funcName)s %(pathname)s:%(lineno)d
+
+# prefix each line of exception output with this format
+# (string value)
+#logging_exception_prefix=%(asctime)s.%(msecs)03d %(process)d TRACE %(name)s %(instance)s
+
+# list of logger=LEVEL pairs (list value)
+#default_log_levels=amqplib=WARN,sqlalchemy=WARN,boto=WARN,suds=INFO,keystone=INFO,eventlet.wsgi.server=WARN
+
+# publish error events (boolean value)
+#publish_errors=false
+
+# make deprecations fatal (boolean value)
+#fatal_deprecations=false
+
+# If an instance is passed with the log message, format it
+# like this (string value)
+#instance_format="[instance: %(uuid)s] "
+
+# If an instance UUID is passed with the log message, format
+# it like this (string value)
+#instance_uuid_format="[instance: %(uuid)s] "
+
+# If this option is specified, the logging configuration file
+# specified is used and overrides any other logging options
+# specified. Please see the Python logging module
+# documentation for details on logging configuration files.
+# (string value)
+#log_config=<None>
+
+# A logging.Formatter log message format string which may use
+# any of the available logging.LogRecord attributes. This
+# option is deprecated. Please use
+# logging_context_format_string and
+# logging_default_format_string instead. (string value)
+#log_format=<None>
+
+# Format string for %%(asctime)s in log records. Default:
+# %(default)s (string value)
+#log_date_format=%Y-%m-%d %H:%M:%S
+
+# (Optional) Name of log file to output to. If no default is
+# set, logging will go to stdout. (string value)
+#log_file=<None>
+
+# (Optional) The base directory used for relative --log-file
+# paths (string value)
+#log_dir=<None>
+
+# Use syslog for logging. (boolean value)
+#use_syslog=false
+
+# syslog facility to receive log lines (string value)
+#syslog_log_facility=LOG_USER
+
+
+#
+# Options defined in ironic.openstack.common.notifier.api
+#
+
+# Driver or drivers to handle sending notifications (multi
+# valued)
+#notification_driver=
+
+# Default notification level for outgoing notifications
+# (string value)
+#default_notification_level=INFO
+
+# Default publisher_id for outgoing notifications (string
+# value)
+#default_publisher_id=$host
+
+
+#
+# Options defined in ironic.openstack.common.notifier.rpc_notifier
+#
+
+# AMQP topic used for openstack notifications (list value)
+#notification_topics=notifications
+
+
+#
+# Options defined in ironic.openstack.common.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 ironic.openstack.common.rpc
+#
+
+# The messaging module to use, defaults to kombu. (string
+# value)
+#rpc_backend=ironic.openstack.common.rpc.impl_kombu
+
+# Size of RPC thread pool (integer value)
+#rpc_thread_pool_size=64
+
+# Size of RPC connection pool (integer value)
+#rpc_conn_pool_size=30
+
+# Seconds to wait for a response from call or multicall
+# (integer value)
+#rpc_response_timeout=60
+
+# Seconds to wait before a cast expires (TTL). Only supported
+# by impl_zmq. (integer value)
+#rpc_cast_timeout=30
+
+# Modules of exceptions that are permitted to be recreatedupon
+# receiving exception data from an rpc call. (list value)
+#allowed_rpc_exception_modules=ironic.openstack.common.exception,nova.exception,cinder.exception,exceptions
+
+# If passed, use a fake RabbitMQ provider (boolean value)
+#fake_rabbit=false
+
+# AMQP exchange to connect to if using RabbitMQ or Qpid
+# (string value)
+#control_exchange=openstack
+
+
+#
+# Options defined in ironic.openstack.common.rpc.amqp
+#
+
+# Enable a fast single reply queue if using AMQP based RPC
+# like RabbitMQ or Qpid. (boolean value)
+#amqp_rpc_single_reply_queue=false
+
+
+#
+# Options defined in ironic.openstack.common.rpc.impl_kombu
+#
+
+# SSL version to use (valid only if SSL enabled) (string
+# value)
+#kombu_ssl_version=
+
+# SSL key file (valid only if SSL enabled) (string value)
+#kombu_ssl_keyfile=
+
+# SSL cert file (valid only if SSL enabled) (string value)
+#kombu_ssl_certfile=
+
+# SSL certification authority file (valid only if SSL enabled)
+# (string value)
+#kombu_ssl_ca_certs=
+
+# The RabbitMQ broker address where a single node is used
+# (string value)
+#rabbit_host=localhost
+
+# The RabbitMQ broker port where a single node is used
+# (integer value)
+#rabbit_port=5672
+
+# RabbitMQ HA cluster host:port pairs (list value)
+#rabbit_hosts=$rabbit_host:$rabbit_port
+
+# connect over SSL for RabbitMQ (boolean value)
+#rabbit_use_ssl=false
+
+# the RabbitMQ userid (string value)
+#rabbit_userid=guest
+
+# the RabbitMQ password (string value)
+#rabbit_password=guest
+
+# the RabbitMQ virtual host (string value)
+#rabbit_virtual_host=/
+
+# how frequently to retry connecting with RabbitMQ (integer
+# value)
+#rabbit_retry_interval=1
+
+# how long to backoff for between retries when connecting to
+# RabbitMQ (integer value)
+#rabbit_retry_backoff=2
+
+# maximum retries with trying to connect to RabbitMQ (the
+# default of 0 implies an infinite retry count) (integer
+# value)
+#rabbit_max_retries=0
+
+# use durable queues in RabbitMQ (boolean value)
+#rabbit_durable_queues=false
+
+# use H/A queues in RabbitMQ (x-ha-policy: all).You need to
+# wipe RabbitMQ database when changing this option. (boolean
+# value)
+#rabbit_ha_queues=false
+
+
+#
+# Options defined in ironic.openstack.common.rpc.impl_qpid
+#
+
+# Qpid broker hostname (string value)
+#qpid_hostname=localhost
+
+# Qpid broker port (integer value)
+#qpid_port=5672
+
+# Qpid HA cluster host:port pairs (list value)
+#qpid_hosts=$qpid_hostname:$qpid_port
+
+# Username for qpid connection (string value)
+#qpid_username=
+
+# Password for qpid connection (string value)
+#qpid_password=
+
+# Space separated list of SASL mechanisms to use for auth
+# (string value)
+#qpid_sasl_mechanisms=
+
+# Seconds between connection keepalive heartbeats (integer
+# value)
+#qpid_heartbeat=60
+
+# Transport to use, either 'tcp' or 'ssl' (string value)
+#qpid_protocol=tcp
+
+# Disable Nagle algorithm (boolean value)
+#qpid_tcp_nodelay=true
+
+
+#
+# Options defined in ironic.openstack.common.rpc.impl_zmq
+#
+
+# ZeroMQ bind address. Should be a wildcard (*), an ethernet
+# interface, or IP. The "host" option should point or resolve
+# to this address. (string value)
+#rpc_zmq_bind_address=*
+
+# MatchMaker driver (string value)
+#rpc_zmq_matchmaker=ironic.openstack.common.rpc.matchmaker.MatchMakerLocalhost
+
+# ZeroMQ receiver listening port (integer value)
+#rpc_zmq_port=9501
+
+# Number of ZeroMQ contexts, defaults to 1 (integer value)
+#rpc_zmq_contexts=1
+
+# Maximum number of ingress messages to locally buffer per
+# topic. Default is unlimited. (integer value)
+#rpc_zmq_topic_backlog=<None>
+
+# Directory for holding IPC sockets (string value)
+#rpc_zmq_ipc_dir=/var/run/openstack
+
+# Name of this node. Must be a valid hostname, FQDN, or IP
+# address. Must match "host" option, if running Nova. (string
+# value)
+#rpc_zmq_host=ironic
+
+
+#
+# Options defined in ironic.openstack.common.rpc.matchmaker
+#
+
+# Heartbeat frequency (integer value)
+#matchmaker_heartbeat_freq=300
+
+# Heartbeat time-to-live. (integer value)
+#matchmaker_heartbeat_ttl=600
+
+
+[rpc_notifier2]
+
+#
+# Options defined in ironic.openstack.common.notifier.rpc_notifier2
+#
+
+# AMQP topic(s) used for openstack notifications (list value)
+#topics=notifications
+
+
+[matchmaker_redis]
+
+#
+# Options defined in ironic.openstack.common.rpc.matchmaker_redis
+#
+
+# Host to locate redis (string value)
+#host=127.0.0.1
+
+# Use this port to connect to redis host. (integer value)
+#port=6379
+
+# Password for Redis server. (optional) (string value)
+#password=<None>
+
+
+[matchmaker_ring]
+
+#
+# Options defined in ironic.openstack.common.rpc.matchmaker_ring
+#
+
+# Matchmaker ring file (JSON) (string value)
+#ringfile=/etc/oslo/matchmaker_ring.json
+
+
+[database]
+
+#
+# Options defined in ironic.openstack.common.db.api
+#
+
+# The backend to use for db (string value)
+#backend=sqlalchemy
+
+# Enable the experimental use of thread pooling for all DB API
+# calls (boolean value)
+#use_tpool=false
+
+
+#
+# Options defined in ironic.openstack.common.db.sqlalchemy.session
+#
+
+# The SQLAlchemy connection string used to connect to the
+# database (string value)
+#connection=sqlite:////ironic/openstack/common/db/$sqlite_db
+
+# timeout before idle sql connections are reaped (integer
+# value)
+#idle_timeout=3600
+
+# Minimum number of SQL connections to keep open in a pool
+# (integer value)
+#min_pool_size=1
+
+# Maximum number of SQL connections to keep open in a pool
+# (integer value)
+#max_pool_size=5
+
+# maximum db connection retries during startup. (setting -1
+# implies an infinite retry count) (integer value)
+#max_retries=10
+
+# interval between retries of opening a sql connection
+# (integer value)
+#retry_interval=10
+
+# If set, use this value for max_overflow with sqlalchemy
+# (integer value)
+#max_overflow=<None>
+
+# Verbosity of SQL debugging information. 0=None,
+# 100=Everything (integer value)
+#connection_debug=0
+
+# Add python stack traces to SQL as comment strings (boolean
+# value)
+#connection_trace=false
+
+
+# Total option count: 106
diff --git a/ironic/openstack/common/config/generator.py b/ironic/openstack/common/config/generator.py
new file mode 100755
index 000000000..deb006e59
--- /dev/null
+++ b/ironic/openstack/common/config/generator.py
@@ -0,0 +1,254 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 SINA Corporation
+# 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.
+#
+# @author: Zhongyue Luo, SINA Corporation.
+#
+"""Extracts OpenStack config option info from module(s)."""
+
+import imp
+import os
+import re
+import socket
+import sys
+import textwrap
+
+from oslo.config import cfg
+
+from ironic.openstack.common import gettextutils
+from ironic.openstack.common import importutils
+
+gettextutils.install('ironic')
+
+STROPT = "StrOpt"
+BOOLOPT = "BoolOpt"
+INTOPT = "IntOpt"
+FLOATOPT = "FloatOpt"
+LISTOPT = "ListOpt"
+MULTISTROPT = "MultiStrOpt"
+
+OPT_TYPES = {
+ STROPT: 'string value',
+ BOOLOPT: 'boolean value',
+ INTOPT: 'integer value',
+ FLOATOPT: 'floating point value',
+ LISTOPT: 'list value',
+ MULTISTROPT: 'multi valued',
+}
+
+OPTION_COUNT = 0
+OPTION_REGEX = re.compile(r"(%s)" % "|".join([STROPT, BOOLOPT, INTOPT,
+ FLOATOPT, LISTOPT,
+ MULTISTROPT]))
+
+PY_EXT = ".py"
+BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
+ "../../../../"))
+WORDWRAP_WIDTH = 60
+
+
+def generate(srcfiles):
+ mods_by_pkg = dict()
+ for filepath in 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 = filter(lambda x: x.endswith(PY_EXT), mods_by_pkg.keys())
+ pkg_names.sort()
+ ext_names = filter(lambda x: x not in pkg_names, mods_by_pkg.keys())
+ ext_names.sort()
+ 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': []}
+
+ 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:
+ continue
+
+ 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, opts in opts_by_group.items():
+ print_group_opts(group, opts)
+
+ print "# Total option count: %d" % OPTION_COUNT
+
+
+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 ImportError as ie:
+ sys.stderr.write("%s\n" % str(ie))
+ return None
+ except Exception:
+ return None
+
+
+def _is_in_group(opt, group):
+ "Check if opt is in group."
+ for key, value in group._opts.items():
+ if value['opt'] == opt:
+ return True
+ return False
+
+
+def _guess_groups(opt, mod_obj):
+ # is it in the DEFAULT group?
+ if _is_in_group(opt, cfg.CONF):
+ return 'DEFAULT'
+
+ # what other groups is it in?
+ for key, value in cfg.CONF.items():
+ 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()
+ 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, obj), []).append(opt)
+ return ret.items()
+
+
+def print_group_opts(group, opts_by_module):
+ print "[%s]" % group
+ print
+ global OPTION_COUNT
+ for mod, opts in opts_by_module:
+ OPTION_COUNT += len(opts)
+ print '#'
+ print '# Options defined in %s' % mod
+ print '#'
+ print
+ for opt in opts:
+ _print_opt(opt)
+ print
+
+
+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(s):
+ """Set up a reasonably sensible default for pybasedir, my_ip and host."""
+ if s.startswith(BASEDIR):
+ return s.replace(BASEDIR, '/usr/lib/python/site-packages')
+ elif BASEDIR in s:
+ return s.replace(BASEDIR, '')
+ elif s == _get_my_ip():
+ return '10.0.0.1'
+ elif s == socket.gethostname():
+ return 'ironic'
+ elif s.strip() != s:
+ return '"%s"' % s
+ return s
+
+
+def _print_opt(opt):
+ 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_type = None
+ try:
+ opt_type = OPTION_REGEX.search(str(type(opt))).group(0)
+ except (ValueError, AttributeError) as err:
+ sys.stderr.write("%s\n" % str(err))
+ sys.exit(1)
+ opt_help += ' (' + OPT_TYPES[opt_type] + ')'
+ print '#', "\n# ".join(textwrap.wrap(opt_help, WORDWRAP_WIDTH))
+ try:
+ if opt_default is None:
+ print '#%s=<None>' % opt_name
+ elif opt_type == STROPT:
+ assert(isinstance(opt_default, basestring))
+ print '#%s=%s' % (opt_name, _sanitize_default(opt_default))
+ 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 == 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 == MULTISTROPT:
+ assert(isinstance(opt_default, list))
+ if not opt_default:
+ opt_default = ['']
+ for default in opt_default:
+ print '#%s=%s' % (opt_name, default)
+ print
+ except Exception:
+ sys.stderr.write('Error in option "%s"\n' % opt_name)
+ sys.exit(1)
+
+
+def main():
+ if len(sys.argv) < 2:
+ print "usage: %s [srcfile]...\n" % sys.argv[0]
+ sys.exit(0)
+ generate(sys.argv[1:])
+
+if __name__ == '__main__':
+ main()
diff --git a/openstack-common.conf b/openstack-common.conf
index 24cf5d917..7f8dbac3d 100644
--- a/openstack-common.conf
+++ b/openstack-common.conf
@@ -1,5 +1,6 @@
[DEFAULT]
module=cliutils
+module=config.generator
module=context
module=db
module=db.sqlalchemy
diff --git a/tools/conf/generate_sample.sh b/tools/conf/generate_sample.sh
new file mode 100755
index 000000000..2b442ea2f
--- /dev/null
+++ b/tools/conf/generate_sample.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 SINA Corporation
+# All Rights Reserved.
+# Author: Zhongyue Luo <lzyeval@gmail.com>
+#
+# 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.
+
+FILES=$(find ironic -type f -name "*.py" ! -path "ironic/tests/*" \
+ ! -path "ironic/nova/*" -exec grep -l "Opt(" {} + | sort -u)
+
+export EVENTLET_NO_GREENDNS=yes
+
+MODULEPATH=$(dirname "$0")/../../ironic/openstack/common/config/generator.py
+OUTPUTPATH=etc/ironic/ironic.conf.sample
+PYTHONPATH=./:${PYTHONPATH} python $MODULEPATH $FILES > $OUTPUTPATH