# (Copyright) 2015 ABF OSIELL # (Copyright) 2018 Creu Blanca # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import errno import logging import os import shlex import subprocess import psutil from odoo import _, api, fields, models from odoo.exceptions import UserError from odoo.tools import config 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 = "NSCA Server" name = fields.Char("Hostname", required=True) port = fields.Integer(default=5667, required=True) password = fields.Char() encryption_method = fields.Selection( selection="_selection_encryption_method", string="Encryption method", default="1", required=True, ) config_dir_path = fields.Char( "Configuration directory", compute="_compute_config_dir_path" ) config_file_path = fields.Char( "Configuration file", compute="_compute_config_file_path" ) node_hostname = fields.Char( "Hostname of this node", required=True, help="This is the hostname of the current Odoo node declared in the " "monitoring server.", ) check_ids = fields.One2many("nsca.check", "server_id", string="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", "0 - None (Do NOT use this option)"), ("1", "1 - Simple XOR"), ("2", "2 - DES"), ("3", "3 - 3DES (Triple DES)"), ("4", "4 - CAST-128"), ("5", "5 - CAST-256"), ("6", "6 - xTEA"), ("7", "7 - 3WAY"), ("8", "8 - BLOWFISH"), ("9", "9 - TWOFISH"), ("10", "10 - LOKI97"), ("11", "11 - RC2"), ("12", "12 - ARCFOUR"), ("14", "14 - RIJNDAEL-128"), ("15", "15 - RIJNDAEL-192"), ("16", "16 - RIJNDAEL-256"), ("19", "19 - WAKE"), ("20", "20 - SERPENT"), ("22", "22 - ENIGMA (Unix crypt)"), ("23", "23 - GOST"), ("24", "24 - SAFER64"), ("25", "25 - SAFER128"), ("26", "26 - SAFER+"), ] 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 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 def write_config_file(self): for server in self: try: os.makedirs(server.config_dir_path) except OSError as exception: if exception.errno != 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 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 callable(process.parent): 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, "OK", performance def _prepare_command(self): """Prepare the shell command used to send the check result to the NSCA daemon. """ cmd = "/usr/sbin/send_nsca -H {} -p {} -c {}".format( 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( _( "Command '%s' not found. Please install the NSCA client.\n" "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 = "{}\t{}\t{}\t{}".format(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) def show_checks(self): self.ensure_one() result = self.env["ir.actions.act_window"]._for_xml_id( "nsca_client.action_nsca_check_tree" ) 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