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