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):
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
87 Creates an HTTP(S) client to connect to the Cloudera Manager API.
88 """
89 self._base_url = base_url.rstrip('/')
90 self._exc_class = exc_class or RestException
91 self._logger = logger or LOG
92 self._headers = { }
93
94
95 self._passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
96 authhandler = urllib2.HTTPBasicAuthHandler(self._passmgr)
97
98
99 cookiejar = cookielib.CookieJar()
100
101
102
103 if (ssl_context is not None):
104 self._opener = urllib2.build_opener(
105 urllib2.HTTPSHandler(context=ssl_context),
106 HTTPErrorProcessor(),
107 urllib2.HTTPCookieProcessor(cookiejar),
108 authhandler)
109 else:
110 self._opener = urllib2.build_opener(
111 HTTPErrorProcessor(),
112 urllib2.HTTPCookieProcessor(cookiejar),
113 authhandler)
114
116 """
117 Set up basic auth for the client
118 @param username: Login name.
119 @param password: Login password.
120 @param realm: The authentication realm.
121 @return: The current object
122 """
123 self._passmgr.add_password(realm, self._base_url, username, password)
124 return self
125
127 """
128 Add headers to the request
129 @param headers: A dictionary with the key value pairs for the headers
130 @return: The current object
131 """
132 self._headers = headers
133 return self
134
135
136 @property
138 return self._base_url
139
140 @property
143
145 res = self._headers.copy()
146 if headers:
147 res.update(headers)
148 return res
149
150 - def execute(self, http_method, path, params=None, data=None, headers=None):
151 """
152 Submit an HTTP request.
153 @param http_method: GET, POST, PUT, DELETE
154 @param path: The path of the resource.
155 @param params: Key-value parameter data.
156 @param data: The data to attach to the body of the request.
157 @param headers: The headers to set for this request.
158
159 @return: The result of urllib2.urlopen()
160 """
161
162 url = self._make_url(path, params)
163 if http_method in ("GET", "DELETE"):
164 if data is not None:
165 self.logger.warn(
166 "GET method does not pass any data. Path '%s'" % (path,))
167 data = None
168
169
170 request = urllib2.Request(url, data)
171
172 request.get_method = lambda: http_method
173
174 headers = self._get_headers(headers)
175 for k, v in headers.items():
176 request.add_header(k, v)
177
178
179 self.logger.debug("%s %s" % (http_method, url))
180 try:
181 return self._opener.open(request)
182 except urllib2.HTTPError, ex:
183 raise self._exc_class(ex)
184
186 res = self._base_url
187 if path:
188 res += posixpath.normpath('/' + path.lstrip('/'))
189 if params:
190 param_str = urllib.urlencode(params, True)
191 res += '?' + param_str
192 return iri_to_uri(res)
193
196 """
197 Python 2.4 only recognize 200 and 206 as success. It's broken. So we install
198 the following processor to catch the bug.
199 """
204
205 https_response = http_response
206
211 """
212 Convert an Internationalized Resource Identifier (IRI) portion to a URI
213 portion that is suitable for inclusion in a URL.
214
215 This is the algorithm from section 3.1 of RFC 3987. However, since we are
216 assuming input is either UTF-8 or unicode already, we can simplify things a
217 little from the full method.
218
219 Returns an ASCII string containing the encoded result.
220 """
221
222
223
224
225
226
227
228
229
230
231
232
233 if iri is None:
234 return iri
235 return urllib.quote(smart_str(iri), safe="/#%[]=:;$&()+,!?*@'~")
236
237
238
239
240 -def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):
241 """
242 Returns a bytestring version of 's', encoded as specified in 'encoding'.
243
244 If strings_only is True, don't convert (some) non-string-like objects.
245 """
246 if strings_only and isinstance(s, (types.NoneType, int)):
247 return s
248 elif not isinstance(s, basestring):
249 try:
250 return str(s)
251 except UnicodeEncodeError:
252 if isinstance(s, Exception):
253
254
255
256 return ' '.join([smart_str(arg, encoding, strings_only,
257 errors) for arg in s])
258 return unicode(s).encode(encoding, errors)
259 elif isinstance(s, unicode):
260 return s.encode(encoding, errors)
261 elif s and encoding != 'utf-8':
262 return s.decode('utf-8', errors).encode(encoding, errors)
263 else:
264 return s
265