天天看點

cinderclient源碼解析之二

感謝朋友支援本部落格,歡迎共同探讨交流,由于能力和時間有限,錯誤之處在所難免,歡迎指正!

如果轉載,請保留作者資訊。

部落格位址:http://blog.csdn.net/gaoxingnengjisuan

郵箱位址:[email protected]

我們接續上一片部落格,來繼續解析cinderclient的源代碼。

上篇部落格中說到在/python-cinderclient/cinderclient/shell.py----mian方法的最後,執行了語句args.func(self.cs, args),實作根據指令行參數的解析調用具體的方法,輸出示例為args.func = do_list,說明當執行指令行cinder list時,這裡調用的方法為do_list。

具體來看代碼,/python-cinderclient/cinderclient/v1/shell.py----do_list(cs, args):

@utils.service_type('volume')
def do_list(cs, args):
    """
    List all the volumes.
    實作列出所有的卷操作;
    """
    all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants))
    search_opts = {
        'all_tenants': all_tenants,
        'display_name': args.display_name,
        'status': args.status,
        'metadata': _extract_metadata(args) if args.metadata else None,
    }
    volumes = cs.volumes.list(search_opts=search_opts)
    _translate_volume_keys(volumes)

    # Create a list of servers to which the volume is attached
    for vol in volumes:
        servers = [s.get('server_id') for s in vol.attachments]
        setattr(vol, 'attached_to', ','.join(map(str, servers)))
    utils.print_list(volumes, ['ID', 'Status', 'Display Name',
                     'Size', 'Volume Type', 'Bootable', 'Attached to'])
           

首先來看語句volumes = cs.volumes.list(search_opts=search_opts),其中cs已經定位了類/python-cinderclient/cinderclient/v1/client.py----class Client(object),在其類的初始化方法中我們可以看到變量self.volumes = volumes.VolumeManager(self),定位到類/python-cinderclient/cinderclient/v1/volumes.py----class VolumeManager(base.ManagerWithFind),進而可以知道,語句volumes = cs.volumes.list(search_opts=search_opts)所實作調用的方法為/python-cinderclient/cinderclient/v1/volumes.py----class VolumeManager(base.ManagerWithFind)----def list(self, detailed=True, search_opts=None),我們來看方法list的具體代碼:

def list(self, detailed=True, search_opts=None):
        """
        ***********************************************
        Get a list of all volumes.
        擷取包含所有卷的清單;

        :rtype: list of :class:`Volume`
        """
        if search_opts is None:
            search_opts = {}

        qparams = {}

        for opt, val in six.iteritems(search_opts):
            if val:
                qparams[opt] = val

        query_string = "?%s" % urlencode(qparams) if qparams else ""

        detail = ""
        if detailed:
            detail = "/detail"
            
        # /cinderclient/base.py----class Manager(utils.HookableMixin):
        # def _list(self, url, response_key, obj_class=None, body=None):
        return self._list("/volumes%s%s" % (detail, query_string),
                          "volumes")
           

在這個方法中,前面都是進行的一些列參數解析操作,重點來看語句:

return self._list("/volumes%s%s" % (detail, query_string),
                          "volumes")
           

這裡調用了方法/python-cinderclient/cinderclient/base.py----class Manager(utils.HookableMixin)----def _list(self, url, response_key, obj_class=None, body=None),具體來看代碼:

def _list(self, url, response_key, obj_class=None, body=None):
        resp = None
        if body:
            resp, body = self.api.client.post(url, body=body)
        else:
            resp, body = self.api.client.get(url)

        if obj_class is None:
            obj_class = self.resource_class

        data = body[response_key]
        # NOTE(ja): keystone returns values as list as {'values': [ ... ]}
        #           unlike other services which just return the list...
        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]
           

這裡來看代碼:

        if body:

            resp, body = self.api.client.post(url, body=body)

        else:

            resp, body = self.api.client.get(url)

對于resp, body = self.api.client.post(url, body=body),這裡調用了方法/python-cinderclient/cinderclient/client.py----class HTTPClient(object)----def post(self, url, **kwargs),詳細代碼如下:

def post(self, url, **kwargs):
        return self._cs_request(url, 'POST', **kwargs)
           

對于resp, body = self.api.client.get(url),這裡調用了方法/python-cinderclient/cinderclient/client.py----class HTTPClient(object)----def get(self, url, **kwargs):,詳細代碼如下:

def get(self, url, **kwargs):
        return self._cs_request(url, 'GET', **kwargs)
           

可見,在post方法和get方法中都進一步調用了方法_cs_request,并且對應的傳入可參數POST或GET,具體來看方法/python-cinderclient/cinderclient/client.py----class HTTPClient(object)----def _cs_request(self, url, method, **kwargs)的實作:

