feat(server): recover the application and synchronize the recovery functions to sys_db (#1879)

* feat(server): recover the application and synchronize the recovery functions to sys_db

---------

Co-authored-by: HUAHUAI23 <lim@outlook.com>
This commit is contained in:
limbo 2024-03-02 16:12:00 +08:00 committed by GitHub
parent 1c688b8afc
commit 46a0cd440f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -5,7 +5,12 @@ import { GenerateAlphaNumericPassword } from 'src/utils/random'
import { MongoService } from './mongo.service'
import * as mongodb_uri from 'mongodb-uri'
import { RegionService } from 'src/region/region.service'
import { TASK_LOCK_INIT_TIME } from 'src/constants'
import {
CN_PUBLISHED_CONF,
CN_PUBLISHED_FUNCTIONS,
CN_PUBLISHED_WEBSITE_HOSTING,
TASK_LOCK_INIT_TIME,
} from 'src/constants'
import { Region } from 'src/region/entities/region'
import { SystemDatabase } from '../system-database'
import {
@ -18,6 +23,9 @@ import { exec } from 'node:child_process'
import { promisify } from 'node:util'
import { DatabaseSyncRecord } from './entities/database-sync-record'
import { MongoClient, ObjectId } from 'mongodb'
import { DedicatedDatabaseService } from './dedicated-database/dedicated-database.service'
import { CloudFunction } from 'src/function/entities/cloud-function'
import { ApplicationConfiguration } from 'src/application/entities/application-configuration'
const p_exec = promisify(exec)
@ -29,6 +37,7 @@ export class DatabaseService {
constructor(
private readonly mongoService: MongoService,
private readonly regionService: RegionService,
private readonly dedicatedDatabaseService: DedicatedDatabaseService,
) {}
async create(appid: string) {
@ -226,11 +235,31 @@ export class DatabaseService {
async exportDatabase(appid: string, filePath: string, uid: ObjectId) {
const region = await this.regionService.findByAppId(appid)
const database = await this.findOne(appid)
assert(database, 'Database not found')
const sharedDatabase = await this.findOne(appid)
const dedicatedDatabase = await this.dedicatedDatabaseService.findOne(appid)
const connectionUri = this.getControlConnectionUri(region, database)
assert(connectionUri, 'Database connection uri not found')
if (sharedDatabase && dedicatedDatabase) {
throw new Error(
`Database ${appid} found in both shared and dedicated databases.`,
)
}
if (!sharedDatabase && !dedicatedDatabase) {
throw new Error(
`Database ${appid} not found in both shared and dedicated databases.`,
)
}
let connectionUri
if (sharedDatabase) {
connectionUri = this.getControlConnectionUri(region, sharedDatabase)
} else {
connectionUri = await this.dedicatedDatabaseService.getConnectionUri(
region,
dedicatedDatabase,
)
}
assert(connectionUri, `Database ${appid} connection uri not found`)
try {
await p_exec(
@ -252,16 +281,39 @@ export class DatabaseService {
uid: ObjectId,
): Promise<void> {
const region = await this.regionService.findByAppId(appid)
const database = await this.findOne(appid)
assert(database, 'Database not found')
const connectionUri = this.getControlConnectionUri(region, database)
assert(connectionUri, 'Database connection uri not found')
const sharedDatabase = await this.findOne(appid)
const dedicatedDatabase = await this.dedicatedDatabaseService.findOne(appid)
if (sharedDatabase && dedicatedDatabase) {
throw new Error(
`Database ${appid} found in both shared and dedicated databases.`,
)
}
if (!sharedDatabase && !dedicatedDatabase) {
throw new Error(
`Database ${appid} not found in both shared and dedicated databases.`,
)
}
let connectionUri
if (sharedDatabase) {
connectionUri = this.getControlConnectionUri(region, sharedDatabase)
} else {
connectionUri = await this.dedicatedDatabaseService.getConnectionUri(
region,
dedicatedDatabase,
)
}
assert(connectionUri, `Database ${appid} connection uri not found`)
try {
await p_exec(
`mongorestore --uri='${connectionUri}' --gzip --archive='${filePath}' --nsFrom="${dbName}.*" --nsTo="${appid}.*" -v --nsInclude="${dbName}.*"`,
)
await this.recoverFunctionsToSystemDatabase(appid, uid)
await this.db
.collection<DatabaseSyncRecord>('DatabaseSyncRecord')
.insertOne({ uid, createdAt: new Date() })
@ -271,4 +323,54 @@ export class DatabaseService {
throw error
}
}
async recoverFunctionsToSystemDatabase(appid: string, uid: ObjectId) {
const { db, client } =
(await this.dedicatedDatabaseService.findAndConnect(appid)) ||
(await this.findAndConnect(appid))
try {
const appFunctionCollection = db.collection(CN_PUBLISHED_FUNCTIONS)
const appConfCollection = db.collection(CN_PUBLISHED_CONF)
const appWebsiteCollection = db.collection(CN_PUBLISHED_WEBSITE_HOSTING)
const functionsExist = await this.db
.collection<CloudFunction>('CloudFunction')
.countDocuments({ appid })
if (functionsExist) {
this.logger.debug(`${appid} Functions already exist in system database`)
return
}
const funcs: CloudFunction[] = await appFunctionCollection
.find<CloudFunction>({})
.toArray()
if (funcs.length === 0) {
this.logger.debug(` ${appid} No functions for recover.`)
return
}
funcs.forEach((func) => {
delete func._id
func.appid = appid
func.createdBy = uid
})
await this.db.collection<CloudFunction>('CloudFunction').insertMany(funcs)
// sync conf
const conf = await this.db
.collection<ApplicationConfiguration>('ApplicationConfiguration')
.findOne({ appid })
await appConfCollection.deleteMany({})
await appConfCollection.insertOne(conf)
await appWebsiteCollection.deleteMany({})
} finally {
await client.close()
}
}
}