Skip to content

Commit cd374bb

Browse files
committed
feat: add scf concurrency config, fix alias
1 parent 60d4d45 commit cd374bb

File tree

7 files changed

+269
-8
lines changed

7 files changed

+269
-8
lines changed

__tests__/scf/smooth-update.test.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { Scf } from '../../src';
2+
import { ScfDeployInputs } from '../../src/modules/scf/interface';
3+
4+
describe('Scf Smooth Update', () => {
5+
const credentials = {
6+
SecretId: process.env.TENCENT_SECRET_ID,
7+
SecretKey: process.env.TENCENT_SECRET_KEY,
8+
};
9+
const scf = new Scf(credentials);
10+
const inputs: ScfDeployInputs = {
11+
name: `serverless-test-concurrency-${Date.now()}`,
12+
code: {
13+
bucket: process.env.BUCKET,
14+
object: 'express_code.zip',
15+
},
16+
runtime: 'Nodejs12.16',
17+
region: 'ap-guangzhou',
18+
namespace: 'test',
19+
type: 'web',
20+
};
21+
22+
test('Deploy Function', async () => {
23+
const res = await scf.deploy(inputs);
24+
console.log(res);
25+
});
26+
27+
test('Reserve funciton concurrency', async () => {
28+
await scf.concurrency.setReserved({
29+
functionName: inputs.name,
30+
namespace: inputs.namespace,
31+
reservedMem: 1024,
32+
});
33+
34+
const getRes = await scf.concurrency.getReserved({
35+
functionName: inputs.name,
36+
namespace: inputs.namespace,
37+
});
38+
expect(getRes.reservedMem).toEqual(1024);
39+
});
40+
41+
test('Update funciton version to 1', async () => {
42+
const res = await scf.version.publish({
43+
functionName: inputs.name,
44+
namespace: inputs.namespace,
45+
});
46+
console.log(res);
47+
});
48+
49+
test('Provision function concurrency', async () => {
50+
await scf.scf.wait({
51+
functionName: inputs.name,
52+
namespace: inputs.namespace,
53+
qualifier: '1',
54+
});
55+
56+
await scf.concurrency.waitProvisioned({
57+
functionName: inputs.name,
58+
namespace: inputs.namespace,
59+
});
60+
61+
const res = await scf.concurrency.setProvisioned({
62+
functionName: inputs.name,
63+
namespace: inputs.namespace,
64+
provisionedNum: 10,
65+
qualifier: '1',
66+
});
67+
68+
const getRes = await scf.concurrency.getProvisioned({
69+
functionName: inputs.name,
70+
namespace: inputs.namespace,
71+
});
72+
73+
expect(getRes.allocated[0].allocatedNum).toEqual(10);
74+
expect(getRes.allocated[0].qualifier).toEqual('1');
75+
76+
console.log(res);
77+
});
78+
79+
test('Update funciton version to 2', async () => {
80+
const res = await scf.version.publish({
81+
functionName: inputs.name,
82+
namespace: inputs.namespace,
83+
});
84+
console.log(res);
85+
});
86+
87+
test('Update Provision function concurrency', async () => {
88+
await scf.scf.wait({
89+
functionName: inputs.name,
90+
namespace: inputs.namespace,
91+
qualifier: '2',
92+
});
93+
94+
await scf.concurrency.waitProvisioned({
95+
functionName: inputs.name,
96+
namespace: inputs.namespace,
97+
});
98+
99+
const res = await scf.concurrency.setProvisioned({
100+
functionName: inputs.name,
101+
namespace: inputs.namespace,
102+
provisionedNum: 10,
103+
qualifier: '2',
104+
lastQualifier: '1',
105+
});
106+
107+
const getRes = await scf.concurrency.getProvisioned({
108+
functionName: inputs.name,
109+
namespace: inputs.namespace,
110+
});
111+
112+
expect(getRes.allocated[0].allocatedNum).toEqual(10);
113+
expect(getRes.allocated[0].qualifier).toEqual('2');
114+
115+
console.log(res);
116+
});
117+
118+
test('Remove function', async () => {
119+
await scf.remove({
120+
functionName: inputs.name,
121+
namespace: inputs.namespace,
122+
});
123+
});
124+
});

