diff --git a/server/src/constants.ts b/server/src/constants.ts index a068095b3d..3a3c0e1f9e 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -192,6 +192,10 @@ export class ServerConfig { static get HTTP_INTERCEPTOR_URL() { return process.env.HTTP_INTERCEPTOR_URL } + + static get KUBEBLOCK_V5_UPGRADE_URL() { + return process.env.KUBEBLOCK_V5_UPGRADE_URL + } } export const LABEL_KEY_USER_ID = 'laf.dev/user.id' @@ -255,3 +259,6 @@ export const STORAGE_LIMIT = 1000 // 1000 items // HTTP interceptor export const HTTP_INTERCEPTOR_TIMEOUT = 3000 // 3s + +// KubeBlock v5 upgrade API +export const KUBEBLOCK_V5_UPGRADE_API_TIMEOUT = 3000 // 3s diff --git a/server/src/database/dedicated-database/dedicated-database.service.ts b/server/src/database/dedicated-database/dedicated-database.service.ts index febebd0dea..00aaf3b630 100644 --- a/server/src/database/dedicated-database/dedicated-database.service.ts +++ b/server/src/database/dedicated-database/dedicated-database.service.ts @@ -20,6 +20,7 @@ import { ApplicationBundle } from 'src/application/entities/application-bundle' import * as assert from 'assert' import { extractNumber } from 'src/utils/number' import { formatK8sErrorAsJson } from 'src/utils/k8s-error' +import { ServerConfig, KUBEBLOCK_V5_UPGRADE_API_TIMEOUT } from 'src/constants' const getDedicatedDatabaseName = (appid: string) => appid @@ -142,6 +143,7 @@ export class DedicatedDatabaseService { appid, 'horizontalScaling', ) + if (!OpsRequestManifest) { const result = await this.applyKubeBlockOpsRequestManifestForSpec( region, @@ -149,6 +151,10 @@ export class DedicatedDatabaseService { spec, 'horizontalScaling', ) + + // Call KubeBlock v5 compatibility API if needed + await this.handleKubeBlockV5Upgrade(appid, manifest, spec.replicas) + results.push(result) this.logger.log( `Applied horizontalScaling ops request for ${appid}: replicas=${spec.replicas}`, @@ -702,4 +708,116 @@ export class DedicatedDatabaseService { return false } } + + /** + * Handle KubeBlock v5 upgrade API call for horizontal scaling + * This is a compatibility feature for KubeBlock v5 mongodb-5.0 clusters + * + * @param appid - Application ID + * @param manifest - Current deployment manifest + * @param replicas - Number of replicas + */ + private async handleKubeBlockV5Upgrade( + appid: string, + manifest: KubernetesObject & { spec: any; status: any }, + replicas: number, + ): Promise { + try { + // Early return if not a v5 mongodb-5.0 cluster + if (!this.isKubeBlockV5MongoDb(manifest)) { + return + } + + const url = ServerConfig.KUBEBLOCK_V5_UPGRADE_URL + if (!url) { + this.logger.warn( + `KubeBlock v5 upgrade URL not configured (KUBEBLOCK_V5_UPGRADE_URL env var not set) for ${appid}`, + ) + return + } + + await this.callKubeBlockV5UpgradeAPI(appid, manifest, replicas, url) + } catch (error) { + this.logger.error( + `Failed to call KubeBlock v5 upgrade API for ${appid}: ${error.message}`, + ) + // Don't throw error, just log it as it's a compatibility feature + } + } + + /** + * Check if the manifest is a KubeBlock v5 mongodb-5.0 cluster + */ + private isKubeBlockV5MongoDb( + manifest: KubernetesObject & { spec: any; status: any }, + ): boolean { + return ( + manifest?.metadata?.labels?.['clusterversion.kubeblocks.io/name'] === + 'mongodb-5.0' + ) + } + + /** + * Call KubeBlock v5 upgrade API with timeout control + */ + private async callKubeBlockV5UpgradeAPI( + appid: string, + manifest: KubernetesObject & { spec: any; status: any }, + replicas: number, + url: string, + ): Promise { + const clusterName = manifest.metadata.name + const namespace = manifest.metadata.namespace + + // Create AbortController for timeout + const controller = new AbortController() + const timeoutId = setTimeout( + () => controller.abort(), + KUBEBLOCK_V5_UPGRADE_API_TIMEOUT, + ) + + try { + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + namespace, + database_name: clusterName, + replicas, + }), + signal: controller.signal, + }) + + const responseData = await this.parseKubeBlockV5Response(response) + + if (!response.ok) { + throw new Error( + `HTTP error! status: ${response.status}, statusText: ${ + response.statusText + }, body: ${JSON.stringify(responseData)}`, + ) + } + + this.logger.log( + `Called KubeBlock v5 upgrade API for ${appid}: cluster=${clusterName}, replicas=${replicas}, response: ${JSON.stringify( + responseData, + )}`, + ) + } finally { + clearTimeout(timeoutId) + } + } + + /** + * Parse response body from KubeBlock v5 API + */ + private async parseKubeBlockV5Response(response: Response): Promise { + const contentType = response.headers.get('content-type') + if (contentType?.includes('application/json')) { + return await response.json() + } + return await response.text() + } }