Cinder-client 开发分析

一、简介

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


Cinder-client 开发分析_第1张图片
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] 

Show volume storage details.

Positional arguments:
        Name or ID of the storage.

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

输出结果:


Cinder-client 开发分析_第2张图片
image.png

1.入口

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

@utils.arg('id',
           metavar='',
           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

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


Cinder-client 开发分析_第3张图片
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这个资源的操作管理类,可定义增删改查等方法。

Cinder-client 开发分析_第4张图片
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组件调用。比如:


Cinder-client 开发分析_第5张图片
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

你可能感兴趣的:(Cinder-client 开发分析)