From 2e568d341357d9e4c010d8053fc7a77b0856ff7a Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Thu, 19 Nov 2020 10:41:37 +0200 Subject: [PATCH] Store prometheus metadata for clusters based on metrics requests Signed-off-by: Lauri Nevala --- src/common/cluster-store.ts | 8 +++++++- src/main/cluster.ts | 1 + src/main/context-handler.ts | 17 ++++++++--------- src/main/routes/metrics-route.ts | 23 +++++++++++++++-------- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index 7ea92b612a..9a366706bf 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -21,7 +21,13 @@ export interface ClusterIconUpload { } export interface ClusterMetadata { - [key: string]: string | number | boolean; + [key: string]: string | number | boolean | object; +} + +export type ClusterPrometheusMetadata = { + success?: boolean; + provider?: string; + autoDetected?: boolean; } export interface ClusterStoreModel { diff --git a/src/main/cluster.ts b/src/main/cluster.ts index b808f6d997..d8f54ac7ce 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -139,6 +139,7 @@ export class Cluster implements ClusterModel, ClusterState { if (ipcMain) { this.eventDisposers.push( reaction(() => this.getState(), () => this.pushState()), + reaction(() => this.preferences, () => this.contextHandler.setupPrometheus(this.preferences)), () => { clearInterval(refreshTimer) clearInterval(refreshMetadataTimer) diff --git a/src/main/context-handler.ts b/src/main/context-handler.ts index a3cf6185dd..42ede41fac 100644 --- a/src/main/context-handler.ts +++ b/src/main/context-handler.ts @@ -22,7 +22,7 @@ export class ContextHandler { this.setupPrometheus(cluster.preferences); } - protected setupPrometheus(preferences: ClusterPreferences = {}) { + public setupPrometheus(preferences: ClusterPreferences = {}) { this.prometheusProvider = preferences.prometheusProvider?.type; this.prometheusPath = null; if (preferences.prometheus) { @@ -32,13 +32,18 @@ export class ContextHandler { } protected async resolvePrometheusPath(): Promise { - const { service, namespace, port } = await this.getPrometheusService() + const prometheusService = await this.getPrometheusService() + if (!prometheusService) return null + const { service, namespace, port } = prometheusService return `${namespace}/services/${service}:${port}` } async getPrometheusProvider() { if (!this.prometheusProvider) { const service = await this.getPrometheusService() + if (!service) { + return null + } logger.info(`using ${service.id} as prometheus provider`) this.prometheusProvider = service.id } @@ -52,13 +57,7 @@ export class ContextHandler { return await provider.getPrometheusService(apiClient) }) const resolvedPrometheusServices = await Promise.all(prometheusPromises) - const service = resolvedPrometheusServices.filter(n => n)[0]; - return service || { - id: "lens", - namespace: "lens-metrics", - service: "prometheus", - port: 80 - } + return resolvedPrometheusServices.filter(n => n)[0]; } async getPrometheusPath(): Promise { diff --git a/src/main/routes/metrics-route.ts b/src/main/routes/metrics-route.ts index dc77f7fb9f..02c6e4733a 100644 --- a/src/main/routes/metrics-route.ts +++ b/src/main/routes/metrics-route.ts @@ -2,6 +2,7 @@ import { LensApiRequest } from "../router" import { LensApi } from "../lens-api" import { Cluster } from "../cluster" import _ from "lodash" +import { ClusterPrometheusMetadata } from "../../common/cluster-store" export type IMetricsQuery = string | string[] | { [metricName: string]: string; @@ -22,11 +23,8 @@ async function loadMetrics(promQueries: string[], cluster: Cluster, prometheusPa try { return await cluster.getMetrics(prometheusPath, { query, ...queryParams }) } catch (error) { - if (lastAttempt || error?.statusCode === 404) { - return { - status: error.toString(), - data: { result: [] }, - } + if (lastAttempt || (error?.statusCode >= 400 && error?.statusCode < 500)) { + throw new Error("Metrics not available"); } await new Promise(resolve => setTimeout(resolve, (attempt + 1) * 1000)); // add delay before repeating request @@ -36,20 +34,25 @@ async function loadMetrics(promQueries: string[], cluster: Cluster, prometheusPa return loaders.get(query) ?? loaders.set(query, loadMetricHelper()).get(query) } - return Promise.all(queries.map(loadMetric)) } class MetricsRoute extends LensApi { async routeMetrics({ response, cluster, payload, query }: LensApiRequest) { const queryParams: IMetricsQuery = Object.fromEntries(query.entries()) - + const prometheusMetadata: ClusterPrometheusMetadata = {} try { const [prometheusPath, prometheusProvider] = await Promise.all([ cluster.contextHandler.getPrometheusPath(), cluster.contextHandler.getPrometheusProvider() ]) - + prometheusMetadata["provider"] = prometheusProvider?.id + prometheusMetadata["autoDetected"] = !cluster.preferences.prometheusProvider?.type + if (!prometheusPath) { + prometheusMetadata["success"] = false + this.respondJson(response, {}) + return + } // return data in same structure as query if (typeof payload === "string") { const [data] = await loadMetrics([payload], cluster, prometheusPath, queryParams) @@ -65,8 +68,12 @@ class MetricsRoute extends LensApi { const data = Object.fromEntries(Object.keys(payload).map((metricName, i) => [metricName, result[i]])) this.respondJson(response, data) } + prometheusMetadata["success"] = true } catch { + prometheusMetadata["success"] = false this.respondJson(response, {}) + } finally { + cluster.metadata["prometheus"] = prometheusMetadata } } }