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
python:
- "2.6"
- "2.7"
- "pypy"
- "3.3"
- "3.4"
before_install:
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi
- pip install -r requirements.txt
- pip install -r test-requirements.txt

View File

@ -34,6 +34,7 @@ import requests
from requests.auth import HTTPBasicAuth
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 GetServiceVersions
@ -177,7 +178,7 @@ class VimSessionOrientedStub(SessionOrientedStub):
def Connect(host='localhost', port=443, user='root', pwd='',
service="hostd", adapter="SOAP", namespace=None, path="/sdk",
version=None, keyFile=None, certFile=None,
version=None, keyFile=None, certFile=None, thumbprint=None,
sslContext=None):
"""
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
@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
supported in Python 2.7.9 or higher.
@type sslContext: SSL.Context
@ -233,7 +236,7 @@ def Connect(host='localhost', port=443, user='root', pwd='',
elif not version:
version="vim.version.version6"
si, stub = __Login(host, port, user, pwd, service, adapter, version, path,
keyFile, certFile, sslContext)
keyFile, certFile, thumbprint, sslContext)
SetSi(si)
return si
@ -268,7 +271,7 @@ def GetLocalTicket(si, user):
## connected service instance object.
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
connected service instance object.
@ -293,6 +296,8 @@ def __Login(host, port, user, pwd, service, adapter, version, 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
supported in Python 2.7.9 or higher.
@type sslContext: SSL.Context
@ -304,7 +309,8 @@ def __Login(host, port, user, pwd, service, adapter, version, path,
# Create the SOAP stub adapter
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
si = vim.ServiceInstance("ServiceInstance", stub)
@ -555,10 +561,54 @@ def __FindSupportedVersion(protocol, server, port, path, preferredApiVersions, s
return desiredVersion
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='',
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,
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
be used.
@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
supported in Python 2.7.9 or higher.
@type sslContext: SSL.Context
@ -618,6 +674,9 @@ def SmartConnect(protocol='https', host='localhost', port=443, user='root', pwd=
adapter='SOAP',
version=supportedVersion,
path=path,
keyFile=keyFile,
certFile=certFile,
thumbprint=thumbprint,
sslContext=sslContext)
def OpenUrlWithBasicAuth(url, user='root', pwd=''):

View File

@ -104,6 +104,17 @@ def encode(string, encoding):
return string.encode(encoding)
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 <, >, &
def XmlEscape(xmlStr):
escaped = xmlStr.replace("&", "&amp;").replace(">", "&gt;").replace("<", "&lt;")
@ -236,7 +247,7 @@ class SoapSerializer:
# @param info the field
def SerializeFaultDetail(self, val, info):
""" 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):
""" Get xml ns prefix. self.nsMap must be set """
@ -494,12 +505,7 @@ class ExpatDeserializerNSHandlers:
## Get current default ns
def GetCurrDefNS(self):
namespaces = self.nsMap.get(None)
if namespaces:
ns = namespaces[-1]
else:
ns = ""
return ns
return self._GetNamespaceFromPrefix()
## Get namespace and wsdl name from tag
def GetNSAndWsdlname(self, tag):
@ -510,9 +516,17 @@ class ExpatDeserializerNSHandlers:
else:
prefix, name = None, tag
# Map prefix to ns
ns = self.nsMap[prefix][-1]
ns = self._GetNamespaceFromPrefix(prefix)
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
def StartNamespaceDeclHandler(self, prefix, uri):
namespaces = self.nsMap.get(prefix)
@ -930,9 +944,7 @@ try:
sha1.update(derCert)
sha1Digest = sha1.hexdigest().lower()
if sha1Digest != thumbprint:
raise Exception("Server has wrong SHA1 thumbprint: {0} "
"(required) != {1} (server)".format(
thumbprint, sha1Digest))
raise ThumbprintMismatchException(thumbprint, sha1Digest)
# Function used to wrap sockets with SSL
_SocketWrapper = ssl.wrap_socket
@ -963,7 +975,7 @@ except ImportError:
class HTTPSConnectionWrapper(object):
def __init__(self, *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()
self._sslArgs = {}
tmpKwargs = kwargs.copy()
@ -1028,11 +1040,13 @@ class SSLTunnelConnection(object):
# @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)
# Only pass in the named arguments that HTTPConnection constructor
# understands
tmpKwargs = {}
for key in http_client.HTTPConnection.__init__.__code__.co_varnames:
if key in kwargs and key != 'self':
tmpKwargs[key] = kwargs[key]
tunnel = http_client.HTTPConnection(path, **tmpKwargs)
tunnel.request('CONNECT', self.proxyPath)
resp = tunnel.getresponse()
if resp.status != 200:
@ -1164,7 +1178,7 @@ class SoapStubAdapter(SoapStubAdapterBase):
# the UnixSocketConnection ctor expects to find it -- see above
self.host = sock
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
# keyword argument as passed in.
if urlpath not in ('', '/'):
@ -1219,6 +1233,20 @@ class SoapStubAdapter(SoapStubAdapterBase):
self.requestModifierList = []
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.
# @param func The func that takes in the serialized message and modifies the
@ -1287,7 +1315,7 @@ class SoapStubAdapter(SoapStubAdapterBase):
deserializer = SoapResponseDeserializer(outerStub)
obj = deserializer.Deserialize(fd, info.result)
except Exception as exc:
conn.close()
self._CloseConnection(conn)
# 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
@ -1306,7 +1334,7 @@ class SoapStubAdapter(SoapStubAdapterBase):
else:
raise obj # pylint: disable-msg=E0702
else:
conn.close()
self._CloseConnection(conn)
raise http_client.HTTPException("{0} {1}".format(resp.status, resp.reason))
## Clean up connection pool to throw away idle timed-out connections
@ -1324,7 +1352,7 @@ class SoapStubAdapter(SoapStubAdapterBase):
break
for conn, _ in idleConnections:
conn.close()
self._CloseConnection(conn)
## Get a HTTP connection from the pool
def GetConnection(self):
@ -1365,7 +1393,7 @@ class SoapStubAdapter(SoapStubAdapterBase):
self.pool = []
self.lock.release()
for conn, _ in oldConnections:
conn.close()
self._CloseConnection(conn)
## Return a HTTP connection to the pool
def ReturnConnection(self, conn):
@ -1380,7 +1408,7 @@ class SoapStubAdapter(SoapStubAdapterBase):
# 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()
self._CloseConnection(conn)
## Disable nagle on a http connections
def DisableNagle(self, conn):

View File

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

View File

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

View File

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