熟悉Openstack的童鞋都清楚:Openstack下的大多数项目比如Nova、Keystone、Cinder、Glance、Neutron、Ceilometer等这些项目在开发时都会用到一个叫做oslo
的基础公共库,如下就一些最常见的Openstack基础共用库简单展开聊聊他们各自的用途和基本用法。
1. oslo.config
该组件功能为Openstack解析配置文件,使用起来也比较容易,如下举一个简单的例子:
from oslo_config import cfg
CONF = cfg.CONF
options = [
cfg.BoolOpt('source_is_ipv6',
default=False,
help='Source is ipv6'),
cfg.StrOpt('cert',
default='self.pem',
help='SSL certificate file'),
]
CONF.register_opts(options) # 默认放在default组下面,如果是其他组的配置,则可以使用CONF.register_opts(options, group=group)
# 然后就可以直接操作对应的配置参数,比如:
def test_func():
print(CONF.cert)
Openstack配置文件使用INI
文件格式,具体配置都会以键值对的形式呈现,并且可以分配到不同的group
(默认是DEFAULT)下面,另外#
开始的行是注释说明,针对上面的配置具体到文件里应该是如下样式:
[DEFAULT]
# Source is ipv6
# (boolean value)
source_is_ipv6 = True
# SSL certificate file
# (string value)
# cert = self.pem
对于设置了默认值的参数项,如果配置文件中没有配置则实际取默认值;参数项支持多种参数类型比如布尔型(BoolOpt)、字符串型(StrOpt)、浮点型(FloatOpt)、字典(DictOpt)、列表(ListOpt)、整型(IntOpt)、IP类型(IPOpt)、多值类型(MultiStrOpt)等;实际配置中可以对前面已经定义的参数项进行引用,具体方式跟bash引用变量类似,在key前加上$
即可,而如果$
有实际意义则用$$
;另外如果实际的参数值包含空格,则需要把整个字符串值放在引号里。
使用
实际使用中,Openstack很多服务都需要基于配置文件来启动,只需在服务启动的命令后加上--config-file
即可。
oslo-config-generator
oslo-config-generator能够生成一个完整的配置文件的样本(默认格式是INI
),实际使用时只需根据实际场景对这个配置文件更新即可,但在使用这个工具生成配置之前,需要定义配置的发现入口:
# 1. 设置setup.cfg
[entry_points]
oslo.config.opts =
oslo.messaging = oslo.messaging.opts:list_opts
umha.conf = umha.opts.list_opts
# 2. 增加opts.py文件定义list_opts函数,比如增加umha/opts.py:
def list_opts():
return [
('DEFAULT', utils.cfg.common_opts),
('time', utils.cfg.time_opts),
('db', [utils.cfg.db_opt]),
('auth', utils.cfg.auth_opts),
('email', utils.cfg.email_opts),
('valve', utils.cfg.valve_opts),
]
如此就可以通过命令:
oslo-config-generator --namespace umha.conf --namespace oslo.messaging > /etc/umha/umha.conf
生成所需的配置文件样本了。
2. oslo.cache
该组件为Openstack提供缓存功能,把一些常用的数据比如keystone token信息等放进缓存,降低数据库的压力提高查询效率。oslo.cache
是通过dogpile.cache
库为Openstack各个服务组件提供一个适用多种缓存后端的缓存接口,目前已经实现多种缓存后端的缓存设计比如Memcache、etcd、MongoDB等,其中Memcache最常用,因此本文仅就Nova项目(其他项目使用都类似)中的Memcache使用做展开。
Nova项目跟缓存相关的最核心的文件:./nova/cache_utils.py
这个文件主要实现了两大块:
- 类CacheClient:定义缓存的各种操作比如查增删等;
- 函数get_client():用来获取缓存client,其实质是类
CacheClient
的实例化,而实现的方式通常是基于oslo.config
的配置来创建和配置region
(dogpile.cache.region.CacheRegion类实例),之后基于这个region
来生成一个CacheClient
实例;
类CacheClient实现代码:
class CacheClient(object):
def __init__(self, region):
self.region = region
def get(self, key):
value = self.region.get(key)
if value == cache.NO_VALUE:
return None
return value
def get_or_create(self, key, creator):
return self.region.get_or_create(key, creator)
def set(self, key, value):
return self.region.set(key, value)
def add(self, key, value):
return self.region.get_or_create(key, lambda: value)
def delete(self, key):
return self.region.delete(key)
def get_multi(self, keys):
values = self.region.get_multi(keys)
return [None if value is cache.NO_VALUE else value for value in
values]
def delete_multi(self, keys):
return self.region.delete_multi(keys)
函数get_client()代码实现:
def get_client(expiration_time=0):
if CONF.memcached_servers:
return CacheClient(
_get_custom_cache_region(expiration_time=expiration_time,
backend='dogpile.cache.memcached',
url=CONF.memcached_servers))
elif CONF.cache.enabled:
return CacheClient(
_get_default_cache_region(expiration_time=expiration_time))
return CacheClient(
_get_custom_cache_region(expiration_time=expiration_time,
backend='oslo_cache.dict'))
其实上面所述的CacheClient
中缓存各种操作的实现都是基于region
(具体可参见dogpile.cache.region.py
和dogpile.cache.backends.memcached.py
)来的,至于实现细节可阅读dogpile库的源码
来进一步深入。
3. oslo.messaging
此处不再展开,请参考我的另一篇博文Openstack基础组件之oslo.messaging即可。
4. oslo.service
该组件提供了一个框架,用于为Openstack应用定义长时间运行的服务,库的名称是oslo_service
,在oslo.service的实现中,依赖于oslo_service.service的两个最核心的类:Service类和Launcher类。
- Service类:基于抽象基类
oslo_service.service.ServiceBase
,它定义了一个服务对象以及管理整个服务生命周期的方法,包括reset、start、stop、wait
,另外,在对服务对象实例化时可以传进一个threads
的参数创建一个包含threads个线程的线程组ThreadGroup对象,用来管理服务中的所有线程。还有一个叫做Services
的类,用来管理一组服务。 - Launcher类:主要用来启动一个或多个服务并等待其完成。其定义了launcher_service(service, workers)、stop()、wait()、restart()方法管理Launcher对象中所有服务的生命周期,并且基于workers的数量又有两种实现机制:ServiceLauncher类和ProcessLauncher类。
实际使用场景下,我们在创建Service时会对oslo_service.service.Service
进一步封装,比如下面这样:
from oslo_sevice import service
from oslo_config import cfg
CONF = cfg.CONF
class TestService(service.Service):
def __init__(self, *args, **kwargs):
pass
def start(self):
# do something
pass
def stop(self):
pass
def reset(self):
pass
if __name__ == '__main__':
service.launch(CONF, TestService()).wait()