def _cs_request(self, url, method, **kwargs):
        auth_attempts = 0
        attempts = 0
        backoff = 1
        while True:
            attempts += 1
            if not self.management_url or not self.auth_token:
                self.authenticate()
            kwargs.setdefault('headers', {})['X-Auth-Token'] = self.auth_token
            if self.projectid:
                kwargs['headers']['X-Auth-Project-Id'] = self.projectid
            try:
                resp, body = self.request(self.management_url + url, method, **kwargs)
                return resp, body
            except exceptions.BadRequest as e:
                if attempts > self.retries:
                    raise
            except exceptions.Unauthorized:
                if auth_attempts > 0:
                    raise
                self._logger.debug("Unauthorized, reauthenticating.")
                self.management_url = self.auth_token = None
                # First reauth. Discount this attempt.
                attempts -= 1
                auth_attempts += 1
                continue
            except exceptions.ClientException as e:
                if attempts > self.retries:
                    raise
                if 500 <= e.code <= 599:
                    pass
                else:
                    raise
            except requests.exceptions.ConnectionError as e:
                # Catch a connection refused from requests.request
                self._logger.debug("Connection refused: %s" % e)
                msg = 'Unable to establish connection: %s' % e
                raise exceptions.ConnectionError(msg)
            self._logger.debug(
                "Failed attempt(%s of %s), retrying in %s seconds" %
                (attempts, self.retries, backoff))
            sleep(backoff)
            backoff *= 2
           

來看最重要的一條語句:

resp, body = self.request(self.management_url + url, method, **kwargs)

這裡調用了方法request,并傳入了相關參數,執行相應的操作,并從伺服器端擷取相應的響應傳回值。有輸出示例如:

self.management_url + url: http://172.21.5.164:8776/v1/55d34f8573ed4ac19379a0d80afca4bf/volumes/detail

method: GET

kwargs: {'headers': {'X-Auth-Project-Id': 'admin', 'User-Agent': 'python-cinderclient', 'Accept': 'application/json', 'X-Auth-Token': u'MIISwwYJKoZIhvcNAQcCoIIStDCCErA......PQ=='}}

具體來看方法/python-cinderclient/cinderclient/client.py----class HTTPClient(object)----def request(self, url, method, **kwargs):

def request(self, url, method, **kwargs):
        kwargs.setdefault('headers', kwargs.get('headers', {}))
        kwargs['headers']['User-Agent'] = self.USER_AGENT
        kwargs['headers']['Accept'] = 'application/json'
        if 'body' in kwargs:
            kwargs['headers']['Content-Type'] = 'application/json'
            kwargs['data'] = json.dumps(kwargs['body'])
            del kwargs['body']

        if self.timeout:
            kwargs.setdefault('timeout', self.timeout)
        self.http_log_req((url, method,), kwargs)
        resp = requests.request(
            method,
            url,
            verify=self.verify_cert,
            **kwargs)
        self.http_log_resp(resp)

        if resp.text:
            try:
                body = json.loads(resp.text)
            except ValueError:
                pass
                body = None
        else:
            body = None

        if resp.status_code >= 400:
            raise exceptions.from_response(resp, body)

        return resp, body
           

來看這裡最重要的一段代碼:

resp = requests.request(

            method,

            url,

            verify=self.verify_cert,

            **kwargs)

來看輸出示例:

method = GET

url = http://172.21.5.164:8776/v1/55d34f8573ed4ac19379a0d80afca4bf/volumes/detail

verify = True

kwargs = {'headers': {'X-Auth-Project-Id': 'admin', 'User-Agent': 'python-cinderclient', 'Accept': 'application/json', 'X-Auth-Token': u'MIISwwYJKoZIhvcNAQcCoIIStDCCErA......PQ=='}}

這裡應用了python中的requests庫,具體調用的方法是/requests/api.py----def request(method, url, **kwargs):

def request(method, url, **kwargs):
    """Constructs and sends a :class:`Request <Request>`.
    Returns :class:`Response <Response>` object.

    :param method: method for the new :class:`Request` object.
    :param url: URL for the new :class:`Request` object.
    :param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
    :param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
    :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
    :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
    :param files: (optional) Dictionary of 'name': file-like-objects (or {'name': ('filename', fileobj)}) for multipart encoding upload.
    :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
    :param timeout: (optional) Float describing the timeout of the request.
    :param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
    :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
    :param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided.
    :param stream: (optional) if ``False``, the response content will be immediately downloaded.
    :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.

    Usage::

      >>> import requests
      >>> req = requests.request('GET', 'http://httpbin.org/get')
      <Response [200]>
    """
    session = sessions.Session()
    return session.request(method=method, url=url, **kwargs)
           

這個庫遵循HTTP協定,實作了通路遠端伺服器,并擷取相應的響應資訊的功能,本文在這裡就不深入展開了。

本文是以指令行cinder list為例,是以從伺服器端擷取相關卷的資訊的傳回值後,會在方法/python-cinderclient/cinderclient/base.py----class Manager(utils.HookableMixin)----def _list(self, url, response_key, obj_class=None, body=None)中進行解析并進行列印輸出,得到卷的清單資訊。

至此,卷的查詢指令cinder list在cinderclient中執行過程分析完成,後面一片部落格我将會簡單總結cinderclient中的源碼結構。

繼續閱讀