1# (c) 2019, NetApp, Inc
2# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
3
4''' unit tests for Ansible module: na_ontap_volume_autosize '''
5
6from __future__ import (absolute_import, division, print_function)
7__metaclass__ = type
8import json
9import pytest
10
11from ansible.module_utils import basic
12from ansible.module_utils._text import to_bytes
13from ansible_collections.netapp.ontap.tests.unit.compat import unittest
14from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch
15import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
16
17from ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume_autosize \
18    import NetAppOntapVolumeAutosize as autosize_module  # module under test
19
20if not netapp_utils.has_netapp_lib():
21    pytestmark = pytest.mark.skip('skipping as missing required netapp_lib')
22
23
24# REST API canned responses when mocking send_request
25SRR = {
26    # common responses
27    'is_rest': (200, {}, None),
28    'is_zapi': (400, {}, "Unreachable"),
29    'empty_good': (200, {}, None),
30    'end_of_sequence': (500, None, "Unexpected call to send_request"),
31    'generic_error': (400, None, "Expected error"),
32    # module specific responses
33    'get_uuid': (200, {'records': [{'uuid': 'testuuid'}]}, None),
34    'get_autosize': (200,
35                     {'uuid': 'testuuid',
36                      'name': 'testname',
37                      'autosize': {"maximum": 10737418240,
38                                   "minimum": 22020096,
39                                   "grow_threshold": 99,
40                                   "shrink_threshold": 40,
41                                   "mode": "grow"
42                                   }
43                      }, None)
44}
45
46
47def set_module_args(args):
48    """prepare arguments so that they will be picked up during module creation"""
49    args = json.dumps({'ANSIBLE_MODULE_ARGS': args})
50    basic._ANSIBLE_ARGS = to_bytes(args)  # pylint: disable=protected-access
51
52
53class AnsibleExitJson(Exception):
54    """Exception class to be raised by module.exit_json and caught by the test case"""
55
56
57class AnsibleFailJson(Exception):
58    """Exception class to be raised by module.fail_json and caught by the test case"""
59
60
61def exit_json(*args, **kwargs):  # pylint: disable=unused-argument
62    """function to patch over exit_json; package return data into an exception"""
63    if 'changed' not in kwargs:
64        kwargs['changed'] = False
65    raise AnsibleExitJson(kwargs)
66
67
68def fail_json(*args, **kwargs):  # pylint: disable=unused-argument
69    """function to patch over fail_json; package return data into an exception"""
70    kwargs['failed'] = True
71    raise AnsibleFailJson(kwargs)
72
73
74class MockONTAPConnection(object):
75    ''' mock server connection to ONTAP host '''
76
77    def __init__(self, kind=None, data=None):
78        ''' save arguments '''
79        self.kind = kind
80        self.params = data
81        self.xml_in = None
82        self.xml_out = None
83
84    def invoke_successfully(self, xml, enable_tunneling):  # pylint: disable=unused-argument
85        ''' mock invoke_successfully returning xml data '''
86        self.xml_in = xml
87        if self.kind == 'autosize':
88            xml = self.build_autosize_info(self.params)
89        self.xml_out = xml
90        return xml
91
92    @staticmethod
93    def build_autosize_info(autosize_details):
94        xml = netapp_utils.zapi.NaElement('xml')
95        attributes = {
96            'grow-threshold-percent': autosize_details['grow_threshold_percent'],
97            'maximum-size': '10485760',
98            'minimum-size': '21504',
99            'increment_size': '10240',
100            'mode': autosize_details['mode'],
101            'shrink-threshold-percent': autosize_details['shrink_threshold_percent']
102        }
103        xml.translate_struct(attributes)
104        return xml
105
106
107class TestMyModule(unittest.TestCase):
108    ''' Unit tests for na_ontap_job_schedule '''
109
110    def setUp(self):
111        self.mock_module_helper = patch.multiple(basic.AnsibleModule,
112                                                 exit_json=exit_json,
113                                                 fail_json=fail_json)
114        self.mock_module_helper.start()
115        self.addCleanup(self.mock_module_helper.stop)
116        self.mock_autosize = {
117            'grow_threshold_percent': 99,
118            'maximum_size': '10g',
119            'minimum_size': '21m',
120            'increment_size': '10m',
121            'mode': 'grow',
122            'shrink_threshold_percent': 40,
123            'vserver': 'test_vserver',
124            'volume': 'test_volume'
125        }
126
127    def mock_args(self, rest=False):
128        if rest:
129            return {
130                'vserver': self.mock_autosize['vserver'],
131                'volume': self.mock_autosize['volume'],
132                'grow_threshold_percent': self.mock_autosize['grow_threshold_percent'],
133                'maximum_size': self.mock_autosize['maximum_size'],
134                'minimum_size': self.mock_autosize['minimum_size'],
135                'mode': self.mock_autosize['mode'],
136                'shrink_threshold_percent': self.mock_autosize['shrink_threshold_percent'],
137                'hostname': 'test',
138                'username': 'test_user',
139                'password': 'test_pass!'
140            }
141        else:
142            return {
143                'vserver': self.mock_autosize['vserver'],
144                'volume': self.mock_autosize['volume'],
145                'grow_threshold_percent': self.mock_autosize['grow_threshold_percent'],
146                'maximum_size': self.mock_autosize['maximum_size'],
147                'minimum_size': self.mock_autosize['minimum_size'],
148                'increment_size': self.mock_autosize['increment_size'],
149                'mode': self.mock_autosize['mode'],
150                'shrink_threshold_percent': self.mock_autosize['shrink_threshold_percent'],
151                'hostname': 'test',
152                'username': 'test_user',
153                'password': 'test_pass!',
154                'use_rest': 'never'
155            }
156
157    def get_autosize_mock_object(self, cx_type='zapi', kind=None):
158        autosize_obj = autosize_module()
159        if cx_type == 'zapi':
160            if kind is None:
161                autosize_obj.server = MockONTAPConnection()
162            elif kind == 'autosize':
163                autosize_obj.server = MockONTAPConnection(kind='autosize', data=self.mock_autosize)
164        return autosize_obj
165
166    def test_module_fail_when_required_args_missing(self):
167        ''' required arguments are reported as errors '''
168        with pytest.raises(AnsibleFailJson) as exc:
169            set_module_args({})
170            autosize_module()
171        print('Info: %s' % exc.value.args[0]['msg'])
172
173    def test_idempotent_modify(self):
174        set_module_args(self.mock_args())
175        with pytest.raises(AnsibleExitJson) as exc:
176            self.get_autosize_mock_object('zapi', 'autosize').apply()
177        assert not exc.value.args[0]['changed']
178
179    def test_successful_modify(self):
180        data = self.mock_args()
181        data['maximum_size'] = '11g'
182        set_module_args(data)
183        with pytest.raises(AnsibleExitJson) as exc:
184            self.get_autosize_mock_object('zapi', 'autosize').apply()
185        assert exc.value.args[0]['changed']
186
187    def test_successful_reset(self):
188        data = {}
189        data['reset'] = True
190        data['hostname'] = 'test'
191        data['username'] = 'test_user'
192        data['password'] = 'test_pass!'
193        data['volume'] = 'test_vol'
194        data['vserver'] = 'test_vserver'
195        data['use_rest'] = 'never'
196        set_module_args(data)
197        with pytest.raises(AnsibleExitJson) as exc:
198            self.get_autosize_mock_object('zapi', 'autosize').apply()
199        assert exc.value.args[0]['changed']
200
201    @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request')
202    def test_rest_error(self, mock_request):
203        data = self.mock_args(rest=True)
204        set_module_args(data)
205        mock_request.side_effect = [
206            SRR['is_rest'],
207            SRR['generic_error'],
208            SRR['end_of_sequence']
209        ]
210        with pytest.raises(AnsibleFailJson) as exc:
211            self.get_autosize_mock_object(cx_type='rest').apply()
212        assert exc.value.args[0]['msg'] == SRR['generic_error'][2]
213
214    @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request')
215    def test_rest_successful_modify(self, mock_request):
216        data = self.mock_args(rest=True)
217        data['maximum_size'] = '11g'
218        set_module_args(data)
219        mock_request.side_effect = [
220            SRR['is_rest'],
221            SRR['get_uuid'],
222            SRR['get_autosize'],
223            SRR['empty_good'],
224            SRR['end_of_sequence']
225        ]
226        with pytest.raises(AnsibleExitJson) as exc:
227            self.get_autosize_mock_object(cx_type='rest').apply()
228        assert exc.value.args[0]['changed']
229
230    @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request')
231    def test_rest_idempotent_modify(self, mock_request):
232        data = self.mock_args(rest=True)
233        set_module_args(data)
234        mock_request.side_effect = [
235            SRR['is_rest'],
236            SRR['get_uuid'],
237            SRR['get_autosize'],
238            SRR['empty_good'],
239            SRR['end_of_sequence']
240        ]
241        with pytest.raises(AnsibleExitJson) as exc:
242            self.get_autosize_mock_object(cx_type='rest').apply()
243        assert not exc.value.args[0]['changed']
244