Openstack基础之oslo(一)

原创 2017年01月29日 19:13:02

    OpenStack通用库(Oslo)包含了众多不需要重复发明的"轮子".当开发者觉得现有的代码中有适合被其他OpenStack项目共用的部分时,就可以申请把这些能用的代码放入oslo-incubator代码库中进行"孵化".

    当oslo-incubator中的某部分代码让社区觉得已经足够成熟可以毕业后,其代码开发维护者可以提出毕业申请.毕业后的代码会成为一个单独的OpenStack项目,独立于oslo-incubator进行开发和维护.

    oslo-incubator的代码库位于https://github.com/openstack/oslo-incubator.相应的Lauchpad页面是https://launchpad.net/oslo.

    和使用其他第三方python库一样,使用已经毕业的oslo能用库,我们只需要在自己的代码中import对应的库就可以了,比如下面的代码就使用oslo.config库定义了一个配置选项sample_source:

   

from oslo_config import cfg

OPTS = [
    cfg.StrOpt('sample_source',
               default='openstack',
               deprecated_name='counter_source',
               help='Source for samples emitted on this instance.'),
]

cfg.CONF.register_opts(OPTS)


    如果在某一个OpenStack项目中要使用还在孵化期中的oslo.incubator代码,需要把oslo.incubator的代码同步到该项目代码的openstack/common目录下.所以我们如果看到诸如"from xxx.openstack.common import yyy"之类的代码,一般这些代码都是从oslo.incubator代码库中同步过来的,比如:
from ceilometer.openstack.common import log
from ceilometer.openstack.common import service as os_service


LOG = log.getLogger(__name__)

Cliff
    Cliff(Command Line Interface Formulation Framework)可以用来帮助构建命令行程序.开发者可以利用Cliff框架可以构建诸如svn,git那样的支持多层命令的命令行程序.主程序只负责基本的命令行参数的解析,然后调用各个子命令去执行不同的操作.利用python动态代码载入的特性,Cliff框架中的每
个子命令可以和主程序分开来实现,打包和分发.
    Cliff的代码库们于http://git.openstack.org/cgit/openstack/cliff,项目主页在https://launchpad.net/python-cliff.
    整个Cliff框架主要包括以下四种不同类型的对象:
  • cliff.app.App:主程序对象,用来启动程序,并且负责一些对所有子命令都能用的操作,比如设置日志选项和输入输出等.
  • cliff.commandmanager.CommandManager:主要用来载入每个子命令插件.默认是通过Setuptools的entry points来载入.
  • cliff.command.Command:用户可以实现Command的子类来实现不同的子命令,这些子命令被注册在Setuptools的entry points中,被CommandManager载入每个子命令可以有自己的参数解析(一般使用argparse),同时要实现take_action()方法完成具体的命令.
  • cliff.interactive.InteractiveApp:实现交互式命令行.一般使用框架提供的默认实现.
Cliff源码中附带了一个示例demoApp,下面我们结合这个例子来了解Cliff的大致接口:

import logging
import sys

from cliff.app import App
from cliff.commandmanager import CommandManager

class DemoApp(App):

    log = logging.getLogger(__name__)

    def __init__(self):
        super(DemoApp, self).__init__(
            description='cliff demo app',
            version='0.1',
            command_manager=CommandManager('cliff.demo')
        )

    def initialize_app(self, argv):
        self.log.debug('initialize_app')

    def prepare_to_run_command(self, cmd):
        self.log.debug('prepare_to_run_command%s',
                       cmd.__class__.__name__)

    def clean_up(self, cmd, result, err):
        self.log.debug('clean_up %s', cmd.__class__.__name__)
        if err:
            self.log.debug('got an error: %s', err)

def main(argv=sys.argv[1:]):
    myapp = DemoApp()
    return myapp.run(argv)

if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))



    上面是主程序的代码,主程序新建一个DemoApp对象实例,并且调用其run方法运行.DemoApp是cliff.app.App的子类,它的初始化函数的原型定义为:
