[openstack-dev] [openstack][nova] Streamlining of config options in nova

Doug Hellmann doug at doughellmann.com
Fri Jul 24 17:25:27 UTC 2015

Excerpts from Joshua Harlow's message of 2015-07-24 10:09:10 -0700:
> Doug Hellmann wrote:
> > Excerpts from Joshua Harlow's message of 2015-07-24 09:07:13 -0700:
> >> Daniel P. Berrange wrote:
> >>> On Thu, Jul 23, 2015 at 05:55:36PM +0300, mhorban wrote:
> >>>> Hi all,
> >>>>
> >>>> During development process in nova I faced with an issue related with config
> >>>> options. Now we have lists of config options and registering options mixed
> >>>> with source code in regular files.
> >>>>   From one side it can be convenient: to have module-encapsulated config
> >>>> options. But problems appear when we need to use some config option in
> >>>> different modules/packages.
> >>>>
> >>>> If some option is registered in module X and module X imports module Y for
> >>>> some reasons...
> >>>> and in one day we need to import this option in module Y we will get
> >>>> exception
> >>>> NoSuchOptError on import_opt in module Y.
> >>>> Because of circular dependency.
> >>>> To resolve it we can move registering of this option in Y module(in the
> >>>> inappropriate place) or use other tricks.
> >>>>
> >>>> I offer to create file options.py in each package and move all package's
> >>>> config options and registration code there.
> >>>> Such approach allows us to import any option in any place of nova without
> >>>> problems.
> >>>>
> >>>> Implementations of this refactoring can be done piece by piece where piece
> >>>> is
> >>>> one package.
> >>>>
> >>>> What is your opinion about this idea?
> >>> I tend to think that focusing on problems with dependancy ordering when
> >>> modules import each others config options is merely attacking a symptom
> >>> of the real root cause problem.
> >> Amen to that :)
> >>
> >>> The way we use config options is really entirely wrong. We have gone
> >>> to the trouble of creating (or trying to create) structured code with
> >>> isolated functional areas, files and object classes, and then we throw
> >>> in these config options which are essentially global variables which are
> >>> allowed to be accessed by any code anywhere. This destroys the isolation
> >>> of the various classes we've created, and means their behaviour often
> >>> based on side effects of config options from unrelated pieces of code.
> >>> It is total madness in terms of good design practices to have such use
> >>> of global variables.
> >>>
> >>> So IMHO, if we want to fix the real big problem with config options, we
> >>> need to be looking to solution where we stop using config options as
> >>> global variables. We should change our various classes so that the
> >>> neccessary configurable options as passed into object constructors
> >>> and/or methods as parameters.
> >>>
> >>> As an example in the libvirt driver.
> >>>
> >>> I would set it up so that /only/ the LibvirtDriver class in driver.py
> >>> was allowed to access the CONF config options. In its constructor it
> >>> would load all the various config options it needs, and either set
> >>> class attributes for them, or pass them into other methods it calls.
> >>> So in the driver.py, instead of calling CONF.libvirt.libvirt_migration_uri
> >>> everywhere in the code,  in the constructor we'd save that config param
> >>> value to an attribute 'self.mig_uri = CONF.libvirt.libvirt_migration_uri'
> >>> and then where needed, we'd just call "self.mig_uri".
> >>>
> >>> Now in the various other libvirt files, imagebackend.py, volume.py
> >>> vif.py, etc. None of those files would /ever/ access CONF.*. Any time
> >>> they needed a config parameter, it would be passed into their constructor
> >>> or method, by the LibvirtDriver or whatever invoked them.
> >> +1 and IMHO if some driver needs some 'funky' new parameter that isn't
> >> getting passed previously, probably the driver may be built wrong, or
> >> it's not conforming to the API that is provided (and therefore either
> >> the API needs adjustments or the driver needs to be fixed); all the
> >> above IMHO is pretty standard OOP practices and I'd like to see more of
> >> it honestly :)
> >
> > While it's good to share common options as much as possible, there
> > are a lot of perfectly legitimate reasons for drivers to need
> > different configuration options. Different backends might use different
> > authentication mechanisms, or need different ways to specify "where" the
> > thing the driver is managing actually lives. The job of the driver API
> > layer is to hide those differences so the application doesn't have to
> > care about them. Pushing all of the options up to be API parameters
> > defeats the purpose of that if not all drivers use all of the options.
> >
> I get what u are saying, but at that point it does make me wonder what 
> the API that is being provided really is, if the API is trying to hide 
> those differences, and all drivers are different (and/or require 
> different options via config) then I'm not really sure u can call that a 
> common/shared API, especially if the sole option the API takes is a 
> config object.
> I reckon this to usage of void* pointer in c/c++ (where the void* is 
> nearly equivalent to the config object we pass around), having an API 
> that takes a single void* pointer and then lets other functions do 
> things/anything with it isn't exactly a API (by my definition).
> But if the API expects some standard arguments, and then as a final 
> parameter takes a extra void* pointer, then this starts to become more 
> acceptable IMHO (the extra void* pointer could be considered equivalent 
> to **kwargs in python and the implementations of that can use it to 
> receive other options that they use to specialize themselves). It's a 
> balance between just sending one void* pointer in (not really an API by 
> my definition) between expecting say X common API parameters and then a 
> single void* for extra options that are API derivative specific.

Right, I don't think anyone is suggesting that we stuff everything
into the config object. In general, parameters that are defined by
the application (message topic or queue name) should be passed
explicitly.  Any parameters the driver might need to allow deployer
tuning but for which the application shouldn't care (connection
parameters for a message broker) can come from the configuration object.

> I've found the following to be an interesting read on a pluggable system 
> that most people have used that I think does a pretty good job of this 
> (and doesn't just default to passing one void* pointer around): 
> http://fabiensanglard.net/doom3/index.php (most components of doom3 are 
> pluggable).
> > Doug
> >
> >>> Getting rid of the global CONF object usage in all these files trivially
> >>> now solves the circular dependancy import problem, as well as improving
> >>> the overall structure and isolation of our code, freeing all these methods
> >>> from unexpected side-effects from global variables.
> >>>
> >>> Regards,
> >>> Daniel
> >
> > __________________________________________________________________________
> > OpenStack Development Mailing List (not for usage questions)
> > Unsubscribe: OpenStack-dev-request at lists.openstack.org?subject:unsubscribe
> > http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev

More information about the OpenStack-dev mailing list