src/modules/scf/apis.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ const ACTIONS = [
2020
'Invoke',
2121
'ListTriggers',
2222
'GetDemoAddress',
23+
'PutReservedConcurrencyConfig',
24+
'PutProvisionedConcurrencyConfig',
25+
'DeleteProvisionedConcurrencyConfig',
26+
'GetReservedConcurrencyConfig',
27+
'GetProvisionedConcurrencyConfig',
2328
] as const;
2429

2530
export type ActionType = typeof ACTIONS[number];
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import BaseEntity from './base';
2+
3+
// 文档:https://cloud.tencent.com/document/product/583/51247
4+
interface ScfSetReservedInputs {
5+
functionName: string;
6+
namespace?: string;
7+
8+
reservedMem: number;
9+
}
10+
11+
// 文档:https://cloud.tencent.com/document/product/583/51246
12+
interface ScfSetProvisionedInputs {
13+
functionName: string;
14+
namespace?: string;
15+
16+
// 上次部署,这次要删除的版本
17+
lastQualifier?: string;
18+
19+
qualifier: string;
20+
provisionedNum: number;
21+
}
22+
23+
// https://cloud.tencent.com/document/product/583/51247
24+
interface ScfGetReservedInputs {
25+
functionName: string;
26+
namespace: string;
27+
}
28+
29+
interface ScfGetProvisionedInputs {
30+
functionName: string;
31+
namespace: string;
32+
}
33+
34+
export class ConcurrencyEntity extends BaseEntity {
35+
// 设置保留配额
36+
async setReserved(inputs: ScfSetReservedInputs) {
37+
return await this.request({
38+
Action: 'PutReservedConcurrencyConfig',
39+
FunctionName: inputs.functionName,
40+
ReservedConcurrencyMem: inputs.reservedMem,
41+
Namespace: inputs.namespace,
42+
});
43+
}
44+
45+
async getReserved(inputs: ScfGetReservedInputs) {
46+
const res = await this.request({
47+
Action: 'GetReservedConcurrencyConfig',
48+
FunctionName: inputs.functionName,
49+
Namespace: inputs.namespace,
50+
});
51+
return {
52+
reservedMem: res.ReservedMem,
53+
};
54+
}
55+
56+
// 设置预置并发
57+
async setProvisioned(inputs: ScfSetProvisionedInputs) {
58+
// 删除上个版本的预置
59+
if (inputs.lastQualifier) {
60+
await this.request({
61+
Action: 'DeleteProvisionedConcurrencyConfig',
62+
FunctionName: inputs.functionName,
63+
Namespace: inputs.namespace,
64+
65+
Qualifier: inputs.lastQualifier,
66+
});
67+
68+
await new Promise((res) => setTimeout(res, 2000));
69+
}
70+
71+
return await this.request({
72+
Action: 'PutProvisionedConcurrencyConfig',
73+
FunctionName: inputs.functionName,
74+
Namespace: inputs.namespace,
75+
76+
Qualifier: inputs.qualifier,
77+
VersionProvisionedConcurrencyNum: inputs.provisionedNum,
78+
});
79+
}
80+
81+
async getProvisioned(inputs: ScfGetProvisionedInputs) {
82+
const res = await this.request({
83+
Action: 'GetProvisionedConcurrencyConfig',
84+
FunctionName: inputs.functionName,
85+
Namespace: inputs.namespace,
86+
});
87+
88+
const ret: {
89+
unallocatedNum: number;
90+
allocated: {
91+
allocatedNum: number;
92+
availableNum: number;
93+
status: string;
94+
statusReason: string;
95+
qualifier: string;
96+
}[];
97+
} = {
98+
unallocatedNum: res.UnallocatedConcurrencyNum as number,
99+
allocated: res.Allocated.map((v: any) => {
100+
return {
101+
allocatedNum: v.AllocatedProvisionedConcurrencyNum,
102+
availableNum: v.AvailableProvisionedConcurrencyNum,
103+
status: v.Status,
104+
statusReason: v.StatusReason,
105+
qualifier: v.Qualifier,
106+
};
107+
}),
108+
};
109+
return ret;
110+
}
111+
112+
async waitProvisioned(inputs: ScfGetProvisionedInputs) {
113+
while (true) {
114+
const outputs = await this.getProvisioned(inputs);
115+
let finish = true;
116+
for (const a of outputs.allocated) {
117+
if (a.allocatedNum !== a.availableNum) {
118+
finish = false;
119+
}
120+
}
121+
122+
await new Promise((res) => setTimeout(res, 1000));
123+
124+
if (finish) {
125+
break;
126+
}
127+
}
128+
}
129+
}

