From: <ma...@us...> - 2015-01-21 20:08:05
|
Revision: 721 http://sourceforge.net/p/pywbem/code/721 Author: maiera Date: 2015-01-21 20:07:57 +0000 (Wed, 21 Jan 2015) Log Message: ----------- Fixed bugs 31, 32, 36: Improved exception handling and simplified the exceptions that can be seen by a user of clas WBEMConnection. See there for details. Modified Paths: -------------- pywbem/trunk/pywbem/NEWS pywbem/trunk/pywbem/cim_http.py pywbem/trunk/pywbem/cim_operations.py pywbem/trunk/pywbem/tupleparse.py Modified: pywbem/trunk/pywbem/NEWS =================================================================== --- pywbem/trunk/pywbem/NEWS 2015-01-21 18:31:17 UTC (rev 720) +++ pywbem/trunk/pywbem/NEWS 2015-01-21 20:07:57 UTC (rev 721) @@ -1,4 +1,4 @@ -pywbem-0.8.0-dev.r715 +pywbem-0.8.0-dev.r721 ENHANCEMENTS: @@ -79,6 +79,10 @@ _CDATA_ESCAPING accordingly, which is a global variable in the cim_xml module. (Andreas Maier) + * Simplified the exceptions that can be raised by WBEMConnection methods, + and improved the information in the exception messages. See description + of WBEMConnection class for details. (Andreas Maier) + BUG FIXES: * Fix syntax error in CIM DTDVERSION error path. Allow KEYVALUE Modified: pywbem/trunk/pywbem/cim_http.py =================================================================== --- pywbem/trunk/pywbem/cim_http.py 2015-01-21 18:31:17 UTC (rev 720) +++ pywbem/trunk/pywbem/cim_http.py 2015-01-21 20:07:57 UTC (rev 721) @@ -57,6 +57,15 @@ """This exception is raised when an authentication error (401) occurs.""" pass +class ConnectionError(Error): + """This exception is raised when a connection-related problem occurs, + where a retry might make sense.""" + pass + +class TimeoutError(Error): + """This exception is raised when the client timeout is exceeded.""" + pass + def parse_url(url): """Return a tuple of ``(host, port, ssl)`` from the URL specified in the ``url`` parameter. @@ -353,9 +362,9 @@ h = FileHTTPConnection(url) local = True else: - raise Error('Invalid URL') + raise Error('Invalid URL: %s' % url) except OSError: - raise Error('Invalid URL') + raise Error('Invalid URL: %s' % url) locallogin = None if host in ('localhost', 'localhost6', '127.0.0.1', '::1'): @@ -479,11 +488,30 @@ body = response.read() except httplib.BadStatusLine, arg: - raise Error("The web server returned a bad status line: '%s'" % arg) + # Background: BadStatusLine is documented to be raised only when + # strict=True is used (that is not the case here). However, httplib + # currently raises BadStatusLine also independent of strict when a + # keep-alive connection times out (e.g. because the server went + # down). See http://bugs.python.org/issue8450. + # On how to detect this: A connection timeout definitely causes + # arg==None, but it is not clear whether other situations could + # also cause arg==None. + if arg.line.strip().strip("'") == '': + raise ConnectionError("Connection error: The CIM server "\ + "closed the connection without "\ + "returning any data, or the client "\ + "timed out") + else: + raise Error("HTTP error: The CIM server returned a bad HTTP "\ + "status line: '%s'" % arg.line) + except httplib.IncompleteRead, arg: + raise ConnectionError("Connection error: HTTP incomplete read: %s" % arg) + except httplib.NotConnected, arg: + raise ConnectionError("Connection error: HTTP not connected: %s" % arg) except socket.error, arg: - raise Error("Socket error: %s" % (arg,)) + raise ConnectionError("Connection error: Socket error %s" % arg) except socket.sslerror, arg: - raise Error("SSL error: %s" % (arg,)) + raise ConnectionError("Connection error: SSL error %s" % arg) break Modified: pywbem/trunk/pywbem/cim_operations.py =================================================================== --- pywbem/trunk/pywbem/cim_operations.py 2015-01-21 18:31:17 UTC (rev 720) +++ pywbem/trunk/pywbem/cim_operations.py 2015-01-21 20:07:57 UTC (rev 721) @@ -41,6 +41,7 @@ from pywbem import cim_obj, cim_xml, cim_http, cim_types, tupletree, tupleparse from pywbem.cim_obj import CIMInstance, CIMInstanceName, CIMClass, \ CIMClassName, NocaseDict +from pywbem.tupleparse import ParseError __all__ = ['DEFAULT_NAMESPACE', 'check_utf8_xml_chars', 'CIMError', 'WBEMConnection', 'is_subclass', @@ -239,24 +240,43 @@ class CIMError(Exception): """ - Exception indicating that a CIM error has happened. + Exception indicating that either the WBEM server has returned an error or + the response from the WBEM server has some issue. - The exception value is a tuple of ``(error_code, description)``, where: + The exception value is a tuple of + ``(error_code, description, exception_obj)``, where: - * ``error_code``: a numeric error code. + * ``error_code``: a numeric error code. See below for details. - A value of 0 indicates an error detected by the PyWBEM client, or a - connection error, or an HTTP error reported by the WBEM server. + * ``description``: a string (`unicode` or UTF-8 encoded `str`) + representing a human readable message describing the error. See below + for details. - Values other than 0 indicate that the WBEM server has returned an error - response, and the value is the CIM status code of the error response. - See `cim_constants` for constants defining CIM status code values. + * ``exception_obj``: the underlying exception object that caused this + exception to be raised. See below for details. - * ``description``: a string (`unicode` or UTF-8 encoded `str`) - representing a human readable message describing the error. If - `error_code` is not 0, this string is the CIM status description of the - error response returned by the WBEM server. Otherwise, this is a - message issued by the PyWBEM client. + An ``error_code`` value other than 0 indicates that the WBEM server has + returned an error response, and ``error_code`` is the CIM status code of + the error response. See `cim_constants` for constants defining CIM status + code values. In this case, ``description`` is the CIM status description + text returned by the server, and ``exception_obj`` is ``None``. + + An ``error_code`` value of 0 indicates an error detected in the PyWBEM + client after receiving the response returned by the WBEM server (for + example, an XML parsing error). In this case, ``exception_obj`` is another + exception object describing the error, and ``description`` is the + string representation of the error message of that other exception object. + The following other exception objects are used: + + * `cim_http.AuthError`: Authentication error (HTTP status 401). + * `cim_http.ConnectionError`: Problem with the connection to the server, + a retry sometimes later would make sense. + * `cim_http.TimeoutError`: Client timeout occurred. + * `tupleparse.ParseError`: CIM-XML parsing issue. + * `cim_http.Error`: Other low level error (e.g. HTTP error, CIMError + HTTP header). This exception is a base class for `cim_http.AuthError`, + `cim_http.ConnectionError`, and `cim_http.TimeoutError` and thus must + be handled at the end. """ @@ -581,7 +601,7 @@ self.last_raw_reply = None self.last_reply = None - # Get XML response + # Send request and receive response try: reply_xml = cim_http.wbem_request( @@ -590,23 +610,27 @@ verify_callback=self.verify_callback, ca_certs=self.ca_certs, no_verification=self.no_verification) - except cim_http.AuthError: - raise - except cim_http.Error, arg: - # Convert cim_http exceptions to CIMError exceptions - raise CIMError(0, str(arg)) + except (cim_http.AuthError, cim_http.ConnectionError, + cim_http.TimeoutError, cim_http.Error) as exc: + raise CIMError(0, str(exc), exc) - ## TODO: Perhaps only compute this if it's required? Should not be - ## all that expensive. - # Set the raw response before parsing (which can fail) if self.debug: self.last_raw_reply = reply_xml try: reply_dom = minidom.parseString(reply_xml) - except ExpatError: + except ParseError as exc: + msg = str(exc) parsing_error = True + except ExpatError as exc: + # This is raised e.g. when XML numeric entity references of invalid + # XML characters are used (e.g. '�'). + # str(exc) is: "{message}, line {X}, offset {Y}" + parsed_line = str(reply_xml).splitlines()[exc.lineno-1] + msg = "ExpatError %s: %s: %r" % (str(exc.code), str(exc), + parsed_line) + parsing_error = True else: parsing_error = False @@ -615,7 +639,15 @@ # so we do this only if it already has failed. Because the check # function we invoke catches more errors than minidom.parseString, # we call it also when debug is turned on. - check_utf8_xml_chars(reply_xml, "CIM-XML response") + try: + check_utf8_xml_chars(reply_xml, "CIM-XML response") + except ParseError as exc2: + raise CIMError(0, str(exc2), exc2) + else: + if parsing_error: + # We did not catch it in the check function, but + # minidom.parseString() failed. + raise CIMError(0, msg, exc) # data from previous exception if self.debug: pretty_reply = reply_dom.toprettyxml(indent=' ') @@ -627,24 +659,29 @@ tt = tupleparse.parse_cim(tupletree.dom_to_tupletree(reply_dom)) if tt[0] != 'CIM': - raise CIMError(0, 'Expecting CIM element, got %s' % tt[0]) + exc = ParseError('Expecting CIM element, got %s' % tt[0]) + raise CIMError(0, str(exc), exc) tt = tt[2] if tt[0] != 'MESSAGE': - raise CIMError(0, 'Expecting MESSAGE element, got %s' % tt[0]) + exc = ParseError('Expecting MESSAGE element, got %s' % tt[0]) + raise CIMError(0, str(exc), exc) tt = tt[2] if len(tt) != 1 or tt[0][0] != 'SIMPLERSP': - raise CIMError(0, 'Expecting one SIMPLERSP element') + exc = ParseError('Expecting one SIMPLERSP element') + raise CIMError(0, str(exc), exc) tt = tt[0][2] if tt[0] != 'IMETHODRESPONSE': - raise CIMError( - 0, 'Expecting IMETHODRESPONSE element, got %s' % tt[0]) + exc = ParseError('Expecting IMETHODRESPONSE element, got %s' %\ + tt[0]) + raise CIMError(0, str(exc), exc) if tt[1]['NAME'] != methodname: - raise CIMError(0, 'Expecting attribute NAME=%s, got %s' % - (methodname, tt[1]['NAME'])) + exc = ParseError('Expecting attribute NAME=%s, got %s' %\ + (methodname, tt[1]['NAME'])) + raise CIMError(0, str(exc), exc) tt = tt[2] # At this point we either have a IRETURNVALUE, ERROR element @@ -661,7 +698,8 @@ raise CIMError(code, 'Error code %s' % tt[1]['CODE']) if tt[0] != 'IRETURNVALUE': - raise CIMError(0, 'Expecting IRETURNVALUE element, got %s' % tt[0]) + exc = ParseError('Expecting IRETURNVALUE element, got %s' % tt[0]) + raise CIMError(0, str(exc), exc) return tt @@ -779,7 +817,7 @@ self.last_raw_reply = None self.last_reply = None - # Get XML response + # Send request and receive response try: reply_xml = cim_http.wbem_request( @@ -788,9 +826,9 @@ verify_callback=self.verify_callback, ca_certs=self.ca_certs, no_verification=self.no_verification) - except cim_http.Error, arg: - # Convert cim_http exceptions to CIMError exceptions - raise CIMError(0, str(arg)) + except (cim_http.AuthError, cim_http.ConnectionError, + cim_http.TimeoutError, cim_http.Error) as exc: + raise CIMError(0, str(exc), exc) # Set the raw response before parsing and checking (which can fail) if self.debug: @@ -798,8 +836,17 @@ try: reply_dom = minidom.parseString(reply_xml) - except ExpatError: + except ParseError as exc: + msg = str(exc) parsing_error = True + except ExpatError as exc: + # This is raised e.g. when XML numeric entity references of invalid + # XML characters are used (e.g. '�'). + # str(exc) is: "{message}, line {X}, offset {Y}" + parsed_line = str(reply_xml).splitlines()[exc.lineno-1] + msg = "ExpatError %s: %s: %r" % (str(exc.code), str(exc), + parsed_line) + parsing_error = True else: parsing_error = False @@ -808,7 +855,15 @@ # so we do this only if it already has failed. Because the check # function we invoke catches more errors than minidom.parseString, # we call it also when debug is turned on. - check_utf8_xml_chars(reply_xml, "CIM-XML response") + try: + check_utf8_xml_chars(reply_xml, "CIM-XML response") + except ParseError as exc2: + raise CIMError(0, str(exc2), exc2) + else: + if parsing_error: + # We did not catch it in the check function, but + # minidom.parseString() failed. + raise CIMError(0, msg, exc) # data from previous exception if self.debug: pretty_reply = reply_dom.toprettyxml(indent=' ') @@ -820,24 +875,29 @@ tt = tupleparse.parse_cim(tupletree.dom_to_tupletree(reply_dom)) if tt[0] != 'CIM': - raise CIMError(0, 'Expecting CIM element, got %s' % tt[0]) + exc = ParseError('Expecting CIM element, got %s' % tt[0]) + raise CIMError(0, str(exc), exc) tt = tt[2] if tt[0] != 'MESSAGE': - raise CIMError(0, 'Expecting MESSAGE element, got %s' % tt[0]) + exc = ParseError('Expecting MESSAGE element, got %s' % tt[0]) + raise CIMError(0, str(exc), exc) tt = tt[2] if len(tt) != 1 or tt[0][0] != 'SIMPLERSP': - raise CIMError(0, 'Expecting one SIMPLERSP element') + exc = ParseError('Expecting one SIMPLERSP element') + raise CIMError(0, str(exc), exc) tt = tt[0][2] if tt[0] != 'METHODRESPONSE': - raise CIMError( - 0, 'Expecting METHODRESPONSE element, got %s' % tt[0]) + exc = ParseError('Expecting METHODRESPONSE element, got %s' %\ + tt[0]) + raise CIMError(0, str(exc), exc) if tt[1]['NAME'] != methodname: - raise CIMError(0, 'Expecting attribute NAME=%s, got %s' % - (methodname, tt[1]['NAME'])) + exc = ParseError('Expecting attribute NAME=%s, got %s' %\ + (methodname, tt[1]['NAME'])) + raise CIMError(0, str(exc), exc) tt = tt[2] # At this point we have an optional RETURNVALUE and zero or Modified: pywbem/trunk/pywbem/tupleparse.py =================================================================== --- pywbem/trunk/pywbem/tupleparse.py 2015-01-21 18:31:17 UTC (rev 720) +++ pywbem/trunk/pywbem/tupleparse.py 2015-01-21 20:07:57 UTC (rev 721) @@ -918,8 +918,14 @@ for q in list_of_matching(tt, ['QUALIFIER']): quals[q.name] = q - val = unpack_value(tt) a = attrs(tt) + try: + val = unpack_value(tt) + except ValueError as exc: + msg = str(exc) + raise ParseError('Cannot parse value for property "%s": %s' %\ + (a['NAME'], msg)) + embedded_object = None if 'EmbeddedObject' in a or 'EMBEDDEDOBJECT' in a: try: This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |