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

Adding ontap_cluster_peer.py Modules #44189

Merged
merged 7 commits into from
Aug 28, 2018
Merged
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
268 changes: 268 additions & 0 deletions lib/ansible/modules/storage/netapp/na_ontap_cluster_peer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
#!/usr/bin/python

# (c) 2018, 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

ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}


DOCUMENTATION = '''
author: NetApp Ansible Team (ng-ansibleteam@netapp.com)
description:
- Create/Delete cluster peer relations on ONTAP
extends_documentation_fragment:
- netapp.ontap
module: na_ontap_cluster_peer
options:
state:
choices: ['present', 'absent']
description:
- Whether the specified cluster peer should exist or not.
default: present
source_intercluster_lif:
description:
- Intercluster address of the source cluster.
- Used as peer-address in destination cluster.
dest_intercluster_lif:
description:
- Intercluster address of the destination cluster.
- Used as peer-address in source cluster.
passphrase:
description:
- The arbitrary passphrase that matches the one given to the peer cluster.
source_cluster_name:
description:
- The name of the source cluster name in the peer relation to be deleted.
dest_cluster_name:
description:
- The name of the destination cluster name in the peer relation to be deleted.
dest_hostname:
description:
- Destination cluster IP or hostname which needs to be peered.
required: True
dest_username:
description:
- Destination username.
- Optional if this is same as source username.
dest_password:
description:
- Destination password.
- Optional if this is same as source password.
short_description: "NetApp ONTAP Manage Cluster peering"
version_added: "2.7"
'''

EXAMPLES = """

- name: Create cluster peer
na_ontap_cluster_peer:
state: present
source_intercluster_lif: 1.2.3.4
dest_intercluster_lif: 5.6.7.8
passphrase: XXXX
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
dest_hostname: "{{ dest_netapp_hostname }}"

- name: Delete cluster peer
na_ontap_cluster_peer:
state: absent
source_cluster_name: test-source-cluster
dest_cluster_name: test-dest-cluster
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
dest_hostname: "{{ dest_netapp_hostname }}"
"""

RETURN = """
"""

import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule

HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()


class NetAppONTAPClusterPeer(object):
"""
Class with cluster peer methods
"""

def __init__(self):

self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
source_intercluster_lif=dict(required=False, type='str'),
dest_intercluster_lif=dict(required=False, type='str'),
passphrase=dict(required=False, type='str', no_log=True),
dest_hostname=dict(required=True, type='str'),
dest_username=dict(required=False, type='str'),
dest_password=dict(required=False, type='str', no_log=True),
source_cluster_name=dict(required=False, type='str'),
dest_cluster_name=dict(required=False, type='str')
))

self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_together=[['source_intercluster_lif', 'dest_intercluster_lif', 'passphrase']],
required_if=[('state', 'absent', ['source_cluster_name', 'dest_cluster_name'])],
supports_check_mode=True
)

self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)

if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_ontap_zapi(module=self.module)
# set destination server connection
self.module.params['hostname'] = self.parameters['dest_hostname']
if self.parameters.get('dest_username'):
self.module.params['username'] = self.parameters['dest_username']
if self.parameters.get('dest_password'):
self.module.params['password'] = self.parameters['dest_password']
self.dest_server = netapp_utils.setup_ontap_zapi(module=self.module)

def cluster_peer_get_iter(self, cluster):
"""
Compose NaElement object to query current source cluster using peer-cluster-name and peer-addresses parameters
:param cluster: type of cluster (source or destination)
:return: NaElement object for cluster-get-iter with query
"""
cluster_peer_get = netapp_utils.zapi.NaElement('cluster-peer-get-iter')
query = netapp_utils.zapi.NaElement('query')
cluster_peer_info = netapp_utils.zapi.NaElement('cluster-peer-info')
if cluster == 'source':
peer_lif, peer_cluster = 'dest_intercluster_lif', 'dest_cluster_name'
else:
peer_lif, peer_cluster = 'source_intercluster_lif', 'source_cluster_name'
peer_addresses = netapp_utils.zapi.NaElement('peer-addresses')
if self.parameters.get(peer_lif):
peer_addresses.add_new_child('remote-inet-address', self.parameters[peer_lif])
cluster_peer_info.add_child_elem(peer_addresses)
if self.parameters.get(peer_cluster):
cluster_peer_info.add_new_child('cluster-name', self.parameters[peer_cluster])
query.add_child_elem(cluster_peer_info)
cluster_peer_get.add_child_elem(query)
return cluster_peer_get

def cluster_peer_get(self, cluster):
"""
Get current cluster peer info
:param cluster: type of cluster (source or destination)
: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()
if cluster == 'source':
server = self.server
else:
server = self.dest_server
try:
result = server.invoke_successfully(cluster_peer_get_iter, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching cluster peer %s: %s'
% (self.parameters['dest_cluster_name'], to_native(error)),
exception=traceback.format_exc())
# return cluster peer details
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) >= 1:
cluster_peer_info = result.get_child_by_name('attributes-list').get_child_by_name('cluster-peer-info')
cluster_info['cluster_name'] = cluster_peer_info.get_child_content('cluster-name')
peers = cluster_peer_info.get_child_by_name('peer-addresses')
cluster_info['peer-addresses'] = [peer.get_content() for peer in peers.get_children()]
return cluster_info
return None

def cluster_peer_delete(self, cluster):
"""
Delete a cluster peer on source or destination
For source cluster, peer cluster-name = destination cluster name and vice-versa
:param cluster: type of cluster (source or destination)
:return:
"""
if cluster == 'source':
server, peer_cluster_name = self.server, self.parameters['dest_cluster_name']
else:
server, peer_cluster_name = self.dest_server, self.parameters['source_cluster_name']
cluster_peer_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'cluster-peer-delete', **{'cluster-name': peer_cluster_name})
try:
server.invoke_successfully(cluster_peer_delete, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting cluster peer %s: %s'
% (peer_cluster_name, to_native(error)),
exception=traceback.format_exc())

def cluster_peer_create(self, cluster):
"""
Create a cluster peer on source or destination
For source cluster, peer address = destination inter-cluster LIF and vice-versa
: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']})
peer_addresses = netapp_utils.zapi.NaElement('peer-addresses')
if cluster == 'source':
server, peer_address = self.server, self.parameters['dest_intercluster_lif']
else:
server, peer_address = self.dest_server, self.parameters['source_intercluster_lif']
peer_addresses.add_new_child('remote-inet-address', peer_address)
cluster_peer_create.add_child_elem(peer_addresses)
try:
server.invoke_successfully(cluster_peer_create, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating cluster peer %s: %s'
% (peer_address, to_native(error)),
exception=traceback.format_exc())

def apply(self):
"""
Apply action to cluster peer
:return: None
"""
source = self.cluster_peer_get('source')
destination = self.cluster_peer_get('destination')
source_action = self.na_helper.get_cd_action(source, self.parameters)
destination_action = self.na_helper.get_cd_action(destination, self.parameters)
self.na_helper.changed = False
# create only if expected cluster peer relation is not present on both source and destination clusters
if source_action == 'create' and destination_action == 'create':
self.cluster_peer_create('source')
self.cluster_peer_create('destination')
self.na_helper.changed = True
# delete peer relation in cluster where relation is present
else:
if source_action == 'delete':
self.cluster_peer_delete('source')
self.na_helper.changed = True
if destination_action == 'delete':
self.cluster_peer_delete('destination')
self.na_helper.changed = True

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


def main():
"""
Execute action
:return: None
"""
community_obj = NetAppONTAPClusterPeer()
community_obj.apply()


if __name__ == '__main__':
main()