Files
deb-python-pyvmomi/pyVmomi/SoapAdapter.py
Arnon Yaari 423ec22610 white space consistency
tab length is 3 spaces, except for the code in ReadDocument
2014-09-16 10:51:36 +03:00

1597 lines
60 KiB
Python

# VMware vSphere Python SDK
# Copyright (c) 2008-2013 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 __future__ import absolute_import
from six import PY2
from six import PY3
from six import reraise
from six.moves import http_client
if PY3:
long = int
basestring = str
from six import u
import sys
import os
import socket
import subprocess
import time
from six.moves.urllib.parse import urlparse
from datetime import datetime
from xml.parsers.expat import ParserCreate
# We have our own escape functionality.
# from xml.sax.saxutils import escape
if PY2:
from cStringIO import StringIO
if PY3:
from io import StringIO
from pyVmomi.VmomiSupport import *
from pyVmomi.StubAdapterAccessorImpl import StubAdapterAccessorMixin
import pyVmomi.Iso8601
import base64
from xml.parsers.expat import ExpatError
import copy
import contextlib
try:
USERWORLD = os.uname()[0] == 'VMkernel'
except:
USERWORLD = False
# Timeout value used for idle connections in client connection pool.
# Default value is 900 seconds (15 minutes).
CONNECTION_POOL_IDLE_TIMEOUT_SEC = 900
NS_SEP = " "
XML_ENCODING = 'UTF-8'
XML_HEADER = '<?xml version="1.0" encoding="{0}"?>'.format(XML_ENCODING)
XMLNS_SOAPENC = "http://schemas.xmlsoap.org/soap/encoding/"
XMLNS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/"
XSI_TYPE = XMLNS_XSI + NS_SEP + u('type')
# Note: Must make a copy to use the SOAP_NSMAP
# TODO: Change to frozendict, if available
SOAP_NSMAP = { XMLNS_SOAPENC: 'soapenc', XMLNS_SOAPENV: 'soapenv',
XMLNS_XSI: 'xsi', XMLNS_XSD: 'xsd' }
SOAP_ENVELOPE_TAG = "{0}:Envelope".format(SOAP_NSMAP[XMLNS_SOAPENV])
SOAP_HEADER_TAG = "{0}:Header".format(SOAP_NSMAP[XMLNS_SOAPENV])
SOAP_FAULT_TAG = "{0}:Fault".format(SOAP_NSMAP[XMLNS_SOAPENV])
SOAP_BODY_TAG = "{0}:Body".format(SOAP_NSMAP[XMLNS_SOAPENV])
SOAP_ENVELOPE_START = '<{0} '.format(SOAP_ENVELOPE_TAG) + \
' '.join(['xmlns:' + prefix + '="' + urn + '"' \
for urn, prefix in iteritems(SOAP_NSMAP)]) + \
'>\n'
SOAP_ENVELOPE_END = "\n</{0}>".format(SOAP_ENVELOPE_TAG)
SOAP_HEADER_START = "<{0}>".format(SOAP_HEADER_TAG)
SOAP_HEADER_END = "</{0}>".format(SOAP_HEADER_TAG)
SOAP_BODY_START = "<{0}>".format(SOAP_BODY_TAG)
SOAP_BODY_END = "</{0}>".format(SOAP_BODY_TAG)
SOAP_START = SOAP_ENVELOPE_START + SOAP_BODY_START + '\n'
SOAP_END = '\n' + SOAP_BODY_END + SOAP_ENVELOPE_END
WSSE_PREFIX = "wsse"
WSSE_HEADER_TAG = "{0}:Security".format(WSSE_PREFIX)
WSSE_NS_URL = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
WSSE_NS = 'xmlns:{0}="{1}"'.format(WSSE_PREFIX, WSSE_NS_URL)
WSSE_HEADER_START = "<{0} {1}>".format(WSSE_HEADER_TAG, WSSE_NS)
WSSE_HEADER_END = "</{0}>".format(WSSE_HEADER_TAG)
## MethodFault type
MethodFault = GetVmodlType("vmodl.MethodFault")
## Localized MethodFault type
LocalizedMethodFault = GetVmodlType("vmodl.LocalizedMethodFault")
def encode(string, encoding):
if PY2:
return string.encode(encoding)
return u(string)
## Escape <, >, &
def XmlEscape(xmlStr):
escaped = xmlStr.replace("&", "&amp;").replace(">", "&gt;").replace("<", "&lt;")
return escaped
## Get the start tag, end tag, and text handlers of a class
def GetHandlers(obj):
return (obj.StartElementHandler,
obj.EndElementHandler,
obj.CharacterDataHandler,
obj.StartNamespaceDeclHandler,
obj.EndNamespaceDeclHandler)
## Set the start tag, end tag, and text handlers of a parser
def SetHandlers(obj, handlers):
(obj.StartElementHandler,
obj.EndElementHandler,
obj.CharacterDataHandler,
obj.StartNamespaceDeclHandler,
obj.EndNamespaceDeclHandler) = handlers
## Serialize an object
#
# This function assumes CheckField(info, val) was already called
# @param val the value to serialize
# @param info the field
# @param version the version
# @param nsMap a dict of xml ns -> prefix
# @return the serialized object as a string
def Serialize(val, info=None, version=None, nsMap=None, encoding=None):
if version is None:
try:
if isinstance(val, list):
itemType = val.Item
version = itemType._version
else:
if val is None:
# neither val nor version is given
return ''
# Pick up the version from val
version = val._version
except AttributeError:
version = BASE_VERSION
if info is None:
info = Object(name="object", type=object, version=version, flags=0)
writer = StringIO()
SoapSerializer(writer, version, nsMap, encoding).Serialize(val, info)
return writer.getvalue()
## Serialize fault detail
#
# Serializes a fault as the content of the detail element in a
# soapenv:Fault (i.e. without a LocalizedMethodFault wrapper).
#
# This function assumes CheckField(info, val) was already called
# @param val the value to serialize
# @param info the field
# @param version the version
# @param nsMap a dict of xml ns -> prefix
# @return the serialized object as a string
def SerializeFaultDetail(val, info=None, version=None, nsMap=None, encoding=None):
if version is None:
try:
if not isinstance(val, MethodFault):
raise TypeError('{0} is not a MethodFault'.format(str(val)))
version = val._version
except AttributeError:
version = BASE_VERSION
if info is None:
info = Object(name="object", type=object, version=version, flags=0)
writer = StringIO()
SoapSerializer(writer, version, nsMap, encoding).SerializeFaultDetail(val, info)
return writer.getvalue()
## SOAP serializer
#
class SoapSerializer:
""" SoapSerializer """
## Serializer constructor
#
# @param writer File writer
# @param version the version
# @param nsMap a dict of xml ns -> prefix
def __init__(self, writer, version, nsMap, encoding):
""" Constructor """
self.writer = writer
self.version = version
self.nsMap = nsMap and nsMap or {}
self.encoding = encoding and encoding or XML_ENCODING
for ns, prefix in iteritems(self.nsMap):
if prefix == '':
self.defaultNS = ns
break
else:
self.defaultNS = ''
# Additional attr for outermost tag
self.outermostAttrs = ''
# Fill in required xmlns, if not defined
for nsPrefix, ns, attrName in [('xsi', XMLNS_XSI, 'xsiPrefix'),
('xsd', XMLNS_XSD, 'xsdPrefix')]:
prefix = self.nsMap.get(ns)
if not prefix:
prefix = nsPrefix
self.outermostAttrs += ' xmlns:{0}="{1}"'.format(prefix, ns)
self.nsMap = self.nsMap.copy()
self.nsMap[ns] = prefix
setattr(self, attrName, prefix + ":")
## Serialize an object
#
# This function assumes CheckField(info, val) was already called
# @param val the value to serialize
# @param info the field
def Serialize(self, val, info):
""" Serialize an object """
self._Serialize(val, info, self.defaultNS)
## Serialize fault detail
#
# Serializes a fault as the content of the detail element in a
# soapenv:Fault (i.e. without a LocalizedMethodFault wrapper).
#
# This function assumes CheckField(info, val) was already called
# @param val the value to serialize
# @param info the field
def SerializeFaultDetail(self, val, info):
""" Serialize an object """
self._SerializeDataObject(val, info, '', self.defaultNS)
def _NSPrefix(self, ns):
""" Get xml ns prefix. self.nsMap must be set """
if ns == self.defaultNS:
return ''
prefix = self.nsMap[ns]
return prefix and prefix + ':' or ''
def _QName(self, typ, defNS):
""" Get fully qualified wsdl name (prefix:name) """
attr = ''
ns, name = GetQualifiedWsdlName(typ)
if ns == defNS:
prefix = ''
else:
try:
prefix = self.nsMap[ns]
except KeyError:
# We have not seen this ns before
prefix = ns.split(':', 1)[-1]
attr = ' xmlns:{0}="{1}"'.format(prefix, ns)
return attr, prefix and prefix + ':' + name or name
## Serialize an object (internal)
#
# @param val the value to serialize
# @param info the field
# @param defNS the default namespace
def _Serialize(self, val, info, defNS):
""" Serialize an object """
if not IsChildVersion(self.version, info.version):
return
if val is None:
if info.flags & F_OPTIONAL:
return
else:
raise TypeError('Field "{0}" is not optional'.format(info.name))
elif isinstance(val, list) and len(val) == 0:
if info.type is object:
# Make sure an empty array assigned to Any is typed
if not isinstance(val, Array):
raise TypeError('Field "{0}": Cannot assign empty native python array to an Any'.format(info.name))
elif info.flags & F_OPTIONAL:
# Skip optional non-Any
return
else:
raise TypeError('Field "{0}" not optional'.format(info.name))
if self.outermostAttrs:
attr = self.outermostAttrs
self.outermostAttrs = None
else:
attr = ''
currDefNS = defNS
# Emit default ns if tag ns is not the same
currTagNS = GetWsdlNamespace(info.version)
if currTagNS != defNS:
attr += ' xmlns="{0}"'.format(currTagNS)
currDefNS = currTagNS
if isinstance(val, DataObject):
if isinstance(val, MethodFault):
newVal = LocalizedMethodFault(fault=val, localizedMessage=val.msg)
if info.type is object:
faultType = object
else:
faultType = LocalizedMethodFault
newInfo = Object(name=info.name, type=faultType,
version=info.version, flags=info.flags)
self._SerializeDataObject(newVal, newInfo, attr, currDefNS)
else:
self._SerializeDataObject(val, info, attr, currDefNS)
elif isinstance(val, ManagedObject):
if info.type is object:
nsattr, qName = self._QName(ManagedObject, currDefNS)
attr += '{0} {1}type="{2}"'.format(nsattr, self.xsiPrefix, qName)
if val._serverGuid is not None:
attr += ' serverGuid="{0}"'.format(val._serverGuid)
# val in vim type attr is not namespace qualified
# TODO: Add a new "typens" attr?
ns, name = GetQualifiedWsdlName(Type(val))
attr += ' type="{0}"'.format(name)
self.writer.write('<{0}{1}>{2}</{3}>'.format(info.name, attr,
encode(val._moId, self.encoding),
info.name))
elif isinstance(val, list):
if info.type is object:
itemType = val.Item
if (itemType is ManagedMethod or itemType is PropertyPath
or itemType is type):
tag = 'string'
typ = GetVmodlType("string[]")
elif issubclass(itemType, ManagedObject):
tag = 'ManagedObjectReference'
typ = ManagedObject.Array
else:
tag = GetWsdlName(itemType)
typ = Type(val)
nsattr, qName = self._QName(typ, currDefNS)
# For WSDL, since we set tag of ManagedObjects to ManagedObjectReferences,
# the name of its array should be ArrayOfManagedObjectReference
if qName.endswith("ArrayOfManagedObject"):
qName += "Reference"
attr += '{0} {1}type="{2}"'.format(nsattr, self.xsiPrefix, qName)
self.writer.write('<{0}{1}>'.format(info.name, attr))
itemInfo = Object(name=tag, type=itemType,
version=info.version, flags=info.flags)
for it in val:
self._Serialize(it, itemInfo, currDefNS)
self.writer.write('</{0}>'.format(info.name))
else:
itemType = info.type.Item
itemInfo = Object(name=info.name, type=itemType,
version=info.version, flags=info.flags)
for it in val:
self._Serialize(it, itemInfo, defNS)
elif isinstance(val, type) or isinstance(val, type(Exception)):
if info.type is object:
attr += ' {0}type="{1}string"'.format(self.xsiPrefix, self.xsdPrefix)
self.writer.write('<{0}{1}>{2}</{0}>'.format(
info.name, attr, GetWsdlName(val)))
elif isinstance(val, ManagedMethod):
if info.type is object:
attr += ' {0}type="{1}string"'.format(self.xsiPrefix, self.xsdPrefix)
self.writer.write('<{0}{1}>{2}</{0}>'.format(
info.name, attr, val.info.wsdlName))
elif isinstance(val, datetime):
if info.type is object:
nsattr, qName = self._QName(Type(val), currDefNS)
attr += '{0} {1}type="{2}"'.format(nsattr, self.xsiPrefix, qName)
result = Iso8601.ISO8601Format(val)
self.writer.write('<{0}{1}>{2}</{0}>'.format(info.name, attr, result))
elif isinstance(val, binary):
if info.type is object:
nsattr, qName = self._QName(Type(val), currDefNS)
attr += '{0} {1}type="{2}"'.format(nsattr, self.xsiPrefix, qName)
result = base64.b64encode(val)
self.writer.write('<{0}{1}>{2}</{0}>'.format(info.name, attr, result))
elif isinstance(val, bool):
if info.type is object:
nsattr, qName = self._QName(Type(val), currDefNS)
attr += '{0} {1}type="{2}"'.format(nsattr, self.xsiPrefix, qName)
result = val and "true" or "false"
self.writer.write('<{0}{1}>{2}</{0}>'.format(info.name, attr, result))
else:
if info.type is object:
if isinstance(val, PropertyPath):
attr += ' {0}type="{1}string"'.format(self.xsiPrefix, self.xsdPrefix)
else:
nsattr, qName = self._QName(Type(val), currDefNS)
attr += '{0} {1}type="{2}"'.format(nsattr, self.xsiPrefix, qName)
if not isinstance(val, text_type):
# Use UTF-8 rather than self.encoding. self.encoding is for
# output of serializer, while 'val' is our input. And regardless
# of what our output is, our input should be always UTF-8. Yes,
# it means that if you emit output in other encoding than UTF-8,
# you cannot serialize it again once more. That's feature, not
# a bug.
val = str(val)
if PY2:
val = val.decode('UTF-8')
result = XmlEscape(val)
self.writer.write('<{0}{1}>{2}</{0}>'.format(info.name, attr,
encode(result,
self.encoding)))
## Serialize a a data object (internal)
#
# @param val the value to serialize
# @param info the field
# @param attr attributes to serialized in the outermost elementt
# @param currDefNS the current default namespace
def _SerializeDataObject(self, val, info, attr, currDefNS):
if info.flags & F_LINK:
# Attribute is a link and Object is present instead of its key.
# We need to serialize just the key and not the entire object
self._Serialize(val.key, info, currDefNS)
return
dynType = GetCompatibleType(Type(val), self.version)
if dynType != info.type:
nsattr, qName = self._QName(dynType, currDefNS)
attr += '{0} {1}type="{2}"'.format(nsattr, self.xsiPrefix, qName)
self.writer.write('<{0}{1}>'.format(info.name, attr))
if dynType is LocalizedMethodFault:
# Serialize a MethodFault as LocalizedMethodFault on wire
# See PR 670229
for prop in val._GetPropertyList():
propVal = getattr(val, prop.name)
if prop.name == 'fault':
propVal = copy.copy(propVal)
propVal.msg = None
self._SerializeDataObject(propVal, prop, '', currDefNS)
else:
self._Serialize(propVal, prop, currDefNS)
else:
for prop in val._GetPropertyList():
self._Serialize(getattr(val, prop.name), prop, 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.
# @param data the data to deserialize (a file object or string)
# @param resultType expected result type
# @param stub stub for moRef deserialization
# @return the deserialized object
def Deserialize(data, resultType=object, stub=None):
parser = ParserCreate(namespace_separator=NS_SEP)
ds = SoapDeserializer(stub)
ds.Deserialize(parser, resultType)
ReadDocument(parser, data)
return ds.GetResult()
## Expat deserializer namespace handler
class ExpatDeserializerNSHandlers:
def __init__(self, nsMap=None):
# nsMap is a dict of ns prefix to a stack (list) of namespaces
# The last element of the stack is current namespace
if not nsMap:
nsMap = {}
self.nsMap = nsMap
## Get current default ns
def GetCurrDefNS(self):
namespaces = self.nsMap.get(None)
if namespaces:
ns = namespaces[-1]
else:
ns = ""
return ns
## Get namespace and wsdl name from tag
def GetNSAndWsdlname(self, tag):
""" Map prefix:name tag into ns, name """
idx = tag.find(":")
if idx >= 0:
prefix, name = tag[:idx], tag[idx + 1:]
else:
prefix, name = None, tag
# Map prefix to ns
ns = self.nsMap[prefix][-1]
return ns, name
## Handle namespace begin
def StartNamespaceDeclHandler(self, prefix, uri):
namespaces = self.nsMap.get(prefix)
if namespaces:
namespaces.append(uri)
else:
self.nsMap[prefix] = [uri]
## Handle namespace end
def EndNamespaceDeclHandler(self, prefix):
self.nsMap[prefix].pop()
## SOAP -> Python Deserializer
class SoapDeserializer(ExpatDeserializerNSHandlers):
## Constructor
#
# @param self self
# @param stub Stub adapter to use for deserializing moRefs
def __init__(self, stub=None, version=None):
ExpatDeserializerNSHandlers.__init__(self)
self.stub = stub
if version:
self.version = version
elif self.stub:
self.version = self.stub.version
else:
self.version = None
self.result = None
## Deserialize a SOAP object
#
# @param self self
# @param parser an expat parser
# @param resultType the static type of the result
# @param isFault true if the response is a fault response
# @param nsMap a dict of prefix -> [xml ns stack]
# @return the deserialized object
def Deserialize(self, parser, resultType=object, isFault=False, nsMap=None):
self.isFault = isFault
self.parser = parser
self.origHandlers = GetHandlers(parser)
SetHandlers(parser, GetHandlers(self))
self.resultType = resultType
self.stack = []
self.data = ""
self.serverGuid = None
if issubclass(resultType, list):
self.result = resultType()
else:
self.result = None
if not nsMap:
nsMap = {}
self.nsMap = nsMap
## Get the result of deserialization
# The links will not be resolved. User needs to explicitly resolve them
# using LinkResolver.
def GetResult(self):
return self.result
def SplitTag(self, tag):
""" Split tag into ns, name """
idx = tag.find(NS_SEP)
if idx >= 0:
return tag[:idx], tag[idx + 1:]
else:
return "", tag
def LookupWsdlType(self, ns, name, allowManagedObjectReference=False):
""" Lookup wsdl type. Handle special case for some vmodl version """
try:
return GetWsdlType(ns, name)
except KeyError:
if allowManagedObjectReference:
if name.endswith('ManagedObjectReference') and ns == XMLNS_VMODL_BASE:
return GetWsdlType(ns, name[:-len('Reference')])
# WARNING!!! This is a temporary hack to get around server not
# honoring @service tag (see bug 521744). Once it is fix, I am
# going to back out this change
if name.endswith('ManagedObjectReference') and allowManagedObjectReference:
return GetWsdlType(XMLNS_VMODL_BASE, name[:-len('Reference')])
return GuessWsdlType(name)
## Handle an opening XML tag
def StartElementHandler(self, tag, attr):
self.data = ""
self.serverGuid = None
deserializeAsLocalizedMethodFault = True
if not self.stack:
if self.isFault:
ns, name = self.SplitTag(tag)
objType = self.LookupWsdlType(ns, name[:-5])
# Only top level soap fault should be deserialized as method fault
deserializeAsLocalizedMethodFault = False
else:
objType = self.resultType
elif isinstance(self.stack[-1], list):
objType = self.stack[-1].Item
elif isinstance(self.stack[-1], DataObject):
# TODO: Check ns matches DataObject's namespace
ns, name = self.SplitTag(tag)
objType = self.stack[-1]._GetPropertyInfo(name).type
# LocalizedMethodFault <fault> tag should be deserialized as method fault
if name == "fault" and isinstance(self.stack[-1], LocalizedMethodFault):
deserializeAsLocalizedMethodFault = False
else:
raise TypeError("Invalid type for tag {0}".format(tag))
xsiType = attr.get(XSI_TYPE)
if xsiType:
# Ignore dynamic type for TypeName, MethodName, PropertyPath
# @bug 150459
if not (objType is type or objType is ManagedMethod or \
objType is PropertyPath):
ns, name = self.GetNSAndWsdlname(xsiType)
dynType = self.LookupWsdlType(ns, name, allowManagedObjectReference=True)
# TODO: Should be something like...
# dynType must be narrower than objType, except for
# ManagedObjectReference
if not (issubclass(dynType, list) and issubclass(objType, list)):
objType = dynType
else:
if issubclass(objType, list):
objType = objType.Item
if self.version:
objType = GetCompatibleType(objType, self.version)
if issubclass(objType, ManagedObject):
typeAttr = attr[u('type')]
# val in vim type attr is not namespace qualified
# However, this doesn't hurt to strip out namespace
# TODO: Get the ns from "typens" attr?
ns, name = self.GetNSAndWsdlname(typeAttr)
if u('serverGuid') in attr:
self.serverGuid = attr[u('serverGuid')]
self.stack.append(GuessWsdlType(name))
elif issubclass(objType, DataObject) or issubclass(objType, list):
if deserializeAsLocalizedMethodFault and issubclass(objType, Exception):
objType = LocalizedMethodFault
self.stack.append(objType())
else:
self.stack.append(objType)
## Handle a closing XML tag
def EndElementHandler(self, tag):
try:
obj = self.stack.pop()
except IndexError:
SetHandlers(self.parser, self.origHandlers)
handler = self.parser.EndElementHandler
del self.parser, self.origHandlers, self.stack, self.resultType
if handler:
return handler(tag)
return
data = self.data
if isinstance(obj, type) or isinstance(obj, type(Exception)):
if obj is type:
if data is None or data == '':
obj = None
else:
try:
# val in type val is not namespace qualified
# However, this doesn't hurt to strip out namespace
ns, name = self.GetNSAndWsdlname(data)
obj = GuessWsdlType(name)
except KeyError:
raise TypeError(data)
elif obj is ManagedMethod:
# val in Method val is not namespace qualified
# However, this doesn't hurt to strip out namespace
ns, name = self.GetNSAndWsdlname(data)
obj = GuessWsdlMethod(name)
elif obj is bool:
if data == "0" or data.lower() == "false":
obj = bool(False)
elif data == "1" or data.lower() == "true":
obj = bool(True)
else:
raise TypeError(data)
elif obj is binary:
# Raise type error if decode failed
obj = obj(base64.b64decode(data))
elif obj is str:
try:
obj = str(data)
except ValueError:
obj = data
elif obj is datetime:
obj = pyVmomi.Iso8601.ParseISO8601(data)
if not obj:
raise TypeError(data)
# issubclass is very expensive. Test last
elif issubclass(obj, ManagedObject):
obj = obj(data, self.stub, self.serverGuid)
elif issubclass(obj, Enum):
obj = getattr(obj, data)
else:
obj = obj(data)
elif isinstance(obj, LocalizedMethodFault):
obj.fault.msg = obj.localizedMessage
obj = obj.fault
if self.stack:
top = self.stack[-1]
if isinstance(top, list):
top.append(obj)
elif isinstance(top, DataObject):
ns, name = self.SplitTag(tag)
info = top._GetPropertyInfo(name)
if not isinstance(obj, list) and issubclass(info.type, list):
getattr(top, info.name).append(obj)
else:
setattr(top, info.name, obj)
else:
ns, name = self.SplitTag(tag)
setattr(top, name, obj)
else:
if not isinstance(obj, list) and issubclass(self.resultType, list):
self.result.append(obj)
else:
self.result = obj
SetHandlers(self.parser, self.origHandlers)
del self.parser, self.origHandlers, self.stack, self.resultType
## Handle text data
def CharacterDataHandler(self, data):
self.data += data
## SOAP Response Deserializer class
class SoapResponseDeserializer(ExpatDeserializerNSHandlers):
## Constructor
#
# @param self self
# @param stub Stub adapter to use for deserializing moRefs
def __init__(self, stub):
ExpatDeserializerNSHandlers.__init__(self)
self.stub = stub
self.deser = SoapDeserializer(stub)
self.soapFaultTag = XMLNS_SOAPENV + NS_SEP + "Fault"
## Deserialize a SOAP response
#
# @param self self
# @param response the response (a file object or a string)
# @param resultType expected result type
# @param nsMap a dict of prefix -> [xml ns stack]
# @return the deserialized object
def Deserialize(self, response, resultType, nsMap=None):
self.resultType = resultType
self.stack = []
self.msg = ""
self.deser.result = None
self.isFault = False
self.parser = ParserCreate(namespace_separator=NS_SEP)
try: # buffer_text only in python >= 2.3
self.parser.buffer_text = True
except AttributeError:
pass
if not nsMap:
nsMap = {}
self.nsMap = nsMap
SetHandlers(self.parser, GetHandlers(self))
ReadDocument(self.parser, response)
result = self.deser.GetResult()
if self.isFault:
if result is None:
result = GetVmodlType("vmodl.RuntimeFault")()
result.msg = self.msg
del self.resultType, self.stack, self.parser, self.msg, self.data, self.nsMap
return result
## Handle an opening XML tag
def StartElementHandler(self, tag, attr):
self.data = ""
if tag == self.soapFaultTag:
self.isFault = True
elif self.isFault and tag == "detail":
self.deser.Deserialize(self.parser, object, True, self.nsMap)
elif tag.endswith("Response"):
self.deser.Deserialize(self.parser, self.resultType, False, self.nsMap)
## Handle text data
def CharacterDataHandler(self, data):
self.data += data
## Handle a closing XML tag
def EndElementHandler(self, tag):
if self.isFault and tag == "faultstring":
try:
self.msg = str(self.data)
except ValueError:
self.msg = self.data
## Base class that implements common functionality for stub adapters.
## Method that must be provided by the implementation class:
## -- InvokeMethod(ManagedObject mo, Object methodInfo, Object[] args)
class StubAdapterBase(StubAdapterAccessorMixin):
def __init__(self, version):
StubAdapterAccessorMixin.__init__(self)
self.ComputeVersionInfo(version)
## Compute the version information for the specified namespace
#
# @param ns the namespace
def ComputeVersionInfo(self, version):
versionNS = GetVersionNamespace(version)
if versionNS.find("/") >= 0:
self.versionId = '"urn:{0}"'.format(versionNS)
else:
self.versionId = ''
self.version = version
## Base class that implements common functionality for SOAP-based stub adapters.
## Method that must be provided by the implementation class:
## -- InvokeMethod(ManagedObject mo, Object methodInfo, Object[] args)
class SoapStubAdapterBase(StubAdapterBase):
## Serialize a VMOMI request to SOAP
#
# @param version API version
# @param mo the 'this'
# @param info method info
# @param args method arguments
# @return the serialized request
def SerializeRequest(self, mo, info, args):
if not IsChildVersion(self.version, info.version):
raise GetVmodlType("vmodl.fault.MethodNotFound")(receiver=mo,
method=info.name)
nsMap = SOAP_NSMAP.copy()
defaultNS = GetWsdlNamespace(self.version)
nsMap[defaultNS] = ''
# Add xml header and soap envelope
result = [XML_HEADER, '\n', SOAP_ENVELOPE_START]
# Add request context and samlToken to soap header, if exists
reqContexts = GetRequestContext()
samlToken = getattr(self, 'samlToken', None)
if reqContexts or samlToken:
result.append(SOAP_HEADER_START)
for key, val in iteritems(reqContexts):
# Note: Support req context of string type only
if not isinstance(val, basestring):
raise TypeError("Request context key ({0}) has non-string value ({1}) of {2}".format(key, val, type(val)))
ret = Serialize(val,
Object(name=key, type=str, version=self.version),
self.version,
nsMap)
result.append(ret)
if samlToken:
result.append('{0} {1} {2}'.format(WSSE_HEADER_START,
samlToken,
WSSE_HEADER_END))
result.append(SOAP_HEADER_END)
result.append('\n')
# Serialize soap body
result.extend([SOAP_BODY_START,
'<{0} xmlns="{1}">'.format(info.wsdlName, defaultNS),
Serialize(mo, Object(name="_this", type=ManagedObject,
version=self.version),
self.version, nsMap)])
# Serialize soap request parameters
for (param, arg) in zip(info.params, args):
result.append(Serialize(arg, param, self.version, nsMap))
result.extend(['</{0}>'.format(info.wsdlName), SOAP_BODY_END, SOAP_ENVELOPE_END])
return ''.join(result)
## Subclass of HTTPConnection that connects over a Unix domain socket
## instead of a TCP port. The path of the socket is passed in place of
## the hostname. Fairly gross but does the job.
# NOTE (hartsock): rewrite this class as a wrapper, see HTTPSConnectionWrapper
# below for a guide.
class UnixSocketConnection(http_client.HTTPConnection):
# The HTTPConnection ctor expects a single argument, which it interprets
# as the host to connect to; for UnixSocketConnection, we instead interpret
# the parameter as the filesystem path of the Unix domain socket.
def __init__(self, path):
# Pass '' as the host to HTTPConnection; it doesn't really matter
# what we pass (since we've overridden the connect method) as long
# as it's a valid string.
http_client.HTTPConnection.__init__(self, '')
self.path = path
def connect(self):
# Hijack the connect method of HTTPConnection to connect to the
# specified Unix domain socket instead. Obey the same contract
# as HTTPConnection.connect, which puts the socket in self.sock.
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect(self.path)
self.sock = sock
try:
# The ssl module is not available in python versions less than 2.6
SSL_THUMBPRINTS_SUPPORTED = True
import ssl
import hashlib
def _VerifyThumbprint(thumbprint, connection):
'''If there is a thumbprint, connect to the server and verify that the
SSL certificate matches the given thumbprint. An exception is thrown
if there is a mismatch.'''
if thumbprint and isinstance(connection, http_client.HTTPSConnection):
if not connection.sock:
connection.connect()
derCert = connection.sock.getpeercert(True)
sha1 = hashlib.sha1()
sha1.update(derCert)
sha1Digest = sha1.hexdigest().lower()
if sha1Digest != thumbprint:
raise Exception("Server has wrong SHA1 thumbprint: {0} "
"(required) != {1} (server)".format(
thumbprint, sha1Digest))
# Function used to wrap sockets with SSL
_SocketWrapper = ssl.wrap_socket
except ImportError:
SSL_THUMBPRINTS_SUPPORTED = False
def _VerifyThumbprint(thumbprint, connection):
if thumbprint and isinstance(connection, http_client.HTTPSConnection):
raise Exception(
"Thumbprint verification not supported on python < 2.6")
def _SocketWrapper(rawSocket, keyfile, certfile, *args, **kwargs):
wrappedSocket = socket.ssl(rawSocket, keyfile, certfile)
return http_client.FakeSocket(rawSocket, wrappedSocket)
## https connection wrapper
#
# NOTE (hartsock): do not override core library types or implementations
# directly because this makes brittle code that is too easy to break and
# closely tied to implementation details we do not control. Instead, wrap
# the core object to introduce additional behaviors.
#
# Purpose:
# Support ssl.wrap_socket params which are missing from httplib
# HTTPSConnection (e.g. ca_certs)
# Note: Only works iff the ssl params are passing in as kwargs
class HTTPSConnectionWrapper(object):
def __init__(self, *args, **kwargs):
wrapped = http_client.HTTPSConnection(*args, **kwargs)
# Extract ssl.wrap_socket param unknown to httplib.HTTPConnection,
# and push back the params in connect()
self._sslArgs = {}
tmpKwargs = kwargs.copy()
for key in ["server_side", "cert_reqs", "ssl_version", "ca_certs",
"do_handshake_on_connect", "suppress_ragged_eofs",
"ciphers"]:
if key in tmpKwargs:
self._sslArgs[key] = tmpKwargs.pop(key)
self._wrapped = wrapped
## Override connect to allow us to pass in additional ssl paramters to
# ssl.wrap_socket (e.g. cert_reqs, ca_certs for ca cert verification)
def connect(self, wrapped):
if len(self._sslArgs) == 0 or hasattr(self, '_baseclass'):
# No override
return wrapped.connect
# Big hack. We have to copy and paste the httplib connect fn for
# each python version in order to handle extra ssl paramters. Yuk!
if hasattr(self, "source_address"):
# Python 2.7
sock = socket.create_connection((self.host, self.port),
self.timeout, self.source_address)
if wrapped._tunnel_host:
wrapped.sock = sock
wrapped._tunnel()
wrapped.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, **self._sslArgs)
elif hasattr(self, "timeout"):
# Python 2.6
sock = socket.create_connection((self.host, self.port), self.timeout)
wrapped.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, **self._sslArgs)
return wrapped.connect
# TODO: Additional verification of peer cert if needed
#cert_reqs = self._sslArgs.get("cert_reqs", ssl.CERT_NONE)
#ca_certs = self._sslArgs.get("ca_certs", None)
#if cert_reqs != ssl.CERT_NONE and ca_certs:
# if hasattr(self.sock, "getpeercert"):
# # TODO: verify peer cert
# dercert = self.sock.getpeercert(False)
# # pemcert = ssl.DER_cert_to_PEM_cert(dercert)
def __getattr__(self, item):
if item == 'connect':
return self.connect(self._wrapped)
return getattr(self._wrapped, item)
## Stand-in for the HTTPSConnection class that will connect to a proxy and
## issue a CONNECT command to start an SSL tunnel.
class SSLTunnelConnection(object):
# @param proxyPath The path to pass to the CONNECT command.
def __init__(self, proxyPath):
self.proxyPath = proxyPath
# Connects to a proxy server and initiates a tunnel to the destination
# specified by proxyPath. If successful, a new HTTPSConnection is returned.
#
# @param path The destination URL path.
# @param key_file The SSL key file to use when wrapping the socket.
# @param cert_file The SSL certificate file to use when wrapping the socket.
# @param kwargs In case caller passed in extra parameters not handled by
# SSLTunnelConnection
def __call__(self, path, key_file=None, cert_file=None, **kwargs):
# Don't pass any keyword args that HTTPConnection won't understand.
for arg in kwargs.keys():
if arg not in ("port", "strict", "timeout", "source_address"):
del kwargs[arg]
tunnel = http_client.HTTPConnection(path, **kwargs)
tunnel.request('CONNECT', self.proxyPath)
resp = tunnel.getresponse()
if resp.status != 200:
raise http_client.HTTPException("{0} {1}".format(resp.status, resp.reason))
retval = http_client.HTTPSConnection(path)
retval.sock = _SocketWrapper(tunnel.sock,
keyfile=key_file, certfile=cert_file)
return retval
class GzipReader:
GZIP = 1
DEFLATE = 2
def __init__(self, rfile, encoding=GZIP, readChunkSize=512):
self.rfile = rfile
self.chunks = []
self.bufSize = 0 # Remaining buffer
assert(encoding in (GzipReader.GZIP, GzipReader.DEFLATE))
self.encoding = encoding
self.unzip = None
self.readChunkSize = readChunkSize
def _CreateUnzip(self, firstChunk):
import zlib
if self.encoding == GzipReader.GZIP:
wbits = zlib.MAX_WBITS + 16
elif self.encoding == GzipReader.DEFLATE:
# Sniff out real deflate format
chunkLen = len(firstChunk)
# Assume raw deflate
wbits = -zlib.MAX_WBITS
if firstChunk[:3] == ['\x1f', '\x8b', '\x08']:
# gzip: Apache mod_deflate will send gzip. Yurk!
wbits = zlib.MAX_WBITS + 16
elif chunkLen >= 2:
b0 = ord(firstChunk[0])
b1 = ord(firstChunk[1])
if (b0 & 0xf) == 8 and (((b0 * 256 + b1)) % 31) == 0:
# zlib deflate
wbits = min(((b0 & 0xf0) >> 4) + 8, zlib.MAX_WBITS)
else:
assert(False)
self.unzip = zlib.decompressobj(wbits)
return self.unzip
def read(self, bytes=-1):
chunks = self.chunks
bufSize = self.bufSize
while bufSize < bytes or bytes == -1:
# Read and decompress
chunk = self.rfile.read(self.readChunkSize)
if self.unzip == None:
self._CreateUnzip(chunk)
if chunk:
inflatedChunk = self.unzip.decompress(chunk)
bufSize += len(inflatedChunk)
chunks.append(inflatedChunk)
else:
# Returns whatever we have
break
if bufSize <= bytes or bytes == -1:
leftoverBytes = 0
leftoverChunks = []
else:
leftoverBytes = bufSize - bytes
# Adjust last chunk to hold only the left over bytes
lastChunk = chunks.pop()
chunks.append(lastChunk[:-leftoverBytes])
leftoverChunks = [lastChunk[-leftoverBytes:]]
self.chunks = leftoverChunks
self.bufSize = leftoverBytes
buf = "".join(chunks)
return buf
## SOAP stub adapter object
class SoapStubAdapter(SoapStubAdapterBase):
## Constructor
#
# The endpoint can be specified individually as either a host/port
# combination, or with a URL (using a url= keyword).
#
# @param self self
# @param host host
# @param port port (pass negative port number for no SSL)
# @param **** Deprecated. Please use version instead **** ns API namespace
# @param path location of SOAP VMOMI service
# @param url URL (overrides host, port, path if set)
# @param sock unix domain socket path (overrides host, port, url if set)
# @param poolSize size of HTTP connection pool
# @param certKeyFile The path to the PEM-encoded SSL private key file.
# @param certFile The path to the PEM-encoded SSL certificate file.
# @param httpProxyHost The host name of the proxy server.
# @param httpProxyPort The proxy server port.
# @param sslProxyPath Path to use when tunneling through VC's reverse proxy.
# @param thumbprint The SHA1 thumbprint of the server's SSL certificate.
# Some use a thumbprint of the form xx:xx:xx..:xx. We ignore the ":"
# characters. If set to None, any thumbprint is accepted.
# @param cacertsFile CA certificates file in PEM format
# @param version API version
# @param connectionPoolTimeout Timeout in secs for idle connections in client pool. Use -1 to disable any timeout.
# @param samlToken SAML Token that should be used in SOAP security header for login
def __init__(self, host='localhost', port=443, ns=None, path='/sdk',
url=None, sock=None, poolSize=5,
certFile=None, certKeyFile=None,
httpProxyHost=None, httpProxyPort=80, sslProxyPath=None,
thumbprint=None, cacertsFile=None, version=None,
acceptCompressedResponses=True,
connectionPoolTimeout=CONNECTION_POOL_IDLE_TIMEOUT_SEC,
samlToken=None):
if ns:
assert(version is None)
version = versionMap[ns]
elif not version:
version = 'vim.version.version1'
SoapStubAdapterBase.__init__(self, version=version)
self.cookie = ""
if sock:
self.scheme = UnixSocketConnection
# Store sock in the host member variable because that's where
# the UnixSocketConnection ctor expects to find it -- see above
self.host = sock
elif url:
scheme, self.host, urlpath = urlparse.urlparse(url)[:3]
# Only use the URL path if it's sensible, otherwise use the path
# keyword argument as passed in.
if urlpath not in ('', '/'):
path = urlpath
self.scheme = scheme == "http" and http_client.HTTPConnection \
or scheme == "https" and HTTPSConnectionWrapper
else:
port, self.scheme = port < 0 and (-port, http_client.HTTPConnection) \
or (port, HTTPSConnectionWrapper)
if host.find(':') != -1: # is IPv6?
host = '[' + host + ']'
self.host = '{0}:{1}'.format(host, port)
self.path = path
if thumbprint:
self.thumbprint = thumbprint.replace(":", "").lower()
if len(self.thumbprint) != 40:
raise Exception("Invalid SHA1 thumbprint -- {0}".format(thumbprint))
else:
self.thumbprint = None
if sslProxyPath:
self.scheme = SSLTunnelConnection(sslProxyPath)
elif httpProxyHost:
if self.scheme == HTTPSConnectionWrapper:
self.scheme = SSLTunnelConnection(self.host)
else:
if url:
self.path = url
else:
self.path = "http://{0}/{1}".format(self.host, path)
# Swap the actual host with the proxy.
self.host = "{0}:{1}".format(httpProxyHost, httpProxyPort)
self.poolSize = poolSize
self.pool = []
self.connectionPoolTimeout = connectionPoolTimeout
self.lock = threading.Lock()
self.schemeArgs = {}
if certKeyFile:
self.schemeArgs['key_file'] = certKeyFile
if certFile:
self.schemeArgs['cert_file'] = certFile
if cacertsFile:
self.schemeArgs['ca_certs'] = cacertsFile
self.schemeArgs['cert_reqs'] = ssl.CERT_REQUIRED
self.samlToken = samlToken
self.requestModifierList = []
self._acceptCompressedResponses = acceptCompressedResponses
# Context modifier used to modify the SOAP request.
# @param func The func that takes in the serialized message and modifies the
# the request. The func is appended to the requestModifierList and then
# popped after the request is modified.
@contextlib.contextmanager
def requestModifier(self, func):
self.requestModifierList.append(func)
try:
yield
finally:
self.requestModifierList.pop()
## Invoke a managed method
#
# @param self self
# @param mo the 'this'
# @param info method info
# @param args arguments
# @param outerStub If not-None, this should be a reference to the wrapping
# stub adapter. Any ManagedObject references returned from this method
# will have outerStub in their _stub field. Note that this also changes
# the return type to a tuple containing the HTTP status and the
# deserialized object so that it's easier to distinguish an API error from
# a connection error.
def InvokeMethod(self, mo, info, args, outerStub=None):
if outerStub is None:
outerStub = self
headers = {'Cookie' : self.cookie,
'SOAPAction' : self.versionId,
'Content-Type': 'text/xml; charset={0}'.format(XML_ENCODING)}
if self._acceptCompressedResponses:
headers['Accept-Encoding'] = 'gzip, deflate'
req = self.SerializeRequest(mo, info, args)
for modifier in self.requestModifierList:
req = modifier(req)
conn = self.GetConnection()
try:
conn.request('POST', self.path, req, headers)
resp = conn.getresponse()
except (socket.error, http_client.HTTPException):
# The server is probably sick, drop all of the cached connections.
self.DropConnections()
raise
# 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 case-sensitive header for compatibility
cookie = resp.getheader('Set-Cookie')
status = resp.status
if cookie:
self.cookie = cookie
if status == 200 or status == 500:
try:
fd = resp
encoding = resp.getheader('Content-Encoding', 'identity').lower()
if encoding == 'gzip':
fd = GzipReader(resp, encoding=GzipReader.GZIP)
elif encoding == 'deflate':
fd = GzipReader(resp, encoding=GzipReader.DEFLATE)
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 exc
else:
resp.read()
self.ReturnConnection(conn)
if outerStub != self:
return (status, obj)
if status == 200:
return obj
else:
raise obj # pylint: disable-msg=E0702
else:
conn.close()
raise http_client.HTTPException("{0} {1}".format(resp.status, resp.reason))
## Clean up connection pool to throw away idle timed-out connections
# SoapStubAdapter lock must be acquired before this method is called.
def _CloseIdleConnections(self):
if self.connectionPoolTimeout >= 0:
currentTime = time.time()
idleConnections = []
for conn, lastAccessTime in self.pool:
idleTime = currentTime - lastAccessTime
if idleTime >= self.connectionPoolTimeout:
i = self.pool.index((conn, lastAccessTime))
idleConnections = self.pool[i:]
self.pool = self.pool[:i]
break
for conn, _ in idleConnections:
conn.close()
## Get a HTTP connection from the pool
def GetConnection(self):
self.lock.acquire()
self._CloseIdleConnections()
if self.pool:
result, _ = self.pool.pop(0)
self.lock.release()
else:
self.lock.release()
result = self.scheme(self.host, **self.schemeArgs)
# Always disable NAGLE algorithm
#
# Python httplib (2.6 and below) is splitting a http request into 2
# packets (header and body). It first send the header, but will not
# send the body until it receives the ack (for header) from server
# [NAGLE at work]. The delayed ack time on ESX is around 40 - 100 ms
# (depends on version) and can go up to 200 ms. This effectively slow
# down each pyVmomi call by the same amount of time.
#
# Disable NAGLE on client will force both header and body packets to
# get out immediately, and eliminated the delay
#
# This bug is fixed in python 2.7, however, only if the request
# body is a string (which is true for now)
if sys.version_info[:2] < (2,7):
self.DisableNagle(result)
_VerifyThumbprint(self.thumbprint, result)
return result
## Drop all cached connections to the server.
def DropConnections(self):
self.lock.acquire()
oldConnections = self.pool
self.pool = []
self.lock.release()
for conn, _ in oldConnections:
conn.close()
## Return a HTTP connection to the pool
def ReturnConnection(self, conn):
self.lock.acquire()
self._CloseIdleConnections()
if len(self.pool) < self.poolSize:
self.pool.insert(0, (conn, time.time()))
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
def DisableNagle(self, conn):
# Override connections' connect function to force disable NAGLE
if self.scheme != UnixSocketConnection and getattr(conn, "connect"):
orgConnect = conn.connect
def ConnectDisableNagle(*args, **kwargs):
orgConnect(*args, **kwargs)
sock = getattr(conn, "sock")
if sock:
try:
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
except Exception:
pass
conn.connect = ConnectDisableNagle
HEADER_SECTION_END = '\r\n\r\n'
## Parse an HTTP response into its headers and body
def ParseHttpResponse(httpResponse):
headerEnd = httpResponse.find(HEADER_SECTION_END)
if headerEnd == -1:
return ('', '')
headerEnd += len(HEADER_SECTION_END);
headerText = httpResponse[:headerEnd]
bodyText = httpResponse[headerEnd:]
return (headerText, bodyText)
## SOAP-over-stdio stub adapter object
class SoapCmdStubAdapter(SoapStubAdapterBase):
## Constructor
#
# @param self self
# @param cmd command to execute
# @param ns API namespace
def __init__(self, cmd, version='vim.version.version1'):
SoapStubAdapterBase.__init__(self, version=version)
self.cmd = cmd
self.systemError = GetVmodlType('vmodl.fault.SystemError')
## Invoke a managed method
#
# @param self self
# @param mo the 'this'
# @param info method info
# @param args arguments
def InvokeMethod(self, mo, info, args):
argv = self.cmd.split()
req = self.SerializeRequest(mo, info, args)
env = dict(os.environ)
env['REQUEST_METHOD'] = 'POST'
env['CONTENT_LENGTH'] = str(len(req))
env['HTTP_SOAPACTION'] = self.versionId[1:-1]
p = subprocess.Popen(argv,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env)
(outText, errText) = p.communicate(req)
if p.returncode < 0:
# Process died with a signal
errText = "Process terminated with signal {0}\n{1}".format(-p.returncode, errText)
raise self.systemError(msg=errText, reason=errText)
try:
(responseHeaders, responseBody) = ParseHttpResponse(outText)
obj = SoapResponseDeserializer(self).Deserialize(responseBody, info.result)
except:
errText = "Failure parsing SOAP response ({0})\n{1}}".format(outText, errText)
raise self.systemError(msg=errText, reason=errText)
if p.returncode == 0:
return obj
elif obj is None:
raise self.systemError(msg=errText, reason=errText)
else:
raise obj # pylint: disable-msg=E0702
class SessionOrientedStub(StubAdapterBase):
'''A session-oriented stub adapter that will relogin to the destination if a
session-oriented exception is thrown.
Here's an example. First, we setup the communication substrate:
>>> soapStub = SoapStubAdapter(host="192.168.1.2", ns="vim25/5.0")
Create a SessionOrientedStub that uses the stub we just created for talking
to the server:
>>> from pyVim.connect import VimSessionOrientedStub
>>> sessionStub = VimSessionOrientedStub(
... soapStub,
... VimSessionOrientedStub.makeUserLoginMethod("root", "vmware"))
Perform some privileged operations without needing to explicitly login:
>>> si = Vim.ServiceInstance("ServiceInstance", sessionStub)
>>> si.content.sessionManager.sessionList
>>> si.content.sessionManager.Logout()
>>> si.content.sessionManager.sessionList
'''
STATE_UNAUTHENTICATED = 0
STATE_AUTHENTICATED = 1
SESSION_EXCEPTIONS = tuple()
def __init__(self, soapStub, loginMethod, retryDelay=0.1, retryCount=4):
'''Construct a SessionOrientedStub.
The stub starts off in the "unauthenticated" state, so it will call the
loginMethod on the first invocation of a method. If a communication error
is encountered, the stub will wait for retryDelay seconds and then try to
call the method again. If the server throws an exception that is in the
SESSION_EXCEPTIONS tuple, it will be caught and the stub will transition
back into the "unauthenticated" state so that another login will be
performed.
@param soapStub The communication substrate.
@param loginMethod A function that takes a single parameter, soapStub, and
performs the necessary operations to authenticate with the server.
@param retryDelay The amount of time to sleep before retrying after a
communication error.
@param retryCount The number of times to retry connecting to the server.
'''
assert callable(loginMethod)
assert retryCount >= 0
StubAdapterBase.__init__(self, version=soapStub.version)
self.lock = threading.Lock()
self.soapStub = soapStub
self.state = self.STATE_UNAUTHENTICATED
self.loginMethod = loginMethod
self.retryDelay = retryDelay
self.retryCount = retryCount
def InvokeMethod(self, mo, info, args):
# This retry logic is replicated in InvokeAccessor and the two copies need
# to be in sync
retriesLeft = self.retryCount
while retriesLeft > 0:
try:
if self.state == self.STATE_UNAUTHENTICATED:
self._CallLoginMethod()
# Invoke the method
status, obj = self.soapStub.InvokeMethod(mo, info, args, self)
except (socket.error, http_client.HTTPException, ExpatError):
if self.retryDelay and retriesLeft:
time.sleep(self.retryDelay)
retriesLeft -= 1
continue
if status == 200:
# Normal return from the server, return the object to the caller.
return obj
# An exceptional return from the server
if isinstance(obj, self.SESSION_EXCEPTIONS):
# Our session might've timed out, change our state and retry.
self._SetStateUnauthenticated()
else:
# It's an exception from the method that was called, send it up.
raise obj
# Raise any socket/httplib errors caught above.
raise SystemError()
## Retrieve a managed property
#
# @param self self
# @param mo managed object
# @param info property info
def InvokeAccessor(self, mo, info):
# This retry logic is replicated in InvokeMethod and the two copies need
# to be in sync
retriesLeft = self.retryCount
while retriesLeft > 0:
try:
if self.state == self.STATE_UNAUTHENTICATED:
self._CallLoginMethod()
# Invoke the method
obj = StubAdapterBase.InvokeAccessor(self, mo, info)
except (socket.error, http_client.HTTPException, ExpatError):
if self.retryDelay and retriesLeft:
time.sleep(self.retryDelay)
retriesLeft -= 1
continue
except Exception as e:
if isinstance(e, self.SESSION_EXCEPTIONS):
# Our session might've timed out, change our state and retry.
self._SetStateUnauthenticated()
else:
raise e
return obj
# Raise any socket/httplib errors caught above.
raise SystemError()
## Handle the login method call
#
# This method calls the login method on the soap stub and changes the state
# to authenticated
def _CallLoginMethod(self):
try:
self.lock.acquire()
if self.state == self.STATE_UNAUTHENTICATED:
self.loginMethod(self.soapStub)
self.state = self.STATE_AUTHENTICATED
finally:
self.lock.release()
## Change the state to unauthenticated
def _SetStateUnauthenticated(self):
self.lock.acquire()
if self.state == self.STATE_AUTHENTICATED:
self.state = self.STATE_UNAUTHENTICATED
self.lock.release()