源码版本:H版
FAULT_OS_COMPUTE_API_VERSION = "1.1"

一、目录结构及概况

novaclient/

|---client.py -------------主要提供HTTPClient类,也提供根据版本创建Client对象的函数

|---base.py   -------------提供基本的Manager基类

|---shell.py  -------------命令解析,创建相应版本的Client类对象,调用相应版本的shell.py中的函数

...

|---v1_1

|---client.py ---------版本Client类,拥有一系列Manager类对象,这些Manager可以调用相应的组件

|---flavors.py --------具体的Manager类,使用HTTPClient对象与对应的组件进行通信

...

|---shell.py   ---------提供每个Command对应的方法

1、client的基本创建

  首先有一个版本v1_1的client,这个client版本里面应该有一个Client类,拥有一堆的Manager负责管理各种资源,只需引用这些Manager就可以操作资源,然后创建一系列的Manager类来负责处理资源,在这些Manager类中主要使用HTTPClient来发送请求对相应的组件进行操作,最后,将client版本能够实现的功能封装成函数,这些函数进而能够被相应的command调用。这样,一个版本的client就写好了,可供外部调用。

2、如何调用?

1)如果Python编程使用版本client的话,可以参考:http://docs.openstack.org/user-guide/content/ch_sdk.html

2)如果创建shell的话,首先需写一个shell.py,创建解析器能够解析版本中shell.py里面给出的方法,然后解析调用,因为各版本中的shell.py里面的方法都是调用Client类的Manager来进行处理的,所以必须先创建一个Client对象传入。

二、以nova flavor-list为例分析源码

  说明:本例中nova脚本安装在/usr/bin目录下,novaclient模块安装在/usr/lib/python2.6/site-packages目录下。下面的文件位置标记中都除掉这些prefix。

  当我们输入nova flavor-list时,先查看nova脚本:

/usr/bin/nova

import sys
from novaclient.shell import main
if __name__ == "__main__":
sys.exit(main()) 

novaclient/shell.py

def main():
try:
OpenStackComputeShell().main(map(strutils.safe_decode, sys.argv[:]))
... 
OpenStackComputeShell类:
def main(self, argv):
...
"""
对命令行参数进行解析,此处用到了argparse的相关知识,
参考文档:https://docs.python.org/2/library/argparse.html?highlight=argparse#module-argparse """
subcommand_parser = self.get_subcommand_parser(
options.os_compute_api_version)【】
self.parser = subcommand_parser
...
args = subcommand_parser.parse_args(argv)
...
"""构造一个Client对象,具体的Client会根据版本创建"""
self.cs = client.Client(options.os_compute_api_version, os_username,
...) 身份认证【3】
...

   """
   由于输入命令行为nova flavor-list,所以经过对参数的解析,args.func实际表示novaclient/v1_1/shell.py中的do_flavor_list函数,调用该函数进行处理
   """
args.func(self.cs, args)【】
...

1、分析【1】处,命令行参数解析

novaclient/shell.py

OpenStackComputeShell类:
def get_subcommand_parser(self, version):
“””获取基本参数解析器,这个不难理解”””
parser = self.get_base_parser() self.subcommands = {}
subparsers = parser.add_subparsers(metavar='<subcommand>') try:
“””此处actions_module=shell_v1_1,而根据from novaclient.v1_1 import shell as shell_v1_1,shell_v1_1表示novaclient/v1_1/shell.py”””
actions_module = {
'1.1': shell_v1_1,
'': shell_v1_1,
'': shell_v3,
}[version]
except KeyError:
actions_module = shell_v1_1 self._find_actions(subparsers, actions_module)
self._find_actions(subparsers, self) for extension in self.extensions:
self._find_actions(subparsers, extension.module) self._add_bash_completion_subparser(subparsers) return parser def _find_actions(self, subparsers, actions_module):
for attr in (a for a in dir(actions_module) if a.startswith('do_')):
“””对novaclient/v1_1/shell.py中的每个do_xxx函数进行处理”””
command = attr[3:].replace('_', '-')
callback = getattr(actions_module, attr)
desc = callback.__doc__ or ''
action_help = desc.strip()
"""
观察novaclient/v1_1/shell.py中的do_xxx函数都使用了装饰器进行处理,而具体的处理就是为函数添加arguments属性,关于装饰器,可以参考文档:
http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html
http://www.cnblogs.com/huxi/archive/2011/03/01/1967600.html
"""
arguments = getattr(callback, 'arguments', []) “””添加子命令解析器”””
subparser = subparsers.add_parser(command,
help=action_help,
description=desc,
add_help=False,
formatter_class=OpenStackHelpFormatter
)
subparser.add_argument('-h', '--help',
action='help',
help=argparse.SUPPRESS,
)
self.subcommands[command] = subparser
for (args, kwargs) in arguments:
subparser.add_argument(*args, **kwargs) “””此处设置了子命令的缺省处理函数,与后面对func的调用相呼应”””
subparser.set_defaults(func=callback) 

