how to read openstack code : stevedore
学习了WSGI/Paste deploy后,还需要对一些在openstack中一些package有一些了解,才能更好的理解openstack的代码
What is stevedore
我们在写代码的时候通常把一个一个的功能块独立编写,甚至发布一定的规则和接口由第三方编写,然后在运行时根据实际情况来选择加载哪些功能模块。这样的好处是松耦合,灵活,而且便于协作。
stevedore就是一个很好的帮助动态加载代码的工具,openstack中很多plugin就是通过stevedore加载的。
下面通过一些应用场景看一下如何使用stevedore。
Driver 场景
假设我们有一个开源系统,我们不希望限定用户对数据库的选择。用户可以选择mysql/oracle/mongo等任意数据库。这就意味着我们需要对很多种数据库提供接口。这个工作量很大,并且要求开发人员对每一种数据库非常熟悉。最好的做法就是把数据库的接口设计成driver,并发布出该driver所需要实现的接口。这样任何第三方的组织或个人都可以参与开发。
比如,我们定义下面的接口。任何开发者,只要编写一个实现了下列接口的class,就可以实现一个driver
@six.add_metaclass(abc.ABCMeta)
class Base(object):
    """
    The Base class for db driver
    """
    @abc.abstractmethod
    def create_user(self, user_name):
        """
        :param user_name:
            The name of user
        :return:
            A user object
        """
基于上述接口,我们尝试开发一个mysql的driver如下:
# mysql_driver.py
import base
class MysqlDriver(base.Base):
    def create_user(self, user_name):
        return "create user %s in mysql" % user_name
该package的目录结构如下
[root@controller POTUS]# tree MysqlDriver/
MysqlDriver/
├── mysql_driver
│   ├── __init__.py
│   └── mysql_driver.py
└── setup.py
要注意在mysql_driver/init.py中要有 import mysql_driver这句话
setup.py的内容如下:
from setuptools import setup, find_packages
setup(
    name='mysql_driver',
    version='1.0',
    packages=find_packages(),
    entry_points={
        'my_system.db_driver': [
            'mysql = mysql_driver.mysql_driver:MysqlDriver',
        ],
    },
)
运行python setup.py install 后,会在系统目录中安装mysql_driver这个egg包,egg中有entry_point文件中记录了如下内容:
[my_system.db_driver]
mysql = mysql_driver.mysql_driver:MysqlDriver
my_system.db_driver是操作系统全局唯一的namespace。mysql是entry point的名字。通过这两个组合,可以定位到需要加载的代码,也就是我们的driver。
我们同样的再开发一个oracle_driver,如下:
[root@controller POTUS]# tree OracleDriver/
OracleDriver/
├── oracle_driver
│   ├── __init__.py
│   └── oracle_driver.py
└── setup.py
oracle_driver.py内容如下
# mysql_driver.py
import base
class OracleDriver(base.Base):
    def create_user(self, user_name):
        return "create user %s in oracle" % user_name
setup.py内容如下:
from setuptools import setup, find_packages
setup(
    name='oracle_driver',
    version='1.0',
    packages=find_packages(),
    entry_points={
        'my_system.db_driver': [
            'oracle = oracle_driver.oracle_driver:OracleDriver',
        ],
    },
)
不要忘了在__init__.py中加上 import oracle_driver。
ok, 在python setup.py install 后,我们的系统中有了mysql_driver 和 oracle_driver 下面就是应用了。
>>> from stevedore import driver
>>> mgr = driver.DriverManager(namespace = "my_system.db_driver", name = "mysql", invoke_on_load = True, invoke_args = ())
>>> mgr.driver.create_user('Tom')
'create user Tom in mysql'
>>> mgr = driver.DriverManager(namespace = "my_system.db_driver", name = "oracle", invoke_on_load = True, invoke_args = ())
>>> mgr.driver.create_user('Tom')
'create user Tom in oracle'
可以看到,当name=mysql时,调用的是mysql_driver, 当name=oracle时,调用的是oracle_driver
Driver的方式,让不同的driver开发过程相互独立,调用也更加灵活
Hook场景
Hook 顾名思义,钩子。就像window开发中的hook一样,每一个hook都是一个功能组件,会被一定的事件驱动。一个事件可以驱动一个hook,也可以驱动多个hook。同一个hook可以被多个不同的事件驱动。
假设我们开发了一个IDE工具,在鼠标点击关闭按钮的时候,会产生CLOSE_WINDOW EVENT, 这时候需要save_content组件保存内容。在鼠标点击commit按钮的时候,会产生COMMIT EVENT,这时候需要save_content发挥作用,也需要upload_content组件把内容上传到服务器。
这种场景下,我们可以开发save_content, upload_content两个组件,并且把save_content注册在CLOSE_WINDOW 和 COMMIT 两个event下,把upload_content注册在COMMIT event下。开发过程如下:
首先是save_content,
[root@controller POTUS]# tree IDE_HOOKA/
IDE_HOOKA/
├── save_content
│   ├── __init__.py
│   └── save_content.py
└── setup.py
save_content 内容如下:
class SaveContent(object):
    def __call__(self, *args, **kwargs):
        return "content are saved"
