I reworked my OpenStack API based cluster builder Ossipee last weekend. It makes heavy use of dependency resolution now, and breaks apart the super-base class into properly scoped components.
work.py is the worker classes. These are designed to be reusable components.
plan.py is a merger of the config and plan objects from before. Killed the majority of the copying. It is the least cleaned up of any of the code. I might continue to rework this.
ossipee.py has the factories which determine how to build the components. Python’s lack of type support is really apparent here, leading to boilerplate code.
I particularly like how the Session and client factories now work.
def session_factory(resolver): parser = resolver.resolve(argparse.ArgumentParser) args = parser.parse_args() auth_plugin = ksc_auth.load_from_argparse_arguments(args) try: if not auth_plugin.auth_url: logging.error('OS_AUTH_URL not set. Aborting.') sys.exit(-1) except AttributeError: pass session = ksc_session.Session.load_from_cli_options( args, auth=auth_plugin) return session
def nova_client_factory(resolver): session = resolver.resolve(ksc_session.Session) nova_client = novaclient.Client('2', session=session) return nova_client
They are registered like this:
depend.register(ksc_session.Session, session_factory) depend.register(novaclient.Client, nova_client_factory)
So, the worker object to create a host declares its dependencies in the constructor.
class Server(object): def __init__(self, nova, neutron, spec): self.name = spec.name self.nova = nova self.neutron = neutron self.spec = spec
Ideally, the parameters to the __init__ function would have documentation about types. While that can make use of ABC, it does not help for all the code out there that does not use ABC. ABC would be useful for providing a means to automate dependency resolution.
I pulled the resolver code I wrote a few years into the tree for now for ease of development. I’ll probably merge it back to the original project. The biggest addition is the ability to name components, to be able to distinguish between two components that implement the same contract. Without this, I had subclass proliferation. Python tuples really make sense here: A Factory is registered via the tuple of the class and the (optional) name, and is resolved the same way.
Mixing named and unnamed components is still a little grungy, but it makes it nice to have a component that can both be a top level worker, and a piece of another workflow.
An instance is resolved via the scope, the class, and the name. We can cheat, and pass in a string as the Class for the name of the “worker”, but I don’t think I want to encourage that.
I only have a single scope for Ossipee, the global scope, as it was fairly short lived. I’d like to try the depend.py code in a web app with both request and session scope to see how well it works to organize things.