|
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.
|