setup.py 内容如下:
from setuptools import setup, find_packages
setup(
    name='save_content',
    version='1.0',
    packages=find_packages(),
    entry_points={
        'ide.hooks': [
            'CLOSE_WINDOW = save_content.save_content:SaveContent',
            'COMMIT = save_content.save_content:SaveContent'
        ],
    },
)
init.py中有import save_content这句不要落下。
在python setup.py install 后,save_content.save_content:SaveContent 会被注册到CLOSE_WINDOW 和 COMMIT这两个event下。
我们再开发一个upload_content 组件如下:
[root@controller POTUS]# tree IDE_HOOKB/
IDE_HOOKB/
├── setup.py
└── upload_content
	├── __init__.py
	└── upload_content.py
主要看一下setup.py
from setuptools import setup, find_packages
setup(
    name='upload_content',
    version='1.0',
    packages=find_packages(),
    entry_points={
        'ide.hooks': [
            'COMMIT = upload_content.upload_content:UploadContent'
        ],
    },
)
这次,我们只注册了upload_content到COMMIT下。
在安装后,我们有了save_content 注册在CLOSE_WINDOW 和 COMMIT下, upload_content注册在COMMIT下。这样,当COMMIT事件发生,系统会保存内容并upload内容到服务器,当CLOSE_WINDOW事件发生,会保存内容。
运行如下:
# import hookmanager
# 定义运行hook的函数
>>> from stevedore import HookManager
>>> def run(hook):
...     return (hook.name, hook.obj())
... 
# 根据Close Window调用hook
>>> mgr = HookManager(namespace = "ide.hooks", name = "CLOSE_WINDOW", invoke_on_load = True, invoke_args = ())
>>> mgr.map(run)
[('CLOSE_WINDOW', 'content are saved')]
# 根据commit调用hook
>>> mgr = HookManager(namespace = "ide.hooks", name = "COMMIT", invoke_on_load = True, invoke_args = ())
>>> mgr.map(run)
[('COMMIT', 'content are saved'), ('COMMIT', 'content are uploaded')]
extension 场景
对比driver hook,他们都是通过setuptools的entry point来调用代码。driver是根据一个名字加载一个driver, hook是根据一个名字加载多个组件,而extension则是不根据名字,把namespace下所有的组件加载。
[root@controller POTUS]# tree EXT/
EXT/
├── ext
│   ├── ext1.py
│   ├── ext2.py
│   ├── ext3.py
│   └── __init__.py
└── setup.py
ext1的内容如下
class EXT1(object):
    def __call__(self, *args, **kwargs):
        return "ext1 is called"
setup.py内容如下:
from setuptools import setup, find_packages
setup(
    name='ext',
    version='1.0',
    packages=find_packages(),
    entry_points={
        'my_namespace': [
            'ext1 = ext.ext1:EXT1',
            'ext2 = ext.ext2:EXT2',
            'ext3 = ext.ext3:EXT3'
        ],
    },
)
按照完成后,ext1/ext2/ext3都被注册到my_namespace下。 调用方式如下:
>>> from stevedore import ExtensionManager
>>> mgr=ExtensionManager(namespace='my_namespace', invoke_on_load=True, invoke_args=())
>>> def run(hook):
...     return (hook.name, hook.obj)
...
>>> mgr.map(run)
[('ext3', <ext.ext3.EXT3 object at 0xd62a50>), ('ext2', <ext.ext2.EXT2 object at 0x102b6d0>), ('ext1', <ext.ext1.EXT1 object at 0x1034c10>)]
调用方式可以看到,通过namespace可以把其下所有组件加载
以上是stevedore的主要用法,下一篇讲展示routes的用法
how to read openstack code : stevedore的更多相关文章
- how to read openstack code: loading process
		之前我们了解了neutron的结构,plugin 和 extension等信息.这一章我们看一下neutron如何加载这些plugin和extension.也就是neutron的启动过程.本文涉及的代 ... 
