migrated grpc keyword to the newer monitoringtype

This commit is contained in:
Frank Elsinga 2024-06-04 05:34:13 +02:00
parent 10ebdcacaa
commit 334e37eaa0
4 changed files with 89 additions and 92 deletions

View file

@ -4,7 +4,7 @@ const { Prometheus } = require("../prometheus");
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
SQL_DATETIME_FORMAT
} = require("../../src/util");
const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius, grpcQuery,
const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius,
redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
} = require("../util-server");
const { R } = require("redbean-node");
@ -773,37 +773,6 @@ class Monitor extends BeanModel {
bean.msg = "";
bean.status = UP;
bean.ping = dayjs().valueOf() - startTime;
} else if (this.type === "grpc-keyword") {
let startTime = dayjs().valueOf();
const options = {
grpcUrl: this.grpcUrl,
grpcProtobufData: this.grpcProtobuf,
grpcServiceName: this.grpcServiceName,
grpcEnableTls: this.grpcEnableTls,
grpcMethod: this.grpcMethod,
grpcBody: this.grpcBody,
};
const response = await grpcQuery(options);
bean.ping = dayjs().valueOf() - startTime;
log.debug("monitor:", `gRPC response: ${JSON.stringify(response)}`);
let responseData = response.data;
if (responseData.length > 50) {
responseData = responseData.toString().substring(0, 47) + "...";
}
if (response.code !== 1) {
bean.status = DOWN;
bean.msg = `Error in send gRPC ${response.code} ${response.errorMessage}`;
} else {
let keywordFound = response.data.toString().includes(this.keyword);
if (keywordFound === !this.isInvertKeyword()) {
bean.status = UP;
bean.msg = `${responseData}, keyword [${this.keyword}] ${keywordFound ? "is" : "not"} found`;
} else {
log.debug("monitor:", `GRPC response [${response.data}] + ", but keyword [${this.keyword}] is ${keywordFound ? "present" : "not"} in [" + ${response.data} + "]"`);
bean.status = DOWN;
bean.msg = `, but keyword [${this.keyword}] is ${keywordFound ? "present" : "not"} in [" + ${responseData} + "]`;
}
}
} else if (this.type === "postgres") {
let startTime = dayjs().valueOf();

View file

@ -0,0 +1,86 @@
const { MonitorType } = require("./monitor-type");
const { UP, log } = require("../../src/util");
const dayjs = require("dayjs");
const grpc = require("@grpc/grpc-js");
const protojs = require("protobufjs");
export class GrpcKeywordMonitorType extends MonitorType {
name = "grpc-keyword";
/**
* @inheritdoc
*/
async check(monitor, heartbeat, _server) {
const startTime = dayjs().valueOf();
const service = this.constructGrpcService(this.grpcUrl, this.grpcProtobuf, this.grpcServiceName, this.grpcEnableTls);
let response = await this.grpcQuery(service, this.grpcMethod, this.grpcBody);
heartbeat.ping = dayjs().valueOf() - startTime;
log.debug(this.name, `gRPC response: ${response}`);
if (response.length > 50) {
response = response.toString().substring(0, 47) + "...";
}
let keywordFound = response.toString().includes(this.keyword);
if (keywordFound !== !this.isInvertKeyword()) {
log.debug(this.name, `GRPC response [${response}] + ", but keyword [${this.keyword}] is ${keywordFound ? "present" : "not"} in [" + ${response} + "]"`);
throw new Error(`keyword [${this.keyword}] is ${keywordFound ? "present" : "not"} in [" + ${response} + "]`);
}
heartbeat.status = UP;
heartbeat.msg = `${response}, keyword [${this.keyword}] ${keywordFound ? "is" : "not"} found`;
}
/**
* Create gRPC client
* @param {string} url grpc Url
* @param {string} protobufData grpc ProtobufData
* @param {string} serviceName grpc ServiceName
* @param {string} enableTls grpc EnableTls
* @returns {grpc.Service} grpc Service
*/
constructGrpcService(url, protobufData, serviceName, enableTls) {
const protocObject = protojs.parse(protobufData);
const protoServiceObject = protocObject.root.lookupService(serviceName);
const Client = grpc.makeGenericClientConstructor({});
const credentials = enableTls ? grpc.credentials.createSsl() : grpc.credentials.createInsecure();
const client = new Client(url, credentials);
return protoServiceObject.create((method, requestData, cb) => {
const fullServiceName = method.fullName;
const serviceFQDN = fullServiceName.split(".");
const serviceMethod = serviceFQDN.pop();
const serviceMethodClientImpl = `/${serviceFQDN.slice(1).join(".")}/${serviceMethod}`;
log.debug(this.name, `gRPC method ${serviceMethodClientImpl}`);
client.makeUnaryRequest(
serviceMethodClientImpl,
arg => arg,
arg => arg,
requestData,
cb);
}, false, false);
}
/**
* Create gRPC client stib
* @param {grpc.Service} service grpc Url
* @param {string} method grpc Method
* @param {string} body grpc Body
* @returns {Promise<string>} Result of gRPC query
*/
async grpcQuery(service, method, body) {
return new Promise((resolve, reject) => {
try {
service[`${method}`](JSON.parse(body), (err, response) => {
if (err) {
if (err.code !== 1) {
reject(`Error in send gRPC ${err.code} ${err.details}`);
}
log.debug(this.name, `ignoring ${err.code} ${err.details}, as code=1 is considered OK`);
resolve(`${err.code} is considered OK because ${err.details}`);
}
resolve(JSON.stringify(response));
});
} catch (err) {
reject(`Error ${err}. Please review your gRPC configuration option. The service name must not include package name value, and the method name must follow camelCase format`);
}
});
}
}

View file

@ -113,6 +113,7 @@ class UptimeKumaServer {
UptimeKumaServer.monitorTypeList["tailscale-ping"] = new TailscalePing();
UptimeKumaServer.monitorTypeList["dns"] = new DnsMonitorType();
UptimeKumaServer.monitorTypeList["mqtt"] = new MqttMonitorType();
UptimeKumaServer.monitorTypeList["grpc-keyword"] = new GrpcKeywordMonitorType();
UptimeKumaServer.monitorTypeList["mongodb"] = new MongodbMonitorType();
// Allow all CORS origins (polling) in development
@ -517,4 +518,5 @@ const { RealBrowserMonitorType } = require("./monitor-types/real-browser-monitor
const { TailscalePing } = require("./monitor-types/tailscale-ping");
const { DnsMonitorType } = require("./monitor-types/dns");
const { MqttMonitorType } = require("./monitor-types/mqtt");
const { GrpcKeywordMonitorType } = require("./monitor-types/grpc");
const { MongodbMonitorType } = require("./monitor-types/mongodb");

View file

@ -13,8 +13,6 @@ const postgresConParse = require("pg-connection-string").parse;
const mysql = require("mysql2");
const { NtlmClient } = require("axios-ntlm");
const { Settings } = require("./settings");
const grpc = require("@grpc/grpc-js");
const protojs = require("protobufjs");
const radiusClient = require("node-radius-client");
const redis = require("redis");
const oidc = require("openid-client");
@ -919,64 +917,6 @@ module.exports.timeObjectToLocal = (obj, timezone = undefined) => {
return timeObjectConvertTimezone(obj, timezone, false);
};
/**
* Create gRPC client stib
* @param {object} options from gRPC client
* @returns {Promise<object>} Result of gRPC query
*/
module.exports.grpcQuery = async (options) => {
const { grpcUrl, grpcProtobufData, grpcServiceName, grpcEnableTls, grpcMethod, grpcBody } = options;
const protocObject = protojs.parse(grpcProtobufData);
const protoServiceObject = protocObject.root.lookupService(grpcServiceName);
const Client = grpc.makeGenericClientConstructor({});
const credentials = grpcEnableTls ? grpc.credentials.createSsl() : grpc.credentials.createInsecure();
const client = new Client(
grpcUrl,
credentials
);
const grpcService = protoServiceObject.create(function (method, requestData, cb) {
const fullServiceName = method.fullName;
const serviceFQDN = fullServiceName.split(".");
const serviceMethod = serviceFQDN.pop();
const serviceMethodClientImpl = `/${serviceFQDN.slice(1).join(".")}/${serviceMethod}`;
log.debug("monitor", `gRPC method ${serviceMethodClientImpl}`);
client.makeUnaryRequest(
serviceMethodClientImpl,
arg => arg,
arg => arg,
requestData,
cb);
}, false, false);
return new Promise((resolve, _) => {
try {
return grpcService[`${grpcMethod}`](JSON.parse(grpcBody), function (err, response) {
const responseData = JSON.stringify(response);
if (err) {
return resolve({
code: err.code,
errorMessage: err.details,
data: ""
});
} else {
log.debug("monitor:", `gRPC response: ${JSON.stringify(response)}`);
return resolve({
code: 1,
errorMessage: "",
data: responseData
});
}
});
} catch (err) {
return resolve({
code: -1,
errorMessage: `Error ${err}. Please review your gRPC configuration option. The service name must not include package name value, and the method name must follow camelCase format`,
data: ""
});
}
});
};
/**
* Returns an array of SHA256 fingerprints for all known root certificates.
* @returns {Set} A set of SHA256 fingerprints.