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