2、分析【2】处,版本client对象的使用

novaclient/v1_1/shell.py

def do_flavor_list(cs, args):
"""Print a list of available 'flavors' (sizes of servers)."""
if args.all:
flavors = cs.flavors.list(is_public=None)
else:
flavors = cs.flavors.list()
“””格式化打印获取的flavor信息”””
_print_flavor_list(flavors, args.extra_specs)

  flavors = cs.flavors.list()是一个关键性的调用,具体分析如下:

2.1、首先需要分析cs:

novaclient/client.py

def Client(version, *args, **kwargs):
“””此处version为1.1,所以获取novaclient/v1_1/client.py中的Client类”””
client_class = get_client_class(version)
return client_class(*args, **kwargs)

  综上,这里的cs实际为novaclient/v1_1/client.py中的Client类对象

2.2、然后分析cs.flavors:

novaclient/v1_1/cli

Client类:
def __init__(self, username, api_key, project_id, auth_url=None,
insecure=False, timeout=None, proxy_tenant_id=None,
proxy_token=None, region_name=None,
endpoint_type='publicURL', extensions=None,
service_type='compute', service_name=None,
volume_service_name=None, timings=False,
bypass_url=None, os_cache=False, no_cache=True,
http_log_debug=False, auth_system='keystone',
auth_plugin=None, auth_token=None,
cacert=None, tenant_id=None):
password = api_key
self.projectid = project_id
self.tenant_id = tenant_id
  “””在self上继续绑定了一系列的Manager”””
  self.flavors = flavors.FlavorManager(self)
  ...
  self.client = client.HTTPClient(username,
...
                     cacert=cacert)【4】

下图为FlavorManager类的继承关系图:

  从中可以看出在构造FlavorManager时,调用的构造函数如下:

novaclient/base.py

Manager类:
def __init__(self, api):
  self.api = api

  由此形成了如下的关联:

2.3、最后分析list函数:

novaclient/v1_1/flavors.py

FlavorManager类:
def list(self, detailed=True, is_public=True):
...
“””此处为self._list(“/flavors/detail”,"flavors")”””
return self._list("/flavors%s%s" % (detail, query_string), "flavors")

由于继承关系:

novaclient/base.py

Manager类:
def _list(self, url, response_key, obj_class=None, body=None):
if body:
_resp, body = self.api.client.post(url, body=body)
else:
“””这里的client指代【】处创建的HTTPClient对象”””
_resp, body = self.api.client.get(url) if obj_class is None:
obj_class = self.resource_class data = body[response_key]
if isinstance(data, dict):
try:
data = data['values']
except KeyError:
pass with self.completion_cache('human_id', obj_class, mode="w"):
with self.completion_cache('uuid', obj_class, mode="w"):
return [obj_class(self, res, loaded=True)
for res in data if res]

novaclient/client.py

HTTPClient类:
def get(self, url, **kwargs):
return self._cs_request(url, 'GET', **kwargs) def _cs_request(self, url, method, **kwargs):
if not self.management_url:
self.authenticate() try:
kwargs.setdefault('headers', {})['X-Auth-Token'] = self.auth_token
if self.projectid:
kwargs['headers']['X-Auth-Project-Id'] = self.projectid resp, body = self._time_request(self.management_url + url, method,
**kwargs)
return resp, body
“””有可能出现没有认证的情况,需要先认证再发送请求”””
except exceptions.Unauthorized as e:
... def _time_request(self, url, method, **kwargs):
start_time = time.time()
resp, body = self.request(url, method, **kwargs)
self.times.append(("%s %s" % (method, url),
start_time, time.time()))
return resp, body def request(self, url, method, **kwargs):
“””构造请求报文参数”””
...
“””这里使用了第三方的requests库,self.http=requests.Session()”””
resp = self.http.request(
method,
url,
**kwargs)
self.http_log_resp(resp) if resp.text:
if resp.status_code == 400:
if ('Connection refused' in resp.text or
'actively refused' in resp.text):
raise exceptions.ConnectionRefused(resp.text)
try:
body = json.loads(resp.text)
except ValueError:
body = None
else:
body = None
“””根据请求返回的结果决定是否抛出异常”””
if resp.status_code >= 400:
raise exceptions.from_response(resp, body, url, method) return resp, body

