diff options
| author | Mark McClain <mark.mcclain@dreamhost.com> | 2013-07-02 18:44:42 -0400 |
|---|---|---|
| committer | Mark McClain <mark.mcclain@dreamhost.com> | 2013-07-03 11:56:44 -0400 |
| commit | 93ac15bfeb51ee6a097d878e4eeef69cd2b3a6bc (patch) | |
| tree | 39acb2ba93a28e1f03a3e6d739845758cd58715b /neutronclient/client.py | |
| parent | 8ed38707b12ae6e77480ae8d8542712d63b7fc70 (diff) | |
| download | python-neutronclient-2.2.4.tar.gz | |
Rename quantumclient to neutronclient2.2.4
Implements Blueprint: remove-use-of-quantum
Change-Id: Idebe92d56d277435ffd23f292984f9b8b8fdb2df
Diffstat (limited to 'neutronclient/client.py')
| -rw-r--r-- | neutronclient/client.py | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/neutronclient/client.py b/neutronclient/client.py new file mode 100644 index 0000000..9dc80a3 --- /dev/null +++ b/neutronclient/client.py @@ -0,0 +1,250 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +try: + import json +except ImportError: + import simplejson as json +import logging +import os +import urlparse +# Python 2.5 compat fix +if not hasattr(urlparse, 'parse_qsl'): + import cgi + urlparse.parse_qsl = cgi.parse_qsl + +import httplib2 + +from neutronclient.common import exceptions +from neutronclient.common import utils + +_logger = logging.getLogger(__name__) + + +if os.environ.get('NEUTRONCLIENT_DEBUG'): + ch = logging.StreamHandler() + _logger.setLevel(logging.DEBUG) + _logger.addHandler(ch) + + +class ServiceCatalog(object): + """Helper methods for dealing with a Keystone Service Catalog.""" + + def __init__(self, resource_dict): + self.catalog = resource_dict + + def get_token(self): + """Fetch token details fron service catalog.""" + token = {'id': self.catalog['access']['token']['id'], + 'expires': self.catalog['access']['token']['expires'], } + try: + token['user_id'] = self.catalog['access']['user']['id'] + token['tenant_id'] = ( + self.catalog['access']['token']['tenant']['id']) + except Exception: + # just leave the tenant and user out if it doesn't exist + pass + return token + + def url_for(self, attr=None, filter_value=None, + service_type='network', endpoint_type='publicURL'): + """Fetch the URL from the Neutron service for + a particular endpoint type. If none given, return + publicURL. + """ + + catalog = self.catalog['access'].get('serviceCatalog', []) + matching_endpoints = [] + for service in catalog: + if service['type'] != service_type: + continue + + endpoints = service['endpoints'] + for endpoint in endpoints: + if not filter_value or endpoint.get(attr) == filter_value: + matching_endpoints.append(endpoint) + + if not matching_endpoints: + raise exceptions.EndpointNotFound() + elif len(matching_endpoints) > 1: + raise exceptions.AmbiguousEndpoints(message=matching_endpoints) + else: + if endpoint_type not in matching_endpoints[0]: + raise exceptions.EndpointTypeNotFound(message=endpoint_type) + + return matching_endpoints[0][endpoint_type] + + +class HTTPClient(httplib2.Http): + """Handles the REST calls and responses, include authn.""" + + USER_AGENT = 'python-neutronclient' + + def __init__(self, username=None, tenant_name=None, + password=None, auth_url=None, + token=None, region_name=None, timeout=None, + endpoint_url=None, insecure=False, + endpoint_type='publicURL', + auth_strategy='keystone', **kwargs): + super(HTTPClient, self).__init__(timeout=timeout) + self.username = username + self.tenant_name = tenant_name + self.password = password + self.auth_url = auth_url.rstrip('/') if auth_url else None + self.endpoint_type = endpoint_type + self.region_name = region_name + self.auth_token = token + self.content_type = 'application/json' + self.endpoint_url = endpoint_url + self.auth_strategy = auth_strategy + # httplib2 overrides + self.force_exception_to_status_code = True + self.disable_ssl_certificate_validation = insecure + + def _cs_request(self, *args, **kwargs): + kargs = {} + kargs.setdefault('headers', kwargs.get('headers', {})) + kargs['headers']['User-Agent'] = self.USER_AGENT + + if 'content_type' in kwargs: + kargs['headers']['Content-Type'] = kwargs['content_type'] + kargs['headers']['Accept'] = kwargs['content_type'] + else: + kargs['headers']['Content-Type'] = self.content_type + kargs['headers']['Accept'] = self.content_type + + if 'body' in kwargs: + kargs['body'] = kwargs['body'] + args = utils.safe_encode_list(args) + kargs = utils.safe_encode_dict(kargs) + utils.http_log_req(_logger, args, kargs) + resp, body = self.request(*args, **kargs) + utils.http_log_resp(_logger, resp, body) + status_code = self.get_status_code(resp) + if status_code == 401: + raise exceptions.Unauthorized(message=body) + elif status_code == 403: + raise exceptions.Forbidden(message=body) + return resp, body + + 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() + + def do_request(self, url, method, **kwargs): + self.authenticate_and_fetch_endpoint_url() + # Perform the request once. If we get a 401 back then it + # might be because the auth token expired, so try to + # re-authenticate and try again. If it still fails, bail. + try: + 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 + 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 + + def _extract_service_catalog(self, body): + """Set the client's service catalog from the response data.""" + self.service_catalog = ServiceCatalog(body) + try: + sc = self.service_catalog.get_token() + self.auth_token = sc['id'] + self.auth_tenant_id = sc.get('tenant_id') + self.auth_user_id = sc.get('user_id') + except KeyError: + raise exceptions.Unauthorized() + self.endpoint_url = self.service_catalog.url_for( + attr='region', filter_value=self.region_name, + endpoint_type=self.endpoint_type) + + def authenticate(self): + if self.auth_strategy != 'keystone': + raise exceptions.Unauthorized(message='unknown auth strategy') + body = {'auth': {'passwordCredentials': + {'username': self.username, + 'password': self.password, }, + 'tenantName': self.tenant_name, }, } + + token_url = self.auth_url + "/tokens" + + # Make sure we follow redirects when trying to reach Keystone + tmp_follow_all_redirects = self.follow_all_redirects + self.follow_all_redirects = True + try: + resp, body = self._cs_request(token_url, "POST", + body=json.dumps(body), + content_type="application/json") + finally: + self.follow_all_redirects = tmp_follow_all_redirects + status_code = self.get_status_code(resp) + if status_code != 200: + raise exceptions.Unauthorized(message=body) + if body: + try: + body = json.loads(body) + except ValueError: + pass + else: + body = None + self._extract_service_catalog(body) + + def _get_endpoint_url(self): + url = self.auth_url + '/tokens/%s/endpoints' % self.auth_token + try: + resp, body = self._cs_request(url, "GET") + except exceptions.Unauthorized: + # rollback to authenticate() to handle case when neutron client + # is initialized just before the token is expired + self.authenticate() + return self.endpoint_url + + body = json.loads(body) + for endpoint in body.get('endpoints', []): + if (endpoint['type'] == 'network' and + endpoint.get('region') == self.region_name): + if self.endpoint_type not in endpoint: + raise exceptions.EndpointTypeNotFound( + message=self.endpoint_type) + return endpoint[self.endpoint_type] + + raise exceptions.EndpointNotFound() + + def get_auth_info(self): + return {'auth_token': self.auth_token, + 'auth_tenant_id': self.auth_tenant_id, + 'auth_user_id': self.auth_user_id, + 'endpoint_url': self.endpoint_url} + + def get_status_code(self, response): + """Returns the integer status code from the response. + + Either a Webob.Response (used in testing) or httplib.Response + is returned. + """ + if hasattr(response, 'status_int'): + return response.status_int + else: + return response.status |
