UNPKG

zabbix-utils

Version:

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

321 lines 13.3 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.AsyncSender = 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 tls = __importStar(require("tls")); const fs = __importStar(require("fs")); const common_1 = require("./common"); const exceptions_1 = require("./exceptions"); const types_1 = require("./types"); class AsyncSender { constructor(options = {}) { this.tls = {}; const { server, port = 10051, useConfig = false, timeout = 10, useIpv6 = false, sourceIp, chunkSize = 250, clusters, sslContext, 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 (sslContext && typeof sslContext !== 'function') { throw new TypeError('Value "sslContext" should be a function.'); } this.sslContext = sslContext; 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(socket) { try { const result = JSON.parse(await common_1.ZabbixProtocol.parseSyncPacket(socket, console, exceptions_1.ProcessingError)); console.debug('Received async 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 socket = null; for (let i = 0; i < cluster.nodes.length; i++) { const node = cluster.nodes[i]; console.debug(`Trying to send async data to ${node}`); try { const connectOptions = { host: node.addr, port: node.port, family: this.useIpv6 ? 6 : 4 }; if (this.sourceIp) { connectOptions.localAddress = this.sourceIp; } if (this.sslContext) { const secureContext = this.sslContext(this.tls); if (!(secureContext instanceof Object)) { throw new TypeError('Function "sslContext" must return "tls.SecureContext".'); } socket = tls.connect({ ...connectOptions, secureContext: secureContext }); } else { socket = new net.Socket(); } socket.setTimeout(this.timeout); await new Promise((resolve, reject) => { const onConnect = () => { socket.removeListener('error', onError); socket.removeListener('timeout', onTimeout); resolve(); }; const onError = (err) => { socket.removeListener('connect', onConnect); socket.removeListener('timeout', onTimeout); reject(err); }; const onTimeout = () => { socket.removeListener('connect', onConnect); socket.removeListener('error', onError); reject(new Error(`Connection timeout after ${this.timeout}ms`)); }; socket.once('connect', onConnect); socket.once('error', onError); socket.once('timeout', onTimeout); if (!this.sslContext) { socket.connect(connectOptions); } // TLS socket connects automatically }); 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(`Async connection failed to ${node}:`, error); if (socket) { socket.destroy(); socket = null; } } } if (!activeNode || !socket) { throw new exceptions_1.ProcessingError(`Couldn't connect to all of cluster nodes: ${cluster.nodes.map(n => n.toString())}`); } try { await new Promise((resolve, reject) => { socket.write(packet, (err) => { if (err) reject(err); else resolve(); }); }); } catch (error) { socket.destroy(); throw error; } let response; try { response = await this.getResponse(socket); } catch (error) { socket.destroy(); throw error; } console.debug(`Async 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(':')); socket.destroy(); return this.sendToCluster(cluster, packet); } else { socket.destroy(); throw new Error(JSON.stringify(response)); } } socket.destroy(); return [activeNode, response]; } async chunkSend(items) { const responses = {}; const packet = common_1.ZabbixProtocol.createPacket(this.createRequest(items), console, this.compression); const promises = this.clusters.map(async (cluster) => { const [activeNode, response] = await this.sendToCluster(cluster, packet); return { nodeStr: activeNode.toString(), response }; }); const results = await Promise.all(promises); for (const { nodeStr, response } of results) { responses[nodeStr] = response; } return responses; } /** * Sends packets asynchronously 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 asynchronously 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.AsyncSender = AsyncSender; //# sourceMappingURL=aiosender.js.map