← Back to team overview

openstack team mailing list archive

Re: cfg usage - option registration, global objects

 

How about decorators? For example:

@cfg.used_here(
    cfg.IntOpt('one_opt', default=1),
    cfg.StrOpt('other_opt', default="www"))
def func([self, ]conf, arg1, arg2):
    do_smth_with(conf.one_opt, conf.other_opt)

Of course, this used_here should be able to receive a list of opts and
to be applied to a class (so that it wraps its __init__ or all
static/class methods). Before wrapping it should look for conf
argument and call register_opt on it while still passing it into the
function.
For testing purposes, we'll be able to make it unregister listed
options so that we prevent some strange behavior like fun2 works if
fun1 was called sometime, but doesn't work if its conf is not
preprocessed (options are not registered).

Kind regards, Yuriy.



On Tue, Mar 6, 2012 at 14:10, Mark McLoughlin <markmc@xxxxxxxxxx> wrote:
> 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@xxxxxxxxxxxxxxxxxxx
> Unsubscribe : https://launchpad.net/~openstack
> More help   : https://help.launchpad.net/ListHelp


References