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