1#!/usr/local/bin/python3.8 2# (c) 2016, 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': ['deprecated'], 11 'supported_by': 'community'} 12 13 14DOCUMENTATION = """ 15--- 16module: netapp_e_amg 17short_description: NetApp E-Series create, remove, and update asynchronous mirror groups 18description: 19 - Allows for the creation, removal and updating of Asynchronous Mirror Groups for NetApp E-series storage arrays 20version_added: '2.2' 21author: Kevin Hulquest (@hulquest) 22extends_documentation_fragment: 23 - netapp_eseries.santricity.santricity.netapp.eseries 24options: 25 name: 26 description: 27 - The name of the async array you wish to target, or create. 28 - If C(state) is present and the name isn't found, it will attempt to create. 29 type: str 30 required: yes 31 new_name: 32 description: 33 - New async array name 34 type: str 35 required: no 36 secondaryArrayId: 37 description: 38 - The ID of the secondary array to be used in mirroring process 39 type: str 40 required: yes 41 syncIntervalMinutes: 42 description: 43 - The synchronization interval in minutes 44 type: int 45 default: 10 46 manualSync: 47 description: 48 - Setting this to true will cause other synchronization values to be ignored 49 type: bool 50 default: 'no' 51 recoveryWarnThresholdMinutes: 52 description: 53 - Recovery point warning threshold (minutes). The user will be warned when the age of the last good failures point exceeds this value 54 type: int 55 default: 20 56 repoUtilizationWarnThreshold: 57 description: 58 - Recovery point warning threshold 59 type: int 60 default: 80 61 interfaceType: 62 description: 63 - The intended protocol to use if both Fibre and iSCSI are available. 64 type: str 65 choices: 66 - iscsi 67 - fibre 68 syncWarnThresholdMinutes: 69 description: 70 - The threshold (in minutes) for notifying the user that periodic synchronization has taken too long to complete. 71 default: 10 72 type: int 73 state: 74 description: 75 - A C(state) of present will either create or update the async mirror group. 76 - A C(state) of absent will remove the async mirror group. 77 type: str 78 choices: [ absent, present ] 79 required: yes 80""" 81 82EXAMPLES = """ 83 - name: AMG removal 84 na_eseries_amg: 85 state: absent 86 ssid: "{{ ssid }}" 87 secondaryArrayId: "{{amg_secondaryArrayId}}" 88 api_url: "{{ netapp_api_url }}" 89 api_username: "{{ netapp_api_username }}" 90 api_password: "{{ netapp_api_password }}" 91 new_name: "{{amg_array_name}}" 92 name: "{{amg_name}}" 93 when: amg_create 94 95 - name: AMG create 96 netapp_e_amg: 97 state: present 98 ssid: "{{ ssid }}" 99 secondaryArrayId: "{{amg_secondaryArrayId}}" 100 api_url: "{{ netapp_api_url }}" 101 api_username: "{{ netapp_api_username }}" 102 api_password: "{{ netapp_api_password }}" 103 new_name: "{{amg_array_name}}" 104 name: "{{amg_name}}" 105 when: amg_create 106""" 107 108RETURN = """ 109msg: 110 description: Successful creation 111 returned: success 112 type: str 113 sample: '{"changed": true, "connectionType": "fc", "groupRef": "3700000060080E5000299C24000006E857AC7EEC", "groupState": "optimal", "id": "3700000060080E5000299C24000006E857AC7EEC", "label": "amg_made_by_ansible", "localRole": "primary", "mirrorChannelRemoteTarget": "9000000060080E5000299C24005B06E557AC7EEC", "orphanGroup": false, "recoveryPointAgeAlertThresholdMinutes": 20, "remoteRole": "secondary", "remoteTarget": {"nodeName": {"ioInterfaceType": "fc", "iscsiNodeName": null, "remoteNodeWWN": "20040080E5299F1C"}, "remoteRef": "9000000060080E5000299C24005B06E557AC7EEC", "scsiinitiatorTargetBaseProperties": {"ioInterfaceType": "fc", "iscsiinitiatorTargetBaseParameters": null}}, "remoteTargetId": "ansible2", "remoteTargetName": "Ansible2", "remoteTargetWwn": "60080E5000299F880000000056A25D56", "repositoryUtilizationWarnThreshold": 80, "roleChangeProgress": "none", "syncActivity": "idle", "syncCompletionTimeAlertThresholdMinutes": 10, "syncIntervalMinutes": 10, "worldWideName": "60080E5000299C24000006E857AC7EEC"}' 114""" # NOQA 115 116import json 117import traceback 118 119from ansible.module_utils.basic import AnsibleModule 120from ansible.module_utils._text import to_native 121from ansible_collections.netapp_eseries.santricity.plugins.module_utils.netapp import request, eseries_host_argument_spec 122 123 124HEADERS = { 125 "Content-Type": "application/json", 126 "Accept": "application/json", 127} 128 129 130def has_match(module, ssid, api_url, api_pwd, api_usr, body): 131 compare_keys = ['syncIntervalMinutes', 'syncWarnThresholdMinutes', 132 'recoveryWarnThresholdMinutes', 'repoUtilizationWarnThreshold'] 133 desired_state = dict((x, (body.get(x))) for x in compare_keys) 134 label_exists = False 135 matches_spec = False 136 current_state = None 137 async_id = None 138 api_data = None 139 desired_name = body.get('name') 140 endpoint = 'storage-systems/%s/async-mirrors' % ssid 141 url = api_url + endpoint 142 try: 143 rc, data = request(url, url_username=api_usr, url_password=api_pwd, headers=HEADERS) 144 except Exception as e: 145 module.exit_json(msg="Error finding a match. Message: %s" % to_native(e), exception=traceback.format_exc()) 146 147 for async_group in data: 148 if async_group['label'] == desired_name: 149 label_exists = True 150 api_data = async_group 151 async_id = async_group['groupRef'] 152 current_state = dict( 153 syncIntervalMinutes=async_group['syncIntervalMinutes'], 154 syncWarnThresholdMinutes=async_group['syncCompletionTimeAlertThresholdMinutes'], 155 recoveryWarnThresholdMinutes=async_group['recoveryPointAgeAlertThresholdMinutes'], 156 repoUtilizationWarnThreshold=async_group['repositoryUtilizationWarnThreshold'], 157 ) 158 159 if current_state == desired_state: 160 matches_spec = True 161 162 return label_exists, matches_spec, api_data, async_id 163 164 165def create_async(module, ssid, api_url, api_pwd, api_usr, body): 166 endpoint = 'storage-systems/%s/async-mirrors' % ssid 167 url = api_url + endpoint 168 post_data = json.dumps(body) 169 try: 170 rc, data = request(url, data=post_data, method='POST', url_username=api_usr, url_password=api_pwd, 171 headers=HEADERS) 172 except Exception as e: 173 module.exit_json(msg="Exception while creating aysnc mirror group. Message: %s" % to_native(e), 174 exception=traceback.format_exc()) 175 return data 176 177 178def update_async(module, ssid, api_url, pwd, user, body, new_name, async_id): 179 endpoint = 'storage-systems/%s/async-mirrors/%s' % (ssid, async_id) 180 url = api_url + endpoint 181 compare_keys = ['syncIntervalMinutes', 'syncWarnThresholdMinutes', 182 'recoveryWarnThresholdMinutes', 'repoUtilizationWarnThreshold'] 183 desired_state = dict((x, (body.get(x))) for x in compare_keys) 184 185 if new_name: 186 desired_state['new_name'] = new_name 187 188 post_data = json.dumps(desired_state) 189 190 try: 191 rc, data = request(url, data=post_data, method='POST', headers=HEADERS, 192 url_username=user, url_password=pwd) 193 except Exception as e: 194 module.exit_json(msg="Exception while updating async mirror group. Message: %s" % to_native(e), 195 exception=traceback.format_exc()) 196 197 return data 198 199 200def remove_amg(module, ssid, api_url, pwd, user, async_id): 201 endpoint = 'storage-systems/%s/async-mirrors/%s' % (ssid, async_id) 202 url = api_url + endpoint 203 try: 204 rc, data = request(url, method='DELETE', url_username=user, url_password=pwd, 205 headers=HEADERS) 206 except Exception as e: 207 module.exit_json(msg="Exception while removing async mirror group. Message: %s" % to_native(e), 208 exception=traceback.format_exc()) 209 210 return 211 212 213def main(): 214 argument_spec = eseries_host_argument_spec() 215 argument_spec.update(dict( 216 name=dict(required=True, type='str'), 217 new_name=dict(required=False, type='str'), 218 secondaryArrayId=dict(required=True, type='str'), 219 syncIntervalMinutes=dict(required=False, default=10, type='int'), 220 manualSync=dict(required=False, default=False, type='bool'), 221 recoveryWarnThresholdMinutes=dict(required=False, default=20, type='int'), 222 repoUtilizationWarnThreshold=dict(required=False, default=80, type='int'), 223 interfaceType=dict(required=False, choices=['fibre', 'iscsi'], type='str'), 224 state=dict(required=True, choices=['present', 'absent']), 225 syncWarnThresholdMinutes=dict(required=False, default=10, type='int') 226 )) 227 228 module = AnsibleModule(argument_spec=argument_spec) 229 230 p = module.params 231 232 ssid = p.pop('ssid') 233 api_url = p.pop('api_url') 234 user = p.pop('api_username') 235 pwd = p.pop('api_password') 236 new_name = p.pop('new_name') 237 state = p.pop('state') 238 239 if not api_url.endswith('/'): 240 api_url += '/' 241 242 name_exists, spec_matches, api_data, async_id = has_match(module, ssid, api_url, pwd, user, p) 243 244 if state == 'present': 245 if name_exists and spec_matches: 246 module.exit_json(changed=False, msg="Desired state met", **api_data) 247 elif name_exists and not spec_matches: 248 results = update_async(module, ssid, api_url, pwd, user, 249 p, new_name, async_id) 250 module.exit_json(changed=True, 251 msg="Async mirror group updated", async_id=async_id, 252 **results) 253 elif not name_exists: 254 results = create_async(module, ssid, api_url, user, pwd, p) 255 module.exit_json(changed=True, **results) 256 257 elif state == 'absent': 258 if name_exists: 259 remove_amg(module, ssid, api_url, pwd, user, async_id) 260 module.exit_json(changed=True, msg="Async mirror group removed.", 261 async_id=async_id) 262 else: 263 module.exit_json(changed=False, 264 msg="Async Mirror group: %s already absent" % p['name']) 265 266 267if __name__ == '__main__': 268 main() 269