5113b844fb
Fix several type error for the word "Cloudera" Change-Id: Id423315367d58b6559f42cf3f1b082eced696b5d Closes-bug: 1460867
209 lines
7.4 KiB
Python
209 lines
7.4 KiB
Python
# Copyright (c) 2014 Intel Corporation.
|
|
#
|
|
# 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.
|
|
#
|
|
# The contents of this file are mainly copied from cm_api sources,
|
|
# released by Cloudera. Codes not used by Sahara CDH plugin are removed.
|
|
# You can find the original codes at
|
|
#
|
|
# https://github.com/cloudera/cm_api/tree/master/python/src/cm_api
|
|
#
|
|
# To satisfy the pep8 and python3 tests, we did some changes to the codes.
|
|
# We also change some importings to use Sahara inherited classes.
|
|
|
|
import posixpath
|
|
import types
|
|
|
|
from oslo_log import log as logging
|
|
from oslo_serialization import jsonutils as json
|
|
import six
|
|
from six.moves import urllib
|
|
|
|
from sahara.i18n import _LW
|
|
from sahara.plugins.cdh import exceptions as ex
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class HttpClient(object):
|
|
"""Basic HTTP client tailored for rest APIs."""
|
|
def __init__(self, base_url, exc_class=ex.CMApiException):
|
|
"""Init Method
|
|
|
|
:param base_url: The base url to the API.
|
|
:param exc_class: An exception class to handle non-200 results.
|
|
|
|
Creates an HTTP(S) client to connect to the Cloudera Manager API.
|
|
"""
|
|
self._base_url = base_url.rstrip('/')
|
|
self._exc_class = exc_class
|
|
self._headers = {}
|
|
|
|
# Make a basic auth handler that does nothing. Set credentials later.
|
|
self._passmgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
|
|
authhandler = urllib.request.HTTPBasicAuthHandler(self._passmgr)
|
|
|
|
# Make a cookie processor
|
|
cookiejar = six.moves.http_cookiejar.CookieJar()
|
|
|
|
self._opener = urllib.request.build_opener(
|
|
urllib.request.HTTPErrorProcessor(),
|
|
urllib.request.HTTPCookieProcessor(cookiejar),
|
|
authhandler)
|
|
|
|
def set_basic_auth(self, username, password, realm):
|
|
"""Set up basic auth for the client
|
|
|
|
:param username: Login name.
|
|
:param password: Login password.
|
|
:param realm: The authentication realm.
|
|
:return: The current object
|
|
"""
|
|
self._passmgr.add_password(realm, self._base_url, username, password)
|
|
return self
|
|
|
|
def set_headers(self, headers):
|
|
"""Add headers to the request
|
|
|
|
:param headers: A dictionary with the key value pairs for the headers
|
|
:return: The current object
|
|
"""
|
|
self._headers = headers
|
|
return self
|
|
|
|
@property
|
|
def base_url(self):
|
|
return self._base_url
|
|
|
|
def _get_headers(self, headers):
|
|
res = self._headers.copy()
|
|
if headers:
|
|
res.update(headers)
|
|
return res
|
|
|
|
def execute(self, http_method, path, params=None, data=None, headers=None):
|
|
"""Submit an HTTP request
|
|
|
|
:param http_method: GET, POST, PUT, DELETE
|
|
:param path: The path of the resource.
|
|
:param params: Key-value parameter data.
|
|
:param data: The data to attach to the body of the request.
|
|
:param headers: The headers to set for this request.
|
|
|
|
:return: The result of urllib.request.urlopen()
|
|
"""
|
|
# Prepare URL and params
|
|
url = self._make_url(path, params)
|
|
if http_method in ("GET", "DELETE"):
|
|
if data is not None:
|
|
LOG.warning(_LW("{method} method does not pass any data. "
|
|
"Path {path}").format(method=http_method,
|
|
path=path))
|
|
data = None
|
|
|
|
# Setup the request
|
|
request = urllib.request.Request(url, data)
|
|
# Hack/workaround because urllib2 only does GET and POST
|
|
request.get_method = lambda: http_method
|
|
|
|
headers = self._get_headers(headers)
|
|
for k, v in headers.items():
|
|
request.add_header(k, v)
|
|
|
|
# Call it
|
|
LOG.debug("Method: {method}, URL: {url}".format(method=http_method,
|
|
url=url))
|
|
try:
|
|
return self._opener.open(request)
|
|
except urllib.error.HTTPError as ex:
|
|
message = six.text_type(ex)
|
|
try:
|
|
json_body = json.loads(message)
|
|
message = json_body['message']
|
|
except (ValueError, KeyError):
|
|
pass # Ignore json parsing error
|
|
raise self._exc_class(message)
|
|
|
|
def _make_url(self, path, params):
|
|
res = self._base_url
|
|
if path:
|
|
res += posixpath.normpath('/' + path.lstrip('/'))
|
|
if params:
|
|
param_str = urllib.parse.urlencode(params, True)
|
|
res += '?' + param_str
|
|
return iri_to_uri(res)
|
|
|
|
|
|
#
|
|
# Method copied from Django
|
|
#
|
|
def iri_to_uri(iri):
|
|
"""Convert IRI to URI
|
|
|
|
Convert an Internationalized Resource Identifier (IRI) portion to a URI
|
|
portion that is suitable for inclusion in a URL.
|
|
|
|
This is the algorithm from section 3.1 of RFC 3987. However, since we are
|
|
assuming input is either UTF-8 or unicode already, we can simplify things a
|
|
little from the full method.
|
|
|
|
Returns an ASCII string containing the encoded result.
|
|
"""
|
|
# The list of safe characters here is constructed from the "reserved" and
|
|
# "unreserved" characters specified in sections 2.2 and 2.3 of RFC 3986:
|
|
# reserved = gen-delims / sub-delims
|
|
# gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
|
|
# sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
|
# / "*" / "+" / "," / ";" / "="
|
|
# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
|
# Of the unreserved characters, urllib.parse.quote already considers all
|
|
# but the ~ safe.
|
|
# The % character is also added to the list of safe characters here, as the
|
|
# end of section 3.1 of RFC 3987 specifically mentions that % must not be
|
|
# converted.
|
|
if iri is None:
|
|
return iri
|
|
return urllib.parse.quote(smart_str(iri), safe="/#%[]=:;$&()+,!?*@'~")
|
|
|
|
|
|
#
|
|
# Method copied from Django
|
|
#
|
|
def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):
|
|
"""Convert string into bytestring version
|
|
|
|
Returns a bytestring version of 's', encoded as specified in 'encoding'.
|
|
|
|
If strings_only is True, don't convert (some) non-string-like objects.
|
|
"""
|
|
if strings_only and isinstance(s, (types.NoneType, int)):
|
|
return s
|
|
elif not isinstance(s, basestring):
|
|
try:
|
|
return six.text_type(s)
|
|
except UnicodeEncodeError:
|
|
if isinstance(s, Exception):
|
|
# An Exception subclass containing non-ASCII data that doesn't
|
|
# know how to print itself properly. We shouldn't raise a
|
|
# further exception.
|
|
return ' '.join([smart_str(arg, encoding, strings_only,
|
|
errors) for arg in s])
|
|
return unicode(s).encode(encoding, errors)
|
|
elif isinstance(s, unicode):
|
|
return s.encode(encoding, errors)
|
|
elif s and encoding != 'utf-8':
|
|
return s.decode('utf-8', errors).encode(encoding, errors)
|
|
else:
|
|
return s
|