Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tcp_max_xfer_size option for na_ontap_nfs, plus unit test #52288

Merged
merged 6 commits into from
Mar 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 16 additions & 4 deletions lib/ansible/modules/storage/netapp/na_ontap_nfs.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/python

# (c) 2018, NetApp, Inc
# (c) 2018-2019, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
Expand Down Expand Up @@ -103,6 +103,11 @@
- Whether SVM allows showmount
choices: ['enabled', 'disabled']
version_added: '2.7'
tcp_max_xfer_size:
description:
- TCP Maximum Transfer Size (bytes). The default value is 65536.
version_added: '2.8'
carchi8py marked this conversation as resolved.
Show resolved Hide resolved
type: int

"""

Expand Down Expand Up @@ -159,7 +164,8 @@ def __init__(self):
nfsv41_acl=dict(required=False, default=None, choices=['enabled', 'disabled']),
nfsv41_read_delegation=dict(required=False, default=None, choices=['enabled', 'disabled']),
nfsv41_write_delegation=dict(required=False, default=None, choices=['enabled', 'disabled']),
showmount=dict(required=False, default=None, choices=['enabled', 'disabled'])
showmount=dict(required=False, default=None, choices=['enabled', 'disabled']),
tcp_max_xfer_size=dict(required=False, default=None, type='int')
))

self.module = AnsibleModule(
Expand Down Expand Up @@ -188,6 +194,7 @@ def __init__(self):
self.nfsv41_read_delegation = parameters['nfsv41_read_delegation']
self.nfsv41_write_delegation = parameters['nfsv41_write_delegation']
self.showmount = parameters['showmount']
self.tcp_max_xfer_size = parameters['tcp_max_xfer_size']

if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
Expand Down Expand Up @@ -229,6 +236,7 @@ def get_nfs_service(self):
is_nfsv41_write_delegation_enabled = attributes_list.get_child_content('is-nfsv41-write-delegation-enabled')
is_nfsv41_read_delegation_enabled = attributes_list.get_child_content('is-nfsv41-read-delegation-enabled')
is_showmount_enabled = attributes_list.get_child_content('showmount')
tcp_max_xfer_size = attributes_list.get_child_content('tcp-max-xfer-size')
nfs_details = {
'is_nfsv3_enabled': is_nfsv3_enabled,
'is_nfsv3_fsid_change_enabled': is_nfsv3_fsid_change_enabled,
Expand All @@ -244,7 +252,8 @@ def get_nfs_service(self):
'is_nfsv41_acl_enabled': is_nfsv41_acl_enabled,
'is_nfsv41_write_delegation_enabled': is_nfsv41_write_delegation_enabled,
'is_nfsv41_read_delegation_enabled': is_nfsv41_read_delegation_enabled,
'is_showmount_enabled': is_showmount_enabled
'is_showmount_enabled': is_showmount_enabled,
'tcp_max_xfer_size': tcp_max_xfer_size
}
return nfs_details

Expand Down Expand Up @@ -350,6 +359,8 @@ def modify_nfs(self):
nfs_modify.add_new_child('showmount', 'true')
elif self.showmount == 'disabled':
nfs_modify.add_new_child('showmount', 'false')
if self.tcp_max_xfer_size is not None:
nfs_modify.add_new_child('tcp-max-xfer-size', str(self.tcp_max_xfer_size))
try:
self.server.invoke_successfully(nfs_modify,
enable_tunneling=True)
Expand Down Expand Up @@ -418,7 +429,8 @@ def is_modify_needed():
((self.nfsv40_write_delegation is not None) and state_changed(self.nfsv40_write_delegation,
nfs_service_details['is_nfsv40_write_delegation_enabled'])) or
((self.showmount is not None) and state_changed(self.showmount, nfs_service_details['is_showmount_enabled'])) or
((self.vstorage_state is not None) and state_changed(self.vstorage_state, nfs_service_details['is_vstorage_enabled']))):
((self.vstorage_state is not None) and state_changed(self.vstorage_state, nfs_service_details['is_vstorage_enabled'])) or
((self.tcp_max_xfer_size is not None) and int(self.tcp_max_xfer_size) != int(nfs_service_details['tcp_max_xfer_size']))):
return True
return False

Expand Down
308 changes: 308 additions & 0 deletions test/units/modules/storage/netapp/test_na_ontap_nfs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
# (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_nfs \
import NetAppONTAPNFS as nfs_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, job_error=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 == 'nfs':
xml = self.build_nfs_info(self.params)
self.xml_out = xml
if self.kind == 'nfs_status':
xml = self.build_nfs_status_info(self.params)
return xml

@staticmethod
def build_nfs_info(nfs_details):
''' build xml data for volume-attributes '''
xml = netapp_utils.zapi.NaElement('xml')
attributes = {
"attributes-list": {
"nfs-info": {
"auth-sys-extended-groups": "false",
"cached-cred-harvest-timeout": "86400000",
"cached-cred-negative-ttl": "7200000",
"cached-cred-positive-ttl": "86400000",
"cached-transient-err-ttl": "30000",
"chown-mode": "use_export_policy",
"enable-ejukebox": "true",
"extended-groups-limit": "32",
"file-session-io-grouping-count": "5000",
"file-session-io-grouping-duration": "120",
"ignore-nt-acl-for-root": "false",
"is-checksum-enabled-for-replay-cache": "true",
"is-mount-rootonly-enabled": "true",
"is-netgroup-dns-domain-search": "true",
"is-nfs-access-enabled": "false",
"is-nfs-rootonly-enabled": "false",
"is-nfsv2-enabled": "false",
"is-nfsv3-64bit-identifiers-enabled": "false",
"is-nfsv3-connection-drop-enabled": "true",
"is-nfsv3-enabled": "true",
"is-nfsv3-fsid-change-enabled": "true",
"is-nfsv4-fsid-change-enabled": "true",
"is-nfsv4-numeric-ids-enabled": "true",
"is-nfsv40-acl-enabled": "false",
"is-nfsv40-enabled": "true",
"is-nfsv40-migration-enabled": "false",
"is-nfsv40-read-delegation-enabled": "false",
"is-nfsv40-referrals-enabled": "false",
"is-nfsv40-req-open-confirm-enabled": "false",
"is-nfsv40-write-delegation-enabled": "false",
"is-nfsv41-acl-enabled": "false",
"is-nfsv41-acl-preserve-enabled": "true",
"is-nfsv41-enabled": "true",
"is-nfsv41-migration-enabled": "false",
"is-nfsv41-pnfs-enabled": "true",
"is-nfsv41-read-delegation-enabled": "false",
"is-nfsv41-referrals-enabled": "false",
"is-nfsv41-state-protection-enabled": "true",
"is-nfsv41-write-delegation-enabled": "false",
"is-qtree-export-enabled": "false",
"is-rquota-enabled": "false",
"is-tcp-enabled": "false",
"is-udp-enabled": "false",
"is-v3-ms-dos-client-enabled": "false",
"is-validate-qtree-export-enabled": "true",
"is-vstorage-enabled": "false",
"map-unknown-uid-to-default-windows-user": "true",
"mountd-port": "635",
"name-service-lookup-protocol": "udp",
"netgroup-trust-any-ns-switch-no-match": "false",
"nfsv4-acl-max-aces": "400",
"nfsv4-grace-seconds": "45",
"nfsv4-id-domain": "defaultv4iddomain.com",
"nfsv4-lease-seconds": "30",
"nfsv41-implementation-id-domain": "netapp.com",
"nfsv41-implementation-id-name": "NetApp Release Kalyaniblack__9.4.0",
"nfsv41-implementation-id-time": "1541070767",
"nfsv4x-session-num-slots": "180",
"nfsv4x-session-slot-reply-cache-size": "640",
"nlm-port": "4045",
"nsm-port": "4046",
"ntacl-display-permissive-perms": "false",
"ntfs-unix-security-ops": "use_export_policy",
"permitted-enc-types": {
"string": ["des", "des3", "aes_128", "aes_256"]
},
"rpcsec-ctx-high": "0",
"rpcsec-ctx-idle": "0",
"rquotad-port": "4049",
"showmount": "true",
"showmount-timestamp": "1548372452",
"skip-root-owner-write-perm-check": "false",
"tcp-max-xfer-size": "1048576",
"udp-max-xfer-size": "32768",
"v3-search-unconverted-filename": "false",
"v4-inherited-acl-preserve": "false",
"vserver": "ansible"
}
},
"num-records": "1"
}
xml.translate_struct(attributes)
return xml

@staticmethod
def build_nfs_status_info(nfs_status_details):
''' build xml data for volume-attributes '''
xml = netapp_utils.zapi.NaElement('xml')
attributes = {
'is-enabled': "true"
}
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_nfs_group = {
'vserver': 'nfs_vserver',
}

def mock_args(self):
return {
'vserver': self.mock_nfs_group['vserver'],
'hostname': 'test',
'username': 'test_user',
'password': 'test_pass!',
'https': 'False'
}

def get_nfs_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
"""
nfsy_obj = nfs_module()
nfsy_obj.asup_log_for_cserver = Mock(return_value=None)
nfsy_obj.cluster = Mock()
nfsy_obj.cluster.invoke_successfully = Mock()
if kind is None:
nfsy_obj.server = MockONTAPConnection()
else:
nfsy_obj.server = MockONTAPConnection(kind=kind, data=self.mock_nfs_group)
return nfsy_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({})
nfs_module()
print('Info: %s' % exc.value.args[0]['msg'])

def test_get_nonexistent_nfs(self):
''' Test if get_nfs_service returns None for non-existent nfs '''
set_module_args(self.mock_args())
result = self.get_nfs_mock_object().get_nfs_service()
assert result is None

def test_get_existing_nfs(self):
''' Test if get_policy_group returns details for existing nfs '''
set_module_args(self.mock_args())
result = self.get_nfs_mock_object('nfs').get_nfs_service()
assert result['is_nfsv3_enabled']

def test_get_nonexistent_nfs_status(self):
''' Test if get__nfs_status returns None for non-existent nfs '''
set_module_args(self.mock_args())
result = self.get_nfs_mock_object().get_nfs_status()
assert result is None

def test_get_existing_nfs_status(self):
''' Test if get__nfs_status returns details for nfs '''
set_module_args(self.mock_args())
result = self.get_nfs_mock_object('nfs_status').get_nfs_status()
assert result

def test_modify_nfs(self):
''' Test if modify_nfs runs for existing nfs '''
data = self.mock_args()
data['nfsv3'] = 'enabled'
data['nfsv3_fsid_change'] = 'enabled'
data['nfsv4'] = 'enabled'
data['nfsv41'] = 'enabled'
data['vstorage_state'] = 'enabled'
data['tcp'] = 'enabled'
data['udp'] = 'enabled'
data['nfsv4_id_domain'] = 'nfsv4_id_domain'
data['nfsv40_acl'] = 'enabled'
data['nfsv40_read_delegation'] = 'enabled'
data['nfsv40_write_delegation'] = 'enabled'
data['nfsv41_acl'] = 'enabled'
data['nfsv41_read_delegation'] = 'enabled'
data['nfsv41_write_delegation'] = 'enabled'
data['showmount'] = 'enabled'
data['tcp_max_xfer_size'] = '1048576'
set_module_args(data)
self.get_nfs_mock_object('nfs_status').modify_nfs()

def test_successfully_modify_nfs(self):
''' Test modify nfs successful for modifying tcp max xfer size. '''
data = self.mock_args()
data['tcp_max_xfer_size'] = '8192'
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_nfs_mock_object('nfs').apply()
assert exc.value.args[0]['changed']

def test_modify_nfs_idempotency(self):
''' Test modify nfs idempotency '''
data = self.mock_args()
data['tcp_max_xfer_size'] = '1048576'
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_nfs_mock_object('nfs').apply()
assert not exc.value.args[0]['changed']

@patch('ansible.modules.storage.netapp.na_ontap_nfs.NetAppONTAPNFS.delete_nfs')
def test_successfully_delete_nfs(self, delete_nfs):
''' Test successfully delete nfs '''
data = self.mock_args()
data['state'] = 'absent'
set_module_args(data)
obj = self.get_nfs_mock_object('nfs')
with pytest.raises(AnsibleExitJson) as exc:
obj.apply()
assert exc.value.args[0]['changed']
delete_nfs.assert_called_with()

@patch('ansible.modules.storage.netapp.na_ontap_nfs.NetAppONTAPNFS.get_nfs_service')
def test_successfully_enable_nfs(self, get_nfs_service):
''' Test successfully enable nfs on non-existent nfs '''
data = self.mock_args()
data['state'] = 'present'
set_module_args(data)
get_nfs_service.side_effect = [
None,
{}
]
obj = self.get_nfs_mock_object('nfs')
with pytest.raises(AnsibleExitJson) as exc:
obj.apply()
assert exc.value.args[0]['changed']