* Malformed Faults: introduces test for fault handler * introduces ParserError which includes the malformed XML * testing demonstrates ParserError includes malformed XML closes: https://github.com/vmware/pyvmomi/issues/72
		
			
				
	
	
		
			1598 lines
		
	
	
		
			60 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1598 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("&", "&").replace(">", ">").replace("<", "<")
 | 
						|
    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()
 | 
						|
      tunnelSocket = resp.fp
 | 
						|
      if resp.status != 200:
 | 
						|
        raise httplib.HTTPException("{0} {1}".format(resp.status, resp.reason))
 | 
						|
      retval = http_client.HTTPSConnection(path)
 | 
						|
      retval.sock = _SocketWrapper(tunnelSocket,
 | 
						|
                                   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()
 |