Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(server): hot load environments for runtime instead of restarting runtime #1077

Merged
merged 1 commit into from
Apr 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,817 changes: 798 additions & 1,019 deletions docs/diagrams/laf-arch-full.excalidraw

Large diffs are not rendered by default.

2,481 changes: 694 additions & 1,787 deletions docs/diagrams/laf-arch-simple.excalidraw

Large diffs are not rendered by default.

Binary file removed docs/diagrams/laf-arch-simple.png
Binary file not shown.
1 change: 0 additions & 1 deletion e2e/f1-payload.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"name": "f1",
"description": "laf e2e function(hello-laf)",
"websocket": false,
"methods": [
"GET",
"POST"
Expand Down
1 change: 0 additions & 1 deletion e2e/f2-origin-payload.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"name": "f2",
"description": "laf e2e function(db example)",
"websocket": false,
"methods": [
"GET",
"POST"
Expand Down
2 changes: 1 addition & 1 deletion runtimes/nodejs/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
export const CLOUD_FUNCTION_COLLECTION = '__functions__'
export const POLICY_COLLECTION = '__policies__'
export const FUNCTION_LOG_COLLECTION = '__function_logs__'
export const CONFIG_COLLECTION = '__config__'
export const CONFIG_COLLECTION = '__conf__'

export const WEBSOCKET_FUNCTION_NAME = '__websocket__'
export const INTERCEPTOR_FUNCTION_NAME = '__interceptor__'
Expand Down
2 changes: 0 additions & 2 deletions runtimes/nodejs/src/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { MongoAccessor } from 'database-proxy'
import Config from './config'
import { createLogger, logger } from './support/logger'
import * as mongodb_uri from 'mongodb-uri'
import { FunctionCache } from './support/function-engine/cache'

/**
* Database Management
Expand Down Expand Up @@ -44,7 +43,6 @@ export class DatabaseAgent {
.init()
.then(async () => {
logger.info('db connected')
FunctionCache.initialize()
})
.catch((error) => {
logger.error(error)
Expand Down
7 changes: 7 additions & 0 deletions runtimes/nodejs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,16 @@ import xmlparser from 'express-xml-bodyparser'
// init static method of class
import './support/function-log'
import './support/cloud-sdk'
import { FunctionCache } from './support/function-engine/cache'
import { DatabaseChangeStream } from './support/db-change-stream'

const app = express()

DatabaseAgent.accessor.ready.then(() => {
FunctionCache.initialize()
DatabaseChangeStream.initialize()
})

if (process.env.NODE_ENV === 'development') {
app.use(cors())
}
Expand Down
6 changes: 5 additions & 1 deletion runtimes/nodejs/src/support/cloud-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ function createCloudSdk() {
},
sockets: WebSocketAgent.clients,
appid: Config.APP_ID,
env: process.env,
get env() {
return {
...process.env,
}
},
}

/**
Expand Down
41 changes: 41 additions & 0 deletions runtimes/nodejs/src/support/db-change-stream.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { CONFIG_COLLECTION } from '../constants'
import { DatabaseAgent } from '../db'

import { logger } from './logger'

export class DatabaseChangeStream {
static async initialize() {
this.watchConf()
}

/**
* stream the change of cloud function
* @param
* @returns
*/
static async watchConf() {
logger.info('Listening for changes in conf collection...')
this.updateEnvironments()

const stream = DatabaseAgent.db.collection(CONFIG_COLLECTION).watch()

stream.on('change', async (_change) => {
this.updateEnvironments()
})
}

private static async updateEnvironments() {
const conf = await DatabaseAgent.db
.collection(CONFIG_COLLECTION)
.findOne({})

if (!conf) {
return
}

const environments = conf.environments || []
for (const env of environments) {
process.env[env.name] = env.value
}
}
}
4 changes: 3 additions & 1 deletion runtimes/nodejs/src/support/function-engine/vm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ export class FunctionVm {
clearInterval: clearInterval,
setTimeout: setTimeout,
clearTimeout: clearTimeout,
process: { env: process.env },
process: {
env: { ...process.env },
},
URL: URL,
fetch: globalThis.fetch,
global: null,
Expand Down
34 changes: 23 additions & 11 deletions server/src/application/configuration.service.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,37 @@
import { Injectable, Logger } from '@nestjs/common'
import { ApplicationConfiguration } from '@prisma/client'
import { CN_PUBLISHED_CONF } from 'src/constants'
import { DatabaseService } from 'src/database/database.service'
import { PrismaService } from 'src/prisma/prisma.service'

@Injectable()
export class ApplicationConfigurationService {
private readonly logger = new Logger(ApplicationConfigurationService.name)

constructor(private readonly prisma: PrismaService) {}
constructor(
private readonly prisma: PrismaService,
private readonly databaseService: DatabaseService,
) {}

async count(appid: string) {
return this.prisma.applicationConfiguration.count({
where: {
appid,
},
})
return this.prisma.applicationConfiguration.count({ where: { appid } })
}

async remove(appid: string) {
return this.prisma.applicationConfiguration.delete({
where: {
appid,
},
})
return this.prisma.applicationConfiguration.delete({ where: { appid } })
}

async publish(conf: ApplicationConfiguration) {
const { db, client } = await this.databaseService.findAndConnect(conf.appid)
const session = client.startSession()
try {
await session.withTransaction(async () => {
const coll = db.collection(CN_PUBLISHED_CONF)
await coll.deleteOne({ appid: conf.appid }, { session })
await coll.insertOne(conf, { session })
})
} finally {
await client.close()
}
}
}
9 changes: 8 additions & 1 deletion server/src/application/environment.service.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { Injectable, Logger } from '@nestjs/common'
import { PrismaService } from 'src/prisma/prisma.service'
import { CreateEnvironmentDto } from './dto/create-env.dto'
import { ApplicationConfigurationService } from './configuration.service'

@Injectable()
export class EnvironmentVariableService {
private readonly logger = new Logger(EnvironmentVariableService.name)

constructor(private readonly prisma: PrismaService) {}
constructor(
private readonly prisma: PrismaService,
private readonly confService: ApplicationConfigurationService,
) {}

async updateAll(appid: string, dto: CreateEnvironmentDto[]) {
const res = await this.prisma.applicationConfiguration.update({
where: { appid },
data: { environments: { set: dto } },
})

await this.confService.publish(res)
return res.environments
}

Expand All @@ -37,6 +42,7 @@ export class EnvironmentVariableService {
data: { environments: { set: origin } },
})

await this.confService.publish(res)
return res.environments
}

Expand All @@ -54,6 +60,7 @@ export class EnvironmentVariableService {
data: { environments: { deleteMany: { where: { name } } } },
})

await this.confService.publish(res)
return res
}
}
1 change: 1 addition & 0 deletions server/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export const HTTP_METHODS = ['HEAD', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH']
export const CN_PUBLISHED_FUNCTIONS = '__functions__'
export const CN_PUBLISHED_POLICIES = '__policies__'
export const CN_FUNCTION_LOGS = '__function_logs__'
export const CN_PUBLISHED_CONF = '__conf__'

export const X_LAF_TRIGGER_TOKEN_KEY = 'x-laf-trigger-token'
export const X_LAF_DEVELOP_TOKEN_KEY = 'x-laf-develop-token'
Expand Down