1''' unit tests ONTAP Ansible module: na_ontap_quotas ''' 2 3from __future__ import print_function 4import json 5import pytest 6 7from units.compat import unittest 8from units.compat.mock import patch 9from ansible.module_utils import basic 10from ansible.module_utils._text import to_bytes 11import ansible.module_utils.netapp as netapp_utils 12 13from ansible.modules.storage.netapp.na_ontap_quotas \ 14 import NetAppONTAPQuotas as my_module 15 16if not netapp_utils.has_netapp_lib(): 17 pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') 18 19 20def set_module_args(args): 21 """prepare arguments so that they will be picked up during module creation""" 22 args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) 23 basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access 24 25 26class AnsibleExitJson(Exception): 27 """Exception class to be raised by module.exit_json and caught by the test case""" 28 pass 29 30 31class AnsibleFailJson(Exception): 32 """Exception class to be raised by module.fail_json and caught by the test case""" 33 pass 34 35 36def exit_json(*args, **kwargs): # pylint: disable=unused-argument 37 """function to patch over exit_json; package return data into an exception""" 38 if 'changed' not in kwargs: 39 kwargs['changed'] = False 40 raise AnsibleExitJson(kwargs) 41 42 43def fail_json(*args, **kwargs): # pylint: disable=unused-argument 44 """function to patch over fail_json; package return data into an exception""" 45 kwargs['failed'] = True 46 raise AnsibleFailJson(kwargs) 47 48 49class MockONTAPConnection(object): 50 ''' mock server connection to ONTAP host ''' 51 52 def __init__(self, kind=None): 53 ''' save arguments ''' 54 self.type = kind 55 self.xml_in = None 56 self.xml_out = None 57 58 def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument 59 ''' mock invoke_successfully returning xml data ''' 60 self.xml_in = xml 61 if self.type == 'quotas': 62 xml = self.build_quota_info() 63 elif self.type == 'quota_fail': 64 raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test") 65 self.xml_out = xml 66 return xml 67 68 @staticmethod 69 def build_quota_info(): 70 ''' build xml data for quota-entry ''' 71 xml = netapp_utils.zapi.NaElement('xml') 72 data = {'num-records': 1, 73 'attributes-list': {'quota-entry': {'volume': 'ansible', 74 'file-limit': '-', 'disk-limit': '-', 'threshold': '-'}}, 75 'status': 'true'} 76 xml.translate_struct(data) 77 return xml 78 79 80class TestMyModule(unittest.TestCase): 81 ''' a group of related Unit Tests ''' 82 83 def setUp(self): 84 self.mock_module_helper = patch.multiple(basic.AnsibleModule, 85 exit_json=exit_json, 86 fail_json=fail_json) 87 self.mock_module_helper.start() 88 self.addCleanup(self.mock_module_helper.stop) 89 self.server = MockONTAPConnection() 90 self.onbox = False 91 92 def set_default_args(self): 93 if self.onbox: 94 hostname = '10.193.75.3' 95 username = 'admin' 96 password = 'netapp1!' 97 volume = 'ansible' 98 vserver = 'ansible' 99 policy = 'ansible' 100 quota_target = '/vol/ansible' 101 type = 'user' 102 else: 103 hostname = 'hostname' 104 username = 'username' 105 password = 'password' 106 volume = 'ansible' 107 vserver = 'ansible' 108 policy = 'ansible' 109 quota_target = '/vol/ansible' 110 type = 'user' 111 return dict({ 112 'hostname': hostname, 113 'username': username, 114 'password': password, 115 'volume': volume, 116 'vserver': vserver, 117 'policy': policy, 118 'quota_target': quota_target, 119 'type': type 120 }) 121 122 def test_module_fail_when_required_args_missing(self): 123 ''' required arguments are reported as errors ''' 124 with pytest.raises(AnsibleFailJson) as exc: 125 set_module_args({}) 126 my_module() 127 print('Info: %s' % exc.value.args[0]['msg']) 128 129 def test_ensure_get_called(self): 130 ''' test get_quota for non-existent quota''' 131 set_module_args(self.set_default_args()) 132 my_obj = my_module() 133 my_obj.server = self.server 134 assert my_obj.get_quotas is not None 135 136 def test_ensure_get_called_existing(self): 137 ''' test get_quota for existing quota''' 138 set_module_args(self.set_default_args()) 139 my_obj = my_module() 140 my_obj.server = MockONTAPConnection(kind='quotas') 141 assert my_obj.get_quotas() 142 143 @patch('ansible.modules.storage.netapp.na_ontap_quotas.NetAppONTAPQuotas.quota_entry_set') 144 def test_successful_create(self, quota_entry_set): 145 ''' creating quota and testing idempotency ''' 146 data = self.set_default_args() 147 data.update({'file_limit': '3', 148 'disk_limit': '4'}) 149 # data['file_limit'] = '3' 150 # data['disk_limit'] = '4' 151 # data['threshold'] = '4' 152 set_module_args(data) 153 my_obj = my_module() 154 if not self.onbox: 155 my_obj.server = self.server 156 with pytest.raises(AnsibleExitJson) as exc: 157 my_obj.apply() 158 assert exc.value.args[0]['changed'] 159 quota_entry_set.assert_called_with() 160 # to reset na_helper from remembering the previous 'changed' value 161 set_module_args(self.set_default_args()) 162 my_obj = my_module() 163 if not self.onbox: 164 my_obj.server = MockONTAPConnection('quotas') 165 with pytest.raises(AnsibleExitJson) as exc: 166 my_obj.apply() 167 assert not exc.value.args[0]['changed'] 168 169 @patch('ansible.modules.storage.netapp.na_ontap_quotas.NetAppONTAPQuotas.quota_entry_delete') 170 def test_successful_delete(self, quota_entry_delete): 171 ''' deleting quota and testing idempotency ''' 172 data = self.set_default_args() 173 data['state'] = 'absent' 174 set_module_args(data) 175 my_obj = my_module() 176 if not self.onbox: 177 my_obj.server = MockONTAPConnection('quotas') 178 with pytest.raises(AnsibleExitJson) as exc: 179 my_obj.apply() 180 assert exc.value.args[0]['changed'] 181 quota_entry_delete.assert_called_with() 182 # to reset na_helper from remembering the previous 'changed' value 183 my_obj = my_module() 184 if not self.onbox: 185 my_obj.server = self.server 186 with pytest.raises(AnsibleExitJson) as exc: 187 my_obj.apply() 188 assert not exc.value.args[0]['changed'] 189 190 def test_successful_modify(self): 191 ''' modifying quota and testing idempotency ''' 192 data = self.set_default_args() 193 data['file_limit'] = '3' 194 set_module_args(data) 195 my_obj = my_module() 196 if not self.onbox: 197 my_obj.server = MockONTAPConnection('quotas') 198 with pytest.raises(AnsibleExitJson) as exc: 199 my_obj.apply() 200 assert exc.value.args[0]['changed'] 201 202 def test_quota_on_off(self): 203 ''' quota set on or off ''' 204 data = self.set_default_args() 205 data['set_quota_status'] = 'false' 206 set_module_args(data) 207 my_obj = my_module() 208 if not self.onbox: 209 my_obj.server = MockONTAPConnection('quotas') 210 with pytest.raises(AnsibleExitJson) as exc: 211 my_obj.apply() 212 assert not exc.value.args[0]['changed'] 213 214 def test_if_all_methods_catch_exception(self): 215 module_args = {} 216 module_args.update(self.set_default_args()) 217 set_module_args(module_args) 218 my_obj = my_module() 219 if not self.onbox: 220 my_obj.server = MockONTAPConnection('quota_fail') 221 with pytest.raises(AnsibleFailJson) as exc: 222 my_obj.get_quota_status() 223 assert 'Error fetching quotas status info' in exc.value.args[0]['msg'] 224 with pytest.raises(AnsibleFailJson) as exc: 225 my_obj.get_quotas() 226 assert 'Error fetching quotas info' in exc.value.args[0]['msg'] 227 with pytest.raises(AnsibleFailJson) as exc: 228 my_obj.quota_entry_set() 229 assert 'Error adding/modifying quota entry' in exc.value.args[0]['msg'] 230 with pytest.raises(AnsibleFailJson) as exc: 231 my_obj.quota_entry_delete() 232 assert 'Error deleting quota entry' in exc.value.args[0]['msg'] 233 with pytest.raises(AnsibleFailJson) as exc: 234 my_obj.quota_entry_modify(module_args) 235 assert 'Error modifying quota entry' in exc.value.args[0]['msg'] 236 with pytest.raises(AnsibleFailJson) as exc: 237 my_obj.on_or_off_quota('quota-on') 238 assert 'Error setting quota-on for ansible' in exc.value.args[0]['msg'] 239