1
0
mirror of https://github.com/vmware/vsphere-automation-sdk-python.git synced 2024-11-25 18:59:59 -05:00

Merge pull request #96 from tianhao64/master

Add tests to make sure vsphere client stubs are present
This commit is contained in:
Tianhao He 2018-09-21 13:58:00 -07:00 committed by GitHub
commit 2939cc304f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 190 additions and 49 deletions

View File

@ -6,6 +6,7 @@ python:
install: install:
- pip install -r requirements.txt --extra-index-url file://$PWD/lib --upgrade --ignore-installed six - pip install -r requirements.txt --extra-index-url file://$PWD/lib --upgrade --ignore-installed six
- pip install -r test-requirements.txt - pip install -r test-requirements.txt
- pip install pycodestyle
# command to run tests # command to run tests
script: pycodestyle samples/*.py script: pycodestyle samples tests
script: pytest

View File

@ -48,7 +48,6 @@ class BackupSchedule(object):
self._schedule_id = 'test_schedule' self._schedule_id = 'test_schedule'
def setup(self): def setup(self):
parser = sample_cli.build_arg_parser() parser = sample_cli.build_arg_parser()
@ -154,5 +153,6 @@ def main():
schedule.setup() schedule.setup()
schedule.run() schedule.run()
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -15,7 +15,7 @@ __author__ = 'VMware, Inc.'
__copyright__ = 'Copyright 2013, 2016, 2017 VMware, Inc. All rights reserved.' __copyright__ = 'Copyright 2013, 2016, 2017 VMware, Inc. All rights reserved.'
#Standard library imports. # Standard library imports.
import six.moves.http_client import six.moves.http_client
import re import re
from six import PY3 from six import PY3
@ -33,7 +33,7 @@ from pyVmomi import ThumbprintMismatchException
from uuid import uuid4 from uuid import uuid4
from io import BytesIO from io import BytesIO
from six.moves.urllib.parse import urlparse from six.moves.urllib.parse import urlparse
#Third-party imports. # Third-party imports.
from lxml import etree from lxml import etree
from OpenSSL import crypto from OpenSSL import crypto
import ssl import ssl
@ -42,6 +42,7 @@ UTF_8 = 'utf-8'
SHA256 = 'sha256' SHA256 = 'sha256'
SHA512 = 'sha512' SHA512 = 'sha512'
def _extract_certificate(cert): def _extract_certificate(cert):
''' '''
Extract DER certificate/private key from DER/base64-ed DER/PEM string. Extract DER certificate/private key from DER/base64-ed DER/PEM string.
@ -72,6 +73,7 @@ class SoapException(Exception):
''' '''
Exception raised in case of STS request failure. Exception raised in case of STS request failure.
''' '''
def __init__(self, soap_msg, fault_code, fault_string): def __init__(self, soap_msg, fault_code, fault_string):
''' '''
Initializer for SoapException. Initializer for SoapException.
@ -104,6 +106,7 @@ class SSOHTTPSConnection(six.moves.http_client.HTTPSConnection):
''' '''
An HTTPS class that verifies server's certificate on connect. An HTTPS class that verifies server's certificate on connect.
''' '''
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
''' '''
Initializer. See httplib.HTTPSConnection for other arguments Initializer. See httplib.HTTPSConnection for other arguments
@ -407,7 +410,8 @@ class SsoAuthenticator(object):
@rtype: C{str} @rtype: C{str}
@return: The SAML assertion. @return: The SAML assertion.
''' '''
import sspi, win32api import sspi
import win32api
spn = "sts/%s.com" % win32api.GetDomainName() spn = "sts/%s.com" % win32api.GetDomainName()
sspiclient = sspi.ClientAuth("Kerberos", targetspn=spn) sspiclient = sspi.ClientAuth("Kerberos", targetspn=spn)
in_buf = None in_buf = None
@ -467,7 +471,8 @@ class SsoAuthenticator(object):
@rtype: C{str} @rtype: C{str}
@return: The SAML assertion in Unicode. @return: The SAML assertion in Unicode.
''' '''
import kerberos, platform import kerberos
import platform
service = 'host@%s' % platform.node() service = 'host@%s' % platform.node()
_, context = kerberos.authGSSClientInit(service, 0) _, context = kerberos.authGSSClientInit(service, 0)
challenge = '' challenge = ''
@ -533,7 +538,7 @@ class SsoAuthenticator(object):
token_duration, delegatable, renewable) token_duration, delegatable, renewable)
else: else:
raise Exception("Currently, not supported on this platform") raise Exception("Currently, not supported on this platform")
## TODO Remove this exception once SSO supports validation of tickets # TODO Remove this exception once SSO supports validation of tickets
# generated against host machines # generated against host machines
# saml_token = self._get_bearer_saml_assertion_lin(request_duration, token_duration, delegatable) # saml_token = self._get_bearer_saml_assertion_lin(request_duration, token_duration, delegatable)
return saml_token return saml_token
@ -647,13 +652,14 @@ class SsoAuthenticator(object):
{'saml2': "urn:oasis:names:tc:SAML:2.0:assertion"}), {'saml2': "urn:oasis:names:tc:SAML:2.0:assertion"}),
pretty_print=False).decode(UTF_8) pretty_print=False).decode(UTF_8)
class SecurityTokenRequest(object): class SecurityTokenRequest(object):
''' '''
SecurityTokenRequest class handles the serialization of request to the STS SecurityTokenRequest class handles the serialization of request to the STS
for a SAML token. for a SAML token.
''' '''
#pylint: disable=R0902 # pylint: disable=R0902
def __init__(self, def __init__(self,
username=None, username=None,
password=None, password=None,
@ -699,8 +705,7 @@ class SecurityTokenRequest(object):
self._expires = time.strftime(TIME_FORMAT, self._expires = time.strftime(TIME_FORMAT,
time.gmtime(current + token_duration)) time.gmtime(current + token_duration))
self._request_expires = time.strftime(TIME_FORMAT, self._request_expires = time.strftime(TIME_FORMAT,
time.gmtime(current + time.gmtime(current + request_duration))
request_duration))
self._timestamp = TIMESTAMP_TEMPLATE % self.__dict__ self._timestamp = TIMESTAMP_TEMPLATE % self.__dict__
self._username = escape(username) if username else username self._username = escape(username) if username else username
self._password = escape(password) if password else password self._password = escape(password) if password else password
@ -715,8 +720,8 @@ class SecurityTokenRequest(object):
self._public_key = None self._public_key = None
if gss_binary_token: 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 # The following are populated later. Set to None here to keep in-line
#with PEP8. # with PEP8.
self._binary_security_token = None self._binary_security_token = None
self._hok_token = hok_token self._hok_token = hok_token
self._key_type = None self._key_type = None
@ -730,7 +735,7 @@ class SecurityTokenRequest(object):
self._xml = None self._xml = None
self._request_digest = None self._request_digest = None
#These will only be populated if requesting an HoK token. # These will only be populated if requesting an HoK token.
if self._private_key_file: if self._private_key_file:
with open(self._private_key_file) as fp: with open(self._private_key_file) as fp:
self._private_key = fp.read() self._private_key = fp.read()
@ -942,15 +947,17 @@ def _load_private_key(der_key):
for key_type in ('PRIVATE KEY', 'RSA PRIVATE KEY'): for key_type in ('PRIVATE KEY', 'RSA PRIVATE KEY'):
try: try:
return crypto.load_privatekey(crypto.FILETYPE_PEM, return crypto.load_privatekey(crypto.FILETYPE_PEM,
'-----BEGIN ' + key_type + '-----\n' + '-----BEGIN {}-----\n{}-----END {}-----\n'.format(
base64.encodestring(der_key).decode(UTF_8) + key_type,
'-----END ' + key_type + '-----\n', base64.encodestring(der_key).decode(UTF_8),
key_type),
b'') b'')
except (crypto.Error, ValueError): except (crypto.Error, ValueError):
pass pass
# We could try 'ENCRYPTED PRIVATE KEY' here - but we do not know passphrase. # We could try 'ENCRYPTED PRIVATE KEY' here - but we do not know passphrase.
raise raise
def _sign(private_key, data, digest=SHA256): def _sign(private_key, data, digest=SHA256):
''' '''
An internal helper method to sign the 'data' with the 'private_key'. An internal helper method to sign the 'data' with the 'private_key'.
@ -972,6 +979,7 @@ def _sign(private_key, data, digest=SHA256):
pkey = _load_private_key(_extract_certificate(private_key)) pkey = _load_private_key(_extract_certificate(private_key))
return base64.b64encode(crypto.sign(pkey, data, digest)) return base64.b64encode(crypto.sign(pkey, data, digest))
def _canonicalize(xml_string): def _canonicalize(xml_string):
''' '''
Given an xml string, canonicalize the string per Given an xml string, canonicalize the string per
@ -989,6 +997,7 @@ def _canonicalize(xml_string):
tree.write_c14n(string, exclusive=True, with_comments=False) tree.write_c14n(string, exclusive=True, with_comments=False)
return string.getvalue().decode(UTF_8) return string.getvalue().decode(UTF_8)
def _extract_element(xml, element_name, namespace): def _extract_element(xml, element_name, namespace):
''' '''
An internal method provided to extract an element from the given XML. An internal method provided to extract an element from the given XML.
@ -1042,9 +1051,9 @@ def _make_hash_sha512(data):
TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.987Z" TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.987Z"
#The SAML token requests usually contain an xmldsig which guarantees that the # The SAML token requests usually contain an xmldsig which guarantees that the
#message hasn't been tampered with during the transport. The following # message hasn't been tampered with during the transport. The following
#SIGNED_INFO_TEMPLATE is used to construct the signedinfo part of the signature. # SIGNED_INFO_TEMPLATE is used to construct the signedinfo part of the signature.
SIGNED_INFO_TEMPLATE = """\ SIGNED_INFO_TEMPLATE = """\
<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/> <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
@ -1066,11 +1075,11 @@ SIGNED_INFO_TEMPLATE = """\
</ds:SignedInfo> </ds:SignedInfo>
""" """
#The following template is used as the container for signed info in WS-Trust # The following template is used as the container for signed info in WS-Trust
#SOAP requests signed with the SAML token. It contains the digest of the # SOAP requests signed with the SAML token. It contains the digest of the
#signed info, signed with the private key of the Solution user and contains a # signed info, signed with the private key of the Solution user and contains a
#reference to the actual SAML token which contains the solution user's public # reference to the actual SAML token which contains the solution user's public
#key. # key.
REQUEST_SIGNATURE_TEMPLATE = """\ REQUEST_SIGNATURE_TEMPLATE = """\
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
%(_signed_info)s %(_signed_info)s
@ -1084,9 +1093,9 @@ REQUEST_SIGNATURE_TEMPLATE = """\
</ds:KeyInfo> </ds:KeyInfo>
</ds:Signature>""" </ds:Signature>"""
#The following template is used as a signed info container for the actual SAML # The following template is used as a signed info container for the actual SAML
#token requests requesting a SAML token. It contains the digest of the signed # token requests requesting a SAML token. It contains the digest of the signed
#info signed with the Service User's private key. # info signed with the Service User's private key.
SIGNATURE_TEMPLATE = """\ SIGNATURE_TEMPLATE = """\
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="%(_signature_id)s"> <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="%(_signature_id)s">
%(_signed_info)s %(_signed_info)s
@ -1098,7 +1107,7 @@ SIGNATURE_TEMPLATE = """\
</ds:KeyInfo> </ds:KeyInfo>
</ds:Signature>""" </ds:Signature>"""
#The following template is used to construct the token requests to the STS. # The following template is used to construct the token requests to the STS.
REQUEST_TEMPLATE = """\ REQUEST_TEMPLATE = """\
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header> <SOAP-ENV:Header>
@ -1128,7 +1137,7 @@ REQUEST_TEMPLATE = """\
</SOAP-ENV:Body> </SOAP-ENV:Body>
</SOAP-ENV:Envelope>""" </SOAP-ENV:Envelope>"""
#The following template is used to construct the token-by-token requests to the STS. # The following template is used to construct the token-by-token requests to the STS.
REQUEST_TEMPLATE_TOKEN_BY_TOKEN = """\ REQUEST_TEMPLATE_TOKEN_BY_TOKEN = """\
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header> <SOAP-ENV:Header>
@ -1185,8 +1194,8 @@ GSS_REQUEST_TEMPLATE = """\
</SOAP-ENV:Body> </SOAP-ENV:Body>
</SOAP-ENV:Envelope>""" </SOAP-ENV:Envelope>"""
#Template container for the service user's public key when requesting an HoK # Template container for the service user's public key when requesting an HoK
#token. # token.
BINARY_SECURITY_TOKEN_TEMPLATE = """\ BINARY_SECURITY_TOKEN_TEMPLATE = """\
<ns2:BinarySecurityToken xmlns:ns1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" <ns2:BinarySecurityToken xmlns:ns1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:ns2="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:ns2="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
@ -1195,19 +1204,19 @@ BINARY_SECURITY_TOKEN_TEMPLATE = """\
ns1:Id="%(_security_token_id)s">%(_binary_security_token)s</ns2:BinarySecurityToken> ns1:Id="%(_security_token_id)s">%(_binary_security_token)s</ns2:BinarySecurityToken>
""" """
#Template container for user's credentials when requesting a bearer token. # Template container for user's credentials when requesting a bearer token.
USERNAME_TOKEN_TEMPLATE = """\ USERNAME_TOKEN_TEMPLATE = """\
<ns2:UsernameToken xmlns:ns2="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"> <ns2:UsernameToken xmlns:ns2="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<ns2:Username>%(_username)s</ns2:Username> <ns2:Username>%(_username)s</ns2:Username>
<ns2:Password>%(_password)s</ns2:Password> <ns2:Password>%(_password)s</ns2:Password>
</ns2:UsernameToken>""" </ns2:UsernameToken>"""
#Template containing the anchor to the signature. # Template containing the anchor to the signature.
USE_KEY_TEMPLATE = """\ USE_KEY_TEMPLATE = """\
<UseKey Sig="%(_signature_id)s"/>""" <UseKey Sig="%(_signature_id)s"/>"""
#The follwoing template is used to create a timestamp for the various messages. # The follwoing template is used to create a timestamp for the various messages.
#The timestamp is used to indicate the duration of the request itself. # The timestamp is used to indicate the duration of the request itself.
TIMESTAMP_TEMPLATE = """\ TIMESTAMP_TEMPLATE = """\
<ns3:Timestamp xmlns:ns3="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" ns3:Id="%(_timestamp_id)s"> <ns3:Timestamp xmlns:ns3="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" ns3:Id="%(_timestamp_id)s">
<ns3:Created>%(_created)s</ns3:Created><ns3:Expires>%(_request_expires)s</ns3:Expires></ns3:Timestamp>""" <ns3:Created>%(_created)s</ns3:Created><ns3:Expires>%(_request_expires)s</ns3:Expires></ns3:Timestamp>"""

View File

@ -19,7 +19,7 @@ from samples.vsphere.common.vim.inventory import get_datastore_mo
from samples.vsphere.common.vim import datastore_file from samples.vsphere.common.vim import datastore_file
datastore_path_regex = re.compile('\[(.+)\]\s?(.*)') datastore_path_regex = re.compile(br'\[(.+)\]\s?(.*)')
def parse_datastore_path(datastore_path): def parse_datastore_path(datastore_path):

View File

@ -128,5 +128,6 @@ def main():
log_forwarding.setup() log_forwarding.setup()
log_forwarding.run() log_forwarding.run()
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -25,6 +25,7 @@ from samples.vsphere.common.ssl_helper import get_unverified_session
from samples.vsphere.common import sample_cli from samples.vsphere.common import sample_cli
from samples.vsphere.common import sample_util from samples.vsphere.common import sample_util
class ListServices(object): class ListServices(object):
""" """
Demonstrates the details of vCenter Services Demonstrates the details of vCenter Services
@ -35,7 +36,6 @@ class ListServices(object):
- vCenter Server - vCenter Server
""" """
def __init__(self): def __init__(self):
# Create argument parser for standard inputs: # Create argument parser for standard inputs:
# server, username, password and skipverification # server, username, password and skipverification
@ -51,10 +51,11 @@ class ListServices(object):
username=args.username, username=args.username,
password=args.password, password=args.password,
session=session) session=session)
def run(self): def run(self):
services_list = self.client.vcenter.services.Service.list_details() services_list = self.client.vcenter.services.Service.list_details()
table = [] table = []
for key,value in services_list.items(): for key, value in services_list.items():
row = [key, row = [key,
value.name_key, value.name_key,
value.health, value.health,
@ -62,11 +63,13 @@ class ListServices(object):
value.startup_type] value.startup_type]
table.append(row) table.append(row)
headers = ["Service Name", "Service Name Key", "Service Health", "Service Status", "Service Startup Type"] headers = ["Service Name", "Service Name Key", "Service Health", "Service Status", "Service Startup Type"]
print(tabulate(table,headers)) print(tabulate(table, headers))
def main(): def main():
list_services = ListServices() list_services = ListServices()
list_services.run() list_services.run()
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -62,6 +62,7 @@ def setup(context=None):
password=password, password=password,
session=session) session=session)
def run(): def run():
global vm global vm
vm = get_vm(client, vm_name) vm = get_vm(client, vm_name)

View File

@ -63,6 +63,7 @@ def setup(context=None):
password=password, password=password,
session=session) session=session)
def run(): def run():
global vm global vm
vm = get_vm(client, vm_name) vm = get_vm(client, vm_name)

View File

@ -58,6 +58,7 @@ def setup(context=None):
password=password, password=password,
session=session) session=session)
def run(): def run():
global vm global vm
vm = get_vm(client, vm_name) vm = get_vm(client, vm_name)

View File

@ -59,6 +59,7 @@ def setup(context=None):
password=password, password=password,
session=session) session=session)
def run(): def run():
global vm global vm
vm = get_vm(client, vm_name) vm = get_vm(client, vm_name)

View File

@ -59,6 +59,7 @@ def setup(context=None):
password=password, password=password,
session=session) session=session)
def run(): def run():
# * Floppy images must be pre-existing. This API does not expose # * Floppy images must be pre-existing. This API does not expose
# a way to create new floppy images. # a way to create new floppy images.

View File

@ -58,6 +58,7 @@ def setup(context=None):
password=password, password=password,
session=session) session=session)
def run(): def run():
global vm global vm
vm = get_vm(client, vm_name) vm = get_vm(client, vm_name)

View File

@ -1,2 +1,2 @@
[pycodestyle] [pycodestyle]
ignore = E402, E501, E122, E126, E127, E128, E129, E131 ignore = E402, E501, E122, E126, E127, E128, E129, E131, W503, W504

View File

@ -1 +1,3 @@
testtools>=0.9.34 pytest
pycodestyle

View File

@ -0,0 +1,119 @@
#!/usr/bin/env python
"""
* *******************************************************
* Copyright (c) VMware, Inc. 2018. All Rights Reserved.
* SPDX-License-Identifier: MIT
* *******************************************************
*
* DISCLAIMER. THIS PROGRAM IS PROVIDED TO YOU "AS IS" WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, WHETHER ORAL OR WRITTEN,
* EXPRESS OR IMPLIED. THE AUTHOR SPECIFICALLY DISCLAIMS ANY IMPLIED
* WARRANTIES OR CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY,
* NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE.
"""
__author__ = 'VMware, Inc.'
import requests
from vmware.vapi.bindings.stub import ApiClient, StubFactoryBase
from vmware.vapi.lib.connect import get_requests_connector
from vmware.vapi.stdlib.client.factories import StubConfigurationFactory
from vmware.vapi.vsphere.client import StubFactory
stub_config = StubConfigurationFactory.new_std_configuration(
get_requests_connector(session=requests.session(), url='https://localhost/vapi'))
stub_factory = StubFactory(stub_config)
client = ApiClient(stub_factory)
def test_vcenter_client():
assert hasattr(client, 'vcenter')
assert isinstance(client.vcenter, StubFactoryBase)
def test_cluster_client():
assert hasattr(client.vcenter, 'Cluster')
def test_datacenter_client():
assert hasattr(client.vcenter, 'Datacenter')
def test_datastore_client():
assert hasattr(client.vcenter, 'Datastore')
def test_deployment_client():
assert hasattr(client.vcenter, 'Deployment')
def test_configuration_client():
assert hasattr(client.content, 'Configuration')
def test_appliance_client():
assert hasattr(client, 'appliance')
assert isinstance(client.appliance, StubFactoryBase)
def test_content_client():
assert hasattr(client, 'content')
assert isinstance(client.content, StubFactoryBase)
def test_tagging_client():
assert hasattr(client, 'tagging')
assert isinstance(client.tagging, StubFactoryBase)
def test_ovf_client():
assert hasattr(client.vcenter, 'ovf')
assert isinstance(client.vcenter.ovf, StubFactoryBase)
def test_hvc_client():
assert hasattr(client.vcenter, 'hvc')
assert isinstance(client.vcenter.hvc, StubFactoryBase)
def test_inventory_client():
assert hasattr(client.vcenter, 'inventory')
assert isinstance(client.vcenter.inventory, StubFactoryBase)
def test_iso_client():
assert hasattr(client.vcenter, 'iso')
assert isinstance(client.vcenter.iso, StubFactoryBase)
def test_ovf_client():
assert hasattr(client.vcenter, 'ovf')
assert isinstance(client.vcenter.ovf, StubFactoryBase)
def test_vm_template_client():
assert hasattr(client.vcenter, 'vm_template')
assert isinstance(client.vcenter.vm_template, StubFactoryBase)
def test_appliance_update_client():
assert hasattr(client.appliance, 'recovery')
assert isinstance(client.appliance.recovery, StubFactoryBase)
def test_appliance_vmon_client():
assert hasattr(client.appliance, 'vmon')
assert isinstance(client.appliance.vmon, StubFactoryBase)
def test_compute_policy_client():
assert hasattr(client.vcenter, 'compute')
assert isinstance(client.vcenter.compute, StubFactoryBase)
def test_vm_compute_policy_client():
assert hasattr(client.vcenter.vm, 'compute')
assert isinstance(client.vcenter.vm.compute, StubFactoryBase)