1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Store prometheus cluster metadata based on metrics request responses (#1438)

* Store prometheus metadata for clusters based on metrics requests

Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>

* Use ClusterMetadataKey.PROMETEHUS as key

Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>

* Update metadata only if it is changed

Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>

* Use structural comparer as default for store sync reaction

Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>

* No need to compare metadata as json anymore

Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>

* Use structural comparer only in cluster-store

Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>

* Refactoring

Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>

* React only prometeheus preference changes to re-initialise prometheus connection

Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>

* Add missing semicolons

Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>

* Fix imports

Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>

* Report metrics status in cluster-report (#1443)

Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>

* Apply suggestions from code review

Co-authored-by: Sebastian Malton <sebastian@malton.name>
Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>

* Fix logger reference

Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>

Co-authored-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Lauri Nevala 2020-11-26 09:41:47 +02:00 committed by GitHub
parent 4e02e086a9
commit badabff90e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 60 additions and 29 deletions

View File

@ -118,7 +118,8 @@ export class Tracker extends Util.Singleton {
kubernetesVersion: cluster.metadata.version, kubernetesVersion: cluster.metadata.version,
distribution: cluster.metadata.distribution, distribution: cluster.metadata.distribution,
nodesCount: cluster.metadata.nodes, nodesCount: cluster.metadata.nodes,
lastSeen: cluster.metadata.lastSeen lastSeen: cluster.metadata.lastSeen,
prometheus: cluster.metadata.prometheus
}); });
} }

View File

