利用python语言的特性,运行时动态载入代码变得更加容易.很多python应用程序利用这样的特性在运行时发现和载入所谓的"插件",使得自己更易于扩展.python库stevedore就是在Setuptools的entry points基础上,构造了一层抽象层,使开发者可以更容易地在运行时发现和载入插件.
stevedore的代码库在https://github.com/openstack/stevedore,项目主页在https://launchpad.net/python-stevedore,参考文档在http://stevedore.readthedocs.org/.
entry points的每一个命名空间里,可以包含多个entry point项.stevedore要求每一项都符合如下格式:
name = module:importable
左边是插件的名称,右边是它的具体实现,中间用等号分隔开.插件的具体实现用"模块:可导入的对象"的形式来指定,以Ceilometer为例:
ceilometer.compute.virt =
libvirt = ceilometer.compute.virt.libvirt.inspector:LibvirtInspector
hyperv = ceilometer.compute.virt.hyperv.inspector:HyperVInspector
vsphere = ceilometer.compute.virt.vmware.inspector:VsphereInspector
ceilometer.hardware.inspectors =
snmp = ceilometer.hardware.inspectors.snmp:SNMPInspector
根据每个插件在entry point中名字和具体实现的数量之间的对应关系不同,stevedore提供了多种不同的类来帮助开发者发现和载入插件,如下图所示:
插件名字: 具体实现 | 建议选用stevedore中的类 |
1: 1 | stevedore.driver.DriverManager |
1: n | stevedore.hook.HookManager |
n: m | stevedore.extension.ExtensionManager |
使用stevedore来帮助程序动态载入插件的过程主要分为三个部分:插件的实现,插件的注册,以及插件的载入.下面我们以Ceilometer里动态载入compute agent上的inspector驱动为例来分别进行介绍.
Ceilometer的inspector驱动,为从不同类型hypervisor中获取相关数据提供统一的接口以供compute agent调用.下面是它的基类:
#ceilometer/compute/virt/inspector.py
class Inspector(object):
def inspect_instances(self):
"""List the instances on the current host."""
raise NotImplementedError()
def inspect_cpus(self, instance_name):
"""Inspect the CPU statistics for an instance.
:param instance_name: the name of the target instance
:return: the number of CPUs and cumulative CPU time
"""
raise NotImplementedError()
...
class LibvirtInspector(virt_inspector.Inspector):
def __init__(self, conf):
super(LibvirtInspector, self).__init__(conf)
self._connection = None
@property
def connection(self):
if not self._connection:
self._connection = libvirt_utils.get_libvirt_connection(self.conf)
return self._connection
@libvirt_utils.retry_on_disconnect
def _lookup_by_uuid(self, instance):
instance_name = util.instance_name(instance)
try:
return self.connection.lookupByUUIDString(instance.id)
except Exception as ex:
if not libvirt or not isinstance(ex, libvirt.libvirtError):
raise virt_inspector.InspectorException(six.text_type(ex))
error_code = ex.get_error_code()
if (error_code in (libvirt.VIR_ERR_SYSTEM_ERROR,
libvirt.VIR_ERR_INTERNAL_ERROR) and
ex.get_error_domain() in (libvirt.VIR_FROM_REMOTE,
libvirt.VIR_FROM_RPC)):
raise
msg = _("Error from libvirt while looking up instance "
": "
"[Error Code %(error_code)s] "
"%(ex)s") % {'name': instance_name,
'id': instance.id,
'error_code': error_code,
'ex': ex}
raise virt_inspector.InstanceNotFoundException(msg)
def inspect_cpus(self, instance):
domain = self._get_domain_not_shut_off_or_raise(instance)
# TODO(gordc): this can probably be cached since it can be used to get
# all data related
stats = self.connection.domainListGetStats([domain])
dom_stat = stats[0][1]
return virt_inspector.CPUStats(number=dom_stat['vcpu.current'],
time=dom_stat['cpu.time'])
#setup.cfg
ceilometer.compute.virt =
libvirt = ceilometer.compute.virt.libvirt.inspector:LibvirtInspector
hyperv = ceilometer.compute.virt.hyperv.inspector:HyperVInspector
vsphere = ceilometer.compute.virt.vmware.inspector:VsphereInspector
xenapi = ceilometer.compute.virt.xenapi.inspector:XenapiInspector
这三个插件注册在命名空间"ceilometer.compute.virt"下,分别叫做libvirt,hyper和vsphere.
#ceilometer/compute/virt/libvirt/inspector.py
def get_hypervisor_inspector(conf):
try:
namespace = 'ceilometer.compute.virt'
mgr = driver.DriverManager(namespace,
conf.hypervisor_inspector,
invoke_on_load=True,
invoke_args=(conf, ))
return mgr.driver
except ImportError as e:
LOG.error(_LE("Unable to load the hypervisor inspector: %s") % e)
return Inspector(conf)
参数 = 默认值 | 说 明 |
namespace | (字符串类型)命名空间 |
name | (字符串类型)插件名 |
invoke_on_load = False | (布尔类型)是否调用entry point所返回的插件对象.(在这个例子中,由于entry point所指向的对象是类,相当于是否实例化类对象) |
invoke_args = {} | (元组类型)调用插件对象时所需要的位置参数 |
invoke_kwds = {} | (字典类型)调用插件对象时所需要的命名参数 |
on_load_failure_callback = None | (函数类型)载入某个entry point失败时的回调函数,参数为(manager,entry point,exception) |
verify_requirements = False | (布尔类型)是否用setup tools来确保此插件的依赖关系都能满足 |
注意这里我们使用的命名空间"ceilometer.compute.virt"需要和Setuptools中注册的命名空间一致.具体需要载入的插件名称从配置项hypervisor_inspector中读入.