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:
parent
4e02e086a9
commit
badabff90e
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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<ClusterStoreModel> {
|
||||
@ -84,6 +93,9 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
super({
|
||||
configName: "lens-cluster-store",
|
||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||
syncOptions: {
|
||||
equals: comparer.structural,
|
||||
},
|
||||
migrations,
|
||||
});
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<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}`;
|
||||
}
|
||||
|
||||
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<string> {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user