1''' unit tests for Ansible module: na_elementsw_info.py '''
2
3from __future__ import absolute_import, division, print_function
4__metaclass__ = type
5
6import inspect
7import json
8import pytest
9
10from ansible.module_utils import basic
11from ansible.module_utils._text import to_bytes
12from ansible_collections.netapp.elementsw.tests.unit.compat import unittest
13from ansible_collections.netapp.elementsw.tests.unit.compat.mock import patch
14import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
15
16if not netapp_utils.has_sf_sdk():
17    pytestmark = pytest.mark.skip('skipping as missing required SolidFire Python SDK')
18
19from ansible_collections.netapp.elementsw.plugins.modules.na_elementsw_network_interfaces \
20    import ElementSWNetworkInterfaces as my_module  # module under test
21
22
23def set_module_args(args):
24    """prepare arguments so that they will be picked up during module creation"""
25    args = json.dumps({'ANSIBLE_MODULE_ARGS': args})
26    basic._ANSIBLE_ARGS = to_bytes(args)  # pylint: disable=protected-access
27
28
29class AnsibleExitJson(Exception):
30    """Exception class to be raised by module.exit_json and caught by the test case"""
31
32
33class AnsibleFailJson(Exception):
34    """Exception class to be raised by module.fail_json and caught by the test case"""
35
36
37def exit_json(*args, **kwargs):  # pylint: disable=unused-argument
38    """function to patch over exit_json; package return data into an exception"""
39    if 'changed' not in kwargs:
40        kwargs['changed'] = False
41    raise AnsibleExitJson(kwargs)
42
43
44def fail_json(*args, **kwargs):  # pylint: disable=unused-argument
45    """function to patch over fail_json; package return data into an exception"""
46    kwargs['failed'] = True
47    raise AnsibleFailJson(kwargs)
48
49
50NODE_ID1 = 777
51NODE_ID2 = 888
52NODE_ID3 = 999
53
54MAPPING = dict(
55    bond_mode='bond-mode',
56    bond_lacp_rate='bond-lacp_rate',
57    dns_nameservers='dns-nameservers',
58    dns_search='dns-search',
59    virtual_network_tag='virtualNetworkTag',
60)
61
62
63def mapkey(key):
64    if key in MAPPING:
65        return MAPPING[key]
66    return key
67
68
69class MockSFConnection(object):
70    ''' mock connection to ElementSW host '''
71
72    class Bunch(object):  # pylint: disable=too-few-public-methods
73        ''' create object with arbitrary attributes '''
74        def __init__(self, **kw):
75            ''' called with (k1=v1, k2=v2), creates obj.k1, obj.k2 with values v1, v2 '''
76            setattr(self, '__dict__', kw)
77
78        def __repr__(self):
79            results = dict()
80            for key, value in vars(self).items():
81                results[key] = repr(value)
82            return repr(results)
83
84        def to_json(self):
85            return json.loads(json.dumps(self, default=lambda x: x.__dict__))
86
87    def __init__(self, force_error=False, where=None):
88        ''' save arguments '''
89        self.force_error = force_error
90        self.where = where
91        # self._port = 442
92        self.called = list()
93        self.set_network_config_args = dict()
94        if force_error and where == 'cx':
95            raise netapp_utils.solidfire.common.ApiConnectionError('testme')
96
97    def record(self, args, kwargs):     # pylint: disable=unused-argument
98        name = inspect.stack()[1][3]    # caller function name
99        # print('%s: , args: %s, kwargs: %s' % (name, args, kwargs))
100        self.called.append(name)
101
102    def set_network_config(self, *args, **kwargs):  # pylint: disable=unused-argument
103        self.record(repr(args), repr(kwargs))
104        print('network:', kwargs['network'].to_json())
105        self.set_network_config_args = kwargs['network'].to_json()
106
107
108class TestMyModule(unittest.TestCase):
109    ''' a group of related Unit Tests '''
110
111    DEPRECATED_ARGS = {
112        'ip_address_1g': 'ip_address_1g',
113        'subnet_1g': 'subnet_1g',
114        'gateway_address_1g': 'gateway_address_1g',
115        'mtu_1g': 'mtu_1g',         # make sure the use a value != from default
116        'bond_mode_1g': 'ALB',      # make sure the use a value != from default
117        'lacp_1g': 'Fast',          # make sure the use a value != from default
118        'ip_address_10g': 'ip_address_10g',
119        'subnet_10g': 'subnet_10g',
120        'gateway_address_10g': 'gateway_address_10g',
121        'mtu_10g': 'mtu_10g',       # make sure the use a value != from default
122        'bond_mode_10g': 'LACP',    # make sure the use a value != from default
123        'lacp_10g': 'Fast',         # make sure the use a value != from default
124        'method': 'static',
125        'dns_nameservers': 'dns_nameservers',
126        'dns_search_domains': 'dns_search_domains',
127        'virtual_network_tag': 'virtual_network_tag',
128        'hostname': 'hostname',
129        'username': 'username',
130        'password': 'password',
131    }
132
133    ARGS = {
134        'bond_1g': {
135            'address': '10.10.10.10',
136            'netmask': '255.255.255.0',
137            'gateway': '10.10.10.1',
138            'mtu': '1500',
139            'bond_mode': 'ActivePassive',
140            'dns_nameservers': ['dns_nameservers'],
141            'dns_search': ['dns_search_domains'],
142            'virtual_network_tag': 'virtual_network_tag',
143        },
144        'bond_10g': {
145            'bond_mode': 'LACP',
146            'bond_lacp_rate': 'Fast',
147        },
148        'hostname': 'hostname',
149        'username': 'username',
150        'password': 'password',
151    }
152
153    def setUp(self):
154        self.mock_module_helper = patch.multiple(basic.AnsibleModule,
155                                                 exit_json=exit_json,
156                                                 fail_json=fail_json)
157        self.mock_module_helper.start()
158        self.addCleanup(self.mock_module_helper.stop)
159
160    def test_module_fail_when_required_args_missing(self):
161        ''' required arguments are reported as errors '''
162        with pytest.raises(AnsibleFailJson) as exc:
163            set_module_args({})
164            my_module()
165        print('Info: %s' % exc.value.args[0]['msg'])
166
167    def test_deprecated_nothing(self):
168        ''' deprecated without 1g or 10g options '''
169        args = dict(self.DEPRECATED_ARGS)  # deep copy as other tests can modify args
170        for key in list(args):
171            if '1g' in key or '10g' in key:
172                del args[key]
173        set_module_args(args)
174        with pytest.raises(AnsibleFailJson) as exc:
175            my_module()
176        msg = 'Please use the new bond_1g or bond_10g options to configure the bond interfaces.'
177        assert msg in exc.value.args[0]['msg']
178        msg = 'This module cannot set or change "method"'
179        assert msg in exc.value.args[0]['msg']
180
181    def test_deprecated_all(self):
182        ''' deprecated with all options '''
183        args = dict(self.DEPRECATED_ARGS)  # deep copy as other tests can modify args
184        set_module_args(args)
185        with pytest.raises(AnsibleFailJson) as exc:
186            my_module()
187        msg = 'Please use the new bond_1g and bond_10g options to configure the bond interfaces.'
188        assert msg in exc.value.args[0]['msg']
189        msg = 'This module cannot set or change "method"'
190        assert msg in exc.value.args[0]['msg']
191
192    def test_deprecated_1g_only(self):
193        ''' deprecated with 1g options only '''
194        args = dict(self.DEPRECATED_ARGS)  # deep copy as other tests can modify args
195        for key in list(args):
196            if '10g' in key:
197                del args[key]
198        set_module_args(args)
199        with pytest.raises(AnsibleFailJson) as exc:
200            my_module()
201        msg = 'Please use the new bond_1g option to configure the bond 1G interface.'
202        assert msg in exc.value.args[0]['msg']
203        msg = 'This module cannot set or change "method"'
204        assert msg in exc.value.args[0]['msg']
205
206    def test_deprecated_10g_only(self):
207        ''' deprecated with 10g options only '''
208        args = dict(self.DEPRECATED_ARGS)  # deep copy as other tests can modify args
209        for key in list(args):
210            if '1g' in key:
211                del args[key]
212        set_module_args(args)
213        with pytest.raises(AnsibleFailJson) as exc:
214            my_module()
215        msg = 'Please use the new bond_10g option to configure the bond 10G interface.'
216        assert msg in exc.value.args[0]['msg']
217        msg = 'This module cannot set or change "method"'
218        assert msg in exc.value.args[0]['msg']
219
220    @patch('ansible_collections.netapp.elementsw.plugins.module_utils.netapp.create_sf_connection')
221    def test_modify_nothing(self, mock_create_sf_connection):
222        ''' modify without 1g or 10g options '''
223        args = dict(self.ARGS)  # deep copy as other tests can modify args
224        for key in list(args):
225            if '1g' in key or '10g' in key:
226                del args[key]
227        set_module_args(args)
228        # my_obj.sfe will be assigned a MockSFConnection object:
229        mock_create_sf_connection.return_value = MockSFConnection()
230        my_obj = my_module()
231        print('LN:', my_obj.module.params)
232        with pytest.raises(AnsibleExitJson) as exc:
233            my_obj.apply()
234        print(exc.value.args[0])
235        assert not exc.value.args[0]['changed']
236        assert len(my_obj.sfe.set_network_config_args) == 0
237
238    @patch('ansible_collections.netapp.elementsw.plugins.module_utils.netapp.create_sf_connection')
239    def test_modify_all(self, mock_create_sf_connection):
240        ''' modify with all options '''
241        args = dict(self.ARGS)  # deep copy as other tests can modify args
242        set_module_args(args)
243        # my_obj.sfe will be assigned a MockSFConnection object:
244        mock_create_sf_connection.return_value = MockSFConnection()
245        my_obj = my_module()
246        with pytest.raises(AnsibleExitJson) as exc:
247            my_obj.apply()
248        print(exc.value.args[0])
249        assert exc.value.args[0]['changed']
250        assert 'Bond1G' in my_obj.sfe.set_network_config_args
251
252    @patch('ansible_collections.netapp.elementsw.plugins.module_utils.netapp.create_sf_connection')
253    def test_modify_1g_only(self, mock_create_sf_connection):
254        ''' modify with 1g options only '''
255        args = dict(self.ARGS)  # deep copy as other tests can modify args
256        for key in list(args):
257            if '10g' in key:
258                del args[key]
259        set_module_args(args)
260        # my_obj.sfe will be assigned a MockSFConnection object:
261        mock_create_sf_connection.return_value = MockSFConnection()
262        my_obj = my_module()
263        with pytest.raises(AnsibleExitJson) as exc:
264            my_obj.apply()
265        print(exc.value.args[0])
266        assert exc.value.args[0]['changed']
267        assert 'Bond1G' in my_obj.sfe.set_network_config_args
268        assert 'Bond10G' not in my_obj.sfe.set_network_config_args
269        print(my_obj.sfe.set_network_config_args['Bond1G'])
270        for key in args['bond_1g']:
271            if key != 'bond_lacp_rate':
272                assert my_obj.sfe.set_network_config_args['Bond1G'][mapkey(key)] == args['bond_1g'][key]
273
274    @patch('ansible_collections.netapp.elementsw.plugins.module_utils.netapp.create_sf_connection')
275    def test_modify_10g_only(self, mock_create_sf_connection):
276        ''' modify with 10g options only '''
277        args = dict(self.ARGS)  # deep copy as other tests can modify args
278        for key in list(args):
279            if '1g' in key:
280                del args[key]
281        set_module_args(args)
282        # my_obj.sfe will be assigned a MockSFConnection object:
283        mock_create_sf_connection.return_value = MockSFConnection()
284        my_obj = my_module()
285        with pytest.raises(AnsibleExitJson) as exc:
286            my_obj.apply()
287        print(exc.value.args[0])
288        assert exc.value.args[0]['changed']
289        assert 'Bond1G' not in my_obj.sfe.set_network_config_args
290        assert 'Bond10G' in my_obj.sfe.set_network_config_args
291        assert my_obj.sfe.set_network_config_args['Bond10G']['bond-lacp_rate'] == args['bond_10g']['bond_lacp_rate']
292        for key in args['bond_10g']:
293            assert my_obj.sfe.set_network_config_args['Bond10G'][mapkey(key)] == args['bond_10g'][key]
294