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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM