From c62a6988b1100b029cfe6f0d00508f525615a26e Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Tue, 19 Feb 2019 06:32:40 -0800 Subject: [PATCH] New parameters and bugfixes for na_ontap_volume (#49511) * add changes * Fixed documentation * Review comment --- .../modules/storage/netapp/na_ontap_volume.py | 175 +++++-- .../storage/netapp/test_na_ontap_volume.py | 438 ++++++++++++++++++ 2 files changed, 578 insertions(+), 35 deletions(-) create mode 100644 test/units/modules/storage/netapp/test_na_ontap_volume.py diff --git a/lib/ansible/modules/storage/netapp/na_ontap_volume.py b/lib/ansible/modules/storage/netapp/na_ontap_volume.py index 6b6eff5e9d3..b6879995c62 100644 --- a/lib/ansible/modules/storage/netapp/na_ontap_volume.py +++ b/lib/ansible/modules/storage/netapp/na_ontap_volume.py @@ -21,7 +21,6 @@ extends_documentation_fragment: - netapp.na_ontap version_added: '2.6' author: NetApp Ansible Team (@carchi8py) - description: - Create or destroy or modify volumes on NetApp ONTAP. @@ -86,6 +85,7 @@ options: junction_path: description: - Junction path of the volume. + - To unmount, use junction path C(''). space_guarantee: description: @@ -114,6 +114,19 @@ options: - Allows a storage efficiency policy to be set on volume creation. version_added: '2.7' + unix_permissions: + description: + - Unix permission bits in octal or symbolic format. + - For example, 0 is equivalent to ------------, 777 is equivalent to ---rwxrwxrwx,both formats are accepted. + - The valid octal value ranges between 0 and 777 inclusive. + version_added: '2.8' + + snapshot_policy: + description: + - The name of the snapshot policy. + - the default policy name is 'default'. + version_added: '2.8' + ''' EXAMPLES = """ @@ -202,6 +215,8 @@ class NetAppOntapVolume(object): default='mixed'), encrypt=dict(required=False, type='bool', default=False), efficiency_policy=dict(required=False, type='str'), + unix_permissions=dict(required=False, type='str'), + snapshot_policy=dict(required=False, type='str') )) self.module = AnsibleModule( argument_spec=self.argument_spec, @@ -249,7 +264,6 @@ class NetAppOntapVolume(object): Return details about the volume :param: name : Name of the volume - :return: Details about the volume. None if not found. :rtype: dict """ @@ -260,45 +274,42 @@ class NetAppOntapVolume(object): if volume_get_iter.get_child_by_name('num-records') and \ int(volume_get_iter.get_child_content('num-records')) > 0: - volume_attributes = volume_get_iter.get_child_by_name( - 'attributes-list').get_child_by_name( - 'volume-attributes') - # Get volume's current size - volume_space_attributes = volume_attributes.get_child_by_name( - 'volume-space-attributes') - current_size = int(volume_space_attributes.get_child_content('size')) - + volume_attributes = volume_get_iter['attributes-list']['volume-attributes'] + volume_space_attributes = volume_attributes['volume-space-attributes'] + volume_state_attributes = volume_attributes['volume-state-attributes'] + volume_id_attributes = volume_attributes['volume-id-attributes'] + volume_export_attributes = volume_attributes['volume-export-attributes'] + volume_security_unix_attributes = volume_attributes['volume-security-attributes']['volume-security-unix-attributes'] + volume_snapshot_attributes = volume_attributes['volume-snapshot-attributes'] # Get volume's state (online/offline) - volume_state_attributes = volume_attributes.get_child_by_name( - 'volume-state-attributes') - current_state = volume_state_attributes.get_child_content('state') - volume_id_attributes = volume_attributes.get_child_by_name( - 'volume-id-attributes') - aggregate_name = volume_id_attributes.get_child_content( - 'containing-aggregate-name') - volume_export_attributes = volume_attributes.get_child_by_name( - 'volume-export-attributes') - policy = volume_export_attributes.get_child_content('policy') - space_guarantee = volume_space_attributes.get_child_content( - 'space-guarantee') - + current_state = volume_state_attributes['state'] is_online = (current_state == "online") + return_value = { 'name': vol_name, - 'size': current_size, + 'size': int(volume_space_attributes['size']), 'is_online': is_online, - 'aggregate_name': aggregate_name, - 'policy': policy, - 'space_guarantee': space_guarantee, + 'policy': volume_export_attributes['policy'], + 'space_guarantee': volume_space_attributes['space-guarantee'], + 'unix_permissions': volume_security_unix_attributes['permissions'], + 'snapshot_policy': volume_snapshot_attributes['snapshot-policy'] + } + if volume_id_attributes.get_child_by_name('containing-aggregate-name'): + return_value['aggregate_name'] = volume_id_attributes['containing-aggregate-name'] + else: + return_value['aggregate_name'] = None + if volume_id_attributes.get_child_by_name('junction-path'): + return_value['junction_path'] = volume_id_attributes['junction-path'] + else: + return_value['junction_path'] = '' return return_value def create_volume(self): '''Create ONTAP volume''' if self.parameters.get('aggregate_name') is None: - self.module.fail_json(msg='Error provisioning volume %s: \ - aggregate_name is required' + self.module.fail_json(msg='Error provisioning volume %s: aggregate_name is required' % self.parameters['name']) options = {'volume': self.parameters['name'], 'containing-aggr-name': self.parameters['aggregate_name'], @@ -315,6 +326,10 @@ class NetAppOntapVolume(object): options['space-reserve'] = self.parameters['space_guarantee'] if self.parameters.get('volume_security_style'): options['volume-security-style'] = self.parameters['volume_security_style'] + if self.parameters.get('unix_permissions'): + options['unix-permissions'] = self.parameters['unix_permissions'] + if self.parameters.get('snapshot_policy'): + options['snapshot-policy'] = self.parameters['snapshot_policy'] volume_create = netapp_utils.zapi.NaElement.create_node_with_children('volume-create', **options) try: self.server.invoke_successfully(volume_create, @@ -426,7 +441,7 @@ class NetAppOntapVolume(object): % (self.parameters['name'], state, to_native(error)), exception=traceback.format_exc()) - def volume_modify_policy_space(self): + def volume_modify_attributes(self): """ modify volume parameter 'policy' or 'space_guarantee' """ @@ -445,6 +460,19 @@ class NetAppOntapVolume(object): vol_space_attributes.add_new_child( 'space-guarantee', self.parameters['space_guarantee']) vol_mod_attributes.add_child_elem(vol_space_attributes) + if self.parameters.get('unix_permissions'): + vol_unix_permissions_attributes = netapp_utils.zapi.NaElement( + 'volume-security-unix-attributes') + vol_unix_permissions_attributes.add_new_child('permissions', self.parameters['unix_permissions']) + vol_security_attributes = netapp_utils.zapi.NaElement( + 'volume-security-attributes') + vol_security_attributes.add_child_elem(vol_unix_permissions_attributes) + vol_mod_attributes.add_child_elem(vol_security_attributes) + if self.parameters.get('snapshot_policy'): + vol_snapshot_policy_attributes = netapp_utils.zapi.NaElement( + 'volume-snapshot-attributes') + vol_snapshot_policy_attributes.add_new_child('snapshot-policy', self.parameters['snapshot_policy']) + vol_mod_attributes.add_child_elem(vol_snapshot_policy_attributes) attributes.add_child_elem(vol_mod_attributes) query = netapp_utils.zapi.NaElement('query') vol_query_attributes = netapp_utils.zapi.NaElement('volume-attributes') @@ -457,7 +485,7 @@ class NetAppOntapVolume(object): try: result = self.server.invoke_successfully(vol_mod_iter, enable_tunneling=True) failures = result.get_child_by_name('failure-list') - # handle error if modify space or policy parameter fails + # handle error if modify space, policy, or unix-permissions parameter fails if failures is not None and failures.get_child_by_name('volume-modify-iter-info') is not None: error_msg = failures.get_child_by_name('volume-modify-iter-info').get_child_content('error-message') self.module.fail_json(msg="Error modifying volume %s: %s" @@ -469,16 +497,88 @@ class NetAppOntapVolume(object): % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) + def volume_mount(self): + """ + Mount an existing volume in specified junction_path + :return: None + """ + vol_mount = netapp_utils.zapi.NaElement('volume-mount') + vol_mount.add_new_child('volume-name', self.parameters['name']) + vol_mount.add_new_child('junction-path', self.parameters['junction_path']) + try: + self.server.invoke_successfully(vol_mount, enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error mounting volume %s on path %s: %s' + % (self.parameters['name'], self.parameters['junction_path'], + to_native(error)), exception=traceback.format_exc()) + + def volume_unmount(self): + """ + Unmount an existing volume + :return: None + """ + vol_unmount = netapp_utils.zapi.NaElement.create_node_with_children( + 'volume-unmount', **{'volume-name': self.parameters['name']}) + try: + self.server.invoke_successfully(vol_unmount, enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error unmounting volume %s: %s' + % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) + def modify_volume(self, modify): for attribute in modify.keys(): if attribute == 'size': self.resize_volume() - elif attribute == 'is_online': + if attribute == 'is_online': self.change_volume_state() - elif attribute == 'aggregate_name': + if attribute == 'aggregate_name': self.move_volume() - else: - self.volume_modify_policy_space() + if attribute in ['space_guarantee', 'policy', 'unix_permissions', 'snapshot_policy']: + self.volume_modify_attributes() + if attribute == 'junction_path': + if modify.get('junction_path') == '': + self.volume_unmount() + else: + self.volume_mount() + + def compare_chmod_value(self, current): + """ + compare current unix_permissions to desire unix_permissions. + :return: True if the same, False it not the same or desire unix_permissions is not valid. + """ + desire = self.parameters + if current is None: + return False + octal_value = '' + unix_permissions = desire['unix_permissions'] + if unix_permissions.isdigit(): + return int(current['unix_permissions']) == int(unix_permissions) + else: + if len(unix_permissions) != 12: + return False + if unix_permissions[:3] != '---': + return False + for i in range(3, len(unix_permissions), 3): + if unix_permissions[i] not in ['r', '-'] or unix_permissions[i + 1] not in ['w', '-']\ + or unix_permissions[i + 2] not in ['x', '-']: + return False + group_permission = self.char_to_octal(unix_permissions[i:i + 3]) + octal_value += str(group_permission) + return int(current['unix_permissions']) == int(octal_value) + + def char_to_octal(self, chars): + """ + :param chars: Characters to be converted into octal values. + :return: octal value of the individual group permission. + """ + total = 0 + if chars[0] == 'r': + total += 4 + if chars[1] == 'w': + total += 2 + if chars[2] == 'x': + total += 1 + return total def apply(self): '''Call create/modify/delete operations''' @@ -489,6 +589,11 @@ class NetAppOntapVolume(object): rename = self.na_helper.is_rename_action(self.get_volume(self.parameters['from_name']), current) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) + if self.parameters.get('unix_permissions'): + # current stores unix_permissions' numeric value. + # unix_permission in self.parameter can be either numeric or character. + if self.compare_chmod_value(current): + del self.parameters['unix_permissions'] modify = self.na_helper.get_modified_attributes(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: diff --git a/test/units/modules/storage/netapp/test_na_ontap_volume.py b/test/units/modules/storage/netapp/test_na_ontap_volume.py new file mode 100644 index 00000000000..03356b17e4c --- /dev/null +++ b/test/units/modules/storage/netapp/test_na_ontap_volume.py @@ -0,0 +1,438 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import print_function +import json +import pytest + +from units.compat import unittest +from units.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible.module_utils.netapp as netapp_utils + +from ansible.modules.storage.netapp.na_ontap_volume \ + import NetAppOntapVolume as vol_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'volume': + xml = self.build_volume_info(self.params) + self.xml_out = xml + return xml + + @staticmethod + def build_volume_info(vol_details): + ''' build xml data for volume-attributes ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'volume-attributes': { + 'volume-id-attributes': { + 'containing-aggregate-name': vol_details['aggregate'], + 'junction-path': vol_details['junction_path'] + }, + 'volume-export-attributes': { + 'policy': 'default' + }, + 'volume-state-attributes': { + 'state': "online" + }, + 'volume-space-attributes': { + 'space-guarantee': 'none', + 'size': vol_details['size'] + }, + 'volume-snapshot-attributes': { + 'snapshot-policy': vol_details['snapshot_policy'] + }, + 'volume-security-attributes': { + 'volume-security-unix-attributes': { + 'permissions': vol_details['unix_permissions'] + } + } + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_vol = { + 'name': 'test_vol', + 'aggregate': 'test_aggr', + 'junction_path': '/test', + 'vserver': 'test_vserver', + 'size': 20, + 'unix_permissions': '755', + 'snapshot_policy': 'default' + } + + def mock_args(self): + return { + 'name': self.mock_vol['name'], + 'vserver': self.mock_vol['vserver'], + 'aggregate_name': self.mock_vol['aggregate'], + 'space_guarantee': 'none', + 'policy': 'default', + 'is_online': True, + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'unix_permissions': '---rwxr-xr-x', + 'snapshot_policy': 'default' + } + + def get_volume_mock_object(self, kind=None): + """ + Helper method to return an na_ontap_volume object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_volume object + """ + vol_obj = vol_module() + vol_obj.ems_log_event = Mock(return_value=None) + vol_obj.cluster = Mock() + vol_obj.cluster.invoke_successfully = Mock() + if kind is None: + vol_obj.server = MockONTAPConnection() + else: + vol_obj.server = MockONTAPConnection(kind='volume', data=self.mock_vol) + return vol_obj + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + vol_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_nonexistent_volume(self): + ''' Test if get_volume returns None for non-existent volume ''' + set_module_args(self.mock_args()) + result = self.get_volume_mock_object().get_volume() + assert result is None + + def test_get_existing_volume(self): + ''' Test if get_volume returns details for existing volume ''' + set_module_args(self.mock_args()) + result = self.get_volume_mock_object('volume').get_volume() + assert result['name'] == self.mock_vol['name'] + assert result['size'] == self.mock_vol['size'] + + def test_create_error_missing_param(self): + ''' Test if create throws an error if aggregate_name is not specified''' + data = self.mock_args() + del data['aggregate_name'] + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_volume_mock_object('volume').create_volume() + msg = 'Error provisioning volume test_vol: aggregate_name is required' + assert exc.value.args[0]['msg'] == msg + + def test_successful_create(self): + ''' Test successful create ''' + data = self.mock_args() + data['size'] = 20 + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object().apply() + assert exc.value.args[0]['changed'] + + def test_create_idempotency(self): + ''' Test create idempotency ''' + set_module_args(self.mock_args()) + obj = self.get_volume_mock_object('volume') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert not exc.value.args[0]['changed'] + + def test_successful_delete(self): + ''' Test delete existing volume ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_delete_idempotency(self): + ''' Test delete idempotency ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object().apply() + assert not exc.value.args[0]['changed'] + + def test_successful_modify_size(self): + ''' Test successful modify size ''' + data = self.mock_args() + data['size'] = 200 + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_modify_idempotency(self): + ''' Test modify idempotency ''' + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert not exc.value.args[0]['changed'] + + def test_mount_volume(self): + ''' Test mount volume ''' + data = self.mock_args() + data['junction_path'] = "/test123" + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_unmount_volume(self): + ''' Test unmount volume ''' + data = self.mock_args() + data['junction_path'] = "" + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify_space(self): + ''' Test successful modify space ''' + data = self.mock_args() + data['space_guarantee'] = 'volume' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify_state(self): + ''' Test successful modify state ''' + data = self.mock_args() + data['is_online'] = False + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify_unix_permissions(self): + ''' Test successful modify unix_permissions ''' + data = self.mock_args() + data['unix_permissions'] = '---rw-r-xr-x' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify_snapshot_policy(self): + ''' Test successful modify snapshot_policy ''' + data = self.mock_args() + data['snapshot_policy'] = 'default-1weekly' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_successful_move(self): + ''' Test successful modify aggregate ''' + data = self.mock_args() + data['aggregate_name'] = 'different_aggr' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible.modules.storage.netapp.na_ontap_volume.NetAppOntapVolume.change_volume_state') + @patch('ansible.modules.storage.netapp.na_ontap_volume.NetAppOntapVolume.volume_mount') + def test_modify_helper(self, mount_volume, change_state): + data = self.mock_args() + set_module_args(data) + modify = { + 'is_online': False, + 'junction_path': 'something' + } + obj = self.get_volume_mock_object('volume') + obj.modify_volume(modify) + change_state.assert_called_with() + mount_volume.assert_called_with() + + @patch('ansible.modules.storage.netapp.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_compare_chmod_value_true_1(self, get_volume): + data = self.mock_args() + data['unix_permissions'] = '------------' + set_module_args(data) + current = { + 'unix_permissions': '0' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object() + assert obj.compare_chmod_value(current) + + @patch('ansible.modules.storage.netapp.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_compare_chmod_value_true_2(self, get_volume): + data = self.mock_args() + data['unix_permissions'] = '---rwxrwxrwx' + set_module_args(data) + current = { + 'unix_permissions': '777' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object() + assert obj.compare_chmod_value(current) + + @patch('ansible.modules.storage.netapp.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_compare_chmod_value_true_3(self, get_volume): + data = self.mock_args() + data['unix_permissions'] = '---rwxr-xr-x' + set_module_args(data) + current = { + 'unix_permissions': '755' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object() + assert obj.compare_chmod_value(current) + + @patch('ansible.modules.storage.netapp.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_compare_chmod_value_true_3(self, get_volume): + data = self.mock_args() + data['unix_permissions'] = '755' + set_module_args(data) + current = { + 'unix_permissions': '755' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object() + assert obj.compare_chmod_value(current) + + @patch('ansible.modules.storage.netapp.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_compare_chmod_value_false_1(self, get_volume): + data = self.mock_args() + data['unix_permissions'] = '---rwxrwxrwx' + set_module_args(data) + current = { + 'unix_permissions': '0' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object() + assert not obj.compare_chmod_value(current) + + @patch('ansible.modules.storage.netapp.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_compare_chmod_value_false_2(self, get_volume): + data = self.mock_args() + data['unix_permissions'] = '---rwxrwxrwx' + set_module_args(data) + current = None + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object() + assert not obj.compare_chmod_value(current) + + @patch('ansible.modules.storage.netapp.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_compare_chmod_value_invalid_input_1(self, get_volume): + data = self.mock_args() + data['unix_permissions'] = '---xwrxwrxwr' + set_module_args(data) + current = { + 'unix_permissions': '777' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object() + assert not obj.compare_chmod_value(current) + + @patch('ansible.modules.storage.netapp.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_compare_chmod_value_invalid_input_2(self, get_volume): + data = self.mock_args() + data['unix_permissions'] = '---rwx-wx--a' + set_module_args(data) + current = { + 'unix_permissions': '0' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object() + assert not obj.compare_chmod_value(current) + + @patch('ansible.modules.storage.netapp.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_compare_chmod_value_invalid_input_3(self, get_volume): + data = self.mock_args() + data['unix_permissions'] = '---' + set_module_args(data) + current = { + 'unix_permissions': '0' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object() + assert not obj.compare_chmod_value(current)