Repository URL to install this package:
|
Version:
6.0.0 ▾
|
#!/usr/bin/python
# (c) 2018-2022, 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
DOCUMENTATION = '''
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create/Delete cluster peer relations on ONTAP
extends_documentation_fragment:
- netapp.ontap.netapp.na_ontap
- netapp.ontap.netapp.na_ontap_peer
module: na_ontap_cluster_peer
options:
state:
choices: ['present', 'absent']
type: str
description:
- Whether the specified cluster peer should exist or not.
default: present
source_intercluster_lifs:
description:
- 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.0
type: list
elements: str
aliases:
- source_intercluster_lif
dest_intercluster_lifs:
description:
- 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.0
type: list
elements: str
aliases:
- dest_intercluster_lif
passphrase:
description:
- The arbitrary passphrase that matches the one given to the peer cluster.
type: str
source_cluster_name:
description:
- The name of the source cluster name in the peer relation to be deleted.
type: str
dest_cluster_name:
description:
- The name of the destination cluster name in the peer relation to be deleted.
- Required for delete
type: str
dest_hostname:
description:
- DEPRECATED - please use C(peer_options).
- Destination cluster IP or hostname which needs to be peered.
- Required to complete the peering process at destination cluster.
type: str
dest_username:
description:
- DEPRECATED - please use C(peer_options).
- Destination username.
- Optional if this is same as source username or if a certificate is used.
type: str
dest_password:
description:
- DEPRECATED - please use C(peer_options).
- Destination password.
- Optional if this is same as source password or if a certificate is used..
type: str
ipspace:
description:
- IPspace of the local intercluster LIFs.
- Assumes Default IPspace if not provided.
type: str
version_added: '20.11.0'
encryption_protocol_proposed:
description:
- Encryption protocol to be used for inter-cluster communication.
- Only available on ONTAP 9.5 or later.
choices: ['tls_psk', 'none']
type: str
version_added: '20.5.0'
short_description: NetApp ONTAP Manage Cluster peering
version_added: 2.7.0
'''
EXAMPLES = """
- name: Create cluster peer
netapp.ontap.na_ontap_cluster_peer:
state: present
source_intercluster_lifs: 1.2.3.4,1.2.3.5
dest_intercluster_lifs: 1.2.3.6,1.2.3.7
passphrase: XXXX
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
peer_options:
hostname: "{{ dest_netapp_hostname }}"
encryption_protocol_proposed: tls_psk
- name: Delete cluster peer
netapp.ontap.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 }}"
peer_options:
hostname: "{{ dest_netapp_hostname }}"
- name: Create cluster peer - different credentials
netapp.ontap.na_ontap_cluster_peer:
state: present
source_intercluster_lifs: 1.2.3.4,1.2.3.5
dest_intercluster_lifs: 1.2.3.6,1.2.3.7
passphrase: XXXX
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
peer_options:
hostname: "{{ dest_netapp_hostname }}"
cert_filepath: "{{ cert_filepath }}"
key_filepath: "{{ key_filepath }}"
encryption_protocol_proposed: tls_psk
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
from ansible_collections.netapp.ontap.plugins.module_utils.netapp_module import NetAppModule
from ansible_collections.netapp.ontap.plugins.module_utils.netapp import OntapRestAPI
from ansible_collections.netapp.ontap.plugins.module_utils import rest_generic
class NetAppONTAPClusterPeer:
"""
Class with cluster peer methods
"""
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', elements='str', aliases=['source_intercluster_lif']),
dest_intercluster_lifs=dict(required=False, type='list', elements='str', aliases=['dest_intercluster_lif']),
passphrase=dict(required=False, type='str', no_log=True),
peer_options=dict(type='dict', options=netapp_utils.na_ontap_host_argument_spec_peer()),
dest_hostname=dict(required=False, 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'),
ipspace=dict(required=False, type='str'),
encryption_protocol_proposed=dict(required=False, type='str', choices=['tls_psk', 'none'])
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
mutually_exclusive=[
['peer_options', 'dest_hostname'],
['peer_options', 'dest_username'],
['peer_options', 'dest_password']
],
required_one_of=[['peer_options', 'dest_hostname']],
required_if=[
('state', 'absent', ['source_cluster_name', 'dest_cluster_name']),
('state', 'present', ['source_intercluster_lifs', 'dest_intercluster_lifs'])
],
supports_check_mode=True
)
self.generated_passphrase = None
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
# set peer server connection
if self.parameters.get('dest_hostname') is not None:
# if dest_hostname is present, peer_options is absent
self.parameters['peer_options'] = dict(
hostname=self.parameters.get('dest_hostname'),
username=self.parameters.get('dest_username'),
password=self.parameters.get('dest_password'),
)
netapp_utils.setup_host_options_from_module_params(
self.parameters['peer_options'], self.module,
netapp_utils.na_ontap_host_argument_spec_peer().keys())
self.use_rest = False
self.rest_api = OntapRestAPI(self.module)
self.src_use_rest = self.rest_api.is_rest()
self.dst_rest_api = OntapRestAPI(self.module, host_options=self.parameters['peer_options'])
self.dst_use_rest = self.dst_rest_api.is_rest()
self.use_rest = bool(self.src_use_rest and self.dst_use_rest)
if not self.use_rest:
if not netapp_utils.has_netapp_lib():
self.module.fail_json(msg="the python NetApp-Lib module is required")
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
self.dest_server = netapp_utils.setup_na_ontap_zapi(module=self.module, host_options=self.parameters['peer_options'])
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')
peer_lifs, peer_cluster = self.get_peer_lifs_cluster_keys(cluster)
if self.parameters.get(peer_lifs):
peer_addresses = netapp_utils.zapi.NaElement('peer-addresses')
for peer in self.parameters.get(peer_lifs):
peer_addresses.add_new_child('remote-inet-address', peer)
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
"""
if self.use_rest:
return self.cluster_peer_get_rest(cluster)
cluster_peer_get_iter = self.cluster_peer_get_iter(cluster)
result, cluster_info = None, 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'
% (cluster, 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 get_peer_lifs_cluster_keys(self, cluster):
if cluster == 'source':
return 'dest_intercluster_lifs', 'dest_cluster_name'
return 'source_intercluster_lifs', 'source_cluster_name'
def cluster_peer_get_rest(self, cluster):
api = 'cluster/peers'
fields = 'remote'
restapi = self.rest_api if cluster == 'source' else self.dst_rest_api
records, error = rest_generic.get_0_or_more_records(restapi, api, None, fields)
if error:
self.module.fail_json(msg=error)
cluster_info = {}
if records is not None:
peer_lifs, peer_cluster = self.get_peer_lifs_cluster_keys(cluster)
for record in records:
if 'remote' in record:
peer_cluster_exist, peer_addresses_exist = False, False
# check peer lif or peer cluster present in each peer cluster data in current.
# if peer-lifs not present in parameters, use peer_cluster to filter desired cluster peer in current.
if self.parameters.get(peer_lifs) is not None:
peer_addresses_exist = set(self.parameters[peer_lifs]) == set(record['remote']['ip_addresses'])
else:
peer_cluster_exist = self.parameters[peer_cluster] == record['remote']['name']
if peer_addresses_exist or peer_cluster_exist:
cluster_info['cluster_name'] = record['remote']['name']
cluster_info['peer-addresses'] = record['remote']['ip_addresses']
cluster_info['uuid'] = record['uuid']
return cluster_info
return None
def cluster_peer_delete(self, cluster, uuid=None):
"""
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 self.use_rest:
return self.cluster_peer_delete_rest(cluster, uuid)
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_delete_rest(self, cluster, uuid):
server = self.rest_api if cluster == 'source' else self.dst_rest_api
dummy, error = rest_generic.delete_async(server, 'cluster/peers', uuid)
if error:
self.module.fail_json(msg=error)
def cluster_peer_create(self, cluster):
"""
Create a cluster peer on source or destination
For source cluster, peer addresses = destination inter-cluster LIFs and vice-versa
:param cluster: type of cluster (source or destination)
:return: None
"""
if self.use_rest:
return self.cluster_peer_create_rest(cluster)
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')
server, peer_address = self.get_server_and_peer_address(cluster)
for each in peer_address:
peer_addresses.add_new_child('remote-inet-address', each)
cluster_peer_create.add_child_elem(peer_addresses)
if self.parameters.get('encryption_protocol_proposed') is not None:
cluster_peer_create.add_new_child('encryption-protocol-proposed', self.parameters['encryption_protocol_proposed'])
if self.parameters.get('ipspace') is not None:
cluster_peer_create.add_new_child('ipspace-name', self.parameters['ipspace'])
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 get_server_and_peer_address(self, cluster):
if cluster == 'source':
server = self.rest_api if self.use_rest else self.server
return server, self.parameters['dest_intercluster_lifs']
server = self.dst_rest_api if self.use_rest else self.dest_server
return server, self.parameters['source_intercluster_lifs']
def cluster_peer_create_rest(self, cluster):
api = 'cluster/peers'
body = {}
if self.parameters.get('passphrase') is not None:
body['authentication.passphrase'] = self.parameters['passphrase']
# generate passphrase in source if passphrase not provided.
elif cluster == 'source':
body['authentication.generate_passphrase'] = True
elif cluster == 'destination':
body['authentication.passphrase'] = self.generated_passphrase
server, peer_address = self.get_server_and_peer_address(cluster)
body['remote.ip_addresses'] = peer_address
if self.parameters.get('encryption_protocol_proposed') is not None:
body['encryption.proposed'] = self.parameters['encryption_protocol_proposed']
else:
# Default value for encryption.proposed is tls_psk.
# explicitly set to none if encryption_protocol_proposed options not present in parameters.
body['encryption.proposed'] = 'none'
if self.parameters.get('ipspace') is not None:
body['ipspace.name'] = self.parameters['ipspace']
response, error = rest_generic.post_async(server, api, body)
if error:
self.module.fail_json(msg=error)
if response and cluster == 'source' and 'passphrase' not in self.parameters:
for record in response['records']:
self.generated_passphrase = record['authentication']['passphrase']
def apply(self):
"""
Apply action to cluster peer
:return: None
"""
if not self.use_rest:
netapp_utils.ems_log_event_cserver("na_ontap_cluster_peer", self.server, self.module)
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':
if not self.module.check_mode:
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':
if not self.module.check_mode:
uuid = source['uuid'] if source and self.use_rest else None
self.cluster_peer_delete('source', uuid)
self.na_helper.changed = True
if destination_action == 'delete':
if not self.module.check_mode:
uuid = destination['uuid'] if destination and self.use_rest else None
self.cluster_peer_delete('destination', uuid)
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()