Merge pull request #308 from tianhao64/master

More 6.0 related changes and bug fixes.
This commit is contained in:
Tianhao He 2015-11-30 10:01:00 -08:00
commit ef29bcf442
6 changed files with 129 additions and 37 deletions

View File

@ -1,13 +1,11 @@
language: python language: python
python: python:
- "2.6"
- "2.7" - "2.7"
- "pypy" - "pypy"
- "3.3" - "3.3"
- "3.4" - "3.4"
before_install: before_install:
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi
- pip install -r requirements.txt - pip install -r requirements.txt
- pip install -r test-requirements.txt - pip install -r test-requirements.txt

View File

@ -34,6 +34,7 @@ import requests
from requests.auth import HTTPBasicAuth from requests.auth import HTTPBasicAuth
from pyVmomi import vim, vmodl, SoapStubAdapter, SessionOrientedStub from pyVmomi import vim, vmodl, SoapStubAdapter, SessionOrientedStub
from pyVmomi.SoapAdapter import CONNECTION_POOL_IDLE_TIMEOUT_SEC
from pyVmomi.VmomiSupport import nsMap, versionIdMap, versionMap, IsChildVersion from pyVmomi.VmomiSupport import nsMap, versionIdMap, versionMap, IsChildVersion
from pyVmomi.VmomiSupport import GetServiceVersions from pyVmomi.VmomiSupport import GetServiceVersions
@ -177,7 +178,7 @@ class VimSessionOrientedStub(SessionOrientedStub):
def Connect(host='localhost', port=443, user='root', pwd='', def Connect(host='localhost', port=443, user='root', pwd='',
service="hostd", adapter="SOAP", namespace=None, path="/sdk", service="hostd", adapter="SOAP", namespace=None, path="/sdk",
version=None, keyFile=None, certFile=None, version=None, keyFile=None, certFile=None, thumbprint=None,
sslContext=None): sslContext=None):
""" """
Connect to the specified server, login and return the service Connect to the specified server, login and return the service
@ -212,6 +213,8 @@ def Connect(host='localhost', port=443, user='root', pwd='',
@type keyFile: string @type keyFile: string
@param certFile: ssl cert file path @param certFile: ssl cert file path
@type certFile: string @type certFile: string
@param thumbprint: host cert thumbprint
@type thumbprint: string
@param sslContext: SSL Context describing the various SSL options. It is only @param sslContext: SSL Context describing the various SSL options. It is only
supported in Python 2.7.9 or higher. supported in Python 2.7.9 or higher.
@type sslContext: SSL.Context @type sslContext: SSL.Context
@ -233,7 +236,7 @@ def Connect(host='localhost', port=443, user='root', pwd='',
elif not version: elif not version:
version="vim.version.version6" version="vim.version.version6"
si, stub = __Login(host, port, user, pwd, service, adapter, version, path, si, stub = __Login(host, port, user, pwd, service, adapter, version, path,
keyFile, certFile, sslContext) keyFile, certFile, thumbprint, sslContext)
SetSi(si) SetSi(si)
return si return si
@ -268,7 +271,7 @@ def GetLocalTicket(si, user):
## connected service instance object. ## connected service instance object.
def __Login(host, port, user, pwd, service, adapter, version, path, def __Login(host, port, user, pwd, service, adapter, version, path,
keyFile, certFile, sslContext): keyFile, certFile, thumbprint, sslContext):
""" """
Private method that performs the actual Connect and returns a Private method that performs the actual Connect and returns a
connected service instance object. connected service instance object.
@ -293,6 +296,8 @@ def __Login(host, port, user, pwd, service, adapter, version, path,
@type keyFile: string @type keyFile: string
@param certFile: ssl cert file path @param certFile: ssl cert file path
@type certFile: string @type certFile: string
@param thumbprint: host cert thumbprint
@type thumbprint: string
@param sslContext: SSL Context describing the various SSL options. It is only @param sslContext: SSL Context describing the various SSL options. It is only
supported in Python 2.7.9 or higher. supported in Python 2.7.9 or higher.
@type sslContext: SSL.Context @type sslContext: SSL.Context
@ -304,7 +309,8 @@ def __Login(host, port, user, pwd, service, adapter, version, path,
# Create the SOAP stub adapter # Create the SOAP stub adapter
stub = SoapStubAdapter(host, port, version=version, path=path, stub = SoapStubAdapter(host, port, version=version, path=path,
certKeyFile=keyFile, certFile=certFile, sslContext=sslContext) certKeyFile=keyFile, certFile=certFile,
thumbprint=thumbprint, sslContext=sslContext)
# Get Service instance # Get Service instance
si = vim.ServiceInstance("ServiceInstance", stub) si = vim.ServiceInstance("ServiceInstance", stub)
@ -555,10 +561,54 @@ def __FindSupportedVersion(protocol, server, port, path, preferredApiVersions, s
return desiredVersion return desiredVersion
return None return None
def SmartStubAdapter(host='localhost', port=443, path='/sdk',
url=None, sock=None, poolSize=5,
certFile=None, certKeyFile=None,
httpProxyHost=None, httpProxyPort=80, sslProxyPath=None,
thumbprint=None, cacertsFile=None, preferredApiVersions=None,
acceptCompressedResponses=True,
connectionPoolTimeout=CONNECTION_POOL_IDLE_TIMEOUT_SEC,
samlToken=None, sslContext=None):
"""
Determine the most preferred API version supported by the specified server,
then create a soap stub adapter using that version
The parameters are the same as for pyVmomi.SoapStubAdapter except for
version which is renamed to prefferedApiVersions
@param preferredApiVersions: Acceptable API version(s) (e.g. vim.version.version3)
If a list of versions is specified the versions should
be ordered from most to least preferred. If None is
specified, the list of versions support by pyVmomi will
be used.
@type preferredApiVersions: string or string list
"""
if preferredApiVersions is None:
preferredApiVersions = GetServiceVersions('vim25')
supportedVersion = __FindSupportedVersion('https' if port > 0 else 'http',
host,
port,
path,
preferredApiVersions,
sslContext)
if supportedVersion is None:
raise Exception("%s:%s is not a VIM server" % (host, port))
return SoapStubAdapter(host=host, port=port, path=path,
url=url, sock=sock, poolSize=poolSize,
certFile=certFile, certKeyFile=certKeyFile,
httpProxyHost=httpProxyHost, httpProxyPort=httpProxyPort,
sslProxyPath=sslProxyPath, thumbprint=thumbprint,
cacertsFile=cacertsFile, version=supportedVersion,
acceptCompressedResponses=acceptCompressedResponses,
connectionPoolTimeout=connectionPoolTimeout,
samlToken=samlToken, sslContext=sslContext)
def SmartConnect(protocol='https', host='localhost', port=443, user='root', pwd='', def SmartConnect(protocol='https', host='localhost', port=443, user='root', pwd='',
service="hostd", path="/sdk", service="hostd", path="/sdk",
preferredApiVersions=None, sslContext=None): preferredApiVersions=None,
keyFile=None, certFile=None, thumbprint=None, sslContext=None):
""" """
Determine the most preferred API version supported by the specified server, Determine the most preferred API version supported by the specified server,
then connect to the specified server using that API version, login and return then connect to the specified server using that API version, login and return
@ -591,6 +641,12 @@ def SmartConnect(protocol='https', host='localhost', port=443, user='root', pwd=
specified, the list of versions support by pyVmomi will specified, the list of versions support by pyVmomi will
be used. be used.
@type preferredApiVersions: string or string list @type preferredApiVersions: string or string list
@param keyFile: ssl key file path
@type keyFile: string
@param certFile: ssl cert file path
@type certFile: string
@param thumbprint: host cert thumbprint
@type thumbprint: string
@param sslContext: SSL Context describing the various SSL options. It is only @param sslContext: SSL Context describing the various SSL options. It is only
supported in Python 2.7.9 or higher. supported in Python 2.7.9 or higher.
@type sslContext: SSL.Context @type sslContext: SSL.Context
@ -618,6 +674,9 @@ def SmartConnect(protocol='https', host='localhost', port=443, user='root', pwd=
adapter='SOAP', adapter='SOAP',
version=supportedVersion, version=supportedVersion,
path=path, path=path,
keyFile=keyFile,
certFile=certFile,
thumbprint=thumbprint,
sslContext=sslContext) sslContext=sslContext)
def OpenUrlWithBasicAuth(url, user='root', pwd=''): def OpenUrlWithBasicAuth(url, user='root', pwd=''):

View File

@ -104,6 +104,17 @@ def encode(string, encoding):
return string.encode(encoding) return string.encode(encoding)
return u(string) return u(string)
## Thumbprint mismatch exception
#
class ThumbprintMismatchException(Exception):
def __init__(self, expected, actual):
Exception.__init__(self, "Server has wrong SHA1 thumbprint: %s "
"(required) != %s (server)" % (
expected, actual))
self.expected = expected
self.actual = actual
## Escape <, >, & ## Escape <, >, &
def XmlEscape(xmlStr): def XmlEscape(xmlStr):
escaped = xmlStr.replace("&", "&amp;").replace(">", "&gt;").replace("<", "&lt;") escaped = xmlStr.replace("&", "&amp;").replace(">", "&gt;").replace("<", "&lt;")
@ -236,7 +247,7 @@ class SoapSerializer:
# @param info the field # @param info the field
def SerializeFaultDetail(self, val, info): def SerializeFaultDetail(self, val, info):
""" Serialize an object """ """ Serialize an object """
self._SerializeDataObject(val, info, '', self.defaultNS) self._SerializeDataObject(val, info, ' xsi:typ="{1}"'.format(val._wsdlName), self.defaultNS)
def _NSPrefix(self, ns): def _NSPrefix(self, ns):
""" Get xml ns prefix. self.nsMap must be set """ """ Get xml ns prefix. self.nsMap must be set """
@ -494,12 +505,7 @@ class ExpatDeserializerNSHandlers:
## Get current default ns ## Get current default ns
def GetCurrDefNS(self): def GetCurrDefNS(self):
namespaces = self.nsMap.get(None) return self._GetNamespaceFromPrefix()
if namespaces:
ns = namespaces[-1]
else:
ns = ""
return ns
## Get namespace and wsdl name from tag ## Get namespace and wsdl name from tag
def GetNSAndWsdlname(self, tag): def GetNSAndWsdlname(self, tag):
@ -510,9 +516,17 @@ class ExpatDeserializerNSHandlers:
else: else:
prefix, name = None, tag prefix, name = None, tag
# Map prefix to ns # Map prefix to ns
ns = self.nsMap[prefix][-1] ns = self._GetNamespaceFromPrefix(prefix)
return ns, name return ns, name
def _GetNamespaceFromPrefix(self, prefix = None):
namespaces = self.nsMap.get(prefix)
if namespaces:
ns = namespaces[-1]
else:
ns = ""
return ns
## Handle namespace begin ## Handle namespace begin
def StartNamespaceDeclHandler(self, prefix, uri): def StartNamespaceDeclHandler(self, prefix, uri):
namespaces = self.nsMap.get(prefix) namespaces = self.nsMap.get(prefix)
@ -930,9 +944,7 @@ try:
sha1.update(derCert) sha1.update(derCert)
sha1Digest = sha1.hexdigest().lower() sha1Digest = sha1.hexdigest().lower()
if sha1Digest != thumbprint: if sha1Digest != thumbprint:
raise Exception("Server has wrong SHA1 thumbprint: {0} " raise ThumbprintMismatchException(thumbprint, sha1Digest)
"(required) != {1} (server)".format(
thumbprint, sha1Digest))
# Function used to wrap sockets with SSL # Function used to wrap sockets with SSL
_SocketWrapper = ssl.wrap_socket _SocketWrapper = ssl.wrap_socket
@ -963,7 +975,7 @@ except ImportError:
class HTTPSConnectionWrapper(object): class HTTPSConnectionWrapper(object):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
wrapped = http_client.HTTPSConnection(*args, **kwargs) wrapped = http_client.HTTPSConnection(*args, **kwargs)
# Extract ssl.wrap_socket param unknown to httplib.HTTPConnection, # Extract ssl.wrap_socket param unknown to httplib.HTTPSConnection,
# and push back the params in connect() # and push back the params in connect()
self._sslArgs = {} self._sslArgs = {}
tmpKwargs = kwargs.copy() tmpKwargs = kwargs.copy()
@ -1028,11 +1040,13 @@ class SSLTunnelConnection(object):
# @param kwargs In case caller passed in extra parameters not handled by # @param kwargs In case caller passed in extra parameters not handled by
# SSLTunnelConnection # SSLTunnelConnection
def __call__(self, path, key_file=None, cert_file=None, **kwargs): def __call__(self, path, key_file=None, cert_file=None, **kwargs):
# Don't pass any keyword args that HTTPConnection won't understand. # Only pass in the named arguments that HTTPConnection constructor
for arg in kwargs.keys(): # understands
if arg not in ("port", "strict", "timeout", "source_address"): tmpKwargs = {}
del kwargs[arg] for key in http_client.HTTPConnection.__init__.__code__.co_varnames:
tunnel = http_client.HTTPConnection(path, **kwargs) if key in kwargs and key != 'self':
tmpKwargs[key] = kwargs[key]
tunnel = http_client.HTTPConnection(path, **tmpKwargs)
tunnel.request('CONNECT', self.proxyPath) tunnel.request('CONNECT', self.proxyPath)
resp = tunnel.getresponse() resp = tunnel.getresponse()
if resp.status != 200: if resp.status != 200:
@ -1164,7 +1178,7 @@ class SoapStubAdapter(SoapStubAdapterBase):
# the UnixSocketConnection ctor expects to find it -- see above # the UnixSocketConnection ctor expects to find it -- see above
self.host = sock self.host = sock
elif url: elif url:
scheme, self.host, urlpath = urlparse.urlparse(url)[:3] scheme, self.host, urlpath = urlparse(url)[:3]
# Only use the URL path if it's sensible, otherwise use the path # Only use the URL path if it's sensible, otherwise use the path
# keyword argument as passed in. # keyword argument as passed in.
if urlpath not in ('', '/'): if urlpath not in ('', '/'):
@ -1219,6 +1233,20 @@ class SoapStubAdapter(SoapStubAdapterBase):
self.requestModifierList = [] self.requestModifierList = []
self._acceptCompressedResponses = acceptCompressedResponses self._acceptCompressedResponses = acceptCompressedResponses
# Force a socket shutdown. Before python 2.7, ssl will fail to close
# the socket (http://bugs.python.org/issue10127).
# Not making this a part of the actual _HTTPSConnection since the internals
# of the httplib.HTTP*Connection seem to pass around the descriptors and
# depend on the behavior that close() still leaves the socket semi-functional.
if sys.version_info[:2] < (2,7):
def _CloseConnection(self, conn):
# import pdb; pdb.set_trace()
if self.scheme == HTTPSConnectionWrapper and conn.sock:
conn.sock.shutdown(socket.SHUT_RDWR)
conn.close()
else:
def _CloseConnection(self, conn):
conn.close()
# Context modifier used to modify the SOAP request. # Context modifier used to modify the SOAP request.
# @param func The func that takes in the serialized message and modifies the # @param func The func that takes in the serialized message and modifies the
@ -1287,7 +1315,7 @@ class SoapStubAdapter(SoapStubAdapterBase):
deserializer = SoapResponseDeserializer(outerStub) deserializer = SoapResponseDeserializer(outerStub)
obj = deserializer.Deserialize(fd, info.result) obj = deserializer.Deserialize(fd, info.result)
except Exception as exc: except Exception as exc:
conn.close() self._CloseConnection(conn)
# NOTE (hartsock): This feels out of place. As a rule the lexical # NOTE (hartsock): This feels out of place. As a rule the lexical
# context that opens a connection should also close it. However, # context that opens a connection should also close it. However,
# in this code the connection is passed around and closed in other # in this code the connection is passed around and closed in other
@ -1306,7 +1334,7 @@ class SoapStubAdapter(SoapStubAdapterBase):
else: else:
raise obj # pylint: disable-msg=E0702 raise obj # pylint: disable-msg=E0702
else: else:
conn.close() self._CloseConnection(conn)
raise http_client.HTTPException("{0} {1}".format(resp.status, resp.reason)) raise http_client.HTTPException("{0} {1}".format(resp.status, resp.reason))
## Clean up connection pool to throw away idle timed-out connections ## Clean up connection pool to throw away idle timed-out connections
@ -1324,7 +1352,7 @@ class SoapStubAdapter(SoapStubAdapterBase):
break break
for conn, _ in idleConnections: for conn, _ in idleConnections:
conn.close() self._CloseConnection(conn)
## Get a HTTP connection from the pool ## Get a HTTP connection from the pool
def GetConnection(self): def GetConnection(self):
@ -1365,7 +1393,7 @@ class SoapStubAdapter(SoapStubAdapterBase):
self.pool = [] self.pool = []
self.lock.release() self.lock.release()
for conn, _ in oldConnections: for conn, _ in oldConnections:
conn.close() self._CloseConnection(conn)
## Return a HTTP connection to the pool ## Return a HTTP connection to the pool
def ReturnConnection(self, conn): def ReturnConnection(self, conn):
@ -1380,7 +1408,7 @@ class SoapStubAdapter(SoapStubAdapterBase):
# NOTE (hartsock): this seems to violate good coding practice in that # NOTE (hartsock): this seems to violate good coding practice in that
# the lexical context that opens a connection should also be the # the lexical context that opens a connection should also be the
# same context responsible for closing it. # same context responsible for closing it.
conn.close() self._CloseConnection(conn)
## Disable nagle on a http connections ## Disable nagle on a http connections
def DisableNagle(self, conn): def DisableNagle(self, conn):

View File

@ -465,6 +465,9 @@ class ManagedObject(object):
self.__class__ == other.__class__ and \ self.__class__ == other.__class__ and \
self._serverGuid == other._serverGuid self._serverGuid == other._serverGuid
def __ne__(self, other):
return not(self == other)
def __hash__(self): def __hash__(self):
return str(self).__hash__() return str(self).__hash__()
@ -1248,11 +1251,15 @@ class _BuildVersions:
self._nsMap = {} self._nsMap = {}
def Add(self, version): def Add(self, version):
vmodlNs = version.split(".",1)[0] assert '.version.' in version, 'Invalid version %s' % version
if not (vmodlNs in self._verMap):
self._verMap[vmodlNs] = version vmodlNs = version.split(".version.", 1)[0].split(".")
if not (vmodlNs in self._nsMap): for idx in [1, len(vmodlNs)]:
self._nsMap[vmodlNs] = GetVersionNamespace(version) subVmodlNs = ".".join(vmodlNs[:idx])
if not (subVmodlNs in self._verMap):
self._verMap[subVmodlNs] = version
if not (subVmodlNs in self._nsMap):
self._nsMap[subVmodlNs] = GetVersionNamespace(version)
def Get(self, vmodlNs): def Get(self, vmodlNs):
return self._verMap[vmodlNs] return self._verMap[vmodlNs]

View File

@ -198,7 +198,7 @@ except ImportError:
pyVmomi.VmomiSupport.GetVmodlType("vmodl.DynamicData") pyVmomi.VmomiSupport.GetVmodlType("vmodl.DynamicData")
from pyVmomi.SoapAdapter import SoapStubAdapter, StubAdapterBase, SoapCmdStubAdapter, \ from pyVmomi.SoapAdapter import SoapStubAdapter, StubAdapterBase, SoapCmdStubAdapter, \
SessionOrientedStub SessionOrientedStub, ThumbprintMismatchException
types = pyVmomi.VmomiSupport.types types = pyVmomi.VmomiSupport.types

View File

@ -1,5 +1,5 @@
[tox] [tox]
envlist = py26,py27,py33,py34 envlist = py27,py33,py34
[testenv] [testenv]
deps = -rtest-requirements.txt deps = -rtest-requirements.txt
commands = commands =