OpenStack 通用技术有哪些
wblr6035
8年前
<p>来自: <a href="/misc/goto?guid=4959670128438023344" rel="nofollow">http://1.chaoxu.sinaapp.com/archives/3858</a></p> <p>OpenStack遵循这样的设计原则,“不要重复发明轮子”,即对已实现的功能,开发者直接拿来用即可。这一设计原则最终形成了一个由专门团队维护的Oslo——OpenStack公共库,实现硬件、操作系统和应用程序等的松耦合。</p> <p>一.消息总线(MQ)</p> <p>OpenStack的各项目之间通过REST ful API进行通信;项目内部、不同服务进程之间的通信,则必须要通过消息总线。软件从最初的面向过程、面向对象、再到面向服务,要求我们去考虑各个服务之间如何传递消息,借鉴硬件总线的概念,引入了消息总线的模式,顾名思义,一些服务向总线发送消息,其他服务则从总线上获取消息。</p> <p>OpenStack对很多消息总线的开源实现提供了支持,比如RabbitMQ、Qpid等。基于这些消息总线类型,OpenStack oslo.messaging库实现了以下两种方式来完成项目内部各服务进程之间的通信。</p> <p>远程过程调用(RPC)</p> <p>通过远程过程调用,一个服务进程可以调用其他服务进程的方法,并且有两种调用方式,call和cast。通过call的方式调用,远程方法会被同步执行,调用者会被阻塞直到结果返回。通过cast方式调用,远程方法会被异步执行,结果并不会立即返回,调用者也不会被阻塞,但是调用者需要利用其他方式查询这次远程调用的结果。</p> <p>事件通知(Event Notification)</p> <p>某个服务进程可以把时间通知发送到消息总线上,该消息总线上所有对此类事件感兴趣的服务进程,都可以获得此事件通知并进行下一步的处理,处理的结果并不会返回给事件发送者。这种通信方式,不但可以在同一个项目内部的各个服务进程之间发送通知,也可以实现跨项目之间的通知发送。Ceilometer就通过这种方式大量获取其他OpenStack项目的事件通知,从而进行计量和监控。</p> <p>1.AMQP(高级消息队列协议)</p> <p>OpenStack支持的消息总线类型中,大部分都是基于AMQP的。AMQP是一个异步的应用层消息传递开放协议,主要包括了消息的导向、消息交换、消息队列和路由。对于一个使用了AMQP的中间件服务而言,当不同的消息由生产者(Producer)发送到Server时,它会根据不同的条件把消息传递给不同的消费者(Consumer)。如果消费者无法接收消息或者接收消息不够快时,它会把消息缓存在内存或者磁盘上。</p> <p>2.基于AMQP实现RPC</p> <p>3.OpenStack支持的常见消息总线类型</p> <p>1)RabbitMQ</p> <p>2)Qpid</p> <p>3)ZeroMQ</p> <p>二.SQLAlchemy和数据库(Shane)</p> <p>SQLAlchemy是Python语言下的一款开源软件,提供了SQL工具包以及对象关系映射器ORM,这样SQLAlchemy便能让Python开发人员简单灵活地运行SQL操作后台数据库。</p> <p>SQLAlchemy主要分成两部分,SQLAlchemy Core和SQLAlchemy ORM。前者主要包括SQL语言表达式、数据库引擎、连接池等,其目的是为了实现连接不同的后台数据库、提交查询和更新SQL请求去后台执行。SQLAlchemy ORM提供数据映射模式,也就是把程序语言的对象数据映射成数据库中的关系数据,或者把关系数据映射成对象数据。</p> <p>需要注意的是,如果程序用了对象关系映射器,虽然好处极多,但程序性能会受到一定影响。因此,对象关系映射是一个可选的模块,而开发人员即便不用任何对象关系映射也能直接用SQLAlchemy操作数据。</p> <p>三.RESTful API和WSGI</p> <p>OpenStack项目都是通过RESTful API向外提供服务,这使得OpenStack的接口在性能、可扩展性、可移植性、易用性等方面做到了比较好的平衡。</p> <p>1.什么是REST ful API</p> <p>如果一个软件架构符合REST原则,我们就称它为RESTful架构。</p> <p>RESTful架构的一个核心概念是“资源”。从RESTful的角度看,网络中的任何东西都是资源。它可以是一段文本、一张图片、一首歌曲、一种服务等,每个资源都对应于一个特定的URL,并用它进行标示,访问这个URL就可以获得此资源。</p> <p>资源可以有多种具体的表现形式,也就是资源的“表述”(Represent),比如一张图片可以是JPEG格式也可以是PNG格式,URL代表的是资源实体,而不是表现形式。</p> <p>客户端和服务端之间的互动传递就是资源的表述。我们上网浏览网页,就是在调用资源的URL,获取它不同表现形式的过程,这种互动只能使用无状态协议HTTP,也就是说,服务端必须保存所有的状态,客户端可以使用包括GET、POST、PUT和DELETE这些在内的基本操作,使服务端上的资源发生“状态转移”,也就是所谓的“表述性状态转移”</p> <p>2.RESTful路由</p> <p>Openstack各个项目都提供了RESTful架构的API作为对外提供的接口,而RESTful架构的核心是资源和资源上的操作。也就是说,OpenStack定义了很多的资源,并实现了针对这些资源的各种操作函数。</p> <p>当OpenStack 的API服务进程接收到客户端的HTTP请求时,一个所谓的路由模块会将请求的URL转换成相应的资源,并路由到合适的操作函数上。OpenStack使用的路由模块是Routers。</p> <p>比如,当我们执行“neutron router-list”命令时,Neutron客户端(neutron_client)将这个命令转换成HTTP请求发送给Neutron的server服务进程,然后被路由到下面代码中的“index”操作。</p> <pre> # neutron/api/v2/router.py 43 class Index(wsgi.Application): 44 def __init__(self, resources): 45 self.resources = resources 46 47 @webob.dec.wsgify(RequestClass=wsgi.Request) 48 def __call__(self, req): 49 metadata = {'application/xml': {'attributes': { 50 'resource': ['name', 'collection'], 51 'link': ['href', 'rel']}}} 52 53 layout = [] 54 for name, collection in self.resources.iteritems(): 55 href = urlparse.urljoin(req.path_url, collection) 56 resource = {'name': name, 57 'collection': collection, 58 'links': [{'rel': 'self', 59 'href': href}]}</pre> <p>3.什么是WSGI</p> <p>RESTful只是软件设计的风格而不是标准,Web服务中通常使用基于HTTP的符合RESTful风格的API。而WSGI(Web服务器网关接口)则是Python语言中所定义的Web服务器和Web应用程序之间的通用接口标准。</p> <p>WSGI是一个网关,作用是在协议之间进行转换。也就是说,WSGI是一座桥梁,桥梁的一端称为服务端或者网关端,另一端称为应用端或者框架端。当处理一个WSGI请求时,服务端为应用端提供上下文信息和一个回调函数,应用端处理完请求之后,使用服务端提供的回调函数返回相对应的请求响应。</p> <p>作为一个桥梁,WSGI将Web组件分成了三类,Web服务器(WSGI Server)、Web中间件(WSGI Middleware)与Web应用程序(WSGI APPlication)。WSGI Server接收http请求,封装一系列的环境变量,调用注册的WSGI Application,最后将响应返回给客户端。</p> <p>WSGI Application是一个可被调用的Python对象,它接受两个参数,通常为environ和start_response。如下一个Neutron项目中关于Server的功能测试列子:</p> <pre> # neutron/tests/functional/test_server.py 177 def application(environ, start_response): 178 """A primitive test application.""" 179 180 response_body = 'Response' 181 status = '200 OK' 182 response_headers = [('Content-Type', 'text/plain'), 183 ('Content-Length', str(len(response_body)))] 184 start_response(status, response_headers) 185 return [response_body]</pre> <p>其中,参数environ指向一个Python字典,WSGI Application可以从environ字典中获取相对应的请求及其执行上下文的所有信息。而参数start_response指向一个回调函数,如下所示一个Neutron项目中关于WSGI的单元测试列子:</p> <pre> # vim neutron/tests/unit/test_wsgi.py 161 def hello_world(env, start_response): 162 if env['PATH_INFO'] != '/': 163 start_response('404 Not Found', 164 [('Content-Type', 'text/plain')]) 165 return ['Not Found\r\n']</pre> <p>字符串“404 Not Found”用于表示请求响应的状态,“’Content-Type’, ‘text/plain’”是一个分别代表了header_name,header_value的元组列表。也就是HTTP响应中的http报头和内容。</p> <p>WSGI中间件同时实现了服务端和应用端的API,因此可以在两端之间起协调沟通作用,在服务端看来,中间件就是一个WSGI应用;在应用端看来,中间件则是一个WSGI服务器。</p> <p>WSGI中间件可以将客户端的HTTP请求,路由给不同的应用对象,然后将应用处理后的结果返回给客户端。自然,我们也可以将WSGI中间件理解为服务端和应用端交互的一层包装,经过不同中间件的包装,便具有不同的功能,比如URL路由分发,再比如权限认证,这些不同中间件的组合便形成了WSGI的框架,比如Paste。</p> <p>4.什么是Paste</p> <p>OpenStack使用Paste的Deploy组件来完成WSGI服务器和应用的构建,每个项目源码的etc目录下都有一个Paste配置文件,比如Neutron中的/etc/neutron/api-paste.ini,部署时,这些配置文件会被复制到系统的/etc/Project_name目录下。Paste Deploy的工作便是基于这些配置文件。</p> <p>Paste配置文件分为多个Section,每个Section以type:name的格式命名。使用Paste Deploy的主要目的是从配置文件中生成一个WSGI Application,有了配置文件之后,只需要使用下面的调用方式。</p> <pre> from paste.deploy import loadapp wsgi_app = loadapp(‘config:/path/to/config.ini’)</pre> <p>5.什么是WebOb</p> <p>除了Routers与Paste Deploy外,OpenStack中另一个与WSGI密切相关的是Webob。Webob通过对WSGI的请求与响应进行封装,来简化WSGI应用的编写。</p> <p>6.什么是Eventlet</p> <p>Openstack中的绝大部分项目都采用了协程模型。在操作系统看来,一个Openstack服务只会运行在一个进程中,但在这个进程中,OpenStack巧妙的地利用了Python的网络库Eventlet产生出许多个协程(绿色线程),这些协程之间只有在调用到了某些特殊的Eventlet库函数的时候(比如睡眠sleep、I/O调用等)才会发生切换。</p> <p>与线程类似,协程也拥有自己独立的栈和局部变量,同时,又与其他协程共享全局变量。协程与线程的主要区别是,多个线程可以同时运行,而同一时间内只能有一个协程在运行,无须考虑很多锁的问题。</p> <p>使用线程时,线程的执行完全由操作系统控制,而使用协程时,协程的执行顺序、时间完全由程序自己决定,由于工作方式为主动模式,所以可以最大化的利用CPU性能。协程的实现主要是在协程休息时把当前的寄存器保存起来,然后重新工作时再将其恢复过来,因此,协程可以理解为一个线程内的伪并发方式(并发也就是指创建多个绿色线程)。</p> <p>7.什么是AsynclO</p> <p>目前,OpenStack社区正在考虑使用AsyncIO来代替Eventlet。AsyncIO可以看做是许多第三方Python库的超集,包括Twisted、Eventlet等。</p> <p>三.OpenStack通用库Oslo</p> <p>1.Cliff</p> <p>Cliff,是OpenStack中用来帮助构建命令行程序的通用库。主程序负责基本命令行参数的解析,然后调用各个子命令去执行不同的操作。</p> <p>2.oslo.config</p> <p>oslo.config通用库用于解析命令行和配置文件中的配置选项。</p> <p>3.oslo.db</p> <p>oslo.db是针对SQLAlchemy访问的对象。</p> <p>4.oslo.i18n</p> <p>oslo.i18n是对Python gettext模块的封装,主要用于openstack字符串的翻译和国际化。</p> <p>5.oslo.messaging</p> <p>oslo.messaging通用库为openstack各个项目使用RPC和事件通知提供了一套统一的接口。</p> <p>为了支持不同的RPC后端实现,oslo.messaging对如下的对象进行了统一:</p> <p>Transport</p> <p>Transport传输层主要实现RPC底层的通信,比如Socket以及事件循环、多线程等其他功能。</p> <p>Target</p> <p>Target封装了指定某一个消息最终目的地的所有消息。</p> <p>Server</p> <p>一个RPC服务器可以暴漏多个endpoint,每个endpoint包含一组方法。</p> <p>RPC Client</p> <p>通过RPC Client可以远程调用RPC Server上的方法,有cast和call两种远程调用方式。</p> <p>Stevedore</p> <p>在Python代码运行时动态发现和载入所谓的插件“Plugin”,使得程序更容易的扩展,Python库Stevedore就是在setuptools的entry points基础上,构造了一层抽象层。使用Stevedore实现程序动态载入插件的过程主要分为三个部分:插件的实现;插件的注册,以及插件的载入。</p> <p>Taskflow</p> <p>通过Taskflow通用库,可以更容易的控制任务(Task)的执行。Task、flow和engine是Taskflow中的几个基本概念。</p> <p>Task是Taskflow库中拥有执行(execute)和回滚(revert)功能的最小单位(实际最小单位是atom,其他所有类包括task类都是Atom类的子类),然后新建一个线性流flow。而engine用来载入一个flow,然后驱动该flow中的task/flow运行。</p> <p>Cookiecutter</p> <p>开发人员,可以使用openstack的Cookiecutter通用库模板,新建一个符合惯例的openstack项目。</p> <p>Oslo.policy</p> <p>Policy用于控制用户的权限,能够执行什么样的操作,openstack的每个项目中都有一个/etc/project_name/policy.json文件,可以通过配置该文件来实现对用户的权限管理。将policy操作的公共部分提取出来,便形成了oslo.policy通用库。</p> <p>Oslo.rootwrap</p> <p>Oslo.rootwrap可以让openstack服务以root身份去执行一些shell命令。一般来说openstack的服务都是以普通用户的身份去运行的,但是当他们需要以root身份去执行一些shell命令时,便需要利用到oslo.rootwrap的功能。</p> <p>Oslo.test</p> <p>Oslo.test库是openstack中提供单元测试的基础框架或通用库。Oslo.test基于testtools库定义了oslotest.base.BaseTestCase类,可以作为其他openstack项目单元测试的基类。</p> <p>比如,在下面的neutron/tests/unit/api/v2/test_resource.py文件中:</p> <pre> 28 class RequestTestCase(base.BaseTestCase): 29 def setUp(self): 30 super(RequestTestCase, self).setUp() 31 self.req = wsgi_resource.Request({'foo': 'bar'}) 32 33 def test_content_type_missing(self): 34 request = wsgi.Request.blank('/tests/123', method='POST') 35 request.body = b"<body />" 36 self.assertIsNone(request.get_content_type()) 37 38 def test_content_type_with_charset(self): 39 request = wsgi.Request.blank('/tests/123') 40 request.headers["Content-Type"] = "application/json; charset=UTF-8" 41 result = request.get_content_type() 42 self.assertEqual(result, "application/json")</pre> <p>使用BaseTestCase作为基类时,单元测试中创建的所有临时文件都会被存放在一个单独的目录中,此时系统环境变量HOME也会被设置成一个临时的目录。</p> <p>BaseTestCase类提供了方法create_tempest()来创建临时文件:</p> <pre> Create_tempfiles(files, ext=’.conf’) 参数:files(元组的列表),包含了类似(文件名、文件内容)的元组列表 ext(字符串),新建的文件扩展名 返回:所有新建的文件名列表</pre> <p>oslo.test通用库除了提供上面的基类之外,还提供了两个通用的fixture,oslotest.mockpatch和oslotest.moxstubout供其他Openstack项目开发单元测试用例。不过,一般建议使用oslotest.mockpatch。比如:</p> <p>Oslotest通用库提供了一个debug脚本用来支持在测试中使用pdb。</p> <p>1)在代码中设置调试:</p> <pre> import pdb; pdb.set_trace()</pre> <p>2)在Openstack项目的tox.ini配置文件中加入如下行:</p> <pre> [ testenv:debug] commands = oslo.debug_helper.sh {posargs}</pre> <p>3)运行类似下面的命令来触发断点,进入Python debugger:</p> <pre> # tox –e debug # tox –e debug 程序模块名.类名.方法名</pre>