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

Allow for multiple LIFS instead of 1 at a time #54800

Merged
merged 2 commits into from
Apr 10, 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
44 changes: 31 additions & 13 deletions lib/ansible/modules/storage/netapp/na_ontap_cluster_peer.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
__metaclass__ = type
Expand All @@ -25,14 +25,20 @@
default: present
source_intercluster_lifs:
description:
- Intercluster addresses of the source cluster.
- List of intercluster addresses of the source cluster.
- Used as peer-addresses in destination cluster.
- All these intercluster lifs should belong to the source cluster.
version_added: "2.8"
aliases:
- source_intercluster_lif
dest_intercluster_lifs:
description:
- Intercluster addresses of the destination cluster.
- List of intercluster addresses of the destination cluster.
- Used as peer-addresses in source cluster.
- All these intercluster lifs should belong to the destination cluster.
version_added: "2.8"
aliases:
- dest_intercluster_lif
passphrase:
description:
- The arbitrary passphrase that matches the one given to the peer cluster.
Expand All @@ -42,9 +48,11 @@
dest_cluster_name:
description:
- The name of the destination cluster name in the peer relation to be deleted.
- Required for delete
dest_hostname:
description:
- Destination cluster IP or hostname which needs to be peered.
- Destination cluster IP or hostname which needs to be peered
- Required to complete the peering process at destination cluster.
required: True
dest_username:
description:
Expand Down Expand Up @@ -104,8 +112,8 @@ def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
source_intercluster_lifs=dict(required=False, type='list'),
dest_intercluster_lifs=dict(required=False, type='list'),
source_intercluster_lifs=dict(required=False, type='list', aliases=['source_intercluster_lif']),
dest_intercluster_lifs=dict(required=False, type='list', aliases=['dest_intercluster_lif']),
passphrase=dict(required=False, type='str', no_log=True),
dest_hostname=dict(required=True, type='str'),
dest_username=dict(required=False, type='str'),
Expand All @@ -116,7 +124,7 @@ def __init__(self):

self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_together=[['source_intercluster_lifs', 'dest_intercluster_lifs', 'passphrase']],
required_together=[['source_intercluster_lifs', 'dest_intercluster_lifs']],
required_if=[('state', 'absent', ['source_cluster_name', 'dest_cluster_name'])],
supports_check_mode=True
)
Expand Down Expand Up @@ -169,7 +177,7 @@ def cluster_peer_get(self, cluster):
:return: Dictionary of current cluster peer details if query successful, else return None
"""
cluster_peer_get_iter = self.cluster_peer_get_iter(cluster)
cluster_info = dict()
result, cluster_info = None, dict()
if cluster == 'source':
server = self.server
else:
Expand Down Expand Up @@ -217,8 +225,9 @@ def cluster_peer_create(self, cluster):
:param cluster: type of cluster (source or destination)
:return: None
"""
cluster_peer_create = netapp_utils.zapi.NaElement.create_node_with_children(
'cluster-peer-create', **{'passphrase': self.parameters['passphrase']})
cluster_peer_create = netapp_utils.zapi.NaElement.create_node_with_children('cluster-peer-create')
if self.parameters.get('passphrase') is not None:
cluster_peer_create.add_new_child('passphrase', self.parameters['passphrase'])
peer_addresses = netapp_utils.zapi.NaElement('peer-addresses')
if cluster == 'source':
server, peer_address = self.server, self.parameters['dest_intercluster_lifs']
Expand All @@ -239,9 +248,7 @@ def apply(self):
Apply action to cluster peer
:return: None
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_cluster_peer", cserver)
self.asup_log_for_cserver("na_ontap_cluster_peer")
source = self.cluster_peer_get('source')
destination = self.cluster_peer_get('destination')
source_action = self.na_helper.get_cd_action(source, self.parameters)
Expand All @@ -263,6 +270,17 @@ def apply(self):

self.module.exit_json(changed=self.na_helper.changed)

def asup_log_for_cserver(self, event_name):
"""
Fetch admin vserver for the given cluster
Create and Autosupport log event with the given module name
:param event_name: Name of the event log
:return: None
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event(event_name, cserver)


def main():
"""
Expand Down
211 changes: 211 additions & 0 deletions test/units/modules/storage/netapp/test_na_ontap_cluster_peer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
''' unit tests ONTAP Ansible module: na_ontap_cluster_peer '''

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_cluster_peer \
import NetAppONTAPClusterPeer as my_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, parm1=None):
''' save arguments '''
self.type = kind
self.data = parm1
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.type == 'cluster_peer':
xml = self.build_cluster_peer_info(self.data)
self.xml_out = xml
return xml

