OpenStack基础之stevedore

    利用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 points的命名空间,“ceilometer.compute.virt"和"ceilometer.hardware.inspectors",分别注册有3个和1个插件.每个插件都符合"名字=模块:可导入对象”的格式,在“ceilometer.compute.virt"命名空间里的libvirt插件,它的具体可载入的实现是ceilometer.compute.virt.libvirt.inspector模块中的LibvirtInspector类.

    根据每个插件在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()
    
    ...

    ceilometer/compute/virt/libvirt/inspector.py,ceilometer/compute/virt/hyperv/inspector.py和ceilometer/compute/virt/vmware/inspector.py分别为kvm,hyperv和vsphere三种不同hypervisor的具体实现,比如:

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)

    Ceilometer的compute agent通过调用函数get_hypervisor_inspector来载入具体的某一个插件.此处由于插件和具体实现之间是一对一的关系,所以选用了stevedore和DriverManager类,这个类实例化时可接受的参数如下表所示:

参数 = 默认值 说  明
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中读入.

你可能感兴趣的:(openstack,stevedore,ceilometer,libvirt,inspector,OpenStack)