1# -*- coding: utf-8 -*- 2# Copyright: (c) 2018, Ansible Project 3# Copyright: (c) 2018, Abhijeet Kasurde <akasurde@redhat.com> 4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 6from __future__ import absolute_import, division, print_function 7__metaclass__ = type 8 9import ssl 10import sys 11import pytest 12 13pyvmomi = pytest.importorskip('pyVmomi') 14 15from ansible_collections.community.vmware.tests.unit.compat import mock 16from ansible_collections.community.vmware.plugins.module_utils.vmware import option_diff 17 18import ansible_collections.community.vmware.plugins.module_utils.vmware as vmware_module_utils 19 20 21test_data = [ 22 ( 23 dict( 24 username='Administrator@vsphere.local', 25 password='Esxi@123$%', 26 hostname=False, 27 validate_certs=False, 28 ), 29 "Hostname parameter is missing. Please specify this parameter in task or" 30 " export environment variable like 'export VMWARE_HOST=ESXI_HOSTNAME'" 31 ), 32 ( 33 dict( 34 username=False, 35 password='Esxi@123$%', 36 hostname='esxi1', 37 validate_certs=False, 38 ), 39 "Username parameter is missing. Please specify this parameter in task or" 40 " export environment variable like 'export VMWARE_USER=ESXI_USERNAME'" 41 ), 42 ( 43 dict( 44 username='Administrator@vsphere.local', 45 password=False, 46 hostname='esxi1', 47 validate_certs=False, 48 ), 49 "Password parameter is missing. Please specify this parameter in task or" 50 " export environment variable like 'export VMWARE_PASSWORD=ESXI_PASSWORD'" 51 ), 52 ( 53 dict( 54 username='Administrator@vsphere.local', 55 password='Esxi@123$%', 56 hostname='esxi1', 57 validate_certs=True, 58 ), 59 "certificate verify failed" 60 ), 61 ( 62 dict( 63 username='Administrator@vsphere.local', 64 password='Esxi@123$%', 65 hostname='esxi1', 66 proxy_host='myproxyserver.com', 67 proxy_port=80, 68 validate_certs=False, 69 ), 70 " [proxy: myproxyserver.com:80]" 71 ), 72] 73 74test_ids = [ 75 'hostname', 76 'username', 77 'password', 78 'validate_certs', 79 'valid_http_proxy', 80] 81 82 83class FailJsonException(BaseException): 84 pass 85 86 87@pytest.fixture 88def fake_ansible_module(): 89 ret = mock.Mock() 90 ret.params = test_data[3][0] 91 ret.tmpdir = None 92 ret.fail_json.side_effect = FailJsonException() 93 return ret 94 95 96def fake_connect_to_api(module, return_si=None): 97 return None, mock.Mock(), 98 99 100testdata = [ 101 ('HAS_PYVMOMI', 'PyVmomi'), 102 ('HAS_REQUESTS', 'requests'), 103] 104 105 106@pytest.mark.parametrize("key,libname", testdata) 107def test_lib_loading_failure(monkeypatch, fake_ansible_module, key, libname): 108 """ Test if Pyvmomi is present or not""" 109 monkeypatch.setattr(vmware_module_utils, key, False) 110 with pytest.raises(FailJsonException): 111 vmware_module_utils.PyVmomi(fake_ansible_module) 112 error_str = 'Failed to import the required Python library (%s)' % libname 113 assert fake_ansible_module.fail_json.called_once() 114 assert error_str in fake_ansible_module.fail_json.call_args[1]['msg'] 115 116 117@pytest.mark.skipif(sys.version_info < (2, 7), reason="requires python2.7 and greater") 118@pytest.mark.parametrize("params, msg", test_data, ids=test_ids) 119def test_required_params(request, params, msg, fake_ansible_module): 120 """ Test if required params are correct or not""" 121 fake_ansible_module.params = params 122 with pytest.raises(FailJsonException): 123 vmware_module_utils.connect_to_api(fake_ansible_module) 124 assert fake_ansible_module.fail_json.called_once() 125 # TODO: assert msg in fake_ansible_module.fail_json.call_args[1]['msg'] 126 127 128def test_validate_certs(monkeypatch, fake_ansible_module): 129 """ Test if SSL is required or not""" 130 fake_ansible_module.params = test_data[3][0] 131 132 monkeypatch.setattr(vmware_module_utils, 'ssl', mock.Mock()) 133 del vmware_module_utils.ssl.SSLContext 134 with pytest.raises(FailJsonException): 135 vmware_module_utils.PyVmomi(fake_ansible_module) 136 msg = 'pyVim does not support changing verification mode with python < 2.7.9.' \ 137 ' Either update python or use validate_certs=false.' 138 assert fake_ansible_module.fail_json.called_once() 139 assert msg == fake_ansible_module.fail_json.call_args[1]['msg'] 140 141 142def test_vmdk_disk_path_split(monkeypatch, fake_ansible_module): 143 """ Test vmdk_disk_path_split function""" 144 fake_ansible_module.params = test_data[0][0] 145 146 monkeypatch.setattr(vmware_module_utils, 'connect_to_api', fake_connect_to_api) 147 pyv = vmware_module_utils.PyVmomi(fake_ansible_module) 148 v = pyv.vmdk_disk_path_split('[ds1] VM_0001/VM0001_0.vmdk') 149 assert v == ('ds1', 'VM_0001/VM0001_0.vmdk', 'VM0001_0.vmdk', 'VM_0001') 150 151 152def test_vmdk_disk_path_split_negative(monkeypatch, fake_ansible_module): 153 """ Test vmdk_disk_path_split function""" 154 fake_ansible_module.params = test_data[0][0] 155 156 monkeypatch.setattr(vmware_module_utils, 'connect_to_api', fake_connect_to_api) 157 with pytest.raises(FailJsonException): 158 pyv = vmware_module_utils.PyVmomi(fake_ansible_module) 159 pyv.vmdk_disk_path_split('[ds1]') 160 assert fake_ansible_module.fail_json.called_once() 161 assert 'Bad path' in fake_ansible_module.fail_json.call_args[1]['msg'] 162 163 164@pytest.mark.skipif(sys.version_info < (2, 7), reason="requires python2.7 and greater") 165def test_connect_to_api_validate_certs(monkeypatch, fake_ansible_module): 166 monkeypatch.setattr(vmware_module_utils, 'connect', mock.Mock()) 167 168 def MockSSLContext(proto): 169 ssl_context.proto = proto 170 return ssl_context 171 172 # New Python with SSLContext + validate_certs=True 173 vmware_module_utils.connect.reset_mock() 174 ssl_context = mock.Mock() 175 monkeypatch.setattr(vmware_module_utils.ssl, 'SSLContext', MockSSLContext) 176 fake_ansible_module.params['validate_certs'] = True 177 vmware_module_utils.connect_to_api(fake_ansible_module) 178 assert ssl_context.proto == ssl.PROTOCOL_SSLv23 179 assert ssl_context.verify_mode == ssl.CERT_REQUIRED 180 assert ssl_context.check_hostname is True 181 vmware_module_utils.connect.SmartConnect.assert_called_once_with( 182 host='esxi1', 183 port=443, 184 pwd='Esxi@123$%', 185 user='Administrator@vsphere.local', 186 sslContext=ssl_context) 187 188 # New Python with SSLContext + validate_certs=False 189 vmware_module_utils.connect.reset_mock() 190 ssl_context = mock.Mock() 191 monkeypatch.setattr(vmware_module_utils.ssl, 'SSLContext', MockSSLContext) 192 fake_ansible_module.params['validate_certs'] = False 193 vmware_module_utils.connect_to_api(fake_ansible_module) 194 assert ssl_context.proto == ssl.PROTOCOL_SSLv23 195 assert ssl_context.verify_mode == ssl.CERT_NONE 196 assert ssl_context.check_hostname is False 197 vmware_module_utils.connect.SmartConnect.assert_called_once_with( 198 host='esxi1', 199 port=443, 200 pwd='Esxi@123$%', 201 user='Administrator@vsphere.local', 202 sslContext=ssl_context) 203 204 # Old Python with no SSLContext + validate_certs=True 205 vmware_module_utils.connect.reset_mock() 206 ssl_context = mock.Mock() 207 ssl_context.proto = None 208 monkeypatch.delattr(vmware_module_utils.ssl, 'SSLContext') 209 fake_ansible_module.params['validate_certs'] = True 210 with pytest.raises(FailJsonException): 211 vmware_module_utils.connect_to_api(fake_ansible_module) 212 assert ssl_context.proto is None 213 fake_ansible_module.fail_json.assert_called_once_with(msg=( 214 'pyVim does not support changing verification mode with python ' 215 '< 2.7.9. Either update python or use validate_certs=false.')) 216 assert not vmware_module_utils.connect.SmartConnect.called 217 218 # Old Python with no SSLContext + validate_certs=False 219 vmware_module_utils.connect.reset_mock() 220 ssl_context = mock.Mock() 221 ssl_context.proto = None 222 monkeypatch.delattr(vmware_module_utils.ssl, 'SSLContext', raising=False) 223 fake_ansible_module.params['validate_certs'] = False 224 vmware_module_utils.connect_to_api(fake_ansible_module) 225 assert ssl_context.proto is None 226 vmware_module_utils.connect.SmartConnect.assert_called_once_with( 227 host='esxi1', 228 port=443, 229 pwd='Esxi@123$%', 230 user='Administrator@vsphere.local') 231 232 233@pytest.mark.parametrize("test_options, test_current_options, test_truthy_strings_as_bool", [ 234 ({"data": True}, [], True), 235 ({"data": 1}, [], True), 236 ({"data": 1.2}, [], True), 237 ({"data": 'string'}, [], True), 238 ({"data": True}, [], False), 239 ({"data": 1}, [], False), 240 ({"data": 1.2}, [], False), 241 ({"data": 'string'}, [], False), 242]) 243def test_option_diff(test_options, test_current_options, test_truthy_strings_as_bool): 244 assert option_diff(test_options, test_current_options, test_truthy_strings_as_bool)[0].value == test_options["data"] 245