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,
|
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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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> {
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user