1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 import os
18 import cookielib
19 import logging
20 import posixpath
21 import types
22 import urllib
23
24 try:
25 import socks
26 import socket
27 socks_server = os.environ.get("SOCKS_SERVER", None)
28 if socks_server:
29 host, port = socks_server.split(":")
30 socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, host, int(port))
31 socket.socket = socks.socksocket
32 except ImportError:
33 pass
34
35 import urllib2
36
37 __docformat__ = "epytext"
38
39 LOG = logging.getLogger(__name__)
42 """
43 Any error result from the Rest API is converted into this exception type.
44 """
46 Exception.__init__(self, error)
47 self._error = error
48 self._code = None
49 self._message = str(error)
50
51 try:
52 self._code = error.code
53 self._message = error.read()
54 except AttributeError:
55 pass
56
58 res = self._message or ""
59 if self._code is not None:
60 res += " (error %s)" % (self._code,)
61 return res
62
64 if isinstance(self._error, Exception):
65 return self._error
66 return None
67
68 @property
71
72 @property
75
78 """
79 Basic HTTP client tailored for rest APIs.
80 """
81 - def __init__(self, base_url, exc_class=None, logger=None, ssl_context=None, timeout=None):
82 """
83 @param base_url: The base url to the API.
84 @param exc_class: An exception class to handle non-200 results.
85 @param ssl_context: A custom SSL context to use for HTTPS (Python 2.7.9+)
86 @param timeout: The connection timeout in seconds to use for requests
87
88 Creates an HTTP(S) client to connect to the Cloudera Manager API.
89 """
90 self._base_url = base_url.rstrip('/')
91 self._exc_class = exc_class or RestException
92 self._logger = logger or LOG
93 self._headers = { }
94 self._timeout = timeout
95
96
97 self._passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
98 authhandler = urllib2.HTTPBasicAuthHandler(self._passmgr)
99
100
101 cookiejar = cookielib.CookieJar()
102
103
104
105 if (ssl_context is not None):
106 self._opener = urllib2.build_opener(
107 urllib2.HTTPSHandler(context=ssl_context),
108 HTTPErrorProcessor(),
109 urllib2.HTTPCookieProcessor(cookiejar),
110 authhandler)
111 else:
112 self._opener = urllib2.build_opener(
113 HTTPErrorProcessor(),
114 urllib2.HTTPCookieProcessor(cookiejar),
115 authhandler)
116
118 """
119 Set up basic auth for the client
120 @param username: Login name.
121 @param password: Login password.
122 @param realm: The authentication realm.
123 @return: The current object
124 """
125 self._passmgr.add_password(realm, self._base_url, username, password)
126 return self
127
129 """
130 Add headers to the request
131 @param headers: A dictionary with the key value pairs for the headers
132 @return: The current object
133 """
134 self._headers = headers
135 return self
136
137
138 @property
140 return self._base_url
141
142 @property
145
147 res = self._headers.copy()
148 if headers:
149 res.update(headers)
150 return res
151
152 - def execute(self, http_method, path, params=None, data=None, headers=None):
153 """
154 Submit an HTTP request.
155 @param http_method: GET, POST, PUT, DELETE
156 @param path: The path of the resource.
157 @param params: Key-value parameter data.
158 @param data: The data to attach to the body of the request.
159 @param headers: The headers to set for this request.
160
161 @return: The result of urllib2.urlopen()
162 """
163
164 url = self._make_url(path, params)
165 if http_method in ("GET", "DELETE"):
166 if data is not None:
167 self.logger.warn(
168 "GET method does not pass any data. Path '%s'" % (path,))
169 data = None
170
171
172 request = urllib2.Request(url, data)
173
174 request.get_method = lambda: http_method
175
176 headers = self._get_headers(headers)
177 for k, v in headers.items():
178 request.add_header(k, v)
179
180
181 self.logger.debug("%s %s" % (http_method, url))
182 try:
183 if self._timeout is not None:
184 try:
185 return self._opener.open(request, timeout=self._timeout)
186 except TypeError, tex:
187
188
189 return self._opener.open(request)
190 else:
191 return self._opener.open(request)
192 except urllib2.HTTPError, ex:
193 raise self._exc_class(ex)
194
196 res = self._base_url
197 if path:
198 res += posixpath.normpath('/' + path.lstrip('/'))
199 if params:
200 param_str = urllib.urlencode(params, True)
201 res += '?' + param_str
202 return iri_to_uri(res)
203
206 """
207 Python 2.4 only recognize 200 and 206 as success. It's broken. So we install
208 the following processor to catch the bug.
209 """
214
215 https_response = http_response
216
221 """
222 Convert an Internationalized Resource Identifier (IRI) portion to a URI
223 portion that is suitable for inclusion in a URL.
224
225 This is the algorithm from section 3.1 of RFC 3987. However, since we are
226 assuming input is either UTF-8 or unicode already, we can simplify things a
227 little from the full method.
228
229 Returns an ASCII string containing the encoded result.
230 """
231
232
233
234
235
236
237
238
239
240
241
242
243 if iri is None:
244 return iri
245 return urllib.quote(smart_str(iri), safe="/#%[]=:;$&()+,!?*@'~")
246
247
248
249
250 -def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):
251 """
252 Returns a bytestring version of 's', encoded as specified in 'encoding'.
253
254 If strings_only is True, don't convert (some) non-string-like objects.
255 """
256 if strings_only and isinstance(s, (types.NoneType, int)):
257 return s
258 elif not isinstance(s, basestring):
259 try:
260 return str(s)
261 except UnicodeEncodeError:
262 if isinstance(s, Exception):
263
264
265
266 return ' '.join([smart_str(arg, encoding, strings_only,
267 errors) for arg in s])
268 return unicode(s).encode(encoding, errors)
269 elif isinstance(s, unicode):
270 return s.encode(encoding, errors)
271 elif s and encoding != 'utf-8':
272 return s.decode('utf-8', errors).encode(encoding, errors)
273 else:
274 return s
275