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