1#!/usr/bin/python
2# Copyright: Ansible Project
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': 'community'}
12
13
14DOCUMENTATION = '''
15---
16module: rax_mon_check
17short_description: Create or delete a Rackspace Cloud Monitoring check for an
18                   existing entity.
19description:
20- Create or delete a Rackspace Cloud Monitoring check associated with an
21  existing rax_mon_entity. A check is a specific test or measurement that is
22  performed, possibly from different monitoring zones, on the systems you
23  monitor. Rackspace monitoring module flow | rax_mon_entity ->
24  *rax_mon_check* -> rax_mon_notification -> rax_mon_notification_plan ->
25  rax_mon_alarm
26version_added: "2.0"
27options:
28  state:
29    description:
30    - Ensure that a check with this C(label) exists or does not exist.
31    choices: ["present", "absent"]
32  entity_id:
33    description:
34    - ID of the rax_mon_entity to target with this check.
35    required: true
36  label:
37    description:
38    - Defines a label for this check, between 1 and 64 characters long.
39    required: true
40  check_type:
41    description:
42    - The type of check to create. C(remote.) checks may be created on any
43      rax_mon_entity. C(agent.) checks may only be created on rax_mon_entities
44      that have a non-null C(agent_id).
45    choices:
46    - remote.dns
47    - remote.ftp-banner
48    - remote.http
49    - remote.imap-banner
50    - remote.mssql-banner
51    - remote.mysql-banner
52    - remote.ping
53    - remote.pop3-banner
54    - remote.postgresql-banner
55    - remote.smtp-banner
56    - remote.smtp
57    - remote.ssh
58    - remote.tcp
59    - remote.telnet-banner
60    - agent.filesystem
61    - agent.memory
62    - agent.load_average
63    - agent.cpu
64    - agent.disk
65    - agent.network
66    - agent.plugin
67    required: true
68  monitoring_zones_poll:
69    description:
70    - Comma-separated list of the names of the monitoring zones the check should
71      run from. Available monitoring zones include mzdfw, mzhkg, mziad, mzlon,
72      mzord and mzsyd. Required for remote.* checks; prohibited for agent.* checks.
73  target_hostname:
74    description:
75    - One of `target_hostname` and `target_alias` is required for remote.* checks,
76      but prohibited for agent.* checks. The hostname this check should target.
77      Must be a valid IPv4, IPv6, or FQDN.
78  target_alias:
79    description:
80    - One of `target_alias` and `target_hostname` is required for remote.* checks,
81      but prohibited for agent.* checks. Use the corresponding key in the entity's
82      `ip_addresses` hash to resolve an IP address to target.
83  details:
84    description:
85    - Additional details specific to the check type. Must be a hash of strings
86      between 1 and 255 characters long, or an array or object containing 0 to
87      256 items.
88  disabled:
89    description:
90    - If "yes", ensure the check is created, but don't actually use it yet.
91    type: bool
92  metadata:
93    description:
94    - Hash of arbitrary key-value pairs to accompany this check if it fires.
95      Keys and values must be strings between 1 and 255 characters long.
96  period:
97    description:
98    - The number of seconds between each time the check is performed. Must be
99      greater than the minimum period set on your account.
100  timeout:
101    description:
102    - The number of seconds this check will wait when attempting to collect
103      results. Must be less than the period.
104author: Ash Wilson (@smashwilson)
105extends_documentation_fragment: rackspace.openstack
106'''
107
108EXAMPLES = '''
109- name: Create a monitoring check
110  gather_facts: False
111  hosts: local
112  connection: local
113  tasks:
114  - name: Associate a check with an existing entity.
115    rax_mon_check:
116      credentials: ~/.rax_pub
117      state: present
118      entity_id: "{{ the_entity['entity']['id'] }}"
119      label: the_check
120      check_type: remote.ping
121      monitoring_zones_poll: mziad,mzord,mzdfw
122      details:
123        count: 10
124      meta:
125        hurf: durf
126    register: the_check
127'''
128
129try:
130    import pyrax
131    HAS_PYRAX = True
132except ImportError:
133    HAS_PYRAX = False
134
135from ansible.module_utils.basic import AnsibleModule
136from ansible.module_utils.rax import rax_argument_spec, rax_required_together, setup_rax_module
137
138
139def cloud_check(module, state, entity_id, label, check_type,
140                monitoring_zones_poll, target_hostname, target_alias, details,
141                disabled, metadata, period, timeout):
142
143    # Coerce attributes.
144
145    if monitoring_zones_poll and not isinstance(monitoring_zones_poll, list):
146        monitoring_zones_poll = [monitoring_zones_poll]
147
148    if period:
149        period = int(period)
150
151    if timeout:
152        timeout = int(timeout)
153
154    changed = False
155    check = None
156
157    cm = pyrax.cloud_monitoring
158    if not cm:
159        module.fail_json(msg='Failed to instantiate client. This typically '
160                             'indicates an invalid region or an incorrectly '
161                             'capitalized region name.')
162
163    entity = cm.get_entity(entity_id)
164    if not entity:
165        module.fail_json(msg='Failed to instantiate entity. "%s" may not be'
166                             ' a valid entity id.' % entity_id)
167
168    existing = [e for e in entity.list_checks() if e.label == label]
169
170    if existing:
171        check = existing[0]
172
173    if state == 'present':
174        if len(existing) > 1:
175            module.fail_json(msg='%s existing checks have a label of %s.' %
176                                 (len(existing), label))
177
178        should_delete = False
179        should_create = False
180        should_update = False
181
182        if check:
183            # Details may include keys set to default values that are not
184            # included in the initial creation.
185            #
186            # Only force a recreation of the check if one of the *specified*
187            # keys is missing or has a different value.
188            if details:
189                for (key, value) in details.items():
190                    if key not in check.details:
191                        should_delete = should_create = True
192                    elif value != check.details[key]:
193                        should_delete = should_create = True
194
195            should_update = label != check.label or \
196                (target_hostname and target_hostname != check.target_hostname) or \
197                (target_alias and target_alias != check.target_alias) or \
198                (disabled != check.disabled) or \
199                (metadata and metadata != check.metadata) or \
200                (period and period != check.period) or \
201                (timeout and timeout != check.timeout) or \
202                (monitoring_zones_poll and monitoring_zones_poll != check.monitoring_zones_poll)
203
204            if should_update and not should_delete:
205                check.update(label=label,
206                             disabled=disabled,
207                             metadata=metadata,
208                             monitoring_zones_poll=monitoring_zones_poll,
209                             timeout=timeout,
210                             period=period,
211                             target_alias=target_alias,
212                             target_hostname=target_hostname)
213                changed = True
214        else:
215            # The check doesn't exist yet.
216            should_create = True
217
218        if should_delete:
219            check.delete()
220
221        if should_create:
222            check = cm.create_check(entity,
223                                    label=label,
224                                    check_type=check_type,
225                                    target_hostname=target_hostname,
226                                    target_alias=target_alias,
227                                    monitoring_zones_poll=monitoring_zones_poll,
228                                    details=details,
229                                    disabled=disabled,
230                                    metadata=metadata,
231                                    period=period,
232                                    timeout=timeout)
233            changed = True
234    elif state == 'absent':
235        if check:
236            check.delete()
237            changed = True
238    else:
239        module.fail_json(msg='state must be either present or absent.')
240
241    if check:
242        check_dict = {
243            "id": check.id,
244            "label": check.label,
245            "type": check.type,
246            "target_hostname": check.target_hostname,
247            "target_alias": check.target_alias,
248            "monitoring_zones_poll": check.monitoring_zones_poll,
249            "details": check.details,
250            "disabled": check.disabled,
251            "metadata": check.metadata,
252            "period": check.period,
253            "timeout": check.timeout
254        }
255        module.exit_json(changed=changed, check=check_dict)
256    else:
257        module.exit_json(changed=changed)
258
259
260def main():
261    argument_spec = rax_argument_spec()
262    argument_spec.update(
263        dict(
264            entity_id=dict(required=True),
265            label=dict(required=True),
266            check_type=dict(required=True),
267            monitoring_zones_poll=dict(),
268            target_hostname=dict(),
269            target_alias=dict(),
270            details=dict(type='dict', default={}),
271            disabled=dict(type='bool', default=False),
272            metadata=dict(type='dict', default={}),
273            period=dict(type='int'),
274            timeout=dict(type='int'),
275            state=dict(default='present', choices=['present', 'absent'])
276        )
277    )
278
279    module = AnsibleModule(
280        argument_spec=argument_spec,
281        required_together=rax_required_together()
282    )
283
284    if not HAS_PYRAX:
285        module.fail_json(msg='pyrax is required for this module')
286
287    entity_id = module.params.get('entity_id')
288    label = module.params.get('label')
289    check_type = module.params.get('check_type')
290    monitoring_zones_poll = module.params.get('monitoring_zones_poll')
291    target_hostname = module.params.get('target_hostname')
292    target_alias = module.params.get('target_alias')
293    details = module.params.get('details')
294    disabled = module.boolean(module.params.get('disabled'))
295    metadata = module.params.get('metadata')
296    period = module.params.get('period')
297    timeout = module.params.get('timeout')
298
299    state = module.params.get('state')
300
301    setup_rax_module(module, pyrax)
302
303    cloud_check(module, state, entity_id, label, check_type,
304                monitoring_zones_poll, target_hostname, target_alias, details,
305                disabled, metadata, period, timeout)
306
307
308if __name__ == '__main__':
309    main()
310