diff --git a/.travis.yml b/.travis.yml index 614e5e0df..2b3375599 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ addons: packages: - expect-dev # provides unbuffer utility - python-lxml # because pip installation is slow + - nsca-client env: global: diff --git a/nsca_client/README.rst b/nsca_client/README.rst new file mode 100644 index 000000000..381db2e6a --- /dev/null +++ b/nsca_client/README.rst @@ -0,0 +1,126 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +=========== +NSCA Client +=========== + +This is a technical module to send passive alerts to your favorite NSCA daemon +(Nagios, Shinken...). +This module is based on the Odoo cron system and requires a NSCA client +installed on the system to satisfy the ``/usr/sbin/send_nsca`` command. + +Installation +============ + +To use this module, you need to install a NSCA client. + +On Debian/Ubuntu:: + + $ sudo apt-get install nsca-client + +Configuration +============= + +To configure this module, you need to: + +* Configure your server and a passive service in your monitoring tool + (e.g service ``Odoo Mail Queue`` on host ``MY-SERVER``). + +* Declare your NSCA server in the menu Configuration / Technical / NSCA Client / Servers + +.. image:: nsca_client/static/description/server.png + :width: 400 px + +* Create NSCA checks in the menu Configuration / Technical / NSCA Client / Checks + +.. image:: nsca_client/static/description/check.png + :width: 400 px + +* Code the methods which will be called by the NSCA checks. + +Such methods must return a tuple ``(RC, MESSAGE, PERFORMANCE_DATA)`` where ``RC`` is an integer, +``MESSAGE`` a unicode string AND ``PERFOMANCE_DATA`` is a dictionary. +``RC`` values and the corresponding status are: + +- 0: OK +- 1: WARNING +- 2: CRITICAL +- 3: UNKNOWN + +``PERFORMANCE_DATA`` is not mandatory, so it could be possible to send +``(RC, MESSAGE)``. +Each element of ``PERFORMANCE_DATA`` will be a dictionary that could contain: + +- value: value of the data (required) +- max: Max value on the chart +- min: Minimum value on the chart +- warn: Warning value on the chart +- crit: Critical value on the chart +- uom: Unit of Measure on the chart (s - Seconds, % - Percentage, B - Bytes, c - Continuous) + +The key of the dictionary will be used as the performance_data label. + +E.g: + +.. code-block:: python + + class MailMail(models.Model): + _inherit = 'mail.mail' + + @api.model + def nsca_check_mails(self): + mails = self.search([('state', '=', 'exception')]) + if mails: + return (1, u"%s mails not sent" % len(mails), { + 'exceptions': {'value': len(mails)}}) + return (0, u"OK", {'exceptions': {'value': len(mails)}}) + +On the example, the performance data will use the label ``exceptions`` and the +value will be the number of exception of mails. + +Usage +===== + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/149/11.0 + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. + + +Credits +======= + +Images +------ + +* Daniel Foré: `Icon `_ (Elementary theme, GPL). + +Contributors +------------ + +* Sébastien Alix +* Enric Tobella + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/nsca_client/__init__.py b/nsca_client/__init__.py new file mode 100644 index 000000000..b9367d7f2 --- /dev/null +++ b/nsca_client/__init__.py @@ -0,0 +1,4 @@ +# © 2015 ABF OSIELL +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import models diff --git a/nsca_client/__manifest__.py b/nsca_client/__manifest__.py new file mode 100644 index 000000000..0aebc1cea --- /dev/null +++ b/nsca_client/__manifest__.py @@ -0,0 +1,22 @@ +# © 2015 ABF OSIELL +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +{ + "name": "NSCA Client", + "summary": "Send passive alerts to monitor your Odoo application.", + "version": "11.0.1.0.0", + "category": "Tools", + "website": "http://github.com/OCA/server-tools", + "author": "ABF OSIELL, Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + "installable": True, + "data": [ + "security/ir.model.access.csv", + "views/nsca_menu.xml", + "views/nsca_check.xml", + "views/nsca_server.xml", + ], + "demo": [ + "demo/demo_data.xml", + ], +} diff --git a/nsca_client/demo/demo_data.xml b/nsca_client/demo/demo_data.xml new file mode 100644 index 000000000..ae53551e0 --- /dev/null +++ b/nsca_client/demo/demo_data.xml @@ -0,0 +1,22 @@ + + + + + + nagios.example.net + 5667 + MY-SERVER + + + + + Odoo Mail Queue + + minutes + mail.mail + nsca_check_mails + + + + + diff --git a/nsca_client/i18n/fr.po b/nsca_client/i18n/fr.po new file mode 100644 index 000000000..c092b5b2f --- /dev/null +++ b/nsca_client/i18n/fr.po @@ -0,0 +1,225 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * nsca_client +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-04-07 12:33+0000\n" +"PO-Revision-Date: 2016-04-07 12:33+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_form +msgid "(1, u\"3 mails not sent\")" +msgstr "(1, u\"3 mails non-envoyés\")" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_form +msgid "0: OK" +msgstr "0: OK" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_form +msgid "1: WARNING" +msgstr "1: WARNING" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_form +msgid "2: CRITICAL" +msgstr "2: CRITICAL" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_form +msgid "3: UNKNOWN" +msgstr "3: UNKNOWN" + +#. module: nsca_client +#: field:nsca.check,allow_void_result:0 +msgid "Allow void result" +msgstr "Autoriser l'absence de résultat" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_form +msgid "Any other RC value will be treated as CRITICAL." +msgstr "Tout autre valeur RC sera traitée comme CRITICAL." + +#. module: nsca_client +#: field:nsca.check,nsca_args:0 +msgid "Arguments" +msgstr "Arguments" + +#. module: nsca_client +#: help:nsca.check,allow_void_result:0 +msgid "By default, a CRITICAL message is sent if the method does not return.\n" +"If checked, no message will be sent in such a case." +msgstr "Par défaut, un message de niveau CRITICAL sera envoyé si la méthode ne retourne aucun résultat.\n" +"Si cette option est cochée, aucun message ne sera envoyé dans un tel cas." + +#. module: nsca_client +#: model:ir.actions.act_window,name:nsca_client.action_nsca_check_tree +#: model:ir.ui.menu,name:nsca_client.menu_action_nsca_check_tree +#: view:nsca.server:nsca_client.view_nsca_server_form +#: field:nsca.server,check_ids:0 +msgid "Checks" +msgstr "Contrôles" + +#. module: nsca_client +#: code:addons/nsca_client/models/nsca_check.py:154 +#, python-format +msgid "Command '%s' not found. Please install the NSCA client.\n" +"On Debian/Ubuntu: apt-get install nsca-client" +msgstr "Commande '%s' non-disponible. Veuillez installer le client NSCA.\n" +"Sur Debian/Ubuntu: apt-get install nsca-client" + +#. module: nsca_client +#: field:nsca.check,create_uid:0 +#: field:nsca.server,create_uid:0 +msgid "Created by" +msgstr "Créé par" + +#. module: nsca_client +#: field:nsca.check,create_date:0 +#: field:nsca.server,create_date:0 +msgid "Created on" +msgstr "Créé le" + +#. module: nsca_client +#: field:nsca.check,cron_id:0 +msgid "Cron" +msgstr "Cron" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_form +msgid "E.g." +msgstr "Ex :" + +#. module: nsca_client +#: field:nsca.server,encryption_method:0 +msgid "Encryption method" +msgstr "Méthode de chiffrement" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_form +msgid "Frequency" +msgstr "Fréquence" + +#. module: nsca_client +#: field:nsca.server,name:0 +msgid "Hostname" +msgstr "Serveur" + +#. module: nsca_client +#: field:nsca.server,node_hostname:0 +msgid "Hostname of this node" +msgstr "Nom d'hôte du noeud" + +#. module: nsca_client +#: field:nsca.check,id:0 +#: field:nsca.server,id:0 +msgid "ID" +msgstr "ID" + +#. module: nsca_client +#: field:nsca.check,write_uid:0 +#: field:nsca.server,write_uid:0 +msgid "Last Updated by" +msgstr "Dernière modification par" + +#. module: nsca_client +#: field:nsca.check,write_date:0 +#: field:nsca.server,write_date:0 +msgid "Last Updated on" +msgstr "Dernière mise à jour le" + +#. module: nsca_client +#: field:nsca.check,nsca_function:0 +msgid "Method" +msgstr "Méthode" + +#. module: nsca_client +#: field:nsca.check,nsca_model:0 +msgid "Model" +msgstr "Modèle" + +#. module: nsca_client +#: code:addons/nsca_client/models/nsca_check.py:64 +#: model:ir.model,name:nsca_client.model_nsca_check +#: view:nsca.check:nsca_client.view_nsca_check_form +#, python-format +msgid "NSCA Check" +msgstr "Contrôle NSCA" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_tree +msgid "NSCA Checks" +msgstr "Contrôles NSCA" + +#. module: nsca_client +#: model:ir.ui.menu,name:nsca_client.menu_nsca_client +msgid "NSCA Client" +msgstr "Client NSCA" + +#. module: nsca_client +#: model:ir.model,name:nsca_client.model_nsca_server +#: view:nsca.server:nsca_client.view_nsca_server_form +msgid "NSCA Server" +msgstr "Serveur NSCA" + +#. module: nsca_client +#: view:nsca.server:nsca_client.view_nsca_server_tree +msgid "NSCA Servers" +msgstr "Serveurs NSCA" + +#. module: nsca_client +#: view:nsca.server:nsca_client.view_nsca_server_form +msgid "Node identity" +msgstr "Identité du noeud" + +#. module: nsca_client +#: field:nsca.server,password:0 +msgid "Password" +msgstr "Mot de passe" + +#. module: nsca_client +#: field:nsca.server,port:0 +msgid "Port" +msgstr "Port" + +#. module: nsca_client +#: field:nsca.check,server_id:0 +msgid "Server" +msgstr "Serveur" + +#. module: nsca_client +#: model:ir.actions.act_window,name:nsca_client.action_nsca_server_tree +#: model:ir.ui.menu,name:nsca_client.menu_action_nsca_server_tree +msgid "Servers" +msgstr "Serveurs" + +#. module: nsca_client +#: field:nsca.check,service:0 +msgid "Service" +msgstr "Service" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_form +msgid "Settings" +msgstr "Paramètres" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_form +msgid "The method must return a tuple (RC, MESSAGE) where RC is an integer:" +msgstr "La méthode doit retourner un tuple (RC, MESSAGE) où RC est un entier :" + +#. module: nsca_client +#: help:nsca.server,node_hostname:0 +msgid "This is the hostname of the current Odoo node declared in the monitoring server." +msgstr "C'est le nom d'hôte identifiant le serveur Odoo dans la configuration du serveur NSCA." + diff --git a/nsca_client/i18n/nsca_client.pot b/nsca_client/i18n/nsca_client.pot new file mode 100644 index 000000000..c9cb934f2 --- /dev/null +++ b/nsca_client/i18n/nsca_client.pot @@ -0,0 +1,192 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * nsca_client +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-12-17 07:05+0000\n" +"PO-Revision-Date: 2015-12-17 07:05+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_form +msgid "(1, u\"3 mails not sent\")" +msgstr "" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_form +msgid "0: OK" +msgstr "" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_form +msgid "1: WARNING" +msgstr "" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_form +msgid "2: CRITICAL" +msgstr "" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_form +msgid "3: UNKNOWN" +msgstr "" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_form +msgid "Any other RC value will be treated as CRITICAL." +msgstr "" + +#. module: nsca_client +#: field:nsca.check,nsca_args:0 +msgid "Arguments" +msgstr "" + +#. module: nsca_client +#: model:ir.actions.act_window,name:nsca_client.action_nsca_check_tree +#: model:ir.ui.menu,name:nsca_client.menu_action_nsca_check_tree +#: view:nsca.server:nsca_client.view_nsca_server_form +#: field:nsca.server,check_ids:0 +msgid "Checks" +msgstr "" + +#. module: nsca_client +#: code:addons/nsca_client/models/nsca_check.py:145 +#, python-format +msgid "Command '%s' not found. Please install the NSCA client.\n" +"On Debian/Ubuntu: apt-get install nsca-client" +msgstr "" + +#. module: nsca_client +#: field:nsca.server,config_file_path:0 +msgid "Configuration file" +msgstr "" + +#. module: nsca_client +#: field:nsca.check,create_uid:0 +#: field:nsca.server,create_uid:0 +msgid "Created by" +msgstr "" + +#. module: nsca_client +#: field:nsca.check,create_date:0 +#: field:nsca.server,create_date:0 +msgid "Created on" +msgstr "" + +#. module: nsca_client +#: field:nsca.check,cron_id:0 +msgid "Cron" +msgstr "" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_form +msgid "E.g." +msgstr "" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_form +msgid "Frequency" +msgstr "" + +#. module: nsca_client +#: field:nsca.server,name:0 +msgid "Hostname" +msgstr "" + +#. module: nsca_client +#: field:nsca.check,id:0 +#: field:nsca.server,id:0 +msgid "ID" +msgstr "" + +#. module: nsca_client +#: field:nsca.check,write_uid:0 +#: field:nsca.server,write_uid:0 +msgid "Last Updated by" +msgstr "" + +#. module: nsca_client +#: field:nsca.check,write_date:0 +#: field:nsca.server,write_date:0 +msgid "Last Updated on" +msgstr "" + +#. module: nsca_client +#: field:nsca.check,nsca_function:0 +msgid "Method" +msgstr "" + +#. module: nsca_client +#: field:nsca.check,nsca_model:0 +msgid "Model" +msgstr "" + +#. module: nsca_client +#: code:addons/nsca_client/models/nsca_check.py:60 +#: model:ir.model,name:nsca_client.model_nsca_check +#: view:nsca.check:nsca_client.view_nsca_check_form +#, python-format +msgid "NSCA Check" +msgstr "" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_tree +msgid "NSCA Checks" +msgstr "" + +#. module: nsca_client +#: model:ir.ui.menu,name:nsca_client.menu_nsca_client +msgid "NSCA Client" +msgstr "" + +#. module: nsca_client +#: model:ir.model,name:nsca_client.model_nsca_server +#: view:nsca.server:nsca_client.view_nsca_server_form +msgid "NSCA Server" +msgstr "" + +#. module: nsca_client +#: view:nsca.server:nsca_client.view_nsca_server_tree +msgid "NSCA Servers" +msgstr "" + +#. module: nsca_client +#: field:nsca.server,port:0 +msgid "Port" +msgstr "" + +#. module: nsca_client +#: field:nsca.check,server_id:0 +msgid "Server" +msgstr "" + +#. module: nsca_client +#: model:ir.actions.act_window,name:nsca_client.action_nsca_server_tree +#: model:ir.ui.menu,name:nsca_client.menu_action_nsca_server_tree +msgid "Servers" +msgstr "" + +#. module: nsca_client +#: field:nsca.check,service:0 +msgid "Service" +msgstr "" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_form +msgid "Settings" +msgstr "" + +#. module: nsca_client +#: view:nsca.check:nsca_client.view_nsca_check_form +msgid "The method must return a tuple (RC, MESSAGE) where RC is an integer:" +msgstr "" + diff --git a/nsca_client/models/__init__.py b/nsca_client/models/__init__.py new file mode 100644 index 000000000..b1b898df9 --- /dev/null +++ b/nsca_client/models/__init__.py @@ -0,0 +1 @@ +from . import nsca_check, nsca_server diff --git a/nsca_client/models/nsca_check.py b/nsca_client/models/nsca_check.py new file mode 100644 index 000000000..a96bb9f60 --- /dev/null +++ b/nsca_client/models/nsca_check.py @@ -0,0 +1,108 @@ +# (Copyright) 2015 ABF OSIELL +# (Copyright) 2018 Creu Blanca +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import logging +from odoo import _, api, fields, models +from odoo.tools.safe_eval import safe_eval + +_logger = logging.getLogger(__name__) + + +class NscaCheck(models.Model): + _name = "nsca.check" + _description = u"NSCA Check" + _inherits = {'ir.cron': 'cron_id'} + + cron_id = fields.Many2one( + 'ir.cron', string=u"Cron", + required=True, ondelete='cascade', readonly=True) + server_id = fields.Many2one( + 'nsca.server', string=u"Server", required=True) + service = fields.Char(u"Service", required=True) + nsca_model = fields.Char(u"Model") + nsca_function = fields.Char(u"Method") + nsca_args = fields.Char(u"Arguments") + allow_void_result = fields.Boolean( + u"Allow void result", default=False, + help=u"By default, a CRITICAL message is sent if the method does not " + u"return.\nIf checked, no message will be sent in such a case.") + + @api.model + def default_get(self, fields_list): + """Set some default values on the fly, without overriding fields (which + has the side effect to re-create the fields on the current model). + """ + res = super(NscaCheck, self).default_get(fields_list) + res['name'] = 'TEMP' # Required on 'ir.cron', replaced later + res['interval_number'] = 10 + res['interval_type'] = 'minutes' + return res + + @api.multi + def _force_values(self): + """Force some values: + - Compute the name of the NSCA check to be readable + among the others 'ir.cron' records. + """ + model = self.env['ir.model'].search([('model', '=', self._name)]) + for check in self: + vals = { + 'name': u"%s - %s" % (_(u"NSCA Check"), check.service), + 'model_id': model.id, + 'state': 'code', + 'code': 'model._cron_check(%s,)' % check.id, + 'doall': False, + 'numbercall': -1 + } + super(NscaCheck, check).write(vals) + + @api.model + def create(self, vals): + if not vals.get('model_id', False): + vals['model_id'] = self.env['ir.model'].search([ + ('model', '=', self._name)]).id + if not vals.get('state', False): + vals['state'] = 'code' + check = super(NscaCheck, self).create(vals) + check._force_values() + return check + + @api.multi + def write(self, vals): + res = super(NscaCheck, self).write(vals) + if 'service' in vals: + self._force_values() + return res + + @api.model + def _cron_check(self, check_id): + self.env['nsca.server']._check_send_nsca_command() + check = self.browse(check_id) + rc, message, performance = 3, "Unknown", {} + try: + NscaModel = self.env[check.nsca_model] + results = {'model': NscaModel} + safe_eval( + 'result = model.%s(%s)' % ( + check.nsca_function, check.nsca_args or ''), + results, mode="exec", nocopy=True) + result = results['result'] + if not result: + if check.allow_void_result: + return False + raise ValueError( + "'%s' method does not return" % check.nsca_function) + if len(result) == 2: + rc, message = result + else: + rc, message, performance = result + except Exception as exc: + rc, message = 2, "%s" % exc + _logger.warning("%s - %s", check.service, message) + check._send_nsca(rc, message, performance) + return True + + @api.multi + def _send_nsca(self, rc, message, performance): + for check in self: + check.server_id._send_nsca(check.service, rc, message, performance) diff --git a/nsca_client/models/nsca_server.py b/nsca_client/models/nsca_server.py new file mode 100644 index 000000000..39cc7f9e2 --- /dev/null +++ b/nsca_client/models/nsca_server.py @@ -0,0 +1,239 @@ +# (Copyright) 2015 ABF OSIELL +# (Copyright) 2018 Creu Blanca +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import psutil +import os +import shlex +import subprocess +import logging + +from odoo import api, fields, models, _ +from odoo.tools import config +from odoo.exceptions import UserError + + +def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + + +_logger = logging.getLogger(__name__) + +SEND_NSCA_BIN = '/usr/sbin/send_nsca' + + +class NscaServer(models.Model): + _name = "nsca.server" + _description = u"NSCA Server" + + name = fields.Char(u"Hostname", required=True) + port = fields.Integer(u"Port", default=5667, required=True) + password = fields.Char(u"Password") + encryption_method = fields.Selection( + selection='_selection_encryption_method', + string=u"Encryption method", default='1', required=True) + config_dir_path = fields.Char( + u"Configuration directory", + compute='_compute_config_dir_path') + config_file_path = fields.Char( + u"Configuration file", + compute='_compute_config_file_path') + node_hostname = fields.Char( + u"Hostname of this node", required=True, + help=u"This is the hostname of the current Odoo node declared in the " + u"monitoring server.") + check_ids = fields.One2many( + 'nsca.check', 'server_id', string=u"Checks") + check_count = fields.Integer( + compute='_compute_check_count' + ) + + @api.depends('check_ids') + def _compute_check_count(self): + for r in self: + r.check_count = len(r.check_ids) + + def _selection_encryption_method(self): + return [ + ('0', u"0 - None (Do NOT use this option)"), + ('1', u"1 - Simple XOR"), + ('2', u"2 - DES"), + ('3', u"3 - 3DES (Triple DES)"), + ('4', u"4 - CAST-128"), + ('5', u"5 - CAST-256"), + ('6', u"6 - xTEA"), + ('7', u"7 - 3WAY"), + ('8', u"8 - BLOWFISH"), + ('9', u"9 - TWOFISH"), + ('10', u"10 - LOKI97"), + ('11', u"11 - RC2"), + ('12', u"12 - ARCFOUR"), + ('14', u"14 - RIJNDAEL-128"), + ('15', u"15 - RIJNDAEL-192"), + ('16', u"16 - RIJNDAEL-256"), + ('19', u"19 - WAKE"), + ('20', u"20 - SERPENT"), + ('22', u"22 - ENIGMA (Unix crypt)"), + ('23', u"23 - GOST"), + ('24', u"24 - SAFER64"), + ('25', u"25 - SAFER128"), + ('26', u"26 - SAFER+"), + ] + + @api.multi + def _compute_config_dir_path(self): + for server in self: + data_dir_path = config.get('data_dir') + dir_path = os.path.join( + data_dir_path, 'nsca_client', self.env.cr.dbname) + server.config_dir_path = dir_path + + @api.multi + def _compute_config_file_path(self): + for server in self: + file_name = 'send_nsca_%s.cfg' % server.id + full_path = os.path.join(server.config_dir_path, file_name) + server.config_file_path = full_path + + @api.multi + def write_config_file(self): + for server in self: + try: + os.makedirs(server.config_dir_path) + except OSError as exception: + if exception.errno != os.errno.EEXIST: + raise + with open(server.config_file_path, 'w') as config_file: + if server.password: + config_file.write('password=%s\n' % server.password) + config_file.write( + 'encryption_method=%s\n' % server.encryption_method) + return True + + @api.multi + def write(self, vals): + res = super(NscaServer, self).write(vals) + self.write_config_file() + return res + + @api.model + def create(self, vals): + res = super(NscaServer, self).create(vals) + res.write_config_file() + return res + + @api.model + def current_status(self): + ram = 0 + cpu = 0 + if psutil: + process = psutil.Process(os.getpid()) + # psutil changed its api through versions + processes = [process] + if config.get( + 'workers') and process.parent: # pragma: no cover + if hasattr(process.parent, '__call__'): + process = process.parent() + else: + process = process.parent + if hasattr(process, 'children'): + processes += process.children(True) + elif hasattr(process, 'get_children'): + processes += process.get_children(True) + for process in processes: + if hasattr(process, 'memory_percent'): + ram += process.memory_percent() + if hasattr(process, 'cpu_percent'): + cpu += process.cpu_percent(interval=1) + user_count = 0 + if 'bus.presence' in self.env.registry: + user_count = self.env['bus.presence'].search_count([ + ('status', '=', 'online'), + ]) + performance = { + 'cpu': { + 'value': cpu, + }, + 'ram': { + 'value': ram, + }, + 'user_count': { + 'value': user_count, + }, + } + return 0, u"OK", performance + + @api.multi + def _prepare_command(self): + """Prepare the shell command used to send the check result + to the NSCA daemon. + """ + cmd = u"/usr/sbin/send_nsca -H %s -p %s -c %s" % ( + self.name, + self.port, + self.config_file_path) + return shlex.split(cmd) + + @api.model + def _run_command(self, cmd, check_result): + """Send the check result through the '/usr/sbin/send_nsca' command.""" + try: + proc = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + stderr=subprocess.STDOUT) + stdout = proc.communicate( + input=check_result)[0] + _logger.debug("%s: %s", check_result, stdout.strip()) + except Exception as exc: + _logger.error(exc) + + def _check_send_nsca_command(self): + """Check if the NSCA client is installed.""" + if not is_exe(SEND_NSCA_BIN): + raise UserError( + _(u"Command '%s' not found. Please install the NSCA client.\n" + u"On Debian/Ubuntu: apt-get install nsca-client") % ( + SEND_NSCA_BIN)) + + def _format_check_result(self, service, rc, message): + """Format the check result with tabulations as delimiter.""" + message = message.replace('\t', ' ') + hostname = self.node_hostname + check_result = u"%s\t%s\t%s\t%s" % ( + hostname, service, rc, message) + return check_result.encode('utf-8') + + def _send_nsca(self, service, rc, message, performance): + """Send the result of the check to the NSCA daemon.""" + msg = message + if len(performance) > 0: + msg += '| ' + ''.join( + ["%s=%s%s;%s;%s;%s;%s" % ( + key, + performance[key]['value'], + performance[key].get('uom', ''), + performance[key].get('warn', ''), + performance[key].get('crit', ''), + performance[key].get('min', ''), + performance[key].get('max', ''), + ) for key in sorted(performance)]) + check_result = self._format_check_result( + service, rc, msg) + cmd = self._prepare_command() + self._run_command(cmd, check_result) + + @api.multi + def show_checks(self): + self.ensure_one() + action = self.env.ref('nsca_client.action_nsca_check_tree') + result = action.read()[0] + context = {'default_server_id': self.id} + result['context'] = context + result['domain'] = [('server_id', '=', self.id)] + if len(self.check_ids) == 1: + res = self.env.ref('nsca_client.view_nsca_check_form', False) + result['views'] = [(res and res.id or False, 'form')] + result['res_id'] = self.check_ids.id + return result diff --git a/nsca_client/security/ir.model.access.csv b/nsca_client/security/ir.model.access.csv new file mode 100644 index 000000000..da2d09485 --- /dev/null +++ b/nsca_client/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_nsca_check,access_nsca_check,model_nsca_check,base.group_erp_manager,1,1,1,1 +access_nsca_server,access_nsca_server,model_nsca_server,base.group_erp_manager,1,1,1,1 diff --git a/nsca_client/static/description/check.png b/nsca_client/static/description/check.png new file mode 100644 index 000000000..48d11df0f Binary files /dev/null and b/nsca_client/static/description/check.png differ diff --git a/nsca_client/static/description/icon.png b/nsca_client/static/description/icon.png new file mode 100644 index 000000000..b9f884a28 Binary files /dev/null and b/nsca_client/static/description/icon.png differ diff --git a/nsca_client/static/description/server.png b/nsca_client/static/description/server.png new file mode 100644 index 000000000..200869209 Binary files /dev/null and b/nsca_client/static/description/server.png differ diff --git a/nsca_client/tests/__init__.py b/nsca_client/tests/__init__.py new file mode 100644 index 000000000..3cc2c357b --- /dev/null +++ b/nsca_client/tests/__init__.py @@ -0,0 +1 @@ +from . import test_nsca diff --git a/nsca_client/tests/test_nsca.py b/nsca_client/tests/test_nsca.py new file mode 100644 index 000000000..99cd5b46f --- /dev/null +++ b/nsca_client/tests/test_nsca.py @@ -0,0 +1,104 @@ +# Copyright 2018 Creu Blanca +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import mock + +from odoo.tests.common import TransactionCase + + +class Popen: + def __init__(self, cmd, stdout, stdin, stderr): + self.cmd = cmd + self.stdout = stdout + self.stdin = stdin + self.stderr = stderr + + def communicate(input): + return ['test'] + + +class TestNsca(TransactionCase): + + def test_nsca(self): + server = self.env['nsca.server'].create({ + 'name': 'localhost', + 'password': 'pass', + 'encryption_method': '3', + 'node_hostname': 'odoodev', + }) + self.assertTrue(server.config_file_path) + with mock.patch('subprocess.Popen') as post: + post.return_value = Popen + check = self.env['nsca.check'].create({ + 'server_id': server.id, + 'service': 'test', + 'nsca_model': 'nsca.server', + 'nsca_function': 'current_status' + }) + self.assertTrue(check.model_id) + self.env['nsca.check']._cron_check(check.id,) + + def test_write(self): + server = self.env['nsca.server'].create({ + 'name': 'localhost', + 'password': 'pass', + 'encryption_method': '3', + 'node_hostname': 'odoodev', + }) + self.assertTrue(server.config_file_path) + check = self.env['nsca.check'].create({ + 'server_id': server.id, + 'service': 'test', + 'nsca_model': 'nsca.server', + 'nsca_function': 'current_status' + }) + check.cron_id.state = 'object_create' + check.write({'interval_number': 1}) + self.assertEqual(check.cron_id.state, 'object_create') + check.write({'service': 'change'}) + self.assertNotEqual(check.cron_id.state, 'object_create') + + def test_void_failure(self): + server = self.env['nsca.server'].create({ + 'name': 'localhost', + 'password': 'pass', + 'encryption_method': '3', + 'node_hostname': 'odoodev', + }) + check = self.env['nsca.check'].create({ + 'server_id': server.id, + 'service': 'test', + 'nsca_model': 'nsca.server', + 'allow_void_result': False, + 'nsca_function': '_check_send_nsca_command' + }) + with mock.patch('subprocess.Popen') as post: + post.return_value = Popen + self.env['nsca.check']._cron_check(check.id,) + post.assert_called_once() + + def test_void_ok(self): + server = self.env['nsca.server'].create({ + 'name': 'localhost', + 'password': 'pass', + 'encryption_method': '3', + 'node_hostname': 'odoodev', + }) + self.assertEqual(server.check_count, 0) + check = self.env['nsca.check'].create({ + 'server_id': server.id, + 'service': 'test', + 'nsca_model': 'nsca.server', + 'allow_void_result': True, + 'nsca_function': '_check_send_nsca_command' + }) + self.assertEqual(server.check_count, 1) + action = server.show_checks() + self.assertEqual(check, self.env['nsca.check'].browse( + action['res_id'])) + self.assertEqual(check, self.env['nsca.check'].search( + action['domain'])) + with mock.patch('subprocess.Popen') as post: + post.return_value = Popen + self.env['nsca.check']._cron_check(check.id,) + post.assert_not_called() diff --git a/nsca_client/views/nsca_check.xml b/nsca_client/views/nsca_check.xml new file mode 100644 index 000000000..9713d9cf3 --- /dev/null +++ b/nsca_client/views/nsca_check.xml @@ -0,0 +1,90 @@ + + + + + + + + nsca.check.form + nsca.check + +
+ + + + + + + + + + + +
+

