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