/** * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ import { KubeObject } from "../kube-object"; import { autoBind, cpuUnitsToNumber, iter, unitsToBytes } from "../../../renderer/utils"; import { IMetrics, metricsApi } from "./metrics.api"; import { KubeApi } from "../kube-api"; import type { KubeJsonApiData } from "../kube-json-api"; import { isClusterPageContext } from "../../utils/cluster-id-url-parsing"; export class NodesApi extends KubeApi { } export function getMetricsForAllNodes(): Promise { const opts = { category: "nodes" }; return metricsApi.getMetrics({ memoryUsage: opts, workloadMemoryUsage: opts, memoryCapacity: opts, memoryAllocatableCapacity: opts, cpuUsage: opts, cpuCapacity: opts, fsSize: opts, fsUsage: opts, }); } export interface INodeMetrics { [metric: string]: T; memoryUsage: T; workloadMemoryUsage: T; memoryCapacity: T; memoryAllocatableCapacity: T; cpuUsage: T; cpuCapacity: T; fsUsage: T; fsSize: T; } export interface NodeTaint { key: string; value?: string; effect: string; timeAdded: string; } export function formatNodeTaint(taint: NodeTaint): string { if (taint.value) { return `${taint.key}=${taint.value}:${taint.effect}`; } return `${taint.key}:${taint.effect}`; } export interface NodeCondition { type: string; status: string; lastHeartbeatTime?: string; lastTransitionTime?: string; reason?: string; message?: string; } export interface Node { spec: { podCIDR?: string; podCIDRs?: string[]; providerID?: string; /** * @deprecated see https://issues.k8s.io/61966 */ externalID?: string; taints?: NodeTaint[]; unschedulable?: boolean; }; status: { capacity?: { cpu: string; "ephemeral-storage": string; "hugepages-1Gi": string; "hugepages-2Mi": string; memory: string; pods: string; }; allocatable?: { cpu: string; "ephemeral-storage": string; "hugepages-1Gi": string; "hugepages-2Mi": string; memory: string; pods: string; }; conditions?: NodeCondition[]; addresses?: { type: string; address: string; }[]; daemonEndpoints?: { kubeletEndpoint: { Port: number; //it must be uppercase for backwards compatibility }; }; nodeInfo?: { machineID: string; systemUUID: string; bootID: string; kernelVersion: string; osImage: string; containerRuntimeVersion: string; kubeletVersion: string; kubeProxyVersion: string; operatingSystem: string; architecture: string; }; images?: { names: string[]; sizeBytes?: number; }[]; volumesInUse?: string[]; volumesAttached?: { name: string; devicePath: string; }[]; }; } /** * Iterate over `conditions` yielding the `type` field if the `status` field is * the string `"True"` * @param conditions An iterator of some conditions */ function* getTrueConditionTypes(conditions: IterableIterator | Iterable): IterableIterator { for (const { status, type } of conditions) { if (status === "True") { yield type; } } } /** * This regex is used in the `getRoleLabels()` method bellow, but placed here * as factoring out regexes is best practice. */ const nodeRoleLabelKeyMatcher = /^.*node-role.kubernetes.io\/+(?.+)$/; export class Node extends KubeObject { static kind = "Node"; static namespaced = false; static apiBase = "/api/v1/nodes"; constructor(data: KubeJsonApiData) { super(data); autoBind(this); } /** * Returns the concatination of all current condition types which have a status * of `"True"` */ getNodeConditionText(): string { return iter.join( getTrueConditionTypes(this.status?.conditions ?? []), " ", ); } getTaints() { return this.spec.taints || []; } getRoleLabels(): string { const { labels } = this.metadata; if (!labels || typeof labels !== "object") { return ""; } const roleLabels: string[] = []; for (const labelKey of Object.keys(labels)) { const match = nodeRoleLabelKeyMatcher.exec(labelKey); if (match) { roleLabels.push(match.groups.role); } } if (typeof labels["kubernetes.io/role"] === "string") { roleLabels.push(labels["kubernetes.io/role"]); } if (typeof labels["node.kubernetes.io/role"] === "string") { roleLabels.push(labels["node.kubernetes.io/role"]); } return roleLabels.join(", "); } getCpuCapacity() { if (!this.status.capacity || !this.status.capacity.cpu) return 0; return cpuUnitsToNumber(this.status.capacity.cpu); } getMemoryCapacity() { if (!this.status.capacity || !this.status.capacity.memory) return 0; return unitsToBytes(this.status.capacity.memory); } getConditions() { const conditions = this.status.conditions || []; if (this.isUnschedulable()) { return [{ type: "SchedulingDisabled", status: "True" }, ...conditions]; } return conditions; } getActiveConditions() { return this.getConditions().filter(c => c.status === "True"); } getWarningConditions() { const goodConditions = ["Ready", "HostUpgrades", "SchedulingDisabled"]; return this.getActiveConditions().filter(condition => { return !goodConditions.includes(condition.type); }); } getKubeletVersion() { return this.status.nodeInfo.kubeletVersion; } getOperatingSystem(): string { return this.metadata?.labels?.["kubernetes.io/os"] || this.metadata?.labels?.["beta.kubernetes.io/os"] || this.status?.nodeInfo?.operatingSystem || "linux"; } isUnschedulable() { return this.spec.unschedulable; } } /** * Only available within kubernetes cluster pages */ export const nodesApi = isClusterPageContext() ? new NodesApi({ objectConstructor: Node, }) : undefined;