The method must return a tuple (RC, + MESSAGE) where RC is an integer: +

+ +
    +
  • 0: OK
  • +
  • 1: WARNING
  • +
  • 2: CRITICAL
  • +
  • 3: UNKNOWN
  • +
+ +

Any other RC value will be treated as + CRITICAL. +

+

E.g. + (1, u"3 mails not sent") +

+
+
+
+
+
+
+
+ + + nsca.check.tree + nsca.check + + + + + + + + + + + + Checks + ir.actions.act_window + nsca.check + form + + + ['|', ('active', '=', True), ('active', '=', + False)] + + + + + +
+
diff --git a/nsca_client/views/nsca_menu.xml b/nsca_client/views/nsca_menu.xml new file mode 100644 index 000000000..1a35df036 --- /dev/null +++ b/nsca_client/views/nsca_menu.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/nsca_client/views/nsca_server.xml b/nsca_client/views/nsca_server.xml new file mode 100644 index 000000000..4736a7dfc --- /dev/null +++ b/nsca_client/views/nsca_server.xml @@ -0,0 +1,63 @@ + + + + + + + + nsca.server.form + nsca.server + +
+ +
+ +
+ + + + + + + + + + +
+
+
+
+ + + nsca.server.tree + nsca.server + + + + + + + + + + + Servers + ir.actions.act_window + nsca.server + form + + + + + +
+