Cinder-client 开发分析

一、简介

openstack的各个模块中,都有相应的客户端模块实现,其作用是为用户访问具体模块提供了接口,并且也作为模块之间相互访问的途径。Cinder也一样,有着自己的cinder-client。


image.png

二、argparse

argparse是python用于解析命令行参数和选项的标准模块,作为optparse的一个替代被添加到Python2.7。Cinder-client主要就是调用了argparse这个工具包,在此先介绍下它的使用。
1.使用步骤
① import argparse
② parser = argparse.ArgumentParser()
③ parser.add_argument()
④ parser.parse_args()

解释:首先导入该模块;然后创建一个解析对象;然后向该对象中添加你要关注的命令行参数和选项,每一个add_argument方法对应一个你要关注的参数或选项;最后调用parse_args()方法进行解析;解析成功之后即可使用,下面简单说明一下步骤2和3。

方法 ArgumentParser(prog=None, usage=None,description=None, epilog=None, parents=[],formatter_class=argparse.HelpFormatter, prefix_chars='-',fromfile_prefix_chars=None, argument_default=None,conflict_handler='error', add_help=True)
这些参数都有默认值,当调用parser.print_help()或者运行程序时由于参数不正确(此时python解释器其实也是调用了pring_help()方法)时,会打印这些描述信息,一般只需要传递description参数,如上。

