summaryrefslogtreecommitdiff
path: root/neutronclient/client.py
diff options
context:
space:
mode:
authorMark McClain <mark.mcclain@dreamhost.com>2013-07-02 18:44:42 -0400
committerMark McClain <mark.mcclain@dreamhost.com>2013-07-03 11:56:44 -0400
commit93ac15bfeb51ee6a097d878e4eeef69cd2b3a6bc (patch)
tree39acb2ba93a28e1f03a3e6d739845758cd58715b /neutronclient/client.py
parent8ed38707b12ae6e77480ae8d8542712d63b7fc70 (diff)
downloadpython-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.py250
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