UNPKG

zabbix-utils

Version:

TypeScript port of zabbix-utils - Python library for working with Zabbix API, Sender, and Getter protocols

304 lines 12.6 kB
"use strict"; // zabbix_utils // // Copyright (C) 2001-2023 Zabbix SIA (Original Python library) // Copyright (C) 2024-2025 Han Yong Lim <hanyong.lim@gmail.com> (TypeScript adaptation) // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without restriction, // including without limitation the rights to use, copy, modify, // merge, publish, distribute, sublicense, and/or sell copies // of the Software, and to permit persons to whom the Software // is furnished to do so, subject to the following conditions: var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.Sender = void 0; // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. const net = __importStar(require("net")); const fs = __importStar(require("fs")); const common_1 = require("./common"); const exceptions_1 = require("./exceptions"); const types_1 = require("./types"); class Sender { constructor(options = {}) { this.tls = {}; const { server, port = 10051, useConfig = false, timeout = 10, useIpv6 = false, sourceIp, chunkSize = 250, clusters, socketWrapper, compression = false, configPath = '/etc/zabbix/zabbix_agentd.conf' } = options; this.timeout = timeout * 1000; // Convert to milliseconds this.useIpv6 = useIpv6; this.sourceIp = sourceIp; this.chunkSize = chunkSize; this.compression = compression; if (socketWrapper && typeof socketWrapper !== 'function') { throw new TypeError('Value "socketWrapper" should be a function.'); } this.socketWrapper = socketWrapper; if (useConfig) { this.clusters = []; this.loadConfig(configPath); return; } if (clusters) { if (!Array.isArray(clusters)) { throw new TypeError('Value "clusters" should be an array.'); } const clustersCopy = [...clusters]; if (server) { clustersCopy.push([`${server}:${port}`]); } this.clusters = clustersCopy.map(c => new types_1.Cluster(c)); } else { this.clusters = [new types_1.Cluster([`${server || '127.0.0.1'}:${port}`])]; } } readConfig(config) { const serverRow = config.ServerActive || config.Server || '127.0.0.1:10051'; for (const cluster of serverRow.split(',')) { this.clusters.push(new types_1.Cluster(cluster.trim().split(';'))); } if (config.SourceIP) { this.sourceIp = config.SourceIP; } for (const [key, value] of Object.entries(config)) { if (key.toLowerCase().startsWith('tls')) { this.tls[key] = value; } } } loadConfig(filepath) { const configContent = fs.readFileSync(filepath, 'utf-8'); const config = {}; for (const line of configContent.split('\n')) { const trimmed = line.trim(); if (trimmed && !trimmed.startsWith('#')) { const [key, ...valueParts] = trimmed.split('='); if (key && valueParts.length > 0) { config[key.trim()] = valueParts.join('=').trim(); } } } this.readConfig(config); } async getResponse(conn) { try { const result = JSON.parse(await common_1.ZabbixProtocol.parseSyncPacket(conn, console, exceptions_1.ProcessingError)); console.debug('Received data:', result); return result; } catch (error) { console.debug('Unexpected response was received from Zabbix.'); throw error; } } createRequest(items) { return { request: "sender data", data: items.map(i => i.toJson()) }; } async sendToCluster(cluster, packet) { let activeNode = null; let activeNodeIdx = 0; let connection = null; for (let i = 0; i < cluster.nodes.length; i++) { const node = cluster.nodes[i]; console.debug(`Trying to send data to ${node}`); try { connection = new net.Socket(); connection.setTimeout(this.timeout); await new Promise((resolve, reject) => { const onConnect = () => { connection.removeListener('error', onError); connection.removeListener('timeout', onTimeout); resolve(); }; const onError = (err) => { connection.removeListener('connect', onConnect); connection.removeListener('timeout', onTimeout); reject(err); }; const onTimeout = () => { connection.removeListener('connect', onConnect); connection.removeListener('error', onError); reject(new Error(`Connection timeout after ${this.timeout}ms`)); }; connection.once('connect', onConnect); connection.once('error', onError); connection.once('timeout', onTimeout); const connectOptions = { host: node.addr, port: node.port, family: this.useIpv6 ? 6 : 4 }; if (this.sourceIp) { connectOptions.localAddress = this.sourceIp; } connection.connect(connectOptions); }); activeNodeIdx = i; if (i > 0) { [cluster.nodes[0], cluster.nodes[i]] = [cluster.nodes[i], cluster.nodes[0]]; activeNodeIdx = 0; } activeNode = node; break; } catch (error) { console.debug(`Connection failed to ${node}:`, error); if (connection) { connection.destroy(); connection = null; } } } if (!activeNode || !connection) { throw new exceptions_1.ProcessingError(`Couldn't connect to all of cluster nodes: ${cluster.nodes.map(n => n.toString())}`); } if (this.socketWrapper) { connection = this.socketWrapper(connection, this.tls); } try { await new Promise((resolve, reject) => { connection.write(packet, (err) => { if (err) reject(err); else resolve(); }); }); } catch (error) { connection.destroy(); throw error; } let response; try { response = await this.getResponse(connection); } catch (error) { connection.destroy(); throw error; } console.debug(`Response from ${activeNode}:`, response); if (response && response.response !== 'success') { if (response.redirect) { console.debug(`Packet was redirected from ${activeNode} to ${response.redirect.address}. ` + `Proxy group revision: ${response.redirect.revision}.`); cluster.nodes[activeNodeIdx] = new types_1.Node(...response.redirect.address.split(':')); connection.destroy(); return this.sendToCluster(cluster, packet); } else { connection.destroy(); throw new Error(JSON.stringify(response)); } } connection.destroy(); return [activeNode, response]; } async chunkSend(items) { const responses = {}; const packet = common_1.ZabbixProtocol.createPacket(this.createRequest(items), console, this.compression); for (const cluster of this.clusters) { const [activeNode, response] = await this.sendToCluster(cluster, packet); responses[activeNode.toString()] = response; } return responses; } /** * Sends packets and receives an answer from Zabbix. * * @param items - List of ItemValue objects. * @returns Response from Zabbix server/proxy. */ async send(items) { // Split the list of items into chunks const chunks = []; for (let i = 0; i < items.length; i += this.chunkSize) { chunks.push(items.slice(i, i + this.chunkSize)); } const result = new types_1.TrapperResponse(); result.details = {}; for (let i = 0; i < chunks.length; i++) { const chunk = chunks[i]; if (!chunk.every(item => item instanceof types_1.ItemValue)) { throw new exceptions_1.ProcessingError(`Received unexpected item list. It must be a list of ItemValue objects: ${JSON.stringify(chunk)}`); } const respByNode = await this.chunkSend(chunk); let nodeStep = 1; for (const [nodeStr, resp] of Object.entries(respByNode)) { try { result.add(resp, (i + 1) * nodeStep); } catch (error) { throw new exceptions_1.ProcessingError(error instanceof Error ? error.message : String(error)); } nodeStep += 1; if (!(nodeStr in result.details)) { result.details[nodeStr] = []; } const chunkResponse = new types_1.TrapperResponse(i + 1); chunkResponse.add(resp); result.details[nodeStr].push(chunkResponse); } } return result; } /** * Sends one value and receives an answer from Zabbix. * * @param host - Host name the item belongs to. * @param key - Item key to send value to. * @param value - Item value. * @param clock - Time in Unix timestamp format. Defaults to current time. * @param ns - Time expressed in nanoseconds. Defaults to undefined. * @returns Response from Zabbix server/proxy. */ async sendValue(host, key, value, clock, ns) { return this.send([new types_1.ItemValue(host, key, value, clock, ns)]); } } exports.Sender = Sender; //# sourceMappingURL=sender.js.map