您的位置:首页 > 其它

novaclient源码分析

2015-07-21 10:07 369 查看
源码版本: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 sysfrom novaclient.shell import mainif __name__ == "__main__":    sys.exit(main())


novaclient/shell.py

def main():    try:        OpenStackComputeShell().main(map(strutils.safe_decode, sys.argv[1:]))    ...


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)【1】    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)【2】    ...


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,            '2': shell_v1_1,            '3': 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属性,关于装饰器,可以参考文档:        /article/5189469.html        /article/5268067.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指代【4】处创建的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流程的主干分析。身份认证的具体流程可以参考:/article/5326451.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)


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: