diff --git a/library/monasca/README.md b/library/monasca/README.md index 33cdd35..a80d37e 100644 --- a/library/monasca/README.md +++ b/library/monasca/README.md @@ -1,17 +1,4 @@ # ansible-module-monasca -Ansible module for crud operations on Monasca alarm definitions +Ansible modules for Monasca. See the documentation for each module for more details. -Example usage - - tasks: - - name: Create System Alarm Definitions - monasca_alarm_definition: - name: "{{item.name}}" - expression: "{{item.expression}}" - keystone_url: "{{keystone_url}}" - keystone_user: "{{keystone_user}}" - keystone_password: "{{keystone_password}}" - with_items: - - { name: "High CPU usage", expression: "avg(cpu.idle_perc) < 10 times 3" } - -More information on [Monasca](https://wiki.openstack.org/wiki/Monasca) +For more information on [Monasca](https://wiki.openstack.org/wiki/Monasca). diff --git a/library/monasca/monasca_alarm_definition b/library/monasca/monasca_alarm_definition deleted file mode 100644 index ecf5541..0000000 --- a/library/monasca/monasca_alarm_definition +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python - -DOCUMENTATION = ''' ---- -module: monasca_alarm_definition -short_description: crud operations on Monasca alarm definitions -description: - - Performs crud operations (create/update/delete) on monasca alarm definitions - - Monasca project homepage - https://wiki.openstack.org/wiki/Monasca -author: Tim Kuhlman -requirements: [ python-monascaclient ] -options: - api_version: - required: false - default: '2_0' - description: - - The monasca api version. - description: - required: false - description: - - The description associated with the alarm - expression: - required: false - description: - - The alarm expression, required for create/update operations. - keystone_password: - required: true - description: - - Keystone password to use for authentication. - keystone_url: - required: true - description: - - Keystone url to authenticate against. Example http://192.168.10.5:5000/v3 - keystone_user: - required: true - description: - - Keystone user to log in as. - monasca_api_url: - required: false - description: - - If unset the service endpoing registered with keystone will be used. - name: - required: true - description: - - The alarm definition name - severity: - required: false - default: "LOW" - description: - - The severity set for the alarm must be LOW, MEDIUM, HIGH or CRITICAL - state: - required: false - default: "present" - choices: [ present, absent ] - description: - - Whether the account should exist. When C(absent), removes the user account. -''' - -EXAMPLES = ''' -monasca_alarm: expression='avg(cpu.idle_perc) < 10 times 3' state=present name='High CPU Usage' - keystone_url=http://localhost:5000/v3.0 keystone_user=admin keystone_password=password -''' - -from ansible.module_utils.basic import * - -try: - from monascaclient import client - from monascaclient import ksclient - import requests -except ImportError: - monascaclient_found = False -else: - monascaclient_found = True - -# Todo support check_mode -# ), -# supports_check_mode=True -# todo support setting alarm actions -# todo keep the keystone token across multiple runs - - -def main(): - module = AnsibleModule( - argument_spec=dict( - api_version=dict(required=False, default='2_0', type='str'), - description=dict(required=False, type='str'), - expression=dict(required=False, type='str'), - keystone_password=dict(required=True, type='str'), - keystone_url=dict(required=True, type='str'), - keystone_user=dict(required=True, type='str'), - monasca_api_url=dict(required=False, type='str'), - name=dict(required=True, type='str'), - severity=dict(default='LOW', type='str'), - state=dict(default='present', choices=['present', 'absent'], type='str') - ) - ) - - name = module.params['name'] - expression = module.params['expression'] - - if not monascaclient_found: - module.fail_json(msg="python-monascaclient >= 1.0.9 is required") - - # Authenticate to Keystone - ks = ksclient.KSClient(auth_url=module.params['keystone_url'], username=module.params['keystone_user'], - password=module.params['keystone_password']) - - # construct the mon client - if module.params['monasca_api_url'] is None: - api_url = ks.monasca_url - else: - api_url = module.params['monasca_api_url'] - monasca = client.Client(module.params['api_version'], api_url, token=ks.token) - - # Find existing definitions - definitions = {definition['name']: definition for definition in monasca.alarm_definitions.list()} - - if module.params['state'] == 'absent': - if name not in definitions.keys(): - module.exit_json(changed=False) - - resp = monasca.alarm_definitions.delete(alarm_id=definitions[name].id) - if resp.status_code == requests.codes.ok: - module.exit_json(changed=True) - else: - module.fail_json(msg=resp.text) - else: # Only other option is state=present - def_kwargs = {"name": name, "description": module.params['description'], "expression": expression, - "match_by": ["hostname"], "severity": module.params['severity']} - - if name in definitions.keys(): - if definitions[name]['expression'] == expression: - module.exit_json(changed=False) - body = monasca.alarm_definitions.patch(**def_kwargs) - else: - body = monasca.alarm_definitions.create(**def_kwargs) - - if 'id' in body: - module.exit_json(changed=True) - else: - module.fail_json(msg=body) - - -if __name__ == "__main__": - main() diff --git a/library/monasca/monasca_alarm_definition.py b/library/monasca/monasca_alarm_definition.py new file mode 100644 index 0000000..629268e --- /dev/null +++ b/library/monasca/monasca_alarm_definition.py @@ -0,0 +1,236 @@ +#!/usr/bin/env python + +DOCUMENTATION = ''' +--- +module: monasca_alarm_definition +short_description: crud operations on Monasca alarm definitions +description: + - Performs crud operations (create/update/delete) on monasca alarm definitions + - Monasca project homepage - https://wiki.openstack.org/wiki/Monasca + - When relevant the alarm_definition_id is in the output and can be used with the register action +author: Tim Kuhlman +requirements: [ python-monascaclient ] +options: + alarm_actions: + required: false + description: + - Array of notification method IDs that are invoked for the transition to the ALARM state. + api_version: + required: false + default: '2_0' + description: + - The monasca api version. + description: + required: false + description: + - The description associated with the alarm + expression: + required: false + description: + - The alarm expression, required for create/update operations. + keystone_password: + required: false + description: + - Keystone password to use for authentication, required unless a keystone_token is specified. + keystone_url: + required: false + description: + - Keystone url to authenticate against, required unless keystone_token isdefined. + Example http://192.168.10.5:5000/v3 + keystone_token: + required: false + description: + - Keystone token to use with the monasca api. If this is specified the monasca_api_url is required but + the keystone_user and keystone_password aren't. + keystone_user: + required: false + description: + - Keystone user to log in as, required unless a keystone_token is specified. + match_by: + required: false + default: "[hostname]" + description: + - Alarm definition match by, see the monasca api documentation for more detail. + monasca_api_url: + required: false + description: + - If unset the service endpoing registered with keystone will be used. + name: + required: true + description: + - The alarm definition name + ok_actions: + required: false + description: + - Array of notification method IDs that are invoked for the transition to the OK state. + severity: + required: false + default: "LOW" + description: + - The severity set for the alarm must be LOW, MEDIUM, HIGH or CRITICAL + state: + required: false + default: "present" + choices: [ present, absent ] + description: + - Whether the account should exist. When C(absent), removes the user account. + undetermined_actions: + required: false + description: + - Array of notification method IDs that are invoked for the transition to the UNDETERMINED state. +''' + +EXAMPLES = ''' +- name: Host Alive Alarm + monasca_alarm_definition: + name: "Host Alive Alarm" + expression: "host_alive_status > 0" + keystone_url: "{{keystone_url}}" + keystone_user: "{{keystone_user}}" + keystone_password: "{{keystone_password}}" + tags: + - alarms + - system_alarms + register: out +- name: Create System Alarm Definitions + monasca_alarm_definition: + name: "{{item.name}}" + expression: "{{item.expression}}" + keystone_token: "{{out.keystone_token}}" + monasca_api_url: "{{out.monasca_api_url}}" + with_items: + - { name: "High CPU usage", expression: "avg(cpu.idle_perc) < 10 times 3" } + - { name: "Disk Inode Usage", expression: "disk.inode_used_perc > 90" } +''' + +from ansible.module_utils.basic import * + +try: + from monascaclient import client + from monascaclient import ksclient +except ImportError: + monascaclient_found = False +else: + monascaclient_found = True + + +# With Ansible modules including other files presents difficulties otherwise this would be in its own module +class MonascaAnsible(object): + """ A base class used to build Monasca Client based Ansible Modules + As input an ansible.module_utils.basic.AnsibleModule object is expected. It should have at least + these params defined: + - api_version + - keystone_token and monasca_api_url or keystone_url, keystone_user and keystone_password and optionally + monasca_api_url + """ + def __init__(self, module): + self.module = module + self._keystone_auth() + self.exit_data = {'keystone_token': self.token, 'monasca_api_url': self.api_url} + self.monasca = client.Client(self.module.params['api_version'], self.api_url, token=self.token) + + def _exit_json(self, **kwargs): + """ Exit with supplied kwargs combined with the self.exit_data + """ + kwargs.update(self.exit_data) + self.module.exit_json(**kwargs) + + def _keystone_auth(self): + """ Authenticate to Keystone and set self.token and self.api_url + """ + if self.module.params['keystone_token'] is None: + ks = ksclient.KSClient(auth_url=self.module.params['keystone_url'], + username=self.module.params['keystone_user'], + password=self.module.params['keystone_password']) + + self.token = ks.token + if self.module.params['monasca_api_url'] is None: + self.api_url = ks.monasca_url + else: + self.api_url = self.module.params['monasca_api_url'] + else: + if self.module.params['monasca_api_url'] is None: + self.module.fail_json(msg='Error: When specifying keystone_token, monasca_api_url is required') + self.token = self.module.params['keystone_token'] + self.api_url = self.module.params['monasca_api_url'] + + +class MonascaDefinition(MonascaAnsible): + def run(self): + name = self.module.params['name'] + expression = self.module.params['expression'] + + # Find existing definitions + definitions = {definition['name']: definition for definition in self.monasca.alarm_definitions.list()} + + if self.module.params['state'] == 'absent': + if name not in definitions.keys(): + self._exit_json(changed=False) + + if self.module.check_mode: + self._exit_json(changed=True) + resp = self.monasca.alarm_definitions.delete(alarm_id=definitions[name]['id']) + if resp.status_code == 204: + self._exit_json(changed=True) + else: + self.module.fail_json(msg=str(resp.status_code) + resp.text) + else: # Only other option is state=present + def_kwargs = {"name": name, "description": self.module.params['description'], "expression": expression, + "match_by": self.module.params['match_by'], "severity": self.module.params['severity'], + "alarm_actions": self.module.params['alarm_actions'], + "ok_actions": self.module.params['ok_actions'], + "undetermined_actions": self.module.params['undetermined_actions']} + + if name in definitions.keys(): + if definitions[name]['expression'] == expression and \ + definitions[name]['alarm_actions'] == self.module.params['alarm_actions'] and \ + definitions[name]['ok_actions'] == self.module.params['ok_actions'] and \ + definitions[name]['undetermined_actions'] == self.module.params['undetermined_actions']: + self._exit_json(changed=False, alarm_definition_id=definitions[name]['id']) + def_kwargs['alarm_id'] = definitions[name]['id'] + + if self.module.check_mode: + self._exit_json(changed=True, alarm_definition_id=definitions[name]['id']) + body = self.monasca.alarm_definitions.patch(**def_kwargs) + else: + if self.module.check_mode: + self._exit_json(changed=True) + body = self.monasca.alarm_definitions.create(**def_kwargs) + + if 'id' in body: + self._exit_json(changed=True, alarm_definition_id=body['id']) + else: + self.module.fail_json(msg=body) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + alarm_actions=dict(required=False, default=[], type='list'), + api_version=dict(required=False, default='2_0', type='str'), + description=dict(required=False, type='str'), + expression=dict(required=False, type='str'), + keystone_password=dict(required=False, type='str'), + keystone_token=dict(required=False, type='str'), + keystone_url=dict(required=False, type='str'), + keystone_user=dict(required=False, type='str'), + match_by=dict(default=['hostname'], type='list'), + monasca_api_url=dict(required=False, type='str'), + name=dict(required=True, type='str'), + ok_actions=dict(required=False, default=[], type='list'), + severity=dict(default='LOW', type='str'), + state=dict(default='present', choices=['present', 'absent'], type='str'), + undetermined_actions=dict(required=False, default=[], type='list') + ), + supports_check_mode=True + ) + + if not monascaclient_found: + module.fail_json(msg="python-monascaclient >= 1.0.9 is required") + + definition = MonascaDefinition(module) + definition.run() + + +if __name__ == "__main__": + main() diff --git a/library/monasca/monasca_notification_method.py b/library/monasca/monasca_notification_method.py new file mode 100644 index 0000000..34ee34c --- /dev/null +++ b/library/monasca/monasca_notification_method.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python + +DOCUMENTATION = ''' +--- +module: monasca_notification_method +short_description: crud operations for Monasca notifications methods +description: + - Performs crud operations (create/update/delete) on monasca notification methods + - Monasca project homepage - https://wiki.openstack.org/wiki/Monasca + - When relevant the notification_id is in the output and can be used with the register action +author: Tim Kuhlman +requirements: [ python-monascaclient ] +options: + address: + required: true + description: + - The notification method address corresponding to the type. + api_version: + required: false + default: '2_0' + description: + - The monasca api version. + keystone_password: + required: false + description: + - Keystone password to use for authentication, required unless a keystone_token is specified. + keystone_url: + required: false + description: + - Keystone url to authenticate against, required unless keystone_token isdefined. + Example http://192.168.10.5:5000/v3 + keystone_token: + required: false + description: + - Keystone token to use with the monasca api. If this is specified the monasca_api_url is required but + the keystone_user and keystone_password aren't. + keystone_user: + required: false + description: + - Keystone user to log in as, required unless a keystone_token is specified. + monasca_api_url: + required: false + description: + - If unset the service endpoing registered with keystone will be used. + name: + required: true + description: + - The notification method name + state: + required: false + default: "present" + choices: [ present, absent ] + description: + - Whether the account should exist. When C(absent), removes the user account. + type: + required: true + description: + - The notification type. This must be one of the types supported by the Monasca API. +''' + +EXAMPLES = ''' +- name: Setup root email notification method + monasca_notification_method: + name: "Email Root" + type: 'EMAIL' + address: 'root@localhost' + keystone_url: "{{keystone_url}}" + keystone_user: "{{keystone_user}}" + keystone_password: "{{keystone_password}}" + register: out +- name: Create System Alarm Definitions + monasca_alarm_definition: + name: "Host Alive Alarm" + expression: "host_alive_status > 0" + keystone_token: "{{out.keystone_token}}" + monasca_api_url: "{{out.monasca_api_url}}" + alarm_actions: + - "{{out.notification_method_id}}" + ok_actions: + - "{{out.notification_method_id}}" + undetermined_actions: + - "{{out.notification_method_id}}" +''' + +from ansible.module_utils.basic import * + +try: + from monascaclient import client + from monascaclient import ksclient +except ImportError: + monascaclient_found = False +else: + monascaclient_found = True + + +# With Ansible modules including other files presents difficulties otherwise this would be in its own module +class MonascaAnsible(object): + """ A base class used to build Monasca Client based Ansible Modules + As input an ansible.module_utils.basic.AnsibleModule object is expected. It should have at least + these params defined: + - api_version + - keystone_token and monasca_api_url or keystone_url, keystone_user and keystone_password and optionally + monasca_api_url + """ + def __init__(self, module): + self.module = module + self._keystone_auth() + self.exit_data = {'keystone_token': self.token, 'monasca_api_url': self.api_url} + self.monasca = client.Client(self.module.params['api_version'], self.api_url, token=self.token) + + def _exit_json(self, **kwargs): + """ Exit with supplied kwargs combined with the self.exit_data + """ + kwargs.update(self.exit_data) + self.module.exit_json(**kwargs) + + def _keystone_auth(self): + """ Authenticate to Keystone and set self.token and self.api_url + """ + if self.module.params['keystone_token'] is None: + ks = ksclient.KSClient(auth_url=self.module.params['keystone_url'], + username=self.module.params['keystone_user'], + password=self.module.params['keystone_password']) + + self.token = ks.token + if self.module.params['monasca_api_url'] is None: + self.api_url = ks.monasca_url + else: + self.api_url = self.module.params['monasca_api_url'] + else: + if self.module.params['monasca_api_url'] is None: + self.module.fail_json(msg='Error: When specifying keystone_token, monasca_api_url is required') + self.token = self.module.params['keystone_token'] + self.api_url = self.module.params['monasca_api_url'] + + +class MonascaNotification(MonascaAnsible): + def run(self): + name = self.module.params['name'] + type = self.module.params['type'] + address = self.module.params['address'] + + notifications = {notif['name']:notif for notif in self.monasca.notifications.list()} + if name in notifications.keys(): + notification = notifications[name] + else: + notification = None + + if self.module.params['state'] == 'absent': + if notification is None: + self._exit_json(changed=False) + else: + self.monasca.notifications.delete(notification_id=notification['id']) + self._exit_json(changed=True) + else: # Only other option is present + if notification is None: + body = self.monasca.notifications.create(name=name, type=type, address=address) + self._exit_json(changed=True, notification_method_id=body['id']) + else: + if notification['type'] == type and notification['address'] == address: + self._exit_json(changed=False, notification_method_id=notification['id']) + else: + self.monasca.notifications.update(notification_id=notification['id'], + name=name, type=type, address=address) + self._exit_json(changed=True, notification_method_id=notification['id']) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + address=dict(required=True, type='str'), + api_version=dict(required=False, default='2_0', type='str'), + keystone_password=dict(required=False, type='str'), + keystone_token=dict(required=False, type='str'), + keystone_url=dict(required=False, type='str'), + keystone_user=dict(required=False, type='str'), + monasca_api_url=dict(required=False, type='str'), + name=dict(required=True, type='str'), + state=dict(default='present', choices=['present', 'absent'], type='str'), + type=dict(required=True, type='str') + ), + supports_check_mode=True + ) + + if not monascaclient_found: + module.fail_json(msg="python-monascaclient >= 1.0.9 is required") + + notification = MonascaNotification(module) + notification.run() + + +if __name__ == "__main__": + main() diff --git a/mini-mon.yml b/mini-mon.yml index e1ef8c0..c56885f 100644 --- a/mini-mon.yml +++ b/mini-mon.yml @@ -105,14 +105,33 @@ keystone_user: mini-mon keystone_password: password tasks: + - name: Setup root email notification method + monasca_notification_method: + name: "Email Root" + type: 'EMAIL' + address: 'root@localhost' + keystone_url: "{{keystone_url}}" + keystone_user: "{{keystone_user}}" + keystone_password: "{{keystone_password}}" + tags: + - alarms + - system_alarms + - monasca_alarms + register: out - name: Create System Alarm Definitions monasca_alarm_definition: name: "{{item.name}}" expression: "{{item.expression}}" - keystone_url: "{{keystone_url}}" - keystone_user: "{{keystone_user}}" - keystone_password: "{{keystone_password}}" + keystone_token: "{{out.keystone_token}}" + monasca_api_url: "{{out.monasca_api_url}}" + alarm_actions: + - "{{out.notification_method_id}}" + ok_actions: + - "{{out.notification_method_id}}" + undetermined_actions: + - "{{out.notification_method_id}}" with_items: + - { name: "Host Alive Alarm", expression: "host_alive_status > 0" } - { name: "High CPU usage", expression: "avg(cpu.idle_perc) < 10 times 3" } - { name: "Disk Inode Usage", expression: "disk.inode_used_perc > 90" } - { name: "Disk Usage", expression: "disk.space_used_perc > 90" } @@ -125,9 +144,14 @@ monasca_alarm_definition: name: "{{item.name}}" expression: "{{item.expression}}" - keystone_url: "{{keystone_url}}" - keystone_user: "{{keystone_user}}" - keystone_password: "{{keystone_password}}" + keystone_token: "{{out.keystone_token}}" + monasca_api_url: "{{out.monasca_api_url}}" + alarm_actions: + - "{{out.notification_method_id}}" + ok_actions: + - "{{out.notification_method_id}}" + undetermined_actions: + - "{{out.notification_method_id}}" with_items: - { name: "Monasca Agent emit time", expression: "avg(monasca.emit_time_sec) > 2 times 3" } - { name: "Monasca Agent collection time", expression: "avg(monasca.collection_time_sec) > 5 times 3" }