Import nova filter scheduler to zun
Import nova filter scheduler to zun. Add unittest in next patch Change-Id: Ibf6e80bd9714a584d625852aea1afca57086aa5b Partially-Implements: blueprint import-nova-filter-scheduler
This commit is contained in:
parent
92387ce610
commit
dd63b06a4c
@ -63,6 +63,7 @@ zun.database.migration_backend =
|
|||||||
zun.scheduler.driver =
|
zun.scheduler.driver =
|
||||||
chance_scheduler = zun.scheduler.chance_scheduler:ChanceScheduler
|
chance_scheduler = zun.scheduler.chance_scheduler:ChanceScheduler
|
||||||
fake_scheduler = zun.tests.unit.scheduler.fake_scheduler:FakeScheduler
|
fake_scheduler = zun.tests.unit.scheduler.fake_scheduler:FakeScheduler
|
||||||
|
filter_scheduler = zun.scheduler.filter_scheduler:FilterScheduler
|
||||||
|
|
||||||
zun.image.driver =
|
zun.image.driver =
|
||||||
glance = zun.image.glance.driver:GlanceDriver
|
glance = zun.image.glance.driver:GlanceDriver
|
||||||
|
@ -454,3 +454,16 @@ class CPUPinningInvalid(Invalid):
|
|||||||
class CPUUnpinningInvalid(Invalid):
|
class CPUUnpinningInvalid(Invalid):
|
||||||
msg_fmt = _("CPU set to unpin %(requested)s must be a subset of "
|
msg_fmt = _("CPU set to unpin %(requested)s must be a subset of "
|
||||||
"pinned CPU set %(pinned)s")
|
"pinned CPU set %(pinned)s")
|
||||||
|
|
||||||
|
|
||||||
|
class NotFound(ZunException):
|
||||||
|
msg_fmt = _("Resource could not be found.")
|
||||||
|
code = 404
|
||||||
|
|
||||||
|
|
||||||
|
class SchedulerHostFilterNotFound(NotFound):
|
||||||
|
msg_fmt = _("Scheduler Host Filter %(filter_name)s could not be found.")
|
||||||
|
|
||||||
|
|
||||||
|
class ClassNotFound(NotFound):
|
||||||
|
msg_fmt = _("Class %(class_name)s could not be found: %(exception)s")
|
||||||
|
@ -22,7 +22,8 @@ scheduler_group = cfg.OptGroup(name="scheduler",
|
|||||||
scheduler_opts = [
|
scheduler_opts = [
|
||||||
cfg.StrOpt("driver",
|
cfg.StrOpt("driver",
|
||||||
default="chance_scheduler",
|
default="chance_scheduler",
|
||||||
choices=("chance_scheduler", "fake_scheduler"),
|
choices=("chance_scheduler", "fake_scheduler",
|
||||||
|
"filter_scheduler"),
|
||||||
help="""
|
help="""
|
||||||
The class of the driver used by the scheduler.
|
The class of the driver used by the scheduler.
|
||||||
|
|
||||||
@ -36,6 +37,56 @@ Possible values:
|
|||||||
** 'chance_scheduler', which simply picks a host at random
|
** 'chance_scheduler', which simply picks a host at random
|
||||||
** A custom scheduler driver. In this case, you will be responsible for
|
** A custom scheduler driver. In this case, you will be responsible for
|
||||||
creating and maintaining the entry point in your 'setup.cfg' file
|
creating and maintaining the entry point in your 'setup.cfg' file
|
||||||
|
"""),
|
||||||
|
cfg.MultiStrOpt("available_filters",
|
||||||
|
default=["zun.scheduler.filters.all_filters"],
|
||||||
|
help="""
|
||||||
|
Filters that the scheduler can use.
|
||||||
|
|
||||||
|
An unordered list of the filter classes the zun scheduler may apply. Only the
|
||||||
|
filters specified in the 'scheduler_enabled_filters' option will be used, but
|
||||||
|
any filter appearing in that option must also be included in this list.
|
||||||
|
|
||||||
|
By default, this is set to all filters that are included with zun.
|
||||||
|
|
||||||
|
This option is only used by the FilterScheduler and its subclasses; if you use
|
||||||
|
a different scheduler, this option has no effect.
|
||||||
|
|
||||||
|
Possible values:
|
||||||
|
|
||||||
|
* A list of zero or more strings, where each string corresponds to the name of
|
||||||
|
a filter that may be used for selecting a host
|
||||||
|
|
||||||
|
Related options:
|
||||||
|
|
||||||
|
* scheduler_enabled_filters
|
||||||
|
"""),
|
||||||
|
cfg.ListOpt("enabled_filters",
|
||||||
|
default=[
|
||||||
|
"NoopFilter",
|
||||||
|
],
|
||||||
|
help="""
|
||||||
|
Filters that the scheduler will use.
|
||||||
|
|
||||||
|
An ordered list of filter class names that will be used for filtering
|
||||||
|
hosts. Ignore the word 'default' in the name of this option: these filters will
|
||||||
|
*always* be applied, and they will be applied in the order they are listed so
|
||||||
|
place your most restrictive filters first to make the filtering process more
|
||||||
|
efficient.
|
||||||
|
|
||||||
|
This option is only used by the FilterScheduler and its subclasses; if you use
|
||||||
|
a different scheduler, this option has no effect.
|
||||||
|
|
||||||
|
Possible values:
|
||||||
|
|
||||||
|
* A list of zero or more strings, where each string corresponds to the name of
|
||||||
|
a filter to be used for selecting a host
|
||||||
|
|
||||||
|
Related options:
|
||||||
|
|
||||||
|
* All of the filters in this option *must* be present in the
|
||||||
|
'scheduler_available_filters' option, or a SchedulerHostFilterNotFound
|
||||||
|
exception will be raised.
|
||||||
"""),
|
"""),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
111
zun/scheduler/base_filters.py
Normal file
111
zun/scheduler/base_filters.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Filter support
|
||||||
|
"""
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from zun.common.i18n import _LI
|
||||||
|
from zun.scheduler import loadables
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseFilter(object):
|
||||||
|
"""Base class for all filter classes."""
|
||||||
|
def _filter_one(self, obj, container):
|
||||||
|
"""Return True if it passes the filter, False otherwise."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
def filter_all(self, filter_obj_list, container):
|
||||||
|
"""Yield objects that pass the filter.
|
||||||
|
|
||||||
|
Can be overridden in a subclass, if you need to base filtering
|
||||||
|
decisions on all objects. Otherwise, one can just override
|
||||||
|
_filter_one() to filter a single object.
|
||||||
|
"""
|
||||||
|
for obj in filter_obj_list:
|
||||||
|
if self._filter_one(obj, container):
|
||||||
|
yield obj
|
||||||
|
|
||||||
|
# Set to true in a subclass if a filter only needs to be run once
|
||||||
|
# for each request rather than for each instance
|
||||||
|
run_filter_once_per_request = False
|
||||||
|
|
||||||
|
def run_filter_for_index(self, index):
|
||||||
|
"""Return True or False,
|
||||||
|
|
||||||
|
if the filter needs to be run for the "index-th" instance in a
|
||||||
|
request, return True. Only need to override this if a filter
|
||||||
|
needs anything other than "first only" or "all" behaviour.
|
||||||
|
"""
|
||||||
|
if self.run_filter_once_per_request and index > 0:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class BaseFilterHandler(loadables.BaseLoader):
|
||||||
|
"""Base class to handle loading filter classes.
|
||||||
|
|
||||||
|
This class should be subclassed where one needs to use filters.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_filtered_objects(self, filters, objs, container, index=0):
|
||||||
|
list_objs = list(objs)
|
||||||
|
LOG.debug("Starting with %d host(s)", len(list_objs))
|
||||||
|
part_filter_results = []
|
||||||
|
full_filter_results = []
|
||||||
|
log_msg = "%(cls_name)s: (start: %(start)s, end: %(end)s)"
|
||||||
|
for filter_ in filters:
|
||||||
|
if filter_.run_filter_for_index(index):
|
||||||
|
cls_name = filter_.__class__.__name__
|
||||||
|
start_count = len(list_objs)
|
||||||
|
objs = filter_.filter_all(list_objs, container)
|
||||||
|
if objs is None:
|
||||||
|
LOG.debug("Filter %s says to stop filtering", cls_name)
|
||||||
|
return
|
||||||
|
list_objs = list(objs)
|
||||||
|
end_count = len(list_objs)
|
||||||
|
part_filter_results.append(log_msg % {"cls_name": cls_name,
|
||||||
|
"start": start_count,
|
||||||
|
"end": end_count})
|
||||||
|
if list_objs:
|
||||||
|
remaining = [(getattr(obj, "host", obj),
|
||||||
|
getattr(obj, "nodename", ""))
|
||||||
|
for obj in list_objs]
|
||||||
|
full_filter_results.append((cls_name, remaining))
|
||||||
|
else:
|
||||||
|
LOG.info(_LI("Filter %s returned 0 hosts"), cls_name)
|
||||||
|
full_filter_results.append((cls_name, None))
|
||||||
|
break
|
||||||
|
LOG.debug("Filter %(cls_name)s returned "
|
||||||
|
"%(obj_len)d host(s)",
|
||||||
|
{'cls_name': cls_name, 'obj_len': len(list_objs)})
|
||||||
|
if not list_objs:
|
||||||
|
cnt_uuid = container.uuid
|
||||||
|
msg_dict = {"cnt_uuid": cnt_uuid,
|
||||||
|
"str_results": str(full_filter_results)}
|
||||||
|
full_msg = ("Filtering removed all hosts for the request with "
|
||||||
|
"container ID "
|
||||||
|
"'%(cnt_uuid)s'. Filter results: %(str_results)s"
|
||||||
|
) % msg_dict
|
||||||
|
msg_dict["str_results"] = str(part_filter_results)
|
||||||
|
part_msg = _LI("Filtering removed all hosts for the request with "
|
||||||
|
"container ID "
|
||||||
|
"'%(cnt_uuid)s'. Filter results: %(str_results)s"
|
||||||
|
) % msg_dict
|
||||||
|
LOG.debug(full_msg)
|
||||||
|
LOG.info(part_msg)
|
||||||
|
return list_objs
|
99
zun/scheduler/filter_scheduler.py
Normal file
99
zun/scheduler/filter_scheduler.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
The FilterScheduler is for scheduling container to a host according to
|
||||||
|
your filters configured.
|
||||||
|
You can customize this scheduler by specifying your own Host Filters.
|
||||||
|
"""
|
||||||
|
import random
|
||||||
|
|
||||||
|
from zun.common import exception
|
||||||
|
from zun.common.i18n import _
|
||||||
|
import zun.conf
|
||||||
|
from zun import objects
|
||||||
|
from zun.scheduler import driver
|
||||||
|
from zun.scheduler import filters
|
||||||
|
|
||||||
|
|
||||||
|
CONF = zun.conf.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class FilterScheduler(driver.Scheduler):
|
||||||
|
"""Scheduler that can be used for filtering zun compute."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(FilterScheduler, self).__init__(*args, **kwargs)
|
||||||
|
self.filter_handler = filters.HostFilterHandler()
|
||||||
|
filter_classes = self.filter_handler.get_matching_classes(
|
||||||
|
CONF.scheduler.available_filters)
|
||||||
|
self.filter_cls_map = {cls.__name__: cls for cls in filter_classes}
|
||||||
|
self.filter_obj_map = {}
|
||||||
|
self.enabled_filters = self._choose_host_filters(self._load_filters())
|
||||||
|
|
||||||
|
def _schedule(self, context, container):
|
||||||
|
"""Picks a host according to filters."""
|
||||||
|
services = objects.ZunService.list_by_binary(context, 'zun-compute')
|
||||||
|
hosts = [service.host
|
||||||
|
for service in services
|
||||||
|
if self.servicegroup_api.service_is_up(service)]
|
||||||
|
hosts = self.filter_handler.get_filtered_objects(self.enabled_filters,
|
||||||
|
hosts,
|
||||||
|
container)
|
||||||
|
if not hosts:
|
||||||
|
msg = _("Is the appropriate service running?")
|
||||||
|
raise exception.NoValidHost(reason=msg)
|
||||||
|
|
||||||
|
return random.choice(hosts)
|
||||||
|
|
||||||
|
def select_destinations(self, context, containers):
|
||||||
|
"""Selects destinations by filters."""
|
||||||
|
dests = []
|
||||||
|
for container in containers:
|
||||||
|
host = self._schedule(context, container)
|
||||||
|
host_state = dict(host=host, nodename=None, limits=None)
|
||||||
|
dests.append(host_state)
|
||||||
|
|
||||||
|
if len(dests) < 1:
|
||||||
|
reason = _('There are not enough hosts available.')
|
||||||
|
raise exception.NoValidHost(reason=reason)
|
||||||
|
|
||||||
|
return dests
|
||||||
|
|
||||||
|
def _choose_host_filters(self, filter_cls_names):
|
||||||
|
"""Choose good filters
|
||||||
|
|
||||||
|
Since the caller may specify which filters to use we need
|
||||||
|
to have an authoritative list of what is permissible. This
|
||||||
|
function checks the filter names against a predefined set
|
||||||
|
of acceptable filters.
|
||||||
|
"""
|
||||||
|
if not isinstance(filter_cls_names, (list, tuple)):
|
||||||
|
filter_cls_names = [filter_cls_names]
|
||||||
|
|
||||||
|
good_filters = []
|
||||||
|
bad_filters = []
|
||||||
|
for filter_name in filter_cls_names:
|
||||||
|
if filter_name not in self.filter_obj_map:
|
||||||
|
if filter_name not in self.filter_cls_map:
|
||||||
|
bad_filters.append(filter_name)
|
||||||
|
continue
|
||||||
|
filter_cls = self.filter_cls_map[filter_name]
|
||||||
|
self.filter_obj_map[filter_name] = filter_cls()
|
||||||
|
good_filters.append(self.filter_obj_map[filter_name])
|
||||||
|
if bad_filters:
|
||||||
|
msg = ", ".join(bad_filters)
|
||||||
|
raise exception.SchedulerHostFilterNotFound(filter_name=msg)
|
||||||
|
return good_filters
|
||||||
|
|
||||||
|
def _load_filters(self):
|
||||||
|
return CONF.scheduler.enabled_filters
|
45
zun/scheduler/filters/__init__.py
Normal file
45
zun/scheduler/filters/__init__.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Scheduler host filters
|
||||||
|
"""
|
||||||
|
from zun.scheduler import base_filters
|
||||||
|
|
||||||
|
|
||||||
|
class BaseHostFilter(base_filters.BaseFilter):
|
||||||
|
"""Base class for host filters."""
|
||||||
|
def _filter_one(self, obj, filter_properties):
|
||||||
|
"""Return True if the object passes the filter, otherwise False."""
|
||||||
|
return self.host_passes(obj, filter_properties)
|
||||||
|
|
||||||
|
def host_passes(self, host_state, filter_properties):
|
||||||
|
"""Return True if the HostState passes the filter,otherwise False.
|
||||||
|
|
||||||
|
Override this in a subclass.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class HostFilterHandler(base_filters.BaseFilterHandler):
|
||||||
|
def __init__(self):
|
||||||
|
super(HostFilterHandler, self).__init__(BaseHostFilter)
|
||||||
|
|
||||||
|
|
||||||
|
def all_filters():
|
||||||
|
"""Return a list of filter classes found in this directory.
|
||||||
|
|
||||||
|
This method is used as the default for available scheduler filters
|
||||||
|
and should return a list of all filter classes available.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return HostFilterHandler().get_all_classes()
|
38
zun/scheduler/filters/noop_filter.py
Normal file
38
zun/scheduler/filters/noop_filter.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Copyright (c) 2017 OpenStack Foundation
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from zun.api import servicegroup
|
||||||
|
from zun.scheduler import filters
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class NoopFilter(filters.BaseHostFilter):
|
||||||
|
"""Noop filter for now"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.servicegroup_api = servicegroup.ServiceGroup()
|
||||||
|
|
||||||
|
# Host state does not change within a request
|
||||||
|
run_filter_once_per_request = True
|
||||||
|
|
||||||
|
def host_passes(self, host_state, container):
|
||||||
|
"""Noop filter for now"""
|
||||||
|
|
||||||
|
# Depend on the objects.NodeInfo of below patch to filter node,
|
||||||
|
# https://review.openstack.org/#/c/436572/6, no more thing can do now.
|
||||||
|
return True
|
121
zun/scheduler/loadables.py
Normal file
121
zun/scheduler/loadables.py
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
# 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 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(zun.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 oslo_utils import importutils
|
||||||
|
|
||||||
|
from zun.common import exception
|
||||||
|
|
||||||
|
|
||||||
|
class BaseLoader(object):
|
||||||
|
def __init__(self, loadable_cls_type):
|
||||||
|
mod = sys.modules[self.__class__.__module__]
|
||||||
|
self.path = os.path.abspath(mod.__path__[0])
|
||||||
|
self.package = mod.__package__
|
||||||
|
self.loadable_cls_type = loadable_cls_type
|
||||||
|
|
||||||
|
def _is_correct_class(self, obj):
|
||||||
|
"""Judge whether class type is correct
|
||||||
|
|
||||||
|
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 all classes
|
||||||
|
|
||||||
|
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
|
Loading…
x
Reference in New Issue
Block a user