From dac8a79a481f0cd481a388bad158073c6102fae7 Mon Sep 17 00:00:00 2001 From: Shawn Hartsock Date: Tue, 29 Jul 2014 16:51:19 -0400 Subject: [PATCH] python3: KeyError uses '%' not format method * Malformed Faults: introduces test for fault handler * fixes KeyError raises statements to use 'format' instead partial: https://github.com/vmware/pyvmomi/issues/72 partial: https://github.com/vmware/pyvmomi/issues/55 --- pyVmomi/SoapAdapter.py | 49 +++-- pyVmomi/VmomiSupport.py | 18 +- tests/fixtures/test_unknown_fault.yaml | 274 +++++++++++++++++++++++++ tests/test_fault_deserializer.py | 49 +++++ 4 files changed, 364 insertions(+), 26 deletions(-) create mode 100644 tests/fixtures/test_unknown_fault.yaml create mode 100644 tests/test_fault_deserializer.py diff --git a/pyVmomi/SoapAdapter.py b/pyVmomi/SoapAdapter.py index de50e1583..89dbb43ee 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,24 @@ def _SerializeDataObject(self, val, info, attr, currDefNS): self.writer.write(''.format(info.name)) +class ParserError(KeyError): + def __init__(self, *args, **kwargs): + super(ParserError, self).__init__(*args, **kwargs) + + +def Parse(parser, data): + try: + if isinstance(data, str): + parser.Parse(data) + else: + parser.ParseFile(data) + except Exception as ex: + line = parser.CurrentLineNumber + col = parser.CurrentColumnNumber + pe = ParserError("{0} line:{1}, col:{2}".format(str(ex), line, col)) + (ec, ev, tb) = sys.exc_info() + reraise(ParserError, pe, tb) + ## Deserialize an object from a file or string # # This function will deserialize one top-level XML node. @@ -453,10 +471,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) + Parse(parser, data) return ds.GetResult() @@ -582,11 +597,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 +772,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) + Parse(self.parser, response) result = self.deser.GetResult() if self.isFault: if result is None: @@ -1241,10 +1249,10 @@ 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') + 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 +1265,13 @@ 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() # The server might be sick, drop all of the cached connections. self.DropConnections() - raise + raise exc else: resp.read() self.ReturnConnection(conn) diff --git a/pyVmomi/VmomiSupport.py b/pyVmomi/VmomiSupport.py index a0de125ad..fcaa2aaef 100644 --- a/pyVmomi/VmomiSupport.py +++ b/pyVmomi/VmomiSupport.py @@ -991,7 +991,7 @@ def _SetWsdlType(ns, wsdlName, typ): # @return type if found else throws KeyError def GetWsdlType(ns, name): if ns is None or name is None: - raise KeyError("%s %s" % (ns, name)) + raise KeyError("{0} {1}".format(ns, name)) with _lazyLock: # Check if the type is loaded in the map @@ -1003,14 +1003,20 @@ def GetWsdlType(ns, name): try: return GetWsdlType(ns, name[7:]).Array except KeyError: - raise KeyError("%s %s" % (ns, name)) + raise KeyError("{0} {1}".format(ns, name)) else: # Type is not loaded yet, load it typ = _LoadVmodlType(_wsdlDefMap[(ns, name)][0]) if typ: return typ - raise KeyError("%s %s" % (ns, name)) + raise KeyError("{0} {1}".format(ns, name)) + + +class UnknownWsdlTypeError(KeyError): + def __init__(self, *args, **kwargs): + super(UnknownWsdlTypeError, self).__init__(*args, **kwargs) + ## Guess the type from wsdlname with no ns # WARNING! This should not be used in general, as there is no guarantee for @@ -1026,8 +1032,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 @@ -1203,7 +1209,7 @@ def GetWsdlMethod(ns, wsdlName): LoadManagedType(*method) return _wsdlMethodMap[(ns, wsdlName)] else: - raise KeyError("%s %s" % (ns, name)) + raise KeyError("{0} {1}".format(ns, name)) ## Guess the method from wsdlname with no ns # WARNING! This should not be used in general, as there is no guarantee for 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..b2d2772b6 --- /dev/null +++ b/tests/test_fault_deserializer.py @@ -0,0 +1,49 @@ +# 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 + lam.QueryAssignedLicenses() + 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) + self.assertTrue('detail' in str(fault))