This is a crazy rought draft. More like a semi-organized brainstorm.
To provide a stable API for building drivers. This gives third-party driver writers time (a full cycle) to update their drivers to fulfill new API expectations.
In Keystone we deliver our drivers in the same package as the managers. This makes it much easier for us to deal with changing APIs than for third-party developers.
It’s important to talk about a few layers of the system and how they are affected by supporting multiple versions.
Controllers implement the web stuff. They take data from the web and convert it into manager calls. The data from the manager is then converted into web stuff.
Managers implement much of the business logic behind Keystone’s functionality. They use drivers to abstract away the data access patterns of different data storage systems (DBs, LDAP, etc).
Drivers access data. Unfortunately they also have some business logic due to issues with a particular data storage system, legacy cruft and some other reasons. Drivers are versioned using a version specific ABCMeta-based parent.
The versioning should happen at the manager layer. This can either be done by changing the manager to understand all supported versions or to provide an adapter class. I would prefer that the manager only supports the latest driver version and we automatically wrap older drivers in an adapter. This allows the driver layer to be completely unaware that the manager is written to a different version.
Example adapter:
class V8Adapter(SomeDriverV9): """Adapt the V8 version of SomeDriver to V9."""
def __init__(self, wrapped_driver): self.driver = wrapped_driver
def __getattr__(self, name): f = getattr(self.driver, name) setattr(self, name, f) # cache for later return f
def method(self, ...): """Override method's behavior.""" # do something to inputs? rv = self.driver.method(...) # do something to outputs? return rv
Example manger code:
class Manager(manager.Manager):
def __init__(self, driver_name): super(Manager, self).__init__(driver_name) if isinstance(self.driver, V8Interface): self.driver = V8Adapter(self.driver) elif not isinstance(self.driver, V9Interface): raise UnsupportedDriverVersion()
Keystone provides serveral drivers for its subsystems. Do we need to worry about versioning those? There are two options.
Old drivers get rewritten to support the latest version. We only maintain enough of an old driver implementation to test that the adapter actually works. This is pretty much what we have always done with the addition of the adapter tests.
Keep the N-1 versioned driver along with the new N version. We could optionally add entry points for the N-1 driver to make it easy for deployers to use.
This option brings up the question on what to do for the SQL models for SQL drivers. Do we keep then current and change the old driver to match the newer model? This would mean that certain upgrades would be difficult or maybe impossible to support with multiple versions.
Do we maintain multiple versions of each model? Then we would need a way to only upgrade certain models bases on what driver is configured. This would likely apply to other migrations as well. Another limitation is that the deployer can’t test newer driver versions without upgrading the database and breaking older drivers.
This has so many dragons that I just prefer not suporting the N-1 driver for builtin drivers.
nit: 'class Manger(manager.Manager):' should be 'class Manager'