- how to read openstack code: Core plugin and resource extension
		本章我们将写一个自己的core plugin 和一个resource extension来加深理解.(阅读本文的前提是你已经理解了restful以及stevedore等内容) 什么是 core plu ... 
- how to read openstack code: Neutron architecture
		今天这一章节非常重要.我们知道neutron是一个非常复杂的系统,由很多组件构成.研究这样一个复杂的系统,正确的顺序应该是现在宏观上对其整体结构有所了解,然后再由针对性的对其组件进行深入了解.本章要做 ... 
- how to read openstack code : routes
		When coding a web system, you have to think about an important problem, how to map urls to logic. Op ... 
- how to read openstack code : wsgi
		要读懂本篇,你至少得写过一个python的web程序,并且把它部署到web服务器上过. 什么是wsgi 假设你写了一个python的web程序,并部署到了nginx上,那么一个http request ... 
- how to read openstack code
		本文的目的不是介绍openstack.我们这里假设你已经知道了openstack是什么,能够做什么.所以目的是介绍如何阅读openstack的代码.通过读代码来进一步学习openstack. 转载要求 ... 
- how to read openstack code: request extension
		We have learned resource extension and action extension. This post we will write a request extension ... 
- how to read openstack code: action extension
		之前我们看过了core plugin, service plugin 还有resource extension. resource extension的作用是定义新的资源.而我们说过还有两种exten ... 
- how to read openstack code: service plugin
		We have learned core plugin, service plugin and extension in last post. Now let`s review: Core Plugi ... 
随机推荐
- javscript 导出html中的table到excel
			<script language="JavaScript" type="text/javascript"> /* * 默认转换实现函数,如果需要其他 ... 
- oracle 时间格式转化以及计算
			--A表中的日期字段 create_date 例如:2017-08-05 转化为2017年8月5日 oracle 在这里的双引号会忽略 select to_char(to_date(tt.c ... 
- SCCM大致安装过程,参考前辈教程完成部署
			本安装sccm主站点服务器准备 参考:http://stephen1991.blog.51cto.com/8959108/1529864 1. 准备三台服务器 ,注:所有服务器需要安装 .net3. ... 
- Mysql基本操作、C++Mysql简单应用、PythonMysql简单应用
			MySql基本操作 -- 当指定名称的数据库不存在时创建它并且指定使用的字符集和排序方式 CREATE DATABASE IF NOT EXISTS db_name CHARACTER SET UTF ... 
- ubuntu 普通用户运行virt-manager时libvirt权限设置
			error: Failed to connect socket to '/var/run/libvirt/libvirt-sock': Permission deniederror: failed t ... 
- 关闭警告&关闭eslint
			1.main.js中添加 vue.config.productiontip = false 这样即可去除警告! 第一个除了那个配置意外,还需要将 NODE_ENV 设置为 production 参 ... 
- LAME的“命令行”
			VBR 编码 (强烈推荐) Alt Preset Extreme (平均256kbps) 我们有时在网上可以看到".LAME-APX." 就是这种形式,我们也可以在文件名中包含这个 ... 
- 笔试算法题(14):整数二进制表示中的1 & 判定栈的push和pop序列是否对应
			出题:输入一个整数,要求计算此整数的二进制表示中1的个数 分析: 如果整数表示为k,当其是负数的时候,使用1<<i分别检测k的每一位:当其位整数的时候,则k/2表示将其二进制表示右移一位, ... 
- 小甲鱼Python学习笔记
			一 isdigit()True: Unicode数字,byte数字(单字节),全角数字(双字节),罗马数字False: 汉字数字Error: 无 isdecimal()True: Unicode数字, ... 
- 201621123079《Java程序设计》第1周学习总结
			第1周-Java基本概念 1.本周学习总结 第一次上课接触java,了解了java的由来和历史,还有JCP,JSP的概念,并学会如何建立一个java文件和运行过程.感觉java比之前学习的数据结构更高 ... 