class App(object):
    """Application base class.

    :param description: one-liner explaining the program purpose
    :paramtype description: str
    :param version: application version number
    :paramtype version: str
    :param command_manager: plugin loader
    :paramtype command_manager: cliff.commandmanager.CommandManager
    :param stdin: Standard input stream
    :paramtype stdin: readable I/O stream
    :param stdout: Standard output stream
    :paramtype stdout: writable I/O stream
    :param stderr: Standard error output stream
    :paramtype stderr: writable I/O stream
    :param interactive_app_factory: callable to create an
                                    interactive application
    :paramtype interactive_app_factory: cliff.interactive.InteractiveApp
    :param deferred_help: True - Allow subcommands to accept --help with
                          allowing to defer help print after initialize_app
    :paramtype deferred_help: bool
    """

    def __init__(self, description, version, command_manager,
                 stdin=None, stdout=None, stderr=None,
                 interactive_app_factory=None,
                 deferred_help=False):
其中stdin/stdout/stderr可以用来定义用户自己的标准输入/输出/错误,command_manager必须指向一个cliff.commandmanger.CommandManger
的对象实例,这个实例用来载入各个子命令插件.
    cliff.commandmanager.CommandManager类的初始化函数原型定义为:
class CommandManager(object):
    """Discovers commands and handles lookup based on argv data.

    :param namespace: String containing the setuptools entrypoint namespace
                      for the plugins to be loaded. For example,
                      ``'cliff.formatter.list'``.
    :param convert_underscores: Whether cliff should convert underscores to
                                spaces in entry_point commands.
    """
    def __init__(self, namespace, convert_underscores=True):
        self.commands = {}
        self.namespace = namespace
        self.convert_underscores = convert_underscores
        self._load_commands()
其中namespace用来指定Setuptools entry points的全名空间,CommandManager只会从这个命名空间中载入插件,convert_underscores参数指明
是否需要把entry points中的下划线转化为空格.
    我们可以利用cliff.app.App类的方法initialize_app()做一些初始化工作,这个函数会在主程序解析完用户的命令行参数后被调用,而且只会被调用到唯
一一次.
    prepare_to_run_command()方法可以被用来做一些针对某个具体子命令的初始化工作,它将在该子命令被执行之前调用.clean_up()方法会在具体某个子
命令完成后被调用,用来进行一些清理工作.
    具体某个子命令的实现通过继承cliff.command.Command来完成:
import logging

from cliff.command import Command

class Simple(Command):
    "A simple command that prints a message."

    log = logging.getLogger(__name__)

    def take_action(self, parsed_args):
        self.log.info('sending greeting')
        self.log.debug('debugging')
        self.app.stdout.write('hi!\n')

子命令的实际工作由take_action()完成.这个例子里,simple子命令向标准输出打印一个字符串,它的实现代码由cliff.commandmanager.CommandManager通过Setuptools entry points来载入:
from setuptools import setup, find_packages

if __name__ == '__main__':
    if __name__ == '__main__':
        setup(
            name='cliffdemo',
            version='0.1',
            install_requires=['cliff'],
            namespace_packages=[],
            packages=find_packages(),
            entry_points={
                'console_scripts':[
                    'cliffdemo = cliffdemo.main:main'
                ],
                'cliff.demo':[
                    'simple = cliffdemo.simple:Simple'
                ],
            },
)
在Setup tools entry points的命名空间cliff.demo中,定义了命令simple所对应的插件实现是Simple类.Cliff主程序解析用户输入后,会通过这里所定义
的对应关系调用不同的实现类.
simple命令的执结果为:

$ cliffdemo simple

sending greeting

hi!

bogon:oslo_tutorials zhangzhangxiaoan$ cliffdemo -v simple

initialize_app

prepare_to_run_commandSimple

sending greeting

debugging

hi!

clean_up Simple

bogon:oslo_tutorials zhangzhangxiaoan$ 


oslo.config
    oslo.config库用于解析命令行和配置文件中的配置选项,是oslo-incubator中最早毕业的一个.
    oslo.config的代码库位于https://github.com/openstack/oslo.config,项目主页为https://launchpad.net/oslo.config,参考文档在http://docs.openstack.org/developer/oslo.config/.
    这里通过以下几个应用场景来介绍oslo.config的用法:
定义和注册配置选项
#file: service.py

import socket
import os

