diff --git a/.travis.yml b/.travis.yml
index 470bb71..3abf60e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,10 +6,10 @@ python:
before_install:
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi
+ - pip install -r requirements.txt
+ - pip install -r test-requirements.txt
install:
- - python setup.py bdist_egg
- - pip install -e file://$TRAVIS_BUILD_DIR
+ - python setup.py -q install
-script:
- nosetests
+script: python setup.py test
diff --git a/pyVim/connect.py b/pyVim/connect.py
index 958783a..5105d0a 100644
--- a/pyVim/connect.py
+++ b/pyVim/connect.py
@@ -23,7 +23,7 @@ Connect to a VMOMI ServiceInstance.
Detailed description (for [e]pydoc goes here).
"""
-
+from six import reraise
import sys
import threading
import thread
@@ -313,7 +313,13 @@ def __Login(host, port, user, pwd, service, adapter, version, path,
except vmodl.MethodFault:
raise
except Exception, e:
- raise vim.fault.HostConnectFault(msg=str(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()
+ reraise(vim.fault.HostConnectFault(msg=str(e)), None, traceback)
# Get a ticket if we're connecting to localhost and password is not specified
if host == 'localhost' and not pwd:
diff --git a/pyVmomi/SoapAdapter.py b/pyVmomi/SoapAdapter.py
index 025ec2e..8914bcd 100644
--- a/pyVmomi/SoapAdapter.py
+++ b/pyVmomi/SoapAdapter.py
@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import httplib
+from six.moves import http_client
import sys
import os
import time
@@ -854,7 +854,9 @@ class SoapStubAdapterBase(StubAdapterBase):
## 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(httplib.HTTPConnection):
+# NOTE (hartsock): rewrite this class as a wrapper, see HTTPSConnectionWrapper
+# below for a guide.
+class UnixSocketConnection(http_client.HTTPConnection):
# The HTTPConnection ctor expects a single argument, which it interprets
# as the host to connect to; for UnixSocketConnection, we instead interpret
# the parameter as the filesystem path of the Unix domain socket.
@@ -862,7 +864,7 @@ class UnixSocketConnection(httplib.HTTPConnection):
# 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.
- httplib.HTTPConnection.__init__(self, '')
+ http_client.HTTPConnection.__init__(self, '')
self.path = path
def connect(self):
@@ -884,7 +886,7 @@ try:
'''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, httplib.HTTPSConnection):
+ if thumbprint and isinstance(connection, http_client.HTTPSConnection):
if not connection.sock:
connection.connect()
derCert = connection.sock.getpeercert(True)
@@ -903,21 +905,28 @@ except ImportError:
SSL_THUMBPRINTS_SUPPORTED = False
def _VerifyThumbprint(thumbprint, connection):
- if thumbprint and isinstance(connection, httplib.HTTPSConnection):
+ 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 httplib.FakeSocket(rawSocket, wrappedSocket)
+ return http_client.FakeSocket(rawSocket, wrappedSocket)
-## Internal version of https connection
+## https connection wrapper
#
+# NOTE (hartsock): do not override core library types or implementations
+# directly because this makes brittle code that is too easy to break and
+# closely tied to implementation details we do not control. Instead, wrap
+# the core object to introduce additional behaviors.
+#
+# Purpose:
# Support ssl.wrap_socket params which are missing from httplib
# HTTPSConnection (e.g. ca_certs)
# Note: Only works iff the ssl params are passing in as kwargs
-class _HTTPSConnection(httplib.HTTPSConnection):
+class HTTPSConnectionWrapper(object):
def __init__(self, *args, **kwargs):
+ wrapped = http_client.HTTPSConnection(*args, **kwargs)
# Extract ssl.wrap_socket param unknown to httplib.HTTPConnection,
# and push back the params in connect()
self._sslArgs = {}
@@ -927,15 +936,14 @@ class _HTTPSConnection(httplib.HTTPSConnection):
"ciphers"]:
if key in tmpKwargs:
self._sslArgs[key] = tmpKwargs.pop(key)
- httplib.HTTPSConnection.__init__(self, *args, **tmpKwargs)
+ self._wrapped = wrapped
## Override connect to allow us to pass in additional ssl paramters to
# ssl.wrap_socket (e.g. cert_reqs, ca_certs for ca cert verification)
- def connect(self):
- if len(self._sslArgs) == 0:
+ def connect(self, wrapped):
+ if len(self._sslArgs) == 0 or hasattr(self, '_baseclass'):
# No override
- httplib.HTTPSConnection.connect(self)
- return
+ return wrapped.connect
# Big hack. We have to copy and paste the httplib connect fn for
# each python version in order to handle extra ssl paramters. Yuk!
@@ -943,18 +951,16 @@ class _HTTPSConnection(httplib.HTTPSConnection):
# Python 2.7
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)
+ if wrapped._tunnel_host:
+ wrapped.sock = sock
+ wrapped._tunnel()
+ wrapped.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, **self._sslArgs)
elif hasattr(self, "timeout"):
# Python 2.6
sock = socket.create_connection((self.host, self.port), self.timeout)
- self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, **self._sslArgs)
- else:
- # Unknown python version. Do nothing
- httplib.HTTPSConnection.connect(self)
- return
+ wrapped.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, **self._sslArgs)
+
+ return wrapped.connect
# TODO: Additional verification of peer cert if needed
#cert_reqs = self._sslArgs.get("cert_reqs", ssl.CERT_NONE)
@@ -965,6 +971,11 @@ class _HTTPSConnection(httplib.HTTPSConnection):
# dercert = self.sock.getpeercert(False)
# # pemcert = ssl.DER_cert_to_PEM_cert(dercert)
+ def __getattr__(self, item):
+ if item == 'connect':
+ return self.connect(self._wrapped)
+ return getattr(self._wrapped, item)
+
## Stand-in for the HTTPSConnection class that will connect to a proxy and
## issue a CONNECT command to start an SSL tunnel.
class SSLTunnelConnection(object):
@@ -985,13 +996,13 @@ class SSLTunnelConnection(object):
for arg in kwargs.keys():
if arg not in ("port", "strict", "timeout", "source_address"):
del kwargs[arg]
- tunnel = httplib.HTTPConnection(path, **kwargs)
+ tunnel = http_client.HTTPConnection(path, **kwargs)
tunnel.request('CONNECT', self.proxyPath)
resp = tunnel.getresponse()
tunnelSocket = resp.fp
if resp.status != 200:
- raise httplib.HTTPException("%d %s" % (resp.status, resp.reason))
- retval = httplib.HTTPSConnection(path)
+ raise http_client.HTTPException("%d %s" % (resp.status, resp.reason))
+ retval = http_client.HTTPSConnection(path)
retval.sock = _SocketWrapper(tunnelSocket,
keyfile=key_file, certfile=cert_file)
return retval
@@ -1121,11 +1132,11 @@ class SoapStubAdapter(SoapStubAdapterBase):
# keyword argument as passed in.
if urlpath not in ('', '/'):
path = urlpath
- self.scheme = scheme == "http" and httplib.HTTPConnection \
- or scheme == "https" and _HTTPSConnection
+ self.scheme = scheme == "http" and http_client.HTTPConnection \
+ or scheme == "https" and HTTPSConnectionWrapper
else:
- port, self.scheme = port < 0 and (-port, httplib.HTTPConnection) \
- or (port, _HTTPSConnection)
+ port, self.scheme = port < 0 and (-port, http_client.HTTPConnection) \
+ or (port, HTTPSConnectionWrapper)
if host.find(':') != -1: # is IPv6?
host = '[' + host + ']'
self.host = '%s:%d' % (host, port)
@@ -1141,7 +1152,7 @@ class SoapStubAdapter(SoapStubAdapterBase):
if sslProxyPath:
self.scheme = SSLTunnelConnection(sslProxyPath)
elif httpProxyHost:
- if self.scheme == _HTTPSConnection:
+ if self.scheme == HTTPSConnectionWrapper:
self.scheme = SSLTunnelConnection(self.host)
else:
if url:
@@ -1206,7 +1217,7 @@ class SoapStubAdapter(SoapStubAdapterBase):
try:
conn.request('POST', self.path, req, headers)
resp = conn.getresponse()
- except (socket.error, httplib.HTTPException):
+ except (socket.error, http_client.HTTPException):
# The server is probably sick, drop all of the cached connections.
self.DropConnections()
raise
@@ -1240,7 +1251,7 @@ class SoapStubAdapter(SoapStubAdapterBase):
raise obj # pylint: disable-msg=E0702
else:
conn.close()
- raise httplib.HTTPException("%d %s" % (resp.status, resp.reason))
+ raise http_client.HTTPException("%d %s" % (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.
@@ -1461,7 +1472,7 @@ class SessionOrientedStub(StubAdapterBase):
self._CallLoginMethod()
# Invoke the method
status, obj = self.soapStub.InvokeMethod(mo, info, args, self)
- except (socket.error, httplib.HTTPException, ExpatError):
+ except (socket.error, http_client.HTTPException, ExpatError):
if self.retryDelay and retriesLeft:
time.sleep(self.retryDelay)
retriesLeft -= 1
@@ -1497,7 +1508,7 @@ class SessionOrientedStub(StubAdapterBase):
self._CallLoginMethod()
# Invoke the method
obj = StubAdapterBase.InvokeAccessor(self, mo, info)
- except (socket.error, httplib.HTTPException, ExpatError):
+ except (socket.error, http_client.HTTPException, ExpatError):
if self.retryDelay and retriesLeft:
time.sleep(self.retryDelay)
retriesLeft -= 1
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..07d0f3c
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,2 @@
+requests>=2.3.0
+six>=1.7.3
diff --git a/setup.py b/setup.py
index f472274..5bd3a29 100644
--- a/setup.py
+++ b/setup.py
@@ -16,9 +16,16 @@
from setuptools import setup
import os
+
def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()
+with open('requirements.txt') as f:
+ required = f.read().splitlines()
+
+with open('test-requirements.txt') as f:
+ required_for_tests = f.read().splitlines()
+
setup(
name='pyvmomi',
version='5.5.0_2014.dev',
@@ -27,6 +34,7 @@ setup(
author_email='jhu@vmware.com',
url='https://github.com/vmware/pyvmomi',
packages=['pyVmomi', 'pyVim'],
+ install_requires=required,
license='Apache',
long_description=read('README.md'),
classifiers=[
@@ -38,5 +46,7 @@ setup(
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: System :: Distributed Computing"
],
+ test_suite='tests',
+ tests_require= required_for_tests,
zip_safe=True
)
diff --git a/test-requirements.txt b/test-requirements.txt
new file mode 100644
index 0000000..23f9603
--- /dev/null
+++ b/test-requirements.txt
@@ -0,0 +1,4 @@
+mock
+PyYAML>=3.11
+testtools>=0.9.34
+vcrpy>=1.0.2
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..87fed7b
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,23 @@
+# VMware vSphere Python SDK
+# Copyright (c) 2008-2014 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.
+import os
+
+
+def tests_resource_path(local_path=''):
+ this_file = os.path.dirname(os.path.abspath(__file__))
+ return os.path.join(this_file, local_path)
+
+# Fully qualified path to the fixtures directory underneath this module
+fixtures_path = tests_resource_path('fixtures')
diff --git a/tests/fixtures/basic_connection.yaml b/tests/fixtures/basic_connection.yaml
new file mode 100644
index 0000000..547c05c
--- /dev/null
+++ b/tests/fixtures/basic_connection.yaml
@@ -0,0 +1,234 @@
+interactions:
+- request:
+ body: '
+
+
+
+ <_this type="ServiceInstance">ServiceInstance
+
+ '
+ headers:
+ Accept-Encoding: ['gzip, deflate']
+ Content-Type: [text/xml; charset=UTF-8]
+ Cookie: ['']
+ SOAPAction: ['"urn:vim25/4.1"']
+ method: POST
+ uri: https://vcsa:443/sdk
+ response:
+ body: {string: !!python/unicode "\n\n\ngroup-d1propertyCollectorViewManagerVMware vCenter
+ ServerVMware vCenter Server 5.5.0 build-1623101 (Sim)VMware,
+ Inc.5.5.01623101 (Sim)INTL000linux-x64vpxVirtualCenter5.5E8636946-5510-44E7-B288-20DA0AD9DA38VMware
+ VirtualCenter Server5.0VpxSettingsUserDirectorySessionManagerAuthorizationManagerPerfMgrScheduledTaskManagerAlarmManagerEventManagerTaskManagerExtensionManagerCustomizationSpecManagerCustomFieldsManagerDiagMgrLicenseManagerSearchIndexFileManagervirtualDiskManagerSnmpSystemProvCheckerCompatCheckerOvfManagerIpPoolManagerDVSManagerHostProfileManagerClusterProfileManagerMoComplianceManagerLocalizationManagerStorageResourceManager\n\n"}
+ headers:
+ cache-control: [no-cache]
+ connection: [Keep-Alive]
+ content-length: ['3332']
+ content-type: [text/xml; charset=utf-8]
+ date: ['Mon, 21 Jul 2014 22:31:05 GMT']
+ set-cookie: [vmware_soap_session="52970dd3-2b0f-647b-22b3-44bda6d49983"; Path=/;
+ HttpOnly; Secure;]
+ status: {code: 200, message: OK}
+- request:
+ body: '
+
+
+
+ <_this type="SessionManager">SessionManagermy_usermy_password
+
+ '
+ headers:
+ Accept-Encoding: ['gzip, deflate']
+ Content-Type: [text/xml; charset=UTF-8]
+ Cookie: [vmware_soap_session="52970dd3-2b0f-647b-22b3-44bda6d49983"; Path=/;
+ HttpOnly; Secure;]
+ SOAPAction: ['"urn:vim25/4.1"']
+ method: POST
+ uri: https://vcsa:443/sdk
+ response:
+ body: {string: !!python/unicode "\n\n\n52773cd3-35c6-b40a-17f1-fe664a9f08f3my_userMy User
+ 2014-07-21T22:31:05.480973Z2014-07-21T22:31:05.480973Zenen\n\n"}
+ headers:
+ cache-control: [no-cache]
+ connection: [Keep-Alive]
+ content-length: ['659']
+ content-type: [text/xml; charset=utf-8]
+ date: ['Mon, 21 Jul 2014 22:31:05 GMT']
+ status: {code: 200, message: OK}
+- request:
+ body: '
+
+
+
+ <_this type="ServiceInstance">ServiceInstance
+
+ '
+ headers:
+ Accept-Encoding: ['gzip, deflate']
+ Content-Type: [text/xml; charset=UTF-8]
+ Cookie: [vmware_soap_session="52970dd3-2b0f-647b-22b3-44bda6d49983"; Path=/;
+ HttpOnly; Secure;]
+ SOAPAction: ['"urn:vim25/4.1"']
+ method: POST
+ uri: https://vcsa:443/sdk
+ response:
+ body: {string: !!python/unicode "\n\n\ngroup-d1propertyCollectorViewManagerVMware vCenter
+ ServerVMware vCenter Server 5.5.0 build-1623101 (Sim)VMware,
+ Inc.5.5.01623101 (Sim)INTL000linux-x64vpxVirtualCenter5.5E8636946-5510-44E7-B288-20DA0AD9DA38VMware
+ VirtualCenter Server5.0VpxSettingsUserDirectorySessionManagerAuthorizationManagerPerfMgrScheduledTaskManagerAlarmManagerEventManagerTaskManagerExtensionManagerCustomizationSpecManagerCustomFieldsManagerDiagMgrLicenseManagerSearchIndexFileManagervirtualDiskManagerSnmpSystemProvCheckerCompatCheckerOvfManagerIpPoolManagerDVSManagerHostProfileManagerClusterProfileManagerMoComplianceManagerLocalizationManagerStorageResourceManager\n\n"}
+ headers:
+ cache-control: [no-cache]
+ connection: [Keep-Alive]
+ content-length: ['3332']
+ content-type: [text/xml; charset=utf-8]
+ date: ['Mon, 21 Jul 2014 22:31:05 GMT']
+ status: {code: 200, message: OK}
+- request:
+ body: '
+
+
+
+ <_this type="PropertyCollector">propertyCollectorServiceInstancefalsecontentServiceInstancefalse1
+
+ '
+ headers:
+ Accept-Encoding: ['gzip, deflate']
+ Content-Type: [text/xml; charset=UTF-8]
+ Cookie: [vmware_soap_session="52970dd3-2b0f-647b-22b3-44bda6d49983"; Path=/;
+ HttpOnly; Secure;]
+ SOAPAction: ['"urn:vim25/4.1"']
+ method: POST
+ uri: https://vcsa:443/sdk
+ response:
+ body: {string: !!python/unicode "\n\n\nServiceInstancecontentgroup-d1propertyCollectorViewManagerVMware vCenter
+ ServerVMware vCenter Server 5.5.0 build-1623101 (Sim)VMware,
+ Inc.5.5.01623101 (Sim)INTL000linux-x64vpxVirtualCenter5.5E8636946-5510-44E7-B288-20DA0AD9DA38VMware
+ VirtualCenter Server5.0VpxSettingsUserDirectorySessionManagerAuthorizationManagerPerfMgrScheduledTaskManagerAlarmManagerEventManagerTaskManagerExtensionManagerCustomizationSpecManagerCustomFieldsManagerDiagMgrLicenseManagerSearchIndexFileManagervirtualDiskManagerSnmpSystemProvCheckerCompatCheckerOvfManagerIpPoolManagerDVSManagerHostProfileManagerClusterProfileManagerMoComplianceManagerLocalizationManagerStorageResourceManager\n\n"}
+ headers:
+ cache-control: [no-cache]
+ connection: [Keep-Alive]
+ content-length: ['3472']
+ content-type: [text/xml; charset=utf-8]
+ date: ['Mon, 21 Jul 2014 22:31:05 GMT']
+ status: {code: 200, message: OK}
+- request:
+ body: '
+
+
+
+ <_this type="PropertyCollector">propertyCollectorSessionManagerfalsecurrentSessionSessionManagerfalse1
+
+ '
+ headers:
+ Accept-Encoding: ['gzip, deflate']
+ Content-Type: [text/xml; charset=UTF-8]
+ Cookie: [vmware_soap_session="52970dd3-2b0f-647b-22b3-44bda6d49983"; Path=/;
+ HttpOnly; Secure;]
+ SOAPAction: ['"urn:vim25/4.1"']
+ method: POST
+ uri: https://vcsa:443/sdk
+ response:
+ body: {string: !!python/unicode "\n\n\nSessionManagercurrentSession52773cd3-35c6-b40a-17f1-fe664a9f08f3my_userMy User
+ 2014-07-21T22:31:05.480973Z2014-07-21T22:31:05.480973Zenen\n\n"}
+ headers:
+ cache-control: [no-cache]
+ connection: [Keep-Alive]
+ content-length: ['835']
+ content-type: [text/xml; charset=utf-8]
+ date: ['Mon, 21 Jul 2014 22:31:05 GMT']
+ status: {code: 200, message: OK}
+version: 1
diff --git a/tests/fixtures/basic_connection_bad_password.yaml b/tests/fixtures/basic_connection_bad_password.yaml
new file mode 100644
index 0000000..9417be5
--- /dev/null
+++ b/tests/fixtures/basic_connection_bad_password.yaml
@@ -0,0 +1,89 @@
+interactions:
+- request:
+ body: '
+
+
+
+ <_this type="ServiceInstance">ServiceInstance
+
+ '
+ headers:
+ Accept-Encoding: ['gzip, deflate']
+ Content-Type: [text/xml; charset=UTF-8]
+ Cookie: ['']
+ SOAPAction: ['"urn:vim25/4.1"']
+ method: POST
+ uri: https://vcsa:443/sdk
+ response:
+ body: {string: !!python/unicode "\n\n\ngroup-d1propertyCollectorViewManagerVMware vCenter
+ ServerVMware vCenter Server 5.5.0 build-1750787 (Sim)VMware,
+ Inc.5.5.01750787 (Sim)INTL000linux-x64vpxVirtualCenter5.5EAB4D846-C243-426B-A021-0547644CE59DVMware
+ VirtualCenter Server5.0VpxSettingsUserDirectorySessionManagerAuthorizationManagerPerfMgrScheduledTaskManagerAlarmManagerEventManagerTaskManagerExtensionManagerCustomizationSpecManagerCustomFieldsManagerDiagMgrLicenseManagerSearchIndexFileManagervirtualDiskManagerSnmpSystemProvCheckerCompatCheckerOvfManagerIpPoolManagerDVSManagerHostProfileManagerClusterProfileManagerMoComplianceManagerLocalizationManagerStorageResourceManager\n\n"}
+ headers:
+ cache-control: [no-cache]
+ connection: [Keep-Alive]
+ content-length: ['3332']
+ content-type: [text/xml; charset=utf-8]
+ date: ['Tue, 22 Jul 2014 17:36:32 GMT']
+ set-cookie: [vmware_soap_session="528b8755-46b5-df6a-47fd-89e57d4807c5"; Path=/;
+ HttpOnly; Secure;]
+ status: {code: 200, message: OK}
+- request:
+ body: '
+
+
+
+ <_this type="SessionManager">SessionManagermy_userbad_password
+
+ '
+ headers:
+ Accept-Encoding: ['gzip, deflate']
+ Content-Type: [text/xml; charset=UTF-8]
+ Cookie: [vmware_soap_session="528b8755-46b5-df6a-47fd-89e57d4807c5"; Path=/;
+ HttpOnly; Secure;]
+ SOAPAction: ['"urn:vim25/4.1"']
+ method: POST
+ uri: https://vcsa:443/sdk
+ response:
+ body: {string: !!python/unicode "\n\n\nServerFaultCodeCannot
+ complete login due to an incorrect user name or password.\n\n"}
+ headers:
+ cache-control: [no-cache]
+ connection: [Keep-Alive]
+ content-length: ['585']
+ content-type: [text/xml; charset=utf-8]
+ date: ['Tue, 22 Jul 2014 17:36:37 GMT']
+ status: {code: 500, message: Internal Server Error}
+version: 1
diff --git a/tests/test_connect.py b/tests/test_connect.py
new file mode 100644
index 0000000..bf6dcec
--- /dev/null
+++ b/tests/test_connect.py
@@ -0,0 +1,52 @@
+# VMware vSphere Python SDK
+# Copyright (c) 2008-2014 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 tests import fixtures_path
+import logging
+import unittest
+import vcr
+
+from pyVim import connect
+from pyVmomi import vim
+
+
+class ConnectionTests(unittest.TestCase):
+
+ def setUp(self):
+ logging.basicConfig()
+ vcr_log = logging.getLogger('vcr')
+ vcr_log.setLevel(logging.DEBUG)
+
+ @vcr.use_cassette('basic_connection.yaml',
+ cassette_library_dir=fixtures_path, record_mode='none')
+ def test_basic_connection(self):
+ # see: http://python3porting.com/noconv.html
+ si = connect.Connect(host='vcsa',
+ user='my_user',
+ pwd='my_password')
+ session_id = si.content.sessionManager.currentSession.key
+ # NOTE (hartsock): assertIsNotNone does not work in Python 2.6
+ self.assertTrue(session_id is not None)
+ self.assertEqual('52773cd3-35c6-b40a-17f1-fe664a9f08f3', session_id)
+
+ @vcr.use_cassette('basic_connection_bad_password.yaml',
+ cassette_library_dir=fixtures_path, record_mode='none')
+ def test_basic_connection_bad_password(self):
+ def should_fail():
+ connect.Connect(host='vcsa',
+ user='my_user',
+ pwd='bad_password')
+
+ self.assertRaises(vim.fault.InvalidLogin, should_fail)