zabbix-utils
Version:
TypeScript port of zabbix-utils - Python library for working with Zabbix API, Sender, and Getter protocols
304 lines • 12.6 kB
JavaScript
;
// 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