From a90023fedfeda48cb1824200dc4530084215f99e Mon Sep 17 00:00:00 2001 From: ddraganov Date: Thu, 24 Nov 2022 22:57:49 +0200 Subject: [PATCH] Major pyVmomi update for vSphere 8.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Breaking changes: - Minimum Python 2 requirement is 2.7.9 - DynamicTypeManagerHelper.py is removed - ManagedMethodExecutorHelper.py is removed - connect.ConnectNoSSL() and connect.SmartConnectNoSSL() are removed. Use connect.Connect(disableSslCertValidation=True) and connect.SmartConnect(disableSslCertValidation=True) - VmomiSupport.UncallableManagedMethod is renamed to VmomiSupport.UnknownManagedMethod # New modules: Security.py A new module is added to handle thumbprint verification. There is a predefined set of available crypto functions to verify the certificate thumbprints. Its possible to configure during runtime which of the available crypto functions are allowed. Feature.py A new module related to pyVmomi development within VMware. VmomiJSONEncoder.py The VmomiJSONEncoder is moved into a dedicated module. # More changes: - A new 'binaryIsBytearray' setting is added to select the base type for the binary type. By default, the binary type is 'str' for Python 2 and 'bytes' for Python 3. If binaryIsBytearray is True, the binary type for Python 2 is set to 'bytearray'. Required for VmomiJSONEncoder to work properly. - The license note is removed from the Python files. LICENSE.txt holds the Apache 2 license note. - pyVmomi now uses relative imports - Dependency on “requests” is removed - Added support for SAML token authentication - Added timeout for HTTP requests - Added option to set the maximum amount of time a task is allowed to run. On timeout, an exception is generated if raiseOnError is True. - Add option to get all updates for the task. - Add option to use a logger instead of the standard output - Various bug fixes - Code style improvements # Deprecated: - connect.OpenUrlWithBasicAuth() - connect.OpenPathWithStub() --- pyVim/connect.py | 1694 +++++++------ pyVim/sso.py | 295 ++- pyVim/task.py | 154 +- pyVmomi/Cache.py | 58 +- pyVmomi/Differ.py | 415 ++- pyVmomi/DynamicTypeManagerHelper.py | 289 --- pyVmomi/Feature.py | 36 + pyVmomi/Iso8601.py | 646 ++--- pyVmomi/LICENSE.txt | 202 ++ pyVmomi/ManagedMethodExecutorHelper.py | 118 - pyVmomi/Security.py | 54 + pyVmomi/SoapAdapter.py | 3227 ++++++++++++------------ pyVmomi/StubAdapterAccessorImpl.py | 60 +- pyVmomi/Version.py | 53 +- pyVmomi/VmomiJSONEncoder.py | 129 + pyVmomi/VmomiSupport.py | 3212 +++++++++++------------ pyVmomi/__init__.py | 124 +- pyVmomi/_typeinfos.py | 23 + pyVmomi/pyVmomiSettings.py | 18 +- requirements.txt | 1 - setup.cfg | 2 +- setup.py | 2 +- 22 files changed, 5610 insertions(+), 5202 deletions(-) delete mode 100644 pyVmomi/DynamicTypeManagerHelper.py create mode 100644 pyVmomi/Feature.py create mode 100644 pyVmomi/LICENSE.txt delete mode 100644 pyVmomi/ManagedMethodExecutorHelper.py create mode 100644 pyVmomi/Security.py create mode 100644 pyVmomi/VmomiJSONEncoder.py create mode 100644 pyVmomi/_typeinfos.py diff --git a/pyVim/connect.py b/pyVim/connect.py index 933ce1d0e..5ee59bc83 100644 --- a/pyVim/connect.py +++ b/pyVim/connect.py @@ -1,45 +1,41 @@ -# VMware vSphere Python SDK -# Copyright (c) 2008-2021 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. +############################################################# +# Copyright (c) 2005-2022 VMware, Inc. +############################################################# ## @file connect.py ## @brief Connect to a VMOMI ServiceInstance. ## ## Detailed description (for Doxygen goes here) - """ Connect to a VMOMI ServiceInstance. Detailed description (for [e]pydoc goes here). """ -from six import reraise -import sys + import re import ssl -from xml.etree import ElementTree + +from sys import exc_info from xml.parsers.expat import ExpatError -from six.moves import http_client -import requests -from requests.auth import HTTPBasicAuth +from six.moves import http_client, urllib +from six import reraise -from pyVmomi import vim, vmodl, SoapStubAdapter, SessionOrientedStub +from pyVmomi import SessionOrientedStub, SoapStubAdapter, vim, vmodl from pyVmomi.SoapAdapter import CONNECTION_POOL_IDLE_TIMEOUT_SEC -from pyVmomi.VmomiSupport import nsMap, versionIdMap, versionMap, IsChildVersion -from pyVmomi.VmomiSupport import GetServiceVersions +from pyVmomi.VmomiSupport import (GetServiceVersions, IsChildVersion, nsMap, + versionIdMap, versionMap) + +try: + from xml.etree.ElementTree import ElementTree +except ImportError: + from elementtree.ElementTree import ElementTree +TOKEN_TYPE_SAML = 'saml' +TOKEN_TYPE_SSPI = 'sspi' +TOKEN_TYPES = [TOKEN_TYPE_SAML, TOKEN_TYPE_SSPI] + """ Global regular expression for parsing host and port connection See http://www.ietf.org/rfc/rfc3986.txt sec 3.2.2 @@ -60,250 +56,283 @@ def getSslContext(host, sslContext, disableSslCertValidation): will never match. The OS provides security by only allowing root to bind to low-numbered ports. """ + """ + TODO: the entire 127.0.0.0/8 network is allocated for loopback addresses. + Therefore, the check should be for a valid ip4 address, beginning with 127. + """ if disableSslCertValidation or (not sslContext and host in ['localhost', '127.0.0.1', '::1']): sslContext = ssl._create_unverified_context() return sslContext -class closing(object): - """ - Helper class for using closable objects in a 'with' statement, - similar to the one provided by contextlib. - """ - def __init__(self, obj): - self.obj = obj - def __enter__(self): - return self.obj - def __exit__(self, *exc_info): - self.obj.close() +class closing(object): + """ + Helper class for using closable objects in a 'with' statement, + similar to the one provided by contextlib. + """ + def __init__(self, obj): + self.obj = obj -class VimSessionOrientedStub(SessionOrientedStub): - '''A vim-specific SessionOrientedStub. See the SessionOrientedStub class - in pyVmomi/SoapAdapter.py for more information.''' - - # The set of exceptions that should trigger a relogin by the session stub. - SESSION_EXCEPTIONS = ( - vim.fault.NotAuthenticated, - ) - - @staticmethod - def makeUserLoginMethod(username, password, locale=None): - '''Return a function that will call the vim.SessionManager.Login() method - with the given parameters. The result of this function can be passed as - the "loginMethod" to a SessionOrientedStub constructor.''' - def _doLogin(soapStub): - si = vim.ServiceInstance("ServiceInstance", soapStub) - sm = si.content.sessionManager - if not sm.currentSession: - si.content.sessionManager.Login(username, password, locale) - - return _doLogin - - @staticmethod - def makeExtensionLoginMethod(extensionKey): - '''Return a function that will call the vim.SessionManager.Login() method - with the given parameters. The result of this function can be passed as - the "loginMethod" to a SessionOrientedStub constructor.''' - def _doLogin(soapStub): - si = vim.ServiceInstance("ServiceInstance", soapStub) - sm = si.content.sessionManager - if not sm.currentSession: - si.content.sessionManager.LoginExtensionByCertificate(extensionKey) - - return _doLogin - - @staticmethod - def makeCertHokTokenLoginMethod(stsUrl, stsCert=None): - '''Return a function that will call the vim.SessionManager.LoginByToken() - after obtaining a HoK SAML token from the STS. The result of this function - can be passed as the "loginMethod" to a SessionOrientedStub constructor. - - @param stsUrl: URL of the SAML Token issuing service. (i.e. SSO server). - @param stsCert: public key of the STS service. - ''' - assert(stsUrl) - - def _doLogin(soapStub): - from . import sso - cert = soapStub.schemeArgs['cert_file'] - key = soapStub.schemeArgs['key_file'] - authenticator = sso.SsoAuthenticator(sts_url=stsUrl, - sts_cert=stsCert) - - samlAssertion = authenticator.get_hok_saml_assertion(cert,key) - - - def _requestModifier(request): - return sso.add_saml_context(request, samlAssertion, key) - - si = vim.ServiceInstance("ServiceInstance", soapStub) - sm = si.content.sessionManager - if not sm.currentSession: - with soapStub.requestModifier(_requestModifier): - try: - soapStub.samlToken = samlAssertion - si.content.sessionManager.LoginByToken() - finally: - soapStub.samlToken = None - - return _doLogin - - @staticmethod - def makeCredBearerTokenLoginMethod(username, - password, - stsUrl, - stsCert=None): - '''Return a function that will call the vim.SessionManager.LoginByToken() - after obtaining a Bearer token from the STS. The result of this function - can be passed as the "loginMethod" to a SessionOrientedStub constructor. - - @param username: username of the user/service registered with STS. - @param password: password of the user/service registered with STS. - @param stsUrl: URL of the SAML Token issueing service. (i.e. SSO server). - @param stsCert: public key of the STS service. - ''' - assert(username) - assert(password) - assert(stsUrl) - - def _doLogin(soapStub): - from . import sso - cert = soapStub.schemeArgs['cert_file'] - key = soapStub.schemeArgs['key_file'] - authenticator = sso.SsoAuthenticator(sts_url=stsUrl, - sts_cert=stsCert) - samlAssertion = authenticator.get_bearer_saml_assertion(username, - password, - cert, - key) - si = vim.ServiceInstance("ServiceInstance", soapStub) - sm = si.content.sessionManager - if not sm.currentSession: - try: - soapStub.samlToken = samlAssertion - si.content.sessionManager.LoginByToken() - finally: - soapStub.samlToken = None + def __enter__(self): + return self.obj - return _doLogin + def __exit__(self, *exc_info): + self.obj.close() -def Connect(host='localhost', port=443, user='root', pwd='', - service="hostd", adapter="SOAP", namespace=None, path="/sdk", +class VimSessionOrientedStub(SessionOrientedStub): + '''A vim-specific SessionOrientedStub. See the SessionOrientedStub class + in pyVmomi/SoapAdapter.py for more information. + ''' + + # The set of exceptions that should trigger a relogin by the session stub. + SESSION_EXCEPTIONS = (vim.fault.NotAuthenticated, ) + + @staticmethod + def makeUserLoginMethod(username, password, locale=None): + '''Return a function that will call the vim.SessionManager.Login() method + with the given parameters. The result of this function can be passed as + the "loginMethod" to a SessionOrientedStub constructor. + ''' + def _doLogin(soapStub): + si = vim.ServiceInstance("ServiceInstance", soapStub) + sm = si.content.sessionManager + if not sm.currentSession: + si.content.sessionManager.Login(username, password, locale) + + return _doLogin + + @staticmethod + def makeExtensionLoginMethod(extensionKey): + '''Return a function that will call the vim.SessionManager.Login() method + with the given parameters. The result of this function can be passed as + the "loginMethod" to a SessionOrientedStub constructor. + ''' + def _doLogin(soapStub): + si = vim.ServiceInstance("ServiceInstance", soapStub) + sm = si.content.sessionManager + if not sm.currentSession: + si.content.sessionManager.LoginExtensionByCertificate( + extensionKey) + + return _doLogin + + @staticmethod + def makeCertHokTokenLoginMethod(stsUrl, stsCert=None): + '''Return a function that will call the vim.SessionManager.LoginByToken() + after obtaining a HoK SAML token from the STS. The result of this function + can be passed as the "loginMethod" to a SessionOrientedStub constructor. + + @param stsUrl: URL of the SAML Token issuing service. (i.e. SSO server). + @param stsCert: public key of the STS service. + ''' + assert (stsUrl) + + def _doLogin(soapStub): + from . import sso + cert = soapStub.schemeArgs['cert_file'] + key = soapStub.schemeArgs['key_file'] + authenticator = sso.SsoAuthenticator(sts_url=stsUrl, + sts_cert=stsCert) + + samlAssertion = authenticator.get_hok_saml_assertion(cert, key) + + def _requestModifier(request): + return sso.add_saml_context(request, samlAssertion, key) + + si = vim.ServiceInstance("ServiceInstance", soapStub) + sm = si.content.sessionManager + if not sm.currentSession: + with soapStub.requestModifier(_requestModifier): + try: + soapStub.samlToken = samlAssertion + si.content.sessionManager.LoginByToken() + finally: + soapStub.samlToken = None + + return _doLogin + + @staticmethod + def makeCredBearerTokenLoginMethod(username, + password, + stsUrl, + stsCert=None): + '''Return a function that will call the vim.SessionManager.LoginByToken() + after obtaining a Bearer token from the STS. The result of this function + can be passed as the "loginMethod" to a SessionOrientedStub constructor. + + @param username: username of the user/service registered with STS. + @param password: password of the user/service registered with STS. + @param stsUrl: URL of the SAML Token issueing service. (i.e. SSO server). + @param stsCert: public key of the STS service. + ''' + assert (username) + assert (password) + assert (stsUrl) + + def _doLogin(soapStub): + from . import sso + cert = soapStub.schemeArgs['cert_file'] + key = soapStub.schemeArgs['key_file'] + authenticator = sso.SsoAuthenticator(sts_url=stsUrl, + sts_cert=stsCert) + samlAssertion = authenticator.get_bearer_saml_assertion( + username, password, cert, key) + si = vim.ServiceInstance("ServiceInstance", soapStub) + sm = si.content.sessionManager + if not sm.currentSession: + try: + soapStub.samlToken = samlAssertion + si.content.sessionManager.LoginByToken() + finally: + soapStub.samlToken = None + + return _doLogin + + +def Connect(host='localhost', + port=443, + user='root', + pwd='', + service="hostd", + adapter="SOAP", + namespace=None, + path="/sdk", + version=None, + keyFile=None, + certFile=None, + httpProxyHost=None, + httpProxyPort=80, + thumbprint=None, + sslContext=None, + httpConnectionTimeout=None, connectionPoolTimeout=CONNECTION_POOL_IDLE_TIMEOUT_SEC, - version=None, keyFile=None, certFile=None, thumbprint=None, - sslContext=None, b64token=None, mechanism='userpass', disableSslCertValidation=False): - """ - Connect to the specified server, login and return the service - instance object. - - Throws any exception back to caller. The service instance object is - also saved in the library for easy access. - - Clients should modify the service parameter only when connecting to - a VMOMI server other than hostd/vpxd. For both of the latter, the - default value is fine. - - @param host: Which host to connect to. - @type host: string - @param port: Port - @type port: int - @param user: User - @type user: string - @param pwd: Password - @type pwd: string - @param service: Service - @type service: string - @param adapter: Adapter - @type adapter: string - @param namespace: Namespace *** Deprecated: Use version instead *** - @type namespace: string - @param path: Path - @type path: string - @param connectionPoolTimeout: Timeout in secs for idle connections to close, specify negative numbers for never - closing the connections - @type connectionPoolTimeout: int - @param version: Version - @type version: string - @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 - @param b64token: base64 encoded token - @type b64token: string - @param mechanism: authentication mechanism: userpass or sspi - @type mechanism: string - @type disableSslCertValidation: bool - @param disableSslCertValidation: Creates an unverified SSL context when True. - """ - try: - info = re.match(_rx, host) - if info is not None: - host = info.group(1) - if host[0] == '[': - host = info.group(1)[1:-1] - if info.group(2) is not None: - port = int(info.group(2)[1:]) - except ValueError as ve: - pass - - sslContext = getSslContext(host, sslContext, disableSslCertValidation) - - if namespace: - assert(version is None) - version = versionMap[namespace] - elif not version: - version = "vim.version.version6" - - si, stub = None, None - if mechanism == 'userpass': - si, stub = __Login(host, port, user, pwd, service, adapter, version, path, - keyFile, certFile, thumbprint, sslContext, connectionPoolTimeout) - elif mechanism == 'sspi': - si, stub = __LoginBySSPI(host, port, service, adapter, version, path, - keyFile, certFile, thumbprint, sslContext, b64token, connectionPoolTimeout) - else: - raise Exception('''The provided connection mechanism is not available, the - supported mechanisms are userpass or sspi''') - - SetSi(si) - - return si - -# ConnectNoSSL() is deprecated. Use Connect(disableSslCertValidation=True). -def ConnectNoSSL(host='localhost', port=443, user='root', pwd='', - service="hostd", adapter="SOAP", namespace=None, path="/sdk", - version=None, keyFile=None, certFile=None, thumbprint=None, - b64token=None, mechanism='userpass'): - """ - Provides a standard method for connecting to a specified server without SSL - verification. Useful when connecting to servers with self-signed certificates - or when you wish to ignore SSL altogether. Will attempt to create an unverified - SSL context and then connect via the Connect method. - """ - - sslContext = ssl._create_unverified_context() - - return Connect(host=host, - port=port, - user=user, - pwd=pwd, - service=service, - adapter=adapter, - namespace=namespace, - path=path, - version=version, - keyFile=keyFile, - certFile=certFile, - thumbprint=thumbprint, - sslContext=sslContext, - b64token=b64token, - mechanism=mechanism) + token=None, + tokenType=TOKEN_TYPE_SAML, + disableSslCertValidation=False, + customHeaders=None, + # Deprecated + b64token=None, + # Deprecated + mechanism='userpass'): + """ + Connect to the specified server, login and return the service + instance object. + + Throws any exception back to caller. The service instance object is + also saved in the library for easy access. + + Clients should modify the service parameter only when connecting to + a VMOMI server other than hostd/vpxd. For both of the latter, the + default value is fine. + + @param host: Which host to connect to. + @type host: string + @param port: Port + @type port: int + @param user: User + @type user: string + @param pwd: Password + @type pwd: string + @param service: Service + @type service: string + @param adapter: Adapter + @type adapter: string + @param namespace: Namespace *** Deprecated: Use version instead *** + @type namespace: string + @param path: Path + @type path: string + @param version: Version + @type version: string + @param keyFile: ssl key file path + @type keyFile: string + @param certFile: ssl cert file path + @type certFile: string + @param httpProxyHost The host name of the proxy server. + @type httpProxyHost: string + @param httpProxyPort The proxy server port. + @type httpProxyPort: 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 + @param httpConnectionTimeout: Timeout in secs for http requests. + @type httpConnectionTimeout: int + @param connectionPoolTimeout: Timeout in secs for idle connections to close, specify + negative numbers for never closing the connections + @type connectionPoolTimeout: int + @type token: string + @param token: Authentication and Authorization token to use for the connection. + The presence of this token overrides the user and pwd parameters. + @type tokenType: string + @param tokenType: Select which type of Authentication and Authorization token to use. + @type disableSslCertValidation: bool + @param disableSslCertValidation: Creates an unverified SSL context when True. + @type customHeaders: dictionary + @param customHeaders: Dictionary with custom HTTP headers. + @param b64token: base64 encoded token + *** Deprecated: Use token instead *** + @type b64token: string + @param mechanism: authentication mechanism: userpass or sspi + *** Deprecated: Use tokenType instead *** + @type mechanism: string + """ + try: + info = re.match(_rx, host) + if info is not None: + host = info.group(1) + if host[0] == '[': + host = info.group(1)[1:-1] + if info.group(2) is not None: + port = int(info.group(2)[1:]) + except ValueError as ve: + pass + + sslContext = getSslContext(host, sslContext, disableSslCertValidation) + + if namespace: + assert (version is None) + version = versionMap[namespace] + elif not version: + version = "vim.version.version9" + + # Backwards compatibility for the deprecated arguments - + # b64token and mechanism + msg = "The default connection type uses credentials. " \ + "If you want to authenticate with a token, set 'token' and 'tokenType'." + if mechanism == "sspi": + if b64token: + token = b64token + tokenType = TOKEN_TYPE_SSPI + else: + raise Exception("The b64token parameter is mandatory. " + msg) + elif mechanism != "userpass": + raise Exception("Not supported mechanism. " + msg) + + si, stub = __Login(host, + port, + user, + pwd, + service, + adapter, + version, + path, + keyFile, + certFile, + httpProxyHost, + httpProxyPort, + thumbprint, + sslContext, + httpConnectionTimeout, + connectionPoolTimeout, + token=token, + tokenType=tokenType, + customHeaders=customHeaders) + SetSi(si) + + return si + def Disconnect(si = None): """ @@ -331,605 +360,658 @@ def Disconnect(si = None): ## Method that gets a local ticket for the specified user def GetLocalTicket(si, user): - try: - sessionManager = si.content.sessionManager - except Exception as e: - if type(e).__name__ == 'ExpatError': - msg = 'Malformed response while querying for local ticket: "%s"' % e - raise vim.fault.HostConnectFault(msg=msg) - else: - msg = 'Failed to query for local ticket: "%s"' % e - raise vim.fault.HostConnectFault(msg=msg) - localTicket = sessionManager.AcquireLocalTicket(userName=user) - with open(localTicket.passwordFilePath) as f: - content = f.read() - return localTicket.userName, content + try: + sessionManager = si.content.sessionManager + except Exception as e: + if type(e).__name__ == 'ExpatError': + msg = 'Malformed response while querying for local ticket: "%s"' % e + raise vim.fault.HostConnectFault(msg=msg) + else: + msg = 'Failed to query for local ticket: "%s"' % e + raise vim.fault.HostConnectFault(msg=msg) + localTicket = sessionManager.AcquireLocalTicket(userName=user) + with open(localTicket.passwordFilePath) as f: + content = f.read() + return localTicket.userName, content ## Private method that performs the actual Connect and returns a ## connected service instance object. -def __Login(host, port, user, pwd, service, adapter, version, path, - keyFile, certFile, thumbprint, sslContext, - connectionPoolTimeout=CONNECTION_POOL_IDLE_TIMEOUT_SEC): - """ - Private method that performs the actual Connect and returns a - connected service instance object. - - @param host: Which host to connect to. - @type host: string - @param port: Port - @type port: int - @param user: User - @type user: string - @param pwd: Password - @type pwd: string - @param service: Service - @type service: string - @param adapter: Adapter - @type adapter: string - @param version: Version - @type version: string - @param path: Path - @type path: string - @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 - @param connectionPoolTimeout: Timeout in secs for idle connections to close, specify negative numbers for never - closing the connections - @type connectionPoolTimeout: int - """ - - content, si, stub = __RetrieveContent(host, port, adapter, version, path, - keyFile, certFile, thumbprint, sslContext, connectionPoolTimeout) - - # Get a ticket if we're connecting to localhost and password is not specified - if host == 'localhost' and not pwd: - try: - (user, pwd) = GetLocalTicket(si, user) - except: - pass # This is not supported against vCenter, and connecting - # with an empty password is fine in debug builds - - # Login - try: - x = content.sessionManager.Login(user, pwd, None) - except vim.fault.InvalidLogin: - raise - except Exception as e: - raise - return si, stub - -## Private method that performs LoginBySSPI and returns a -## connected service instance object. -## Copyright (c) 2015 Morgan Stanley. All rights reserved. - -def __LoginBySSPI(host, port, service, adapter, version, path, - keyFile, certFile, thumbprint, sslContext, b64token, - connectionPoolTimeout=CONNECTION_POOL_IDLE_TIMEOUT_SEC): - """ - Private method that performs the actual Connect and returns a - connected service instance object. - - @param host: Which host to connect to. - @type host: string - @param port: Port - @type port: int - @param service: Service - @type service: string - @param adapter: Adapter - @type adapter: string - @param version: Version - @type version: string - @param path: Path - @type path: string - @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 - @param b64token: base64 encoded token - @type b64token: string - @param connectionPoolTimeout: Timeout in secs for idle connections to close, specify negative numbers for never - closing the connections - @type connectionPoolTimeout: int - """ - - content, si, stub = __RetrieveContent(host, port, adapter, version, path, - keyFile, certFile, thumbprint, sslContext, connectionPoolTimeout) - - if b64token is None: - raise Exception('Token is not defined for sspi login') - - # Login - try: - x = content.sessionManager.LoginBySSPI(b64token) - except vim.fault.InvalidLogin: - raise - except Exception as e: - raise - return si, stub - - -## Private method that returns the service content - -def __RetrieveContent(host, port, adapter, version, path, keyFile, certFile, - thumbprint, sslContext, connectionPoolTimeout=CONNECTION_POOL_IDLE_TIMEOUT_SEC): - """ - Retrieve service instance for connection. - @param host: Which host to connect to. - @type host: string - @param port: Port - @type port: int - @param adapter: Adapter - @type adapter: string - @param version: Version - @type version: string - @param path: Path - @type path: string - @param keyFile: ssl key file path - @type keyFile: string - @param certFile: ssl cert file path - @type certFile: string - @param connectionPoolTimeout: Timeout in secs for idle connections to close, specify negative numbers for never - closing the connections - @type connectionPoolTimeout: int - """ - - # XXX remove the adapter and service arguments once dependent code is fixed - if adapter != "SOAP": - raise ValueError(adapter) - - # Create the SOAP stub adapter - stub = SoapStubAdapter(host, port, version=version, path=path, - certKeyFile=keyFile, certFile=certFile, - thumbprint=thumbprint, sslContext=sslContext, - connectionPoolTimeout=connectionPoolTimeout) - - # Get Service instance - si = vim.ServiceInstance("ServiceInstance", stub) - content = None - try: - content = si.RetrieveContent() - except vmodl.MethodFault: - raise - except Exception as e: - # NOTE (hartsock): preserve the traceback for diagnostics - # pulling and preserving the traceback makes diagnosing connection - # failures easier since the fault will also include where inside the - # library the fault occurred. Without the traceback we have no idea - # why the connection failed beyond the message string. - (type, value, traceback) = sys.exc_info() - if traceback: - fault = vim.fault.HostConnectFault(msg=str(e)) - reraise(vim.fault.HostConnectFault, fault, traceback) - else: - raise vim.fault.HostConnectFault(msg=str(e)) - - return content, si, stub + +def __Login(host, + port, + user, + pwd, + # TODO Remove service + service, + adapter, + version, + path, + keyFile, + certFile, + httpProxyHost, + httpProxyPort, + thumbprint, + sslContext, + httpConnectionTimeout, + connectionPoolTimeout, + token, + tokenType, + customHeaders): + """ + Private method that performs the actual Connect and returns a + connected service instance object. + + @param host: Which host to connect to. + @type host: string + @param port: Port + @type port: int + @param user: User + @type user: string + @param pwd: Password + @type pwd: string + @param service: Service + @type service: string + @param adapter: Adapter + @type adapter: string + @param version: Version + @type version: string + @param path: Path + @type path: string + @param keyFile: ssl key file path + @type keyFile: string + @param certFile: ssl cert file path + @type certFile: string + @param httpProxyHost The host name of the proxy server. + @type httpProxyHost: string + @param httpProxyPort The proxy server port. + @type httpProxyPort: 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 + @param httpConnectionTimeout: Timeout in secs for http requests. + @type httpConnectionTimeout: int + @param connectionPoolTimeout: Timeout in secs for idle connections to close, specify + negative numbers for never closing the connections + @type connectionPoolTimeout: int + @type token: string + @param token: Authentication and Authorization token to use for the connection. + The presence of this token overrides the user and pwd parameters. + @type tokenType: string + @param tokenType: Select which type of Authentication and Authorization token to use. + @type customHeaders: dictionary + @param customHeaders: Dictionary with custom HTTP headers. + """ + + # XXX remove the adapter and service arguments once dependent code is fixed + if adapter != "SOAP": + raise ValueError(adapter) + + # Add an HTTP authorization header if a token is provided + if token and tokenType == TOKEN_TYPE_SAML: + if customHeaders is None: + customHeaders = {} + customHeaders.update({"Authorization": "Bearer {}".format(token)}) + + # Create the SOAP stub adapter + stub = SoapStubAdapter( + host, + port, + version=version, + path=path, + certKeyFile=keyFile, + certFile=certFile, + httpProxyHost=httpProxyHost, + httpProxyPort=httpProxyPort, + thumbprint=thumbprint, + sslContext=sslContext, + httpConnectionTimeout=httpConnectionTimeout, + connectionPoolTimeout=connectionPoolTimeout, + customHeaders=customHeaders) + + # Get Service instance + si = vim.ServiceInstance("ServiceInstance", stub) + content = None + try: + content = si.RetrieveContent() + except vmodl.MethodFault: + raise + except Exception as e: + # NOTE (hartsock): preserve the traceback for diagnostics + # pulling and preserving the traceback makes diagnosing connection + # failures easier since the fault will also include where inside the + # library the fault occurred. Without the traceback we have no idea + # why the connection failed beyond the message string. + (type, value, traceback) = exc_info() + fault = vim.fault.HostConnectFault(msg=str(e)) + if traceback: + reraise(vim.fault.HostConnectFault, fault, traceback) + else: + raise fault + + # Get a ticket if we're connecting to localhost and password is not specified + if host == 'localhost' and not pwd and not token: + try: + (user, pwd) = GetLocalTicket(si, user) + except: + pass # This is not supported against vCenter, and connecting + # with an empty password is fine in debug builds + + # Login + if not token: + content.sessionManager.Login(user, pwd, None) + else: + if tokenType == TOKEN_TYPE_SAML: + content.sessionManager.LoginByToken() + elif tokenType == TOKEN_TYPE_SSPI: + content.sessionManager.LoginBySSPI(token) + else: + raise Exception("'{0}' token type is not supported. " + "Supported types are: {1}".format(tokenType, TOKEN_TYPES)) + return si, stub ## Get the saved service instance. + def GetSi(): - """ Get the saved service instance. """ - return _si + """ Get the saved service instance. """ + return _si ## Set the saved service instance. + def SetSi(si): - """ Set the saved service instance. """ + """ Set the saved service instance. """ - global _si - _si = si + global _si + _si = si ## Get the global saved stub + def GetStub(): - """ Get the global saved stub. """ - si = GetSi() - if si: - return si._GetStub() - return None; + """ Get the global saved stub. """ + si = GetSi() + if si: + return si._GetStub() + return None + ## RAII-style class for managing connections + class Connection(object): - def __init__(self, *args, **kwargs): - self.args = args - self.kwargs = kwargs - self.si = None + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + self.si = None + + def __enter__(self): + self.si = Connect(*self.args, **self.kwargs) + return self.si - def __enter__(self): - self.si = Connect(*self.args, **self.kwargs) - return self.si + def __exit__(self, *exc_info): + if self.si: + Disconnect(self.si) + self.si = None - def __exit__(self, *exc_info): - if self.si: - Disconnect(self.si) - self.si = None class SmartConnection(object): - def __init__(self, *args, **kwargs): - self.args = args - self.kwargs = kwargs - self.si = None - - def __enter__(self): - self.si = SmartConnect(*self.args, **self.kwargs) - return self.si - - def __exit__(self, *exc_info): - if self.si: - Disconnect(self.si) - self.si = None - -def __GetElementTree(protocol, server, port, path, sslContext, httpProxyHost=None, httpProxyPort=None): - """ - Private method that returns a root from ElementTree for a remote XML document. - - @param protocol: What protocol to use for the connection (e.g. https or http). - @type protocol: string - @param server: Which server to connect to. - @type server: string - @param port: Port - @type port: int - @param path: Path - @type path: 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 - """ - - if httpProxyHost: - kwargs = {"context": sslContext} if sslContext else {} - conn = http_client.HTTPSConnection(httpProxyHost, port=httpProxyPort, **kwargs) - conn.set_tunnel(server, port) - elif protocol == "https": - kwargs = {"context": sslContext} if sslContext else {} - conn = http_client.HTTPSConnection(server, port=port, **kwargs) - elif protocol == "http": - conn = http_client.HTTPConnection(server, port=port) - else: - raise Exception("Protocol " + protocol + " not supported.") - conn.request("GET", path) - response = conn.getresponse() - if response.status == 200: - try: - tree = ElementTree.fromstring(response.read()) - return tree - except ExpatError: - pass - return None + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + self.si = None + + def __enter__(self): + self.si = SmartConnect(*self.args, **self.kwargs) + return self.si + + def __exit__(self, *exc_info): + if self.si: + Disconnect(self.si) + self.si = None + + +## Deprecated, use __GetElementTree method instead +def __GetElementTreeFromUrl(url, sslContext): + """ + Private method that returns an ElementTree for the XML document referenced by + the url. + + @param url: URL + @type url: 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 + """ + + tree = ElementTree() + + try: + urlopen_kwargs = {} + if sslContext: + # We need to pass the ssl context as kwargs because 'context' parameter + # was added in Python 2.7.9, so passing it as a normal parameter would + # fail in earlier versions. Please see + # https://www.python.org/dev/peps/pep-0476/ for more details. + urlopen_kwargs['context'] = sslContext + with closing(urllib.request.urlopen(url, **urlopen_kwargs)) as sock: + if sock.getcode() == 200: + tree.parse(sock) + return tree + except ExpatError: + pass + return None + + +def __GetElementTree(protocol, server, port, path, sslContext, + httpProxyHost, httpProxyPort): + """ + Private method that returns ElementTree for a remote XML document. + + @param protocol: What protocol to use for the connection (e.g. https or http). + @type protocol: string + @param server: Which server to connect to. + @type server: string + @param port: Port + @type port: int + @param path: Path + @type path: 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 + """ + tree = ElementTree() + + if httpProxyHost: + kwargs = {"context": sslContext} if sslContext else {} + conn = http_client.HTTPSConnection(httpProxyHost, port=httpProxyPort, **kwargs) + conn.set_tunnel(server, port) + elif protocol == "https": + kwargs = {"context": sslContext} if sslContext else {} + conn = http_client.HTTPSConnection(server, port=port, **kwargs) + elif protocol == "http": + conn = http_client.HTTPConnection(server, port=port) + else: + raise Exception("Protocol " + protocol + " not supported.") + conn.request("GET", path) + try: + response = conn.getresponse() + if response.status == 200: + try: + tree.parse(response) + return tree + except ExpatError: + pass + return None + finally: + conn.close() + ## Private method that returns an ElementTree describing the API versions ## supported by the specified server. The result will be vimServiceVersions.xml -## if it exists, otherwise vimService.wsdl if it exists, otherwise None. - -def __GetServiceVersionDescription(protocol, server, port, path, sslContext, httpProxyHost=None, httpProxyPort=None): - """ - Private method that returns a root from an ElementTree describing the API versions - supported by the specified server. The result will be vimServiceVersions.xml - if it exists, otherwise vimService.wsdl if it exists, otherwise None. - - @param protocol: What protocol to use for the connection (e.g. https or http). - @type protocol: string - @param server: Which server to connect to. - @type server: string - @param port: Port - @type port: int - @param path: Path - @type path: 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 - """ - - tree = __GetElementTree(protocol, server, port, - path + "/vimServiceVersions.xml", sslContext, - httpProxyHost, httpProxyPort) - if tree is not None: - return tree - - tree = __GetElementTree(protocol, server, port, - path + "/vimService.wsdl", sslContext, - httpProxyHost, httpProxyPort) - return tree +## if it exists, otherwise None. + + +def __GetServiceVersionDescription(protocol, server, port, path, sslContext, + httpProxyHost, httpProxyPort): + """ + Private method that returns an ElementTree describing the API versions + supported by the specified server. The result will be vimServiceVersions.xml + if it exists, otherwise vimService.wsdl if it exists, otherwise None. + + @param protocol: What protocol to use for the connection (e.g. https or http). + @type protocol: string + @param server: Which server to connect to. + @type server: string + @param port: Port + @type port: int + @param path: Path + @type path: 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 + """ + + return __GetElementTree(protocol, server, port, + path + "/vimServiceVersions.xml", sslContext, + httpProxyHost, httpProxyPort) ## Private method that returns true if the service version description document ## indicates that the desired version is supported + def __VersionIsSupported(desiredVersion, serviceVersionDescription): - """ - Private method that returns true if the service version description document - indicates that the desired version is supported - - @param desiredVersion: The version we want to see if the server supports - (eg. vim.version.version2. - @type desiredVersion: string - @param serviceVersionDescription: A root ElementTree for vimServiceVersions.xml - or vimService.wsdl. - @type serviceVersionDescription: root ElementTree - """ - - root = serviceVersionDescription - if root.tag == 'namespaces': - # serviceVersionDescription appears to be a vimServiceVersions.xml document - if root.get('version') != '1.0': - raise RuntimeError('vimServiceVersions.xml has version %s,' \ - ' which is not understood' % (root.get('version'))) - desiredVersionId = versionIdMap[desiredVersion] - supportedVersion = None - for namespace in root.findall('namespace'): - versionId = namespace.findtext('version') - if versionId == desiredVersionId: - return True - else: - for versionId in namespace.findall('priorVersions/version'): - if versionId.text == desiredVersionId: - return True - else: - # serviceVersionDescription must be a vimService.wsdl document - wsdlNS = 'http://schemas.xmlsoap.org/wsdl/' - importElement = serviceVersionDescription.find('.//{%s}import' % wsdlNS) - supportedVersion = versionMap[importElement.get('namespace')[4:]] - if IsChildVersion(supportedVersion, desiredVersion): - return True - return False + """ + Private method that returns true if the service version description document + indicates that the desired version is supported + + @param desiredVersion: The version we want to see if the server supports + (eg. vim.version.version9. + @type desiredVersion: string + @param serviceVersionDescription: An ElementTree for vimServiceVersions.xml + or vimService.wsdl. + @type serviceVersionDescription: ElementTree + """ + + root = serviceVersionDescription.getroot() + if root.tag == 'namespaces': + # serviceVersionDescription appears to be a vimServiceVersions.xml document + if root.get('version') != '1.0': + raise RuntimeError('vimServiceVersions.xml has version {0},' + ' which is not understood' + .format(root.get('version'))) + desiredVersionId = versionIdMap[desiredVersion] + supportedVersion = None + for namespace in root.findall('namespace'): + versionId = namespace.findtext('version') + if versionId == desiredVersionId: + return True + else: + for versionId in namespace.findall('priorVersions/version'): + if versionId.text == desiredVersionId: + return True + return False ## Private method that returns the most preferred API version supported by the ## specified server, -def __FindSupportedVersion(protocol, server, port, path, preferredApiVersions, sslContext, httpProxyHost=None, httpProxyPort=None): - """ - Private method that returns the most preferred API version supported by the - specified server, - - @param protocol: What protocol to use for the connection (e.g. https or http). - @type protocol: string - @param server: Which server to connect to. - @type server: string - @param port: Port - @type port: int - @param path: Path - @type path: string - @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. - @type preferredApiVersions: string or string list - @param sslContext: SSL Context describing the various SSL options. It is only - supported in Python 2.7.9 or higher. - @type sslContext: SSL.Context - """ - - serviceVersionDescription = __GetServiceVersionDescription(protocol, - server, - port, - path, - sslContext, - httpProxyHost, - httpProxyPort) - if serviceVersionDescription is None: - return None - - if not isinstance(preferredApiVersions, list): - preferredApiVersions = [ preferredApiVersions ] - - for desiredVersion in preferredApiVersions: - if __VersionIsSupported(desiredVersion, serviceVersionDescription): - 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, disableSslCertValidation=False): - """ - 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 +def __FindSupportedVersion(protocol, server, port, path, preferredApiVersions, + sslContext, httpProxyHost, httpProxyPort): + """ + Private method that returns the most preferred API version supported by the + specified server, + + @param protocol: What protocol to use for the connection (e.g. https or http). + @type protocol: string + @param server: Which server to connect to. + @type server: string + @param port: Port + @type port: int + @param path: Path + @type path: string + @param preferredApiVersions: Acceptable API version(s) (e.g. vim.version.version9) + If a list of versions is specified the versions should + be ordered from most to least preferred. + @type preferredApiVersions: string or string list + @param sslContext: SSL Context describing the various SSL options. It is only + supported in Python 2.7.9 or higher. + @type sslContext: SSL.Context + """ - @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 - @type disableSslCertValidation: bool - @param disableSslCertValidation: Creates an unverified SSL context when True. - """ - if preferredApiVersions is None: - preferredApiVersions = GetServiceVersions('vim25') - - sslContext = getSslContext(host, sslContext, disableSslCertValidation) - - supportedVersion = __FindSupportedVersion('https' if port > 0 else 'http', - host, - port, - path, - preferredApiVersions, - sslContext, - httpProxyHost, - httpProxyPort - ) - 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", connectionPoolTimeout=CONNECTION_POOL_IDLE_TIMEOUT_SEC, - preferredApiVersions=None, keyFile=None, certFile=None, - thumbprint=None, sslContext=None, b64token=None, mechanism='userpass', - disableSslCertValidation=False): - """ - Determine the most preferred API version supported by the specified server, - then connect to the specified server using that API version, login and return - the service instance object. - - Throws any exception back to caller. The service instance object is - also saved in the library for easy access. - - Clients should modify the service parameter only when connecting to - a VMOMI server other than hostd/vpxd. For both of the latter, the - default value is fine. - - @param protocol: What protocol to use for the connection (e.g. https or http). - @type protocol: string - @param host: Which host to connect to. - @type host: string - @param port: Port - @type port: int - @param user: User - @type user: string - @param pwd: Password - @type pwd: string - @param service: Service - @type service: string - @param path: Path - @type path: string - @param connectionPoolTimeout: Timeout in secs for idle connections to close, specify negative numbers for never - closing the connections - @type connectionPoolTimeout: int - @param preferredApiVersions: Acceptable API version(s) (e.g. vim.version.version3) + serviceVersionDescription = __GetServiceVersionDescription( + protocol, server, port, path, sslContext, httpProxyHost, httpProxyPort) + if serviceVersionDescription is None: + return None + + if not isinstance(preferredApiVersions, list): + preferredApiVersions = [preferredApiVersions] + + for desiredVersion in preferredApiVersions: + if __VersionIsSupported(desiredVersion, serviceVersionDescription): + 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, + samlToken=None, + sslContext=None, + httpConnectionTimeout=None, + connectionPoolTimeout=CONNECTION_POOL_IDLE_TIMEOUT_SEC, + disableSslCertValidation=False): + """ + 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.version9) + 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 + @type disableSslCertValidation: bool + @param disableSslCertValidation: Creates an unverified SSL context when True. + """ + if preferredApiVersions is None: + preferredApiVersions = GetServiceVersions('vim25') + + sslContext = getSslContext(host, sslContext, disableSslCertValidation) + + supportedVersion = __FindSupportedVersion('https' if port > 0 else 'http', + host, port, path, + preferredApiVersions, sslContext, + httpProxyHost, httpProxyPort) + if supportedVersion is None: + raise Exception("{0}:{1} is down or is not a VIM server" + .format(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, + samlToken=samlToken, + sslContext=sslContext, + httpConnectionTimeout=httpConnectionTimeout, + connectionPoolTimeout=connectionPoolTimeout) + + +def SmartConnect(protocol='https', + host='localhost', + port=443, + user='root', + pwd='', + service="hostd", + path="/sdk", + preferredApiVersions=None, + keyFile=None, + certFile=None, + httpProxyHost=None, + httpProxyPort=80, + thumbprint=None, + sslContext=None, + httpConnectionTimeout=None, + connectionPoolTimeout=CONNECTION_POOL_IDLE_TIMEOUT_SEC, + token=None, + tokenType=TOKEN_TYPE_SAML, + disableSslCertValidation=False, + customHeaders=None, + # Deprecated + b64token=None, + # Deprecated + mechanism='userpass'): + """ + Determine the most preferred API version supported by the specified server, + then connect to the specified server using that API version, login and return + the service instance object. + + Throws any exception back to caller. The service instance object is + also saved in the library for easy access. + + Clients should modify the service parameter only when connecting to + a VMOMI server other than hostd/vpxd. For both of the latter, the + default value is fine. + + @param protocol: What protocol to use for the connection (e.g. https or http). + @type protocol: string + @param host: Which host to connect to. + @type host: string + @param port: Port + @type port: int + @param user: User + @type user: string + @param pwd: Password + @type pwd: string + @param service: Service + @type service: string + @param path: Path + @type path: string + @param preferredApiVersions: Acceptable API version(s) (e.g. vim.version.version9) 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 - @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 - @type disableSslCertValidation: bool - @param disableSslCertValidation: Creates an unverified SSL context when True. - """ - - if preferredApiVersions is None: - preferredApiVersions = GetServiceVersions('vim25') - - sslContext = getSslContext(host, sslContext, disableSslCertValidation) - - supportedVersion = __FindSupportedVersion(protocol, - host, - port, - path, - preferredApiVersions, - sslContext) - if supportedVersion is None: - raise Exception("%s:%s is not a VIM server" % (host, port)) - - portNumber = protocol == "http" and -int(port) or int(port) - - return Connect(host=host, - port=portNumber, - user=user, - pwd=pwd, - service=service, - adapter='SOAP', - version=supportedVersion, - path=path, - connectionPoolTimeout=connectionPoolTimeout, - keyFile=keyFile, - certFile=certFile, - thumbprint=thumbprint, - sslContext=sslContext, - b64token=b64token, - mechanism=mechanism, - disableSslCertValidation=disableSslCertValidation) - -# SmartConnectNoSSL() is deprecated. Use SmartConnect(disableSslCertValidation=True). -def SmartConnectNoSSL(protocol='https', host='localhost', port=443, user='root', pwd='', - service="hostd", path="/sdk", connectionPoolTimeout=CONNECTION_POOL_IDLE_TIMEOUT_SEC, - preferredApiVersions=None, keyFile=None, certFile=None, - thumbprint=None, b64token=None, mechanism='userpass'): - """ - Provides a standard method for connecting to a specified server without SSL - verification. Useful when connecting to servers with self-signed certificates - or when you wish to ignore SSL altogether. Will attempt to create an unverified - SSL context and then connect via the SmartConnect method. - """ - - sslContext = ssl._create_unverified_context() - - return SmartConnect(protocol=protocol, - host=host, - port=port, - user=user, - pwd=pwd, - service=service, - path=path, - connectionPoolTimeout=connectionPoolTimeout, - preferredApiVersions=preferredApiVersions, - keyFile=keyFile, - certFile=certFile, - thumbprint=thumbprint, - sslContext=sslContext, - b64token=b64token, - mechanism=mechanism) + @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 httpProxyHost The host name of the proxy server. + @type httpProxyHost: string + @param httpProxyPort The proxy server port. + @type httpProxyPort: 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 + @param httpConnectionTimeout: Timeout in secs for http requests. + @type httpConnectionTimeout: int + @param connectionPoolTimeout: Timeout in secs for idle connections to close, specify + negative numbers for never closing the connections + @type connectionPoolTimeout: int + @type token: string + @param token: Authentication and Authorization token to use for the connection. + The presence of this token overrides the user and pwd parameters. + @type disableSslCertValidation: bool + @param disableSslCertValidation: Creates an unverified SSL context when True. + @param b64token: base64 encoded token + *** Deprecated: Use token instead *** + @type b64token: string + @param mechanism: authentication mechanism: userpass or sspi + *** Deprecated: Use tokenType instead *** + @type mechanism: string + """ + if preferredApiVersions is None: + preferredApiVersions = GetServiceVersions('vim25') + + sslContext = getSslContext(host, sslContext, disableSslCertValidation) + + supportedVersion = __FindSupportedVersion(protocol, host, port, path, + preferredApiVersions, sslContext, + httpProxyHost, httpProxyPort) + if supportedVersion is None: + raise Exception("{0}:{1} is down or is not a VIM server" + .format(host, port)) + + portNumber = protocol == "http" and -int(port) or int(port) + + return Connect(host=host, + port=portNumber, + user=user, + pwd=pwd, + service=service, + adapter='SOAP', + version=supportedVersion, + path=path, + keyFile=keyFile, + certFile=certFile, + httpProxyHost=httpProxyHost, + httpProxyPort=httpProxyPort, + thumbprint=thumbprint, + sslContext=sslContext, + httpConnectionTimeout=httpConnectionTimeout, + connectionPoolTimeout=connectionPoolTimeout, + token=token, + tokenType=tokenType, + disableSslCertValidation=disableSslCertValidation, + customHeaders=customHeaders, + b64token=b64token, + mechanism=mechanism) + + +# Deprecated def OpenUrlWithBasicAuth(url, user='root', pwd='', verify=True): - """ - Open the specified URL, using HTTP basic authentication to provide - the specified credentials to the server as part of the request. - Returns the response as a file-like object. - """ - return requests.get(url, auth=HTTPBasicAuth(user, pwd), verify=verify) + """ + Open the specified URL, using HTTP basic authentication to provide + the specified credentials to the server as part of the request. + Returns the response as a file-like object. + """ + pwMgr = urllib.request.HTTPPasswordMgrWithDefaultRealm() + pwMgr.add_password(None, url, user, pwd) + authHandler = urllib.request.HTTPBasicAuthHandler(pwMgr) + if verify: + opener = urllib.request.build_opener(authHandler) + else: + sslContext = ssl._create_unverified_context() + sslHandler = urllib.request.HTTPSHandler(context=sslContext) + opener = urllib.request.build_opener(sslHandler, authHandler) + return opener.open(url) + +# Deprecated def OpenPathWithStub(path, stub, verify=True): - """ - Open the specified path using HTTP, using the host/port/protocol - associated with the specified stub. If the stub has a session cookie, - it is included with the HTTP request. Returns the response as a - file-like object. - """ - from six.moves import http_client - if not hasattr(stub, 'scheme'): - raise vmodl.fault.NotSupported() - elif stub.scheme == http_client.HTTPConnection: - protocol = 'http' - verify = False - elif stub.scheme == http_client.HTTPSConnection: - protocol = 'https' - else: - raise vmodl.fault.NotSupported() - hostPort = stub.host - url = '%s://%s%s' % (protocol, hostPort, path) - headers = {} - if stub.cookie: - headers["Cookie"] = stub.cookie - return requests.get(url, headers=headers, verify=verify) + """ + Open the specified path using HTTP, using the host/port/protocol + associated with the specified stub. If the stub has a session cookie, + it is included with the HTTP request. Returns the response as a + file-like object. + """ + if not hasattr(stub, 'scheme'): + raise vmodl.fault.NotSupported() + elif stub.scheme == http_client.HTTPConnection: + protocol = 'http' + verify = False + elif stub.scheme == http_client.HTTPSConnection: + protocol = 'https' + else: + raise vmodl.fault.NotSupported() + hostPort = stub.host + url = '{0}://{1}{2}'.format(protocol, hostPort, path) + request = urllib.request.Request(url) + if stub.cookie: + request.add_header('Cookie', stub.cookie) + if verify: + return urllib.request.urlopen(request) + else: + sslContext = ssl._create_unverified_context() + return urllib.request.urlopen(request, context=sslContext) + +def IsManagedHost(): + """ + Check whether the host is managed by vCenter + """ + try: + SmartConnect() + return False + except Exception as e: + # connect to local server will be refused when host managed by vCenter + return True diff --git a/pyVim/sso.py b/pyVim/sso.py index 9ae5fe714..a2488cb72 100644 --- a/pyVim/sso.py +++ b/pyVim/sso.py @@ -1,9 +1,8 @@ -""" -A python helper module to do SSO related operations. -""" - +############################################################# +# Copyright (c) 2012-2022 VMware, Inc. +# A python helper module to do SSO related operations. +############################################################# __author__ = 'VMware, Inc.' -__copyright__ = 'Copyright 2012, 2017 VMware, Inc. All rights reserved.' #Standard library imports. import six.moves.http_client @@ -18,7 +17,10 @@ import base64 import hashlib -from pyVmomi import ThumbprintMismatchException +from pyVmomi.Security import VerifyCertThumbprint +from pyVmomi import _legacyThumbprintException +if _legacyThumbprintException: + from pyVmomi import ThumbprintMismatchException # noqa: F401 from uuid import uuid4 from io import BytesIO @@ -32,6 +34,10 @@ SHA256 = 'sha256' SHA512 = 'sha512' +# add default parser to etree with resolve_entities set to False +default_parser = etree.XMLParser(resolve_entities=False) +etree.set_default_parser(default_parser) + def _extract_certificate(cert): ''' Extract DER certificate/private key from DER/base64-ed DER/PEM string. @@ -57,11 +63,13 @@ def _extract_certificate(cert): # Unknown format. raise IOError('Invalid certificate file format') + #time.strftime() method returns 6 digit millisecond #Formatting time with 3 digit milliseconds def format_time(time): return time[:-3] + 'Z' + class SoapException(Exception): ''' Exception raised in case of STS request failure. @@ -94,6 +102,24 @@ def __str__(self): "faultxml: %(_soap_msg)s" % self.__dict__) +class SSOHTTPConnection(six.moves.http_client.HTTPConnection): + ''' + A class that establishes HTTP Connection. + Only intened to be used for calls routing through + a local sidecar proxy (localhost:1080). + ''' + def __init__(self, *args, **kwargs): + ''' + Initializer. See httplib.HTTPConnection for other arguments + ''' + tmpKwargs = {} + httpConn = six.moves.http_client.HTTPConnection + for key in httpConn.__init__.__code__.co_varnames: + if key in kwargs and key != 'self': + tmpKwargs[key] = kwargs[key] + self.host = kwargs.pop('host') + six.moves.http_client.HTTPConnection.__init__(self, *args, **tmpKwargs) + class SSOHTTPSConnection(six.moves.http_client.HTTPSConnection): ''' An HTTPS class that verifies server's certificate on connect. @@ -137,18 +163,16 @@ def _check_cert(self, peerCert): @rtype: boolean @return: True if peerCert is acceptable. False otherwise. ''' - if self.server_cert is not None: - if peerCert != self.server_cert: - self.sock.close() - self.sock = None - raise IOError("Invalid certificate") - if self.server_thumbprint is not None: - thumbprint = hashlib.sha1(peerCert).hexdigest().lower() # pylint: disable=E1101 - if thumbprint != self.server_thumbprint: - self.sock.close() - self.sock = None - raise ThumbprintMismatchException( - expected=self.server_thumbprint, actual=thumbprint) + try: + if self.server_cert is not None: + if peerCert != self.server_cert: + raise Exception("Invalid certificate") + if self.server_thumbprint: + VerifyCertThumbprint(peerCert, self.server_thumbprint) + except Exception: + self.sock.close() + self.sock = None + raise def connect(self): ''' @@ -163,17 +187,21 @@ def connect(self): self._check_cert(self.sock.getpeercert(True)) +def is_sidecar_request(scheme, host): + ''' + Check if the request is through + local sidecar (localhost:1080) + ''' + if scheme == "http" and host == "localhost:1080": + return True; + + class SsoAuthenticator(object): ''' A class to handle the transport layer communication between the client and the STS service. ''' - - def __init__(self, - sts_url, - sts_cert=None, - thumbprint=None - ): + def __init__(self, sts_url, sts_cert=None, thumbprint=None): ''' Initializer for SsoAuthenticator. @@ -218,8 +246,16 @@ def perform_request(self, ''' parsed = urlparse(self._sts_url) host = parsed.netloc # pylint: disable=E1101 + scheme = parsed.scheme encoded_message = soap_message.encode(UTF_8) - if hasattr(ssl, '_create_unverified_context'): + + ''' + Allow creation of HTTPConnection, only for calls routing + through local sidecar (localhost:1080) + ''' + if is_sidecar_request(scheme, host): + webservice = SSOHTTPConnection(host=host) + elif hasattr(ssl, '_create_unverified_context'): # Python 2.7.9 has stronger SSL certificate validation, so we need # to pass in a context when dealing with self-signed certificates. webservice = SSOHTTPSConnection(host=host, @@ -244,7 +280,8 @@ def perform_request(self, webservice.putheader("Content-type", "text/xml; charset=\"UTF-8\"") webservice.putheader("Content-length", "%d" % len(encoded_message)) webservice.putheader("Connection", "keep-alive") - webservice.putheader("SOAPAction", + webservice.putheader( + "SOAPAction", "http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue") webservice.endheaders() webservice.send(encoded_message) @@ -317,15 +354,12 @@ def get_bearer_saml_assertion(self, token_duration=token_duration) soap_message = request.construct_bearer_token_request( delegatable=delegatable, renewable=renewable) - bearer_token = self.perform_request(soap_message, - public_key, - private_key, - ssl_context) - return etree.tostring( - _extract_element(etree.fromstring(bearer_token), - 'Assertion', - {'saml2': "urn:oasis:names:tc:SAML:2.0:assertion"}), - pretty_print=False).decode(UTF_8) + bearer_token = self.perform_request(soap_message, public_key, + private_key, ssl_context) + return etree.tostring(_extract_element( + etree.fromstring(bearer_token), 'Assertion', + {'saml2': "urn:oasis:names:tc:SAML:2.0:assertion"}), + pretty_print=False).decode(UTF_8) def _get_gss_soap_response(self, binary_token, @@ -367,8 +401,7 @@ def _get_gss_soap_response(self, gss_binary_token=binary_token) soap_message = request.construct_bearer_token_request_with_binary_token( delegatable=delegatable, renewable=renewable) - return self.perform_request(soap_message, - ssl_context=ssl_context) + return self.perform_request(soap_message, ssl_context=ssl_context) def _get_bearer_saml_assertion_win(self, request_duration=60, @@ -410,27 +443,25 @@ def _get_bearer_saml_assertion_win(self, while True: err, out_buf = sspiclient.authorize(in_buf) sectoken = base64.b64encode(out_buf[0].Buffer) - soap_response = self._get_gss_soap_response(sectoken, - request_duration, token_duration, - delegatable, renewable, ssl_context) + soap_response = self._get_gss_soap_response( + sectoken, request_duration, token_duration, delegatable, + renewable, ssl_context) et = etree.fromstring(soap_response) try: # Check if we have received a challenge token from the server - element = _extract_element(et, - 'BinaryExchange', - {'ns': "http://docs.oasis-open.org/ws-sx/ws-trust/200512"}) + element = _extract_element( + et, 'BinaryExchange', + {'ns': "http://docs.oasis-open.org/ws-sx/ws-trust/200512"}) negotiate_token = element.text out_buf[0].Buffer = base64.b64decode(negotiate_token) in_buf = out_buf except KeyError: # Response does not contain the negotiate token. # It should contain SAML token then. - saml_token = etree.tostring( - _extract_element( - et, - 'Assertion', - {'saml2': "urn:oasis:names:tc:SAML:2.0:assertion"}), - pretty_print=False).decode(UTF_8) + saml_token = etree.tostring(_extract_element( + et, 'Assertion', + {'saml2': "urn:oasis:names:tc:SAML:2.0:assertion"}), + pretty_print=False).decode(UTF_8) break return saml_token @@ -472,26 +503,24 @@ def _get_bearer_saml_assertion_lin(self, if result < 0: break sectoken = kerberos.authGSSClientResponse(context) - soap_response = self._get_gss_soap_response(sectoken, - request_duration, token_duration, delegatable, - renewable) + soap_response = self._get_gss_soap_response( + sectoken, request_duration, token_duration, delegatable, + renewable) et = etree.fromstring(soap_response) try: # Check if we have received a challenge token from the server - element = _extract_element(et, - 'BinaryExchange', - {'ns': "http://docs.oasis-open.org/ws-sx/ws-trust/200512"}) + element = _extract_element( + et, 'BinaryExchange', + {'ns': "http://docs.oasis-open.org/ws-sx/ws-trust/200512"}) negotiate_token = element.text challenge = negotiate_token except KeyError: # Response does not contain the negotiate token. # It should contain SAML token then. - saml_token = etree.tostring( - _extract_element( - et, - 'Assertion', - {'saml2': "urn:oasis:names:tc:SAML:2.0:assertion"}), - pretty_print=False).decode(UTF_8) + saml_token = etree.tostring(_extract_element( + et, 'Assertion', + {'saml2': "urn:oasis:names:tc:SAML:2.0:assertion"}), + pretty_print=False).decode(UTF_8) break return saml_token @@ -523,8 +552,8 @@ def get_bearer_saml_assertion_gss_api(self, @return: The SAML assertion. ''' if sys.platform == "win32": - saml_token = self._get_bearer_saml_assertion_win(request_duration, - token_duration, delegatable, renewable) + saml_token = self._get_bearer_saml_assertion_win( + request_duration, token_duration, delegatable, renewable) else: raise Exception("Currently, not supported on this platform") ## TODO Remove this exception once SSO supports validation of tickets @@ -580,16 +609,12 @@ def get_hok_saml_assertion(self, soap_message = request.construct_hok_request(delegatable=delegatable, act_as_token=act_as_token, renewable=renewable) - hok_token = self.perform_request(soap_message, - public_key, - private_key, + hok_token = self.perform_request(soap_message, public_key, private_key, ssl_context) - return etree.tostring( - _extract_element( - etree.fromstring(hok_token), - 'Assertion', - {'saml2': "urn:oasis:names:tc:SAML:2.0:assertion"}), - pretty_print=False).decode(UTF_8) + return etree.tostring(_extract_element( + etree.fromstring(hok_token), 'Assertion', + {'saml2': "urn:oasis:names:tc:SAML:2.0:assertion"}), + pretty_print=False).decode(UTF_8) def get_token_by_token(self, hok_token, @@ -641,12 +666,11 @@ def get_token_by_token(self, hok_token = self.perform_request(soap_message=soap_message, ssl_context=ssl_context) - return etree.tostring( - _extract_element( - etree.fromstring(hok_token), - 'Assertion', - {'saml2': "urn:oasis:names:tc:SAML:2.0:assertion"}), - pretty_print=False).decode(UTF_8) + return etree.tostring(_extract_element( + etree.fromstring(hok_token), 'Assertion', + {'saml2': "urn:oasis:names:tc:SAML:2.0:assertion"}), + pretty_print=False).decode(UTF_8) + class SecurityTokenRequest(object): ''' @@ -696,10 +720,12 @@ def __init__(self, self._security_token_id = _generate_id() current = datetime.datetime.utcnow() self._created = format_time(current.strftime(TIME_FORMAT)) - self._expires = format_time((current + datetime.timedelta(seconds= - token_duration)).strftime(TIME_FORMAT)) - self._request_expires = format_time((current + datetime.timedelta(seconds= - request_duration)).strftime(TIME_FORMAT)) + self._expires = format_time( + (current + + datetime.timedelta(seconds=token_duration)).strftime(TIME_FORMAT)) + self._request_expires = format_time( + (current + datetime.timedelta(seconds=request_duration) + ).strftime(TIME_FORMAT)) self._timestamp = TIMESTAMP_TEMPLATE % self.__dict__ self._username = escape(username) if username else username self._password = escape(password) if password else password @@ -713,7 +739,7 @@ def __init__(self, self._binary_exchange = None self._public_key = None if gss_binary_token: - self._binary_exchange = BINARY_EXCHANGE_TEMPLATE % gss_binary_token + self._binary_exchange = BINARY_EXCHANGE_TEMPLATE % gss_binary_token #The following are populated later. Set to None here to keep in-line #with PEP8. self._binary_security_token = None @@ -738,7 +764,9 @@ def __init__(self, with open(self._public_key_file) as fp: self._public_key = fp.read() - def construct_bearer_token_request(self, delegatable=False, renewable=False): + def construct_bearer_token_request(self, + delegatable=False, + renewable=False): ''' Constructs the actual Bearer token SOAP request. @@ -756,9 +784,8 @@ def construct_bearer_token_request(self, delegatable=False, renewable=False): self._renewable = str(renewable).lower() return _canonicalize(REQUEST_TEMPLATE % self.__dict__) - def construct_bearer_token_request_with_binary_token(self, - delegatable=False, - renewable=False): + def construct_bearer_token_request_with_binary_token( + self, delegatable=False, renewable=False): ''' Constructs the actual Bearer token SOAP request using the binary exchange GSS/SSPI token. @@ -775,7 +802,9 @@ def construct_bearer_token_request_with_binary_token(self, self._renewable = str(renewable).lower() return _canonicalize(GSS_REQUEST_TEMPLATE % self.__dict__) - def construct_hok_request(self, delegatable=False, act_as_token=None, + def construct_hok_request(self, + delegatable=False, + act_as_token=None, renewable=False): ''' Constructs the actual HoK token SOAP request. @@ -828,20 +857,25 @@ def sign_request(self): trusted service. ''' base_xml = etree.fromstring(self._xml_text) - request_tree = _extract_element(base_xml, - 'Body', - {'SOAP-ENV': "http://schemas.xmlsoap.org/soap/envelope/"}) + request_tree = _extract_element( + base_xml, 'Body', + {'SOAP-ENV': "http://schemas.xmlsoap.org/soap/envelope/"}) request = _canonicalize(etree.tostring(request_tree)) - request_tree = _extract_element(base_xml, - 'Timestamp', - {'ns3': "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"}) + request_tree = _extract_element( + base_xml, 'Timestamp', { + 'ns3': + "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" + }) timestamp = _canonicalize(etree.tostring(request_tree)) self._request_digest = _make_hash(request.encode(UTF_8)).decode(UTF_8) # pylint: disable=W0612 - self._timestamp_digest = _make_hash(timestamp.encode(UTF_8)).decode(UTF_8) # pylint: disable=W0612 + self._timestamp_digest = _make_hash(timestamp.encode(UTF_8)).decode( + UTF_8) # pylint: disable=W0612 self._algorithm = SHA256 self._signed_info = _canonicalize(SIGNED_INFO_TEMPLATE % self.__dict__) - self._signature_value = _sign(self._private_key, self._signed_info).decode(UTF_8) - self._signature_text = _canonicalize(SIGNATURE_TEMPLATE % self.__dict__) + self._signature_value = _sign(self._private_key, + self._signed_info).decode(UTF_8) + self._signature_text = _canonicalize(SIGNATURE_TEMPLATE % + self.__dict__) self.embed_signature() def embed_signature(self): @@ -849,15 +883,20 @@ def embed_signature(self): Embeds the signature in to the header of the SOAP request. ''' self._xml = etree.fromstring(self._xml_text) - security = _extract_element(self._xml, - 'Security', - {'ns6': "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"}) + security = _extract_element( + self._xml, 'Security', { + 'ns6': + "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" + }) self._signature = etree.fromstring(self._signature_text) security.append(self._signature) self._xml_text = etree.tostring(self._xml).decode(UTF_8) -def add_saml_context(serialized_request, saml_token, private_key_file, request_duration=60): +def add_saml_context(serialized_request, + saml_token, + private_key_file, + request_duration=60): ''' A helper method provided to sign the outgoing LoginByToken requests with the HoK token. @@ -883,21 +922,26 @@ def add_saml_context(serialized_request, saml_token, private_key_file, request_d xml = etree.fromstring(serialized_request) value_map = {} value_map['_request_id'] = _generate_id() - request_body = _extract_element(xml, - 'Body', - {'soapenv': "http://schemas.xmlsoap.org/soap/envelope/"}) - request_body.nsmap["wsu"] = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" - request_body.set("{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Id", value_map['_request_id']) + request_body = _extract_element( + xml, 'Body', {'soapenv': "http://schemas.xmlsoap.org/soap/envelope/"}) + request_body.nsmap[ + "wsu"] = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" + request_body.set( + "{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Id", + value_map['_request_id']) value_map['_request_digest'] = _make_hash_sha512( - _canonicalize(etree.tostring(request_body)) - .encode(UTF_8)).decode(UTF_8) - security = _extract_element(xml, - 'Security', - {'ns6': "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"}) + _canonicalize( + etree.tostring(request_body)).encode(UTF_8)).decode(UTF_8) + security = _extract_element( + xml, 'Security', { + 'ns6': + "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" + }) current = datetime.datetime.utcnow() value_map['_created'] = format_time(current.strftime(TIME_FORMAT)) - value_map['_request_expires'] = format_time((current + datetime.timedelta(seconds= - request_duration)).strftime(TIME_FORMAT)) + value_map['_request_expires'] = format_time( + (current + + datetime.timedelta(seconds=request_duration)).strftime(TIME_FORMAT)) value_map['_timestamp_id'] = _generate_id() timestamp = _canonicalize(TIMESTAMP_TEMPLATE % value_map) value_map['_timestamp_digest'] = _make_hash_sha512( @@ -910,8 +954,8 @@ def add_saml_context(serialized_request, saml_token, private_key_file, request_d value_map['_signed_info'], SHA512).decode(UTF_8) value_map['samlId'] = etree.fromstring(saml_token).get("ID") - signature = etree.fromstring(_canonicalize(REQUEST_SIGNATURE_TEMPLATE % - value_map)) + signature = etree.fromstring( + _canonicalize(REQUEST_SIGNATURE_TEMPLATE % value_map)) security.append(signature) return etree.tostring(xml, pretty_print=False).decode(UTF_8) @@ -948,16 +992,16 @@ def _load_private_key(der_key): # Unencrypted PKCS8 for OpenSSL 0.9.8, and PKCS1, just in case... for key_type in ('PRIVATE KEY', 'RSA PRIVATE KEY'): try: - return crypto.load_privatekey(crypto.FILETYPE_PEM, - '-----BEGIN ' + key_type + '-----\n' + - base64.encodestring(der_key).decode(UTF_8) + - '-----END ' + key_type + '-----\n', - b'') + return crypto.load_privatekey( + crypto.FILETYPE_PEM, '-----BEGIN ' + key_type + '-----\n' + + base64.encodebytes(der_key).decode(UTF_8) + '-----END ' + + key_type + '-----\n', b'') except (crypto.Error, ValueError): pass # We could try 'ENCRYPTED PRIVATE KEY' here - but we do not know passphrase. raise + def _sign(private_key, data, digest=SHA256): ''' An internal helper method to sign the 'data' with the 'private_key'. @@ -979,6 +1023,7 @@ def _sign(private_key, data, digest=SHA256): pkey = _load_private_key(_extract_certificate(private_key)) return base64.b64encode(crypto.sign(pkey, data.encode(UTF_8), digest)) + def _canonicalize(xml_string): ''' Given an xml string, canonicalize the string per @@ -990,12 +1035,14 @@ def _canonicalize(xml_string): @rtype: C{str} @return: Canonicalized string in Unicode. ''' - parser = etree.XMLParser(remove_blank_text=True) + # TODO: keep the parser between _canonicalize() invocations. + parser = etree.XMLParser(remove_blank_text=True, resolve_entities=False) tree = etree.fromstring(xml_string, parser=parser).getroottree() string = BytesIO() tree.write_c14n(string, exclusive=True, with_comments=False) return string.getvalue().decode(UTF_8) + def _extract_element(xml, element_name, namespace): ''' An internal method provided to extract an element from the given XML. @@ -1011,9 +1058,9 @@ def _extract_element(xml, element_name, namespace): @rtype: etree element. @return: The extracted element. ''' - assert(len(namespace) == 1) - result = xml.xpath("//%s:%s" % (list(namespace.keys())[0], element_name), - namespaces=namespace) + assert (len(namespace) == 1) + result = xml.xpath("//{0}:{1}".format(list(namespace.keys())[0], element_name), + namespaces=namespace) if result: return result[0] else: diff --git a/pyVim/task.py b/pyVim/task.py index 2630d3a1b..0d377a654 100644 --- a/pyVim/task.py +++ b/pyVim/task.py @@ -1,17 +1,6 @@ -# VMware vSphere Python SDK -# Copyright (c) 2016 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. +############################################################# +# Copyright (c) 2005-2022 VMware, Inc. +############################################################# ## @file task.py ## @brief Task functions @@ -19,7 +8,6 @@ ## This module provies synchronization of client/server operations ## since many VIM operations return 'tasks' which can have ## varying completion times. - """ Task functions @@ -28,8 +16,12 @@ times. """ +__author__ = 'VMware, Inc' + from pyVmomi import vmodl, vim +import time + ## ## @brief Exception class to represent when task is blocked (e.g.: @@ -48,12 +40,12 @@ class TaskBlocked(Exception): # verbose information about task progress # def TaskUpdatesVerbose(task, progress): - if isinstance(task.info.progress, int): - info = task.info + info = task.info + if isinstance(info.progress, int): if not isinstance(progress, str): - progress = '%d%% (%s)' % (info.progress, info.state) - print('Task %s (key:%s, desc:%s) - %s' % ( - info.name.info.name, info.key, info.description, progress)) + progress = '{:.0f}% ({})'.format(info.progress, info.state) + print('Task %s (key:%s, desc:%s) - %s' % + (info.name.info.name, info.key, info.description, progress)) globalTaskUpdate = None @@ -72,11 +64,14 @@ def SetTasksVerbose(verbose=True): ## raiseOnError is set to true ## @param si [in] ServiceInstance to use. If set to None, use the default one. ## @param pc [in] property collector to use else retrieve one from cache +## @param maxWaitTime [in] The maximum amount of time the task is allowed to +## run. Throws an exception if raiseOnError is True. ## @param onProgressUpdate [in] callable to call with task progress updates. ## For example: ## ## def OnTaskProgressUpdate(task, percentDone): -## sys.stderr.write('# Task %s: %d%% complete ...\n' % (task, percentDone)) +## sys.stderr.write('# Task {}: {:.0f}% complete ...\n' +## .format(task, percentDone)) ## ## Given a task object and a service instance, wait for the task completion ## @@ -89,58 +84,85 @@ def WaitForTask(task, raiseOnError=True, si=None, pc=None, - onProgressUpdate=None): + maxWaitTime=None, + onProgressUpdate=None, + log=None, + getAllUpdates=False): """ Wait for task to complete. @type raiseOnError : bool @param raiseOnError : Any exception thrown is thrown up to the caller - if raiseOnError is set to true. + if raiseOnError is set to true. @type si : ManagedObjectReference to a ServiceInstance. @param si : ServiceInstance to use. If None, use the - information from the task. + information from the task. @type pc : ManagedObjectReference to a PropertyCollector. @param pc : Property collector to use. If None, get it from - the ServiceInstance. + the ServiceInstance. + @type maxWaitTime : numeric value + @param maxWaitTime : The maximum amount of time the task is allowed to + run. Throws an exception if raiseOnError is True. @type onProgressUpdate : callable @param onProgressUpdate : Callable to call with task progress updates. + @type log : Optional logger. + @param log : Logging. + @type getAllUpdates : Optional bool value. Default is False. + @param getAllUpdates : Get all updates for the task. For example:: def OnTaskProgressUpdate(task, percentDone): - print 'Task %s is %d%% complete.' % (task, percentDone) + print 'Task {} is {:.0f}% complete.'.format(task, percentDone) """ - if si is None: - si = vim.ServiceInstance("ServiceInstance", task._stub) if pc is None: + if si is None: si = vim.ServiceInstance("ServiceInstance", task._stub) pc = si.content.propertyCollector - progressUpdater = ProgressUpdater(task, onProgressUpdate) + progressUpdater = ProgressUpdater(task, onProgressUpdate, getAllUpdates) progressUpdater.Update('created') filter = CreateFilter(pc, task) version, state = None, None + + if maxWaitTime: + waitTime = maxWaitTime + time.time() + # Loop looking for updates till the state moves to a completed state. while state not in (vim.TaskInfo.State.success, vim.TaskInfo.State.error): try: version, state = GetTaskStatus(task, version, pc) progressUpdater.UpdateIfNeeded() - except vmodl.fault.ManagedObjectNotFound as e: + + if maxWaitTime and waitTime < time.time(): + err = "Task exceeded timeout of %d seconds" % maxWaitTime + progressUpdater.Update(err) + if raiseOnError is True: + raise Exception(err) + break + except vmodl.Fault.ManagedObjectNotFound as e: print("Task object has been deleted: %s" % e.obj) break filter.Destroy() + info = task.info if state == "error": - progressUpdater.Update('error: %s' % str(task.info.error)) + progressUpdater.Update("error: %s" % info.error) if raiseOnError: - raise task.info.error + raise info.error else: - print("Task reported error: " + str(task.info.error)) - else: + _LogMsg(log, "Task reported error: %s" % info.error) + elif state == "success": progressUpdater.Update('completed') + elif state == "queued": + _LogMsg(log, "Task reports as queued: %s" % info) + progressUpdater.UpdateIfNeeded() + else: # state = running + _LogMsg(log, "Task reports as still running: %s" % info) + progressUpdater.UpdateIfNeeded() return state @@ -157,25 +179,23 @@ def WaitForTasks(tasks, si=None, pc=None, onProgressUpdate=None, - results=None): + results=None, + getAllUpdates=False): """ - Wait for mulitiple tasks to complete. Much faster than calling WaitForTask - N times + Wait for multiple tasks to complete. Much faster than calling WaitForTask + N times. """ if not tasks: return - if si is None: - si = vim.ServiceInstance("ServiceInstance", tasks[0]._stub) - if pc is None: - pc = si.content.propertyCollector - if results is None: - results = [] + if si is None: si = vim.ServiceInstance("ServiceInstance", tasks[0]._stub) + if pc is None: pc = si.content.propertyCollector + if results is None: results = [] progressUpdaters = {} for task in tasks: - progressUpdater = ProgressUpdater(task, onProgressUpdate) + progressUpdater = ProgressUpdater(task, onProgressUpdate, getAllUpdates) progressUpdater.Update('created') progressUpdaters[str(task)] = progressUpdater @@ -215,7 +235,8 @@ def WaitForTasks(tasks, if raiseOnError: raise err else: - print("Task %s reported error: %s" % (taskId, str(err))) + print("Task %s reported error: %s" % + (taskId, str(err))) progressUpdaters.pop(taskId) else: if onProgressUpdate: @@ -230,11 +251,12 @@ def WaitForTasks(tasks, def GetTaskStatus(task, version, pc): update = pc.WaitForUpdates(version) - state = task.info.state + info = task.info + state = info.state - if (state == 'running' and task.info.name is not None and task.info.name.info.name != "Destroy" - and task.info.name.info.name != "Relocate"): - CheckForQuestionPending(task) + if (state == 'running' and info.name is not None and info.name.info.name + not in ['Destroy', 'Relocate', 'CreateVm']): + CheckForQuestionPending(info) return update.version, state @@ -250,15 +272,17 @@ def CreateTasksFilter(pc, tasks): return None # First create the object specification as the task object. - objspecs = [vmodl.query.PropertyCollector.ObjectSpec(obj=task) - for task in tasks] + objspecs = [ + vmodl.Query.PropertyCollector.ObjectSpec(obj=task) for task in tasks + ] # Next, create the property specification as the state. - propspec = vmodl.query.PropertyCollector.PropertySpec( - type=vim.Task, pathSet=[], all=True) + propspec = vmodl.Query.PropertyCollector.PropertySpec(type=vim.Task, + pathSet=[], + all=True) # Create a filter spec with the specified object and property spec. - filterspec = vmodl.query.PropertyCollector.FilterSpec() + filterspec = vmodl.Query.PropertyCollector.FilterSpec() filterspec.objectSet = objspecs filterspec.propSet = [propspec] @@ -266,18 +290,24 @@ def CreateTasksFilter(pc, tasks): return pc.CreateFilter(filterspec, True) -def CheckForQuestionPending(task): - """ - Check to see if VM needs to ask a question, throw exception - """ +def CheckForQuestionPending(info): + """ Check to see if VM needs to ask a question, throw exception """ - vm = task.info.entity + vm = info.entity if vm is not None and isinstance(vm, vim.VirtualMachine): qst = vm.runtime.question if qst is not None: raise TaskBlocked("Task blocked, User Intervention required") +def _LogMsg(log, message): + """Helper logging a message with logger or print depending on the flag.""" + if log: + log(message) + else: + print(message) + + ## ## @brief Class that keeps track of task percentage complete and calls ## a provided callback when it changes. @@ -287,10 +317,10 @@ class ProgressUpdater(object): Class that keeps track of task percentage complete and calls a provided callback when it changes. """ - - def __init__(self, task, onProgressUpdate): + def __init__(self, task, onProgressUpdate, getAllUpdates=False): self.task = task self.onProgressUpdate = onProgressUpdate + self.getAllUpdates = getAllUpdates self.prevProgress = 0 self.progress = 0 @@ -305,7 +335,7 @@ def Update(self, state): def UpdateIfNeeded(self): self.progress = self.task.info.progress - if self.progress != self.prevProgress: + if self.getAllUpdates or self.progress != self.prevProgress: self.Update(self.progress) self.prevProgress = self.progress diff --git a/pyVmomi/Cache.py b/pyVmomi/Cache.py index be3b00894..a98decc78 100644 --- a/pyVmomi/Cache.py +++ b/pyVmomi/Cache.py @@ -1,42 +1,30 @@ -# VMware vSphere Python SDK -# Copyright (c) 2008-2015 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. - """ +Copyright (c) 2008-2022 VMware, Inc. + This module implements the cache decorator """ -__author__ = "VMware, Inc." +__author__ = "VMware, Inc" + def Cache(fn): - """ Function cache decorator """ - def fnCache(*args, **kwargs): - """ Cache function """ - key = (args and tuple(args) or None, - kwargs and frozenset(kwargs.items()) or None) - if key not in fn.__cached__: - fn.__cached__[key] = cache = fn(*args, **kwargs) - else: - cache = fn.__cached__[key] - return cache + """ Function cache decorator """ + def fnCache(*args, **kwargs): + """ Cache function """ + key = (args and tuple(args) + or None, kwargs and frozenset(list(kwargs.items())) or None) + if key not in fn.__cached__: + fn.__cached__[key] = cache = fn(*args, **kwargs) + else: + cache = fn.__cached__[key] + return cache - def ResetCache(): - """ Reset cache """ - fn.__cached__ = {} + def ResetCache(): + """ Reset cache """ + fn.__cached__ = {} - setattr(fn, "__cached__", {}) - setattr(fn, "__resetcache__", ResetCache) - fnCache.__name__ = fn.__name__ - fnCache.__doc__ = fn.__doc__ - fnCache.__dict__.update(fn.__dict__) - return fnCache + setattr(fn, "__cached__", {}) + setattr(fn, "__resetcache__", ResetCache) + fnCache.__name__ = fn.__name__ + fnCache.__doc__ = fn.__doc__ + fnCache.__dict__.update(fn.__dict__) + return fnCache diff --git a/pyVmomi/Differ.py b/pyVmomi/Differ.py index f85f9d1a9..32897a612 100644 --- a/pyVmomi/Differ.py +++ b/pyVmomi/Differ.py @@ -1,229 +1,228 @@ -# VMware vSphere Python SDK -# Copyright (c) 2008-2016 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. - -## Diff any two objects +# ********************************************************** +# Copyright (c) 2008-2022 VMware, Inc. +# ********************************************************** +# Diff any two objects + +import logging + import six from six.moves import zip -from pyVmomi import VmomiSupport, types -import logging -from VmomiSupport import GetWsdlName, Type +from .VmomiSupport import F_LINK, F_OPTIONAL, GetWsdlName, Type, types __Log__ = logging.getLogger('ObjDiffer') + def LogIf(condition, message): - """Log a message if the condition is met""" - if condition: - __Log__.debug(message) + """Log a message if the condition is met""" + if condition: + __Log__.debug(message) + def IsPrimitiveType(obj): - """See if the passed in type is a Primitive Type""" - return (isinstance(obj, types.bool) or isinstance(obj, types.byte) or - isinstance(obj, types.short) or isinstance(obj, six.integer_types) or - isinstance(obj, types.double) or isinstance(obj, types.float) or - isinstance(obj, six.string_types) or - isinstance(obj, types.PropertyPath) or - isinstance(obj, types.ManagedMethod) or - isinstance(obj, types.datetime) or - isinstance(obj, types.URI) or isinstance(obj, type)) + """See if the passed in type is a Primitive Type""" + return (isinstance(obj, types.bool) or isinstance(obj, types.byte) + or isinstance(obj, types.short) + or isinstance(obj, six.integer_types) + or isinstance(obj, types.double) or isinstance(obj, types.float) + or isinstance(obj, six.string_types) + or isinstance(obj, types.PropertyPath) + or isinstance(obj, types.ManagedMethod) + or isinstance(obj, types.datetime) or isinstance(obj, types.URI) + or isinstance(obj, types.binary) or isinstance(obj, type)) class Differ: - """Class for comparing two Objects""" - def __init__(self, looseMatch=False, ignoreArrayOrder=True): - self._looseMatch = looseMatch - self._ignoreArrayOrder = ignoreArrayOrder - - def DiffAnyObjects(self, oldObj, newObj, isObjLink=False): - """Diff any two Objects""" - if oldObj == newObj: - return True - if not oldObj or not newObj: - __Log__.debug('DiffAnyObjects: One of the objects is unset.') - return self._looseMatch - oldObjInstance = oldObj - newObjInstance = newObj - if isinstance(oldObj, list): - oldObjInstance = oldObj[0] - if isinstance(newObj, list): - newObjInstance = newObj[0] - # Need to see if it is a primitive type first since type information - # will not be available for them. - if (IsPrimitiveType(oldObj) and IsPrimitiveType(newObj) - and oldObj.__class__.__name__ == newObj.__class__.__name__): - if oldObj == newObj: + """Class for comparing two Objects""" + def __init__(self, looseMatch=False, ignoreArrayOrder=True): + self._looseMatch = looseMatch + self._ignoreArrayOrder = ignoreArrayOrder + + def DiffAnyObjects(self, oldObj, newObj, isObjLink=False): + """Diff any two Objects""" + if oldObj == newObj: return True - elif oldObj == None or newObj == None: - __Log__.debug('DiffAnyObjects: One of the objects in None') - return False - oldType = Type(oldObjInstance) - newType = Type(newObjInstance) - if oldType != newType: - __Log__.debug('DiffAnyObjects: Types do not match %s != %s' % - (repr(GetWsdlName(oldObjInstance.__class__)), - repr(GetWsdlName(newObjInstance.__class__)))) - return False - elif isinstance(oldObj, list): - return self.DiffArrayObjects(oldObj, newObj, isObjLink) - elif isinstance(oldObjInstance, types.ManagedObject): - return (not oldObj and not newObj) or (oldObj and newObj - and oldObj._moId == newObj._moId) - elif isinstance(oldObjInstance, types.DataObject): - if isObjLink: - bMatch = oldObj.GetKey() == newObj.GetKey() - LogIf(not bMatch, 'DiffAnyObjects: Keys do not match %s != %s' - % (oldObj.GetKey(), newObj.GetKey())) - return bMatch - return self.DiffDataObjects(oldObj, newObj) - - else: - raise TypeError("Unknown type: "+repr(GetWsdlName(oldObj.__class__))) - - def DiffDoArrays(self, oldObj, newObj, isElementLinks): - """Diff two DataObject arrays""" - if len(oldObj) != len(newObj): - __Log__.debug('DiffDoArrays: Array lengths do not match %d != %d' - % (len(oldObj), len(newObj))) - return False - for i, j in zip(oldObj, newObj): - if isElementLinks: - if i.GetKey() != j.GetKey(): - __Log__.debug('DiffDoArrays: Keys do not match %s != %s' - % (i.GetKey(), j.GetKey())) - return False - else: - if not self.DiffDataObjects(i, j): - __Log__.debug( - 'DiffDoArrays: one of the elements do not match') - return False - return True - - def DiffAnyArrays(self, oldObj, newObj, isElementLinks): - """Diff two arrays which contain Any objects""" - if len(oldObj) != len(newObj): - __Log__.debug('DiffAnyArrays: Array lengths do not match. %d != %d' - % (len(oldObj), len(newObj))) - return False - for i, j in zip(oldObj, newObj): - if not self.DiffAnyObjects(i, j, isElementLinks): - __Log__.debug('DiffAnyArrays: One of the elements do not match.') + if not oldObj or not newObj: + __Log__.debug('DiffAnyObjects: One of the objects is unset.') + return self._looseMatch + oldObjInstance = oldObj + newObjInstance = newObj + if isinstance(oldObj, list): + oldObjInstance = oldObj[0] + if isinstance(newObj, list): + newObjInstance = newObj[0] + # Need to see if it is a primitive type first since type information + # will not be available for them. + if (IsPrimitiveType(oldObj) and IsPrimitiveType(newObj) + and oldObj.__class__.__name__ == newObj.__class__.__name__): + if oldObj == newObj: + return True + elif oldObj is None or newObj is None: + __Log__.debug('DiffAnyObjects: One of the objects in None') + return False + oldType = Type(oldObjInstance) + newType = Type(newObjInstance) + if oldType != newType: + __Log__.debug('DiffAnyObjects: Types do not match %s != %s', + repr(GetWsdlName(oldObjInstance.__class__)), + repr(GetWsdlName(newObjInstance.__class__))) + return False + elif isinstance(oldObj, list): + return self.DiffArrayObjects(oldObj, newObj, isObjLink) + elif isinstance(oldObjInstance, types.ManagedObject): + return (not oldObj + and not newObj) or (oldObj and newObj + and oldObj._moId == newObj._moId) + elif isinstance(oldObjInstance, types.DataObject): + if isObjLink: + bMatch = oldObj.GetKey() == newObj.GetKey() + LogIf( + not bMatch, 'DiffAnyObjects: Keys do not match %s != %s' % + (oldObj.GetKey(), newObj.GetKey())) + return bMatch + return self.DiffDataObjects(oldObj, newObj) + + else: + raise TypeError("Unknown type: " + + repr(GetWsdlName(oldObj.__class__))) + + def DiffDoArrays(self, oldObj, newObj, isElementLinks): + """Diff two DataObject arrays""" + if len(oldObj) != len(newObj): + __Log__.debug('DiffDoArrays: Array lengths do not match %d != %d', + len(oldObj), len(newObj)) + return False + for i, j in zip(oldObj, newObj): + if isElementLinks: + if i.GetKey() != j.GetKey(): + __Log__.debug('DiffDoArrays: Keys do not match %s != %s', + i.GetKey(), j.GetKey()) + return False + else: + if not self.DiffDataObjects(i, j): + __Log__.debug( + 'DiffDoArrays: one of the elements do not match') + return False + return True + + def DiffAnyArrays(self, oldObj, newObj, isElementLinks): + """Diff two arrays which contain Any objects""" + if len(oldObj) != len(newObj): + __Log__.debug( + 'DiffAnyArrays: Array lengths do not match. %d != %d', + len(oldObj), len(newObj)) return False - return True - - def DiffPrimitiveArrays(self, oldObj, newObj): - """Diff two primitive arrays""" - if len(oldObj) != len(newObj): - __Log__.debug('DiffDoArrays: Array lengths do not match %d != %d' - % (len(oldObj), len(newObj))) - return False - match = True - if self._ignoreArrayOrder: - oldSet = oldObj and frozenset(oldObj) or frozenset() - newSet = newObj and frozenset(newObj) or frozenset() - match = (oldSet == newSet) - else: - for i, j in zip(oldObj, newObj): - if i != j: - match = False - break - if not match: - __Log__.debug( - 'DiffPrimitiveArrays: One of the elements do not match.') - return False - return True - - - def DiffArrayObjects(self, oldObj, newObj, isElementLinks=False): - """Method which deligates the diffing of arrays based on the type""" - if oldObj == newObj: - return True - if not oldObj or not newObj: - return False - if len(oldObj) != len(newObj): - __Log__.debug('DiffArrayObjects: Array lengths do not match %d != %d' - % (len(oldObj), len(newObj))) - return False - firstObj = oldObj[0] - if IsPrimitiveType(firstObj): - return self.DiffPrimitiveArrays(oldObj, newObj) - elif isinstance(firstObj, types.ManagedObject): - return self.DiffAnyArrays(oldObj, newObj, isElementLinks) - elif isinstance(firstObj, types.DataObject): - return self.DiffDoArrays(oldObj, newObj, isElementLinks) - else: - raise TypeError("Unknown type: %s" % oldObj.__class__) - - - def DiffDataObjects(self, oldObj, newObj): - """Diff Data Objects""" - if oldObj == newObj: - return True - if not oldObj or not newObj: - __Log__.debug('DiffDataObjects: One of the objects in None') - return False - oldType = Type(oldObj) - newType = Type(newObj) - if oldType != newType: - __Log__.debug( - 'DiffDataObjects: Types do not match for dataobjects. %s != %s' - % (oldObj._wsdlName, newObj._wsdlName)) - return False - for prop in oldObj._GetPropertyList(): - oldProp = getattr(oldObj, prop.name) - newProp = getattr(newObj, prop.name) - propType = oldObj._GetPropertyInfo(prop.name).type - if not oldProp and not newProp: - continue - elif ((prop.flags & VmomiSupport.F_OPTIONAL) and - self._looseMatch and (not newProp or not oldProp)): - continue - elif not oldProp or not newProp: + for i, j in zip(oldObj, newObj): + if not self.DiffAnyObjects(i, j, isElementLinks): + __Log__.debug( + 'DiffAnyArrays: One of the elements do not match.') + return False + return True + + def DiffPrimitiveArrays(self, oldObj, newObj): + """Diff two primitive arrays""" + if len(oldObj) != len(newObj): + __Log__.debug('DiffDoArrays: Array lengths do not match %d != %d', + len(oldObj), len(newObj)) + return False + match = True + if self._ignoreArrayOrder: + oldSet = oldObj and frozenset(oldObj) or frozenset() + newSet = newObj and frozenset(newObj) or frozenset() + match = (oldSet == newSet) + else: + for i, j in zip(oldObj, newObj): + if i != j: + match = False + break + if not match: __Log__.debug( - 'DiffDataObjects: One of the objects has property %s unset' - % prop.name) + 'DiffPrimitiveArrays: One of the elements do not match.') return False + return True - bMatch = True - if IsPrimitiveType(oldProp): - bMatch = oldProp == newProp - elif isinstance(oldProp, types.ManagedObject): - bMatch = self.DiffAnyObjects(oldProp, newProp, prop.flags - & VmomiSupport.F_LINK) - elif isinstance(oldProp, types.DataObject): - if prop.flags & VmomiSupport.F_LINK: - bMatch = oldObj.GetKey() == newObj.GetKey() - LogIf(not bMatch, 'DiffDataObjects: Key match failed %s != %s' - % (oldObj.GetKey(), newObj.GetKey())) - else: - bMatch = self.DiffAnyObjects(oldProp, newProp, prop.flags - & VmomiSupport.F_LINK) - elif isinstance(oldProp, list): - bMatch = self.DiffArrayObjects(oldProp, newProp, prop.flags - & VmomiSupport.F_LINK) - else: - raise TypeError("Unknown type: "+repr(propType)) - - if not bMatch: - __Log__.debug('DiffDataObjects: Objects differ in property %s' - % prop.name) + def DiffArrayObjects(self, oldObj, newObj, isElementLinks=False): + """Method which deligates the diffing of arrays based on the type""" + if oldObj == newObj: + return True + if not oldObj or not newObj: return False - return True + if len(oldObj) != len(newObj): + __Log__.debug( + 'DiffArrayObjects: Array lengths do not match %d != %d', + len(oldObj), len(newObj)) + return False + firstObj = oldObj[0] + if IsPrimitiveType(firstObj): + return self.DiffPrimitiveArrays(oldObj, newObj) + elif isinstance(firstObj, types.ManagedObject): + return self.DiffAnyArrays(oldObj, newObj, isElementLinks) + elif isinstance(firstObj, types.DataObject): + return self.DiffDoArrays(oldObj, newObj, isElementLinks) + else: + raise TypeError("Unknown type: {0}".format(oldObj.__class__)) + + def DiffDataObjects(self, oldObj, newObj): + """Diff Data Objects""" + if oldObj == newObj: + return True + if not oldObj or not newObj: + __Log__.debug('DiffDataObjects: One of the objects in None') + return False + oldType = Type(oldObj) + newType = Type(newObj) + if oldType != newType: + __Log__.debug( + 'DiffDataObjects: Types do not match for dataobjects. ' + '%s != %s', oldObj._wsdlName, newObj._wsdlName) + return False + for prop in oldObj._GetPropertyList(): + oldProp = getattr(oldObj, prop.name) + newProp = getattr(newObj, prop.name) + propType = oldObj._GetPropertyInfo(prop.name).type + if not oldProp and not newProp: + continue + elif ((prop.flags & F_OPTIONAL) and self._looseMatch + and (not newProp or not oldProp)): + continue + elif not oldProp or not newProp: + __Log__.debug( + 'DiffDataObjects: One of the objects has ' + 'the property %s unset', prop.name) + return False + + bMatch = True + if IsPrimitiveType(oldProp): + bMatch = oldProp == newProp + elif isinstance(oldProp, types.ManagedObject): + bMatch = self.DiffAnyObjects(oldProp, newProp, prop.flags + & F_LINK) + elif isinstance(oldProp, types.DataObject): + if prop.flags & F_LINK: + bMatch = oldObj.GetKey() == newObj.GetKey() + LogIf( + not bMatch, + 'DiffDataObjects: Key match failed %s != %s' % + (oldObj.GetKey(), newObj.GetKey())) + else: + bMatch = self.DiffAnyObjects(oldProp, newProp, prop.flags + & F_LINK) + elif isinstance(oldProp, list): + bMatch = self.DiffArrayObjects(oldProp, newProp, + prop.flags & F_LINK) + else: + raise TypeError("Unknown type: " + repr(propType)) + + if not bMatch: + __Log__.debug('DiffDataObjects: Objects differ in property %s', + prop.name) + return False + return True def DiffAnys(obj1, obj2, looseMatch=False, ignoreArrayOrder=True): - """Diff any two objects. Objects can either be primitive type - or DataObjects""" - differ = Differ(looseMatch = looseMatch, ignoreArrayOrder = ignoreArrayOrder) - return differ.DiffAnyObjects(obj1, obj2) + """Diff any two objects. Objects can either be primitive type + or DataObjects + """ + differ = Differ(looseMatch=looseMatch, ignoreArrayOrder=ignoreArrayOrder) + return differ.DiffAnyObjects(obj1, obj2) diff --git a/pyVmomi/DynamicTypeManagerHelper.py b/pyVmomi/DynamicTypeManagerHelper.py deleted file mode 100644 index 0874bf740..000000000 --- a/pyVmomi/DynamicTypeManagerHelper.py +++ /dev/null @@ -1,289 +0,0 @@ -# VMware vSphere Python SDK -# Copyright (c) 2008-2015 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. - -""" -This module is a converter from dynamic type to pyVmomi type -""" -__author__ = "VMware, Inc." - -from pyVmomi import VmomiSupport, vmodl -from Cache import Cache - -## Dynamic type importer -# -class DynamicTypeImporter: - """ Dynamic type importer """ - - ## Constructor - # - # @param stub Server stub - def __init__(self, stub, hostSystem=None): - self.stub = stub - self.hostSystem = hostSystem - - ## Get dynamic type manager - # - # @return moRef to dynamic type manager - @Cache - def GetTypeManager(self): - """ Get dynamic type manager """ - dynTypeMgr = None - if self.hostSystem: - try: - dynTypeMgr = self.hostSystem.RetrieveDynamicTypeManager() - except vmodl.fault.MethodNotFound as err: - pass - - if not dynTypeMgr: - # Older host not support RetrieveDynamicTypeManager - cmdlineTypesMoId = "ha-dynamic-type-manager" - dynTypeMgr = vmodl.reflect.DynamicTypeManager(cmdlineTypesMoId, - self.stub) - return dynTypeMgr - - ## Import dynamic types - # - # @param prefix Only types with the specified prefix are imported - # @return dynamic types - @Cache - def ImportTypes(self, prefix=''): - """ Build dynamic types """ - # Use QueryTypeInfo to get all types - dynTypeMgr = self.GetTypeManager() - filterSpec = None - if prefix != '': - filterSpec = vmodl.reflect.DynamicTypeManager.TypeFilterSpec( - typeSubstr=prefix) - allTypes = dynTypeMgr.QueryTypeInfo(filterSpec) - - ## Convert dynamic types to pyVmomi types - # - DynamicTypeConstructor().CreateTypes(allTypes) - return allTypes - - -## Construct pyVmomi types from dynamic types definition -# -class DynamicTypeConstructor: - """ Dynamic type constructor """ - - _mapFlags = { "optional": VmomiSupport.F_OPTIONAL, - "linkable": VmomiSupport.F_LINKABLE, - "link": VmomiSupport.F_LINK, - "secret": VmomiSupport.F_SECRET } - - ## Constructor - # - def __init__(self): - """ Constructor """ - pass - - ## Create pyVmomi types from vmodl.reflect.DynamicTypeManager.AllTypeInfo - # - # @param allTypes vmodl.reflect.DynamicTypeManager.AllTypeInfo - def CreateTypes(self, allTypes): - """ - Create pyVmomi types from vmodl.reflect.DynamicTypeManager.AllTypeInfo - """ - enumTypes, dataTypes, managedTypes = self._ConvertAllTypes(allTypes) - self._CreateAllTypes(enumTypes, dataTypes, managedTypes) - - ## Convert all dynamic types to pyVmomi type definitions - # - # @param allTypes vmodl.reflect.DynamicTypeManager.AllTypeInfo - # @return a tuple of (enumTypes, dataTypes, managedTypes) - def _ConvertAllTypes(self, allTypes): - """ Convert all dynamic types to pyVmomi type definitions """ - # Generate lists good for VmomiSupport.CreateXYZType - enumTypes = self._Filter(self._ConvertEnumType, allTypes.enumTypeInfo) - dataTypes = self._Filter(self._ConvertDataType, allTypes.dataTypeInfo) - managedTypes = self._Filter(self._ConvertManagedType, - allTypes.managedTypeInfo) - retAllTypes = (enumTypes, dataTypes, managedTypes) - return retAllTypes - - ## Create pyVmomi types from pyVmomi type definitions - # - # @param enumTypes pyVmomi enum type definitions - # @param dataTypes pyVmomi data type definitions - # @param managedTypes pyVmomi managed type definitions - def _CreateAllTypes(self, enumTypes, dataTypes, managedTypes): - """ Create pyVmomi types from pyVmomi type definitions """ - - # Create versions - for typeInfo in managedTypes: - name = typeInfo[0] - version = typeInfo[3] - VmomiSupport.AddVersion(version, '', '1.0', 0, name) - VmomiSupport.AddVersionParent(version, 'vmodl.version.version0') - VmomiSupport.AddVersionParent(version, 'vmodl.version.version1') - VmomiSupport.AddVersionParent(version, version) - - # Create partial types - for fn, infos in (VmomiSupport.CreateEnumType, enumTypes), \ - (VmomiSupport.CreateDataType, dataTypes), \ - (VmomiSupport.CreateManagedType, managedTypes): - for typeInfo in infos: - try: - fn(*typeInfo) - except Exception as err: - #Ignore errors due to duplicate importing - pass - - def _ConvertAnnotations(self, annotations): - """ Convert annotations to pyVmomi flags """ - flags = 0 - if annotations: - for annotation in annotations: - flags |= self._mapFlags.get(annotation.name, 0) - return flags - - @staticmethod - def _Filter(fn, types): - """ Call fn for each non null element in types. Similiar to filter """ - if types: - return [fn(prop) for prop in types if prop is not None] - else: - return [] - - def _ConvertParamType(self, paramType): - """ - Convert vmodl.reflect.DynamicTypeManager.ParamTypeInfo to pyVmomi param - definition - """ - if paramType: - name = paramType.name - version = paramType.version - aType = paramType.type - flags = self._ConvertAnnotations(paramType.annotation) - privId = paramType.privId - param = (name, aType, version, flags, privId) - else: - param = None - return param - - def _ConvertMethodType(self, methodType): - """ - Convert vmodl.reflect.DynamicTypeManager.MethodTypeInfo to pyVmomi method - definition - """ - if methodType: - name = methodType.name - wsdlName = methodType.wsdlName - version = methodType.version - params = self._Filter(self._ConvertParamType, methodType.paramTypeInfo) - privId = methodType.privId - faults = methodType.fault - - # Figure out reture info - if methodType.returnTypeInfo: - returnTypeInfo = methodType.returnTypeInfo - retFlags = self._ConvertAnnotations(returnTypeInfo.annotation) - methodRetType = returnTypeInfo.type - else: - retFlags = 0 - methodRetType = "void" - if wsdlName.endswith("_Task"): - # TODO: Need a seperate task return type for task, instead of - # hardcode vim.Task as return type - retType = "vim.Task" - else: - retType = methodRetType - retInfo = (retFlags, retType, methodRetType) - - method = (name, wsdlName, version, params, retInfo, privId, faults) - else: - method = None - return method - - def _ConvertManagedPropertyType(self, propType): - """ - Convert vmodl.reflect.DynamicTypeManager.PropertyTypeInfo to pyVmomi - managed property definition - """ - if propType: - name = propType.name - version = propType.version - aType = propType.type - flags = self._ConvertAnnotations(propType.annotation) - privId = propType.privId - prop = (name, aType, version, flags, privId) - else: - prop = None - return prop - - def _ConvertManagedType(self, managedType): - """ - Convert vmodl.reflect.DynamicTypeManager.ManagedTypeInfo to pyVmomi - managed type definition - """ - if managedType: - vmodlName = managedType.name - wsdlName = managedType.wsdlName - version = managedType.version - parent = managedType.base[0] - props = self._Filter(self._ConvertManagedPropertyType, managedType.property) - methods = self._Filter(self._ConvertMethodType, managedType.method) - moType = (vmodlName, wsdlName, parent, version, props, methods) - else: - moType = None - return moType - - def _ConvertDataPropertyType(self, propType): - """ - Convert vmodl.reflect.DynamicTypeManager.PropertyTypeInfo to pyVmomi - data property definition - """ - if propType: - name = propType.name - version = propType.version - aType = propType.type - flags = self._ConvertAnnotations(propType.annotation) - prop = (name, aType, version, flags) - else: - prop = None - return prop - - def _ConvertDataType(self, dataType): - """ - Convert vmodl.reflect.DynamicTypeManager.DataTypeInfo to pyVmomi data - type definition - """ - if dataType: - vmodlName = dataType.name - wsdlName = dataType.wsdlName - version = dataType.version - parent = dataType.base[0] - props = self._Filter(self._ConvertDataPropertyType, dataType.property) - doType = (vmodlName, wsdlName, parent, version, props) - else: - doType = None - return doType - - def _ConvertEnumType(self, enumType): - """ - Convert vmodl.reflect.DynamicTypeManager.EnumTypeInfo to pyVmomi enum - type definition - """ - if enumType: - vmodlName = enumType.name - wsdlName = enumType.wsdlName - version = enumType.version - values = enumType.value - enumType = (vmodlName, wsdlName, version, values) - else: - enumType = None - return enumType - diff --git a/pyVmomi/Feature.py b/pyVmomi/Feature.py new file mode 100644 index 000000000..842375b18 --- /dev/null +++ b/pyVmomi/Feature.py @@ -0,0 +1,36 @@ +""" +Copyright (c) 2021-2022 VMware, Inc. + +This module handles pyVmomi features states +""" +from collections import namedtuple +from . import _assert_not_initialized + +# pyVmomi features along with their default states. +# e.g., 'newFeature': False +_features = { +} + +Features = namedtuple('Features', _features.keys()) +flags = Features(**_features) + + +def _init(): + global flags + flags = Features(**_features) + + +def get_feature_names(): + return _features.keys() + + +def set_feature_state(feature_name, state): + _assert_not_initialized() + if feature_name not in _features: + raise AttributeError("Feature '{0}' is not supported!".format(feature_name)) + if not isinstance(feature_name, str): + raise TypeError("Feature name should be string!") + if not isinstance(state, bool): + raise TypeError("Feature state should be boolean!") + + _features[feature_name] = state diff --git a/pyVmomi/Iso8601.py b/pyVmomi/Iso8601.py index 46df81ce5..a3bea8305 100644 --- a/pyVmomi/Iso8601.py +++ b/pyVmomi/Iso8601.py @@ -1,359 +1,361 @@ -# VMware vSphere Python SDK -# Copyright (c) 2008-2015 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 print_function -# TODO (hartsocks): Introduce logging to remove the need for print function. +#!/usr/bin/env python """ +Copyright (c) 2009-2022 VMware, Inc. + This module is for ISO 8601 parsing """ -__author__ = 'VMware, Inc.' +__author__ = 'VMware, Inc' -from six import iteritems +import re import time from datetime import datetime, timedelta, tzinfo -import re -""" Regular expression to parse a subset of ISO 8601 format """ +import six + +# Regular expression to parse a subset of ISO 8601 format _dtExpr = re.compile( - # XMLSchema datetime. Mandatory to have - and : - # See: http://www.w3.org/TR/xmlschema-2/#isoformats - # Note: python datetime cannot handle the following: - # - leap second, ie. 0-60 seconds (not 0-59) - # - BC (negative years) - # year [-]0000..9999 - r'(?P-?\d{4})' \ - # month 01..12 - r'(-(?P(0[1-9]|1[0-2]))' \ + # XMLSchema datetime. Mandatory to have - and : + # See: http://www.w3.org/TR/xmlschema-2/#isoformats + # Note: python datetime cannot handle the following: + # - leap second, ie. 0-60 seconds (not 0-59) + # - BC (negative years) + # year [-]0000..9999 + r'(?P-?\d{4})' + # month 01..12 + r'(-(?P(0[1-9]|1[0-2]))' # day 01..31 - r'(-(?P(0[1-9]|[1-2]\d|3[01])))?)?' \ - # time separator 'T' - r'(T' \ + r'(-(?P(0[1-9]|[1-2]\d|3[01])))?)?' + # time separator 'T' + r'(T' # hour 00..24 - r'(?P([01]\d|2[0-4]))' \ + r'(?P([01]\d|2[0-4]))' # minute 00..59 - r'((:(?P[0-5]\d))' \ - # seconds 00..60 (leap second ok) - r'(:(?P([0-5]\d|60))' \ - # microsecond. max 16 digits - # - Should not allows trailing zeros. But python isoformat() put zeros - # after microseconds. Oh well, allows trailing zeros, quite harmless - r'(\.(?P\d{1,16}))?)?)?' \ + r'((:(?P[0-5]\d))' + # seconds 00..60 (leap second ok) + r'(:(?P([0-5]\d|60))' + # microsecond. max 16 digits + # - Should not allows trailing zeros. But python isoformat() put zeros + # after microseconds. Oh well, allows trailing zeros, quite harmless + r'(\.(?P\d{1,16}))?)?)?' # UTC 'Z', or... - r'((?PZ)' \ + r'((?PZ)' # tz [+-]00..13:0..59|14:00 - r'|((?P[+-](([0]\d)|(1[0-3])|(?P)14))' \ - r'(:(?P(?(tzlimit)00|([0-5]\d))))?))?' \ - r')?$') + r'|((?P[+-](([0]\d)|(1[0-3])|(?P)14))' + r'(:(?P(?(tzlimit)00|([0-5]\d))))?))?' + r')?$') + +# Default date time val. Key should match the tags in _dtExpr +_dtExprKeyDefValMap = { + 'year': None, + 'month': 1, + 'day': 1, + 'hour': 0, + 'minute': 0, + 'second': 0, + 'microsecond': 0 +} -""" Default date time val. Key should match the tags in _dtExpr """ -_dtExprKeyDefValMap = {'year' : None, 'month' : 1, 'day' : 1, - 'hour' : 0, 'minute' : 0, 'second' : 0, - 'microsecond' : 0} class TZInfo(tzinfo): - """ Timezone info class """ + """ Timezone info class """ - timedelta0 = timedelta(hours=0) - timedelta1 = timedelta(hours=1) + timedelta0 = timedelta(hours=0) + timedelta1 = timedelta(hours=1) - def __init__(self, tzname='UTC', utcOffset=None, dst=None): - self._tzname = tzname - if not utcOffset: - utcOffset = self.timedelta0 - self._utcOffset = utcOffset - if not dst: - dst = None - self._dst = dst + def __init__(self, tzname='UTC', utcOffset=None, dst=None): + self._tzname = tzname + if not utcOffset: + utcOffset = self.timedelta0 + self._utcOffset = utcOffset + if not dst: + dst = None + self._dst = dst - def utcoffset(self, dt): - return self._utcOffset + self.dst(dt) + def utcoffset(self, dt): + return self._utcOffset + self.dst(dt) - def tzname(self, dt): - return self._tzname + def tzname(self, dt): + return self._tzname - def dst(self, dt): - ret = self.timedelta0 - if self._dst: - if self._dst[0] <= dt.replace(tzinfo=None) < self._dst[1]: - ret = self.timedelta1 - return ret + def dst(self, dt): + ret = self.timedelta0 + if self._dst: + if self._dst[0] <= dt.replace(tzinfo=None) < self._dst[1]: + ret = self.timedelta1 + return ret class TZManager: - """ Time zone manager """ - _tzInfos = {} + """ Time zone manager """ + _tzInfos = {} - @staticmethod - def GetTZInfo(tzname='UTC', utcOffset=None, dst=None): - """ Get / Add timezone info """ - key = (tzname, utcOffset, dst) - tzInfo = TZManager._tzInfos.get(key) - if not tzInfo: - tzInfo = TZInfo(tzname, utcOffset, dst) - TZManager._tzInfos[key] = tzInfo - return tzInfo + @staticmethod + def GetTZInfo(tzname='UTC', utcOffset=None, dst=None): + """ Get / Add timezone info """ + key = (tzname, utcOffset, dst) + tzInfo = TZManager._tzInfos.get(key) + if not tzInfo: + tzInfo = TZInfo(tzname, utcOffset, dst) + TZManager._tzInfos[key] = tzInfo + return tzInfo def ParseISO8601(datetimeStr): - """ - Parse ISO 8601 date time from string. - Returns datetime if ok, None otherwise - Note: Allows YYYY / YYYY-MM, but truncate YYYY -> YYYY-01-01, - YYYY-MM -> YYYY-MM-01 - Truncate microsecond to most significant 6 digits - """ - datetimeVal = None - match = _dtExpr.match(datetimeStr) - if match: - try: - dt = {} - for key, defaultVal in iteritems(_dtExprKeyDefValMap): - val = match.group(key) + """ + Parse ISO 8601 date time from string. + Returns datetime if ok, None otherwise + Note: Allows YYYY / YYYY-MM, but truncate YYYY -> YYYY-01-01, + YYYY-MM -> YYYY-MM-01 + Truncate microsecond to most significant 6 digits + """ + datetimeVal = None + match = _dtExpr.match(datetimeStr) + if match: + try: + dt = {} + for key, defaultVal in six.iteritems(_dtExprKeyDefValMap): + val = match.group(key) + if val: + if key == 'microsecond': + val = val[:6] + '0' * (6 - len(val)) + dt[key] = int(val) + elif defaultVal: + dt[key] = defaultVal + + # Orig. XMLSchema don't allow all zeros year. But newer draft is ok + # if dt['year'] == 0: + # # Year cannot be all zeros + # raise Exception('Year cannot be all zeros') + + # 24 is a special case. + # It is actually represented as next day 00:00 + delta = None + if dt.get('hour', 0) == 24: + # Must be 24:00:00.0 + if (dt.get('minute', 0) == 0 and dt.get('second', 0) == 0 + and dt.get('microsecond', 0) == 0): + dt['hour'] = 23 + delta = timedelta(hours=1) + else: + return None + + # Set tzinfo + # TODO: dst + tzInfo = None + val = match.group('tzutc') if val: - if key == 'microsecond': - val = val[:6] + '0' * (6 - len(val)) - dt[key] = int(val) - elif defaultVal: - dt[key] = defaultVal - - # Orig. XMLSchema don't allow all zeros year. But newer draft is ok - #if dt['year'] == 0: - # # Year cannot be all zeros - # raise Exception('Year cannot be all zeros') - - # 24 is a special case. It is actually represented as next day 00:00 - delta = None - if dt.get('hour', 0) == 24: - # Must be 24:00:00.0 - if dt.get('minute', 0) == 0 and dt.get('second', 0) == 0 and \ - dt.get('microsecond', 0) == 0: - dt['hour'] = 23 - delta = timedelta(hours=1) + tzInfo = TZManager.GetTZInfo() else: - return None - - # Set tzinfo - # TODO: dst - tzInfo = None - val = match.group('tzutc') - if val: - tzInfo = TZManager.GetTZInfo() - else: - val = match.group('tzhr') - if val: - # tz hours offset - tzhr = int(val) - utcsign = val[0] - - # tz minutes offset - tzmin = 0 - val = match.group('tzmin') - if val: - tzmin = tzhr >= 0 and int(val) or -int(val) - - # Better tzname (map UTC +-00:00 to UTC) - tzname = 'UTC' - if tzhr != 0 or tzmin != 0: - tzname += ' %s%02d:%02d' % (utcsign, abs(tzhr), abs(tzmin)) - - tzInfo = TZManager.GetTZInfo(tzname=tzname, - utcOffset=timedelta(hours=tzhr, - minutes=tzmin)) - if tzInfo: - dt['tzinfo'] = tzInfo - - datetimeVal = datetime(**dt) - if delta: - datetimeVal += delta - except Exception as e: - pass - return datetimeVal + val = match.group('tzhr') + if val: + # tz hours offset + tzhr = int(val) + utcsign = val[0] + + # tz minutes offset + tzmin = 0 + val = match.group('tzmin') + if val: + tzmin = tzhr >= 0 and int(val) or -int(val) + + # Better tzname (map UTC +-00:00 to UTC) + tzname = 'UTC' + if tzhr != 0 or tzmin != 0: + tzname += ' %s%02d:%02d' % (utcsign, abs(tzhr), + abs(tzmin)) + + tzInfo = TZManager.GetTZInfo( + tzname=tzname, + utcOffset=timedelta(hours=tzhr, minutes=tzmin)) + if tzInfo: + dt['tzinfo'] = tzInfo + + datetimeVal = datetime(**dt) + if delta: + datetimeVal += delta + except Exception: + pass + return datetimeVal + + +def GetUtcOffset(): + try: + return time.localtime().tm_gmtoff + except AttributeError: + useAltZone = time.daylight and time.localtime().tm_isdst + return -(time.altzone if useAltZone else time.timezone) def ISO8601Format(dt): - """ - Python datetime isoformat() has the following problems: - - leave trailing 0 at the end of microseconds (violates XMLSchema rule) - - tz print +00:00 instead of Z - - Missing timezone offset for datetime without tzinfo - """ - isoStr = dt.strftime('%Y-%m-%dT%H:%M:%S') - if dt.microsecond: - isoStr += ('.%06d' % dt.microsecond).rstrip('0') - if dt.tzinfo: - tz = dt.strftime('%z') - else: - if time.daylight and time.localtime().tm_isdst: - utcOffset_minutes = -time.altzone / 60 - else: - utcOffset_minutes = -time.timezone / 60 - tz = "%+.2d%.2d" % (utcOffset_minutes / 60, (abs(utcOffset_minutes) % 60)) - if tz == '+0000': - return isoStr + 'Z' - elif tz: - return isoStr + tz[:3] + ':' + tz[3:] - else: - # Local offset is unknown - return isoStr + '-00:00' + """ + Python datetime isoformat() has the following problems: + - leave trailing 0 at the end of microseconds (violates XMLSchema rule) + - tz print +00:00 instead of Z + - Missing timezone offset for datetime without tzinfo + """ + isoStr = dt.strftime('%Y-%m-%dT%H:%M:%S') + if dt.microsecond: + isoStr += ('.%06d' % dt.microsecond).rstrip('0') + if dt.tzinfo: + tz = dt.strftime('%z') + else: + utcOffset_minutes = GetUtcOffset() / 60 + tz = "%+.2d%.2d" % (utcOffset_minutes / 60, + (abs(utcOffset_minutes) % 60)) + if tz == '+0000': + return isoStr + 'Z' + elif tz: + return isoStr + tz[:3] + ':' + tz[3:] + else: + # Local offset is unknown + return isoStr + '-00:00' # Testing if __name__ == '__main__': - # Valid entries - for testStr in [ - '1971', # 1971-01-01 - '1971-11', # 1971-11-01 - '1971-11-02', - '1971-11-02T23', - '1971-11-02T23Z', - '1971-11-02T23:04', - '1971-11-02T23:04Z', - '1971-11-02T23:04:15', - '1971-11-02T23:04:15Z', - '1971-11-02T23:04:15.1', - '1971-11-02T23:04:15.01', - '1971-11-02T23:04:15.023456', - '1971-11-02T23:04:15.103456Z', - '1971-11-02T23:04:15.123456+11', - '1971-11-02T23:04:15.123456-11', - '1971-11-02T23:04:15.123456+11:30', - '1971-11-02T23:04:15.123456-11:30', - '1971-11-02T23:04:15.123456+00:00', # Same as Z - '1971-11-02T23:04:15.123456-00:00', # Same as Z - - '1971-01-02T23:04:15+14', - '1971-01-02T23:04:15+14:00', - '1971-01-02T23:04:15-14', - '1971-01-02T23:04:15-14:00', - - # Valid: Truncate microsec to 6 digits - '1971-01-02T23:04:15.123456891+11', - - '1971-01-02T24', # 24 is valid. It should represent the 00:00 the - # next day - '1971-01-02T24:00', - '1971-01-02T24:00:00', - '1971-01-02T24:00:00.0', - - # Should NOT be valid but python isoformat adding trailing zeros - '1971-01-02T23:04:15.123430', # Microseconds ends in zero - '1971-01-02T23:04:15.0', # Microseconds ends in zero - - # Should be valid but python datetime don't support it - #'2005-12-31T23:59:60Z', # Leap second - #'-0001', # BC 1 - ]: - dt = ParseISO8601(testStr) - if dt == None: - print('Failed to parse ({0})'.format(testStr)) - assert(False) - - # Make sure we can translate back - isoformat = ISO8601Format(dt) - dt1 = ParseISO8601(isoformat) - if dt.tzinfo is None: - dt = dt.replace(tzinfo=dt1.tzinfo) - if dt1 != dt: - print('ParseISO8601 -> ISO8601Format -> ParseISO8601 failed ({0})'.format(testStr)) - assert(False) - - # Make sure we can parse python isoformat() - dt2 = ParseISO8601(dt.isoformat()) - if dt2 == None: - print('ParseISO8601("{0}".isoformat()) failed'.format(testStr)) - assert(False) - - print(testStr, '->', dt, isoformat) - - # Basic form - for testStr in [ - '197111', # 1971-11-01 - '19711102', - '19711102T23', - '19711102T23Z', - '19711102T2304', - '19711102T2304Z', - '19711102T230415', - '19711102T230415Z', - '19711102T230415.123456', - '19711102T230415.123456Z', - '19711102T230415.123456+11', - '19711102T230415.123456-11', - '19711102T230415.123456+1130', - '19711102T230415.123456-1130', - ]: - # Reject for now - dt = ParseISO8601(testStr) - if dt != None: - print('ParseISO8601 ({0}) should fail, but it did not'.format(testStr)) - assert(False) - #print testStr, '->', dt - #assert(dt != None) - - # Invalid entries - for testStr in [ - # Xml schema reject year 0 - '0000', # 0 years are not allowed - '+0001', # Leading + is not allowed - - '', # Empty datetime str - '09', # Years must be at least 4 digits - '1971-01-02T', # T not follow by time - '1971-01-02TZ', # T not follow by time - '1971-01-02T+10', # T not follow by time - '1971-01-02T-10', # T not follow by time - '1971-01-02T23:', # extra : - '1971-01-02T23:04:', # extra : - '1971-01-02T23:0d', # 0d - '1971-01-02T23:04:15.', # Dot not follows by microsec - '1971-01-02+12', # time without T - '1971Z', # Z without T - '1971-01-02T23:04:15.123456Z+11', # Z follows by + - '1971-01-02T23:04:15.123456Z-11', # Z follows by - - '1971-01-02T23:04:15.123456+:30', # extra : - '1971-01-02T23:04:15.123456+30:', # extra : - '1971-01-02T23:04:15.01234567890123456789', # Too many microseconds digits - - # Python isoformat leave trailing zeros in microseconds - # Relax regular expression to accept it - #'1971-01-02T23:04:15.123430', # Microseconds ends in zero - #'1971-01-02T23:04:15.0', # Microseconds ends in zero - - # Timezone must be between +14 / -14 - '1971-01-02T23:04:15+15', - '1971-01-02T23:04:15-15', - '1971-01-02T23:04:15+14:01', - '1971-01-02T23:04:15-14:01', - - # Mix basic form with extended format - '197101-02T23:04:15.123456', - '19710102T23:04:15.123456', - '19710102T230415.123456+11:30', - '1971-01-02T230415.123456', - '1971-01-02T23:04:15.123456+1130', - - # Error captured by datetime class - '1971-00-02', # Less than 1 month - '1971-13-02', # Larger than 12 months - '1971-01-00', # Less than 1 day - '1971-11-32', # Larger than 30 days for Nov - '1971-12-32', # Larger than 31 days - '1971-01-02T24:01', # Larger than 23 hr - '1971-01-02T23:61', # Larger than 60 min - '1971-01-02T23:60:61', # Larger than 61 sec - ]: - dt = ParseISO8601(testStr) - if dt != None: - print('ParseISO8601 ({0}) should fail, but it did not'.format(testStr)) - assert(False) + # Valid entries + for testStr in [ + '1971', # 1971-01-01 + '1971-11', # 1971-11-01 + '1971-11-02', + '1971-11-02T23', + '1971-11-02T23Z', + '1971-11-02T23:04', + '1971-11-02T23:04Z', + '1971-11-02T23:04:15', + '1971-11-02T23:04:15Z', + '1971-11-02T23:04:15.1', + '1971-11-02T23:04:15.01', + '1971-11-02T23:04:15.023456', + '1971-11-02T23:04:15.103456Z', + '1971-11-02T23:04:15.123456+11', + '1971-11-02T23:04:15.123456-11', + '1971-11-02T23:04:15.123456+11:30', + '1971-11-02T23:04:15.123456-11:30', + '1971-11-02T23:04:15.123456+00:00', # Same as Z + '1971-11-02T23:04:15.123456-00:00', # Same as Z + '1971-01-02T23:04:15+14', + '1971-01-02T23:04:15+14:00', + '1971-01-02T23:04:15-14', + '1971-01-02T23:04:15-14:00', + + # Valid: Truncate microsec to 6 digits + '1971-01-02T23:04:15.123456891+11', + '1971-01-02T24', # 24 is valid. It should represent the 00:00 the + # next day + '1971-01-02T24:00', + '1971-01-02T24:00:00', + '1971-01-02T24:00:00.0', + + # Should NOT be valid but python isoformat adding trailing zeros + '1971-01-02T23:04:15.123430', # Microseconds ends in zero + '1971-01-02T23:04:15.0', # Microseconds ends in zero + + # Should be valid but python datetime don't support it + # '2005-12-31T23:59:60Z', # Leap second + # '-0001', # BC 1 + ]: + dt = ParseISO8601(testStr) + if dt is None: + print('Failed to parse ({0})'.format(testStr)) + assert (False) + + # Make sure we can translate back + isoformat = ISO8601Format(dt) + dt1 = ParseISO8601(isoformat) + if dt.tzinfo is None: + dt = dt.replace(tzinfo=dt1.tzinfo) + if dt1 != dt: + print('ParseISO8601 -> ISO8601Format -> ParseISO8601 failed (%s)' % + testStr) + assert (False) + + # Make sure we can parse python isoformat() + dt2 = ParseISO8601(dt.isoformat()) + if dt2 is None: + print('ParseISO8601("{0}".isoformat()) failed'.format(testStr)) + assert (False) + + print(testStr, '->', dt, isoformat) + + # Basic form + for testStr in [ + '197111', # 1971-11-01 + '19711102', + '19711102T23', + '19711102T23Z', + '19711102T2304', + '19711102T2304Z', + '19711102T230415', + '19711102T230415Z', + '19711102T230415.123456', + '19711102T230415.123456Z', + '19711102T230415.123456+11', + '19711102T230415.123456-11', + '19711102T230415.123456+1130', + '19711102T230415.123456-1130', + ]: + # Reject for now + dt = ParseISO8601(testStr) + if dt is not None: + print('ParseISO8601 ({0}) should fail, but it did not'.format(testStr)) + assert (False) + # print testStr, '->', dt + # assert(dt != None) + + # Invalid entries + for testStr in [ + # Xml schema reject year 0 + '0000', # 0 years are not allowed + '+0001', # Leading + is not allowed + '', # Empty datetime str + '09', # Years must be at least 4 digits + '1971-01-02T', # T not follow by time + '1971-01-02TZ', # T not follow by time + '1971-01-02T+10', # T not follow by time + '1971-01-02T-10', # T not follow by time + '1971-01-02T23:', # extra : + '1971-01-02T23:04:', # extra : + '1971-01-02T23:0d', # 0d + '1971-01-02T23:04:15.', # Dot not follows by microsec + '1971-01-02+12', # time without T + '1971Z', # Z without T + '1971-01-02T23:04:15.123456Z+11', # Z follows by + + '1971-01-02T23:04:15.123456Z-11', # Z follows by - + '1971-01-02T23:04:15.123456+:30', # extra : + '1971-01-02T23:04:15.123456+30:', # extra : + # Too many microseconds digits + '1971-01-02T23:04:15.01234567890123456789', + + # Python isoformat leave trailing zeros in microseconds + # Relax regular expression to accept it + # '1971-01-02T23:04:15.123430', # Microseconds ends in zero + # '1971-01-02T23:04:15.0', # Microseconds ends in zero + + # Timezone must be between +14 / -14 + '1971-01-02T23:04:15+15', + '1971-01-02T23:04:15-15', + '1971-01-02T23:04:15+14:01', + '1971-01-02T23:04:15-14:01', + + # Mix basic form with extended format + '197101-02T23:04:15.123456', + '19710102T23:04:15.123456', + '19710102T230415.123456+11:30', + '1971-01-02T230415.123456', + '1971-01-02T23:04:15.123456+1130', + + # Error captured by datetime class + '1971-00-02', # Less than 1 month + '1971-13-02', # Larger than 12 months + '1971-01-00', # Less than 1 day + '1971-11-32', # Larger than 30 days for Nov + '1971-12-32', # Larger than 31 days + '1971-01-02T24:01', # Larger than 23 hr + '1971-01-02T23:61', # Larger than 60 min + '1971-01-02T23:60:61', # Larger than 61 sec + ]: + dt = ParseISO8601(testStr) + if dt is not None: + print('ParseISO8601 ({0}) should fail, but it did not'.format(testStr)) + assert (False) diff --git a/pyVmomi/LICENSE.txt b/pyVmomi/LICENSE.txt new file mode 100644 index 000000000..73b19f034 --- /dev/null +++ b/pyVmomi/LICENSE.txt @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/pyVmomi/ManagedMethodExecutorHelper.py b/pyVmomi/ManagedMethodExecutorHelper.py deleted file mode 100644 index 2d768cd92..000000000 --- a/pyVmomi/ManagedMethodExecutorHelper.py +++ /dev/null @@ -1,118 +0,0 @@ -# VMware vSphere Python SDK -# Copyright (c) 2008-2016 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. - -""" -This module provides convinent fns related to ManagedMethodExecutor -""" -__author__ = "VMware, Inc." - -from pyVmomi import VmomiSupport, SoapAdapter, vmodl -from .SoapAdapter import SoapStubAdapterBase, SerializeToUnicode, Deserialize - -## ManagedMethodExecutor soap stub adapter -# -class MMESoapStubAdapter(SoapStubAdapterBase): - """ Managed method executor stub adapter """ - - ## 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 mme managed method executor - def __init__(self, mme): - stub = mme._stub - SoapStubAdapterBase.__init__(self, version=stub.version) - self.mme = mme - - ## Compute the version information for the specified namespace - # - # @param ns the namespace - def ComputeVersionInfo(self, version): - SoapStubAdapterBase.ComputeVersionInfo(self, version) - self.versionId = self.versionId[1:-1] - - ## Invoke a managed method, with _ExecuteSoap. Wohooo! - # - # @param self self - # @param mo the 'this' - # @param info method info - # @param args arguments - def InvokeMethod(self, mo, info, args): - # Serialize parameters to soap parameters - methodArgs = None - if info.params: - methodArgs = vmodl.Reflect.ManagedMethodExecutor.SoapArgument.Array() - for param, arg in zip(info.params, args): - if arg is not None: - # Serialize parameters to soap snippets - soapVal = SerializeToUnicode(val=arg, info=param, version=self.version) - - # Insert argument - soapArg = vmodl.Reflect.ManagedMethodExecutor.SoapArgument( - name=param.name, val=soapVal) - methodArgs.append(soapArg) - - moid = mo._GetMoId() - version = self.versionId - methodName = VmomiSupport.GetVmodlName(info.type) + "." + info.name - - # Execute method - result = self.mme.ExecuteSoap(moid=moid, - version=version, - method=methodName, - argument=methodArgs) - return self._DeserializeExecutorResult(result, info.result) - - ## Invoke a managed property accessor - # - # @param self self - # @param mo the 'this' - # @param info property info - def InvokeAccessor(self, mo, info): - moid = mo._GetMoId() - version = self.versionId - prop = info.name - - # Fetch property - result = self.mme.FetchSoap(moid=moid, version=version, prop=prop) - return self._DeserializeExecutorResult(result, info.type) - - ## Deserialize result from ExecuteSoap / FetchSoap - # - # @param self self - # @param result result from ExecuteSoap / FetchSoap - # @param resultType Expected result type - def _DeserializeExecutorResult(self, result, resultType): - obj = None - if result: - # Parse the return soap snippet. If fault, raise exception - if result.response: - # Deserialize back to result - obj = Deserialize(result.response, resultType, stub=self) - elif result.fault: - # Deserialize back to fault (or vmomi fault) - fault = Deserialize(result.fault.faultDetail, - object, - stub=self) - # Silent pylint - raise fault # pylint: disable-msg=E0702 - else: - # Unexpected: result should have either response or fault - msg = "Unexpected execute/fetchSoap error" - reason = "execute/fetchSoap did not return response or fault" - raise vmodl.Fault.SystemError(msg=msg, reason=reason) - return obj diff --git a/pyVmomi/Security.py b/pyVmomi/Security.py new file mode 100644 index 000000000..d99041360 --- /dev/null +++ b/pyVmomi/Security.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +""" +Copyright (c) 2022 VMware, Inc. + +This module is the python vmomi client security module. +""" +__author__ = "VMware, Inc" + +import hashlib + +_isSha1Enabled = True +_isSha256Enabled = True +_isSha512Enabled = True + + +def SetSha1Enabled(state): + global _isSha1Enabled + _isSha1Enabled = state + + +def SetSha256Enabled(state): + global _isSha256Enabled + _isSha256Enabled = state + + +def SetSha512Enabled(state): + global _isSha512Enabled + _isSha512Enabled = state + + +def VerifyCertThumbprint(derCert, thumbprint): + thumbprint_len = len(thumbprint) + if thumbprint_len == 40 and _isSha1Enabled: + sha = hashlib.sha1() + elif thumbprint_len == 64 and _isSha256Enabled: + sha = hashlib.sha256() + elif thumbprint_len == 128 and _isSha512Enabled: + sha = hashlib.sha512() + else: + raise ThumbprintMismatchException(thumbprint, + '') + sha.update(derCert) + shaDigest = sha.hexdigest().lower() + if shaDigest != thumbprint: + raise ThumbprintMismatchException(thumbprint, shaDigest) + + +class ThumbprintMismatchException(Exception): + def __init__(self, expected, actual): + Exception.__init__(self, "SHA thumbprint mismatch. Expected: `{0}`, " + "actual: `{1}`".format(expected, actual)) + + self.expected = expected + self.actual = actual diff --git a/pyVmomi/SoapAdapter.py b/pyVmomi/SoapAdapter.py index 529dbb15f..cd8683e30 100644 --- a/pyVmomi/SoapAdapter.py +++ b/pyVmomi/SoapAdapter.py @@ -1,51 +1,48 @@ -# VMware vSphere Python SDK -# Copyright (c) 2008-2016 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 - -import six -from six import reraise -from six.moves import http_client -from six.moves import StringIO -from six.moves import zip -from six import u -from six import iteritems +# ********************************************************** +# Copyright (c) 2005-2022 VMware, Inc. +# ********************************************************** -import sys +import base64 +import contextlib +import copy import os import platform +import re +import ssl import socket import subprocess +import sys +import threading 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.parsers.expat import ExpatError, ParserCreate +# For Visor, space is very limited. Import xml.sax pull in too many junk. +# Define our own xml escape instead # from xml.sax.saxutils import escape -from pyVmomi.VmomiSupport import * -from pyVmomi.StubAdapterAccessorImpl import StubAdapterAccessorMixin -import pyVmomi.Iso8601 -import base64 -from xml.parsers.expat import ExpatError -import copy -import contextlib +import six +from six import PY3 +from six.moves import StringIO, zip +from six.moves.urllib.parse import urlparse -try: - USERWORLD = os.uname()[0] == 'VMkernel' -except: - USERWORLD = False +from . import Iso8601 +from .StubAdapterAccessorImpl import StubAdapterAccessorMixin +from .VmomiSupport import ( + BASE_VERSION, F_LINK, F_OPTIONAL, XMLNS_VMODL_BASE, XMLNS_XSD, XMLNS_XSI, + Array, DataObject, Enum, GetCompatibleType, GetQualifiedWsdlName, + GetRequestContext, GetVersionNamespace, GetVmodlType, GetWsdlMethod, + GetWsdlName, GetWsdlNamespace, GetWsdlType, GuessWsdlMethod, GuessWsdlType, + IsChildVersion, ManagedMethod, UnknownManagedMethod, ManagedObject, + Object, PropertyPath, Type, binary, versionIdMap, versionMap) +from .Security import VerifyCertThumbprint +from . import _legacyThumbprintException +if _legacyThumbprintException: + from .Security import ThumbprintMismatchException # noqa: F401 + +if PY3: + from urllib.parse import splitport +else: + from urllib import splitport # Timeout value used for idle connections in client connection pool. # Default value is 900 seconds (15 minutes). @@ -59,22 +56,28 @@ XMLNS_SOAPENC = "http://schemas.xmlsoap.org/soap/encoding/" XMLNS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/" -XSI_TYPE = XMLNS_XSI + NS_SEP + u('type') +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_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' +NSMAP_DEF = ' '.join([ + 'xmlns:{}="{}"'.format(prefix, urn) + for urn, prefix in six.iteritems(SOAP_NSMAP) +]) + +SOAP_ENVELOPE_START = '<{} {}>\n'.format(SOAP_ENVELOPE_TAG, NSMAP_DEF) SOAP_ENVELOPE_END = "\n".format(SOAP_ENVELOPE_TAG) SOAP_HEADER_START = "<{0}>".format(SOAP_HEADER_TAG) SOAP_HEADER_END = "".format(SOAP_HEADER_TAG) @@ -85,60 +88,100 @@ 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_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 = "".format(WSSE_HEADER_TAG) -## MethodFault type +# MethodFault type MethodFault = GetVmodlType("vmodl.MethodFault") -## Localized MethodFault type +# Localized MethodFault type LocalizedMethodFault = GetVmodlType("vmodl.LocalizedMethodFault") # These info are included in the http user-agent header -PYTHON_VERSION = platform.python_version() -OS_NAME = platform.uname()[0] -OS_VERSION = platform.uname()[2] -OS_ARCH = platform.uname()[4] +# platform.uname() is doing fork() + exec(), so we prefer to avoid it on +# vmkernel. Causing MemSpike platform.uname() is retained to support +# Windows platform +PYTHON_VERSION = sys.version.split(' (')[0] +try: + OS_INFO = os.uname() + OS_NAME = OS_INFO[0] + OS_VERSION = OS_INFO[2] + OS_ARCH = OS_INFO[4] +except AttributeError: + PLATFORM_INFO = platform.uname() + OS_NAME = PLATFORM_INFO[0] + OS_VERSION = PLATFORM_INFO[2] + OS_ARCH = PLATFORM_INFO[4] SOAP_ADAPTER_ARGS = [ - "server_side", "cert_reqs", "ssl_version", "ca_certs", "do_handshake_on_connect", - "suppress_ragged_eofs", "ciphers"] - + "server_side", "cert_reqs", "ssl_version", "ca_certs", "do_handshake_on_connect", + "suppress_ragged_eofs", "ciphers"] -## 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): - escaped = xmlStr.replace("&", "&").replace(">", ">").replace("<", "<") + escaped = xmlStr.replace("&", + "&").replace(">", + ">").replace("<", "<") return escaped -## Get the start tag, end tag, and text handlers of a class + +# Clone SSLContext +# context.load_cert_chain(key_file, cert_file) is not thread safe +# Creating local context and assigning all paramters to context +# @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 context SSL Context describing the various SSL options. It is only +# supported in Python 2.7.9 or higher. +def _CloneSSLContext(context, certFile=None, certKeyFile=None): + sslContext = ssl.create_default_context() + sslContext.check_hostname = context.check_hostname + sslContext.verify_mode = context.verify_mode + if certFile and certKeyFile: + sslContext.load_cert_chain(certFile, certKeyFile) + return sslContext + + +# Validate IPv4 IP Pattern +# @param ip string ipv4 ip value to validate with IP pattern +# @return True if ip is IPv4 address, else False +def _CheckIPv4(ip): + ipv4_pattern = re.compile(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$') + return True if ipv4_pattern.match(ip) else False + + +# Validate IPv6 IP pattern +# @param ip string ipv6 ip value to validate with IP pattern +# @return True if ip is IPv6 address, else False +def _CheckIPv6(ip): + ipv6_pattern = re.compile(r'[:a-fA-F0-9]*:[:a-fA-F0-9]*:[:a-fA-F0-9.]*?$') + return True if ipv6_pattern.match(ip) else False + + +# Validate Hostname Pattern +# @param hostname string hostname value to validate with IP pattern +# @return True if hostname is hostname address, else False +def _CheckHostname(hostname): + hostname_pattern = re.compile(r'[a-z0-9A-Z-]+[\.a-z0-9A-Z-]+$') + return True if hostname_pattern.match(hostname) else False + + +# 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) + return (obj.StartElementHandler, obj.EndElementHandler, + obj.CharacterDataHandler, obj.StartNamespaceDeclHandler, + obj.EndNamespaceDeclHandler) -## Set the start tag, end tag, and text handlers of a parser + +# 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 + (obj.StartElementHandler, obj.EndElementHandler, obj.CharacterDataHandler, + obj.StartNamespaceDeclHandler, obj.EndNamespaceDeclHandler) = handlers + -## Serialize an object to bytes +# Serialize an object to bytes # # This function assumes CheckField(info, val) was already called # @param val the value to serialize @@ -146,53 +189,73 @@ def SetHandlers(obj, handlers): # @param version the version # @param nsMap a dict of xml ns -> prefix # @return the serialized object as bytes -# @param encoding Deprecated this is not used during serialization since we always -# use utf-8 to encode a request message. We didn't remove the -# parameter so it is still compatible with clients that are still using it. -def Serialize(val, info=None, version=None, nsMap=None, encoding=None): - return _SerializeToUnicode(val, info=info, version=version, nsMap=nsMap).encode(XML_ENCODING) - -## Serialize an object to unicode +# @param encoding Deprecated this is not used during serialization since we +# always use utf-8 to encode a request message. We didn't remove the +# parameter so it is still compatible with clients that are still +# using it. +def Serialize(val, + info=None, + version=None, + nsMap=None, + encoding=None, + hidepasswd=False): + return _SerializeToStr(val, + info=info, + version=version, + nsMap=nsMap, + hidepasswd=hidepasswd).encode(XML_ENCODING) + + +# Serialize an object to str # # 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 unicode -def SerializeToUnicode(val, info=None, version=None, nsMap=None): - return _SerializeToUnicode(val, info=info, version=version, nsMap=nsMap) +# @return the serialized object as str +def SerializeToStr(val, info=None, version=None, nsMap=None): + return _SerializeToStr(val, info=info, version=version, nsMap=nsMap) + -## Serialize an object to unicode +# Serialize an object to str # # 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 unicode -def _SerializeToUnicode(val, info=None, version=None, nsMap=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).Serialize(val, info) - return writer.getvalue() - -## Serialize fault detail +# @return the serialized object as str +def _SerializeToStr(val, + info=None, + version=None, + nsMap=None, + hidepasswd=False): + if hidepasswd and isinstance( + val, DataObject) and val._wsdlName == 'PasswordField': + val.value = '(notShown)' + 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="obj", type=object, version=version, flags=0) + + writer = StringIO() + SoapSerializer(writer, version, nsMap).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). @@ -203,328 +266,341 @@ def _SerializeToUnicode(val, info=None, version=None, nsMap=None): # @param version the version # @param nsMap a dict of xml ns -> prefix # @return the serialized object as a unicode 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 -# +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="obj", type=object, version=version, flags=0) + + writer = StringIO() + SoapSerializer(writer, version, nsMap, + encoding).SerializeFaultDetail(val, info) + return writer.getvalue() + + +# TODO: figure out the proper name for this method +def isDynamicType(objType): + """Checks whether the provided type is a dynamic type""" + return (objType is ManagedMethod or objType is PropertyPath + or objType is type) + + +# SOAP serializer class SoapSerializer: - """ SoapSerializer """ - ## Serializer constructor - # - # @param writer File writer - # @param version the version - # @param nsMap a dict of xml ns -> prefix - # @param encoding Deprecated this is not used during serialization since we always - # use utf-8 to encode a request message. We didn't remove the - # parameter so it is still compatible with clients that are still using it. - def __init__(self, writer, version, nsMap, encoding=None): - """ Constructor """ - self.writer = writer - self.version = version - self.nsMap = nsMap and nsMap or {} - 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, ' xsi:typ="{1}"'.format(val._wsdlName), 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 to unicode (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 + """ SoapSerializer """ + + # Serializer constructor + # + # @param writer File writer + # @param version the version + # @param nsMap a dict of xml ns -> prefix + # @param encoding Deprecated this is not used during serialization since we + # always use utf-8 to encode a request message. We didn't + # remove the parameter so it is still compatible with + # clients that are still using it. + def __init__(self, writer, version, nsMap, encoding=None): + """ Constructor """ + self.writer = writer + self.version = version + self.nsMap = nsMap and nsMap or {} + for ns, prefix in six.iteritems(self.nsMap): + if prefix == '': + self.defaultNS = ns + break + else: + self.defaultNS = '' + + # Additional attr for outermost tag + self.outermostAttrs = '' + + if version: + self.outermostAttrs += ' versionId="{0}"'.format(versionIdMap[version]) + + # 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 to str (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 - 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 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: - faultType = 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: - 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}'.format(info.name, attr, - val._moId, - 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 + 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: - 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(''.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}'.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}'.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}'.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) - if PY3: - # In python3 the bytes result after the base64 encoding has a - # leading 'b' which causes error when we use it to construct the - # soap message. Workaround the issue by converting the result to - # string. Since the result of base64 encoding contains only subset - # of ASCII chars, converting to string will not change the value. - result = str(result, XML_ENCODING) - self.writer.write('<{0}{1}>{2}'.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}'.format(info.name, attr, result)) - elif isinstance(val, six.integer_types) or isinstance(val, float): - if info.type is object: - nsattr, qName = self._QName(Type(val), currDefNS) - attr += '{0} {1}type="{2}"'.format(nsattr, self.xsiPrefix, qName) - result = six.text_type(val) - self.writer.write('<{0}{1}>{2}'.format(info.name, attr, result)) - elif isinstance(val, Enum): - if info.type is object: - nsattr, qName = self._QName(Type(val), currDefNS) - attr += '{0} {1}type="{2}"'.format(nsattr, self.xsiPrefix, qName) - self.writer.write('<{0}{1}>{2}'.format(info.name, attr, val)) - 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 isinstance(val, six.binary_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 = val.decode(XML_ENCODING) - result = XmlEscape(val) - self.writer.write(u'<{0}{1}>{2}'.format(info.name, attr, result)) - - ## 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) + 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}'.format( + info.name, attr, val._moId)) + elif isinstance(val, list): + if info.type is object: + itemType = val.Item + if isDynamicType(itemType): + 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(''.format(info.name)) else: - self._Serialize(propVal, prop, currDefNS) - else: - for prop in val._GetPropertyList(): - self._Serialize(getattr(val, prop.name), prop, currDefNS) - - self.writer.write(''.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 ParseData(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. - try: - if isinstance(data, six.binary_type) or isinstance(data, six.text_type): - parser.Parse(data) - else: - parser.ParseFile(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 + 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}'.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}'.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}'.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) + if PY3: + # In python3 the bytes result after the base64 encoding has a + # leading 'b' which causes error when we use it to construct + # the soap message. Workaround the issue by converting the + # result to string. Since the result of base64 encoding + # contains only subset of ASCII chars, converting to string + # will not change the value. + result = str(result, XML_ENCODING) + self.writer.write('<{0}{1}>{2}'.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}'.format( + info.name, attr, result)) + elif isinstance(val, six.integer_types) or isinstance(val, float): + if info.type is object: + nsattr, qName = self._QName(Type(val), currDefNS) + attr += '{0} {1}type="{2}"'.format( + nsattr, self.xsiPrefix, qName) + result = six.text_type(val) + self.writer.write('<{0}{1}>{2}'.format( + info.name, attr, result)) + elif isinstance(val, Enum): + if info.type is object: + nsattr, qName = self._QName(Type(val), currDefNS) + attr += '{0} {1}type="{2}"'.format( + nsattr, self.xsiPrefix, qName) + self.writer.write('<{0}{1}>{2}'.format( + info.name, attr, val)) + 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 isinstance(val, six.binary_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, nota bug. + val = val.decode('UTF-8') + result = XmlEscape(val) + self.writer.write(u'<{0}{1}>{2}'.format( + info.name, attr, result)) + + # 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 + 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(''.format(info.name)) + + +# 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) @@ -532,1172 +608,1205 @@ def ParseData(parser, data): # @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) - ParseData(parser, data) - return ds.GetResult() - - -## Expat deserializer namespace handler + parser = ParserCreate(namespace_separator=NS_SEP) + ds = SoapDeserializer(stub) + ds.Deserialize(parser, resultType) + # Many existing tests pass in str directly in python2 for testing purpose. + # But in python3 the input become unicode and the handling will fall into + # ParseFile case. + # Adding unicode input support to make it more test friendly. + if isinstance(data, six.binary_type) or isinstance(data, six.text_type): + parser.Parse(data) + else: + parser.ParseFile(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): - return self._GetNamespaceFromPrefix() - - ## 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._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) - if namespaces: - namespaces.append(uri) - else: - self.nsMap[prefix] = [uri] - - ## Handle namespace end - def EndNamespaceDeclHandler(self, prefix): - self.nsMap[prefix].pop() - - -## SOAP -> Python Deserializer + 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): + return self._GetNamespaceFromPrefix() + + # 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._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) + 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 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 + # 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: - 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) - try: - obj = GuessWsdlMethod(name) - except KeyError: - obj = UncallableManagedMethod(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: + 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) - setattr(top, name, obj) - else: - if not isinstance(obj, list) and issubclass(self.resultType, list): - self.result.append(obj) - else: - self.result = obj + objType = self.stack[-1]._GetPropertyInfo(name).type + + # LocalizedMethodFault 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 isDynamicType(objType): + 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: + if not self.stub or not self.stub.SupportServerGUIDs(): + self.serverGuid = attr[u'serverGuid'] + self.stack.append(self.LookupWsdlType(ns, 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 - ## Handle text data - def CharacterDataHandler(self, data): - self.data += data + 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 = self.LookupWsdlType(ns, 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) + try: + obj = GetWsdlMethod(ns, name) + except KeyError: + try: + obj = GuessWsdlMethod(name) + except KeyError: + obj = UnknownManagedMethod(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 UnicodeError: + obj = data + elif obj is datetime: + obj = 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 +# 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)) - ParseData(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) + # 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) + self.parser.buffer_text = True + if not nsMap: + nsMap = {} + self.nsMap = nsMap + SetHandlers(self.parser, GetHandlers(self)) + # Many existing tests pass in str directly in python2 for testing + # purpose. But in python3 the input become unicode and the handling + # will fall into ParseFile case. + # Adding unicode input support to make it more test friendly. + if isinstance(response, six.binary_type) or isinstance( + response, six.text_type): + self.parser.Parse(response) + else: + self.parser.ParseFile(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 + del 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 UnicodeError: + 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): - # Make sure we do NOT fallback to an older version - if hasattr(self, 'version') and IsChildVersion(self.version, version): - # print("WARNING: stub degrading: " + self.version + " -> " + version) - return - - 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() - if self.requestContext: - reqContexts.update(self.requestContext) - 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, six.string_types): - raise TypeError("Request context key ({0}) has non-string value ({1}) of {2}".format(key, val, type(val))) - ret = _SerializeToUnicode(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), - _SerializeToUnicode(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(_SerializeToUnicode(arg, param, self.version, nsMap)) - result.extend([''.format(info.wsdlName), SOAP_BODY_END, SOAP_ENVELOPE_END]) - return ''.join(result).encode(XML_ENCODING) - -## 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. -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 + 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): + # Make sure we do NOT fallback to an older version + if hasattr(self, 'version') and IsChildVersion(self.version, version): + # print("WARNING: stub degrading: " + + # self.version + " -> " + version) + return -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 ThumbprintMismatchException(thumbprint, sha1Digest) + versionNS = GetVersionNamespace(version) + if versionNS.find("/") >= 0: + self.versionId = '"urn:{0}"'.format(versionNS) + else: + self.versionId = '' + self.version = version - # Function used to wrap sockets with SSL - _SocketWrapper = ssl.wrap_socket -except ImportError: - SSL_THUMBPRINTS_SUPPORTED = False +# 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 = copy.deepcopy(GetRequestContext()) + if self.requestContext: + reqContexts.update(self.requestContext) + samlToken = getattr(self, 'samlToken', None) + + if reqContexts or samlToken: + result.append(SOAP_HEADER_START) + for key, val in six.iteritems(reqContexts): + # Note: Support req context of string type only + if not isinstance(val, six.string_types): + raise TypeError( + "Request context key ({0}) has non-string value" + " ({1}) of {2}".format(key, val, type(val))) + ret = _SerializeToStr( + val, Object(name=key, type=str, version=self.version), + self.version, nsMap) + result.append(ret) + if samlToken: + result.append('%s %s %s' % + (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), + _SerializeToStr( + 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(_SerializeToStr(arg, param, self.version, nsMap)) + result.extend([''.format(info.wsdlName), + SOAP_BODY_END, SOAP_ENVELOPE_END]) + return ''.join(result).encode(XML_ENCODING) + + +# 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. +class UnixSocketConnection(six.moves.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. + six.moves.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 + + +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, + six.moves.http_client.HTTPSConnection): + if not connection.sock: + connection.connect() + derCert = connection.sock.getpeercert(True) + VerifyCertThumbprint(derCert, thumbprint) - 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) +# Internal version of http connection +class _HTTPConnection(six.moves.http_client.HTTPConnection): + def __init__(self, *args, **kwargs): + # Only pass in the named arguments that HTTPConnection constructor + # understands + tmpKwargs = {} + httpConn = six.moves.http_client.HTTPConnection + for key in httpConn.__init__.__code__.co_varnames: + if key in kwargs and key != 'self': + tmpKwargs[key] = kwargs[key] + six.moves.http_client.HTTPConnection.__init__(self, *args, **tmpKwargs) -## Internal version of https connection +# Internal version of https connection # # Support ssl.wrap_socket params which are missing from httplib # HTTPSConnection (e.g. ca_certs) -# Note: Only works if the ssl params are passing in as kwargs -class _HTTPSConnection(http_client.HTTPSConnection): - def __init__(self, *args, **kwargs): - # Extract ssl.wrap_socket param unknown to httplib.HTTPSConnection, - # and push back the params in connect() - self._sslArgs = {} - tmpKwargs = kwargs.copy() - for key in SOAP_ADAPTER_ARGS: - if key in tmpKwargs: - self._sslArgs[key] = tmpKwargs.pop(key) - http_client.HTTPSConnection.__init__(self, *args, **tmpKwargs) - - ## 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): - if len(self._sslArgs) == 0: - # No override - http_client.HTTPSConnection.connect(self) - return - - # 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 self._tunnel_host: +# Note: Only works iff the ssl params are passing in as kwargs +class _HTTPSConnection(six.moves.http_client.HTTPSConnection): + def __init__(self, *args, **kwargs): + # Extract ssl.wrap_socket param unknown to httplib.HTTPSConnection, + # and push back the params in connect() + self._sslArgs = {} + tmpKwargs = kwargs.copy() + for key in SOAP_ADAPTER_ARGS: + if key in tmpKwargs: + self._sslArgs[key] = tmpKwargs.pop(key) + six.moves.http_client.HTTPSConnection.__init__(self, *args, + **tmpKwargs) + + # 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): + if len(self._sslArgs) == 0: + # No override + six.moves.http_client.HTTPSConnection.connect(self) + return + + sock = socket.create_connection((self.host, self.port), self.timeout, + self.source_address) + if self._tunnel_host: self.sock = sock self._tunnel() - self.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) - self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, - **self._sslArgs) - else: - # Unknown python version. Do nothing - http_client.HTTPSConnection.connect(self) - return - - # 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) - - -## Stand-in for the HTTPSConnection class that will connect to a SSL proxy, -## VCenter's /sdkTunnel endpoint. It will issue a CONNECT command to start -## an SSL tunnel. + self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, + **self._sslArgs) + + # 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) + + +# 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): - # 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: - raise http_client.HTTPException("{0} {1}".format(resp.status, resp.reason)) - retval = http_client.HTTPSConnection(path) - retval.sock = _SocketWrapper(tunnel.sock, - keyfile=key_file, certfile=cert_file) - return retval - - -## Stand-in for the HTTPSConnection class that will connect to a regular HTTP -## proxy. + # @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. + # For Python Version < 2.7.9. cert_reqs=CERT_OPTIONAL to verify + # server certificate + # + # @param path The destination URL path. + # @param key_file **** Deprecated. Please pass context instread **** + # sslContext.load_cert_chain(cert_file, key_file) + # The SSL key file to use when wrapping the socket. + # @param cert_file **** Deprecated. Please pass context instread **** + # sslContext.load_cert_chain(cert_file, key_file) + # The SSL certificate file to use when wrapping + # the socket. + # @param context SSL Context describing the various SSL options. It is + # only supported in Python 2.7.9 or higher. + # if context is used, load cert & key to the context with API + # context = ssl.create_default_context(cafile=ca_cert_file) + # context.load_cert_chain(key_file, cert_file) + # @param kwargs In case caller passed in extra parameters not handled by + # SSLTunnelConnection + def __call__(self, + path, + key_file=None, + cert_file=None, + context=None, + **kwargs): + _sslArgs = {} + tmpKwargs = kwargs.copy() + for key in SOAP_ADAPTER_ARGS: + if key in tmpKwargs: + _sslArgs[key] = tmpKwargs.pop(key) + if context: + tmpKwargs['context'] = _CloneSSLContext(context, cert_file, key_file) \ + if cert_file and key_file else context + else: + if key_file: + tmpKwargs['key_file'] = key_file + if cert_file: + tmpKwargs['cert_file'] = cert_file + tunnel = _HTTPConnection(path, **kwargs) + tunnel.request('CONNECT', self.proxyPath) + resp = tunnel.getresponse() + if resp.status != 200: + raise six.moves.http_client.HTTPException( + "{0} {1}".format(resp.status, resp.reason)) + host, port = splitport(path) + if 'port' not in tmpKwargs: + tmpKwargs['port'] = port + retval = six.moves.http_client.HTTPSConnection(host=host, **tmpKwargs) + if hasattr(retval, '_context'): + if host in ['localhost', '127.0.0.1', '::1']: + retval._context.check_hostname = False + if 'ca_certs' in kwargs and kwargs['ca_certs']: + retval._context.load_verify_locations(kwargs['ca_certs']) + # Call set_tunnel when proxyPath is a stand alone proxy host. + proxyHost = splitport(self.proxyPath)[0] + if (_CheckIPv4(proxyHost) or _CheckIPv6(proxyHost) + or _CheckHostname(proxyHost)): + retval.set_tunnel(self.proxyPath) + # Call wrap_socket if ProxyPath is VC inbuilt proxyPath + # ex: /sdkTunnel + else: + retval.sock = retval._context.wrap_socket(sock=tunnel.sock, + server_hostname=host) + else: + if host in ['localhost', '127.0.0.1', '::1']: + _sslArgs['cert_reqs'] = ssl.CERT_NONE + retval.sock = ssl.wrap_socket(tunnel.sock, + keyfile=key_file, + certfile=cert_file, + **_sslArgs) + return retval + + +# Stand-in for the HTTPSConnection class that will connect to a regular HTTP +# proxy. class HTTPProxyConnection(object): - # @param proxyPath The path to pass to the CONNECT command. - def __init__(self, proxyPath): - self.proxyPath = proxyPath - - # Connects to a HTTP 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 args Arguments are ignored - # @param kwargs Arguments for HTTPSConnection - def __call__(self, path, *args, **kwargs): - httpsConnArgs = {k: kwargs[k] for k in kwargs if k not in SOAP_ADAPTER_ARGS} - conn = http_client.HTTPSConnection(path, **httpsConnArgs) - conn.set_tunnel(self.proxyPath) - return conn + # @param proxyPath The path to pass to the CONNECT command. + def __init__(self, proxyPath): + self.proxyPath = proxyPath + + # Connects to an HTTP 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 args Arguments are ignored + # @param kwargs Arguments for HTTPSConnection + def __call__(self, path, **kwargs): + httpsConnArgs = {k: kwargs[k] for k in kwargs if k not in SOAP_ADAPTER_ARGS} + conn = six.moves.http_client.HTTPSConnection(path, **httpsConnArgs) + conn.set_tunnel(self.proxyPath) + return conn + 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! + 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 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 = b"".join(chunks) - return buf - -## SOAP stub adapter object + 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 is 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 = b"".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 - # @param sslContext SSL Context describing the various SSL options. It is only - # supported in Python 2.7.9 or higher. - 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, sslContext=None, requestContext=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(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 _HTTPSConnection - else: - port, self.scheme = port < 0 and (-port, http_client.HTTPConnection) \ - or (port, _HTTPSConnection) - 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 - - self.is_tunnel = False - if sslProxyPath: - self.scheme = SSLTunnelConnection(sslProxyPath) - self.is_tunnel = True - elif httpProxyHost: - self.scheme = HTTPProxyConnection(self.host) - self.is_tunnel = True - 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 - if sslContext: - self.schemeArgs['context'] = sslContext - self.samlToken = samlToken - self.requestContext = requestContext - 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): - if self.scheme == _HTTPSConnection 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 - # 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), - 'User-Agent': 'pyvmomi Python/{0} ({1}; {2}; {3})'. - format(PYTHON_VERSION, OS_NAME, OS_VERSION, OS_ARCH)} - 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: - 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 - # contexts (ie: methods) that we are blind to here. Refactor this. - - # The server might be sick, drop all of the cached connections. + # 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 **** Deprecated. Please load cert to context and pass + # context instread **** + # sslContext.load_cert_chain(key_file, cert_file) + # The path to the PEM-encoded SSL private key file. + # @param certFile **** Deprecated. Please pass context instread **** + # sslContext.load_cert_chain(key_file, cert_file) + # 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/SHA256/SHA512 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 **** Deprecated. Please load cert to context and pass + # context instread **** + # sslContext.load_verify_locations(cafile=ca_cert_file) + # 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 + # @param sslContext SSL Context describing the various SSL options. It is + # only supported in Python 2.7.9 or higher. + # if sslContext is used, load cert & key to the context with API + # sslContext = ssl.create_default_context(cafile=ca_cert_file) + # sslContext.load_cert_chain(key_file, cert_file) + # @param httpConnectionTimeout Timeout in secs for http requests. + 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, + sslContext=None, + requestContext=None, + httpConnectionTimeout=None, + customHeaders=None): + self._customHeaders = customHeaders + if ns: + assert (version is None) + version = versionMap[ns] + elif not version: + version = 'vim.version.version9' + 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(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 _HTTPConnection + or scheme == "https" and _HTTPSConnection) + else: + port, self.scheme = (port < 0 and (-port, _HTTPConnection) + or (port, _HTTPSConnection)) + if host.find(':') != -1 and host[0] != '[': # 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) not in (40, 64, 128): + raise Exception( + "Invalid SHA thumbprint -- {0}".format(thumbprint)) + else: + self.thumbprint = None + + self.is_tunnel = False + if sslProxyPath: + self.scheme = SSLTunnelConnection(sslProxyPath) + self.is_tunnel = True + elif httpProxyHost: + self.scheme = HTTPProxyConnection(self.host) + self.is_tunnel = True + + # Is httpProxyHost IPv6 + if httpProxyHost.find(':') != -1 and httpProxyHost[0] != '[': + httpProxyHost = '[' + httpProxyHost + ']' + + # 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 sslContext: + self.schemeArgs['context'] = sslContext + if certFile and certKeyFile: + self.schemeArgs['context'] = _CloneSSLContext( + sslContext, certFile, certKeyFile) + else: + 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 + if httpConnectionTimeout: + self.schemeArgs['timeout'] = httpConnectionTimeout + self.samlToken = samlToken + self.requestContext = requestContext + 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), + 'User-Agent': + 'pyvmomi Python/{0} ({1}; {2}; {3})'.format( + PYTHON_VERSION, OS_NAME, OS_VERSION, OS_ARCH) + } + if self._customHeaders: + headers.update(self._customHeaders) + 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, six.moves.http_client.HTTPException): + # The server is probably 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: - 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 - # 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: - self._CloseConnection(conn) - - ## 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: - self._CloseConnection(conn) - - ## Return a HTTP connection to the pool - def ReturnConnection(self, conn): - self.lock.acquire() - self._CloseIdleConnections() - # In case of ssl tunneling, only add the conn if the conn has not been closed - if len(self.pool) < self.poolSize and (not self.is_tunnel or conn.sock): - 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. - self._CloseConnection(conn) - - ## 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 - -## Need to override the depcopy method. Since, the stub is not deep copyable -# due to the thread lock and connection pool, deep copy of a managed object -# fails. Further different instances of a managed object still share the -# same soap stub. Hence, returning self here is fine. -def __deepcopy__(self, memo): - return self + raise + cookie = resp.getheader('Set-Cookie') + if cookie is None: + # try lower-case header for backwards compat. with old vSphere + 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) + obj = SoapResponseDeserializer(outerStub).Deserialize( + fd, info.result) + # TODO Add specific exception(s) + except: # noqa: E722 + conn.close() + # The server might be sick, drop all of the cached connections. + self.DropConnections() + raise + else: + resp.read() + self.ReturnConnection(conn) + if outerStub != self: + return (status, obj) + if status == 200: + return obj + else: + if not issubclass(obj.__class__, Exception): + _dict = obj.__dict__ if hasattr(obj, "__dict__") else "" + import inspect + inheritanceTree = inspect.getmro(obj.__class__) + formatMsg = ("Raising a non-exception object:\n" + " Attributes: {}\n Hierarchy: {}") + msg = formatMsg.format(_dict, str(inheritanceTree)) + raise Exception(msg) + raise obj # pylint: disable-msg=E0702 + else: + conn.close() + raise six.moves.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) + + _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() + # In case of ssl tunneling, only add the conn if the conn has + # not been closed + if len(self.pool) < self.poolSize and (not self.is_tunnel + or conn.sock): + self.pool.insert(0, (conn, time.time())) + self.lock.release() + else: + self.lock.release() + conn.close() + + # Need to override the depcopy method. Since, the stub is not deep copyable + # due to the thread lock and connection pool, deep copy of a managed object + # fails. Further different instances of a managed object still share the + # same soap stub. Hence, returning self here is fine. + def __deepcopy__(self, memo): + return self + HEADER_SECTION_END = '\r\n\r\n' -## Parse an HTTP response into its headers and body + +# 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 + headerEnd = httpResponse.find(HEADER_SECTION_END) + if headerEnd == -1: + return ('', '') + headerEnd += len(HEADER_SECTION_END) + headerText = httpResponse[:headerEnd] + bodyText = httpResponse[headerEnd:] + return (headerText, bodyText) 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.DropConnections = soapStub.DropConnections - 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. + '''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.5") + + 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.DropConnections = soapStub.DropConnections + 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 True: + try: + if self.state == self.STATE_UNAUTHENTICATED: + self._CallLoginMethod() + # Invoke the method + status, obj = self.soapStub.InvokeMethod(mo, info, args, self) + except (socket.error, six.moves.http_client.HTTPException, + ExpatError) as ex: + if self.retryDelay and retriesLeft: + time.sleep(self.retryDelay) + retriesLeft -= 1 + if retriesLeft <= 0: + raise ex + 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. + if self.retryDelay and retriesLeft: + time.sleep(self.retryDelay) + retriesLeft -= 1 + if retriesLeft <= 0: + raise obj + self._SetStateUnauthenticated() + else: + # It's an exception from the method that was called, + # send it up. + raise obj + + # 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 True: + try: + if self.state == self.STATE_UNAUTHENTICATED: + self._CallLoginMethod() + # Invoke the method + obj = StubAdapterBase.InvokeAccessor(self, mo, info) + except Exception as e: + if isinstance(e, self.SESSION_EXCEPTIONS): + # Our session might've timed out, + # change our state and retry. + self._SetStateUnauthenticated() + elif isinstance(e, ExpatError) or \ + isinstance(e, socket.error) or \ + isinstance(e, six.moves.http_client.HTTPException): + if self.retryDelay and retriesLeft > 0: + time.sleep(self.retryDelay) + else: + retriesLeft = 0 + if retriesLeft > 0: + retriesLeft -= 1 + continue + raise e 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: + # 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._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() - retriesLeft -= 1 - continue - 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() + 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() diff --git a/pyVmomi/StubAdapterAccessorImpl.py b/pyVmomi/StubAdapterAccessorImpl.py index 7a9bb5ab8..ae5946c93 100644 --- a/pyVmomi/StubAdapterAccessorImpl.py +++ b/pyVmomi/StubAdapterAccessorImpl.py @@ -1,38 +1,30 @@ -# VMware vSphere Python SDK -# Copyright (c) 2008-2022 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 pyVmomi.VmomiSupport import GetVmodlType, ManagedObject, Object +# ********************************************************** +# Copyright (c) 2011-2022 VMware, Inc. +# ********************************************************** + +from .VmomiSupport import GetVmodlType, ManagedObject, Object class StubAdapterAccessorMixin: - ## Retrieve a managed property - # - # @param self self - # @param mo managed object - # @param info property info - def InvokeAccessor(self, mo, info): - prop = info.name - param = Object(name="prop", type=str, version=self.version, flags=0) - info = Object(name=info.name, - type=ManagedObject, - wsdlName="Fetch", - version=info.version, - params=(param, ), - isTask=False, - resultFlags=info.flags, - result=info.type, - methodResult=info.type) - return self.InvokeMethod(mo, info, (prop, )) + # Retrieve a managed property + # + # @param self self + # @param mo managed object + # @param info property info + def InvokeAccessor(self, mo, info): + prop = info.name + param = Object(name="prop", type=str, version=self.version, flags=0) + info = Object(name=info.name, + type=ManagedObject, + wsdlName="Fetch", + version=info.version, + params=(param, ), + isTask=False, + resultFlags=info.flags, + result=info.type, + methodResult=info.type) + return self.InvokeMethod(mo, info, (prop, )) + + def SupportServerGUIDs(self): + return False diff --git a/pyVmomi/Version.py b/pyVmomi/Version.py index e08488f5c..68e056e78 100644 --- a/pyVmomi/Version.py +++ b/pyVmomi/Version.py @@ -1,36 +1,23 @@ -# VMware vSphere Python SDK -# Copyright (c) 2008-2015 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 pyVmomi.VmomiSupport import nsMap, versionMap, versionIdMap, serviceNsMap, parentMap +# ********************************************************** +# Copyright (c) 2008-2022 VMware, Inc. +# ********************************************************** +from .VmomiSupport import CreateVersion, parentMap -## Add an API version -def AddVersion(version, ns, versionId='', isLegacy=0, serviceNs=''): - if not ns: - ns = serviceNs - if version not in parentMap: - nsMap[version] = ns - if len(versionId) > 0: - versionMap[ns + '/' + versionId] = version - if isLegacy or ns == "": - versionMap[ns] = version - versionIdMap[version] = versionId - if not serviceNs: - serviceNs = ns - serviceNsMap[version] = serviceNs - parentMap[version] = set() -## Check if a version is a child of another +# Version-specific initialization +def Init(): + pass + + +# Add an API version +def AddVersion(version, + ns, + versionId='', + isLegacy=0, + serviceNs=''): + CreateVersion(version, ns, versionId, isLegacy, serviceNs) + + +# Check if a version is a child of another def IsChildVersion(child, parent): - return child == parent or parent in parentMap[child] + return child == parent or parent in parentMap[child] diff --git a/pyVmomi/VmomiJSONEncoder.py b/pyVmomi/VmomiJSONEncoder.py new file mode 100644 index 000000000..cad9bd722 --- /dev/null +++ b/pyVmomi/VmomiJSONEncoder.py @@ -0,0 +1,129 @@ +""" +Copyright (c) 2022 VMware, Inc. +""" + +import base64 +import json + +from datetime import datetime +from six import PY3 + +from . import Iso8601 +from .VmomiSupport import ManagedObject, DataObject, ManagedMethod, \ + UnknownManagedMethod, GetWsdlType, XMLNS_VMODL_BASE, binary + + +class VmomiJSONEncoder(json.JSONEncoder): + """ + Custom JSON encoder to encode vSphere objects. + When a ManagedObject is encoded, it gains three properties: + _vimid is the _moId (ex: 'vm-42') + _vimref is the moRef (ex: 'vim.VirtualMachine:vm-42') + _vimtype is the class name (ex: 'vim.VirtualMachine') + When a DataObject is encoded, it gains one property: + _vimtype is the class name (ex: 'vim.VirtualMachineQuestionInfo') + If the dynamicProperty and dynamicType are empty, they are optionally + omitted from the results of DataObjects and ManagedObjects + @example "Explode only the object passed in" + data = json.dumps(vm, cls=VmomiJSONEncoder) + @example "Explode specific objects" + data = json.dumps(vm, cls=VmomiJSONEncoder, + explode=[vm, vm.network[0]]) + @example "Explode all virtual machines in a list and their snapshots" + data = json.dumps([vm1, vm2], cls=VmomiJSONEncoder, + explode=[templateOf('VirtualMachine'), + templateOf('VirtualMachineSnapshot')]) + """ + def __init__(self, *args, **kwargs): + """ + Consumer must specify what types to explode into full content + and what types to leave as a ManagedObjectReference. If the + root object of the encoding is a ManagedObject, it will be + added to the explode list automatically. + A ManagedObject is only exploded once, the first time it is + encountered. All subsequent times it will be a moRef. + @param explode (list) A list of objects and/or types to + expand in the conversion process. Directly add any + objects to the list, or add the type of any object + using type(templateOf('VirtualMachine')) for example. + @param strip_dynamic (bool) Every object normally contains + a dynamicProperty list and a dynamicType field even if + the contents are [] and None respectively. These fields + clutter the output making it more difficult for a person + to read and bloat the size of the result unnecessarily. + This option removes them if they are the empty values above. + The default is False. + """ + self._done = set() + self._first = True + self._explode = kwargs.pop('explode', None) + self._strip_dynamic = kwargs.pop('strip_dynamic', False) + super(VmomiJSONEncoder, self).__init__(*args, **kwargs) + + def _remove_empty_dynamic_if(self, result): + if self._strip_dynamic: + if 'dynamicProperty' in result and len(result['dynamicProperty']) == 0: + result.pop('dynamicProperty') + if 'dynamicType' in result and not result['dynamicType']: + result.pop('dynamicType') + return result + + def default(self, obj): # pylint: disable=method-hidden + if self._first: + self._first = False + if not self._explode: + self._explode = [] + if isinstance(obj, ManagedObject): + self._explode.append(obj) + if isinstance(obj, ManagedObject): + if self.explode(obj): + result = {} + result['_vimid'] = obj._moId + result['_vimref'] = '{}:{}'.format(obj.__class__.__name__, + obj._moId) + result['_vimtype'] = obj.__class__.__name__ + for prop in obj._GetPropertyList(): + result[prop.name] = getattr(obj, prop.name) + return self._remove_empty_dynamic_if(result) + return str(obj).strip("'") # see VmomiSupport.FormatObject + if isinstance(obj, DataObject): + result = {k:v for k,v in obj.__dict__.items()} + result['_vimtype'] = obj.__class__.__name__ + return self._remove_empty_dynamic_if(result) + if isinstance(obj, binary): + result = base64.b64encode(obj) + if PY3: # see VmomiSupport.FormatObject + result = str(result, 'utf-8') + return result + if isinstance(obj, datetime): + return Iso8601.ISO8601Format(obj) + if isinstance(obj, UnknownManagedMethod): + return obj.name + if isinstance(obj, ManagedMethod): + return str(obj) # see VmomiSupport.FormatObject + if isinstance(obj, type): + return obj.__name__ + super(VmomiJSONEncoder, self).default(obj) + + def explode(self, obj): + """ Determine if the object should be exploded. """ + if obj in self._done: + return False + result = False + for item in self._explode: + if hasattr(item, '_moId'): + # If it has a _moId it is an instance + if obj._moId == item._moId: + result = True + else: + # If it does not have a _moId it is a template + if obj.__class__.__name__ == item.__name__: + result = True + if result: + self._done.add(obj) + return result + + +def templateOf(typestr): + """ Returns a class template. """ + return GetWsdlType(XMLNS_VMODL_BASE, typestr) diff --git a/pyVmomi/VmomiSupport.py b/pyVmomi/VmomiSupport.py index 98c650ae3..61265e7af 100644 --- a/pyVmomi/VmomiSupport.py +++ b/pyVmomi/VmomiSupport.py @@ -1,57 +1,40 @@ -# VMware vSphere Python SDK -# Copyright (c) 2008-2018 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. - -## VMOMI support code -from __future__ import absolute_import -from __future__ import with_statement # 2.5 only - -from six import iteritems -from six import iterkeys -from six import itervalues -from six import text_type -from six import string_types -from six import binary_type -from six import PY3 -from datetime import datetime -from pyVmomi import Iso8601 +# Copyright (c) 2005-2022 VMware, Inc. +"""VMOMI support code + +This module contains support for VMOMI abstractions such as VMODL types, +Versions, etc. +""" + import base64 -import json import threading -if PY3: - from functools import cmp_to_key +from datetime import datetime +from sys import version_info + +import six +from six import PY3, binary_type, string_types +from six.moves import map, range +from . import Iso8601 +from . import _allowGetSet +from . import _allowCapitalizedNames +from . import _binaryIsBytearray + +if version_info[0] >= 3: + from functools import cmp_to_key NoneType = type(None) -try: - from pyVmomi.pyVmomiSettings import allowGetSet - _allowGetSet = allowGetSet -except: - _allowGetSet = True try: - from pyVmomi.pyVmomiSettings import allowCapitalizedNames - _allowCapitalizedNames = allowCapitalizedNames -except: - _allowCapitalizedNames = True + from six.moves import intern +except ImportError: + # The old version of six package (< 1.8.0) doesn't have intern mapping so + # we need to tolerate the import error here. + def intern(s): + return s -(F_LINK, - F_LINKABLE, - F_OPTIONAL, - F_SECRET) = [ 1< len(info.params): - s = "s"*(len(info.params)!=1) - raise TypeError("%s() takes at most %d argument%s (%d given)" % - (Capitalize(info.name), len(info.params), s, len(posargs))) - args = list(posargs) + [None] * (len(info.params) - len(posargs)) - if len(kwargs) > 0: - paramNames = [param.name for param in info.params] - for (k, v) in list(kwargs.items()): + _wsdlName = "ManagedObject" + _propInfo = {} + _propList = [] + _methodInfo = {} + _version = BASE_VERSION + + # Constructor + # + # @param[in] self self + # @param[in] moId The ID of this managed object + # @param[in] stub The stub adapter, if this is a client stub object + def __init__(self, moId, stub=None, serverGuid=None): + try: + moId = intern(moId) + except: + pass + object.__setattr__(self, "_moId", moId) + object.__setattr__(self, "_stub", stub) + object.__setattr__(self, "_serverGuid", serverGuid) + + # Invoke a managed method + # + # @param info method info + # @param self self + # @param ... arguments + def _InvokeMethod(info, self, *posargs, **kwargs): + if len(posargs) > len(info.params): + s = "s" * (len(info.params) != 1) + raise TypeError( + "%s() takes at most %d argument%s (%d given)" % + (Capitalize(info.name), len(info.params), s, len(posargs))) + args = list(posargs) + [None] * (len(info.params) - len(posargs)) + if len(kwargs) > 0: + paramNames = [param.name for param in info.params] + for (k, v) in list(kwargs.items()): + try: + idx = paramNames.index(k) + except ValueError: + raise TypeError( + "%s() got an unexpected keyword argument '%s'" % + (Capitalize(info.name), k)) + if idx < len(posargs): + raise TypeError( + "%s() got multiple values for keyword argument '%s'" % + (Capitalize(info.name), k)) + args[idx] = v + list(map(CheckField, info.params, args)) + return self._stub.InvokeMethod(self, info, args) + + _InvokeMethod = staticmethod(_InvokeMethod) + + # Invoke a managed property accessor + # + # @param info property info + # @param self self + def _InvokeAccessor(info, self): + return self._stub.InvokeAccessor(self, info) + + _InvokeAccessor = staticmethod(_InvokeAccessor) + + # Get the ID of a managed object + def _GetMoId(self): + return self._moId + + # Get the serverGuid of a managed object + def _GetServerGuid(self): + return self._serverGuid + + # Get the stub of a managed object + def _GetStub(self): + return self._stub + + # Get a list of all properties of this type and base types + # + # @param cls The managed object type + @classmethod + def _GetPropertyList(cls, includeBaseClassProps=True): + if not includeBaseClassProps: + return cls._propList + prop = {} + result = [] + while cls != ManagedObject: + # Iterate through props, add info for prop not found in + # derived class + result = [ + info for info in cls._propList + if prop.setdefault(info.name, cls) == cls + ] + result + cls = cls.__bases__[0] + return result + + # Get a list of all methods of this type and base types + # + # @param cls The managed object type + @classmethod + def _GetMethodList(cls): + meth = {} + result = [] + while cls != ManagedObject: + # Iterate through methods, add info for method not found in + # derived class + result = [ + info for info in list(cls._methodInfo.values()) + if meth.setdefault(info.name, cls) == cls + ] + result + cls = cls.__bases__[0] + return result + + # Lookup a method for a given managed object type + # + # @param type the type + # @param name the name of the property + @classmethod + def _GetMethodInfo(type, name): + while hasattr(type, "_methodInfo"): try: - idx = paramNames.index(k) - except ValueError: - raise TypeError("%s() got an unexpected keyword argument '%s'" % - (Capitalize(info.name), k)) - if idx < len(posargs): - raise TypeError("%s() got multiple values for keyword argument '%s'" % - (Capitalize(info.name), k)) - args[idx] = v - list(map(CheckField, info.params, args)) - return self._stub.InvokeMethod(self, info, args) - _InvokeMethod = staticmethod(_InvokeMethod) - - ## Invoke a managed property accessor - # - # @param info property info - # @param self self - def _InvokeAccessor(info, self): - return self._stub.InvokeAccessor(self, info) - _InvokeAccessor = staticmethod(_InvokeAccessor) - - ## Get the ID of a managed object - def _GetMoId(self): - return self._moId - - ## Get the serverGuid of a managed object - def _GetServerGuid(self): - return self._serverGuid - - ## Get the stub of a managed object - def _GetStub(self): - return self._stub - - ## Get a list of all properties of this type and base types - # - # @param cls The managed object type - def _GetPropertyList(cls, includeBaseClassProps=True): - if not includeBaseClassProps: - return cls._propList - prop = {} - result = [] - while cls != ManagedObject: - # Iterate through props, add info for prop not found in derived class - result = [info for info in cls._propList - if prop.setdefault(info.name, cls) == cls] + result - cls = cls.__bases__[0] - return result - _GetPropertyList = classmethod(_GetPropertyList) - - ## Get a list of all methods of this type and base types - # - # @param cls The managed object type - def _GetMethodList(cls): - meth = {} - result = [] - while cls != ManagedObject: - # Iterate through methods, add info for method not found in derived class - result = [info for info in list(cls._methodInfo.values()) - if meth.setdefault(info.name, cls) == cls] + result - cls = cls.__bases__[0] - return result - _GetMethodList = classmethod(_GetMethodList) - - ## Lookup a method for a given managed object type - # - # @param type the type - # @param name the name of the property - def _GetMethodInfo(type, name): - while hasattr(type, "_methodInfo"): - try: - return type._methodInfo[name] - except KeyError: - type = type.__bases__[0] - raise AttributeError(name) - _GetMethodInfo = classmethod(_GetMethodInfo) - - def __setattr__(self,*args): - if self._stub is not None: - raise Exception("Managed object attributes are read-only") - else: - object.__setattr__(self,*args) - __delattr__ = __setattr__ - - if _allowGetSet == True: - def __getattr__(self, name): - if name.startswith("Get"): - return lambda : getattr(self, name[3].lower() + name[4:]) - elif name.startswith("Set"): - return lambda val: setattr(self, name[3].lower() + name[4:], val) - raise AttributeError(name) - - ## The equality test of ManagedObject is for client side only and might - # not be appropriate for server side objects. The server side object has - # to override this function if it has a different equality logic - def __eq__(self, other): - if other is None: - return False - else: - return self._moId == other._moId and \ - self.__class__ == other.__class__ and \ - self._serverGuid == other._serverGuid - - def __ne__(self, other): - return not(self == other) - - def __hash__(self): - return str(self).__hash__() - - __str__ = __repr__ = FormatObject - _GetPropertyInfo = classmethod(GetPropertyInfo) - -## VMOMI Data Object class -class DataObject(Base): - _wsdlName = "DataObject" - _propInfo = {} - _propList = [] - _version = BASE_VERSION - - ## Constructor - # - # @param info property info - # @param ... keyword arguments indicate properties - def __init__(self, **kwargs): - for info in self._GetPropertyList(): - if issubclass(info.type, list): - SetAttr(self, info.name, info.type()) - elif info.flags & F_OPTIONAL: - SetAttr(self, info.name, None) - elif info.type is bool: - SetAttr(self, info.name, False) - elif issubclass(info.type, Enum): - SetAttr(self, info.name, None) - elif issubclass(info.type, str): - SetAttr(self, info.name, "") - elif info.type is long or \ - issubclass(info.type, int) or \ - issubclass(info.type, float): - # Take care of byte, short, int, long, float and double - SetAttr(self, info.name, info.type(0)) - else: - SetAttr(self, info.name, None) - for (k, v) in list(kwargs.items()): - setattr(self, k, v) - - ## Get a list of all properties of this type and base types - # - # @param cls the data object type - def _GetPropertyList(cls, includeBaseClassProps=True): - if not includeBaseClassProps: - return cls._propList - prop = {} - result = [] - while cls != DataObject: - # Iterate through props, add info for prop not found in derived class - result = [info for info in cls._propList - if prop.setdefault(info.name, cls) == cls] + result - cls = cls.__bases__[0] - return result - _GetPropertyList = classmethod(_GetPropertyList) - - def __setattr__(self, name, val): - CheckField(self._GetPropertyInfo(name), val) - SetAttr(self, name, val) - - if _allowGetSet == True: - def __getattr__(self, name): - if name.startswith("Get"): - return lambda : getattr(self, name[3].lower() + name[4:]) - elif name.startswith("Set"): - return lambda val: setattr(self, name[3].lower() + name[4:], val) - raise AttributeError(name) - - _GetPropertyInfo = classmethod(GetPropertyInfo) - __str__ = __repr__ = FormatObject - -## Base class for enum types -class Enum(str): pass - -## Base class for array types + return type._methodInfo[name] + except KeyError: + type = type.__bases__[0] + raise AttributeError(name) + + def __setattr__(self, *args): + if self._stub is not None: + raise Exception("Managed object attributes are read-only") + else: + object.__setattr__(self, *args) + + __delattr__ = __setattr__ + + if _allowGetSet: + + def __getattr__(self, name): + if name.startswith("Get"): + return lambda: getattr(self, name[3].lower() + name[4:]) + elif name.startswith("Set"): + return lambda val: setattr(self, name[3].lower() + name[4:], + val) + raise AttributeError(name) + + # The equality test of ManagedObject is for client side only and might + # not be appropriate for server side objects. The server side object has + # to override this function if it has a different equality logic + def __eq__(self, other): + if other is None: + return False + else: + return (self._moId == other._moId + and self.__class__ == other.__class__ + and self._serverGuid == other._serverGuid) + + def __ne__(self, other): + return not (self == other) + + def __hash__(self): + return str(self).__hash__() + + __str__ = __repr__ = FormatObject + + # shared with DataObject + _GetPropertyInfo = classmethod(GetPropertyInfo) + + +# VMOMI Data Object class +class DataObject(object): + _wsdlName = "DataObject" + _propInfo = {} + _propList = [] + _version = BASE_VERSION + + # Constructor + # + # @param info property info + # @param ... keyword arguments indicate properties + def __init__(self, **kwargs): + for property in self._GetPropertyList(): + ptype = property.type + if issubclass(ptype, list): + value = ptype() + elif property.flags & F_OPTIONAL: + value = None + elif ptype is bool: + value = False + elif issubclass(ptype, Enum): + value = None + elif issubclass(ptype, str): + value = "" + elif (ptype is long or issubclass(ptype, int) + or issubclass(ptype, float)): + # Take care of byte, short, int, long, float and double + value = ptype(0) + else: + value = None + object.__setattr__(self, property.name, value) + for (k, v) in list(kwargs.items()): + setattr(self, k, v) + + # Get a list of all properties of this type and base types + # + # @param cls the data object type + @classmethod + def _GetPropertyList(cls, includeBaseClassProps=True): + if not includeBaseClassProps: + return cls._propList + prop = {} + result = [] + while cls != DataObject: + # Iterate through props, add info for prop not found in + # derived class + result = [ + info for info in cls._propList + if prop.setdefault(info.name, cls) == cls + ] + result + cls = cls.__bases__[0] + return result + + def __setattr__(self, name, val): + CheckField(self._GetPropertyInfo(name), val) + object.__setattr__(self, name, val) + + __str__ = __repr__ = FormatObject + + # shared with ManagedObject + _GetPropertyInfo = classmethod(GetPropertyInfo) + + +# Base class for enum types +class Enum(str): + pass + + +# Base class for array types class Array(list): - __str__ = __repr__ = FormatObject + __str__ = __repr__ = FormatObject + -## Class for curried function objects +# Class for curried function objects # # Instances of this class are curried python function objects. # If g = Curry(f, a1,...,an), then g(b1,...,bm) = f(a1,...,an, b1,...,bm) class Curry(object): - ## Constructor - # - # @param self self - # @param f the function object - # @param args arguments to fix - def __init__(self, f, *args): - self.f = f - self.args = args - - def __call__(self, *args, **kwargs): - args = self.args + args - return self.f(*args, **kwargs) - - def __get__(self, obj, type): - if obj: - # curried methods will receive 'self' *after* any fixed arguments - return lambda *args, **kwargs: \ - self.f(*(self.args + (obj,) + args), **kwargs) - return self - -## Class for managed object methods + # Constructor + # + # @param self self + # @param f the function object + # @param args arguments to fix + def __init__(self, f, *args): + self.f = f + self.args = args + + def __call__(self, *args, **kwargs): + args = self.args + args + return self.f(*args, **kwargs) + + def __get__(self, obj, type): + if obj: + # curried methods will receive 'self' *after* any fixed arguments + return lambda *args, **kwargs: \ + self.f(*(self.args + (obj,) + args), **kwargs) + return self + + +# Class for managed object methods class ManagedMethod(Curry): - ## Constructor - # - # @param self self - # @param info method info - def __init__(self, info): - Curry.__init__(self, ManagedObject._InvokeMethod, info) - self.info = info - -# Method used to represent any unknown wsdl method returned by server response. + # Constructor + # + # @param self self + # @param info method info + def __init__(self, info): + Curry.__init__(self, ManagedObject._InvokeMethod, info) + self.info = info + + +# __setattr__ implementation for MethodFault object. +# +# Exceptions in python3 have __traceback__ attribute that can be +# (and is - by unittest) modified by exception users. So allow its +# modification although it is not a VMOMI property. Note that +# __traceback__ cannot be deleted. +def _MethodFaultSetAttr(self, name, value): + if name == '__traceback__': + # Call MethodFault.__bases__[1].__setattr__ + Exception.__setattr__(self, name, value) + else: + # Call MethodFault.__bases__[0].__setattr__ + DataObject.__setattr__(self, name, value) + + +# Generic method representing unknown API methods returned by the server. # Server may return unknown method name due to server defects or newer version. -class UncallableManagedMethod(ManagedMethod): - def __init__(self, name): - self.name = name +class UnknownManagedMethod(ManagedMethod): + def __init__(self, name): + self.name = name + + def __call__(self, *args, **kwargs): + raise Exception("Managed method {} is not available".format(self.name)) - def __call__(self, *args, **kwargs): - raise Exception("Managed method {} is not available".format(self.name)) -## Create the vmodl.MethodFault type +# Create the vmodl.MethodFault type # # This type must be generated dynamically because it inherits from # vmodl.DynamicData, which is created dynamically by the emitted code. # # @return the new type def CreateAndLoadMethodFaultType(): - with _lazyLock: - props = [["msg", "string", BASE_VERSION, F_OPTIONAL], - ["faultCause", "vmodl.MethodFault", "vmodl.version.version1", F_OPTIONAL], - ["faultMessage", "vmodl.LocalizableMessage[]", "vmodl.version.version1", F_OPTIONAL]] - propInfo = {} - propList = [ LazyObject(name=p[0], typeName=p[1], version=p[2], flags=p[3]) - for p in props ] - dic = {"_wsdlName" : "MethodFault", "_propInfo" : propInfo, - "_propList" : propList, "_version" : BASE_VERSION} - for info in propList: - propInfo[info.name] = info - name = "vmodl.MethodFault" - CreateDataType("vmodl.MethodFault", "MethodFault", "vmodl.DynamicData", BASE_VERSION, props) - return _AddType(type(Exception)(name, - (GetWsdlType(XMLNS_VMODL_BASE, "DynamicData"), Exception), - dic)) - -# If the name of nested class of vmodl type is same as one of the nested classes -# of its parent, then we have to replace it. Else it won't be possible to intercept -# it with LazyType class + with _lazyLock: + props = [ + ["msg", "string", BASE_VERSION, F_OPTIONAL], + [ + "faultCause", "vmodl.MethodFault", + "vmodl.version.version1", F_OPTIONAL + ], + [ + "faultMessage", "vmodl.LocalizableMessage[]", + "vmodl.version.version1", F_OPTIONAL + ] + ] + propInfo = {} + propList = [ + LazyObject(name=p[0], typeName=p[1], version=p[2], flags=p[3]) + for p in props + ] + dic = { + "_wsdlName": "MethodFault", + "_propInfo": propInfo, + "_propList": propList, + "_version": BASE_VERSION, + "__setattr__": _MethodFaultSetAttr, + } + for info in propList: + propInfo[info.name] = info + name = "vmodl.MethodFault" + CreateDataType("vmodl.MethodFault", "MethodFault", "vmodl.DynamicData", + BASE_VERSION, props) + return _AddType( + type(Exception)( + name, + (GetWsdlType(XMLNS_VMODL_BASE, "DynamicData"), Exception), + dic)) + + +# If the name of nested class of vmodl type is same as one of the nested +# classes of its parent, then we have to replace it. Else it won't be possible +# to intercept it with LazyType class # @param vmodl type # @param parent of the vmodl type # @return vmodl type def _CheckNestedClasses(typ, parent): - with _lazyLock: - vmodlName = typ.__name__ - nestedClasses = _dependencyMap.get(vmodlName, []) - for nestedClass in nestedClasses: - if hasattr(parent, nestedClass): - setattr(typ, nestedClass, GetVmodlType(vmodlName + "." + nestedClass)) - return typ - -## Create and Load a data object type at once + with _lazyLock: + vmodlName = typ.__name__ + nestedClasses = _dependencyMap.get(vmodlName, []) + for nestedClass in nestedClasses: + if hasattr(parent, nestedClass): + setattr(typ, nestedClass, + GetVmodlType(vmodlName + "." + nestedClass)) + return typ + + +# Create and Load a data object type at once # # @param vmodlName the VMODL name of the type # @param wsdlName the WSDL name of the type @@ -773,10 +704,11 @@ def _CheckNestedClasses(typ, parent): # @param props properties of the type # @return vmodl type def CreateAndLoadDataType(vmodlName, wsdlName, parent, version, props): - CreateDataType(vmodlName, wsdlName, parent, version, props) - return LoadDataType(vmodlName, wsdlName, parent, version, props) + CreateDataType(vmodlName, wsdlName, parent, version, props) + return LoadDataType(vmodlName, wsdlName, parent, version, props) + -## Create a data object type +# Create a data object type # # @param vmodlName the VMODL name of the type # @param wsdlName the WSDL name of the type @@ -784,19 +716,20 @@ def CreateAndLoadDataType(vmodlName, wsdlName, parent, version, props): # @param version the version of the type # @param props properties of the type def CreateDataType(vmodlName, wsdlName, parent, version, props): - with _lazyLock: - dic = [vmodlName, wsdlName, parent, version, props] - names = vmodlName.split(".") - if _allowCapitalizedNames: - vmodlName = ".".join(name[0].lower() + name[1:] for name in names) - _AddToDependencyMap(names) - typeNs = GetWsdlNamespace(version) - - _dataDefMap[vmodlName] = dic - _wsdlDefMap[(typeNs, wsdlName)] = dic - _wsdlTypeMapNSs.add(typeNs) - -## Load a data object type + with _lazyLock: + dic = [vmodlName, wsdlName, parent, version, props] + names = vmodlName.split(".") + if _allowCapitalizedNames: + vmodlName = ".".join(name[0].lower() + name[1:] for name in names) + _AddToDependencyMap(names) + typeNs = GetInternedWsdlNamespace(version) + + _dataDefMap[vmodlName] = dic + _wsdlDefMap[(typeNs, wsdlName)] = dic + _wsdlTypeMapNSs.add(typeNs) + + +# Load a data object type # This function also loads the parent of the type if it's not loaded yet # # @param vmodlName the VMODL name of the type @@ -806,56 +739,80 @@ def CreateDataType(vmodlName, wsdlName, parent, version, props): # @param props properties of the type # @return the new data object type def LoadDataType(vmodlName, wsdlName, parent, version, props): - with _lazyLock: - # Empty lists are saved as None in globals maps as it is much more memory - # efficient. PythonStubEmitter files emit empty lists as None. - if props is None: - props = [] - propInfo = {} - propList = [] - for p in props: - # DataObject Property does not contain the privilege for emitted types. - # However, DynamicTypeConstructor from DynamicTypeManagerHelper.py creates - # DataTypes with properties containing privilege id. - name, typeName, propVersion, flags = p[:4] - if flags & F_LINK: - if typeName.endswith("[]"): - linkType = "Link[]" + with _lazyLock: + # Empty lists are saved as None in globals maps as it is much more + # memory efficient. PythonStubEmitter files emit empty lists as None. + if props is None: + props = [] + propList = [] + for p in props: + # DataObject Property does not contain the privilege for emitted + # types. However, DynamicTypeConstructor from + # DynamicTypeManagerHelper.py creates DataTypes with properties + # containing privilege id. + name, typeName, propVersion, flags = p[:4] + if flags & F_LINK: + if typeName.endswith("[]"): + linkType = "Link[]" + else: + linkType = "Link" + obj = LazyObject(name=name, + typeName=linkType, + version=propVersion, + flags=flags, + expectedType=typeName) else: - linkType = "Link" - obj = LazyObject(name=name, typeName=linkType, version=propVersion, - flags=flags, expectedType=typeName) - else: - obj = LazyObject(name=name, typeName=typeName, version=propVersion, - flags=flags) - propList.append(obj) - dic = {"_wsdlName" : wsdlName, "_propInfo" : propInfo, - "_propList" : propList, "_version" : version} - for info in propList: - propInfo[info.name] = info - name = vmodlName - parent = GetVmodlType(parent) - result = _AddType(LazyType(name, (parent,), dic)) - - # MethodFault and RuntimeFault are builtin types, but MethodFault extends - # DynamicData, which is (erroneously?) an emitted type, so we can't create - # MethodFault and RuntimeFault until we have loaded DynamicData - if wsdlName == "DynamicData": - CreateAndLoadMethodFaultType() - CreateAndLoadDataType("vmodl.RuntimeFault", "RuntimeFault", - "vmodl.MethodFault", BASE_VERSION, []) - # Strictly speaking LocalizedMethodFault is not a data object type - # (it can't be used in VMODL) But it can be treated as a data object for - # (de)serialization purpose - CreateAndLoadDataType("vmodl.LocalizedMethodFault", "LocalizedMethodFault", - "vmodl.MethodFault", BASE_VERSION, - [("fault", "vmodl.MethodFault", BASE_VERSION, 0), - ("localizedMessage", "string", BASE_VERSION, F_OPTIONAL), - ]) - - return _CheckNestedClasses(result, parent) - -## Create and Load a managed object type at once + obj = LazyObject(name=name, + typeName=typeName, + version=propVersion, + flags=flags) + propList.append(obj) + propInfo = {} + for info in propList: + propInfo[info.name] = info + dic = { + "_wsdlName": wsdlName, + "_propInfo": propInfo, + "_propList": propList, + "_version": version + } + + if _allowGetSet: + for property in propList: + name = property.name + suffix = name[0].upper() + name[1:] + tGlobals = {} + exec( + 'def Get%s(self): return getattr(self, "%s")' % + (suffix, name) + '\n' + + 'def Set%s(self, v): setattr(self, "%s", v)' % + (suffix, name), tGlobals, dic) + + vmodlParent = GetVmodlType(parent) + result = _AddType(LazyType(vmodlName, (vmodlParent, ), dic)) + + # MethodFault and RuntimeFault are builtin types, but MethodFault + # extends DynamicData, which is (erroneously?) an emitted type, so we + # can't create MethodFault and RuntimeFault until we have loaded + # DynamicData + if wsdlName == "DynamicData": + CreateAndLoadMethodFaultType() + CreateAndLoadDataType("vmodl.RuntimeFault", "RuntimeFault", + "vmodl.MethodFault", BASE_VERSION, []) + # Strictly speaking LocalizedMethodFault is not a data object type + # (it can't be used in VMODL) But it can be treated as a data + # object for (de)serialization purpose + CreateAndLoadDataType( + "vmodl.LocalizedMethodFault", "LocalizedMethodFault", + "vmodl.MethodFault", BASE_VERSION, [ + ("fault", "vmodl.MethodFault", BASE_VERSION, 0), + ("localizedMessage", "string", BASE_VERSION, F_OPTIONAL), + ]) + + return _CheckNestedClasses(result, vmodlParent) + + +# Create and Load a managed object type at once # # @param vmodlName the VMODL name of the type # @param wsdlName the WSDL name of the type @@ -864,11 +821,14 @@ def LoadDataType(vmodlName, wsdlName, parent, version, props): # @param props properties of the type # @param methods methods of the type # @return vmodl type -def CreateAndLoadManagedType(vmodlName, wsdlName, parent, version, props, methods): - CreateManagedType(vmodlName, wsdlName, parent, version, props, methods) - return LoadManagedType(vmodlName, wsdlName, parent, version, props, methods) +def CreateAndLoadManagedType(vmodlName, wsdlName, parent, version, props, + methods): + CreateManagedType(vmodlName, wsdlName, parent, version, props, methods) + return LoadManagedType(vmodlName, wsdlName, parent, version, props, + methods) + -## Create a managed object type +# Create a managed object type # # @param vmodlName the VMODL name of the type # @param wsdlName the WSDL name of the type @@ -877,24 +837,30 @@ def CreateAndLoadManagedType(vmodlName, wsdlName, parent, version, props, method # @param props properties of the type # @param methods methods of the type def CreateManagedType(vmodlName, wsdlName, parent, version, props, methods): - with _lazyLock: - dic = [vmodlName, wsdlName, parent, version, props, methods] - names = vmodlName.split(".") - if _allowCapitalizedNames: - vmodlName = ".".join(name[0].lower() + name[1:] for name in names) - - _AddToDependencyMap(names) - typeNs = GetWsdlNamespace(version) - - if methods: - for meth in methods: - _SetWsdlMethod(typeNs, meth[1], dic) - - _managedDefMap[vmodlName] = dic - _wsdlDefMap[(typeNs, wsdlName)] = dic - _wsdlTypeMapNSs.add(typeNs) - -## Load a managed object type + with _lazyLock: + dic = [vmodlName, wsdlName, parent, version, props, methods] + names = vmodlName.split(".") + if _allowCapitalizedNames: + vmodlName = ".".join(name[0].lower() + name[1:] for name in names) + + _AddToDependencyMap(names) + typeNs = GetInternedWsdlNamespace(version) + + if methods: + for meth in methods: + # We need to use method namespace to avoid duplicate wsdl + # method issue when to find wsdl method from managed type + # namespace for vSAN vmodls that can have different version + # namespaces for managed methods and managed object type. + methNs = GetInternedWsdlNamespace(meth[2]) + _SetWsdlMethod(methNs, meth[1], dic) + + _managedDefMap[vmodlName] = dic + _wsdlDefMap[(typeNs, wsdlName)] = dic + _wsdlTypeMapNSs.add(typeNs) + + +# Load a managed object type # This function also loads the parent of the type if it's not loaded yet # # @param vmodlName the VMODL name of the type @@ -905,55 +871,77 @@ def CreateManagedType(vmodlName, wsdlName, parent, version, props, methods): # @param methods methods of the type # @return the new managed object type def LoadManagedType(vmodlName, wsdlName, parent, version, props, methods): - with _lazyLock: - # Empty lists are saved as None in globals maps as it is much more memory - # efficient. PythonStubEmitter files emit empty lists as None. - if props is None: - props = [] - if methods is None: - methods = [] - parent = GetVmodlType(parent) - propInfo = {} - methodInfo = {} - propList = [LazyObject(name=p[0], typeName=p[1], version=p[2], flags=p[3], - privId=p[4]) for p in props] - dic = {"_wsdlName" : wsdlName, "_propInfo" : propInfo, - "_propList" : propList, - "_methodInfo" : methodInfo, "_version" : version} - for info in propList: - propInfo[info.name] = info - getter = Curry(ManagedObject._InvokeAccessor, info) - dic[info.name] = property(getter) - for (mVmodl, mWsdl, mVersion, mParams, mResult, mPrivilege, mFaults) in methods: - if mFaults is None: - mFaults = [] - mName = Capitalize(mVmodl) - isTask = False - if mName.endswith("_Task"): - mName = mName[:-5] - isTask = True - params = tuple([LazyObject(name=p[0], typeName=p[1], version=p[2], flags=p[3], - privId=p[4]) for p in mParams]) - info = LazyObject(name=mName, typeName=vmodlName, wsdlName=mWsdl, - version=mVersion, params=params, isTask=isTask, - resultFlags=mResult[0], resultName=mResult[1], - methodResultName=mResult[2], privId=mPrivilege, faults=mFaults) - methodInfo[mName] = info - mm = ManagedMethod(info) - ns = GetWsdlNamespace(info.version) - method = _SetWsdlMethod(ns, info.wsdlName, mm) - if method != mm: - raise RuntimeError( - "Duplicate wsdl method %s %s (new class %s vs existing %s)" % \ - (ns, info.wsdlName, mm.info.type, method.info.type)) - dic[mWsdl] = mm - dic[mName] = mm - name = vmodlName - result = _AddType(LazyType(name, (parent,) , dic)) - - return _CheckNestedClasses(result, parent) - -## Create an enum type + with _lazyLock: + # Empty lists are saved as None in globals maps as it is much more + # memory efficient. PythonStubEmitter files emit empty lists as None. + if props is None: + props = [] + if methods is None: + methods = [] + parent = GetVmodlType(parent) + propInfo = {} + methodInfo = {} + propList = [ + LazyObject(name=p[0], + typeName=p[1], + version=p[2], + flags=p[3], + privId=p[4]) for p in props + ] + dic = { + "_wsdlName": wsdlName, + "_propInfo": propInfo, + "_propList": propList, + "_methodInfo": methodInfo, + "_version": version + } + for info in propList: + propInfo[info.name] = info + getter = Curry(ManagedObject._InvokeAccessor, info) + dic[info.name] = property(getter) + for (mVmodl, mWsdl, mVersion, mParams, mResult, mPrivilege, + mFaults) in methods: + if mFaults is None: + mFaults = [] + mName = Capitalize(mVmodl) + isTask = False + if mWsdl.endswith("_Task"): + isTask = True + params = tuple([ + LazyObject(name=p[0], + typeName=p[1], + version=p[2], + flags=p[3], + privId=p[4]) for p in mParams + ]) + info = LazyObject(name=mName, + typeName=vmodlName, + wsdlName=mWsdl, + version=mVersion, + params=params, + isTask=isTask, + resultFlags=mResult[0], + resultName=mResult[1], + methodResultName=mResult[2], + privId=mPrivilege, + faults=mFaults) + methodInfo[mName] = info + mm = ManagedMethod(info) + ns = GetInternedWsdlNamespace(info.version) + method = _SetWsdlMethod(ns, info.wsdlName, mm) + if method != mm: + raise RuntimeError("Duplicate wsdl method {0} {1} " + "(new class {2} vs existing {3})".format( + ns, info.wsdlName, mm.info.type, method.info.type)) + dic[mWsdl] = mm + dic[mName] = mm + name = vmodlName + result = _AddType(LazyType(name, (parent, ), dic)) + + return _CheckNestedClasses(result, parent) + + +# Create an enum type # # @param vmodlName the VMODL name of the type # @param wsdlName the WSDL name of the type @@ -961,30 +949,32 @@ def LoadManagedType(vmodlName, wsdlName, parent, version, props, methods): # @param values enum values # @return vmodl type def CreateAndLoadEnumType(vmodlName, wsdlName, version, values): - CreateEnumType(vmodlName, wsdlName, version, values) - return LoadEnumType(vmodlName, wsdlName, version, values) + CreateEnumType(vmodlName, wsdlName, version, values) + return LoadEnumType(vmodlName, wsdlName, version, values) + -## Create an enum type +# Create an enum type # # @param vmodlName the VMODL name of the type # @param wsdlName the WSDL name of the type # @param version the version of the type # @param values enum values def CreateEnumType(vmodlName, wsdlName, version, values): - with _lazyLock: - dic = [vmodlName, wsdlName, version, values] - names = vmodlName.split(".") - if _allowCapitalizedNames: - vmodlName = ".".join(name[0].lower() + name[1:] for name in names) + with _lazyLock: + dic = [vmodlName, wsdlName, version, values] + names = vmodlName.split(".") + if _allowCapitalizedNames: + vmodlName = ".".join(name[0].lower() + name[1:] for name in names) - _AddToDependencyMap(names) - typeNs = GetWsdlNamespace(version) + _AddToDependencyMap(names) + typeNs = GetInternedWsdlNamespace(version) - _enumDefMap[vmodlName] = dic - _wsdlDefMap[(typeNs, wsdlName)] = dic - _wsdlTypeMapNSs.add(typeNs) + _enumDefMap[vmodlName] = dic + _wsdlDefMap[(typeNs, wsdlName)] = dic + _wsdlTypeMapNSs.add(typeNs) -## Load an enum type + +# Load an enum type # # @param vmodlName the VMODL name of the type # @param wsdlName the WSDL name of the type @@ -992,560 +982,651 @@ def CreateEnumType(vmodlName, wsdlName, version, values): # @param values enum values # @return the new enum type def LoadEnumType(vmodlName, wsdlName, version, values): - with _lazyLock: - name = vmodlName - # Enum type cannot have nested classes. So, creating normal type - # instead of LazyType - result = type(name, (Enum,), - {"_wsdlName" : wsdlName, "_version" : version}) - result.values = map(result, values) - for value in result.values: - setattr(result, value, value) - return _AddType(result) - -## Create an array type + with _lazyLock: + name = vmodlName + # Enum type cannot have nested classes. So, creating normal type + # instead of LazyType + result = type(name, (Enum, ), { + "_wsdlName": wsdlName, + "_version": version + }) + result.values = list(map(result, values)) + for value in result.values: + setattr(result, value, value) + return _AddType(result) + + +# Create an array type # # @param itemType the item type # @return the new array type def CreateArrayType(itemType): - return type("%s[]" % itemType.__name__, (Array,), {'Item' : itemType}) + return type("{0}[]".format(itemType.__name__), (Array, ), {'Item': itemType}) + -## Add a new type to the type maps, create array constructors -# Note: Must be holding the _lazyLock, or in main init path +# Add a new type to the type maps, create array constructors +# Note: Must be holding the _lazyLock, or in main init path # # @param type the type object # @return type def _AddType(type): - """ Note: Must be holding the _lazyLock, or in main init path """ - type.Array = CreateArrayType(type) + """ Note: Must be holding the _lazyLock, or in main init path """ + type.Array = CreateArrayType(type) + + typeNS = GetInternedWsdlNamespace(type._version) + newType = _SetWsdlType(typeNS, type._wsdlName, type) + if newType != type: + raise RuntimeError("Duplicate wsdl type %s (already in typemap)" % + (type._wsdlName)) + + return type - typeNS = GetWsdlNamespace(type._version) - newType = _SetWsdlType(typeNS, type._wsdlName, type) - if newType != type: - raise RuntimeError("Duplicate wsdl type %s (already in typemap)" % (type._wsdlName)) - return type +def _areBasicTypes(info, valType): + """Checks whether the provided type matches with the expected and both are + basic types. + + @param info the expected type + @param valType the type of the object to check + """ + return (info is type and valType is type(Exception) + or issubclass(info, int) and issubclass(valType, int) + or issubclass(info, long) and (issubclass(valType, int) + or issubclass(valType, long)) + or issubclass(info, float) and issubclass(valType, float) + or issubclass(info, string_types) and issubclass(valType, string_types) + or issubclass(info, binary_type) and issubclass(valType, binary_type)) + -## Check that a value matches a given type, and annotate if neccesary +# Check that a value matches a given type, and annotate if neccesary # # @param info object containing of expected type # @param val object to check # @throw TypeError if the value does not match the type def CheckField(info, val): - with _lazyLock: - valType = Type(val) - if val is None or (isinstance(val, list) and len(val) == 0): - # If type of the property is an Any. We should allow this to have - # unset items - if not (info.flags & F_OPTIONAL) and info.type is not object: - raise TypeError('Required field "%s" not provided (not @optional)' % info.name) - return - elif info.type is object: - try: - GetQualifiedWsdlName(valType) + with _lazyLock: + valType = Type(val) + if val is None or (isinstance(val, list) and len(val) == 0): + # If type of the property is an Any. We should allow this to have + # unset items + if not (info.flags & F_OPTIONAL) and info.type is not object: + raise TypeError( + 'Required field "%s" not provided (not @optional)' % + info.name) return - except KeyError: - raise TypeError('Unknown type for %s' % info.type.__name__) - elif isinstance(val, info.type): - return - elif issubclass(info.type, list): - # Checking the values of VMOMI array types is surprisingly complicated.... - if isinstance(val, Array): - # 1. We've got a PyVmomi Array object, which is effectively a typed list; - # verify that the type of the Array is a subclass of the expected type. - if issubclass(valType.Item, info.type.Item): - return - elif info.flags & F_LINK: - # Allow objects of expected type to be assigned to links - if issubclass(valType, GetVmodlType(info.expectedType)): - return - elif val: - # 2. We've got a non-empty Python list object, which is untyped; - # walk the list and make sure that each element is a subclass - # of the expected type. - - # Masking out F_OPTIONAL part of flags since we are checking for - # each element of the list - flags = info.flags & (F_LINKABLE | F_LINK) - if flags & F_LINK: - if info.expectedType.endswith('[]'): - expectedType = info.expectedType[:-2] - else: - expectedType = info.expectedType - itemInfo = Object(type=info.type.Item, name=info.name, flags=flags, - expectedType=expectedType) - else: - itemInfo = Object(type=info.type.Item, name=info.name, flags=flags) - for it in val: - CheckField(itemInfo, it) - return - else: - # 3. We've got None or an empty Python list object; - # no checking required, since the result will be an empty array. + elif info.type is object: + try: + GetQualifiedWsdlName(valType) + return + except KeyError: + raise TypeError('Unknown type for {0}'.format(info.type.__name__)) + elif isinstance(val, info.type): return - elif info.type is type and valType is type(Exception) \ - or issubclass(info.type, int) and issubclass(valType, int) \ - or issubclass(info.type, long) and (issubclass(valType, int) or \ - issubclass(valType, long)) \ - or issubclass(info.type, float) and issubclass(valType, float) \ - or issubclass(info.type, string_types) and issubclass(valType, string_types): - return - elif issubclass(info.type, Link): - # Allow object of expected type to be assigned to link - if issubclass(valType, GetVmodlType(info.expectedType)): + elif issubclass(info.type, list): + # Checking the values of VMOMI array types is surprisingly + # complicated.... + if isinstance(val, Array): + # 1. We've got a PyVmomi Array object, which is effectively a + # typed list; + # verify that the type of the Array is a subclass of the + # expected type. + if issubclass(valType.Item, info.type.Item): + return + elif info.flags & F_LINK: + # Allow objects of expected type to be assigned to links + if issubclass(valType, GetVmodlType(info.expectedType)): + return + elif issubclass(info.type.Item, Enum) and issubclass( + valType.Item, string_types): + # Allow String array object to be assigned to enum array + return + elif val: + # 2. We've got a non-empty Python list object, which is + # untyped; + # walk the list and make sure that each element is a subclass + # of the expected type. + + # Masking out F_OPTIONAL part of flags since we are checking + # for each element of the list + flags = info.flags & (F_LINKABLE | F_LINK) + if flags & F_LINK: + if info.expectedType.endswith('[]'): + expectedType = info.expectedType[:-2] + else: + expectedType = info.expectedType + itemInfo = Object(type=info.type.Item, + name=info.name, + flags=flags, + expectedType=expectedType) + else: + itemInfo = Object(type=info.type.Item, + name=info.name, + flags=flags) + for it in val: + CheckField(itemInfo, it) + return + else: + # 3. We've got None or an empty Python list object; + # no checking required since the result will be an empty array. + return + elif _areBasicTypes(info.type, valType): return - raise TypeError('For "%s" expected type %s, but got %s' - % (info.name, info.type.__name__, valType.__name__)) + elif issubclass(info.type, Link): + # Allow object of expected type to be assigned to link + if issubclass(valType, GetVmodlType(info.expectedType)): + return + raise TypeError('For "%s" expected type %s, but got %s' % + (info.name, info.type.__name__, valType.__name__)) + -## Finalize a created type +# Finalize a created type # # @param type a created type def FinalizeType(type): - if issubclass(type, DataObject): - for info in type._propList: - info.type = GetVmodlType(info.type) - elif issubclass(type, ManagedObject): - for info in list(type._propInfo.values()): - info.type = GetVmodlType(info.type) - for info in list(type._methodInfo.values()): - info.result = GetVmodlType(info.result) - info.methodResult = GetVmodlType(info.methodResult) - info.type = GetVmodlType(info.type) - for param in info.params: - param.type = GetVmodlType(param.type) - -## Get the type of an object, for both new and old-style classes + if issubclass(type, DataObject): + for info in type._propList: + info.type = GetVmodlType(info.type) + elif issubclass(type, ManagedObject): + for info in list(type._propInfo.values()): + info.type = GetVmodlType(info.type) + for info in list(type._methodInfo.values()): + info.result = GetVmodlType(info.result) + info.methodResult = GetVmodlType(info.methodResult) + info.type = GetVmodlType(info.type) + for param in info.params: + param.type = GetVmodlType(param.type) + + +# Get the type of an object, for both new and old-style classes def Type(obj): - try: - return obj.__class__ - except AttributeError: - return type(obj) + try: + return obj.__class__ + except AttributeError: + return type(obj) -## Set a WSDL type with wsdl namespace and wsdl name -# Internal to VmomiSupport -# -# Note: Must be holding the _lazyLock, or in main init path + +# Set a WSDL type with wsdl namespace and wsdl name Internal to VmomiSupport +# Note: Must be holding the _lazyLock, or in main init path def _SetWsdlType(ns, wsdlName, typ): - """ - Set a WSDL type with wsdl namespace and wsdl name. - Returns added type / existing type if (ns, wsdlName) already in the map + """ + Set a WSDL type with wsdl namespace and wsdl name. + Returns added type / existing type if (ns, wsdlName) already in the map + + Note: Must be holding the _lazyLock, or in main init path + """ + return _wsdlTypeMap.setdefault((ns, wsdlName), typ) - Note: Must be holding the _lazyLock, or in main init path - """ - return _wsdlTypeMap.setdefault((ns, wsdlName), typ) -## Lookup a WSDL type from wsdl namespace and wsdl name +# Lookup a WSDL type from wsdl namespace and wsdl name +# # @param ns XML namespace # @param name wsdl name # @return type if found else throws KeyError def GetWsdlType(ns, name): - if ns is None or name is None: - raise KeyError("{0} {1}".format(ns, name)) - - with _lazyLock: - # Check if the type is loaded in the map - typ = _wsdlTypeMap.get( (ns, name) ) - if typ: - return typ - # It is an array type, get the actual type and return the array - elif name.startswith("ArrayOf"): - try: - return GetWsdlType(ns, name[7:]).Array - except KeyError: - raise KeyError("{0} {1}".format(ns, name)) - else: - # Type is not loaded yet, load it - typ = _LoadVmodlType(_wsdlDefMap[(ns, name)][0]) - if typ: - return typ + if ns is None or name is None: + raise KeyError("{0} {1}".format(ns, name)) - raise KeyError("{0} {1}".format(ns, name)) + with _lazyLock: + # Check if the type is loaded in the map + typ = _wsdlTypeMap.get((ns, name)) + if typ: + return typ + # It is an array type, get the actual type and return the array + elif name.startswith("ArrayOf"): + try: + return GetWsdlType(ns, name[7:]).Array + except KeyError: + raise KeyError("{0} {1}".format(ns, name)) + else: + # Type is not loaded yet, load it + typ = _LoadVmodlType(_wsdlDefMap[(ns, name)][0]) + if typ: + return typ + raise KeyError("{0} {1}".format(ns, name)) -class UnknownWsdlTypeError(KeyError): - # NOTE (hartsock): KeyError is extended here since most logic will be - # looking for the KeyError type. I do want to distinguish malformed WSDL - # errors as a separate classification of error for easier bug reports. - pass -## Guess the type from wsdlname with no ns -# WARNING! This should not be used in general, as there is no guarantee for -# the correctness of the guessing type +# Guess the type from wsdlname with no ns +# WARNING! This should not be used in general, as there is no guarantee for +# the correctness of the guessing type +# # @param name wsdl name # @return type if found in any one of the name spaces else throws KeyError def GuessWsdlType(name): - with _lazyLock: - # Some types may exist in multiple namespaces, and returning - # the wrong one will cause a deserialization error. - # Since in python3 the order of entries in set is not deterministic, - # we will try to get the type from vim25 namespace first. - try: - return GetWsdlType(XMLNS_VMODL_BASE, name) - except KeyError: - pass - - for ns in _wsdlTypeMapNSs: - try: - return GetWsdlType(ns, name) - except KeyError: - pass - raise UnknownWsdlTypeError(name) - -## Return a map that contains all the wsdl types + with _lazyLock: + # Some types may exist in multiple namespaces, and returning + # the wrong one will cause a deserialization error (1656837). + # Since in python3 the order of entries in set is not deterministic, + # we will try to get the type from vim25 namespace first. + try: + return GetWsdlType(XMLNS_VMODL_BASE, name) + except KeyError: + pass + + for ns in _wsdlTypeMapNSs: + try: + return GetWsdlType(ns, name) + except KeyError: + pass + raise KeyError(name) + + +# Return a map that contains all the wsdl types # This function is rarely used -# By calling GetWsdlType on all wsdl names, we will -# make sure that the types are loaded before returning -# the iterator +# By calling GetWsdlType on all wsdl names, we will make sure that the +# types are loaded before returning the iterator +# # @return iterator to the wsdl type map def GetWsdlTypes(): - with _lazyLock: - for ns, name in _wsdlDefMap: - GetWsdlType(ns, name) - return itervalues(_wsdlTypeMap) + with _lazyLock: + for ns, name in _wsdlDefMap: + GetWsdlType(ns, name) + return six.itervalues(_wsdlTypeMap) -## Get the qualified XML schema name (ns, name) of a type + +# Get the qualified XML schema name (ns, name) of a type def GetQualifiedWsdlName(type): - with _lazyLock: - wsdlNSAndName = _wsdlNameMap.get(type) - if wsdlNSAndName: - return wsdlNSAndName - else: - if issubclass(type, list): - ns = GetWsdlNamespace(type.Item._version) - return (ns, "ArrayOf" + Capitalize(type.Item._wsdlName)) - else: - ns = GetWsdlNamespace(type._version) - return (ns, type._wsdlName) - -## Get the WSDL of a type + with _lazyLock: + wsdlNSAndName = _wsdlNameMap.get(type) + if wsdlNSAndName: + return wsdlNSAndName + else: + if issubclass(type, list): + ns = GetWsdlNamespace(type.Item._version) + return (ns, "ArrayOf" + Capitalize(type.Item._wsdlName)) + else: + ns = GetWsdlNamespace(type._version) + return (ns, type._wsdlName) + + +# Get the WSDL of a type def GetWsdlName(type): - return GetQualifiedWsdlName(type)[-1] + return GetQualifiedWsdlName(type)[-1] + -## Capitalize a string +# Capitalize a string def Capitalize(str): - if str: - return str[0].upper() + str[1:] - return str + if str: + return str[0].upper() + str[1:] + return str -## Uncapitalize a string + +# Uncapitalize a string def Uncapitalize(str): - if str: - return str[0].lower() + str[1:] - return str + if str: + return str[0].lower() + str[1:] + return str + -## To uncapitalize the entire vmodl name -# pyVmomi used to map Java package names to capitalized Python module names, -# but now maps the Java package names unchanged to Python module names. +# To uncapitalize the entire vmodl name pyVmomi used to map Java package +# names to capitalized Python module names, but now maps the Java +# package names unchanged to Python module names. # This function is needed to support the legacy name mapping. def UncapitalizeVmodlName(str): - if str: - return ".".join(name[0].lower() + name[1:] for name in str.split(".")) - return str + if str: + return ".".join(name[0].lower() + name[1:] for name in str.split(".")) + return str -## Add a parent version + +# Add a parent version def AddVersionParent(version, parent): - parentMap[version].add(parent) + parentMap[version].add(parent) + + +def CreateVersion(version, ns, versionId, isLegacy, serviceNs): + """ + The primitive for creating and registering a version, used by + AddVersion() implementations (Fling and proprietary). + """ + if not ns: + ns = serviceNs + if version not in parentMap: + nsMap[version] = ns + if len(versionId) > 0: + versionMap[ns + '/' + versionId] = version + if isLegacy or ns == "": + versionMap[ns] = version + versionIdMap[version] = versionId + if not serviceNs: + serviceNs = ns + serviceNsMap[version] = serviceNs + parentMap[version] = set() + def GetVersionProps(version): - """Get version properties + """Get version properties - This function is a fixed version of GetVersion(). - """ + This function is a fixed version of GetVersion(). + """ - ns = nsMap[version] - versionId = versionIdMap[version] - isLegacy = versionMap.get(ns) == version - serviceNs = serviceNsMap[version] - return ns, versionId, isLegacy, serviceNs + ns = nsMap[version] + versionId = versionIdMap[version] + isLegacy = versionMap.get(ns) == version + serviceNs = serviceNsMap[version] + return ns, versionId, isLegacy, serviceNs -## Get version namespace from version +# Get version namespace from version def GetVersionNamespace(version): - """ Get version namespace from version """ - ns = nsMap[version] - if not ns: - ns = serviceNsMap[version] - versionId = versionIdMap[version] - if not versionId: - namespace = ns - else: - namespace = '%s/%s' % (ns, versionId) - return namespace - -## Get version from the version uri + """ Get version namespace from version """ + ns = nsMap[version] + if not ns: + ns = serviceNsMap[version] + versionId = versionIdMap[version] + if not versionId: + namespace = ns + else: + namespace = '{0}/{1}'.format(ns, versionId) + return namespace + + +# Get version from the version uri def GetVersionFromVersionUri(version): - return versionMap[version.rsplit(":", 1)[-1]] + return versionMap[version.rsplit(":", 1)[-1]] + -## Get wsdl namespace from version +# Get wsdl namespace from version def GetWsdlNamespace(version): - """ Get wsdl namespace from version """ - return "urn:" + serviceNsMap[version] + """ Get wsdl namespace from version """ + return "urn:" + serviceNsMap[version] + -## Get an iterable with all version parents +def GetInternedWsdlNamespace(version): + """ Get interned wsdl namespace from version """ + return intern(GetWsdlNamespace(version)) + + +# Get an iterable with all version parents def GetVersionParents(version): - return parentMap[version] + return parentMap[version] -## Get all the versions for the service with specified namespace (partially) ordered -## by compatibility (i.e. any version in the list that is compatible with some version -## v in the list will preceed v) + +# Get all the versions for the service with specified namespace (partially) +# ordered by compatibility (i.e. any version in the list that is compatible +# with some version v in the list will preceed v) +# # @param namespace XML namespace identifying a service -# @return returns all the versions for the service with specified namespace (partially) -# ordered by compatibility +# @return returns all the versions for the service with specified +# namespace (partially) ordered by compatibility # -# NOTE: For this function, we use 'namespace' as a representation of 'service'. While -# this works for most services, for compatibility reasons, the core and query -# services share the 'vim25' namespace with the vim service. Fortunately, this -# shouldn't be an issue in practice, as the implementations of the vim -# service (vpxd and hostd) don't currently advertise that they support any -# versions of the core or query services, and we don't expect that they ever will. -# This function assumes that all other namespaces identify a unique service. +# NOTE: +# For this function, we use 'namespace' as a representation of 'service'. +# While this works for most services, for compatibility reasons, the core and +# query services share the 'vim25' namespace with the vim service. +# Fortunately, this shouldn't be an issue in practice, as the implementations +# of the vim service (vpxd and hostd) don't currently advertise that they +# support any versions of the core or query services, and we don't expect that +# they ever will. This function assumes that all other namespaces identify a +# unique service. def GetServiceVersions(namespace): - """ - Get all the versions for the service with specified namespace (partially) ordered - by compatibility (i.e. any version in the list that is compatible with some version - v in the list will preceed v) - """ - def compare(a, b): - if a == b: - return 0 - if b in parentMap[a]: - return -1 - if a in parentMap[b]: - return 1 - return (a > b) - (a < b) - - if PY3: - return sorted([v for (v, n) in iteritems(serviceNsMap) if n == namespace], - key=cmp_to_key(compare)) - else: - return sorted([v for (v, n) in iteritems(serviceNsMap) if n == namespace], - compare) - - -## Set a WSDL method with wsdl namespace and wsdl name + """ + Get all the versions for the service with specified namespace (partially) + ordered by compatibility (i.e. any version in the list that is compatible + with some version v in the list will preceed v) + """ + def compare(a, b): + if a == b: + return 0 + if b in parentMap[a]: + return -1 + if a in parentMap[b]: + return 1 + return (a > b) - (a < b) + + if version_info[0] >= 3: + return sorted( + [v for (v, n) in six.iteritems(serviceNsMap) if n == namespace], + key=cmp_to_key(compare)) + else: + return sorted( + [v for (v, n) in six.iteritems(serviceNsMap) if n == namespace], + compare) + + +# Set a WSDL method with wsdl namespace and wsdl name # Internal to VmomiSupport # Note: Must be holding the _lazyLock # # @param ns XML namespace # @param wsdlName wsdl name # @param inputMM managed method object or info to load it (it points to -# list object that points to the type info which holds -# this managed method's information) +# list object that points to the type info which holds +# this managed method's information) # @return returns added method or exising method if (ns, wsdlName) -# is already in the map. It throws a runtime error if -# trying to set two type info list's to the same (ns, wsdlName) +# is already in the map. It throws a runtime error if +# trying to set two type info list's to the same (ns, wsdlName) def _SetWsdlMethod(ns, wsdlName, inputMM): - """ - Set a WSDL method with wsdl namespace and wsdl name - Returns added method / existing method if (ns, wsdlName) already in the map - - Note: Must be holding the _lazyLock - """ - _wsdlMethodNSs.add(ns) - curMM = _wsdlMethodMap.get( (ns, wsdlName) ) - # if inputMM is a list - if isinstance(inputMM, list): - if curMM is None: - _wsdlMethodMap[(ns, wsdlName)] = inputMM - return inputMM - elif isinstance(curMM, list): - raise RuntimeError( - "Duplicate wsdl method %s %s (new class %s vs existing %s)" % \ - (ns, wsdlName, inputMM[0], curMM[0])) - else: - return curMM - # if inputMM is a ManagedMethod - else: - if curMM is None or isinstance(curMM, list): - _wsdlMethodMap[(ns, wsdlName)] = inputMM - return inputMM - else: - return curMM - -## Get wsdl method from ns, wsdlName + """ + Set a WSDL method with wsdl namespace and wsdl name + Returns added method / existing method if (ns, wsdlName) already in the map + + Note: Must be holding the _lazyLock + """ + _wsdlMethodNSs.add(ns) + curMM = _wsdlMethodMap.get((ns, wsdlName)) + # if inputMM is a list + if isinstance(inputMM, list): + if curMM is None: + _wsdlMethodMap[(ns, wsdlName)] = inputMM + return inputMM + elif isinstance(curMM, list): + raise RuntimeError( + "Duplicate wsdl method %s %s (new class %s vs existing %s)" % + (ns, wsdlName, inputMM[0], curMM[0])) + else: + return curMM + # if inputMM is a ManagedMethod + else: + if curMM is None or isinstance(curMM, list): + _wsdlMethodMap[(ns, wsdlName)] = inputMM + return inputMM + else: + return curMM + + +# Get wsdl method from ns, wsdlName +# # @param ns XML namespace # @param wsdlName wsdl name # @return managed method object or throws a KeyError def GetWsdlMethod(ns, wsdlName): - """ Get wsdl method from ns, wsdlName """ - with _lazyLock: - method = _wsdlMethodMap[(ns, wsdlName)] - if isinstance(method, ManagedMethod): - # The type corresponding to the method is loaded, - # just return the method object - return method - elif method: - # The type is not loaded, the map contains the info - # to load the type. Load the actual type and - # return the method object - LoadManagedType(*method) - return _wsdlMethodMap[(ns, wsdlName)] - else: - raise KeyError("{0} {1}".format(ns, name)) - -## Guess the method from wsdlname with no ns -# WARNING! This should not be used in general, as there is no guarantee for -# the correctness of the guessing method + """ Get wsdl method from ns, wsdlName """ + with _lazyLock: + method = _wsdlMethodMap[(ns, wsdlName)] + if isinstance(method, ManagedMethod): + # The type corresponding to the method is loaded, + # just return the method object + return method + elif method: + # The type is not loaded, the map contains the info + # to load the type. Load the actual type and + # return the method object + LoadManagedType(*method) + return _wsdlMethodMap[(ns, wsdlName)] + else: + raise KeyError("{0} {1}".format(ns, wsdlName)) + + +# Guess the method from wsdlname with no ns +# WARNING! This should not be used in general, as there is no guarantee for +# the correctness of the guessing method +# # @param name wsdl name -# @return managed method object if found in any namespace else throws -# KeyError +# @return managed method object if found in any namespace else throws KeyError def GuessWsdlMethod(name): - with _lazyLock: - # Some methods may exist in multiple namespaces, and returning - # the wrong one will cause a deserialization error. - # Since in python3 the order of entries in set is not deterministic, - # we will try to get the method from vim25 namespace first. - try: - return GetWsdlMethod(XMLNS_VMODL_BASE, name) - except KeyError: - pass - - for ns in _wsdlMethodNSs: - try: - return GetWsdlMethod(ns, name) - except KeyError: + with _lazyLock: + # Some methods may exist in multiple namespaces, and returning + # the wrong one will cause a deserialization error (1656837). + # Since in python3 the order of entries in set is not deterministic, + # we will try to get the method from vim25 namespace first. + try: + return GetWsdlMethod(XMLNS_VMODL_BASE, name) + except KeyError: pass - raise KeyError(name) -## Widen a type to one supported in a given version + for ns in _wsdlMethodNSs: + try: + return GetWsdlMethod(ns, name) + except KeyError: + pass + raise KeyError(name) + + +# Widen a type to one supported in a given version def GetCompatibleType(type, version): - # Type can be widened if it has the attribute "_version" (which implies it - # is either a DataObject or ManagedObject) - if hasattr(type, "_version"): - while not IsChildVersion(version, type._version): - type = type.__bases__[0] - return type - -## Invert an injective mapping + # Type can be widened if it has the attribute "_version" (which implies it + # is either a DataObject or ManagedObject) + if hasattr(type, "_version"): + while not IsChildVersion(version, type._version): + type = type.__bases__[0] + return type + + +# Invert an injective mapping def InverseMap(map): - return dict([ (v, k) for (k, v) in iteritems(map) ]) + return dict([(v, k) for (k, v) in six.iteritems(map)]) + def GetVmodlNs(version): - versionParts = version.split('.version.') - assert len(versionParts) == 2, 'Unsupported version format: %s' % version - return versionParts[0] + versionParts = version.split('.version.') + assert len(versionParts) == 2, 'Unsupported version format: {0}'.format(version) + return versionParts[0] + types = Object() -nsMap = {} -versionIdMap = {} -versionMap = {} -serviceNsMap = { BASE_VERSION : XMLNS_VMODL_BASE.split(":")[-1] } -parentMap = {} + +nsMap = {} # e.g. 'vim.version.version12' -> 'vim25' +versionIdMap = {} # e.g. 'vim.version.version12' -> '6.7' +versionMap = {} # e.g. 'vim25/6.7' -> 'vim.version.version12' +# also 'vim25' -> 'vim.version.version3' + +parentMap = {} # e.g. 'vim.version.version12' -> set( +# 'vim.version.version11', +# 'vim.version.version10', +# ... +# 'vmodl.version.version0' +# ) + +serviceNsMap = {BASE_VERSION: XMLNS_VMODL_BASE.split(":")[-1]} + class _MaturitySet: - """ - Registry for versions from all namespaces defining a given maturity. - The registration is automatic (relevant code is generated by emitters), - while for the query one may use either the VMODL namespace id (e.g. 'vim'), - or the wire namespace id (e.g. 'vim25'). - """ - def __init__(self): - self._verNameMap = {} # e.g. 'vim' -> 'vim.version.version12' - self._verNameMapW = {} # e.g. 'vim25' -> 'vim.version.version12' - self._wireIdMap = {} # e.g. 'vim' -> 'vim25/6.7' - self._wireIdMapW = {} # e.g. 'vim25' -> 'vim25/6.7' - - def Add(self, version): - """ - Register the version at corresponding maturity for a given VMODL - namespace. The 'version' parameter is in the VMODL name format - e.g. 'vim.version.version12'. This method is typically used by - auto-generated code. - """ - vmodlNs = GetVmodlNs(version) - - # TODO fix the VSAN-related part of vcenter-all to enable the assert - # assert not (vmodlNs in self._verNameMap), 'Re-definition: %s' % vmodlNs - - wireId = GetVersionNamespace(version) - wireNs = wireId.split('/')[0] - self._verNameMap[vmodlNs] = version - self._verNameMapW[wireNs] = version - self._wireIdMap[vmodlNs] = wireId - self._wireIdMapW[wireNs] = wireId - return wireId, wireNs - - def GetName(self, vmodlNs): - """ - VMODL namespace to registered version name mapping, e.g. - 'vim' -> 'vim.version.version12' - """ - return self._verNameMap[vmodlNs] - - def GetNameW(self, wireNs): - """ - Wire namespace to registered version name mapping, e.g. - 'vim25' -> 'vim.version.version12' - """ - return self._verNameMapW[wireNs] - - def GetWireId(self, vmodlNs): - """ - VMODL namespace to registered version wire-id mapping, e.g. - 'vim' -> 'vim25/6.7' - """ - return self._wireIdMap[vmodlNs] - - def GetWireIdW(self, wireNs): - """ - Wire namespace to registered version wire-id mapping, e.g. - 'vim25' -> 'vim25/6.7' - """ - return self._wireIdMapW[wireNs] - - def EnumerateVmodlNs(self): - """ - Returns an iterable with registered VMODL namespace, e.g. - ['vim', 'vpx', ... ] - """ - return self._verNameMap.keys() - - def EnumerateWireNs(self): - """ - Returns an iterable with registered wire namespace, e.g. - ['vim25', 'vpxd3', ... ] - """ - return self._verNameMapW.keys() - - def EnumerateVersions(self): - """ - Returns an iterable with registered VMODL versions, e.g. - ['vim.version.version12', 'vpx.version.version12', ... ] - """ - return self._verNameMap.values() - - def EnumerateWireIds(self): - """ - Returns an iterable with registered versions wire-ids, e.g. - e.g. ['vim25/6.7', 'vpxd3/6.7', ... ] - """ - return self._wireIdMap.values() + """ + Registry for versions from all namespaces defining a given maturity. + The registration is automatic (relevant code is generated by emitters), + while for the query one may use either the VMODL namespace id (e.g. 'vim'), + or the wire namespace id (e.g. 'vim25'). + """ + def __init__(self): + self._verNameMap = {} # e.g. 'vim' -> 'vim.version.version12' + self._verNameMapW = {} # e.g. 'vim25' -> 'vim.version.version12' + self._wireIdMap = {} # e.g. 'vim' -> 'vim25/6.7' + self._wireIdMapW = {} # e.g. 'vim25' -> 'vim25/6.7' + + def Add(self, version): + """ + Register the version at corresponding maturity for a given VMODL + namespace. The 'version' parameter is in the VMODL name format + e.g. 'vim.version.version12'. This method is typically used by + auto-generated code. + """ + vmodlNs = GetVmodlNs(version) + + # TODO fix the VSAN-related part of vcenter-all to enable the assert + # assert not (vmodlNs in self._verNameMap), + # 'Re-definition: {}'.format(vmodlNs) + + wireId = GetVersionNamespace(version) + wireNs = wireId.split('/')[0] + self._verNameMap[vmodlNs] = version + self._verNameMapW[wireNs] = version + self._wireIdMap[vmodlNs] = wireId + self._wireIdMapW[wireNs] = wireId + return wireId, wireNs + + def GetName(self, vmodlNs): + """ + VMODL namespace to registered version name mapping, e.g. + 'vim' -> 'vim.version.version12' + """ + return self._verNameMap[vmodlNs] + + def GetNameW(self, wireNs): + """ + Wire namespace to registered version name mapping, e.g. + 'vim25' -> 'vim.version.version12' + """ + return self._verNameMapW[wireNs] + + def GetWireId(self, vmodlNs): + """ + VMODL namespace to registered version wire-id mapping, e.g. + 'vim' -> 'vim25/6.7' + """ + return self._wireIdMap[vmodlNs] + + def GetWireIdW(self, wireNs): + """ + Wire namespace to registered version wire-id mapping, e.g. + 'vim25' -> 'vim25/6.7' + """ + return self._wireIdMapW[wireNs] + + def EnumerateVmodlNs(self): + """ + Returns an iterable with registered VMODL namespace, e.g. + ['vim', 'vpx', ... ] + """ + return self._verNameMap.keys() + + def EnumerateWireNs(self): + """ + Returns an iterable with registered wire namespace, e.g. + ['vim25', 'vpxd3', ... ] + """ + return self._verNameMapW.keys() + + def EnumerateVersions(self): + """ + Returns an iterable with registered VMODL versions, e.g. + ['vim.version.version12', 'vpx.version.version12', ... ] + """ + return self._verNameMap.values() + + def EnumerateWireIds(self): + """ + Returns an iterable with registered versions wire-ids, e.g. + e.g. ['vim25/6.7', 'vpxd3/6.7', ... ] + """ + return self._wireIdMap.values() + # Backward compatibility aliases _MaturitySet.Get = _MaturitySet.GetName _MaturitySet.GetNamespace = _MaturitySet.GetWireId +from .Version import Init as _VersionInit # noqa: E402 +_VersionInit() newestVersions = _MaturitySet() ltsVersions = _MaturitySet() dottedVersions = _MaturitySet() oldestVersions = _MaturitySet() -# Alias for backward compatibility. +# Alias for backward compatibility. Required until VMC M11 is phased out. publicVersions = ltsVersions -from .Version import AddVersion, IsChildVersion +# AddVersion is not used in this module however it is imported from it in the +# generated bindings so removing it will break the world. +from .Version import AddVersion, IsChildVersion # noqa: E402, F401 -if not isinstance(bool, type): # bool not a type in python <= 2.2 - bool = type("bool", (int,), - {"__new__": lambda cls, val=0: int.__new__(cls, val and 1 or 0)}) -byte = type("byte", (int,), {}) -short = type("short", (int,), {}) -double = type("double", (float,), {}) +byte = type("byte", (int, ), {}) +short = type("short", (int, ), {}) +double = type("double", (float, ), {}) if PY3: - long = type("long", (int,), {}) -URI = type("URI", (str,), {}) -if not PY3: + long = type("long", (int, ), {}) +URI = type("URI", (str, ), {}) +if not PY3 and _binaryIsBytearray: # six defines binary_type in python2 as a string; this means the # JSON encoder sees checksum properties as strings and attempts # to perform utf-8 decoding on them because they contain high-bit @@ -1553,48 +1634,66 @@ def EnumerateWireIds(self): binary = type("binary", (bytearray,), {}) else: binary = type("binary", (binary_type,), {}) -PropertyPath = type("PropertyPath", (text_type,), {}) +PropertyPath = type("PropertyPath", (six.text_type, ), {}) # _wsdlTypeMapNSs store namespaces added to _wsdlTypeMap in _SetWsdlType _wsdlTypeMapNSs = set() _wsdlTypeMap = { - # Note: xsd has no void type. This is a hack from before. Can be removed? - (XMLNS_XSD, 'void') : NoneType, - (XMLNS_XSD, 'anyType') : object, - (XMLNS_XSD, 'boolean') : bool, - (XMLNS_XSD, 'byte') : byte, - (XMLNS_XSD, 'short') : short, - (XMLNS_XSD, 'int') : int, - (XMLNS_XSD, 'long') : long, - (XMLNS_XSD, 'float') : float, - (XMLNS_XSD, 'double') : double, - (XMLNS_XSD, 'string') : str, - (XMLNS_XSD, 'anyURI') : URI, - (XMLNS_XSD, 'base64Binary') : binary, - (XMLNS_XSD, 'dateTime') : datetime, - (XMLNS_XSD, 'Link') : Link, - (XMLNS_VMODL_BASE, 'TypeName') : type, - (XMLNS_VMODL_BASE, 'MethodName') : ManagedMethod, - (XMLNS_VMODL_BASE, 'PropertyPath') : PropertyPath + # Note: xsd has no void type. This is a hack from before. Can be removed? + (XMLNS_XSD, 'void'): + NoneType, + (XMLNS_XSD, 'anyType'): + object, + (XMLNS_XSD, 'boolean'): + bool, + (XMLNS_XSD, 'byte'): + byte, + (XMLNS_XSD, 'short'): + short, + (XMLNS_XSD, 'int'): + int, + (XMLNS_XSD, 'long'): + long, + (XMLNS_XSD, 'float'): + float, + (XMLNS_XSD, 'double'): + double, + (XMLNS_XSD, 'string'): + str, + (XMLNS_XSD, 'anyURI'): + URI, + (XMLNS_XSD, 'base64Binary'): + binary, + (XMLNS_XSD, 'dateTime'): + datetime, + (XMLNS_XSD, 'Link'): + Link, + (XMLNS_VMODL_BASE, 'TypeName'): + type, + (XMLNS_VMODL_BASE, 'MethodName'): + ManagedMethod, + (XMLNS_VMODL_BASE, 'PropertyPath'): + PropertyPath } _wsdlNameMap = InverseMap(_wsdlTypeMap) for ((ns, name), typ) in list(_wsdlTypeMap.items()): - if typ is not NoneType: - setattr(types, typ.__name__, typ) - _wsdlTypeMapNSs.add(ns) - arrayType = CreateArrayType(typ) - setattr(types, Capitalize(typ.__name__) + "Array", arrayType) - arrayName = "ArrayOf" + Capitalize(name) - arrayNS = XMLNS_VMODL_BASE - _SetWsdlType(arrayNS, arrayName, arrayType) - _wsdlNameMap[arrayType] = (arrayNS, arrayName) + if typ is not NoneType: + setattr(types, typ.__name__, typ) + _wsdlTypeMapNSs.add(ns) + arrayType = CreateArrayType(typ) + setattr(types, Capitalize(typ.__name__) + "Array", arrayType) + arrayName = "ArrayOf" + Capitalize(name) + arrayNS = XMLNS_VMODL_BASE + _SetWsdlType(arrayNS, arrayName, arrayType) + _wsdlNameMap[arrayType] = (arrayNS, arrayName) del name, typ # unicode is mapped to wsdl name 'string' (Cannot put in wsdlTypeMap or name # collision with non-unicode string) -_wsdlNameMap[text_type] = (XMLNS_XSD, 'string') -_wsdlNameMap[CreateArrayType(text_type)] = (XMLNS_VMODL_BASE, 'ArrayOfString') +_wsdlNameMap[six.text_type] = (XMLNS_XSD, 'string') +_wsdlNameMap[CreateArrayType(six.text_type)] = (XMLNS_VMODL_BASE, + 'ArrayOfString') # _wsdlMethodNSs store namespaces added to _wsdlMethodMap in _SetWsdlMethod _wsdlMethodNSs = set() @@ -1611,339 +1710,356 @@ def EnumerateWireIds(self): _AddType(DataObject) setattr(types, DataObject.__name__, DataObject) -## Vmodl types +# Vmodl types vmodlTypes = { - # Note: xsd has no void type. This is a hack from before. Can be removed? - "void" : GetWsdlType(XMLNS_XSD, 'void'), - "anyType": GetWsdlType(XMLNS_XSD, 'anyType'), - "string" : GetWsdlType(XMLNS_XSD, 'string'), - "bool" : GetWsdlType(XMLNS_XSD, 'boolean'), - "boolean": GetWsdlType(XMLNS_XSD, 'boolean'), - "byte" : GetWsdlType(XMLNS_XSD, 'byte'), - "short" : GetWsdlType(XMLNS_XSD, 'short'), - "int" : GetWsdlType(XMLNS_XSD, 'int'), - "long" : GetWsdlType(XMLNS_XSD, 'long'), - "float" : GetWsdlType(XMLNS_XSD, 'float'), - "double" : GetWsdlType(XMLNS_XSD, 'double'), - "Link" : GetWsdlType(XMLNS_XSD, 'Link'), - "vmodl.URI" : GetWsdlType(XMLNS_XSD, 'anyURI'), - "vmodl.Binary" : GetWsdlType(XMLNS_XSD, 'base64Binary'), - "vmodl.DateTime" : GetWsdlType(XMLNS_XSD, 'dateTime'), - "vmodl.TypeName" : GetWsdlType(XMLNS_VMODL_BASE, 'TypeName'), - "vmodl.MethodName" : GetWsdlType(XMLNS_VMODL_BASE, 'MethodName'), - "vmodl.DataObject" : GetWsdlType(XMLNS_VMODL_BASE, 'DataObject'), - "vmodl.ManagedObject" : GetWsdlType(XMLNS_VMODL_BASE, 'ManagedObject'), - "vmodl.PropertyPath" : GetWsdlType(XMLNS_VMODL_BASE, 'PropertyPath'), + # Note: xsd has no void type. This is a hack from before. Can be removed? + "void": GetWsdlType(XMLNS_XSD, 'void'), + "anyType": GetWsdlType(XMLNS_XSD, 'anyType'), + "string": GetWsdlType(XMLNS_XSD, 'string'), + "bool": GetWsdlType(XMLNS_XSD, 'boolean'), + "boolean": GetWsdlType(XMLNS_XSD, 'boolean'), + "byte": GetWsdlType(XMLNS_XSD, 'byte'), + "short": GetWsdlType(XMLNS_XSD, 'short'), + "int": GetWsdlType(XMLNS_XSD, 'int'), + "long": GetWsdlType(XMLNS_XSD, 'long'), + "float": GetWsdlType(XMLNS_XSD, 'float'), + "double": GetWsdlType(XMLNS_XSD, 'double'), + "Link": GetWsdlType(XMLNS_XSD, 'Link'), + "vmodl.URI": GetWsdlType(XMLNS_XSD, 'anyURI'), + "vmodl.Binary": GetWsdlType(XMLNS_XSD, 'base64Binary'), + "vmodl.DateTime": GetWsdlType(XMLNS_XSD, 'dateTime'), + "vmodl.TypeName": GetWsdlType(XMLNS_VMODL_BASE, 'TypeName'), + "vmodl.MethodName": GetWsdlType(XMLNS_VMODL_BASE, 'MethodName'), + "vmodl.DataObject": GetWsdlType(XMLNS_VMODL_BASE, 'DataObject'), + "vmodl.ManagedObject": GetWsdlType(XMLNS_VMODL_BASE, 'ManagedObject'), + "vmodl.PropertyPath": GetWsdlType(XMLNS_VMODL_BASE, 'PropertyPath'), } vmodlNames = {} -## Add array type into special names -for name, typ in vmodlTypes.copy().items(): - if typ is not NoneType: - try: - arrayType = typ.Array - except AttributeError: - wsdlName = GetWsdlName(typ) - arrayNS = XMLNS_VMODL_BASE - arrayType = GetWsdlType(arrayNS, "ArrayOf" + Capitalize(wsdlName)) - arrayName = name + "[]" - vmodlTypes[arrayName] = arrayType - - # Set type to vmodl name map - vmodlNames[typ] = name - vmodlNames[arrayType] = arrayName + +# Add array type into special names +for name, typ in six.iteritems(vmodlTypes.copy()): + if typ is not NoneType: + try: + arrayType = typ.Array + except AttributeError: + wsdlName = GetWsdlName(typ) + arrayNS = XMLNS_VMODL_BASE + arrayType = GetWsdlType(arrayNS, "ArrayOf" + Capitalize(wsdlName)) + arrayName = name + "[]" + vmodlTypes[arrayName] = arrayType + + # Set type to vmodl name map + if name != "bool": + vmodlNames[typ] = name + vmodlNames[arrayType] = arrayName del name, typ -## Get type from vmodl name +# Get type from vmodl name # # @param name vmodl name # @return vmodl type def GetVmodlType(name): - """ Get type from vmodl name """ - - # If the input is already a type, just return - if isinstance(name, type): - return name - - # Try to get type from vmodl type names table - typ = vmodlTypes.get(name) - if typ: - return typ - - # Else get the type from the _wsdlTypeMap - isArray = name.endswith("[]") - if isArray: - name = name[:-2] - ns, wsdlName = _GetWsdlInfo(name) - try: - typ = GetWsdlType(ns, wsdlName) - except KeyError: - raise KeyError(name) - if typ: - return isArray and typ.Array or typ - else: - raise KeyError(name) - -## Get VMODL type name from type + """ Get type from vmodl name """ + + # If the input is already a type, just return + if isinstance(name, type): + return name + + # Try to get type from vmodl type names table + typ = vmodlTypes.get(name) + if typ: + return typ + + # Else get the type from the _wsdlTypeMap + isArray = name.endswith("[]") + if isArray: + name = name[:-2] + ns, wsdlName = _GetWsdlInfo(name) + try: + typ = GetWsdlType(ns, wsdlName) + except KeyError: + raise KeyError(name) + if typ: + return isArray and typ.Array or typ + else: + raise KeyError(name) + + +# Get VMODL type name from type # # @param typ vmodl type # @return vmodl name def GetVmodlName(typ): - """ Get vmodl type name from type """ - try: - return vmodlNames[typ] - except KeyError: - return typ.__name__ + """ Get vmodl type name from type """ + try: + return vmodlNames[typ] + except KeyError: + return typ.__name__ + -## Get Wsdl type name from Python type name +# Get Wsdl type name from Python type name # # @param pythonTypeName Python type name # @return wsdl type name def GetWsdlTypeName(pythonTypeName): - try: - typ = GetVmodlType(pythonTypeName) - except KeyError: - raise NameError('No type found with name ' + pythonTypeName) - return GetWsdlName(typ) + try: + typ = GetVmodlType(pythonTypeName) + except KeyError: + raise NameError('No type found with name ' + pythonTypeName) + return GetWsdlName(typ) -## Get Wsdl method name from Python method name + +# Get Wsdl method name from Python method name # # @param pythonTypeName Python type name # @param pythonMethodName Python method name # @return wsdl method name def GetWsdlMethodName(pythonTypeName, pythonMethodName): - try: - typ = GetVmodlType(pythonTypeName) - _, _, _, _, _, methods = _wsdlDefMap[GetQualifiedWsdlName(typ)] - except KeyError: - raise NameError('No type found with name ' + pythonTypeName) - uncapPythonMethodName = Uncapitalize(pythonMethodName) - for method in methods: - mVmodl, mWsdl, _, _, _, _, _ = method - if mVmodl == uncapPythonMethodName or mVmodl == pythonMethodName: - return mWsdl - raise NameError('No method found with name ' + pythonMethodName) - -## Get Python type name from Wsdl type name + try: + typ = GetVmodlType(pythonTypeName) + _, _, _, _, _, methods = _wsdlDefMap[GetQualifiedWsdlName(typ)] + except KeyError: + raise NameError('No type found with name ' + pythonTypeName) + uncapPythonMethodName = Uncapitalize(pythonMethodName) + for method in methods: + mVmodl, mWsdl, _, _, _, _, _ = method + if mVmodl == uncapPythonMethodName or mVmodl == pythonMethodName: + return mWsdl + raise NameError('No method found with name ' + pythonMethodName) + + +# Get Python type name from Wsdl type name # # @param ns wsdl namespace # @param wsdlTypeName wsdl type name # @return python type name def GetPythonTypeName(wsdlTypeName, ns): - try: - typ = GetWsdlType(ns, wsdlTypeName) - except KeyError: - raise NameError('No type found with namespace %s and name %s' % (ns, wsdlTypeName)) - return GetVmodlName(typ) + try: + typ = GetWsdlType(ns, wsdlTypeName) + except KeyError: + raise NameError('No type found with namespace %s and name %s' % + (ns, wsdlTypeName)) + return GetVmodlName(typ) + -## Get Python method name from Wsdl method name +# Get Python method name from Wsdl method name # # @param ns wsdl namespace # @param wsdlTypeName wsdl type name # @param wsdlMethodName wsdl method name # @return python method name def GetPythonMethodName(wsdlTypeName, ns, wsdlMethodName): - try: - _, _, _, _, _, methods = _wsdlDefMap[(ns, wsdlTypeName)] - except KeyError: - raise NameError('No type found with namespace %s and name %s' % (ns, wsdlTypeName)) - for method in methods: - mVmodl, mWsdl, _, _, _, _, _ = method - if mWsdl == wsdlMethodName: - return Capitalize(mVmodl) - raise NameError('No method found with name ' + wsdlMethodName) - -## String only dictionary: same as dict, except it only accept string as value -# + try: + _, _, _, _, _, methods = _wsdlDefMap[(ns, wsdlTypeName)] + except KeyError: + raise NameError('No type found with namespace %s and name %s' % + (ns, wsdlTypeName)) + for method in methods: + mVmodl, mWsdl, _, _, _, _, _ = method + if mWsdl == wsdlMethodName: + return Capitalize(mVmodl) + raise NameError('No method found with name ' + wsdlMethodName) + + +# String only dictionary: same as dict, except it only accept string as value class StringDict(dict): - """ - String only dictionary: same as dict, except it only accept string as value - - dict in python is kind of strange. U cannot just override __setitem__, as - __init__, update, and setdefault all bypass __setitem__. When override, - we have to override all three together - """ - def __init__(self, *args, **kwargs): - dict.__init__(self) - self.update(*args, **kwargs) - - # Same as dict setdefault, except this will call through our __setitem__ - def update(self, *args, **kwargs): - for k, v in iteritems(dict(*args, **kwargs)): - self[k] = v - - # Same as dict setdefault, except this will call through our __setitem__ - def setdefault(self, key, val=None): - if key in self: - return self[key] - else: - self[key] = val - return val - - def __setitem__(self, key, val): - """x.__setitem__(i, y) <==> x[i]=y, where y must be a string""" - if not isinstance(val, string_types): - raise TypeError("key %s has non-string value %s of %s" % - (key, val, type(val))) - return dict.__setitem__(self, key, val) - -## Retrieves the actual vmodl name from type dictionaries + """ + String only dictionary: same as dict, except it only accept string as value + + dict in python is kind of strange. U cannot just override __setitem__, as + __init__, update, and setdefault all bypass __setitem__. When override, + we have to override all three together + """ + def __init__(self, *args, **kwargs): + dict.__init__(self) + self.update(*args, **kwargs) + + # Same as dict setdefault, except this will call through our __setitem__ + def update(self, *args, **kwargs): + for k, v in six.iteritems(dict(*args, **kwargs)): + self[k] = v + + # Same as dict setdefault, except this will call through our __setitem__ + def setdefault(self, key, val=None): + if key in self: + return self[key] + else: + self[key] = val + return val + + def __setitem__(self, key, val): + """x.__setitem__(i, y) <==> x[i]=y, where y must be a string""" + if not isinstance(val, string_types): + raise TypeError("key %s has non-string value %s of %s" % + (key, val, type(val))) + return dict.__setitem__(self, key, val) + + +# Retrieves the actual vmodl name from type dictionaries +# Note: Must be holding the _lazyLock # -# Note: Must be holding the _lazyLock # @param name upcapitalized vmodl name # @return vmodl name def _GetActualName(name): - """ Note: Must be holding the _lazyLock """ - if _allowCapitalizedNames: - name = UncapitalizeVmodlName(name) - for defMap in _dataDefMap, _managedDefMap, _enumDefMap: - dic = defMap.get(name) - if dic: - return dic[0] - return None - -## Retrieves the actual wsdl name from type dictionaries + """ Note: Must be holding the _lazyLock """ + if _allowCapitalizedNames: + name = UncapitalizeVmodlName(name) + for defMap in _dataDefMap, _managedDefMap, _enumDefMap: + dic = defMap.get(name) + if dic: + return dic[0] + return None + + +# Retrieves the actual wsdl name from type dictionaries # # @param name upcapitalized vmodl name # @return (wsdl namespace, wsdl name) def _GetWsdlInfo(name): - if _allowCapitalizedNames: - name = UncapitalizeVmodlName(name) - - with _lazyLock: - # For data and managed objects, emitter puts version in field #3 and in - # enum objects, it is in field #2. So, have to handle them differently - for defMap in _dataDefMap, _managedDefMap: - dic = defMap.get(name) - if dic: - return GetWsdlNamespace(dic[3]), dic[1] - - dic = _enumDefMap.get(name) - if dic: - return GetWsdlNamespace(dic[2]), dic[1] - return None, None - -## Checks if the definition exists for a vmodl name + if _allowCapitalizedNames: + name = UncapitalizeVmodlName(name) + + with _lazyLock: + # For data and managed objects, emitter puts version in field #3 and in + # enum objects, it is in field #2. So, have to handle them differently + for defMap in _dataDefMap, _managedDefMap: + dic = defMap.get(name) + if dic: + return GetWsdlNamespace(dic[3]), dic[1] + + dic = _enumDefMap.get(name) + if dic: + return GetWsdlNamespace(dic[2]), dic[1] + return None, None + + +# Checks if the definition exists for a vmodl name # # @param name vmodl name # @return True if name exists, False otherwise def TypeDefExists(name): - # Check if is one of the primitive types - typ = vmodlTypes.get(name) - if typ: - return True + # Check if is one of the primitive types + typ = vmodlTypes.get(name) + if typ: + return True - # Check if it's type definition is loaded in the dictionaries - if name.endswith("[]"): - name = name[:-2] + # Check if it's type definition is loaded in the dictionaries + if name.endswith("[]"): + name = name[:-2] + + with _lazyLock: + actualName = _GetActualName(name) + return actualName is not None - with _lazyLock: - actualName = _GetActualName(name) - return actualName is not None # Thread local for req context _threadLocalContext = threading.local() +HOSTAPI_INVOKE_TIMEOUT_KEY = 'timeout' + # Get the RequestContext for the current thread -# def GetRequestContext(): - """ Get the RequestContext for the current thread """ - global _threadLocalContext - return _threadLocalContext.__dict__.setdefault('reqCtx', StringDict()) + """ Get the RequestContext for the current thread """ + global _threadLocalContext + return _threadLocalContext.__dict__.setdefault('reqCtx', StringDict()) + # Get the Http context for the current thread -# def GetHttpContext(): - """ Get the Http context for the current thread """ - global _threadLocalContext - return _threadLocalContext.__dict__.setdefault('httpCtx', dict()) + """ Get the Http context for the current thread """ + global _threadLocalContext + return _threadLocalContext.__dict__.setdefault('httpCtx', dict()) + -## Class that resolves links +# Class that resolves links class LinkResolver: - ## Constructor - # - # @param self self - # @param scope DataObject to be used against for resolving links - def __init__(self, scope): - self.linkables = {} - self._VisitDataObject(scope) - - ## Visit a DataObject and add it to linkable if it is one. Also - # visit its properties that are DataObjects - # - # @param self self - # @param obj DataObject to be visited - def _VisitDataObject(self, obj): - if isinstance(obj, DataObject): - for prop in obj._GetPropertyList(): - if issubclass(prop.type, list): - for dataObj in getattr(obj, prop.name): - if (prop.flags & F_LINKABLE): - self._AddLinkable(dataObj) - self._VisitDataObject(dataObj) + # Constructor + # + # @param self self + # @param scope DataObject to be used against for resolving links + def __init__(self, scope): + self.linkables = {} + self._VisitDataObject(scope) + + # Visit a DataObject and add it to linkable if it is one. Also + # visit its properties that are DataObjects + # + # @param self self + # @param obj DataObject to be visited + def _VisitDataObject(self, obj): + if isinstance(obj, DataObject): + for prop in obj._GetPropertyList(): + if issubclass(prop.type, list): + for dataObj in getattr(obj, prop.name): + if (prop.flags & F_LINKABLE): + self._AddLinkable(dataObj) + self._VisitDataObject(dataObj) + else: + dataObj = getattr(obj, prop.name) + if (prop.flags & F_LINKABLE): + self._AddLinkable(dataObj) + self._VisitDataObject(dataObj) + elif isinstance(obj, list): + for dataObj in obj: + self._VisitDataObject(dataObj) + + # Adds a DataObject to linkable dictionary using its key + # + # @param self self + # @param obj DataObject to be added to linkable + def _AddLinkable(self, obj): + key = getattr(obj, "key") + if key and key != '': + if key in self.linkables: + # duplicate key present + raise AttributeError(key) else: - dataObj = getattr(obj, prop.name) - if (prop.flags & F_LINKABLE): - self._AddLinkable(dataObj) - self._VisitDataObject(dataObj) - elif isinstance(obj, list): - for dataObj in obj: - self._VisitDataObject(dataObj) - - ## Adds a DataObject to linkable dictionary using its key - # - # @param self self - # @param obj DataObject to be added to linkable - def _AddLinkable(self, obj): - key = getattr(obj, "key") - if key and key != '': - if key in self.linkables: - #duplicate key present + self.linkables[key] = obj + else: + # empty key raise AttributeError(key) - else: - self.linkables[key] = obj - else: - #empty key - raise AttributeError(key) - - ## Resolves a key by looking up linkable dictionary - # - # @param self self - # @param key Key to be resolved - def ResolveLink(self, key): - val = self.linkables[key] - return val - - ## Resolves a list of keys by resolving each key - # - # @param self self - # @param keys keys to be resolved - def ResolveLinks(self, keys): - val = [self.linkables[k] for k in keys] - return val - -## Resolves a link key using the object provided as its scope by creating a -# link resolver object + + # Resolves a key by looking up linkable dictionary + # + # @param self self + # @param key Key to be resolved + def ResolveLink(self, key): + val = self.linkables[key] + return val + + # Resolves a list of keys by resolving each key + # + # @param self self + # @param keys keys to be resolved + def ResolveLinks(self, keys): + val = [self.linkables[k] for k in keys] + return val + + +# Resolves a link key using the object provided as its scope by creating a +# link resolver object # # @param key Key to be resolved # @param obj DataObject to be used against for resolving links def ResolveLink(key, obj): - if obj is None: - return None - linkResolver = LinkResolver(obj) - return linkResolver.ResolveLink(key) + if obj is None: + return None + linkResolver = LinkResolver(obj) + return linkResolver.ResolveLink(key) + -## Resolves a list of link keys using the object provided as its scope by creating a -# link resolver object +# Resolves a list of link keys using the object provided as its scope by +# creating a link resolver object # # @param keys keys to be resolved # @param obj DataObject to be used against for resolving links def ResolveLinks(keys, obj): - if obj is None: - return None - linkResolver = LinkResolver(obj) - return linkResolver.ResolveLinks(keys) + if obj is None: + return None + linkResolver = LinkResolver(obj) + return linkResolver.ResolveLinks(keys) # Dictionary of type { 'branch' : { 'namespace' : count } } @@ -1953,8 +2069,8 @@ def ResolveLinks(keys, obj): def AddBreakingChangesInfo(branchName, vmodlNamespace, count): - _breakingChanges.setdefault(branchName, {})[vmodlNamespace] = count + _breakingChanges.setdefault(branchName, {})[vmodlNamespace] = count def GetBreakingChanges(): - return _breakingChanges + return _breakingChanges diff --git a/pyVmomi/__init__.py b/pyVmomi/__init__.py index 244062e92..4341be457 100644 --- a/pyVmomi/__init__.py +++ b/pyVmomi/__init__.py @@ -1,51 +1,74 @@ -# VMware vSphere Python SDK -# Copyright (c) 2008-2015 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 -# In VmomiSupport, to support dynamic type loading, all the data types are -# wrapped around using a meta type which can intercept attribute access and -# load the necessary nested classes. This can be implemented only in python 2.5 -# version or more. +# ********************************************************** +# Copyright (c) 2005-2022 VMware, Inc. +# ********************************************************** + import sys -if sys.version_info < (2,5): - sys.stderr.write("You need Python 2.5 or later to import pyVmomi module\n") - sys.exit(1) - -import pyVmomi.VmomiSupport -import pyVmomi._typeinfo_core -import pyVmomi._typeinfo_query -import pyVmomi._typeinfo_vim -import pyVmomi._typeinfo_pbm -import pyVmomi._typeinfo_sms -import pyVmomi._typeinfo_eam +import importlib + +if sys.version_info < (2, 7, 9): + sys.stderr.write("pyVmomi requires Python 2.7.9 or newer") + sys.exit(1) + +_initialized = False + + +# Definition precedes pyVmomi modules imports to escape circular +# dependency error if modules import _assert_not_initialized() +def _assert_not_initialized(): + if _initialized: + raise RuntimeError("pyVmomi is already initialized!") + + +def _import_typeinfo(typeinfo): + try: + __import__('_typeinfo_' + typeinfo, globals(), level=1) + except ImportError: + pass + + +def _load_typeinfos(): + from ._typeinfos import typeinfos + for typeinfo in typeinfos: + _import_typeinfo(typeinfo) + +try: + settings = importlib.import_module('.pyVmomiSettings', 'pyVmomi') +except ImportError: + settings = None + +# set default settings +_allowGetSet = getattr(settings, 'allowGetSet', True) +_allowCapitalizedNames = \ + getattr(settings, 'allowCapitalizedNames', True) +_binaryIsBytearray = \ + getattr(settings, 'binaryIsBytearray', False) +_legacyThumbprintException = \ + getattr(settings, 'legacyThumbprintException', False) + + +from . import VmomiSupport # noqa: E402 +from . import Feature # noqa: E402 + +_load_typeinfos() # All data object types and fault types have DynamicData as an ancestor # As well load it proactively. # Note: This should be done before importing SoapAdapter as it uses # some fault types -pyVmomi.VmomiSupport.GetVmodlType("vmodl.DynamicData") +VmomiSupport.GetVmodlType("vmodl.DynamicData") -from pyVmomi.SoapAdapter import SoapStubAdapter, StubAdapterBase, SoapCmdStubAdapter, \ - SessionOrientedStub, ThumbprintMismatchException +from .SoapAdapter import ( # noqa: E402,F401 + SessionOrientedStub, SoapStubAdapter, StubAdapterBase) +if _legacyThumbprintException: + from .Security import ThumbprintMismatchException # noqa: F401 -types = pyVmomi.VmomiSupport.types +types = VmomiSupport.types # This will allow files to use Create** functions # directly from pyVmomi -CreateEnumType = pyVmomi.VmomiSupport.CreateEnumType -CreateDataType = pyVmomi.VmomiSupport.CreateDataType -CreateManagedType = pyVmomi.VmomiSupport.CreateManagedType +CreateEnumType = VmomiSupport.CreateEnumType +CreateDataType = VmomiSupport.CreateDataType +CreateManagedType = VmomiSupport.CreateManagedType # For all the top level names, creating a LazyModule object # in the global namespace of pyVmomi. Files can just import the @@ -54,14 +77,21 @@ # ALLOWED: from pyVmomi import vim # NOT ALLOWED: from pyVmomi import vim.host _globals = globals() -for name in pyVmomi.VmomiSupport._topLevelNames: - upperCaseName = pyVmomi.VmomiSupport.Capitalize(name) - obj = pyVmomi.VmomiSupport.LazyModule(name) - _globals[name] = obj - if pyVmomi.VmomiSupport._allowCapitalizedNames: - _globals[upperCaseName] = obj - if not hasattr(pyVmomi.VmomiSupport.types, name): - setattr(pyVmomi.VmomiSupport.types, name, obj) - if pyVmomi.VmomiSupport._allowCapitalizedNames: - setattr(pyVmomi.VmomiSupport.types, upperCaseName, obj) +for name in VmomiSupport._topLevelNames: + upperCaseName = VmomiSupport.Capitalize(name) + obj = VmomiSupport.LazyModule(name) + _globals[name] = obj + if _allowCapitalizedNames: + _globals[upperCaseName] = obj + if not hasattr(VmomiSupport.types, name): + setattr(VmomiSupport.types, name, obj) + if _allowCapitalizedNames: + setattr(VmomiSupport.types, upperCaseName, obj) del _globals + + +def init(): + _assert_not_initialized() + Feature._init() + global _initialized + _initialized = True diff --git a/pyVmomi/_typeinfos.py b/pyVmomi/_typeinfos.py new file mode 100644 index 000000000..499a5780e --- /dev/null +++ b/pyVmomi/_typeinfos.py @@ -0,0 +1,23 @@ +# ********************************************************** +# Copyright (c) 2005-2022 VMware, Inc. +# ********************************************************** + +import sys + +typeinfos = [ + 'core', + 'eam', + 'pbm', + 'query', + 'sms', + 'vim', +] + +# Deprecated +# VmomiJSONEncoder was originally part of VmomiSupport and not a separate module. +# This insertion into VmomiSupport is for backwards compatibility. +from .VmomiJSONEncoder import VmomiJSONEncoder +from .VmomiJSONEncoder import templateOf +VmomiSupport = sys.modules['pyVmomi.VmomiSupport'] +setattr(VmomiSupport, 'VmomiJSONEncoder', VmomiJSONEncoder) +setattr(VmomiSupport, 'templateOf', templateOf) diff --git a/pyVmomi/pyVmomiSettings.py b/pyVmomi/pyVmomiSettings.py index 91d4f179a..041294dde 100644 --- a/pyVmomi/pyVmomiSettings.py +++ b/pyVmomi/pyVmomiSettings.py @@ -1,17 +1,7 @@ -# VMware vSphere Python SDK -# Copyright (c) 2008-2015 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. +# ********************************************************** +# Copyright (c) 2008-2022 VMware, Inc. +# ********************************************************** allowGetSet = False allowCapitalizedNames = False +binaryIsBytearray = True diff --git a/requirements.txt b/requirements.txt index 07d0f3c0b..bb2662d24 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1 @@ -requests>=2.3.0 six>=1.7.3 diff --git a/setup.cfg b/setup.cfg index 30dff432b..cf4b5e5a6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,4 +2,4 @@ universal=1 [bdist_rpm] -requires=python-six,python-requests +requires=python-six diff --git a/setup.py b/setup.py index e7be1a0f1..8723b368f 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ def read(fname): setup( name='pyvmomi', - version='8.0.0', + version='8.0.0.1', description='VMware vSphere Python SDK', # NOTE: pypi prefers the use of RST to render docs long_description=read('README.rst'),