一、简介
openstack的各个模块中,都有相应的客户端模块实现,其作用是为用户访问具体模块提供了接口,并且也作为模块之间相互访问的途径。Cinder也一样,有着自己的cinder-client。
二、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.
输出结果:
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
用于输出对象列表,输出格式如下,类似于常见的表格,对象的属性名作为标题。
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这个资源的操作管理类,可定义增删改查等方法。
比如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组件调用。比如:
四、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