diff --git a/pyVmomi/SoapAdapter.py b/pyVmomi/SoapAdapter.py
index de50e1583..bca8f02ab 100644
--- a/pyVmomi/SoapAdapter.py
+++ b/pyVmomi/SoapAdapter.py
@@ -16,7 +16,7 @@
from six import PY2
from six import PY3
-
+from six import reraise
from six.moves import http_client
if PY3:
@@ -442,6 +442,32 @@ def _SerializeDataObject(self, val, info, attr, currDefNS):
self.writer.write('{0}>'.format(info.name))
+class ParserError(KeyError):
+ # NOTE (hartsock): extends KeyError since parser logic is written to
+ # catch KeyError types. Normally, I would want PerserError to be a root
+ # type for all parser faults.
+ pass
+
+def ReadDocument(parser, data):
+ # NOTE (hartsock): maintaining library internal consistency here, this is
+ # a refactoring that rolls up some repeated code blocks into a method so
+ # that we can refactor XML parsing behavior in a single place.
+ if not isinstance(data, str):
+ data = data.read()
+ try:
+ parser.Parse(data)
+ except Exception:
+ # wrap all parser faults with additional information for later
+ # bug reporting on the XML parser code itself.
+ (ec, ev, tb) = sys.exc_info()
+ line = parser.CurrentLineNumber
+ col = parser.CurrentColumnNumber
+ pe = ParserError("xml document: "
+ "{0} parse error at: "
+ "line:{1}, col:{2}".format(data, line, col))
+ # use six.reraise for python 2.x and 3.x compatability
+ reraise(ParserError, pe, tb)
+
## Deserialize an object from a file or string
#
# This function will deserialize one top-level XML node.
@@ -453,10 +479,7 @@ def Deserialize(data, resultType=object, stub=None):
parser = ParserCreate(namespace_separator=NS_SEP)
ds = SoapDeserializer(stub)
ds.Deserialize(parser, resultType)
- if isinstance(data, str):
- parser.Parse(data)
- else:
- parser.ParseFile(data)
+ ReadDocument(parser, data)
return ds.GetResult()
@@ -582,11 +605,7 @@ def StartElementHandler(self, tag, attr):
if not self.stack:
if self.isFault:
ns, name = self.SplitTag(tag)
- try:
- objType = self.LookupWsdlType(ns, name[:-5])
- except KeyError:
- message = "{0} was not found in the WSDL".format(name[:-5])
- raise VmomiMessageFault(message)
+ objType = self.LookupWsdlType(ns, name[:-5])
# Only top level soap fault should be deserialized as method fault
deserializeAsLocalizedMethodFault = False
else:
@@ -761,10 +780,7 @@ def Deserialize(self, response, resultType, nsMap=None):
nsMap = {}
self.nsMap = nsMap
SetHandlers(self.parser, GetHandlers(self))
- if isinstance(response, str):
- self.parser.Parse(response)
- else:
- self.parser.ParseFile(response)
+ ReadDocument(self.parser, response)
result = self.deser.GetResult()
if self.isFault:
if result is None:
@@ -1241,10 +1257,15 @@ def InvokeMethod(self, mo, info, args, outerStub=None):
# The server is probably sick, drop all of the cached connections.
self.DropConnections()
raise
- cookie = resp.getheader('Set-Cookie')
+ # NOTE (hartsocks): this cookie handling code should go away in a future
+ # release. The string 'set-cookie' and 'Set-Cookie' but both are
+ # acceptable, but the supporting library may have a bug making it
+ # case sensitive when it shouldn't be. The term 'set-cookie' will occur
+ # more frequently than 'Set-Cookie' based on practical testing.
+ cookie = resp.getheader('set-cookie')
if cookie is None:
- # try lower-case header for backwards compat. with old vSphere
- cookie = resp.getheader('set-cookie')
+ # try case-sensitive header for compatibility
+ cookie = resp.getheader('Set-Cookie')
status = resp.status
if cookie:
@@ -1257,12 +1278,18 @@ def InvokeMethod(self, mo, info, args, outerStub=None):
fd = GzipReader(resp, encoding=GzipReader.GZIP)
elif encoding == 'deflate':
fd = GzipReader(resp, encoding=GzipReader.DEFLATE)
- obj = SoapResponseDeserializer(outerStub).Deserialize(fd, info.result)
- except:
+ deserializer = SoapResponseDeserializer(outerStub)
+ obj = deserializer.Deserialize(fd, info.result)
+ except Exception as exc:
conn.close()
+ # NOTE (hartsock): This feels out of place. As a rule the lexical
+ # context that opens a connection should also close it. However,
+ # in this code the connection is passed around and closed in other
+ # contexts (ie: methods) that we are blind to here. Refactor this.
+
# The server might be sick, drop all of the cached connections.
self.DropConnections()
- raise
+ raise exc
else:
resp.read()
self.ReturnConnection(conn)
@@ -1343,6 +1370,9 @@ def ReturnConnection(self, conn):
self.lock.release()
else:
self.lock.release()
+ # NOTE (hartsock): this seems to violate good coding practice in that
+ # the lexical context that opens a connection should also be the
+ # same context responsible for closing it.
conn.close()
## Disable nagle on a http connections
diff --git a/pyVmomi/VmomiSupport.py b/pyVmomi/VmomiSupport.py
index e6cacb97b..a31573b0a 100644
--- a/pyVmomi/VmomiSupport.py
+++ b/pyVmomi/VmomiSupport.py
@@ -1012,6 +1012,13 @@ def GetWsdlType(ns, name):
raise KeyError("{0} {1}".format(ns, name))
+
+class UnknownWsdlTypeError(KeyError):
+ # NOTE (hartsock): KeyError is extended here since most logic will be
+ # looking for the KeyError type. I do want to distinguish malformed WSDL
+ # errors as a separate classification of error for easier bug reports.
+ pass
+
## Guess the type from wsdlname with no ns
# WARNING! This should not be used in general, as there is no guarantee for
# the correctness of the guessing type
@@ -1026,8 +1033,8 @@ def GuessWsdlType(name):
try:
return GetWsdlType(ns, name)
except KeyError:
- pass
- raise KeyError(name)
+ pass
+ raise UnknownWsdlTypeError(name)
## Return a map that contains all the wsdl types
# This function is rarely used
diff --git a/tests/fixtures/test_unknown_fault.yaml b/tests/fixtures/test_unknown_fault.yaml
new file mode 100644
index 000000000..8c99ba404
--- /dev/null
+++ b/tests/fixtures/test_unknown_fault.yaml
@@ -0,0 +1,274 @@
+interactions:
+- request:
+ body: '
+
+
+
+ <_this type="ServiceInstance">ServiceInstance
+
+ '
+ headers:
+ Accept-Encoding: ['gzip, deflate']
+ Content-Type: [text/xml; charset=UTF-8]
+ Cookie: ['']
+ SOAPAction: ['"urn:vim25/4.1"']
+ method: POST
+ uri: https://vcsa:443/sdk
+ response:
+ body: {string: "\n\n\ngroup-d1propertyCollectorViewManagerVMware vCenter
+ ServerVMware vCenter Server 5.5.0 build-1750787 (Sim)VMware,
+ Inc.5.5.01750787 (Sim)INTL000linux-x64vpxVirtualCenter5.5EAB4D846-C243-426B-A021-0547644CE59DVMware
+ VirtualCenter Server5.0VpxSettingsUserDirectorySessionManagerAuthorizationManagerPerfMgrScheduledTaskManagerAlarmManagerEventManagerTaskManagerExtensionManagerCustomizationSpecManagerCustomFieldsManagerDiagMgrLicenseManagerSearchIndexFileManagervirtualDiskManagerSnmpSystemProvCheckerCompatCheckerOvfManagerIpPoolManagerDVSManagerHostProfileManagerClusterProfileManagerMoComplianceManagerLocalizationManagerStorageResourceManager\n\n"}
+ headers:
+ Cache-Control: [no-cache]
+ Connection: [Keep-Alive]
+ Content-Length: ['3332']
+ Content-Type: [text/xml; charset=utf-8]
+ Date: ['Tue, 29 Jul 2014 18:58:40 GMT']
+ Set-Cookie: ['vmware_soap_session="52d6ea56-0052-259f-e3f6-8ea7a7f349cb"; Path=/;
+ HttpOnly; Secure; ']
+ status: {code: 200, message: OK}
+- request:
+ body: '
+
+
+
+ <_this type="SessionManager">SessionManagermy_usermy_password
+
+ '
+ headers:
+ Accept-Encoding: ['gzip, deflate']
+ Content-Type: [text/xml; charset=UTF-8]
+ Cookie: ['vmware_soap_session="52d6ea56-0052-259f-e3f6-8ea7a7f349cb"; Path=/;
+ HttpOnly; Secure; ']
+ SOAPAction: ['"urn:vim25/4.1"']
+ method: POST
+ uri: https://vcsa:443/sdk
+ response:
+ body: {string: "\n\n\n52c20b61-24c3-f233-a549-d36d3ae68e14my_usermy_user
+ 2014-07-29T18:58:41.001537Z2014-07-29T18:58:41.001537Zenen\n\n"}
+ headers:
+ Cache-Control: [no-cache]
+ Connection: [Keep-Alive]
+ Content-Length: ['665']
+ Content-Type: [text/xml; charset=utf-8]
+ Date: ['Tue, 29 Jul 2014 18:58:41 GMT']
+ status: {code: 200, message: OK}
+- request:
+ body: '
+
+
+
+ <_this type="ServiceInstance">ServiceInstance
+
+ '
+ headers:
+ Accept-Encoding: ['gzip, deflate']
+ Content-Type: [text/xml; charset=UTF-8]
+ Cookie: ['vmware_soap_session="52d6ea56-0052-259f-e3f6-8ea7a7f349cb"; Path=/;
+ HttpOnly; Secure; ']
+ SOAPAction: ['"urn:vim25/4.1"']
+ method: POST
+ uri: https://vcsa:443/sdk
+ response:
+ body: {string: "\n\n\ngroup-d1propertyCollectorViewManagerVMware vCenter
+ ServerVMware vCenter Server 5.5.0 build-1750787 (Sim)VMware,
+ Inc.5.5.01750787 (Sim)INTL000linux-x64vpxVirtualCenter5.5EAB4D846-C243-426B-A021-0547644CE59DVMware
+ VirtualCenter Server5.0VpxSettingsUserDirectorySessionManagerAuthorizationManagerPerfMgrScheduledTaskManagerAlarmManagerEventManagerTaskManagerExtensionManagerCustomizationSpecManagerCustomFieldsManagerDiagMgrLicenseManagerSearchIndexFileManagervirtualDiskManagerSnmpSystemProvCheckerCompatCheckerOvfManagerIpPoolManagerDVSManagerHostProfileManagerClusterProfileManagerMoComplianceManagerLocalizationManagerStorageResourceManager\n\n"}
+ headers:
+ Cache-Control: [no-cache]
+ Connection: [Keep-Alive]
+ Content-Length: ['3332']
+ Content-Type: [text/xml; charset=utf-8]
+ Date: ['Tue, 29 Jul 2014 18:58:41 GMT']
+ status: {code: 200, message: OK}
+- request:
+ body: '
+
+
+
+ <_this type="ServiceInstance">ServiceInstance
+
+ '
+ headers:
+ Accept-Encoding: ['gzip, deflate']
+ Content-Type: [text/xml; charset=UTF-8]
+ Cookie: ['vmware_soap_session="52d6ea56-0052-259f-e3f6-8ea7a7f349cb"; Path=/;
+ HttpOnly; Secure; ']
+ SOAPAction: ['"urn:vim25/4.1"']
+ method: POST
+ uri: https://vcsa:443/sdk
+ response:
+ body: {string: "\n\n\ngroup-d1propertyCollectorViewManagerVMware vCenter
+ ServerVMware vCenter Server 5.5.0 build-1750787 (Sim)VMware,
+ Inc.5.5.01750787 (Sim)INTL000linux-x64vpxVirtualCenter5.5EAB4D846-C243-426B-A021-0547644CE59DVMware
+ VirtualCenter Server5.0VpxSettingsUserDirectorySessionManagerAuthorizationManagerPerfMgrScheduledTaskManagerAlarmManagerEventManagerTaskManagerExtensionManagerCustomizationSpecManagerCustomFieldsManagerDiagMgrLicenseManagerSearchIndexFileManagervirtualDiskManagerSnmpSystemProvCheckerCompatCheckerOvfManagerIpPoolManagerDVSManagerHostProfileManagerClusterProfileManagerMoComplianceManagerLocalizationManagerStorageResourceManager\n\n"}
+ headers:
+ Cache-Control: [no-cache]
+ Connection: [Keep-Alive]
+ Content-Length: ['3332']
+ Content-Type: [text/xml; charset=utf-8]
+ Date: ['Tue, 29 Jul 2014 18:58:41 GMT']
+ status: {code: 200, message: OK}
+- request:
+ body: '
+
+
+
+ <_this type="PropertyCollector">propertyCollectorLicenseManagerfalselicenseAssignmentManagerLicenseManagerfalse1
+
+ '
+ headers:
+ Accept-Encoding: ['gzip, deflate']
+ Content-Type: [text/xml; charset=UTF-8]
+ Cookie: ['vmware_soap_session="52d6ea56-0052-259f-e3f6-8ea7a7f349cb"; Path=/;
+ HttpOnly; Secure; ']
+ SOAPAction: ['"urn:vim25/4.1"']
+ method: POST
+ uri: https://vcsa:443/sdk
+ response:
+ body: {string: "\n\n\nLicenseManagerlicenseAssignmentManagerLicenseAssignmentManager\n\n"}
+ headers:
+ Cache-Control: [no-cache]
+ Connection: [Keep-Alive]
+ Content-Length: ['652']
+ Content-Type: [text/xml; charset=utf-8]
+ Date: ['Tue, 29 Jul 2014 18:58:41 GMT']
+ status: {code: 200, message: OK}
+- request:
+ body: '
+
+
+
+ <_this type="LicenseAssignmentManager">LicenseAssignmentManager
+
+ '
+ headers:
+ Accept-Encoding: ['gzip, deflate']
+ Content-Type: [text/xml; charset=UTF-8]
+ Cookie: ['vmware_soap_session="52d6ea56-0052-259f-e3f6-8ea7a7f349cb"; Path=/;
+ HttpOnly; Secure; ']
+ SOAPAction: ['"urn:vim25/4.1"']
+ method: POST
+ uri: https://vcsa:443/sdk
+ response:
+ body: {string: "
+
+
+
+ ServerFaultCode
+
+
+
+ unknownReason
+
+
+
+"}
+ headers:
+ Cache-Control: [no-cache]
+ Connection: [Keep-Alive]
+ Content-Type: [text/xml; charset=utf-8]
+ Date: ['Tue, 29 Jul 2014 18:58:41 GMT']
+ Transfer-Encoding: [chunked]
+ status: {code: 500, message: Internal Server Error}
+version: 1
diff --git a/tests/test_fault_deserializer.py b/tests/test_fault_deserializer.py
new file mode 100644
index 000000000..d9abdd20a
--- /dev/null
+++ b/tests/test_fault_deserializer.py
@@ -0,0 +1,55 @@
+# VMware vSphere Python SDK
+# Copyright (c) 2008-2014 VMware, Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from tests import fixtures_path
+import unittest
+import vcr
+
+from pyVim import connect
+from pyVmomi import SoapStubAdapter
+from pyVmomi import vim
+
+class DeserializerTests(unittest.TestCase):
+
+ @vcr.use_cassette('test_unknown_fault.yaml',
+ cassette_library_dir=fixtures_path, record_mode='once')
+ def test_unknown_fault(self):
+ # see: http://python3porting.com/noconv.html
+ si = connect.Connect(host='vcsa',
+ user='my_user',
+ pwd='my_password')
+ content = si.RetrieveContent()
+ lm = content.licenseManager
+ # NOTE (hartsock): assertIsNotNone does not work in Python 2.6
+ self.assertTrue(lm is not None)
+ lam = lm.licenseAssignmentManager
+ # NOTE (hartsock): assertIsNotNone does not work in Python 2.6
+ self.assertTrue(lam is not None)
+ # cassette is altered to raise a fault here:
+ fault = None
+ try:
+ lam.QueryAssignedLicenses()
+ except Exception as ex:
+ # NOTE (hartsock): not using 'assertRaises' so we can inspect obj
+ fault = ex
+ # NOTE (hartsock): assertIsNotNone does not work in Python 2.6
+ self.assertTrue(fault is not None) # only until 2.6 support is dropped.
+ # Observe that the malformed XML was reported up the stack to the
+ # user so that field reports will contain SOAP message information.
+ self.assertTrue(' '
+ ' '
+ 'unknownReason'
+ ' '
+ '' in str(fault))