From badabff90e0aba0a511063e52742f912518b64be Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Thu, 26 Nov 2020 09:41:47 +0200 Subject: [PATCH] Store prometheus cluster metadata based on metrics request responses (#1438) * Store prometheus metadata for clusters based on metrics requests Signed-off-by: Lauri Nevala * Use ClusterMetadataKey.PROMETEHUS as key Signed-off-by: Lauri Nevala * Update metadata only if it is changed Signed-off-by: Lauri Nevala * Use structural comparer as default for store sync reaction Signed-off-by: Lauri Nevala * No need to compare metadata as json anymore Signed-off-by: Lauri Nevala * Use structural comparer only in cluster-store Signed-off-by: Lauri Nevala * Refactoring Signed-off-by: Lauri Nevala * React only prometeheus preference changes to re-initialise prometheus connection Signed-off-by: Lauri Nevala * Add missing semicolons Signed-off-by: Lauri Nevala * Fix imports Signed-off-by: Lauri Nevala * Report metrics status in cluster-report (#1443) Signed-off-by: Lauri Nevala * Apply suggestions from code review Co-authored-by: Sebastian Malton Signed-off-by: Lauri Nevala * Fix logger reference Signed-off-by: Lauri Nevala Co-authored-by: Sebastian Malton --- extensions/telemetry/src/tracker.ts | 3 ++- src/common/cluster-store.ts | 24 ++++++++++++++++++------ src/main/cluster.ts | 15 ++++++++++++--- src/main/context-handler.ts | 19 +++++++++---------- src/main/routes/metrics-route.ts | 28 +++++++++++++++++++--------- 5 files changed, 60 insertions(+), 29 deletions(-) diff --git a/extensions/telemetry/src/tracker.ts b/extensions/telemetry/src/tracker.ts index 8c2fdab8e4..982ae146c4 100644 --- a/extensions/telemetry/src/tracker.ts +++ b/extensions/telemetry/src/tracker.ts @@ -118,7 +118,8 @@ export class Tracker extends Util.Singleton { kubernetesVersion: cluster.metadata.version, distribution: cluster.metadata.distribution, nodesCount: cluster.metadata.nodes, - lastSeen: cluster.metadata.lastSeen + lastSeen: cluster.metadata.lastSeen, + prometheus: cluster.metadata.prometheus }); } diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index 907f9b7aad..35ec663a55 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -2,7 +2,7 @@ import { workspaceStore } from "./workspace-store"; import path from "path"; import { app, ipcRenderer, remote, webFrame } from "electron"; import { unlink } from "fs-extra"; -import { action, computed, observable, reaction, toJS } from "mobx"; +import { action, comparer, computed, observable, reaction, toJS } from "mobx"; import { BaseStore } from "./base-store"; import { Cluster, ClusterState } from "../main/cluster"; import migrations from "../migrations/cluster-store"; @@ -23,9 +23,15 @@ 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 { activeCluster?: ClusterId; // last opened cluster clusters?: ClusterModel[] @@ -47,9 +53,15 @@ export interface ClusterModel { kubeConfig?: string; // yaml } -export interface ClusterPreferences { +export interface ClusterPreferences extends ClusterPrometheusPreferences{ terminalCWD?: string; clusterName?: string; + iconOrder?: number; + icon?: string; + httpsProxy?: string; +} + +export interface ClusterPrometheusPreferences { prometheus?: { namespace: string; service: string; @@ -59,9 +71,6 @@ export interface ClusterPreferences { prometheusProvider?: { type: string; }; - iconOrder?: number; - icon?: string; - httpsProxy?: string; } export class ClusterStore extends BaseStore { @@ -84,6 +93,9 @@ export class ClusterStore extends BaseStore { super({ configName: "lens-cluster-store", accessPropertiesByDotNotation: false, // To make dots safe in cluster context names + syncOptions: { + equals: comparer.structural, + }, migrations, }); diff --git a/src/main/cluster.ts b/src/main/cluster.ts index c7749ebe77..80b0289c08 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -1,8 +1,8 @@ import { ipcMain } from "electron"; -import type { ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences } from "../common/cluster-store"; +import type { ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences } from "../common/cluster-store"; import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api"; import type { WorkspaceId } from "../common/workspace-store"; -import { action, computed, observable, reaction, toJS, when } from "mobx"; +import { action, comparer, computed, observable, reaction, toJS, when } from "mobx"; import { apiKubePrefix } from "../common/vars"; import { broadcastMessage } from "../common/ipc"; import { ContextHandler } from "./context-handler"; @@ -27,7 +27,8 @@ export enum ClusterMetadataKey { CLUSTER_ID = "id", DISTRIBUTION = "distribution", NODES_COUNT = "nodes", - LAST_SEEN = "lastSeen" + LAST_SEEN = "lastSeen", + PROMETHEUS = "prometheus" } export type ClusterRefreshOptions = { @@ -87,6 +88,13 @@ export class Cluster implements ClusterModel, ClusterState { return this.preferences.clusterName || this.contextName; } + @computed get prometheusPreferences(): ClusterPrometheusPreferences { + const { prometheus, prometheusProvider } = this.preferences; + return toJS({ prometheus, prometheusProvider }, { + recurseEverything: true, + }); + } + get version(): string { return String(this.metadata?.version) || ""; } @@ -136,6 +144,7 @@ export class Cluster implements ClusterModel, ClusterState { if (ipcMain) { this.eventDisposers.push( reaction(() => this.getState(), () => this.pushState()), + reaction(() => this.prometheusPreferences, (prefs) => this.contextHandler.setupPrometheus(prefs), { equals: comparer.structural, }), () => { clearInterval(refreshTimer); clearInterval(refreshMetadataTimer); diff --git a/src/main/context-handler.ts b/src/main/context-handler.ts index a1ef58ad0f..2c0c0b4e8d 100644 --- a/src/main/context-handler.ts +++ b/src/main/context-handler.ts @@ -1,5 +1,5 @@ import type { PrometheusProvider, PrometheusService } from "./prometheus/provider-registry"; -import type { ClusterPreferences } from "../common/cluster-store"; +import type { ClusterPrometheusPreferences } from "../common/cluster-store"; import type { Cluster } from "./cluster"; import type httpProxy from "http-proxy"; import url, { UrlWithStringQuery } from "url"; @@ -22,7 +22,7 @@ export class ContextHandler { this.setupPrometheus(cluster.preferences); } - protected setupPrometheus(preferences: ClusterPreferences = {}) { + public setupPrometheus(preferences: ClusterPrometheusPreferences = {}) { 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 254abe188f..4572030cca 100644 --- a/src/main/routes/metrics-route.ts +++ b/src/main/routes/metrics-route.ts @@ -1,7 +1,9 @@ +import _ from "lodash"; import { LensApiRequest } from "../router"; import { LensApi } from "../lens-api"; -import { Cluster } from "../cluster"; -import _ from "lodash"; +import { Cluster, ClusterMetadataKey } from "../cluster"; +import { ClusterPrometheusMetadata } from "../../common/cluster-store"; +import logger from "../logger"; export type IMetricsQuery = string | string[] | { [metricName: string]: string; @@ -22,11 +24,9 @@ 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)) { + logger.error("[Metrics]: metrics not available", { error }); + throw new Error("Metrics not available"); } await new Promise(resolve => setTimeout(resolve, (attempt + 1) * 1000)); // add delay before repeating request @@ -43,13 +43,19 @@ async function loadMetrics(promQueries: string[], cluster: Cluster, prometheusPa 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 +71,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[ClusterMetadataKey.PROMETHEUS] = prometheusMetadata; } } }