from oslo_config import cfg

OPTS = [
    cfg.StrOpt('host',
               default=socket.gethostname(),
               help='Name of this node'),
    cfg.IntOpt('collector_workers',
               default=1,
               help='Number of workers for collector service.')
]

cfg.CONF.register_opts(OPTS)

CLI_OPTS = [
    cfg.StrOpt('os-tenant-id',
               deprecated_group='DEFAULT',
               default=os.environ.get('OS_TENANT_ID', ''),
               help='Tenant ID to use for OpenStack service access.'),
    cfg.IntOpt('insecure',
               default=False,
               help='Disable X.509 certificate validation when an '
                    'SSL connection to Identify Service is established.'),
]

cfg.CONF.register_cli_opts(CLI_OPTS,
                           group='service_credentials')


配置选项有不同的类型,目前所支持的如表4-4所求.
类  名说 明
oslo.config.cfg.StrOpt字符串类型
oslo.config.cfg.BoolOpt布尔型
oslo.config.cfg.IntOpt整数类型
oslo.config.cfg.FloatOpt浮点数类型
oslo.config.cfg.ListOpt字符串列表类型
oslo.config.cfg.DictOpt字典类型,字典中的值需要是字符串类型
oslo.config.cfg.MultiStrOpt可以分多次配置的字符串列表
oslo.config.cfg.IPOptIP地址类型

定义后的配置选项,必须要注册后才能使用.此外,配置选项还可以注册为命令行选项,之后,这些配置选项的值就可以从命令行读取,并覆盖从配置文件中读取的值.

注册配置选项时,可以把某些配置选项注册在一个特定的组下.这样可以帮助管理员更好地组织配置选项文件.如果没有指定,默认的组是'DEFAULT'.

在新版本的oslo.config中(版本号>=1.3.0),增加了另一种新的定义配置选项的方式:

from oslo_config import cfg
from oslo_config import types

PortType = types.Integer(1, 65535)

common_opts = [
    cfg.Opt('bind_port',
            type=PortType(),
            default=9292,
            help="Port number to listen on.")
]

相比于前面的方法,这种定义配置选项的方式能够更好地支持选项值的合法性检查,同时也能支持自定义选项类型.因此,建议新的项目采用这种方式定义配置选项.但由于目前大部分OpenStack项目还是在采用旧方式,为了读者理解代码的方便,这里我们仍然采用旧方式.


使用配置文件和命令行选项指定配置选项

为了正确使用oslo.config,应用程序一般需要在启动的时候初始化,比如:

import sys

from oslo_config import cfg

cfg.CONF(sys.argv[1:], project='xyz')
初始化后,才能正常解析配置文件和命令行选项.最终用户可以用默认的命令行选项"--config-file"或者"--config-dir"来指定配置文件名或者位置.如果没有明确指定,默认按下面的顺序寻找配置文件:

~/.xyz/xyz.conf ~/xyz.conf /etc/xyz/xyz.conf /etc/xyz.conf

配置文件一般采用类似.ini文件的格式,其中每一个section对应oslo.confi中定义的一个配置选项组,section[DEFAULT]对应了默认组"DEFAULT".比如:

[DEFAULT]

host = 1.1.1.1

collector_workers = 3

[service_credentials]

secure = TRUE

用命令行指定配置选项值时,如果是定义在某个选项组中的选项,命令行选项名中需要包括该组名作为前缀:

--service_credentials-os-tenant-id ab23ef67


使用其他模块已经注册过的配置选项

对于已经注册过的配置选项,开发者可以直接访问:

from oslo_config import cfg
import service

hostname = cfg.CONF.host
tenant_id = cfg.CONF.service_credentials.os-tenant-id
这里导入service模块是因为选项host和os-tenant-id是在service模块中注册的.但是从编码风格来看,上述代码比较古怪,我们导入了service模块却从未直接使用它.所以,我们也可以使用import_opt来申明在别的模块中定义的配置选项:

from oslo_config import cfg
import service

cfg.CONF.import_opt('host', 'service')
hostname = cfg.CONF.host


oslo.db

