import * as plugins from '../plugins.js'; import type { TSha256Digest, TImmutableContainerPlatform } from './immutableimage.js'; export interface IClusterNodeMetrics { cpuUsagePercent: number; memoryUsedMB: number; memoryAvailableMB: number; diskUsedGB: number; diskAvailableGB: number; containerCount: number; timestamp: number; } /** * Lightweight high-frequency node metrics sample (1s cadence). * Streamed spark -> cloudly -> admin UIs; never persisted. */ export interface INodeMetricsSample { timestamp: number; cpuUsagePercent: number; memoryUsedMB: number; memoryAvailableMB: number; /** Host-level network receive rate across non-loopback interfaces. */ networkRxBytesPerSec?: number; /** Host-level network transmit rate across non-loopback interfaces. */ networkTxBytesPerSec?: number; } export type TNodeActionName = 'systemUpgrade' | 'reboot' | 'serveZoneServiceUpdate'; export type TNodeActionStatus = 'pending' | 'running' | 'succeeded' | 'failed'; /** * An operator-requested action executed by spark on a node. * Delivered to spark in metrics-sample responses; results posted back. */ export interface INodeAction { id: string; nodeId: string; actionName: TNodeActionName; payload?: { /** Reboot only: gracefully stop workloads before rebooting. */ drainFirst?: boolean; }; status: TNodeActionStatus; requestedAt: number; requestedBy?: string; startedAt?: number; finishedAt?: number; resultText?: string; } export interface IServeZoneServiceRuntimeInfo { name: string; serviceId?: string; image?: string; desiredImage?: string; imageVersion?: string; runningImageId?: string; runningTaskCount?: number; running: boolean; checkedAt: number; updatedAt?: number; error?: string; } /** Spark -> Cloudly HTTP body for POST /spark/v1/nodes/metrics-sample */ export interface ISparkMetricsSampleRequest { nodeId: string; nodeToken: string; sample: INodeMetricsSample; } export interface ISparkMetricsSampleResponse { accepted: boolean; message?: string; /** Actions queued for this node; spark executes them and posts results. */ pendingActions?: INodeAction[]; } /** Spark -> Cloudly HTTP body for POST /spark/v1/nodes/action-result */ export interface ISparkActionResultRequest { nodeId: string; nodeToken: string; actionId: string; status: Extract; resultText?: string; } export interface ISparkActionResultResponse { accepted: boolean; message?: string; } export interface ISwarmManagerObservedNodeV1 { swarmNodeId: string; nodeName: string; platform: TImmutableContainerPlatform; role: 'manager' | 'worker'; availability: 'active' | 'pause' | 'drain'; state: 'unknown' | 'down' | 'ready' | 'disconnected'; managerStatus?: { leader: boolean; reachability: 'unknown' | 'unreachable' | 'reachable'; }; } export interface ISwarmManagerSnapshotV1 { snapshotVersion: 1; snapshotDigest: TSha256Digest; nodeCount: number; managerCount: number; nodes: ISwarmManagerObservedNodeV1[]; } interface ISparkSwarmObservationBaseV1 { schemaVersion: 1; reporterSessionId: string; observationSequence: number; observedAt: number; } export type TSparkSwarmObservationV1 = (ISparkSwarmObservationBaseV1 & { state: 'unknown'; reason: 'docker-unavailable' | 'query-failed' | 'not-initialized'; }) | (ISparkSwarmObservationBaseV1 & { state: 'not-member'; }) | (ISparkSwarmObservationBaseV1 & { state: 'member'; swarmClusterId: string; localSwarmNodeId: string; controlAvailable: boolean; managerSnapshot?: ISwarmManagerSnapshotV1; }); interface ISparkSwarmObservationBaseV2 { schemaVersion: 2; reporterSessionId: string; observationSequence: number; observedAt: number; } export type TSparkSwarmObservationV2 = (ISparkSwarmObservationBaseV2 & { state: 'unknown'; reason: 'docker-unavailable' | 'query-failed' | 'not-initialized'; }) | (ISparkSwarmObservationBaseV2 & { state: 'not-member'; }) | (ISparkSwarmObservationBaseV2 & { state: 'member'; localSwarmNodeId: string; controlAvailable: false; }) | (ISparkSwarmObservationBaseV2 & { state: 'member'; swarmClusterId: string; localSwarmNodeId: string; controlAvailable: true; managerSnapshot?: ISwarmManagerSnapshotV1; }); export type TSparkSwarmObservationV2RejectionCode = 'sequence-conflict' | 'sequence-stale' | 'sequence-gap' | 'session-sequence-invalid' | 'session-retired' | 'observation-too-old' | 'observation-from-future' | 'observation-time-not-advancing' | 'acceptance-race'; export declare const sparkSwarmObservationV2Contract: Readonly<{ readonly endpoint: "/spark/v2/nodes/swarm-observation"; readonly digestDomain: "serve.zone/spark-swarm-observation-v2"; readonly maximumPastAgeMs: 300000; readonly maximumFutureSkewMs: 30000; readonly retainedRetiredSessionIds: 32; readonly rejectionRetryability: Readonly<{ 'sequence-conflict': false; 'sequence-stale': false; 'sequence-gap': false; 'session-sequence-invalid': false; 'session-retired': false; 'observation-too-old': false; 'observation-from-future': false; 'observation-time-not-advancing': false; 'acceptance-race': true; }>; }>; /** Cloudly derives node and cluster scope from this authenticated Spark request. */ export interface ISparkSwarmObservationV2Request { nodeId: string; nodeToken: string; observation: TSparkSwarmObservationV2; } export interface ISparkSwarmObservationV2AcceptedReceipt { schemaVersion: 2; state: 'accepted'; nodeId: string; reporterSessionId: string; observationSequence: number; observationDigest: TSha256Digest; acceptedAt: number; /** Runtime target generation observed immediately after this acceptance. */ targetGenerationAtAcceptance: number; } export interface ISparkSwarmObservationV2RejectedReceipt { schemaVersion: 2; state: 'rejected'; nodeId: string; reporterSessionId: string; observationSequence: number; observationDigest: TSha256Digest; rejectedAt: number; code: TSparkSwarmObservationV2RejectionCode; retryable: boolean; } /** * Bound receipts are returned only after request validation and node * authentication. Malformed and authentication failures use generic HTTP * errors so they cannot expose credential or persisted replay state. */ export type TSparkSwarmObservationV2Response = ISparkSwarmObservationV2AcceptedReceipt | ISparkSwarmObservationV2RejectedReceipt; /** Cloudly derives node and cluster scope from this authenticated Spark request. */ export interface ISparkSwarmObservationRequest { nodeId: string; nodeToken: string; observation: TSparkSwarmObservationV1; } export interface ISparkSwarmObservationResponse { accepted: boolean; acceptedSequence?: number; targetGeneration?: number; message?: string; } export interface IClusterRuntimeTargetV1 { cloudlyNodeId: string; swarmClusterId: string; swarmNodeId: string; nodeName: string; platform: TImmutableContainerPlatform; } export interface IClusterRuntimeTargetSetReadyV1 { schemaVersion: 1; state: 'ready'; cloudlyClusterId: string; swarmClusterId: string; generation: number; targetSetDigest: TSha256Digest; acceptedAt: number; freshUntil: number; targets: IClusterRuntimeTargetV1[]; } export interface IClusterRuntimeTargetSetUnavailableV1 { schemaVersion: 1; state: 'unavailable'; cloudlyClusterId: string; generation: number; changedAt: number; reason: 'never-observed' | 'stale' | 'no-manager-consensus' | 'identity-conflict' | 'no-schedulable-targets'; } export type TClusterRuntimeTargetSetV1 = IClusterRuntimeTargetSetReadyV1 | IClusterRuntimeTargetSetUnavailableV1; export declare const createSparkSwarmObservationV2DigestInput: (observationArg: TSparkSwarmObservationV2) => string; export declare const computeSparkSwarmObservationV2Digest: (observationArg: TSparkSwarmObservationV2) => Promise; export declare const createSwarmManagerSnapshotDigestInput: (swarmClusterIdArg: string, snapshotArg: ISwarmManagerSnapshotV1) => string; export declare const computeSwarmManagerSnapshotDigest: (swarmClusterIdArg: string, snapshotArg: ISwarmManagerSnapshotV1) => Promise; export declare const validateSparkSwarmObservation: (observationArg: unknown) => Promise; export declare const validateSparkSwarmObservationV2: (observationArg: unknown) => Promise; export declare const validateSparkSwarmObservationV2Request: (requestArg: unknown) => Promise; export declare const validateSparkSwarmObservationV2Response: (responseArg: unknown, expectedRequestArg: ISparkSwarmObservationV2Request) => Promise; export declare const createClusterRuntimeTargetSetDigestInput: (targetSetArg: IClusterRuntimeTargetSetReadyV1) => string; export declare const computeClusterRuntimeTargetSetDigest: (targetSetArg: IClusterRuntimeTargetSetReadyV1) => Promise; export declare const validateClusterRuntimeTargetSet: (targetSetArg: unknown) => Promise; export declare const isClusterRuntimeTargetSetFresh: (targetSetArg: TClusterRuntimeTargetSetV1, nowArg: number) => targetSetArg is IClusterRuntimeTargetSetReadyV1; export type TSparkNodeMode = 'cloudly' | 'coreflow-node'; export type TSparkCloudlyConnectionStatus = 'not-configured' | 'connecting' | 'connected' | 'failed'; export interface ISparkNodeRuntimeInfo { runtime: 'spark'; nodeId: string; mode?: TSparkNodeMode; hostname?: string; platform: string; arch: string; osRelease?: string; sparkVersion: string; cloudlyUrl?: string; cloudlyConnectionStatus: TSparkCloudlyConnectionStatus; dockerAvailable: boolean; swarmNodeId?: string; serveZoneServices?: IServeZoneServiceRuntimeInfo[]; checkedAt: number; lastError?: string; } export interface IClusterNode { id: string; data: { /** * Reference to the cluster this node belongs to */ clusterId: string; /** * Reference to the physical server (if applicable) */ baremetalId?: string; /** * Type of node */ nodeType: 'baremetal' | 'vm' | 'container'; /** * Current status of the node */ status: 'initializing' | 'online' | 'offline' | 'maintenance'; /** * Role of the node in the cluster */ role: 'master' | 'worker'; /** * Timestamp when node joined the cluster */ joinedAt: number; /** * Last health check timestamp */ lastHealthCheck: number; /** * Current metrics for the node */ metrics?: IClusterNodeMetrics; /** * Runtime status reported by the Spark node agent. */ sparkRuntimeInfo?: ISparkNodeRuntimeInfo; /** * Docker swarm node ID if part of swarm */ swarmNodeId?: string; /** * SSH keys deployed to this node */ sshKeys: plugins.tsclass.network.ISshKey[]; /** * Debian packages installed on this node */ requiredDebianPackages: string[]; }; } export {};