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