oslo.db是针对SQLAlchemy访问的抽象.代码库位于https://github.com/openstack/oslo.db.项目主页为https://bugs.launchpad.net/oslo,参考文档在http://docs.openstack.org/developer/oslo.db.

这里我们通过几个不同的使用范例来了解oslo.db中主要接口的使用方法.

获取SQLAlchemy的engine和session对象实例

from oslo_config import cfg
from oslo_db.sqlalchemy import session as db_session


_FACADE = None

def _create_facade_lazily():
    global _FACADE
    if _FACADE is None:
        _FACADE = db_session.EngineFacade.from_config(cfg.CONF)
    return _FACADE

def get_engine():
    return _create_facade_lazily()

def get_session(**kwargs):
    return _create_facade_lazily().get_session(**kwargs)

获取了engine和session对象实例后,开发者就可以按照一般访问SQLAlchemy的方式进行使用,这里的engine对象是共享的,同时也是线程安全的,可以等效成一组数据库连接.而session对象可以看做是一个数据库交易事务的上下文,它不是线程安全的,不应该被共享使用.

管理员可以通过配置文件来配置oslo.db的许多选项,比如:

[database]

connection = mysql://root:123456@localhost/ceilometer?charset=utf8

常用的配置选项如下表所示(具体参见oslo/db/options.py)

配置项 = 默认值说  明
backend = sqlalchemy(字符串类型)后台数据库标识
connection = None(字符串类型)sqlalchemy用此来连接数据库
connection_debug = 0(整型)sqlalchemy的debug等级,0表示不输出任何调试信息,100表示输出所有调试信息
connection_trace = False(布尔型)是否把python的调用栈信息加到SQL的注释中
db_inc_retry_interval = True(布尔型)连接重试时,是否增加重试之间的时间间隔
db_max_retries = 20(整型)连接重试的最多次数(-1表示一直重试)
db_retry_interval = 1(整型)连接重试时间间隔,单位为秒
idle_timeout = 3600(整型)连接被回收之前的空闲时间,单位为秒
max_overflow = None(整型)如果设置了,这个参数会被直接传递给sqlalchemy
max_pool_size = None(整型)在一个连接池中,最大可同时打开的连接数
max_retires = 10(整型)打开连接时最大重试次数(-1表示一直重试)
retry_interval = 10(整型)打开连接时重试的时间间隔

使用OpenStack中通用的SQLAlchemy model类

from oslo_db.sqlalchemy import models

class ProjectSomething(models.TimestampMixin, models.ModelBase):
    id = Column(Interger, primary_key=True)
    ...
 

oslo.db.models模块目前只定义了两种Mixin: TimestampMixin和SoftDeleteMixin.使用TimestampMixin时SQLAlchemy model中会多出两列create_at和updated_at,分别表示记录的创建时间和上一次个性时间.

SoftDeleteMixin支持使用soft delete功能,比如:

from oslo_db.sqlalchemy import models

class BarModel(models.SoftDeleteMixin, models.ModelBase):
    id = Column(Integer, primary_key=True)
    ...
    
...
count = model_query(BarModel).find(some_condition).soft_delete()
if count == 0:
    raise Exception("0 entries were soft deleted")

不同DB后端的支持

from oslo_config import cfg
from oslo_db import api as db_api


_BACKEND_MAPPING = {'sqlalchemy': 'project.db.sqlalchemy.api'}

IMPL = db_api.DBAPI.from_config(cfg.CONF,
                                backend_mapping=_BACKEND_MAPPING)

def get_engine():
    return IMPL.get_engine()

def get_session():
    return IMPL.get_session()

def do_somethin(somethind_id):
    return IMPL.do_something(somethind_id)

不同backend具体实现时,需要定义如下函数返回具体DB API的实现类:

def get_backend():
    return MyImplementationClass


oslo.i18n

oslo.i18n是对python gettext模块的封装,主要用于字符串的翻译和国际化.代码库们于https://github.com/openstack/oslo.i18n,项目主页为https://launchpad.net/oslo.i18n,参考文档在

http://docs.openstack.org/developer/oslo.i18n/.

使用oslo.i18n前,需要先创建一个如下的集成模块:

import oslo_i18n

_translators = oslo_i18n.TranslatorFactory(domain='myapp')

