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

Implement base metadata gathering system

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2020-10-09 11:47:10 -04:00
parent abe6a4e0b1
commit ad23e51704
10 changed files with 364 additions and 12 deletions

View File

@ -9,12 +9,12 @@ import logger from "../main/logger";
import { broadcastIpc, IpcBroadcastParams } from "./ipc"; import { broadcastIpc, IpcBroadcastParams } from "./ipc";
import isEqual from "lodash/isEqual"; import isEqual from "lodash/isEqual";
export interface BaseStoreParams<T = any> extends ConfOptions<T> { export interface BaseStoreParams<T> extends ConfOptions<T> {
autoLoad?: boolean; autoLoad?: boolean;
syncEnabled?: boolean; syncEnabled?: boolean;
} }
export class BaseStore<T = any> extends Singleton { export abstract class BaseStore<T> extends Singleton {
protected storeConfig: Config<T>; protected storeConfig: Config<T>;
protected syncDisposers: Function[] = []; protected syncDisposers: Function[] = [];
@ -22,7 +22,7 @@ export class BaseStore<T = any> extends Singleton {
@observable isLoaded = false; @observable isLoaded = false;
@observable protected data: T; @observable protected data: T;
protected constructor(protected params: BaseStoreParams) { protected constructor(protected params: BaseStoreParams<T>) {
super(); super();
this.params = { this.params = {
autoLoad: false, autoLoad: false,
@ -157,10 +157,7 @@ export class BaseStore<T = any> extends Singleton {
return subFrames; return subFrames;
} }
@action protected abstract fromStore(data: T): void;
protected fromStore(data: T) {
this.data = data;
}
// todo: use "serializr" ? // todo: use "serializr" ?
toJSON(): T { toJSON(): T {

View File

@ -0,0 +1,196 @@
import { autobind } from "../renderer/utils";
import { clusterMetaStore } from "./cluster-meta-store";
import { ClusterId } from "./cluster-store";
import Singleton from "./utils/singleton";
export abstract class ClusterMetaCollector {
/**
* start tells the collector to start collecting its metadata once.
*
* - If finished collecting last time (either producing a value or error)
* then start to collect again
* - If still collected since last time then should continue and **not**
* restart
*/
abstract start(): void;
/**
* stop tells the collector to stop collecting its metadata. If `start()` is
* called after `stop()` then the collector should begin completely fresh.
*
* Should not throw if called multiple times.
*/
abstract stop(): void;
}
/**
* This is the constructor for a type that extends the abstract class
* `ClusterMetaCollector`
*
* @param clusterId the ID of the cluster that the collector should be targeting
* @param onSuccess the function that should be called when the collector has
* collected its metadata successfully
* @param onError the function that should be called when the collector
* encounters an error during the collection process
*/
export type MetadataConstructor<T extends ClusterMetaCollector> = new (clusterId: ClusterId, onSuccess: (result: any) => void, onError: (err: string) => void) => T;
export class ClusterMetaManager extends Singleton {
/**
* registeredCollectors is a mapping between the name of the metadata and the
* means of creating new collectors when new clusters are activated.
*/
protected registeredCollectors = new Map<ClusterId, MetadataConstructor<ClusterMetaCollector>>();
/**
* createdCollectors is the mapping between clusters and those collectors
* targeting that cluster. Each are stored so that it can be easily iterated
* over by name of metadata to be collected and by cluster ID
*/
protected createdCollectors = new Map<ClusterId, Map<string, ClusterMetaCollector>>();
protected interval: NodeJS.Timeout
// the milliseconds since 1970 when the last interval fired
protected lastInterval = Date.now()
protected constructor(protected collectionPeriod = 10 * 1000) {
super()
this.interval = setInterval(this.onInterval, this.collectionPeriod)
}
@autobind()
protected onInterval() {
this.lastInterval = Date.now()
for (const byCluster of this.createdCollectors.values()) {
for (const collector of byCluster.values()) {
collector.start()
}
}
}
/**
* registerCollector adds the collector to the list of current collectors and
* starts collection of metadata on all currently active clusters
*
* @param name is the name of the metadata to be collected, will be the
* name of the field in the metadata store
* @param Collector the type of the collector. This is so that the manager
* can create new instances on demand
*
* @throws if `name` has already been registered
*/
public registerCollector<T extends ClusterMetaCollector>(name: string, CollectorType: MetadataConstructor<T>) {
if (this.registeredCollectors.has(name)) {
throw new Error(`A collector for ${name} has already been registered`)
}
this.registeredCollectors.set(name, CollectorType)
/**
* add the collector to all the clusters currently been collected on
*/
for (const [clusterId, byCluster] of this.createdCollectors) {
const collector = new CollectorType(
clusterId,
this.onCollectionSuccess.bind(clusterId, name),
this.onCollectionError.bind(clusterId, name)
)
collector.start()
byCluster.set(name, collector)
}
}
public startCollectingFor(clusterId: ClusterId): () => void {
if (this.createdCollectors.has(clusterId)) {
console.log(`CLUSTER-META-MANAGER: already collecting for cluster ID ${clusterId}`)
} else {
const collectorsForCluster = new Map()
for (const [name, CollectorType] of this.registeredCollectors) {
const collector = new CollectorType(
clusterId,
this.onCollectionSuccess.bind(clusterId, name),
this.onCollectionError.bind(clusterId, name)
)
collector.start()
collectorsForCluster.set(name, collector)
}
this.createdCollectors.set(clusterId, collectorsForCluster)
}
return () => {
if (!this.createdCollectors.has(clusterId)) {
console.log(`CLUSTER-META-MANAGER: already stopped collecting for cluster ID ${clusterId}`)
return
}
for (const collector of this.createdCollectors.get(clusterId).values()) {
collector.stop()
}
this.createdCollectors.delete(clusterId)
}
}
@autobind()
private onCollectionSuccess(clusterId: ClusterId, metadataName: string, value: any): void {
clusterMetaStore.updateMetadataValue(clusterId, metadataName, value)
}
@autobind()
private onCollectionError(clusterId: ClusterId, metadataName: string, err: string): void {
clusterMetaStore.updateMetadataError(clusterId, metadataName, err)
}
/**
* deregisterCollector removes the currently registered collector and stops
* all instances from collecting
*
* @param name the name of the metadata collector to stop collecting
*
* @throws if `name` isn't the name of a currently registered metadata collector
*/
public deregisterCollector(name: string) {
if (!this.registeredCollectors.has(name)) {
throw new Error(`No collector for ${name} has been registered`)
}
/**
* stop all collectors by for metadata `name`
*/
for (const byCluster of this.createdCollectors.values()) {
byCluster.get(name).stop()
byCluster.delete(name)
}
this.registeredCollectors.delete(name)
}
/**
* setCollectionPeriod updates the time between collection starts and
* optionally starts one immediately
*
* @param newPeriod the new number of milliseconds between interval
* fires
* @param fireImmediately if true and `newPeriod` implies the interval would
* have already fired, then fire the interval
*/
public setCollectionPeriod(newPeriod: number, fireImmediately = true) {
this.collectionPeriod = newPeriod
clearInterval(this.interval)
setInterval(this.onInterval, this.collectionPeriod)
if (fireImmediately && (this.lastInterval + newPeriod) >= Date.now()) {
// the last time the interval fired is more than `newPeriod` in the past
// then fire immediately
this.onInterval()
}
}
}
export const clusterMetaManager = ClusterMetaManager.getInstance<ClusterMetaManager>()

View File

@ -0,0 +1,131 @@
import { BaseStore } from "./base-store";
import { ClusterId } from "./cluster-store";
import migrations from "../migrations/cluster-meta-store"
import { action, computed, observable, toJS } from "mobx";
interface ClusterMetadataModel {
reportingTime: string;
value?: any;
error?: string;
}
export interface ClusterMetaStoreModel {
metadata?: Record<ClusterId, {
[name: string]: ClusterMetadataModel
}>
}
export interface ClusterMetaData {
[name: string]: MetadataContainer;
}
export interface MetadataContainer {
reportingTime: Date;
value?: any;
error?: string;
}
export class ClusterMetaStore extends BaseStore<ClusterMetaStoreModel> {
@observable metadata = observable.map<ClusterId, ClusterMetaData>()
private constructor() {
super({
configName: "lens-cluster-meta-store",
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
migrations: migrations,
});
}
private updateMetadata(clusterId: ClusterId, name: string, report: MetadataContainer) {
if (!this.metadata.has(clusterId)) {
this.metadata.set(clusterId, {})
}
this.metadata.get(clusterId)[name] = report
}
public updateMetadataValue(clusterId: ClusterId, name: string, value: any) {
this.updateMetadata(clusterId, name, {
reportingTime: new Date(),
value,
})
}
public updateMetadataError(clusterId: ClusterId, name: string, error: string) {
this.updateMetadata(clusterId, name, {
reportingTime: new Date(),
error,
})
}
@action
protected fromStore({ metadata = {} }: ClusterMetaStoreModel = {}): void {
const updatedMetadata = this.metadata.toJS()
for (const [curCluster, curCollected] of this.metadata) {
if (!(curCluster in metadata)) {
continue
}
const prevCollected = metadata[curCluster]
delete metadata[curCluster]
for (const [name, curValue] of Object.entries(curCollected)) {
if (!(name in prevCollected)) {
continue
}
const prevValue = prevCollected[name]
delete prevCollected[name]
const prevReportingTime = new Date(prevValue.reportingTime)
if (prevReportingTime.getTime() >= curValue.reportingTime.getTime()) {
updatedMetadata.get(curCluster)[name] = {
...prevValue,
reportingTime: prevReportingTime,
}
}
}
for (const [newName, newValue] of Object.entries(prevCollected)) {
const newReportingTime = new Date(newValue.reportingTime)
updatedMetadata.get(curCluster)[newName] = {
...newValue,
reportingTime: newReportingTime,
}
}
}
for (const [newCluster, newCollected] of Object.entries(metadata)) {
updatedMetadata.set(newCluster, {})
for (const [newName, newValue] of Object.entries(newCollected)) {
const newReportingTime = new Date(newValue.reportingTime)
updatedMetadata.get(newCluster)[newName] = {
...newValue,
reportingTime: newReportingTime,
}
}
}
this.metadata = observable.map(updatedMetadata)
}
toJSON() {
const metadata: ClusterMetaStoreModel["metadata"] = {}
for (const [clusterId, collected] of this.metadata) {
const converted: Record<string, ClusterMetadataModel> = {}
for (const [name, value] of Object.entries(collected)) {
converted[name] = {
...value,
reportingTime: value.reportingTime.toISOString(),
}
}
metadata[clusterId] = converted
}
return toJS({ metadata, }, { recurseEverything: true })
}
}
export const clusterMetaStore = ClusterMetaStore.getInstance<ClusterMetaStore>()

View File

@ -1,5 +1,5 @@
import path from "path"; import path from "path";
import { app, ipcRenderer, remote, webFrame, webContents } from "electron"; import { app, ipcRenderer, remote, webFrame } from "electron";
import { unlink } from "fs-extra"; import { unlink } from "fs-extra";
import { action, computed, observable, toJS } from "mobx"; import { action, computed, observable, toJS } from "mobx";
import { BaseStore } from "./base-store"; import { BaseStore } from "./base-store";

View File

@ -0,0 +1,16 @@
import { ClusterMetaCollector } from "../cluster-meta-manager";
import { ClusterId } from "../cluster-store";
export class Distribution extends ClusterMetaCollector {
constructor(protected clusterId: ClusterId, protected onSuccess: (result: any) => void, protected onError: (err: string) => void) {
super()
}
start(): void {
// TODO
}
stop(): void {
// TODO
}
}

View File

@ -1,14 +1,14 @@
import { isMac, isWindows } from "./vars"; import { isMac, isWindows } from "./vars";
import winca from "win-ca" import winCa from "win-ca"
import macca from "mac-ca" import macCa from "mac-ca"
import logger from "../main/logger" import logger from "../main/logger"
if (isMac) { if (isMac) {
for (const crt of macca.all()) { for (const crt of macCa.all()) {
const attributes = crt.issuer?.attributes?.map((a: any) => `${a.name}=${a.value}`) const attributes = crt.issuer?.attributes?.map((a: any) => `${a.name}=${a.value}`)
logger.debug("Using host CA: " + attributes.join(",")) logger.debug("Using host CA: " + attributes.join(","))
} }
} }
if (isWindows) { if (isWindows) {
winca.inject("+") // see: https://github.com/ukoloff/win-ca#caveats winCa.inject("+") // see: https://github.com/ukoloff/win-ca#caveats
} }

View File

@ -14,6 +14,7 @@ import { getFeatures, installFeature, uninstallFeature, upgradeFeature } from ".
import request, { RequestPromiseOptions } from "request-promise-native" import request, { RequestPromiseOptions } from "request-promise-native"
import { apiResources } from "../common/rbac"; import { apiResources } from "../common/rbac";
import logger from "./logger" import logger from "./logger"
import { clusterMetaManager } from "../common/cluster-meta-manager";
export enum ClusterStatus { export enum ClusterStatus {
AccessGranted = 2, AccessGranted = 2,
@ -115,6 +116,7 @@ export class Cluster implements ClusterModel {
this.eventDisposers.push( this.eventDisposers.push(
reaction(this.getState, this.pushState), reaction(this.getState, this.pushState),
clusterMetaManager.startCollectingFor(this.id),
() => clearInterval(refreshTimer), () => clearInterval(refreshTimer),
); );
} }

View File

@ -18,6 +18,7 @@ import { userStore } from "../common/user-store";
import { workspaceStore } from "../common/workspace-store"; import { workspaceStore } from "../common/workspace-store";
import { tracker } from "../common/tracker"; import { tracker } from "../common/tracker";
import logger from "./logger" import logger from "./logger"
import { registerCollectors } from "./register-collectors";
const workingDir = path.join(app.getPath("appData"), appName); const workingDir = path.join(app.getPath("appData"), appName);
app.setName(appName); app.setName(appName);
@ -44,6 +45,8 @@ async function main() {
registerFileProtocol("static", __static); registerFileProtocol("static", __static);
registerCollectors()
// find free port // find free port
let proxyPort: number let proxyPort: number
try { try {

View File

@ -0,0 +1,6 @@
import { clusterMetaManager } from "../common/cluster-meta-manager";
import { Distribution } from "../common/meta-collectors/distribution";
export function registerCollectors() {
clusterMetaManager.registerCollector("distribution", Distribution)
}

View File

@ -0,0 +1 @@
export default {}