3、分析【3】处,身份认证

  说明:将这一部分放在最后分析主要是为了不影响对整个client流程的主干分析。身份认证的具体流程可以参考:http://www.cnblogs.com/littlebugfish/p/4027061.html

身份认证的主要代码如下:

try:
# This does a couple of bits which are useful even if we've
# got the token + service URL already. It exits fast in that case.
“””检查args.func是否不需要认证”””
if not cliutils.isunauthenticated(args.func):
self.cs.authenticate()
except exc.Unauthorized:
raise exc.CommandError(_("Invalid OpenStack Nova credentials."))
except exc.AuthorizationFailure:
raise exc.CommandError(_("Unable to authorize user"))

novaclient/v1_1/client.py

Client类:
def authenticate(self):
  ...
  self.client.authenticate()

由之前的分析可知,self.client为HTTPClient对象。

novaclient/client.py

HTTPClient类:
def authenticate(self):
...
if self.version == "v2.0": # FIXME(chris): This should be better.
while auth_url:
if not self.auth_system or self.auth_system == 'keystone':
auth_url = self._v2_auth(auth_url)
else:
auth_url = self._plugin_auth(auth_url)
...
“””存储认证结果获取的信息”””
self._save_keys() def _v2_auth(self, url):
...
return self._authenticate(url, body) def _authenticate(self, url, body, **kwargs):
"""Authenticate and extract the service catalog."""
method = "POST"
token_url = url + "/tokens" # Make sure we follow redirects when trying to reach Keystone
“””_time_request函数的具体解释见上面”””
resp, respbody = self._time_request(
token_url,
method,
body=body,
allow_redirects=True,
**kwargs)
“””获取认证结果信息”””
return self._extract_service_catalog(url, resp, respbody)

参考文档:

http://www.tuicool.com/articles/32muqe

novaclient源码分析的更多相关文章

  1. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  2. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  3. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  4. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  5. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  6. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  7. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  8. ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...

  9. ABP源码分析三:ABP Module

    Abp是一种基于模块化设计的思想构建的.开发人员可以将自定义的功能以模块(module)的形式集成到ABP中.具体的功能都可以设计成一个单独的Module.Abp底层框架提供便捷的方法集成每个Modu ...

随机推荐

  1. [shell] awk学习

    awk处理最后一行 awk '{if(NR>1)print a;a=$0}END{print a="b"}' file awk 'BEGIN{getline a}{print ...

  2. 4个数的和为0 51nod 1267

    给出N个整数,你来判断一下是否能够选出4个数,他们的和为0,可以则输出"Yes",否则输出"No". Input 第1行,1个数N,N为数组的长度(4 < ...

  3. LeetCode 206. Reverse Linked List(C++)

    题目: Reverse a singly linked list. Example: Input: 1->2->3->4->5->NULL Output: 5->4 ...

  4. (十三)Jmeter之Bean Shell 的使用(二)

    该文章来自:http://www.cnblogs.com/puresoul/p/5092628.html 上一篇Jmeter之Bean shell使用(一)简单介绍了下Jmeter中的Bean she ...

  5. Oracle数据库表空间常用操作

    1. 查看所有表空间大小 SQL> select tablespace_name,sum(bytes)/1024/1024 from dba_data_files group by tables ...

  6. PHP之array_flip()方法

    array_flip — 交换数组中的键和值 array array_flip ( array $trans ) array_flip() 返回一个反转后的 array,例如 trans 中的键名变成 ...

  7. Android四大组件之Service(续2)

    1.HttpRequest package com.joyfulmath.android4example.http; import java.io.BufferedReader; import jav ...

  8. 【HLSDK系列】服务端实体 edict_t 和 控制类

    我们来了解一下引擎是怎么管理实体的吧!我们这里就说说服务端的实体(edict_t) 服务端用 edict_t 这个结构体来保存一个实体,可以说一个 edict_t 就是一个 服务端实体,下文简称实体. ...

  9. C++解析(28):异常处理

    0.目录 1.C语言异常处理 2.C++中的异常处理 3.小结 1.C语言异常处理 异常的概念: 程序在运行过程中可能产生异常 异常(Exception)与 Bug 的区别 异常是程序运行时可预料的执 ...

  10. Remember the Word UVALive - 3942(dp+trie)

    题意: 给S个不同的单词和一个长字符串 问将其分解为若干个单词有多少种方法(单词可重复使用) 解析: dp[i]表示在这个字符串中以某个位置i为起点的 的一段子字符串 则这个子字符串若存在某个前缀恰好 ...