]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Update openstack.common.config
authorÉmilien Macchi <emilien.macchi@enovance.com>
Wed, 10 Jul 2013 14:01:00 +0000 (16:01 +0200)
committerÉmilien Macchi <emilien.macchi@enovance.com>
Thu, 18 Jul 2013 19:01:02 +0000 (12:01 -0700)
- Add generator.py from Oslo
- Add notifier from Oslo
- Add generator bash script used by other projects to generate configuration
files
- Update openstack-common.conf file

The patch https://review.openstack.org/#/c/36493/ needs
to be merged before this one. I'll rebase this patch and
generate the heat configuration.

Fix bug #1183275

Change-Id: Id7f24874b7e3e07d06dcf14bc381b9d630f67fc8

etc/heat/heat.conf.sample [new file with mode: 0644]
heat/openstack/common/config/__init__.py [new file with mode: 0644]
heat/openstack/common/config/generator.py [new file with mode: 0755]
openstack-common.conf
tools/conf/check_uptodate.sh [new file with mode: 0755]
tools/conf/generate_sample.sh [new file with mode: 0755]
tox.ini

diff --git a/etc/heat/heat.conf.sample b/etc/heat/heat.conf.sample
new file mode 100644 (file)
index 0000000..f1fc6ff
--- /dev/null
@@ -0,0 +1,491 @@
+[DEFAULT]
+
+#
+# Options defined in heat.common.config
+#
+
+# The SQLAlchemy connection string used to connect to the
+# database (string value)
+#sql_connection=mysql://heat:heat@localhost/heat
+
+# timeout before idle sql connections are reaped (integer
+# value)
+#sql_idle_timeout=3600
+
+# The default user for new instances (string value)
+#instance_user=ec2-user
+
+# Driver to use for controlling instances (string value)
+#instance_driver=heat.engine.nova
+
+# List of directories to search for Plugins (list value)
+#plugin_dirs=/usr/lib64/heat,/usr/lib/heat
+
+# Name of the engine node. This can be an opaque identifier.It
+# is not necessarily a hostname, FQDN, or IP address. (string
+# value)
+#host=heat
+
+# seconds between nodes reporting state to datastore (integer
+# value)
+#report_interval=10
+
+# seconds between running periodic tasks (integer value)
+#periodic_interval=60
+
+# IP address for EC2 API to listen (string value)
+#ec2_listen=0.0.0.0
+
+# port for ec2 api to listen (integer value)
+#ec2_listen_port=8773
+
+# IP address for OpenStack API to listen (string value)
+#osapi_compute_listen=0.0.0.0
+
+# list port for osapi compute (integer value)
+#osapi_compute_listen_port=8774
+
+# IP address for OpenStack Volume API to listen (string value)
+#osapi_volume_listen=0.0.0.0
+
+# port for os volume api to listen (integer value)
+#osapi_volume_listen_port=8776
+
+# URL of the Heat metadata server (string value)
+#heat_metadata_server_url=
+
+# URL of the Heat waitcondition server (string value)
+#heat_waitcondition_server_url=
+
+# URL of the Heat cloudwatch server (string value)
+#heat_watch_server_url=
+
+# Instance connection to cfn/cw API via https (string value)
+#instance_connection_is_secure=0
+
+# Instance connection to cfn/cw API validate certs if ssl
+# (string value)
+#instance_connection_https_validate_certificates=1
+
+# Keystone role for heat template-defined users (string value)
+#heat_stack_user_role=heat_stack_user
+
+
+#
+# Options defined in heat.common.crypt
+#
+
+# Encryption key used for authentication info in database
+# (string value)
+#auth_encryption_key=notgood but just long enough i think
+
+
+#
+# Options defined in heat.common.policy
+#
+
+#  (string value)
+#policy_file=policy.json
+
+#  (string value)
+#policy_default_rule=default
+
+
+#
+# Options defined in heat.common.wsgi
+#
+
+#  (string value)
+#bind_host=0.0.0.0
+
+#  (integer value)
+#bind_port=<None>
+
+#  (integer value)
+#backlog=4096
+
+#  (string value)
+#cert_file=<None>
+
+#  (string value)
+#key_file=<None>
+
+#  (integer value)
+#workers=0
+
+
+#
+# Options defined in heat.db.api
+#
+
+# The backend to use for db (string value)
+#db_backend=sqlalchemy
+
+
+#
+# Options defined in heat.engine.clients
+#
+
+# Cloud module to use as a backend. Defaults to OpenStack.
+# (string value)
+#cloud_backend=<None>
+
+
+#
+# Options defined in heat.openstack.common.eventlet_backdoor
+#
+
+# port for eventlet backdoor to listen (integer value)
+#backdoor_port=<None>
+
+
+#
+# Options defined in heat.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 heat.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=<None>
+
+
+#
+# Options defined in heat.openstack.common.notifier.list_notifier
+#
+
+# List of drivers to send notifications (multi valued)
+#list_notifier_drivers=heat.openstack.common.notifier.no_op_notifier
+
+
+#
+# Options defined in heat.openstack.common.notifier.rpc_notifier
+#
+
+# AMQP topic used for openstack notifications (list value)
+#notification_topics=notifications
+
+
+#
+# Options defined in heat.openstack.common.rpc
+#
+
+# The messaging module to use, defaults to kombu. (string
+# value)
+#rpc_backend=heat.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=heat.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 heat.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 heat.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 heat.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=heat.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=heat
+
+
+#
+# Options defined in heat.openstack.common.rpc.matchmaker
+#
+
+# Heartbeat frequency (integer value)
+#matchmaker_heartbeat_freq=300
+
+# Heartbeat time-to-live. (integer value)
+#matchmaker_heartbeat_ttl=600
+
+
+[paste_deploy]
+
+#
+# Options defined in heat.common.config
+#
+
+#  (string value)
+#flavor=<None>
+
+# The API paste config file to use (string value)
+#api_paste_config=api-paste.ini
+
+
+[rpc_notifier2]
+
+#
+# Options defined in heat.openstack.common.notifier.rpc_notifier2
+#
+
+# AMQP topic(s) used for openstack notifications (list value)
+#topics=notifications
+
+
+[ec2authtoken]
+
+#
+# Options defined in heat.api.aws.ec2token
+#
+
+#  (string value)
+#auth_uri=<None>
+
+#  (string value)
+#keystone_ec2_uri=<None>
+
+
+[matchmaker_redis]
+
+#
+# Options defined in heat.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 heat.openstack.common.rpc.matchmaker_ring
+#
+
+# Matchmaker ring file (JSON) (string value)
+#ringfile=/etc/oslo/matchmaker_ring.json
+
+
+# Total option count: 107
diff --git a/heat/openstack/common/config/__init__.py b/heat/openstack/common/config/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/heat/openstack/common/config/generator.py b/heat/openstack/common/config/generator.py
new file mode 100755 (executable)
index 0000000..30c08a8
--- /dev/null
@@ -0,0 +1,263 @@
+#!/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)."""
+
+from __future__ import print_function
+
+import imp
+import os
+import re
+import socket
+import sys
+import textwrap
+
+from oslo.config import cfg
+
+from heat.openstack.common import gettextutils
+from heat.openstack.common import importutils
+
+gettextutils.install('heat')
+
+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(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.
+        s = os.path.normpath("/usr/" + s[len(sys.prefix):])
+    elif 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 'heat'
+    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_help = ""
+    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()
index c5e551c88638035f74f8df14d16b6bebd84a85ed..81b30c2f68eb9d4de4b2cb825bda496327f0e7a8 100644 (file)
@@ -21,6 +21,7 @@ module=service
 module=threadgroup
 module=timeutils
 module=uuidutils
+module=config
 
 # The base module to hold the copy of openstack.common
 base=heat
diff --git a/tools/conf/check_uptodate.sh b/tools/conf/check_uptodate.sh
new file mode 100755 (executable)
index 0000000..138a12f
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/sh
+TMPFILE=`mktemp`
+trap "rm -f ${TMPFILE}" EXIT
+tools/conf/generate_sample.sh "${TMPFILE}"
+if ! diff "${TMPFILE}" etc/heat/heat.conf.sample
+then
+    echo "E: heat.conf.sample is not up to date, please run tools/conf/generate_sample.sh"
+    exit 42
+fi
diff --git a/tools/conf/generate_sample.sh b/tools/conf/generate_sample.sh
new file mode 100755 (executable)
index 0000000..6a49117
--- /dev/null
@@ -0,0 +1,26 @@
+#!/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 heat -type f -name "*.py" ! -path "heat/tests/*" -exec \
+    grep -l "Opt(" {} \; | sort -u)
+
+DEST=${1:-etc/heat/heat.conf.sample}
+
+PYTHONPATH=./:${PYTHONPATH} \
+    python $(dirname "$0")/../../heat/openstack/common/config/generator.py ${FILES} > $DEST
diff --git a/tox.ini b/tox.ini
index 7df776a215810bd8975c602aef4baaf2619e0df0..3c9ede2a0e4c024858c524a34944310824f92719 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -10,7 +10,9 @@ commands =
   python setup.py testr --slowest --testr-args='{posargs}'
 
 [testenv:pep8]
-commands = flake8 heat bin/heat-api bin/heat-api-cfn bin/heat-api-cloudwatch bin/heat-cfn bin/heat-engine bin/heat-manage bin/heat-watch
+commands =
+    flake8 heat bin/heat-api bin/heat-api-cfn bin/heat-api-cloudwatch bin/heat-cfn bin/heat-engine bin/heat-manage bin/heat-watch
+    {toxinidir}/tools/conf/check_uptodate.sh
 
 [testenv:venv]
 commands = {posargs}