VMware: not ssl.SSLContext if validate_certs false (#57185)
Python < 2.7.9 does not have the ssl.SSLContext attribute. ssl.SSLContext is only required when we want to validate the SSL connection. If `validate_certs` is false, we don't initialize the `ssl_context` variable. Add unit-test coverage and a little refactoring: - avoid the use of `mocker`, when we can push `monkeypatch` which is `pytest`'s default. - use `mock.Mocker()` when possible closes: #57072
This commit is contained in:
parent
1f38a12057
commit
3ea8e0a144
3 changed files with 121 additions and 63 deletions
|
@ -0,0 +1,2 @@
|
|||
bugfixes:
|
||||
- vmware - Ensure we can use the modules with Python < 2.7.9 or RHEL/CentOS < 7.4, this as soon as ``validate_certs`` is disabled.
|
|
@ -526,12 +526,17 @@ def connect_to_api(module, disconnect_atexit=True, return_si=False):
|
|||
if validate_certs and not hasattr(ssl, 'SSLContext'):
|
||||
module.fail_json(msg='pyVim does not support changing verification mode with python < 2.7.9. Either update '
|
||||
'python or use validate_certs=false.')
|
||||
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
||||
if validate_certs:
|
||||
elif validate_certs:
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
||||
ssl_context.verify_mode = ssl.CERT_REQUIRED
|
||||
ssl_context.check_hostname = True
|
||||
ssl_context.load_default_certs()
|
||||
elif hasattr(ssl, 'SSLContext'):
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
ssl_context.check_hostname = False
|
||||
else: # Python < 2.7.9 or RHEL/Centos < 7.4
|
||||
ssl_context = None
|
||||
|
||||
service_instance = None
|
||||
proxy_host = module.params.get('proxy_host')
|
||||
|
|
|
@ -6,12 +6,15 @@
|
|||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
import ssl
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
pyvmomi = pytest.importorskip('pyVmomi')
|
||||
|
||||
from ansible.module_utils.vmware import connect_to_api, PyVmomi
|
||||
from units.compat import mock
|
||||
|
||||
import ansible.module_utils.vmware as vmware_module_utils
|
||||
|
||||
|
||||
test_data = [
|
||||
|
@ -76,61 +79,38 @@ test_ids = [
|
|||
]
|
||||
|
||||
|
||||
class AnsibleModuleExit(Exception):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
|
||||
class ExitJson(AnsibleModuleExit):
|
||||
pass
|
||||
|
||||
|
||||
class FailJson(AnsibleModuleExit):
|
||||
class FailJsonException(BaseException):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fake_ansible_module():
|
||||
return FakeAnsibleModule()
|
||||
ret = mock.Mock()
|
||||
ret.params = test_data[3][0]
|
||||
ret.tmpdir = None
|
||||
ret.fail_json.side_effect = FailJsonException()
|
||||
return ret
|
||||
|
||||
|
||||
class FakeAnsibleModule:
|
||||
def __init__(self):
|
||||
self.params = {}
|
||||
self.tmpdir = None
|
||||
|
||||
def exit_json(self, *args, **kwargs):
|
||||
raise ExitJson(*args, **kwargs)
|
||||
|
||||
def fail_json(self, *args, **kwargs):
|
||||
raise FailJson(*args, **kwargs)
|
||||
def fake_connect_to_api(module, return_si=None):
|
||||
return None, mock.Mock(),
|
||||
|
||||
|
||||
def fake_connect_to_api(module, disconnect_atexit=True, return_si=False):
|
||||
class MyContent():
|
||||
customFieldsManager = None
|
||||
if return_si:
|
||||
return (None, MyContent())
|
||||
return MyContent()
|
||||
testdata = [
|
||||
('HAS_PYVMOMI', 'PyVmomi'),
|
||||
('HAS_REQUESTS', 'requests'),
|
||||
]
|
||||
|
||||
|
||||
def test_pyvmomi_lib_exists(mocker, fake_ansible_module):
|
||||
@pytest.mark.parametrize("key,libname", testdata)
|
||||
def test_lib_loading_failure(monkeypatch, fake_ansible_module, key, libname):
|
||||
""" Test if Pyvmomi is present or not"""
|
||||
mocker.patch('ansible.module_utils.vmware.HAS_PYVMOMI', new=False)
|
||||
with pytest.raises(FailJson) as exec_info:
|
||||
PyVmomi(fake_ansible_module)
|
||||
|
||||
assert 'Failed to import the required Python library (PyVmomi) on' in exec_info.value.kwargs['msg']
|
||||
|
||||
|
||||
def test_requests_lib_exists(mocker, fake_ansible_module):
|
||||
""" Test if requests is present or not"""
|
||||
mocker.patch('ansible.module_utils.vmware.HAS_REQUESTS', new=False)
|
||||
with pytest.raises(FailJson) as exec_info:
|
||||
PyVmomi(fake_ansible_module)
|
||||
|
||||
assert 'Failed to import the required Python library (requests) on' in exec_info.value.kwargs['msg']
|
||||
monkeypatch.setattr(vmware_module_utils, key, False)
|
||||
with pytest.raises(FailJsonException):
|
||||
vmware_module_utils.PyVmomi(fake_ansible_module)
|
||||
error_str = 'Failed to import the required Python library (%s)' % libname
|
||||
assert fake_ansible_module.fail_json.called_once()
|
||||
assert error_str in fake_ansible_module.fail_json.call_args[1]['msg']
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (2, 7), reason="requires python2.7 and greater")
|
||||
|
@ -138,40 +118,111 @@ def test_requests_lib_exists(mocker, fake_ansible_module):
|
|||
def test_required_params(request, params, msg, fake_ansible_module):
|
||||
""" Test if required params are correct or not"""
|
||||
fake_ansible_module.params = params
|
||||
with pytest.raises(FailJson) as exec_info:
|
||||
connect_to_api(fake_ansible_module)
|
||||
assert msg in exec_info.value.kwargs['msg']
|
||||
with pytest.raises(FailJsonException):
|
||||
vmware_module_utils.connect_to_api(fake_ansible_module)
|
||||
assert fake_ansible_module.fail_json.called_once()
|
||||
assert msg in fake_ansible_module.fail_json.call_args[1]['msg']
|
||||
|
||||
|
||||
def test_validate_certs(mocker, fake_ansible_module):
|
||||
def test_validate_certs(monkeypatch, fake_ansible_module):
|
||||
""" Test if SSL is required or not"""
|
||||
fake_ansible_module.params = test_data[3][0]
|
||||
|
||||
mocker.patch('ansible.module_utils.vmware.ssl', new=None)
|
||||
with pytest.raises(FailJson) as exec_info:
|
||||
PyVmomi(fake_ansible_module)
|
||||
monkeypatch.setattr(vmware_module_utils, 'ssl', None)
|
||||
with pytest.raises(FailJsonException):
|
||||
vmware_module_utils.PyVmomi(fake_ansible_module)
|
||||
msg = 'pyVim does not support changing verification mode with python < 2.7.9.' \
|
||||
' Either update python or use validate_certs=false.'
|
||||
assert msg == exec_info.value.kwargs['msg']
|
||||
assert fake_ansible_module.fail_json.called_once()
|
||||
assert msg in fake_ansible_module.fail_json.call_args[1]['msg']
|
||||
|
||||
|
||||
def test_vmdk_disk_path_split(mocker, fake_ansible_module):
|
||||
def test_vmdk_disk_path_split(monkeypatch, fake_ansible_module):
|
||||
""" Test vmdk_disk_path_split function"""
|
||||
fake_ansible_module.params = test_data[0][0]
|
||||
|
||||
mocker.patch('ansible.module_utils.vmware.connect_to_api', new=fake_connect_to_api)
|
||||
pyv = PyVmomi(fake_ansible_module)
|
||||
monkeypatch.setattr(vmware_module_utils, 'connect_to_api', fake_connect_to_api)
|
||||
pyv = vmware_module_utils.PyVmomi(fake_ansible_module)
|
||||
v = pyv.vmdk_disk_path_split('[ds1] VM_0001/VM0001_0.vmdk')
|
||||
assert v == ('ds1', 'VM_0001/VM0001_0.vmdk', 'VM0001_0.vmdk', 'VM_0001')
|
||||
|
||||
|
||||
def test_vmdk_disk_path_split_negative(mocker, fake_ansible_module):
|
||||
def test_vmdk_disk_path_split_negative(monkeypatch, fake_ansible_module):
|
||||
""" Test vmdk_disk_path_split function"""
|
||||
fake_ansible_module.params = test_data[0][0]
|
||||
|
||||
mocker.patch('ansible.module_utils.vmware.connect_to_api', new=fake_connect_to_api)
|
||||
with pytest.raises(FailJson) as exec_info:
|
||||
pyv = PyVmomi(fake_ansible_module)
|
||||
monkeypatch.setattr(vmware_module_utils, 'connect_to_api', fake_connect_to_api)
|
||||
with pytest.raises(FailJsonException):
|
||||
pyv = vmware_module_utils.PyVmomi(fake_ansible_module)
|
||||
pyv.vmdk_disk_path_split('[ds1]')
|
||||
assert fake_ansible_module.fail_json.called_once()
|
||||
assert 'Bad path' in fake_ansible_module.fail_json.call_args[1]['msg']
|
||||
|
||||
assert 'Bad path' in exec_info.value.kwargs['msg']
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (2, 7), reason="requires python2.7 and greater")
|
||||
def test_connect_to_api_validate_certs(monkeypatch, fake_ansible_module):
|
||||
monkeypatch.setattr(vmware_module_utils, 'connect', mock.Mock())
|
||||
|
||||
def MockSSLContext(proto):
|
||||
ssl_context.proto = proto
|
||||
return ssl_context
|
||||
|
||||
# New Python with SSLContext + validate_certs=True
|
||||
vmware_module_utils.connect.reset_mock()
|
||||
ssl_context = mock.Mock()
|
||||
monkeypatch.setattr(vmware_module_utils.ssl, 'SSLContext', MockSSLContext)
|
||||
fake_ansible_module.params['validate_certs'] = True
|
||||
vmware_module_utils.connect_to_api(fake_ansible_module)
|
||||
assert ssl_context.proto == ssl.PROTOCOL_SSLv23
|
||||
assert ssl_context.verify_mode == ssl.CERT_REQUIRED
|
||||
assert ssl_context.check_hostname is True
|
||||
vmware_module_utils.connect.SmartConnect.assert_called_once_with(
|
||||
host='esxi1',
|
||||
port=443,
|
||||
pwd='Esxi@123$%',
|
||||
user='Administrator@vsphere.local',
|
||||
sslContext=ssl_context)
|
||||
|
||||
# New Python with SSLContext + validate_certs=False
|
||||
vmware_module_utils.connect.reset_mock()
|
||||
ssl_context = mock.Mock()
|
||||
monkeypatch.setattr(vmware_module_utils.ssl, 'SSLContext', MockSSLContext)
|
||||
fake_ansible_module.params['validate_certs'] = False
|
||||
vmware_module_utils.connect_to_api(fake_ansible_module)
|
||||
assert ssl_context.proto == ssl.PROTOCOL_SSLv23
|
||||
assert ssl_context.verify_mode == ssl.CERT_NONE
|
||||
assert ssl_context.check_hostname is False
|
||||
vmware_module_utils.connect.SmartConnect.assert_called_once_with(
|
||||
host='esxi1',
|
||||
port=443,
|
||||
pwd='Esxi@123$%',
|
||||
user='Administrator@vsphere.local',
|
||||
sslContext=ssl_context)
|
||||
|
||||
# Old Python with no SSLContext + validate_certs=True
|
||||
vmware_module_utils.connect.reset_mock()
|
||||
ssl_context = mock.Mock()
|
||||
ssl_context.proto = None
|
||||
monkeypatch.delattr(vmware_module_utils.ssl, 'SSLContext')
|
||||
fake_ansible_module.params['validate_certs'] = True
|
||||
with pytest.raises(FailJsonException):
|
||||
vmware_module_utils.connect_to_api(fake_ansible_module)
|
||||
assert ssl_context.proto is None
|
||||
fake_ansible_module.fail_json.assert_called_once_with(msg=(
|
||||
'pyVim does not support changing verification mode with python '
|
||||
'< 2.7.9. Either update python or use validate_certs=false.'))
|
||||
assert not vmware_module_utils.connect.SmartConnect.called
|
||||
|
||||
# Old Python with no SSLContext + validate_certs=False
|
||||
vmware_module_utils.connect.reset_mock()
|
||||
ssl_context = mock.Mock()
|
||||
ssl_context.proto = None
|
||||
monkeypatch.delattr(vmware_module_utils.ssl, 'SSLContext', raising=False)
|
||||
fake_ansible_module.params['validate_certs'] = False
|
||||
vmware_module_utils.connect_to_api(fake_ansible_module)
|
||||
assert ssl_context.proto is None
|
||||
vmware_module_utils.connect.SmartConnect.assert_called_once_with(
|
||||
host='esxi1',
|
||||
port=443,
|
||||
pwd='Esxi@123$%',
|
||||
user='Administrator@vsphere.local')
|
||||
|
|
Loading…
Reference in a new issue