#主要的翻译函数,类似gettext中的"_"函数
_ = _translators.primary

#不同log level对应的翻译函数
_LI = _translators.log_info
_LW = _translators.log_warning
_LE = _translators.log_error
_LC = _translators.log_critical

之后在程序中就可以比较容易使用:

from .itegera import _, _LW, _LE

LOG.warn(_LW('warning message: %s'), var)
... 

try:
    ...
except Exception:
    LOG.exception(_LE('There was an error.'))
    
...

raise ValueError(_('error: v1=%(v1)s v2=%(v2)s') % {'v1': v1, 'v2': v2})

版权声明:本文为博主原创文章,未经博主允许不得转载。

openstack的公共库(oslo)的使用

声明: 本博客欢迎转载,但请保留原作者信息! 作者:柯晓东 团队:华为杭州OpenStack团队 为了降低代码冗余度,openstack社区开发了很多公共库。通过这些公共库,可以很容易弄出一个完善...
  • canxinghen
  • canxinghen
  • 2016年06月19日 11:37
  • 6101

OpenStack企业私有云实践(笔记)

2.OpenStack-架构概述、实验环境准 文件存储分3种:文件   块   对象 所有服务中nova占比最大 linux安装建议:  内存2G/网卡桥接/英文,基本安装,所有分区分配 ...
  • yxwmzouzou
  • yxwmzouzou
  • 2015年01月10日 22:12
  • 11537

[OpenStack] Nova中的线程模型

1) greenlet - python中的协程库 1.1) 什么是协程 (Coroutine) Coroutine Wiki http://en.wikipedia.org/wiki/Cor...
  • zhaoeryi
  • zhaoeryi
  • 2014年08月27日 16:57
  • 2114

Openstack基础之oslo.messaging

oslo.messaging   oslo.messaging库为OpenStack各个项目使用RPC和事件通知(Event Notification)提供了一套统一的接口.代码库位于https://...
  • happyAnger6
  • happyAnger6
  • 2017年01月30日 11:49
  • 1251

OpenStack开发基础-oslo.config

The cfg Modulecfg Module来自于OpenStack中的重要的基础组件oslo.config,通过cfg Module可以用来通过命令行或者是配置文件来配置一些options,对于...
  • zhangyifei216
  • zhangyifei216
  • 2015年12月30日 15:32
  • 1222

openstack源码阅读笔记2 配置与oslo_config

Openstack提取了一些在多数项目中经常使用的功能,做成了共公的包,这些公共包同样在nova中的各个服务模块大量使用。这此公共包都以oslo开头。 在分析具体代码之前,必须先熟悉这些公共包,以下先...
  • ATsuwu
  • ATsuwu
  • 2016年08月30日 00:10
  • 1064

openstack的公共库(oslo)的使用

作者:柯晓东 团队:华为杭州OpenStack团队 为了降低代码冗余度,openstack社区开发了很多公共库。通过这些公共库,可以很容易弄出一个完善鉴权、分布式、易配置、带调用链日志...
  • u011956172
  • u011956172
  • 2016年11月01日 18:18
  • 875

OpenStack的oslo_messaging组件使用

首先给出OpenStack的RPC通信的代码调用架构。 OpenStack消息通信架构图         OpenStack层封装call和cast接口用于远程调用RPC的server上的方法,...
  • gj19890923
  • gj19890923
  • 2015年12月12日 22:29
  • 4418

Openstack_通用模块_Oslo_vmware 创建 vSS PortGroup

目录目录 vSS vSSPG vSphere SDK 中相关的网络对象 创建 vSS PortGroupvSS & vSSPGvSS(Standard vSwitch 标准交换机) 为在同一 ESX...
  • Jmilk
  • Jmilk
  • 2017年04月25日 23:52
  • 824

为什么openstack中的oslo模块总喜欢发生代码冲突? (by quqi99)

作者:张华  发表于:2014-02-02版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本版权声明(http://blog.csdn.net/quqi99 )      ...
  • quqi99
  • quqi99
  • 2015年02月02日 14:50
  • 6092
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Openstack基础之oslo(一)
举报原因:
原因补充:

(最多只允许输入30个字)