Openstack基础之oslo(一)

    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})

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

self-motivation

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值