@ -2,7 +2,7 @@ import { workspaceStore } from "./workspace-store";
import path from "path"; import path from "path";
import { app, ipcRenderer, remote, webFrame } from "electron"; import { app, ipcRenderer, remote, webFrame } from "electron";
import { unlink } from "fs-extra"; 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 { BaseStore } from "./base-store";
import { Cluster, ClusterState } from "../main/cluster"; import { Cluster, ClusterState } from "../main/cluster";
import migrations from "../migrations/cluster-store"; import migrations from "../migrations/cluster-store";
@ -23,9 +23,15 @@ export interface ClusterIconUpload {
} }
export interface ClusterMetadata { 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 { export interface ClusterStoreModel {
activeCluster?: ClusterId; // last opened cluster activeCluster?: ClusterId; // last opened cluster
clusters?: ClusterModel[] clusters?: ClusterModel[]
@ -47,9 +53,15 @@ export interface ClusterModel {
kubeConfig?: string; // yaml kubeConfig?: string; // yaml
} }
export interface ClusterPreferences { export interface ClusterPreferences extends ClusterPrometheusPreferences{
terminalCWD?: string; terminalCWD?: string;
clusterName?: string; clusterName?: string;
iconOrder?: number;
icon?: string;
httpsProxy?: string;
}
export interface ClusterPrometheusPreferences {
prometheus?: { prometheus?: {
namespace: string; namespace: string;
service: string; service: string;
@ -59,9 +71,6 @@ export interface ClusterPreferences {
prometheusProvider?: { prometheusProvider?: {
type: string; type: string;
}; };
iconOrder?: number;
icon?: string;
httpsProxy?: string;
} }
export class ClusterStore extends BaseStore<ClusterStoreModel> { export class ClusterStore extends BaseStore<ClusterStoreModel> {
@ -84,6 +93,9 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
super({ super({
configName: "lens-cluster-store", configName: "lens-cluster-store",
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
syncOptions: {
equals: comparer.structural,
},
migrations, migrations,
}); });

View File

@ -1,8 +1,8 @@
import { ipcMain } from "electron"; 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 { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api";
import type { WorkspaceId } from "../common/workspace-store"; 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 { apiKubePrefix } from "../common/vars";
import { broadcastMessage } from "../common/ipc"; import { broadcastMessage } from "../common/ipc";
import { ContextHandler } from "./context-handler"; import { ContextHandler } from "./context-handler";
@ -27,7 +27,8 @@ export enum ClusterMetadataKey {
CLUSTER_ID = "id", CLUSTER_ID = "id",
DISTRIBUTION = "distribution", DISTRIBUTION = "distribution",
NODES_COUNT = "nodes", NODES_COUNT = "nodes",
LAST_SEEN = "lastSeen" LAST_SEEN = "lastSeen",
PROMETHEUS = "prometheus"
} }
export type ClusterRefreshOptions = { export type ClusterRefreshOptions = {
@ -87,6 +88,13 @@ export class Cluster implements ClusterModel, ClusterState {
return this.preferences.clusterName || this.contextName; return this.preferences.clusterName || this.contextName;
} }
@computed get prometheusPreferences(): ClusterPrometheusPreferences {
const { prometheus, prometheusProvider } = this.preferences;
return toJS({ prometheus, prometheusProvider }, {
recurseEverything: true,
});
}
get version(): string { get version(): string {
return String(this.metadata?.version) || ""; return String(this.metadata?.version) || "";
} }
@ -136,6 +144,7 @@ export class Cluster implements ClusterModel, ClusterState {
if (ipcMain) { if (ipcMain) {
this.eventDisposers.push( this.eventDisposers.push(
reaction(() => this.getState(), () => this.pushState()), reaction(() => this.getState(), () => this.pushState()),
reaction(() => this.prometheusPreferences, (prefs) => this.contextHandler.setupPrometheus(prefs), { equals: comparer.structural, }),
() => { () => {
clearInterval(refreshTimer); clearInterval(refreshTimer);
clearInterval(refreshMetadataTimer); clearInterval(refreshMetadataTimer);

View File

@ -1,5 +1,5 @@
import type { PrometheusProvider, PrometheusService } from "./prometheus/provider-registry"; 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 { Cluster } from "./cluster";
import type httpProxy from "http-proxy"; import type httpProxy from "http-proxy";
import url, { UrlWithStringQuery } from "url"; import url, { UrlWithStringQuery } from "url";
@ -22,7 +22,7 @@ export class ContextHandler {
this.setupPrometheus(cluster.preferences); this.setupPrometheus(cluster.preferences);
} }
protected setupPrometheus(preferences: ClusterPreferences = {}) { public setupPrometheus(preferences: ClusterPrometheusPreferences = {}) {
this.prometheusProvider = preferences.prometheusProvider?.type; this.prometheusProvider = preferences.prometheusProvider?.type;
this.prometheusPath = null; this.prometheusPath = null;
if (preferences.prometheus) { if (preferences.prometheus) {
@ -32,13 +32,18 @@ export class ContextHandler {
} }
protected async resolvePrometheusPath(): Promise<string> { protected async resolvePrometheusPath(): Promise<string> {
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}`; return `${namespace}/services/${service}:${port}`;
} }
async getPrometheusProvider() { async getPrometheusProvider() {
if (!this.prometheusProvider) { if (!this.prometheusProvider) {
const service = await this.getPrometheusService(); const service = await this.getPrometheusService();
if (!service) {
return null;
}
logger.info(`using ${service.id} as prometheus provider`); logger.info(`using ${service.id} as prometheus provider`);
this.prometheusProvider = service.id; this.prometheusProvider = service.id;
} }
@ -52,13 +57,7 @@ export class ContextHandler {
return await provider.getPrometheusService(apiClient); return await provider.getPrometheusService(apiClient);
}); });
const resolvedPrometheusServices = await Promise.all(prometheusPromises); const resolvedPrometheusServices = await Promise.all(prometheusPromises);
const service = resolvedPrometheusServices.filter(n => n)[0]; return resolvedPrometheusServices.filter(n => n)[0];
return service || {
id: "lens",
namespace: "lens-metrics",
service: "prometheus",
port: 80
};
} }
async getPrometheusPath(): Promise<string> { async getPrometheusPath(): Promise<string> {

View File

@ -1,7 +1,9 @@
import _ from "lodash";
import { LensApiRequest } from "../router"; import { LensApiRequest } from "../router";
import { LensApi } from "../lens-api"; import { LensApi } from "../lens-api";
import { Cluster } from "../cluster"; import { Cluster, ClusterMetadataKey } from "../cluster";
import _ from "lodash"; import { ClusterPrometheusMetadata } from "../../common/cluster-store";
import logger from "../logger";
export type IMetricsQuery = string | string[] | { export type IMetricsQuery = string | string[] | {
[metricName: string]: string; [metricName: string]: string;
@ -22,11 +24,9 @@ async function loadMetrics(promQueries: string[], cluster: Cluster, prometheusPa
try { try {
return await cluster.getMetrics(prometheusPath, { query, ...queryParams }); return await cluster.getMetrics(prometheusPath, { query, ...queryParams });
} catch (error) { } catch (error) {
if (lastAttempt || error?.statusCode === 404) { if (lastAttempt || (error?.statusCode >= 400 && error?.statusCode < 500)) {
return { logger.error("[Metrics]: metrics not available", { error });
status: error.toString(), throw new Error("Metrics not available");
data: { result: [] },
};
} }
await new Promise(resolve => setTimeout(resolve, (attempt + 1) * 1000)); // add delay before repeating request 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 { class MetricsRoute extends LensApi {
async routeMetrics({ response, cluster, payload, query }: LensApiRequest) { async routeMetrics({ response, cluster, payload, query }: LensApiRequest) {
const queryParams: IMetricsQuery = Object.fromEntries(query.entries()); const queryParams: IMetricsQuery = Object.fromEntries(query.entries());
const prometheusMetadata: ClusterPrometheusMetadata = {};
try { try {
const [prometheusPath, prometheusProvider] = await Promise.all([ const [prometheusPath, prometheusProvider] = await Promise.all([
cluster.contextHandler.getPrometheusPath(), cluster.contextHandler.getPrometheusPath(),
cluster.contextHandler.getPrometheusProvider() 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 // return data in same structure as query
if (typeof payload === "string") { if (typeof payload === "string") {
const [data] = await loadMetrics([payload], cluster, prometheusPath, queryParams); 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]])); const data = Object.fromEntries(Object.keys(payload).map((metricName, i) => [metricName, result[i]]));
this.respondJson(response, data); this.respondJson(response, data);
} }
prometheusMetadata.success = true;
} catch { } catch {
prometheusMetadata.success = false;
this.respondJson(response, {}); this.respondJson(response, {});
} finally {
cluster.metadata[ClusterMetadataKey.PROMETHEUS] = prometheusMetadata;
} }
} }
} }