src/modules/scf/entities/scf.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Capi } from '@tencent-sdk/capi';
2-
import { waitResponse } from '@ygkit/request';
2+
import { sleep, waitResponse } from '@ygkit/request';
33
import dayjs from 'dayjs';
44
import { ApiTypeError, ApiError } from '../../../utils/error';
55
import { formatDate } from '../../../utils/dayjs';
@@ -83,6 +83,7 @@ export default class ScfEntity extends BaseEntity {
8383
}
8484
}
8585

86+
wait = this.checkStatus;
8687
// 由于函数的创建/更新是个异步过程,所以需要轮训函数状态
8788
// 每个 200ms(GetFunction 接口平均耗时) 轮训一次,轮训 1200 次,也就是 2 分钟
8889
async checkStatus({
@@ -114,9 +115,7 @@ export default class ScfEntity extends BaseEntity {
114115
if (CONFIGS.failStatus.indexOf(Status) !== -1) {
115116
break;
116117
}
117-
// GetFunction 接口耗时一般需要200ms左右,QPS 大概为 5,小于云 API 20 的限制
118-
// 所以不需要sleep
119-
// await sleep(500);
118+
await sleep(500);
120119
times = times - 1;
121120
}
122121
const { StatusReasons } = detail;

src/modules/scf/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
import ScfEntity from './entities/scf';
2525
import AliasEntity from './entities/alias';
2626
import VersionEntity from './entities/version';
27+
import { ConcurrencyEntity } from './entities/concurrency';
2728

2829
/** 云函数组件 */
2930
export default class Scf {
@@ -36,6 +37,7 @@ export default class Scf {
3637
scf: ScfEntity;
3738
alias: AliasEntity;
3839
version: VersionEntity;
40+
concurrency: ConcurrencyEntity;
3941

4042
constructor(credentials = {}, region: RegionType = 'ap-guangzhou') {
4143
this.region = region;
@@ -54,6 +56,7 @@ export default class Scf {
5456

5557
this.scf = new ScfEntity(this.capi, region);
5658
this.alias = new AliasEntity(this.capi);
59+
this.concurrency = new ConcurrencyEntity(this.capi);
5760
this.version = new VersionEntity(this.capi);
5861
}
5962

@@ -322,8 +325,9 @@ export default class Scf {
322325
namespace,
323326
functionName,
324327
region: this.region,
325-
traffic: strip(1 - inputs.traffic!),
326-
lastVersion: inputs.lastVersion!,
328+
additionalVersions: needSetTraffic
329+
? [{ weight: strip(1 - inputs.traffic!), version: inputs.lastVersion! }]
330+
: [],
327331
aliasName: inputs.aliasName,
328332
description: inputs.aliasDescription,
329333
});

src/modules/scf/interface.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export interface ScfGetAliasInputs {
116116

117117
export interface ScfUpdateAliasInputs extends ScfGetAliasInputs {
118118
description?: string;
119-
additionalVersions?: [{ version: string; weight: number }];
119+
additionalVersions?: { version: string; weight: number }[];
120120
}
121121

122122
export type ScfDeleteAliasInputs = ScfGetAliasInputs;

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"compilerOptions": {
3-
"target": "es6",
3+
"target": "es2017",
44
"module": "commonjs",
55
"strict": true,
66
"moduleResolution": "node",

0 commit comments

Comments
 (0)