Hey, The original cfg design[1] assumed certain usage patterns that I hoped would be adopted by all projects using it. In gerrit, we're debating a set of patch to make keystone use these patterns:
https://review.openstack.org/4547 I thought it was best to move some of that discussion here since I'm hoping we can get some rough consensus across projects. I really think it will be beneficial if we can share common idioms and patterns across projects, rather than just using the same library in different ways. So, what I'm proposing is: 1) We stop using global objects like FLAGS or CONF and instead pass the config object to code which uses it. On an evilness scale, these global objects may be relatively benign but they do still harm modularity. Any module that depends on these global objects cannot be re-used without ensuring the global object exists in the user e.g. doing this: from nova import flags FLAGS = flags.FLAGS is a nice way of ensuring code is nova specific, even if there is no other reason for it to be tied to nova. Also, these global objects force us to do a bunch of hacks in unit tests. We need to do tricks to ensure the object is initialized as we want. We also need to save and restore its state between runs. Subtle mistakes here might mean that a test passes when all the tests are run together, but fail when run individually[2]. If you want to get a feel for the end result when avoiding using global objects, look at glance or my cfg-cleanup[3] keystone branch. 2) We define options close to where they are used. Again, this is for modularity. If some code relies on code in another module registering an option, it creates a dependence between the modules. In practice, this resulted in two patterns. Firstly, where a set of options are used exclusively within a class: class ExtensionManager(object): enabled_apis_opt = cfg.ListOpt('enabled_apis', default=['ec2', 'osapi'], help='List of APIs to enable by default') def __init__(self, conf): self.conf = conf self.conf.register_opt(enabled_apis_opt) ... def _load_extensions(self): for ext_factory in self.conf.osapi_extension: .... Here you have the options used by the class clearly declared at the beginning of the class definition and a config instance passed to the constructor. Secondly, where options are used by functions within a module: crypt_strength_opt = cfg.IntOpt('crypt_strength', default=40000) def hash_password(conf, password): """Hash a password. Hard.""" password_utf8 = password.encode('utf-8') if passlib.hash.sha512_crypt.identify(password_utf8): return password_utf8 conf.register_opt(crypt_strength_opt) h = passlib.hash.sha512_crypt.encrypt(password_utf8, rounds=conf.crypt_strength) return h Here you have the options declared at the beginning of the module and a config instance passed to functions which use options. The option schema is registered by such a function just before the option is used. One complaint about the above pattern is that it's more verbose than simply doing e.g. CONF = config.CONF config.register_opt('crypt_strength', default=40000) def hash_password(password): ... h = passlib.hash.sha512_crypt.encrypt(password_utf8, rounds=CONF.crypt_strength) return h The obvious way of making it less verbose while still removing the dependence on global objects would be: def hash_password(conf, password): ... conf.register_opt('crypt_strength', default=40000) h = passlib.hash.sha512_crypt.encrypt(password_utf8, rounds=conf.crypt_strength) return h However, that does have the effect of obscuring the option definitions somewhat. Any thoughts or ideas on all of this? Thanks, Mark. [1] - http://wiki.openstack.org/CommonConfigModule [2] - https://github.com/openstack/nova/commit/4eeb0b96fc [3] - https://github.com/markmc/keystone/tree/cfg-cleanup _______________________________________________ Mailing list: https://launchpad.net/~openstack Post to : openstack@lists.launchpad.net Unsubscribe : https://launchpad.net/~openstack More help : https://help.launchpad.net/ListHelp