1#!/usr/bin/python 2# (c) 2018, NetApp, Inc 3# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 4 5from __future__ import absolute_import, division, print_function 6__metaclass__ = type 7 8 9ANSIBLE_METADATA = {'metadata_version': '1.1', 10 'status': ['preview'], 11 'supported_by': 'certified'} 12 13 14DOCUMENTATION = ''' 15 16module: na_elementsw_cluster_pair 17 18short_description: NetApp Element Software Manage Cluster Pair 19extends_documentation_fragment: 20 - netapp.solidfire 21version_added: '2.7' 22author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com> 23description: 24- Create, delete cluster pair 25 26options: 27 28 state: 29 description: 30 - Whether the specified cluster pair should exist or not. 31 choices: ['present', 'absent'] 32 default: present 33 34 dest_mvip: 35 description: 36 - Destination IP address of the cluster to be paired. 37 required: true 38 39 dest_username: 40 description: 41 - Destination username for the cluster to be paired. 42 - Optional if this is same as source cluster username. 43 44 dest_password: 45 description: 46 - Destination password for the cluster to be paired. 47 - Optional if this is same as source cluster password. 48 49''' 50 51EXAMPLES = """ 52 - name: Create cluster pair 53 na_elementsw_cluster_pair: 54 hostname: "{{ src_hostname }}" 55 username: "{{ src_username }}" 56 password: "{{ src_password }}" 57 state: present 58 dest_mvip: "{{ dest_hostname }}" 59 60 - name: Delete cluster pair 61 na_elementsw_cluster_pair: 62 hostname: "{{ src_hostname }}" 63 username: "{{ src_username }}" 64 password: "{{ src_password }}" 65 state: absent 66 dest_mvip: "{{ dest_hostname }}" 67 dest_username: "{{ dest_username }}" 68 dest_password: "{{ dest_password }}" 69 70""" 71 72RETURN = """ 73 74""" 75 76from ansible.module_utils.basic import AnsibleModule 77from ansible.module_utils._text import to_native 78import ansible.module_utils.netapp as netapp_utils 79from ansible.module_utils.netapp_elementsw_module import NaElementSWModule 80from ansible.module_utils.netapp_module import NetAppModule 81 82HAS_SF_SDK = netapp_utils.has_sf_sdk() 83try: 84 import solidfire.common 85except ImportError: 86 HAS_SF_SDK = False 87 88 89class ElementSWClusterPair(object): 90 """ class to handle cluster pairing operations """ 91 92 def __init__(self): 93 """ 94 Setup Ansible parameters and ElementSW connection 95 """ 96 self.argument_spec = netapp_utils.ontap_sf_host_argument_spec() 97 self.argument_spec.update(dict( 98 state=dict(required=False, choices=['present', 'absent'], 99 default='present'), 100 dest_mvip=dict(required=True, type='str'), 101 dest_username=dict(required=False, type='str'), 102 dest_password=dict(required=False, type='str', no_log=True) 103 )) 104 105 self.module = AnsibleModule( 106 argument_spec=self.argument_spec, 107 supports_check_mode=True 108 ) 109 110 if HAS_SF_SDK is False: 111 self.module.fail_json(msg="Unable to import the SolidFire Python SDK") 112 else: 113 self.elem = netapp_utils.create_sf_connection(module=self.module) 114 115 self.elementsw_helper = NaElementSWModule(self.elem) 116 self.na_helper = NetAppModule() 117 self.parameters = self.na_helper.set_parameters(self.module.params) 118 # get element_sw_connection for destination cluster 119 # overwrite existing source host, user and password with destination credentials 120 self.module.params['hostname'] = self.parameters['dest_mvip'] 121 # username and password is same as source, 122 # if dest_username and dest_password aren't specified 123 if self.parameters.get('dest_username'): 124 self.module.params['username'] = self.parameters['dest_username'] 125 if self.parameters.get('dest_password'): 126 self.module.params['password'] = self.parameters['dest_password'] 127 self.dest_elem = netapp_utils.create_sf_connection(module=self.module) 128 self.dest_elementsw_helper = NaElementSWModule(self.dest_elem) 129 130 def check_if_already_paired(self, paired_clusters, hostname): 131 for pair in paired_clusters.cluster_pairs: 132 if pair.mvip == hostname: 133 return pair.cluster_pair_id 134 return None 135 136 def get_src_pair_id(self): 137 """ 138 Check for idempotency 139 """ 140 # src cluster and dest cluster exist 141 paired_clusters = self.elem.list_cluster_pairs() 142 return self.check_if_already_paired(paired_clusters, self.parameters['dest_mvip']) 143 144 def get_dest_pair_id(self): 145 """ 146 Getting destination cluster_pair_id 147 """ 148 paired_clusters = self.dest_elem.list_cluster_pairs() 149 return self.check_if_already_paired(paired_clusters, self.parameters['hostname']) 150 151 def pair_clusters(self): 152 """ 153 Start cluster pairing on source, and complete on target cluster 154 """ 155 try: 156 pair_key = self.elem.start_cluster_pairing() 157 self.dest_elem.complete_cluster_pairing( 158 cluster_pairing_key=pair_key.cluster_pairing_key) 159 except solidfire.common.ApiServerError as err: 160 self.module.fail_json(msg="Error pairing cluster %s and %s" 161 % (self.parameters['hostname'], 162 self.parameters['dest_mvip']), 163 exception=to_native(err)) 164 165 def unpair_clusters(self, pair_id_source, pair_id_dest): 166 """ 167 Delete cluster pair 168 """ 169 try: 170 self.elem.remove_cluster_pair(cluster_pair_id=pair_id_source) 171 self.dest_elem.remove_cluster_pair(cluster_pair_id=pair_id_dest) 172 except solidfire.common.ApiServerError as err: 173 self.module.fail_json(msg="Error unpairing cluster %s and %s" 174 % (self.parameters['hostname'], 175 self.parameters['dest_mvip']), 176 exception=to_native(err)) 177 178 def apply(self): 179 """ 180 Call create / delete cluster pair methods 181 """ 182 pair_id_source = self.get_src_pair_id() 183 # If already paired, find the cluster_pair_id of destination cluster 184 if pair_id_source: 185 pair_id_dest = self.get_dest_pair_id() 186 # calling helper to determine action 187 cd_action = self.na_helper.get_cd_action(pair_id_source, self.parameters) 188 if cd_action == "create": 189 self.pair_clusters() 190 elif cd_action == "delete": 191 self.unpair_clusters(pair_id_source, pair_id_dest) 192 self.module.exit_json(changed=self.na_helper.changed) 193 194 195def main(): 196 """ Apply cluster pair actions """ 197 cluster_obj = ElementSWClusterPair() 198 cluster_obj.apply() 199 200 201if __name__ == '__main__': 202 main() 203