方法add_argument(name or flags...[, action][, nargs][, const][, default][, type][, choices][, required][, help][, metavar][, dest])
其中:
name or flags:命令行参数名或者选项,如上面的address或者-p,--port.其中命令行参数如果没给定,且没有设置defualt,则出错。但是如果是选项的话,则设置为None。
nargs:命令行参数的个数,一般使用通配符表示,其中,'?'表示只用一个,'*'表示0到多个,'+'表示至少一个。
default:默认值。
type:参数的类型,默认是字符串string类型,还有float、int等类型。
help:和ArgumentParser方法中的参数作用相似,出现的场合也一致。
dest:如果提供dest,例如dest="a",那么可以通过args.a访问该参数
action:参数出发的动作
store:保存参数,默认
store_const:保存一个被定义为参数规格一部分的值(常量),而不是一个来自参数解析而来的值。
store_ture/store_false:保存相应的布尔值
append:将值保存在一个列表中。
append_const:将一个定义在参数规格中的值(常量)保存在一个列表中。
`count:参数出现的次数

parser.add_argument("-v", "--verbosity", action="count", default=0, help="increase output verbosity")
version:打印程序版本信息
choice:允许的参数值

2.范例

import argparse


def parse_args():
    description = '''This is a description of this command.
    bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla'''

    parser = argparse.ArgumentParser(description=description)

    help = 'The addresses to connect.'
    parser.add_argument('addresses', nargs='*', help=help)

    help = 'The port to listen on. Default to a random available port.'
    parser.add_argument('-p', '--port', type=int, help=help)

    help = 'The interface to listen on. Default is localhost.'
    parser.add_argument('--iface', help=help, default='localhost')

    help = 'The number of seconds between sending bytes.'
    parser.add_argument('--delay', type=float, help=help, default=.7)

    help = 'The number of bytes to send at a time.'
    parser.add_argument('--bytes', type=int, help=help, default=10)

    args = parser.parse_args()
    return args


if __name__ == '__main__':
    args = parse_args()

    for address in args.addresses:
        print 'The address is : %s .' % address
        print 'The port is : %d.' % args.port
        print 'The interface is : %s.' % args.iface
        print 'The number of seconds between sending bytes : %f' % args.delay
        print 'The number of bytes to send at a time : %d.' % args.bytes

执行结果:

D:\ruijie doc\vmware\testing>python test_parse.py  --help
usage: test_parse.py [-h] [-p PORT] [--iface IFACE] [--delay DELAY]
                     [--bytes BYTES]
                     [addresses [addresses ...]]

This is a description of this command. bla bla bla bla bla bla bla bla bla bla
bla bla bla bla bla

positional arguments:
  addresses             The addresses to connect.

optional arguments:
  -h, --help            show this help message and exit
  -p PORT, --port PORT  The port to listen on. Default to a random available
                        port.
  --iface IFACE         The interface to listen on. Default is localhost.
  --delay DELAY         The number of seconds between sending bytes.
  --bytes BYTES         The number of bytes to send at a time.
D:\ruijie doc\vmware\testing>python test_parse.py --port 10000 --delay 1.2 127.0.0.1 172.16.55.67
The address is : 127.0.0.1 .
The port is : 10000.
The interface is : localhost.
The number of seconds between sending bytes : 1.200000
The number of bytes to send at a time : 10.
The address is : 172.16.55.67 .
The port is : 10000.
The interface is : localhost.
The number of seconds between sending bytes : 1.200000
The number of bytes to send at a time : 10.

三、Cinder-client 指令代码分析

以cinder storage-show指令作为范例分析:

[root@node1 site-packages]# cinder help storage-show
usage: cinder storage-show [--detail] <id>

Show volume storage details.

Positional arguments:
  <id>      Name or ID of the storage.

Optional arguments:
  --detail  Show detailed information about storage.

输出结果:


image.png

1.入口

\cinderclient\v2\shell.py定义了指令的入口函数

@utils.arg('id',
           metavar='<id>',
           help='Name or ID of the storage.')
@utils.arg('--detail',
           action='store_true',
           help='Show detailed information about storage.')
def do_storage_show(cs, args):
    """Show volume storage details."""
    detailed = strutils.bool_from_string(args.detail)
    storage = cs.storages.get(args.id, detailed)
    info = dict()
info.update(storage._info)
# 打印字典,并对'pool_info', 'metadatas'的值字典转换字符串
    utils.print_dict(info, formatters=['pool_info', 'metadatas'])

Cinder storage-show对应shell.py里的do_storage_show函数,同样的,其他指令的定义函数名字格式都是:以do_作为前缀,分隔符-对应分隔符_。
do_storage_show函数的__doc__会成为指令的解释说明。
@utils.arg定义了指令的参数,参数格式可参考parser.add_argument。

cs.storages指向cinderclient.v2.client.Client里定义的self.storages = storages.StoragesManager(self)

Shell.py里有两种输出结果的方式:

print_list

用于输出对象列表,输出格式如下,类似于常见的表格,对象的属性名作为标题。


image.png
def print_list(objs, fields, exclude_unavailable=False, formatters=None, sortby_index=0)

参数:

参数 说明
objs 要打印的对象列表
fields 规定每个对象要打印的字段
exclude_unavailable 决定是否把fields里无效的字段删除,fields里的字段在object里不存在即为无效字段。
formatters 要格式化的字段
sortby_index 输出结果排序字段

print_dict

用于输出字典类型的结果,会把对formatters字段做字典转字符串的格式化。

def print_dict(d, property="Property", formatters=None): 

2.manager

一般,我们将某种资源的操作集合定义在一个文件里,比如storage,我们定义在\cinderclient\v2\storages.py。文件里包含两个class,一个是Storages,是storage这个资源的实体类;另一个是StoragesManager,是storage这个资源的操作管理类,可定义增删改查等方法。

image.png

比如storage = cs.storages.get(args.id, detailed),调用的是

cinderclient.v2.storages.StoragesManager#get:
def get(self, storage_id, detailed=False):
    storage = self._get("/storages/{storage_id}?detail={detailed}".
                        format(storage_id=storage_id, detailed=detailed),
                        'storage')
    return storage

这里调用了cinderclient.base.Manager#_get(self, url, response_key=None)方法,这个方法两个参数,一个是rest url地址,一个是url返回值key。比如url="/storages/{storage_id}?detail={detailed}"的返回值是{u'storage': {...}},那么response_key即’storage’,_get方法会把返回值’storage’的volue转换成资源类Storages对象。

cinderclient.base.Manager是所有资源manager的基类,除了_get,它还定义了其它几种常用基础操作函数:
def _create(self, url, body, response_key, return_raw=False, **kwargs): 用于创建资源
def _delete(self, url): 用于删除资源
def _update(self, url, body, response_key=None, **kwargs): 用于更新资源
def _list(self, url, response_key, obj_class=None, body=None, limit=None, items=None): 用于查询资源列表
def _get(self, url, response_key=None): 用于查询单个资源

范例:

    def list(self, storage_name=None, device_id=None,
             volume_backend_name=None, usage=None, nova_aggregate_id=None,
             status=None, detailed=False, project_ids=None):
        url = '/storages'
        filters = {}
        if storage_name:
            filters['storage_name'] = storage_name
        if device_id:
            filters['device_id'] = device_id
        if volume_backend_name:
            filters['volume_backend_name'] = volume_backend_name
        if usage:
            filters['usage'] = usage
        if nova_aggregate_id:
            filters['nova_aggregate_id'] = nova_aggregate_id
        if status:
            filters['status'] = status
        if detailed is True:
            filters['detailed'] = 'true'

        filters = utils.unicode_key_value_to_string(filters)
        if filters:
            params = sorted(filters.items(), key=lambda x: x[0])
            query_string = "?%s" % parse.urlencode(params)
            url = url + query_string

        storages = self._list(url, 'storages')
        return storages

    def get(self, storage_id, detailed=False):
        storage = self._get("/storages/{storage_id}?detail={detailed}".
                            format(storage_id=storage_id, detailed=detailed),
                            'storage')
        return storage

    def create(self, storage_name, device_id, metadata, usage, nova_aggregate_id):
        body = {
            "storage": {
                "storage_name": storage_name,
                "device_id": device_id,
                "metadatas": metadata,
                "usage": usage,
                "nova_aggregate_id": nova_aggregate_id
            }
        }
        return self._create('/storages', body, 'storage')

    def update(self, storage_id, storage_name=None, device_id=None, metadatas=None):
        body = {
            "storage": {
            }
        }
        if storage_name is not None:
            body['storage']['storage_name'] = storage_name
        if device_id is not None:
            body['storage']['device_id'] = device_id
        if metadatas is not None:
            body['storage']['metadatas'] = metadatas
        return self._update('/storages/%s' % storage_id, body, 'storage')

    def delete(self, storage_id):
        return self._delete("/storages/%s" % storage_id)

定义完资源的manager类,要记得在client.py的Client类里引入,提供给shell.py和其他openstack组件调用。比如:


image.png

四、Openstack其他组件调用cinder-client

Openstack组件之间调用,一般都是通过组件的client提供的接口。比如nova的\nova\nova\volume\cinder.py定义了cinder-api的集合类nova.volume.cinder.API。要查询cinder的volume列表,可调用nova.volume.cinder.API#get_all

    @translate_cinder_exception
    def get_all(self, context, search_opts=None):
        search_opts = search_opts or {}
        items = cinderclient(context).volumes.list(detailed=True,
                                                   search_opts=search_opts)
        rval = []
        for item in items:
            rval.append(_untranslate_volume_summary_view(context, item))

        return rval

可以看到,get_all函数里调用cinderclient(context).volumes.list(detailed=True, search_opts=search_opts),即调用了cinderclient的cinderclient.v2.volumes.VolumeManager#list方法。

当然我们cinder调用nova的方法,也是这样。Cinder有\cinder\compute\nova.py,可通过cinder.compute.nova.API里的方法调用novaclient接口。比如创建卷快照:

    def create_volume_snapshot(self, context, volume_id, create_info):
        nova = novaclient(context, privileged_user=True)
        # pylint: disable=E1101
        nova.assisted_volume_snapshots.create(
            volume_id,
            create_info=create_info)

nova.assisted_volume_snapshots.create即调用novaclient.v2.assisted_volume_snapshots.AssistedSnapshotManager#create

五、cinderclient-ext

安装:pip install python-brick-cinderclient-ext

cinderclient-ext提供了cinder-client的拓展指令。
入口定义在\brick_cinderclient_ext\__init__.py,增加了五个指令:
do_get_connector
do_local_attach
do_local_detach
do_get_volume_paths
do_get_all_volume_paths

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • 以下是本人通过阅读Grizzly版OpenStack源码,整理的简要的Nova模块源码结构,希望和大家相互交流。 ...
    Chenzongshu阅读 2,655评论 0 50
  • 第一章 OpenStack基础 OpenStack管理的资源及提供的服务OpenStack做为一个操作系统,...
    sgt_tiger阅读 12,810评论 4 72
  • •王越:VMware存储API整理,比如核心的存储池与卷两者的CURD。可以参考:VMware in OpenSt...
    笨手笨脚越阅读 5,607评论 0 2
  • cinder RPC 分析 [TOC] 我们都知道在Cinder内部,各组件之间通讯是通过RPC api,比如c...
    笨手笨脚越阅读 1,762评论 0 3
  • 佛山的传说 作者:王智恩 编辑配图:黛馨 位于豫陕交界处的佛山,因秋季漫山都是红灿灿、透亮亮、如火一样燃烧的枫叶而...
    添情阅读 1,036评论 0 1