在请求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函数如下:向keystone获取token。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)