Python傳送請求代token

lm_y發表於2017-08-22

在請求neutronserver時,需要先建立neutronclient,再通過neutronclient訪問neutron server。例如:dash board訪問neutron server 時,需要在horirzon/openstack_dashboard/api/neutron.py檔案中建立neutronclient.

例如在list router時,函式如下:

def router_list(request, **params):
    routers = neutronclient(request).list_routers(**params).get('routers')
    return [Router(r) for r in routers]

函式呼叫neutronclient的list_routers函式。

neutronclient定義如下:

def neutronclient(request):
    insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
    cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
    c = neutron_client.Client(token=request.user.token.id,
                              auth_url=base.url_for(request, 'identity'),
                              endpoint_url=base.url_for(request, 'network'),
                              insecure=insecure, ca_cert=cacert)
    return c

from neutronclient.v2_0 import client as neutron_client
因為當前用的是2.0版本,所以在v2_0中建立的neutronclient。建立時帶入了token id。

在neutronclient/v2_0/client.py中 client定義如下

class Client(ClientBase):
    def __init__(self, **kwargs):
        """Initialize a new client for the Neutron v2.0 API."""
        super(Client, self).__init__(**kwargs)
        self._register_extensions(self.version)
client繼承ClientBase,在初始化時首先呼叫父類的建構函式。
class ClientBase(object):
    def __init__(self, **kwargs):
        """Initialize a new client for the Neutron v2.0 API."""
        super(ClientBase, self).__init__()
        self.retries = kwargs.pop('retries', 0)
        self.raise_errors = kwargs.pop('raise_errors', True)
        self.httpclient = client.construct_http_client(**kwargs)
        self.version = '2.0'
        self.format = 'json'
        self.action_prefix = "/v%s" % (self.version)
        self.retry_interval = 1
在父類的建構函式中,要建立httpclient,然後通過httpclient去訪問api.
def construct_http_client(username=None,
                          user_id=None,
                          tenant_name=None,
                          tenant_id=None,
                          password=None,
                          auth_url=None,
                          token=None,
                          region_name=None,
                          timeout=None,
                          endpoint_url=None,
                          insecure=False,
                          endpoint_type='publicURL',
                          log_credentials=None,
                          auth_strategy='keystone',
                          ca_cert=None,
                          service_type='network',
                          session=None,
                          **kwargs):

    if session:
        kwargs.setdefault('user_agent', 'python-neutronclient')
        kwargs.setdefault('interface', endpoint_type)
        return SessionClient(session=session,
                             service_type=service_type,
                             region_name=region_name,
                             **kwargs)
    else:
        # FIXME(bklei): username and password are now optional. Need
        # to test that they were provided in this mode.  Should also
        # refactor to use kwargs.
        return HTTPClient(username=username,
                          password=password,
                          tenant_id=tenant_id,
                          tenant_name=tenant_name,
                          user_id=user_id,
                          auth_url=auth_url,
                          token=token,
                          endpoint_url=endpoint_url,
                          insecure=insecure,
                          timeout=timeout,
                          region_name=region_name,
                          endpoint_type=endpoint_type,
                          service_type=service_type,
                          ca_cert=ca_cert,
                          log_credentials=log_credentials,
                          auth_strategy=auth_strategy)

construct_http_client函式中,通過是否有session來決定建立SessionClient還是HTTPClient.
所以建立的neutronclient的httpclient實際是SessionClient或者HTTPClient。
通過日誌檢視都是SessionClient,大概是因為neutron服務一直在執行中吧,session已經存在。第一次啟動是HTTPClient。
neutronclient的list_routers函式如下:
def list_routers(self, retrieve_all=True, **_params):
    """Fetches a list of all routers for a tenant."""
    # Pass filters in "params" argument to do_request
    return self.list('routers', self.routers_path, retrieve_all,
                     **_params)
list函式如下:
def list(self, collection, path, retrieve_all=True, **params):
    if retrieve_all:
        res = []
        for r in self._pagination(collection, path, **params):
            res.extend(r[collection])
        return {collection: res}
    else:
        return self._pagination(collection, path, **params)
_pagination函式如下:
def _pagination(self, collection, path, **params):
    if params.get('page_reverse', False):
        linkrel = 'previous'
    else:
        linkrel = 'next'
    next = True
    while next:
        res = self.get(path, params=params)
        yield res
        next = False
        try:
            for link in res['%s_links' % collection]:
                if link['rel'] == linkrel:
                    query_str = urlparse.urlparse(link['href']).query
                    params = urlparse.parse_qs(query_str)
                    next = True
                    break
        except KeyError:
            break

self.get函式如下:

def get(self, action, body=None, headers=None, params=None):
    _logger.info("headers is %s,body is %s"%(headers,body))
    return self.retry_request("GET", action, body=body,
                              headers=headers, params=params)

retry_request函式如下:

def retry_request(self, method, action, body=None,
                  headers=None, params=None):

    max_attempts = self.retries + 1
    for i in range(max_attempts):
        try:
            return self.do_request(method, action, body=body,
                                   headers=headers, params=params)
        except exceptions.ConnectionFailed:
            # Exception has already been logged by do_request()
            if i < self.retries:
                _logger.debug('Retrying connection to Neutron service')
                time.sleep(self.retry_interval)
            elif self.raise_errors:
                raise

    if self.retries:
        msg = (_("Failed to connect to Neutron server after %d attempts")
               % max_attempts)
    else:
        msg = _("Failed to connect Neutron server")

    raise exceptions.ConnectionFailed(reason=msg)

do_request函式如下:

def do_request(self, method, action, body=None, headers=None, params=None):
    # Add format and tenant_id
    action += ".%s" % self.format
    action = self.action_prefix + action
    if type(params) is dict and params:
        params = utils.safe_encode_dict(params)
        action += '?' + urlparse.urlencode(params, doseq=1)

    if body:
        body = self.serialize(body)

    resp, replybody = self.httpclient.do_request(
        action, method, body=body,
        content_type=self.content_type())

    status_code = resp.status_code
    if status_code in (requests.codes.ok,
                       requests.codes.created,
                       requests.codes.accepted,
                       requests.codes.no_content):
        return self.deserialize(replybody, status_code)
    else:
        if not replybody:
            replybody = resp.reason
        self._handle_fault_response(status_code, replybody)

在do_request裡,呼叫了httpclient.do_request函式。在BaseClient中構造了httpclient,do_request函式如下:

def do_request(self, url, method, **kwargs):
    # Ensure client always has correct uri - do not guesstimate anything
    self.authenticate_and_fetch_endpoint_url()
    self._check_uri_length(url)

    try:
        kwargs.setdefault('headers', {})
        if self.auth_token is None:
            self.auth_token = ""
        kwargs['headers']['X-Auth-Token'] = self.auth_token
        resp, body = self._cs_request(self.endpoint_url + url, method,
                                      **kwargs)
        return resp, body
    except exceptions.Unauthorized:
        self.authenticate()
        kwargs.setdefault('headers', {})
        kwargs['headers']['X-Auth-Token'] = self.auth_token
        resp, body = self._cs_request(
            self.endpoint_url + url, method, **kwargs)
        return resp, body

在這個函式中,首先進行token和endpoint_url確認。然後堅持請求url的長度,最大不得超過8192.http預設的請求最大是255,不知道這裡怎麼是8192.最後將token資訊放入header中。然後keystonemiddleware從header中獲取token進行驗證。


下面說明在第一次請求時沒有token,怎麼去獲取token。

authenticate_and_fetch_endpoint_url函式如下:
def authenticate_and_fetch_endpoint_url(self):
    if not self.auth_token:
        self.authenticate()
    elif not self.endpoint_url:
        self.endpoint_url = self._get_endpoint_url()
authenticate函式如下:
def authenticate(self):
    if self.auth_strategy == 'keystone':
        self._authenticate_keystone()
    elif self.auth_strategy == 'noauth':
        self._authenticate_noauth()
    else:
        err_msg = _('Unknown auth strategy: %s') % self.auth_strategy
        raise exceptions.Unauthorized(message=err_msg)
_authenticate_keystone函式如下:
def _authenticate_keystone(self):
    if self.user_id:
        creds = {'userId': self.user_id,
                 'password': self.password}
    else:
        creds = {'username': self.username,
                 'password': self.password}

    if self.tenant_id:
        body = {'auth': {'passwordCredentials': creds,
                         'tenantId': self.tenant_id, }, }
    else:
        body = {'auth': {'passwordCredentials': creds,
                         'tenantName': self.tenant_name, }, }

    if self.auth_url is None:
        raise exceptions.NoAuthURLProvided()

    token_url = self.auth_url + "/tokens"
    resp, resp_body = self._cs_request(token_url, "POST",
                                       body=json.dumps(body),
                                       content_type="application/json",
                                       allow_redirects=True)
    if resp.status_code != 200:
        raise exceptions.Unauthorized(message=resp_body)
    if resp_body:
        try:
            resp_body = json.loads(resp_body)
        except ValueError:
            pass
    else:
        resp_body = None
    self._extract_service_catalog(resp_body)

向keystone獲取token。


相關文章