@staticmethod
def build_cluster_peer_info(parm1):
''' build xml data for vserser-info '''
xml = netapp_utils.zapi.NaElement('xml')
attributes = {
'num-records': 1,
'attributes-list': {
'cluster-peer-info': {
'cluster-name': parm1['dest_cluster_name'],
'peer-addresses': parm1['dest_intercluster_lifs']
}
}
}
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.server = MockONTAPConnection()
self.mock_cluster_peer = {
'source_intercluster_lifs': '1.2.3.4,1.2.3.5',
'dest_intercluster_lifs': '1.2.3.6,1.2.3.7',
'passphrase': 'netapp123',
'dest_hostname': '10.20.30.40',
'dest_cluster_name': 'cluster2',
'hostname': 'hostname',
'username': 'username',
'password': 'password',

}

def mock_args(self):
return {
'source_intercluster_lifs': self.mock_cluster_peer['source_intercluster_lifs'],
'dest_intercluster_lifs': self.mock_cluster_peer['dest_intercluster_lifs'],
'passphrase': self.mock_cluster_peer['passphrase'],
'dest_hostname': self.mock_cluster_peer['dest_hostname'],
'dest_cluster_name': 'cluster2',
'hostname': 'hostname',
'username': 'username',
'password': 'password',
}

def get_cluster_peer_mock_object(self, kind=None):
"""
Helper method to return an na_ontap_cluster_peer object
:param kind: passes this param to MockONTAPConnection()
:return: na_ontap_cluster_peer object
"""
cluster_peer_obj = my_module()
cluster_peer_obj.asup_log_for_cserver = Mock(return_value=None)
cluster_peer_obj.cluster = Mock()
cluster_peer_obj.cluster.invoke_successfully = Mock()
if kind is None:
cluster_peer_obj.server = MockONTAPConnection()
cluster_peer_obj.dest_server = MockONTAPConnection()
else:
cluster_peer_obj.server = MockONTAPConnection(kind=kind, parm1=self.mock_cluster_peer)
cluster_peer_obj.dest_server = MockONTAPConnection(kind=kind, parm1=self.mock_cluster_peer)
return cluster_peer_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({})
my_module()
print('Info: %s' % exc.value.args[0]['msg'])

@patch('ansible.modules.storage.netapp.na_ontap_cluster_peer.NetAppONTAPClusterPeer.cluster_peer_get')
def test_successful_create(self, cluster_peer_get):
''' Test successful create '''
set_module_args(self.mock_args())
cluster_peer_get.side_effect = [
None,
None
]
with pytest.raises(AnsibleExitJson) as exc:
self.get_cluster_peer_mock_object().apply()
assert exc.value.args[0]['changed']

@patch('ansible.modules.storage.netapp.na_ontap_cluster_peer.NetAppONTAPClusterPeer.cluster_peer_get')
def test_create_idempotency(self, cluster_peer_get):
''' Test create idempotency '''
set_module_args(self.mock_args())
current1 = {
'cluster_name': 'cluster1',
'peer-addresses': '1.2.3.6,1.2.3.7'
}
current2 = {
'cluster_name': 'cluster2',
'peer-addresses': '1.2.3.4,1.2.3.5'
}
cluster_peer_get.side_effect = [
current1,
current2
]
with pytest.raises(AnsibleExitJson) as exc:
self.get_cluster_peer_mock_object('cluster_peer').apply()
assert not exc.value.args[0]['changed']

@patch('ansible.modules.storage.netapp.na_ontap_cluster_peer.NetAppONTAPClusterPeer.cluster_peer_get')
def test_successful_delete(self, cluster_peer_get):
''' Test delete existing interface '''
data = self.mock_args()
data['state'] = 'absent'
data['source_cluster_name'] = 'cluster1'
set_module_args(data)
current1 = {
'cluster_name': 'cluster1',
'peer-addresses': '1.2.3.6,1.2.3.7'
}
current2 = {
'cluster_name': 'cluster2',
'peer-addresses': '1.2.3.4,1.2.3.5'
}
cluster_peer_get.side_effect = [
current1,
current2
]
with pytest.raises(AnsibleExitJson) as exc:
self.get_cluster_peer_mock_object('cluster_peer').apply()
assert exc.value.args[0]['changed']

@patch('ansible.modules.storage.netapp.na_ontap_cluster_peer.NetAppONTAPClusterPeer.cluster_peer_get')
def test_delete_idempotency(self, cluster_peer_get):
''' Test delete idempotency '''
data = self.mock_args()
data['state'] = 'absent'
data['source_cluster_name'] = 'cluster2'
set_module_args(data)
cluster_peer_get.side_effect = [
None,
None
]
with pytest.raises(AnsibleExitJson) as exc:
self.get_cluster_peer_mock_object().apply()
assert not exc.value.args[0]['changed']