From 8ee7798af73f374485c1a97e82a98fd5ff8b3c48 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Mon, 12 Nov 2012 23:33:01 +0000 Subject: [PATCH] Add module for loading specific classes This adds nova/loadables.py which contains code that is to be shared by host and cell scheduling filtering and weighing. Most of this code originated from nova/scheduler/filters/__init__.py (which was copied from nova/api extension loading). This makes it more generic so that it can be shared. Note that this functionality is quite different than the generic plugin manager that exists in openstack-common. That code could not be used here without some significant changes. It also seems pretty overkill for the functionality required by scheduling filtering and weighing. Change-Id: I1b217dc2bc2d1dc2235c8251318d06b46597e8f4 --- nova/loadables.py | 116 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 nova/loadables.py diff --git a/nova/loadables.py b/nova/loadables.py new file mode 100644 index 000000000..0c930267e --- /dev/null +++ b/nova/loadables.py @@ -0,0 +1,116 @@ +# Copyright (c) 2011-2012 OpenStack, LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Generic Loadable class support. + +Meant to be used by such things as scheduler filters and weights where we +want to load modules from certain directories and find certain types of +classes within those modules. Note that this is quite different than +generic plugins and the pluginmanager code that exists elsewhere. + +Usage: + +Create a directory with an __init__.py with code such as: + +class SomeLoadableClass(object): + pass + + +class MyLoader(nova.loadables.BaseLoader) + def __init__(self): + super(MyLoader, self).__init__(SomeLoadableClass) + +If you create modules in the same directory and subclass SomeLoadableClass +within them, MyLoader().get_all_classes() will return a list +of such classes. +""" + +import inspect +import os +import sys + +from nova import exception +from nova.openstack.common import importutils + + +class BaseLoader(object): + def __init__(self, loadable_cls_type): + mod = sys.modules[self.__class__.__module__] + self.path = mod.__path__[0] + self.package = mod.__package__ + self.loadable_cls_type = loadable_cls_type + + def _is_correct_class(self, obj): + """Return whether an object is a class of the correct type and + is not prefixed with an underscore. + """ + return (inspect.isclass(obj) and + (not obj.__name__.startswith('_')) and + issubclass(obj, self.loadable_cls_type)) + + def _get_classes_from_module(self, module_name): + """Get the classes from a module that match the type we want.""" + classes = [] + module = importutils.import_module(module_name) + for obj_name in dir(module): + # Skip objects that are meant to be private. + if obj_name.startswith('_'): + continue + itm = getattr(module, obj_name) + if self._is_correct_class(itm): + classes.append(itm) + return classes + + def get_all_classes(self): + """Get the classes of the type we want from all modules found + in the directory that defines this class. + """ + classes = [] + for dirpath, dirnames, filenames in os.walk(self.path): + relpath = os.path.relpath(dirpath, self.path) + if relpath == '.': + relpkg = '' + else: + relpkg = '.%s' % '.'.join(relpath.split(os.sep)) + for fname in filenames: + root, ext = os.path.splitext(fname) + if ext != '.py' or root == '__init__': + continue + module_name = "%s%s.%s" % (self.package, relpkg, root) + mod_classes = self._get_classes_from_module(module_name) + classes.extend(mod_classes) + return classes + + def get_matching_classes(self, loadable_class_names): + """Get loadable classes from a list of names. Each name can be + a full module path or the full path to a method that returns + classes to use. The latter behavior is useful to specify a method + that returns a list of classes to use in a default case. + """ + classes = [] + for cls_name in loadable_class_names: + obj = importutils.import_class(cls_name) + if self._is_correct_class(obj): + classes.append(obj) + elif inspect.isfunction(obj): + # Get list of classes from a function + for cls in obj(): + classes.append(cls) + else: + error_str = 'Not a class of the correct type' + raise exception.ClassNotFound(class_name=cls_name, + exception=error_str) + return classes