• novaclient源码分析


    源码版本: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_1client,这个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[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属性,关于装饰器,可以参考文档:
            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类的继承关系图:

    image001

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

    novaclient/base.py

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

      由此形成了如下的关联: 

    image

     

    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流程的主干分析。身份认证的具体流程可以参考: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.clientHTTPClient对象。

    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

  • 相关阅读:
    【HNOI2015】落忆枫音
    【HNOI2015】菜肴制作
    【HNOI2015】亚瑟王
    php内置函数分析之strrev()
    php内置函数分析之ucwords()
    php内置函数分析之strtoupper()、strtolower()
    php内置函数分析之ucfirst()、lcfirst()
    php内置函数分析之trim()
    nginx安装配置目录
    nginx 正向代理
  • 原文地址:https://www.cnblogs.com/littlebugfish/p/4003512.html
Copyright © 2020-2023  润新知