mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Switch to using injectable to setup route for metrics
Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
parent
4bac753f76
commit
ef92a98755
@ -7,7 +7,7 @@
|
||||
|
||||
import moment from "moment";
|
||||
import { apiBase } from "../index";
|
||||
import type { IMetricsQuery } from "../../../main/routes/metrics-route";
|
||||
import type { IMetricsQuery } from "../../../main/routes/metrics/metrics-query";
|
||||
|
||||
export interface IMetrics {
|
||||
status: string;
|
||||
|
||||
@ -11,7 +11,7 @@ import path from "path";
|
||||
import { readFile } from "fs-extra";
|
||||
import type { Cluster } from "../common/cluster/cluster";
|
||||
import { apiPrefix, appName, publicPath } from "../common/vars";
|
||||
import { KubeconfigRoute, MetricsRoute, VersionRoute } from "./routes";
|
||||
import { KubeconfigRoute, VersionRoute } from "./routes";
|
||||
import logger from "./logger";
|
||||
|
||||
export interface RouterRequestOpts {
|
||||
@ -171,10 +171,6 @@ export class Router {
|
||||
|
||||
this.router.add({ method: "get", path: "/version" }, VersionRoute.getVersion);
|
||||
this.router.add({ method: "get", path: `${apiPrefix}/kubeconfig/service-account/{namespace}/{account}` }, KubeconfigRoute.routeServiceAccountRoute);
|
||||
|
||||
// Metrics API
|
||||
this.router.add({ method: "post", path: `${apiPrefix}/metrics` }, MetricsRoute.routeMetrics);
|
||||
this.router.add({ method: "get", path: `${apiPrefix}/metrics/providers` }, MetricsRoute.routeMetricsProviders);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,5 +4,4 @@
|
||||
*/
|
||||
|
||||
export * from "./kubeconfig-route";
|
||||
export * from "./metrics-route";
|
||||
export * from "./version-route";
|
||||
|
||||
@ -1,109 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { LensApiRequest } from "../router";
|
||||
import { respondJson } from "../utils/http-responses";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import { ClusterMetadataKey, ClusterPrometheusMetadata } from "../../common/cluster-types";
|
||||
import logger from "../logger";
|
||||
import { getMetrics } from "../k8s-request";
|
||||
import { PrometheusProviderRegistry } from "../prometheus";
|
||||
|
||||
export type IMetricsQuery = string | string[] | {
|
||||
[metricName: string]: string;
|
||||
};
|
||||
|
||||
// This is used for backoff retry tracking.
|
||||
const ATTEMPTS = [false, false, false, false, true];
|
||||
|
||||
// prometheus metrics loader
|
||||
async function loadMetrics(promQueries: string[], cluster: Cluster, prometheusPath: string, queryParams: Record<string, string>): Promise<any[]> {
|
||||
const queries = promQueries.map(p => p.trim());
|
||||
const loaders = new Map<string, Promise<any>>();
|
||||
|
||||
async function loadMetric(query: string): Promise<any> {
|
||||
async function loadMetricHelper(): Promise<any> {
|
||||
for (const [attempt, lastAttempt] of ATTEMPTS.entries()) { // retry
|
||||
try {
|
||||
return await getMetrics(cluster, prometheusPath, { query, ...queryParams });
|
||||
} catch (error) {
|
||||
if (lastAttempt || (error?.statusCode >= 400 && error?.statusCode < 500)) {
|
||||
logger.error("[Metrics]: metrics not available", error?.response ? error.response?.body : error);
|
||||
throw new Error("Metrics not available");
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, (attempt + 1) * 1000)); // add delay before repeating request
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return loaders.get(query) ?? loaders.set(query, loadMetricHelper()).get(query);
|
||||
}
|
||||
|
||||
return Promise.all(queries.map(loadMetric));
|
||||
}
|
||||
|
||||
interface MetricProviderInfo {
|
||||
name: string;
|
||||
id: string;
|
||||
isConfigurable: boolean;
|
||||
}
|
||||
|
||||
export class MetricsRoute {
|
||||
static async routeMetrics({ response, cluster, payload, query }: LensApiRequest) {
|
||||
const queryParams: IMetricsQuery = Object.fromEntries(query.entries());
|
||||
const prometheusMetadata: ClusterPrometheusMetadata = {};
|
||||
|
||||
try {
|
||||
const { prometheusPath, provider } = await cluster.contextHandler.getPrometheusDetails();
|
||||
|
||||
prometheusMetadata.provider = provider?.id;
|
||||
prometheusMetadata.autoDetected = !cluster.preferences.prometheusProvider?.type;
|
||||
|
||||
if (!prometheusPath) {
|
||||
prometheusMetadata.success = false;
|
||||
|
||||
return respondJson(response, {});
|
||||
}
|
||||
|
||||
// return data in same structure as query
|
||||
if (typeof payload === "string") {
|
||||
const [data] = await loadMetrics([payload], cluster, prometheusPath, queryParams);
|
||||
|
||||
respondJson(response, data);
|
||||
} else if (Array.isArray(payload)) {
|
||||
const data = await loadMetrics(payload, cluster, prometheusPath, queryParams);
|
||||
|
||||
respondJson(response, data);
|
||||
} else {
|
||||
const queries = Object.entries<Record<string, string>>(payload)
|
||||
.map(([queryName, queryOpts]) => (
|
||||
provider.getQuery(queryOpts, queryName)
|
||||
));
|
||||
const result = await loadMetrics(queries, cluster, prometheusPath, queryParams);
|
||||
const data = Object.fromEntries(Object.keys(payload).map((metricName, i) => [metricName, result[i]]));
|
||||
|
||||
respondJson(response, data);
|
||||
}
|
||||
prometheusMetadata.success = true;
|
||||
} catch (error) {
|
||||
prometheusMetadata.success = false;
|
||||
respondJson(response, {});
|
||||
logger.warn(`[METRICS-ROUTE]: failed to get metrics for clusterId=${cluster.id}:`, error);
|
||||
} finally {
|
||||
cluster.metadata[ClusterMetadataKey.PROMETHEUS] = prometheusMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
static async routeMetricsProviders({ response }: LensApiRequest) {
|
||||
const providers: MetricProviderInfo[] = [];
|
||||
|
||||
for (const { name, id, isConfigurable } of PrometheusProviderRegistry.getInstance().providers.values()) {
|
||||
providers.push({ name, id, isConfigurable });
|
||||
}
|
||||
|
||||
respondJson(response, providers);
|
||||
}
|
||||
}
|
||||
103
src/main/routes/metrics/add-metrics-route.injectable.ts
Normal file
103
src/main/routes/metrics/add-metrics-route.injectable.ts
Normal file
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { apiPrefix } from "../../../common/vars";
|
||||
import type { LensApiRequest } from "../../router";
|
||||
import { respondJson } from "../../utils/http-responses";
|
||||
import { routeInjectionToken } from "../../router/router.injectable";
|
||||
import { ClusterMetadataKey, ClusterPrometheusMetadata } from "../../../common/cluster-types";
|
||||
import logger from "../../logger";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
import { getMetrics } from "../../k8s-request";
|
||||
import type { IMetricsQuery } from "./metrics-query";
|
||||
|
||||
// This is used for backoff retry tracking.
|
||||
const ATTEMPTS = [false, false, false, false, true];
|
||||
|
||||
async function loadMetrics(promQueries: string[], cluster: Cluster, prometheusPath: string, queryParams: Record<string, string>): Promise<any[]> {
|
||||
const queries = promQueries.map(p => p.trim());
|
||||
const loaders = new Map<string, Promise<any>>();
|
||||
|
||||
async function loadMetric(query: string): Promise<any> {
|
||||
async function loadMetricHelper(): Promise<any> {
|
||||
for (const [attempt, lastAttempt] of ATTEMPTS.entries()) { // retry
|
||||
try {
|
||||
return await getMetrics(cluster, prometheusPath, { query, ...queryParams });
|
||||
} catch (error) {
|
||||
if (lastAttempt || (error?.statusCode >= 400 && error?.statusCode < 500)) {
|
||||
logger.error("[Metrics]: metrics not available", error?.response ? error.response?.body : error);
|
||||
throw new Error("Metrics not available");
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, (attempt + 1) * 1000)); // add delay before repeating request
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return loaders.get(query) ?? loaders.set(query, loadMetricHelper()).get(query);
|
||||
}
|
||||
|
||||
return Promise.all(queries.map(loadMetric));
|
||||
}
|
||||
|
||||
const addMetricsRoute = async ({ response, cluster, payload, query }: LensApiRequest) => {
|
||||
const queryParams: IMetricsQuery = Object.fromEntries(query.entries());
|
||||
const prometheusMetadata: ClusterPrometheusMetadata = {};
|
||||
|
||||
try {
|
||||
const { prometheusPath, provider } = await cluster.contextHandler.getPrometheusDetails();
|
||||
|
||||
prometheusMetadata.provider = provider?.id;
|
||||
prometheusMetadata.autoDetected = !cluster.preferences.prometheusProvider?.type;
|
||||
|
||||
if (!prometheusPath) {
|
||||
prometheusMetadata.success = false;
|
||||
|
||||
return respondJson(response, {});
|
||||
}
|
||||
|
||||
// return data in same structure as query
|
||||
if (typeof payload === "string") {
|
||||
const [data] = await loadMetrics([payload], cluster, prometheusPath, queryParams);
|
||||
|
||||
respondJson(response, data);
|
||||
} else if (Array.isArray(payload)) {
|
||||
const data = await loadMetrics(payload, cluster, prometheusPath, queryParams);
|
||||
|
||||
respondJson(response, data);
|
||||
} else {
|
||||
const queries = Object.entries<Record<string, string>>(payload)
|
||||
.map(([queryName, queryOpts]) => (
|
||||
provider.getQuery(queryOpts, queryName)
|
||||
));
|
||||
const result = await loadMetrics(queries, cluster, prometheusPath, queryParams);
|
||||
const data = Object.fromEntries(Object.keys(payload).map((metricName, i) => [metricName, result[i]]));
|
||||
|
||||
respondJson(response, data);
|
||||
}
|
||||
prometheusMetadata.success = true;
|
||||
} catch (error) {
|
||||
prometheusMetadata.success = false;
|
||||
respondJson(response, {});
|
||||
logger.warn(`[METRICS-ROUTE]: failed to get metrics for clusterId=${cluster.id}:`, error);
|
||||
} finally {
|
||||
cluster.metadata[ClusterMetadataKey.PROMETHEUS] = prometheusMetadata;
|
||||
}
|
||||
};
|
||||
|
||||
const addMetricsRouteInjectable = getInjectable({
|
||||
id: "add-metrics-route",
|
||||
|
||||
instantiate: () => ({
|
||||
method: "post",
|
||||
path: `${apiPrefix}/metrics`,
|
||||
handler: addMetricsRoute,
|
||||
}),
|
||||
|
||||
injectionToken: routeInjectionToken,
|
||||
});
|
||||
|
||||
export default addMetricsRouteInjectable;
|
||||
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { apiPrefix } from "../../../common/vars";
|
||||
import type { LensApiRequest } from "../../router";
|
||||
import { respondJson } from "../../utils/http-responses";
|
||||
import { routeInjectionToken } from "../../router/router.injectable";
|
||||
import { PrometheusProviderRegistry } from "../../prometheus";
|
||||
import type { MetricProviderInfo } from "../../../common/k8s-api/endpoints/metrics.api";
|
||||
|
||||
const getMetricProvidersRouteInjectable = getInjectable({
|
||||
id: "get-metric-providers-route",
|
||||
|
||||
instantiate: () => ({
|
||||
method: "get",
|
||||
path: `${apiPrefix}/metrics/providers`,
|
||||
|
||||
handler: async (request: LensApiRequest) => {
|
||||
const providers: MetricProviderInfo[] = [];
|
||||
|
||||
for (const { name, id, isConfigurable } of PrometheusProviderRegistry.getInstance().providers.values()) {
|
||||
providers.push({ name, id, isConfigurable });
|
||||
}
|
||||
|
||||
respondJson(request.response, providers);
|
||||
},
|
||||
}),
|
||||
|
||||
injectionToken: routeInjectionToken,
|
||||
});
|
||||
|
||||
export default getMetricProvidersRouteInjectable;
|
||||
7
src/main/routes/metrics/metrics-query.ts
Normal file
7
src/main/routes/metrics/metrics-query.ts
Normal file
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
export type IMetricsQuery = string | string[] | {
|
||||
[metricName: string]: string;
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user