Browse code

Added smn module (#54793)

zhongjun2 authored on 2019/04/04 16:55:34
Showing 3 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,370 @@
0
+#!/usr/bin/python
1
+# -*- coding: utf-8 -*-
2
+#
3
+# Copyright (C) 2019 Huawei
4
+# GNU General Public License v3.0+ (see COPYING or
5
+# https://www.gnu.org/licenses/gpl-3.0.txt)
6
+
7
+from __future__ import absolute_import, division, print_function
8
+__metaclass__ = type
9
+
10
+###############################################################################
11
+# Documentation
12
+###############################################################################
13
+
14
+ANSIBLE_METADATA = {'metadata_version': '1.1',
15
+                    'status': ["preview"],
16
+                    'supported_by': 'community'}
17
+
18
+DOCUMENTATION = '''
19
+---
20
+module: hwc_smn_topic
21
+description:
22
+    - Represents a SMN notification topic resource.
23
+short_description: Creates a resource of SMNTopic in Huaweicloud Cloud
24
+version_added: '2.8'
25
+author: Huawei Inc. (@huaweicloud)
26
+requirements:
27
+    - requests >= 2.18.4
28
+    - keystoneauth1 >= 3.6.0
29
+options:
30
+    state:
31
+        description:
32
+            - Whether the given object should exist in Huaweicloud Cloud.
33
+        type: str
34
+        choices: ['present', 'absent']
35
+        default: 'present'
36
+    display_name:
37
+        description:
38
+            - Topic display name, which is presented as the name of the email
39
+              sender in an email message. The topic display name contains a
40
+              maximum of 192 bytes.
41
+        type: str
42
+        required: false
43
+    name:
44
+        description:
45
+            - Name of the topic to be created. The topic name is a string of 1
46
+              to 256 characters. It must contain upper- or lower-case letters,
47
+              digits, hyphens (-), and underscores C(_), and must start with a
48
+              letter or digit.
49
+        type: str
50
+        required: true
51
+extends_documentation_fragment: hwc
52
+'''
53
+
54
+EXAMPLES = '''
55
+- name: create a smn topic
56
+  hwc_smn_topic:
57
+      identity_endpoint: "{{ identity_endpoint }}"
58
+      user_name: "{{ user_name }}"
59
+      password: "{{ password }}"
60
+      domain_name: "{{ domain_name }}"
61
+      project_name: "{{ project_name }}"
62
+      region: "{{ region }}"
63
+      name: "ansible_smn_topic_test"
64
+      state: present
65
+'''
66
+
67
+RETURN = '''
68
+create_time:
69
+    description:
70
+        - Time when the topic was created.
71
+    returned: success
72
+    type: str
73
+display_name:
74
+    description:
75
+        - Topic display name, which is presented as the name of the email
76
+          sender in an email message. The topic display name contains a
77
+          maximum of 192 bytes.
78
+    returned: success
79
+    type: str
80
+name:
81
+    description:
82
+        - Name of the topic to be created. The topic name is a string of 1
83
+          to 256 characters. It must contain upper- or lower-case letters,
84
+          digits, hyphens (-), and underscores C(_), and must start with a
85
+          letter or digit.
86
+    returned: success
87
+    type: str
88
+push_policy:
89
+    description:
90
+        - Message pushing policy. 0 indicates that the message sending
91
+          fails and the message is cached in the queue. 1 indicates that
92
+          the failed message is discarded.
93
+    returned: success
94
+    type: int
95
+topic_urn:
96
+    description:
97
+        - Resource identifier of a topic, which is unique.
98
+    returned: success
99
+    type: str
100
+update_time:
101
+    description:
102
+        - Time when the topic was updated.
103
+    returned: success
104
+    type: str
105
+'''
106
+
107
+###############################################################################
108
+# Imports
109
+###############################################################################
110
+
111
+from ansible.module_utils.hwc_utils import (HwcSession, HwcModule,
112
+                                            DictComparison, navigate_hash,
113
+                                            remove_nones_from_dict,
114
+                                            remove_empty_from_dict,
115
+                                            are_dicts_different)
116
+import json
117
+import re
118
+
119
+###############################################################################
120
+# Main
121
+###############################################################################
122
+
123
+
124
+def main():
125
+    """Main function"""
126
+
127
+    module = HwcModule(
128
+        argument_spec=dict(
129
+            state=dict(default='present', choices=['present', 'absent'],
130
+                       type='str'),
131
+            display_name=dict(type='str'),
132
+            name=dict(required=True, type='str')
133
+        ),
134
+        supports_check_mode=True,
135
+    )
136
+
137
+    session = HwcSession(module, "app")
138
+
139
+    state = module.params['state']
140
+
141
+    if not module.params.get("id"):
142
+        module.params['id'] = get_resource_id(session)
143
+
144
+    fetch = None
145
+    link = self_link(session)
146
+    # the link will include Nones if required format parameters are missed
147
+    if not re.search('/None/|/None$', link):
148
+        fetch = fetch_resource(session, link)
149
+    changed = False
150
+
151
+    if fetch:
152
+        if state == 'present':
153
+            expect = _get_resource_editable_properties(module)
154
+            current_state = response_to_hash(module, fetch)
155
+            if are_dicts_different(expect, current_state):
156
+                if not module.check_mode:
157
+                    fetch = update(session)
158
+                    fetch = response_to_hash(module, fetch)
159
+                changed = True
160
+            else:
161
+                fetch = current_state
162
+        else:
163
+            if not module.check_mode:
164
+                delete(session)
165
+                fetch = {}
166
+            changed = True
167
+    else:
168
+        if state == 'present':
169
+            if not module.check_mode:
170
+                fetch = create(session)
171
+                fetch = response_to_hash(module, fetch)
172
+            changed = True
173
+        else:
174
+            fetch = {}
175
+
176
+    fetch.update({'changed': changed})
177
+
178
+    module.exit_json(**fetch)
179
+
180
+
181
+def create(session):
182
+    link = collection(session)
183
+    module = session.module
184
+    success_codes = [201, 202]
185
+    r = return_if_object(module,
186
+                         session.post(link, create_resource_opts(module)),
187
+                         success_codes)
188
+
189
+    return get_resource(session, r)
190
+
191
+
192
+def update(session):
193
+    link = self_link(session)
194
+    success_codes = [201, 202]
195
+    module = session.module
196
+    r = return_if_object(module, session.put(link, update_resource_opts(module)), success_codes)
197
+
198
+    return get_resource(session, r)
199
+
200
+
201
+def delete(session):
202
+    link = self_link(session)
203
+    success_codes = [202, 204]
204
+    return_if_object(session.module, session.delete(link), success_codes, False)
205
+
206
+
207
+def link_wrapper(f):
208
+    def _wrapper(module, *args, **kwargs):
209
+        try:
210
+            return f(module, *args, **kwargs)
211
+        except KeyError as ex:
212
+            module.fail_json(
213
+                msg="Mapping keys(%s) are not found in generating link." % ex)
214
+
215
+    return _wrapper
216
+
217
+
218
+def return_if_object(module, response, success_codes, has_content=True):
219
+    code = response.status_code
220
+
221
+    # If not found, return nothing.
222
+    if code == 404:
223
+        return None
224
+
225
+    if not success_codes:
226
+        success_codes = [200, 201, 202, 203, 204, 205, 206, 207, 208, 226]
227
+    # If no content, return nothing.
228
+    if code in success_codes and not has_content:
229
+        return None
230
+
231
+    result = None
232
+    try:
233
+        result = response.json()
234
+    except getattr(json.decoder, 'JSONDecodeError', ValueError) as inst:
235
+        module.fail_json(msg="Invalid JSON response with error: %s" % inst)
236
+
237
+    if code not in success_codes:
238
+        msg = navigate_hash(result, ['message'])
239
+        if msg:
240
+            module.fail_json(msg=msg)
241
+        else:
242
+            module.fail_json(msg="operation failed, return code=%d" % code)
243
+
244
+    return result
245
+
246
+
247
+def fetch_resource(session, link, success_codes=None):
248
+    if not success_codes:
249
+        success_codes = [200]
250
+    return return_if_object(session.module, session.get(link), success_codes)
251
+
252
+
253
+def get_resource(session, result):
254
+    combined = session.module.params.copy()
255
+    combined['topic_urn'] = navigate_hash(result, ['topic_urn'])
256
+    url = 'notifications/topics/{topic_urn}'.format(**combined)
257
+
258
+    e = session.get_service_endpoint('compute')
259
+    url = e.replace("ecs", "smn") + url
260
+    return fetch_resource(session, url)
261
+
262
+
263
+def get_resource_id(session):
264
+    module = session.module
265
+    link = list_link(session, {'limit': 10, 'offset': '{offset}'})
266
+    p = {'offset': 0}
267
+    v = module.params.get('name')
268
+    ids = set()
269
+    while True:
270
+        r = fetch_resource(session, link.format(**p))
271
+        if r is None:
272
+            break
273
+        r = r.get('topics', [])
274
+        if r == []:
275
+            break
276
+        for i in r:
277
+            if i.get('name') == v:
278
+                ids.add(i.get('topic_urn'))
279
+        if len(ids) >= 2:
280
+            module.fail_json(msg="Multiple resources are found")
281
+
282
+        p['offset'] += 1
283
+
284
+    return ids.pop() if ids else None
285
+
286
+
287
+@link_wrapper
288
+def list_link(session, extra_data=None):
289
+    url = "{endpoint}notifications/topics?limit={limit}&offset={offset}"
290
+
291
+    combined = session.module.params.copy()
292
+    if extra_data:
293
+        combined.update(extra_data)
294
+
295
+    e = session.get_service_endpoint('compute')
296
+    combined['endpoint'] = e.replace("ecs", "smn")
297
+
298
+    return url.format(**combined)
299
+
300
+
301
+@link_wrapper
302
+def self_link(session):
303
+    url = "{endpoint}notifications/topics/{id}"
304
+
305
+    combined = session.module.params.copy()
306
+
307
+    e = session.get_service_endpoint('compute')
308
+    combined['endpoint'] = e.replace("ecs", "smn")
309
+
310
+    return url.format(**combined)
311
+
312
+
313
+@link_wrapper
314
+def collection(session):
315
+    url = "{endpoint}notifications/topics"
316
+
317
+    combined = session.module.params.copy()
318
+
319
+    e = session.get_service_endpoint('compute')
320
+    combined['endpoint'] = e.replace("ecs", "smn")
321
+
322
+    return url.format(**combined)
323
+
324
+
325
+def create_resource_opts(module):
326
+    request = remove_empty_from_dict({
327
+        u'display_name': module.params.get('display_name'),
328
+        u'name': module.params.get('name')
329
+    })
330
+    return request
331
+
332
+
333
+def update_resource_opts(module):
334
+    request = remove_nones_from_dict({
335
+        u'display_name': module.params.get('display_name')
336
+    })
337
+    return request
338
+
339
+
340
+def _get_resource_editable_properties(module):
341
+    return remove_nones_from_dict({
342
+        "display_name": module.params.get("display_name"),
343
+    })
344
+
345
+
346
+def response_to_hash(module, response):
347
+    """Remove unnecessary properties from the response.
348
+       This is for doing comparisons with Ansible's current parameters.
349
+    """
350
+    return {
351
+        u'create_time': response.get(u'create_time'),
352
+        u'display_name': response.get(u'display_name'),
353
+        u'name': response.get(u'name'),
354
+        u'push_policy': _push_policy_convert_from_response(
355
+            response.get('push_policy')),
356
+        u'topic_urn': response.get(u'topic_urn'),
357
+        u'update_time': response.get(u'update_time')
358
+    }
359
+
360
+
361
+def _push_policy_convert_from_response(value):
362
+    return {
363
+        0: "the message sending fails and is cached in the queue",
364
+        1: "the failed message is discarded",
365
+    }.get(int(value))
366
+
367
+
368
+if __name__ == '__main__':
369
+    main()
0 370
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+unsupported
0 1
new file mode 100644
... ...
@@ -0,0 +1,76 @@
0
+- name: delete a smn topic
1
+  hwc_smn_topic:
2
+      identity_endpoint: "{{ identity_endpoint }}"
3
+      user: "{{ user }}"
4
+      password: "{{ password }}"
5
+      domain: "{{ domain }}"
6
+      project: "{{ project }}"
7
+      region: "{{ region }}"
8
+      name: "ansible_smn_topic_test"
9
+      state: absent
10
+#----------------------------------------------------------
11
+- name: create a smn topic
12
+  hwc_smn_topic:
13
+      identity_endpoint: "{{ identity_endpoint }}"
14
+      user: "{{ user }}"
15
+      password: "{{ password }}"
16
+      domain: "{{ domain }}"
17
+      project: "{{ project }}"
18
+      region: "{{ region }}"
19
+      name: "ansible_smn_topic_test"
20
+      state: present
21
+  register: result
22
+- name: assert changed is true
23
+  assert:
24
+    that:
25
+      - result is changed
26
+# ----------------------------------------------------------------------------
27
+- name: create a smn topic that already exists
28
+  hwc_smn_topic:
29
+      identity_endpoint: "{{ identity_endpoint }}"
30
+      user: "{{ user }}"
31
+      password: "{{ password }}"
32
+      domain: "{{ domain }}"
33
+      project: "{{ project }}"
34
+      region: "{{ region }}"
35
+      name: "ansible_smn_topic_test"
36
+      state: present
37
+  register: result
38
+- name: assert changed is false
39
+  assert:
40
+    that:
41
+      - result.failed == 0
42
+      - result.changed == false
43
+#----------------------------------------------------------
44
+- name: delete a smn topic
45
+  hwc_smn_topic:
46
+      identity_endpoint: "{{ identity_endpoint }}"
47
+      user: "{{ user }}"
48
+      password: "{{ password }}"
49
+      domain: "{{ domain }}"
50
+      project: "{{ project }}"
51
+      region: "{{ region }}"
52
+      name: "ansible_smn_topic_test"
53
+      state: absent
54
+  register: result
55
+- name: assert changed is true
56
+  assert:
57
+    that:
58
+      - result is changed
59
+# ----------------------------------------------------------------------------
60
+- name: delete a smn topic that does not exist
61
+  hwc_smn_topic:
62
+      identity_endpoint: "{{ identity_endpoint }}"
63
+      user: "{{ user }}"
64
+      password: "{{ password }}"
65
+      domain: "{{ domain }}"
66
+      project: "{{ project }}"
67
+      region: "{{ region }}"
68
+      name: "ansible_smn_topic_test"
69
+      state: absent
70
+  register: result
71
+- name: assert changed is false
72
+  assert:
73
+    that:
74
+      - result.failed == 0
75
+      - result.changed == false