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