Reworking configuration options

Use config tools from Oslo-incubator :

* generate a consistant config file sample:
  etc/climate/climate.conf.sample

* Add a new check in tox env 'pep8' to check out-dated config file sample.

Move specifics options for admin credential in group config 'physical:host'

Factorize by declaring config options in each plugins with available values and
centralizing RESOURCE_TYPE value in plugin's module (one dir per plugin)

Use socket.gethostname() in place of socket.getfqdn() to have
predictable default value for 'host' config in climate.config,
since it's ugly forced in openstack.common.config.generator#L232

Fixes: bug #1271875

Change-Id: Ie54fc98b58b49400360c4fd2ce7d8bb3b75915e2
This commit is contained in:
Swann Croiset 2014-01-23 11:28:02 +01:00
parent 9daf3d8c97
commit e9d03ee1ea
19 changed files with 1292 additions and 63 deletions

View File

@ -21,7 +21,7 @@ cli_opts = [
cfg.BoolOpt('log-exchange', default=False,
help='Log request/response exchange details: environ, '
'headers and bodies'),
cfg.StrOpt('host', default=socket.getfqdn(),
cfg.StrOpt('host', default=socket.gethostname(),
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 '

View File

@ -0,0 +1,293 @@
# 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.
#
"""Extracts OpenStack config option info from module(s)."""
from __future__ import print_function
import argparse
import imp
import os
import re
import socket
import sys
import textwrap
from oslo.config import cfg
import six
import stevedore.named
from climate.openstack.common import gettextutils
from climate.openstack.common import importutils
gettextutils.install('climate')
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_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(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,
)
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, str(e)))
return None
def _is_in_group(opt, group):
"Check if opt is in group."
for value in group._opts.values():
# NOTE(llu): Temporary workaround for bug #1262148, wait until
# newly released oslo.config support '==' operator.
if not(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 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()
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('')
for mod, opts in opts_by_module:
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(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 == socket.gethostname() and 'host' in name:
return 'climate'
elif value.strip() != value:
return '"%s"' % value
return value
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)))
if opt.deprecated_opts:
for deprecated_opt in opt.deprecated_opts:
if deprecated_opt.name:
deprecated_group = (deprecated_opt.group if
deprecated_opt.group else "DEFAULT")
print('# Deprecated group/name - [%s]/%s' %
(deprecated_group,
deprecated_opt.name))
try:
if opt_default is None:
print('#%s=<None>' % opt_name)
elif opt_type == STROPT:
assert(isinstance(opt_default, six.string_types))
print('#%s=%s' % (opt_name, _sanitize_default(opt_name,
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():
generate(sys.argv[1:])
if __name__ == '__main__':
main()

View File

@ -0,0 +1 @@
RESOURCE_TYPE = u'virtual:instance'

View File

@ -19,24 +19,27 @@ from oslo.config import cfg
from climate import exceptions as climate_exceptions
from climate.openstack.common import log as logging
from climate.plugins import base
from climate.plugins import instances as plugin
from climate.utils.openstack import nova
LOG = logging.getLogger(__name__)
plugin_opts = [
cfg.StrOpt('on_end',
default='create_image, delete',
help='Actions which we will use in the end of the lease')
help='Actions which we will use in the end of the lease'),
cfg.StrOpt('on_start',
default='on_start',
help='Actions which we will use at the start of the lease'),
]
CONF = cfg.CONF
CONF.register_opts(plugin_opts, 'virtual:instance')
CONF.register_opts(plugin_opts, group=plugin.RESOURCE_TYPE)
class VMPlugin(base.BasePlugin):
"""Base plugin for VM reservation."""
resource_type = 'virtual:instance'
resource_type = plugin.RESOURCE_TYPE
title = "Basic VM Plugin"
description = ("This is basic plugin for VM management. "
"It can start, snapshot and suspend VMs")
@ -50,7 +53,7 @@ class VMPlugin(base.BasePlugin):
def on_end(self, resource_id):
nova_client = nova.ClimateNovaClient()
actions = self._split_actions(CONF['virtual:instance'].on_end)
actions = self._split_actions(CONF[plugin.RESOURCE_TYPE].on_end)
# actions will be processed in following order:
# - create image from VM

View File

@ -15,6 +15,7 @@
from oslo.config import cfg
RESOURCE_TYPE = u'physical:host'
admin_opts = [
cfg.StrOpt('climate_username',
@ -28,4 +29,4 @@ admin_opts = [
help='Tenant of the user for write operations'),
]
cfg.CONF.register_opts(admin_opts)
cfg.CONF.register_opts(admin_opts, group=RESOURCE_TYPE)

View File

@ -27,30 +27,45 @@ from climate.db import utils as db_utils
from climate.manager import exceptions as manager_ex
from climate.openstack.common import uuidutils
from climate.plugins import base
from climate.plugins import oshosts as plugin
from climate.plugins.oshosts import nova_inventory
from climate.plugins.oshosts import reservation_pool as rp
plugin_opts = [
cfg.StrOpt('on_end',
default='on_end',
help='Actions which we will use in the end of the lease'),
cfg.StrOpt('on_start',
default='on_start',
help='Actions which we will use at the start of the lease'),
]
CONF = cfg.CONF
CONF.register_opts(plugin_opts, group=plugin.RESOURCE_TYPE)
class PhysicalHostPlugin(base.BasePlugin):
"""Plugin for physical host resource."""
resource_type = 'physical:host'
resource_type = plugin.RESOURCE_TYPE
title = 'Physical Host Plugin'
description = 'This plugin starts and shutdowns the hosts.'
freepool_name = cfg.CONF[resource_type].aggregate_freepool_name
freepool_name = CONF[resource_type].aggregate_freepool_name
pool = None
inventory = None
def __init__(self):
#TODO(sbauza): use catalog to find the url
auth_url = "%s://%s:%s/v2.0" % (cfg.CONF.os_auth_protocol,
cfg.CONF.os_auth_host,
cfg.CONF.os_auth_port)
auth_url = "%s://%s:%s/v2.0" % (CONF.os_auth_protocol,
CONF.os_auth_host,
CONF.os_auth_port)
config = CONF[plugin.RESOURCE_TYPE]
#TODO(scroiset): use client wrapped by climate and use trust
self.nova = client.Client('2',
username=cfg.CONF.climate_username,
api_key=cfg.CONF.climate_password,
username=config.climate_username,
api_key=config.climate_password,
auth_url=auth_url,
project_id=cfg.CONF.climate_tenant_name)
project_id=config.climate_tenant_name)
def create_reservation(self, values):
"""Create reservation."""

View File

@ -19,6 +19,7 @@ from oslo.config import cfg
from climate import context
from climate.manager import exceptions as manager_exceptions
from climate.plugins import oshosts as plugin
class NovaInventory(object):
@ -29,11 +30,12 @@ class NovaInventory(object):
auth_url = "%s://%s:%s/v2.0" % (cfg.CONF.os_auth_protocol,
cfg.CONF.os_auth_host,
cfg.CONF.os_auth_port)
config = cfg.CONF[plugin.RESOURCE_TYPE]
self.nova = client.Client('2',
username=cfg.CONF.climate_username,
api_key=cfg.CONF.climate_password,
username=config.climate_username,
api_key=config.climate_password,
auth_url=auth_url,
project_id=cfg.CONF.climate_tenant_name)
project_id=config.climate_tenant_name)
def get_host_details(self, host):
"""Get Nova capabilities of a single host

View File

@ -23,11 +23,12 @@ from oslo.config import cfg
from climate import context
from climate.manager import exceptions as manager_exceptions
from climate.openstack.common import log as logging
from climate.plugins import oshosts as plugin
LOG = logging.getLogger(__name__)
opts = [
OPTS = [
cfg.StrOpt('aggregate_freepool_name',
default='freepool',
help='Name of the special aggregate where all hosts '
@ -43,23 +44,25 @@ opts = [
help='Prefix for Availability Zones created by Climate'),
]
cfg.CONF.register_opts(opts, 'physical:host')
CONF = cfg.CONF
CONF.register_opts(OPTS, group=plugin.RESOURCE_TYPE)
class ReservationPool(object):
def __init__(self):
self.ctx = context.current()
self.freepool_name = cfg.CONF['physical:host'].aggregate_freepool_name
self.config = CONF[plugin.RESOURCE_TYPE]
self.freepool_name = self.config.aggregate_freepool_name
#TODO(scroiset): use catalog to find the url
auth_url = "%s://%s:%s/v2.0" % (cfg.CONF.os_auth_protocol,
cfg.CONF.os_auth_host,
cfg.CONF.os_auth_port)
auth_url = "%s://%s:%s/v2.0" % (CONF.os_auth_protocol,
CONF.os_auth_host,
CONF.os_auth_port)
#TODO(scroiset): use client wrapped by climate and use trust
self.nova = client.Client('2',
username=cfg.CONF.climate_username,
api_key=cfg.CONF.climate_password,
username=self.config.climate_username,
api_key=self.config.climate_password,
auth_url=auth_url,
project_id=cfg.CONF.climate_tenant_name)
project_id=self.config.climate_tenant_name)
def get_aggregate_from_name_or_id(self, aggregate_obj):
"""Return an aggregate by name or an id."""
@ -105,7 +108,7 @@ class ReservationPool(object):
name = name or self._generate_aggregate_name()
if az:
az_name = "%s%s" % (cfg.CONF['physical:host'].climate_az_prefix,
az_name = "%s%s" % (self.config.climate_az_prefix,
name)
LOG.debug('Creating pool aggregate: %s'
'with Availability Zone %s' % (name, az_name))
@ -115,7 +118,7 @@ class ReservationPool(object):
'without Availability Zone' % name)
agg = self.nova.aggregates.create(name, None)
meta = {cfg.CONF['physical:host'].climate_owner: self.ctx.tenant_id}
meta = {self.config.climate_owner: self.ctx.tenant_id}
self.nova.aggregates.set_metadata(agg, meta)
return agg
@ -262,7 +265,7 @@ class ReservationPool(object):
def add_project(self, pool, project_id):
"""Add a project to an aggregate."""
metadata = {project_id: cfg.CONF['physical:host'].tenant_id_key}
metadata = {project_id: self.config.tenant_id_key}
agg = self.get_aggregate_from_name_or_id(pool)

View File

@ -21,6 +21,9 @@ from climate.db.sqlalchemy import models
from climate.openstack.common import uuidutils
from climate import tests
from climate.plugins import instances as vm_plugin
from climate.plugins import oshosts as host_plugin
def _get_fake_random_uuid():
return unicode(uuidutils.generate_uuid())
@ -35,14 +38,14 @@ def _get_fake_phys_reservation_values(lease_id=_get_fake_lease_uuid(),
resource_id=None):
return {'lease_id': lease_id,
'resource_id': '1234' if not resource_id else resource_id,
'resource_type': 'physical:host'}
'resource_type': host_plugin.RESOURCE_TYPE}
def _get_fake_virt_reservation_values(lease_id=_get_fake_lease_uuid(),
resource_id=None):
return {'lease_id': lease_id,
'resource_id': '5678' if not resource_id else resource_id,
'resource_type': 'virtual:instance'}
'resource_type': vm_plugin.RESOURCE_TYPE}
def _get_fake_event_values(id=_get_fake_random_uuid(),
@ -295,7 +298,7 @@ class SQLAlchemyDBApiTestCase(tests.DBTestCase):
self.assertEqual(1, len(db_api.reservation_get_all_by_values(
resource_id='5678')))
self.assertEqual(1, len(db_api.reservation_get_all_by_values(
resource_type='physical:host')))
resource_type=host_plugin.RESOURCE_TYPE)))
def test_reservation_update(self):
result = db_api.reservation_create(_get_fake_phys_reservation_values())

View File

@ -20,6 +20,7 @@ from oslo.config import cfg
from climate import config
from climate import context
from climate.manager import exceptions as manager_exceptions
from climate.plugins import oshosts as host_plugin
from climate.plugins.oshosts import reservation_pool as rp
from climate import tests
from novaclient import client as nova_client
@ -44,10 +45,11 @@ class ReservationPoolTestCase(tests.TestCase):
self.fake_aggregate = AggregateFake(i=123,
name='fooname',
hosts=['host1', 'host2'])
self.freepool_name = cfg.CONF['physical:host'].aggregate_freepool_name
self.tenant_id_key = cfg.CONF['physical:host'].tenant_id_key
self.climate_owner = cfg.CONF['physical:host'].climate_owner
self.climate_az_prefix = cfg.CONF['physical:host'].climate_az_prefix
conf = cfg.CONF[host_plugin.RESOURCE_TYPE]
self.freepool_name = conf.aggregate_freepool_name
self.tenant_id_key = conf.tenant_id_key
self.climate_owner = conf.climate_owner
self.climate_az_prefix = conf.climate_az_prefix
self.fake_freepool = AggregateFake(i=456,
name=self.freepool_name,

View File

@ -23,6 +23,7 @@ from climate.db import utils as db_utils
from climate.manager import exceptions as manager_exceptions
from climate.manager import service
from climate.openstack.common import uuidutils
from climate.plugins import oshosts as plugin
from climate.plugins.oshosts import host_plugin
from climate.plugins.oshosts import nova_inventory
from climate.plugins.oshosts import reservation_pool as rp
@ -312,13 +313,13 @@ class PhysicalHostPluginTestCase(tests.TestCase):
'resource_properties': '',
'start_date': datetime.datetime(2013, 12, 19, 20, 00),
'end_date': datetime.datetime(2013, 12, 19, 21, 00),
'resource_type': u'physical:host',
'resource_type': plugin.RESOURCE_TYPE,
}
reservation_values = {
'id': u'441c1476-9f8f-4700-9f30-cd9b6fef3509',
'lease_id': u'018c1b43-e69e-4aef-a543-09681539cf4c',
'resource_id': '1',
'resource_type': u'physical:host',
'resource_type': plugin.RESOURCE_TYPE,
'status': 'pending',
}
generate_uuid = self.patch(uuidutils, 'generate_uuid')
@ -353,13 +354,13 @@ class PhysicalHostPluginTestCase(tests.TestCase):
'resource_properties': '',
'start_date': datetime.datetime(2013, 12, 19, 20, 00),
'end_date': datetime.datetime(2013, 12, 19, 21, 00),
'resource_type': u'physical:host',
'resource_type': plugin.RESOURCE_TYPE,
}
reservation_values = {
'id': u'441c1476-9f8f-4700-9f30-cd9b6fef3509',
'lease_id': u'018c1b43-e69e-4aef-a543-09681539cf4c',
'resource_id': '1',
'resource_type': u'physical:host',
'resource_type': plugin.RESOURCE_TYPE,
'status': 'pending',
}
generate_uuid = self.patch(uuidutils, 'generate_uuid')

View File

@ -1,21 +0,0 @@
[DEFAULT]
os_auth_host=<auth_host>
os_auth_port=<auth_port>
os_auth_protocol=<http, for example>
os_admin_username=<username>
os_admin_password=<password>
os_admin_tenant_name=<tenant_name>
climate_username=<username>
climate_password=<password>
climate_tenant_name=<tenant_name>
[manager]
plugins=basic.vm.plugin,physical.host.plugin
[virtual:instance]
on_end = create_image, delete
[physical:host]
on_start = wake_up
on_end = delete

View File

@ -0,0 +1,777 @@
[DEFAULT]
#
# Options defined in oslo.messaging
#
# Use durable queues in amqp. (boolean value)
# Deprecated group/name - [DEFAULT]/rabbit_durable_queues
#amqp_durable_queues=false
# Auto-delete queues in amqp. (boolean value)
#amqp_auto_delete=false
# Size of RPC connection pool (integer value)
#rpc_conn_pool_size=30
# Modules of exceptions that are permitted to be recreatedupon
# receiving exception data from an rpc call. (list value)
#allowed_rpc_exception_modules=oslo.messaging.exceptions,nova.exception,cinder.exception,exceptions
# 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
# The qpid topology version to use. Version 1 is what was
# originally used by impl_qpid. Version 2 includes some
# backwards-incompatible changes that allow broker federation
# to work. Users should update to version 2 when they are
# able to take everything down, as it requires a clean break.
# (integer value)
#qpid_topology_version=1
# SSL version to use (valid only if SSL enabled). valid values
# are TLSv1, SSLv23 and SSLv3. SSLv2 may be available on some
# distributions (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 number of RabbitMQ connection retries. Default is 0
# (infinite retry count) (integer value)
#rabbit_max_retries=0
# Use HA queues in RabbitMQ (x-ha-policy: all). If you change
# this option, you must wipe the RabbitMQ database. (boolean
# value)
#rabbit_ha_queues=false
# If passed, use a fake RabbitMQ provider (boolean value)
#fake_rabbit=false
# 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=oslo.messaging._drivers.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=climate
# Seconds to wait before a cast expires (TTL). Only supported
# by impl_zmq. (integer value)
#rpc_cast_timeout=30
# Heartbeat frequency (integer value)
#matchmaker_heartbeat_freq=300
# Heartbeat time-to-live. (integer value)
#matchmaker_heartbeat_ttl=600
# 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>
# Size of RPC greenthread pool (integer value)
#rpc_thread_pool_size=64
# Driver or drivers to handle sending notifications (multi
# valued)
#notification_driver=
# AMQP topic used for OpenStack notifications (list value)
# Deprecated group/name - [rpc_notifier2]/topics
#notification_topics=notifications
# Seconds to wait for a response from a call (integer value)
#rpc_response_timeout=60
# A URL representing the messaging driver to use and its full
# configuration. If not set, we fall back to the rpc_backend
# option and driver specific configuration. (string value)
#transport_url=<None>
# The messaging driver to use, defaults to rabbit. Other
# drivers include qpid and zmq. (string value)
#rpc_backend=rabbit
# The default exchange under which topics are scoped. May be
# overridden by an exchange name specified in the
# transport_url option. (string value)
#control_exchange=openstack
#
# Options defined in climate.config
#
# Log request/response exchange details: environ, headers and
# bodies (boolean value)
#log_exchange=false
# 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
# ZeroMQ, a valid hostname, FQDN, or IP address (string value)
#host=climate
# Protocol used to access OpenStack Identity service (string
# value)
#os_auth_protocol=http
# IP or hostname of machine on which OpenStack Identity
# service is located (string value)
#os_auth_host=127.0.0.1
# Port of OpenStack Identity service (string value)
#os_auth_port=35357
# This OpenStack user is used to verify provided tokens. The
# user must have admin role in <os_admin_tenant_name> tenant
# (string value)
#os_admin_username=admin
# Password of the admin user (string value)
#os_admin_password=climate
# Name of tenant where the user is admin (string value)
#os_admin_tenant_name=admin
# We use API v3 to allow trusts using. (string value)
#os_auth_version=v2.0
#
# Options defined in climate.cmd.api
#
# Port that will be used to listen on (integer value)
#port=1234
#
# Options defined in climate.db.base
#
# Driver to use for database access (string value)
#db_driver=climate.db
#
# Options defined in climate.openstack.common.db.sqlalchemy.session
#
# the filename to use with sqlite (string value)
#sqlite_db=climate.sqlite
# If true, use synchronous mode for sqlite (boolean value)
#sqlite_synchronous=true
#
# Options defined in climate.openstack.common.deprecated.wsgi
#
# Number of backlog requests to configure the socket with
# (integer value)
#backlog=4096
# Sets the value of TCP_KEEPIDLE in seconds for each server
# socket. Not supported on OS X. (integer value)
#tcp_keepidle=600
#
# Options defined in climate.openstack.common.eventlet_backdoor
#
# Enable eventlet backdoor. Acceptable values are 0, <port>,
# and <start>:<end>, where 0 results in listening on a random
# tcp port number; <port> results in listening on the
# specified port number (and not enabling backdoor if that
# port is in use); and <start>:<end> results in listening on
# the smallest unused port number within the specified range
# of port numbers. The chosen port is displayed in the
# service's log file. (string value)
#backdoor_port=<None>
#
# Options defined in climate.openstack.common.lockutils
#
# Whether to disable inter-process locks (boolean value)
#disable_process_locking=false
# Directory to use for lock files. (string value)
#lock_path=<None>
#
# Options defined in climate.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>
# DEPRECATED. 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)
# Deprecated group/name - [DEFAULT]/logfile
#log_file=<None>
# (Optional) The base directory used for relative --log-file
# paths (string value)
# Deprecated group/name - [DEFAULT]/logdir
#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 climate.openstack.common.middleware.sizelimit
#
# the maximum body size per each request(bytes) (integer
# value)
# Deprecated group/name - [DEFAULT]/osapi_max_request_body_size
#max_request_body_size=114688
#
# Options defined in climate.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 climate.openstack.common.policy
#
# JSON file containing policy (string value)
#policy_file=policy.json
# Rule enforced when requested rule is not found (string
# value)
#policy_default_rule=default
#
# Options defined in climate.utils.openstack.keystone
#
# Keystoneclient version (string value)
#keystone_client_version=3
# Identity service to use. (string value)
#identity_service=identityv3
#
# Options defined in climate.utils.openstack.nova
#
# Novaclient version (string value)
#nova_client_version=2
# Nova name in keystone (string value)
#compute_service=compute
# Prefix for VM images if you want to create snapshots (string
# value)
#image_prefix=reserved_
[database]
#
# Options defined in climate.openstack.common.db.api
#
# The backend to use for db (string value)
# Deprecated group/name - [DEFAULT]/db_backend
#backend=sqlalchemy
# Enable the experimental use of thread pooling for all DB API
# calls (boolean value)
# Deprecated group/name - [DEFAULT]/dbapi_use_tpool
#use_tpool=false
#
# Options defined in climate.openstack.common.db.sqlalchemy.session
#
# The SQLAlchemy connection string used to connect to the
# database (string value)
# Deprecated group/name - [DEFAULT]/sql_connection
# Deprecated group/name - [DATABASE]/sql_connection
# Deprecated group/name - [sql]/connection
#connection=sqlite:////climate/openstack/common/db/$sqlite_db
# The SQLAlchemy connection string used to connect to the
# slave database (string value)
#slave_connection=
# timeout before idle sql connections are reaped (integer
# value)
# Deprecated group/name - [DEFAULT]/sql_idle_timeout
# Deprecated group/name - [DATABASE]/sql_idle_timeout
#idle_timeout=3600
# Minimum number of SQL connections to keep open in a pool
# (integer value)
# Deprecated group/name - [DEFAULT]/sql_min_pool_size
# Deprecated group/name - [DATABASE]/sql_min_pool_size
#min_pool_size=1
# Maximum number of SQL connections to keep open in a pool
# (integer value)
# Deprecated group/name - [DEFAULT]/sql_max_pool_size
# Deprecated group/name - [DATABASE]/sql_max_pool_size
#max_pool_size=<None>
# maximum db connection retries during startup. (setting -1
# implies an infinite retry count) (integer value)
# Deprecated group/name - [DEFAULT]/sql_max_retries
# Deprecated group/name - [DATABASE]/sql_max_retries
#max_retries=10
# interval between retries of opening a sql connection
# (integer value)
# Deprecated group/name - [DEFAULT]/sql_retry_interval
# Deprecated group/name - [DATABASE]/reconnect_interval
#retry_interval=10
# If set, use this value for max_overflow with sqlalchemy
# (integer value)
# Deprecated group/name - [DEFAULT]/sql_max_overflow
# Deprecated group/name - [DATABASE]/sqlalchemy_max_overflow
#max_overflow=<None>
# Verbosity of SQL debugging information. 0=None,
# 100=Everything (integer value)
# Deprecated group/name - [DEFAULT]/sql_connection_debug
#connection_debug=0
# Add python stack traces to SQL as comment strings (boolean
# value)
# Deprecated group/name - [DEFAULT]/sql_connection_trace
#connection_trace=false
# If set, use this value for pool_timeout with sqlalchemy
# (integer value)
# Deprecated group/name - [DATABASE]/sqlalchemy_pool_timeout
#pool_timeout=<None>
[keystone_authtoken]
#
# Options defined in keystoneclient.middleware.auth_token
#
# Prefix to prepend at the beginning of the path (string
# value)
#auth_admin_prefix=
# Host providing the admin Identity API endpoint (string
# value)
#auth_host=127.0.0.1
# Port of the admin Identity API endpoint (integer value)
#auth_port=35357
# Protocol of the admin Identity API endpoint(http or https)
# (string value)
#auth_protocol=https
# Complete public Identity API endpoint (string value)
#auth_uri=<None>
# API version of the admin Identity API endpoint (string
# value)
#auth_version=<None>
# Do not handle authorization requests within the middleware,
# but delegate the authorization decision to downstream WSGI
# components (boolean value)
#delay_auth_decision=false
# Request timeout value for communicating with Identity API
# server. (boolean value)
#http_connect_timeout=<None>
# How many times are we trying to reconnect when communicating
# with Identity API Server. (integer value)
#http_request_max_retries=3
# Allows to pass in the name of a fake http_handler callback
# function used instead of httplib.HTTPConnection or
# httplib.HTTPSConnection. Useful for unit testing where
# network is not available. (string value)
#http_handler=<None>
# Single shared secret with the Keystone configuration used
# for bootstrapping a Keystone installation, or otherwise
# bypassing the normal authentication process. (string value)
#admin_token=<None>
# Keystone account username (string value)
#admin_user=<None>
# Keystone account password (string value)
#admin_password=<None>
# Keystone service account tenant name to validate user tokens
# (string value)
#admin_tenant_name=admin
# Env key for the swift cache (string value)
#cache=<None>
# Required if Keystone server requires client certificate
# (string value)
#certfile=<None>
# Required if Keystone server requires client certificate
# (string value)
#keyfile=<None>
# A PEM encoded Certificate Authority to use when verifying
# HTTPs connections. Defaults to system CAs. (string value)
#cafile=<None>
# Verify HTTPS connections. (boolean value)
#insecure=false
# Directory used to cache files related to PKI tokens (string
# value)
#signing_dir=<None>
# If defined, the memcache server(s) to use for caching (list
# value)
# Deprecated group/name - [DEFAULT]/memcache_servers
#memcached_servers=<None>
# In order to prevent excessive requests and validations, the
# middleware uses an in-memory cache for the tokens the
# Keystone API returns. This is only valid if memcache_servers
# is defined. Set to -1 to disable caching completely.
# (integer value)
#token_cache_time=300
# Value only used for unit testing (integer value)
#revocation_cache_time=1
# (optional) if defined, indicate whether token data should be
# authenticated or authenticated and encrypted. Acceptable
# values are MAC or ENCRYPT. If MAC, token data is
# authenticated (with HMAC) in the cache. If ENCRYPT, token
# data is encrypted and authenticated in the cache. If the
# value is not one of these options or empty, auth_token will
# raise an exception on initialization. (string value)
#memcache_security_strategy=<None>
# (optional, mandatory if memcache_security_strategy is
# defined) this string is used for key derivation. (string
# value)
#memcache_secret_key=<None>
# (optional) indicate whether to set the X-Service-Catalog
# header. If False, middleware will not ask for service
# catalog on token validation and will not set the X-Service-
# Catalog header. (boolean value)
#include_service_catalog=true
# Used to control the use and type of token binding. Can be
# set to: "disabled" to not check token binding. "permissive"
# (default) to validate binding information if the bind type
# is of a form known to the server and ignore it if not.
# "strict" like "permissive" but if the bind type is unknown
# the token will be rejected. "required" any form of token
# binding is needed to be allowed. Finally the name of a
# binding method that must be present in tokens. (string
# value)
#enforce_token_bind=permissive
[manager]
#
# Options defined in climate.manager
#
# The topic Climate uses for climate-manager messages. (string
# value)
#rpc_topic=climate.manager
#
# Options defined in climate.manager.service
#
# All plugins to use (one for every resource type to support.)
# (list value)
#plugins=dummy.vm.plugin
[matchmaker_ring]
#
# Options defined in oslo.messaging
#
# Matchmaker ring file (JSON) (string value)
# Deprecated group/name - [DEFAULT]/matchmaker_ringfile
#ringfile=/etc/oslo/matchmaker_ring.json
[physical:host]
#
# Options defined in climate.plugins.oshosts
#
# Name of the user for write operations (string value)
#climate_username=climate_admin
# Password of the user for write operations (string value)
#climate_password=climate_password
# Tenant of the user for write operations (string value)
#climate_tenant_name=admin
#
# Options defined in climate.plugins.oshosts.host_plugin
#
# Actions which we will use in the end of the lease (string
# value)
#on_end=on_end
#
# Options defined in climate.plugins.oshosts.reservation_pool
#
# Name of the special aggregate where all hosts are candidate
# for physical host reservation (string value)
#aggregate_freepool_name=freepool
# Aggregate metadata value for key matching tenant_id (string
# value)
#tenant_id_key=climate:tenant
# Aggregate metadata key for knowing owner tenant_id (string
# value)
#climate_owner=climate:owner
# Prefix for Availability Zones created by Climate (string
# value)
#climate_az_prefix=climate:
[ssl]
#
# Options defined in climate.openstack.common.sslutils
#
# CA certificate file to use to verify connecting clients
# (string value)
#ca_file=<None>
# Certificate file to use when starting the server securely
# (string value)
#cert_file=<None>
# Private key file to use when starting the server securely
# (string value)
#key_file=<None>
[virtual:instance]
#
# Options defined in climate.plugins.instances.vm_plugin
#
# Actions which we will use in the end of the lease (string
# value)
#on_end=create_image, delete
# Actions which we will use at the start of the lease (string
# value)
#on_start=on_start
#
# Options defined in climate.plugins.oshosts.host_plugin
#
# Actions which we will use at the start of the lease (string
# value)
#on_start=on_start

View File

@ -1,6 +1,7 @@
[DEFAULT]
# The list of modules to copy from oslo-incubator
module=config
module=db
module=db.sqlalchemy
module=eventlet_backdoor

25
tools/config/check_uptodate.sh Executable file
View File

@ -0,0 +1,25 @@
#!/usr/bin/env bash
PROJECT_NAME=${PROJECT_NAME:-climate}
CFGFILE_NAME=${PROJECT_NAME}.conf.sample
if [ -e etc/${PROJECT_NAME}/${CFGFILE_NAME} ]; then
CFGFILE=etc/${PROJECT_NAME}/${CFGFILE_NAME}
elif [ -e etc/${CFGFILE_NAME} ]; then
CFGFILE=etc/${CFGFILE_NAME}
else
echo "${0##*/}: can not find config file"
exit 1
fi
TEMPDIR=`mktemp -d /tmp/${PROJECT_NAME}.XXXXXX`
trap "rm -rf $TEMPDIR" EXIT
tools/config/generate_sample.sh -b ./ -p ${PROJECT_NAME} -o ${TEMPDIR}
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."
exit 1
fi

119
tools/config/generate_sample.sh Executable file
View File

@ -0,0 +1,119 @@
#!/usr/bin/env bash
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:-${BASEDIR##*/}}
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/*" \
-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 mod in ${CLIMATE_CONFIG_GENERATOR_EXTRA_MODULES}; do
MODULES="$MODULES -m $mod"
done
for lib in ${CLIMATE_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_MODULEPATH=climate.openstack.common.config.generator
MODULEPATH=${MODULEPATH:-$DEFAULT_MODULEPATH}
OUTPUTFILE=$OUTPUTDIR/$PACKAGENAME.conf.sample
python -m $MODULEPATH $MODULES $LIBRARIES $FILES > $OUTPUTFILE
# 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

View File

@ -0,0 +1,2 @@
export CLIMATE_CONFIG_GENERATOR_EXTRA_MODULES="keystoneclient.middleware.auth_token"
export CLIMATE_CONFIG_GENERATOR_EXTRA_LIBRARIES="oslo.messaging"

View File

@ -12,6 +12,7 @@ setenv = VIRTUAL_ENV={envdir}
DISCOVER_DIRECTORY=climate/tests
commands =
python -m climate.openstack.common.lockutils python setup.py testr --slowest --testr-args="{posargs}"
sitepackages = False
[testenv:cover]
@ -21,6 +22,7 @@ commands =
[testenv:pep8]
commands = flake8 {posargs}
{toxinidir}/tools/config/check_uptodate.sh
[testenv:venv]
commands = {posargs}