Skip to content

Commit 2d9661a

Browse files
committed
fix: support auto provide cidr for subnet
1 parent a474359 commit 2d9661a

File tree

2 files changed

+352
-10
lines changed

2 files changed

+352
-10
lines changed

src/utils/sdk.js

Lines changed: 99 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ const {
1111
initializeStaticinputCdn
1212
} = require('./index')
1313

14+
const { getAvailableCidr } = require('./vpc')
15+
1416
async function deployFaas({ instance, inputs, code, state = {} }) {
1517
const { __TmpCredentials, CONFIGS } = instance
1618
const region = inputs.region || CONFIGS.region
@@ -352,6 +354,90 @@ async function deployVpc({ instance, inputs, state = {} }) {
352354
return vpcOutput
353355
}
354356

357+
async function getDefaultVpcAndSubnet({ instance, inputs, region, zone, state }) {
358+
const { __TmpCredentials, CONFIGS } = instance
359+
360+
const inputVpc = inputs.vpc || {}
361+
const DEFAULT_CONFIGS = deepClone(CONFIGS.vpc)
362+
363+
const { capi } = new Vpc(__TmpCredentials, region)
364+
365+
// state 中有 vpcId,则查询详情
366+
// 如果详情存在则视为存在,如果不存在则尝试创建默认 VPC
367+
let existVpc = false
368+
let defaultVpc = null
369+
if (state.vpcId) {
370+
defaultVpc = await VpcUtils.getVpcDetail(capi, state.vpcId)
371+
if (defaultVpc) {
372+
existVpc = true
373+
}
374+
}
375+
if (!existVpc) {
376+
defaultVpc = await VpcUtils.getDefaultVpc(capi)
377+
if (!defaultVpc) {
378+
console.log(`Creating default vpc`)
379+
defaultVpc = await VpcUtils.createDefaultVpc(capi, zone)
380+
return {
381+
vpcId: defaultVpc.VpcId,
382+
vpcName: defaultVpc.VpcName,
383+
subnetId: defaultVpc.SubnetId,
384+
subnetName: defaultVpc.SubnetName
385+
}
386+
}
387+
}
388+
389+
// 如果默认 VPC 不支持 DHCP,直接返回
390+
const isDhcp = await VpcUtils.isDhcpEnable(capi, defaultVpc.VpcId)
391+
if (!isDhcp) {
392+
return null
393+
}
394+
395+
// state 中有 subnetId,则查询详情,如果详情存在则视为存在
396+
// 如果不存在则查询默认子网,如果不存在符合条件的默认子网再尝试创建子网
397+
let existSubnet = false
398+
let defaultSubnet = null
399+
if (state.subnetId) {
400+
defaultSubnet = await VpcUtils.getSubnetDetail(capi, state.subnetId)
401+
if (defaultSubnet) {
402+
existSubnet = true
403+
}
404+
}
405+
if (!existSubnet) {
406+
defaultSubnet = await VpcUtils.getDefaultSubnet(capi, defaultVpc.VpcId)
407+
408+
// 不存在支持 db 的子网,则自动创建
409+
if (!defaultSubnet || zone !== defaultSubnet.Zone) {
410+
// 获取当前 VPC 下子网列表
411+
const subnetList = await VpcUtils.getSubnetList(capi, defaultVpc.VpcId)
412+
const subnetCidrList = subnetList.map((item) => item.CidrBlock)
413+
const cidrBlock = getAvailableCidr(defaultVpc.CidrBlock, subnetCidrList)
414+
415+
const subnetName = inputVpc.subnetName || DEFAULT_CONFIGS.subnetName
416+
console.log(`Creating subnet ${subnetName}`)
417+
const { SubnetId } = await VpcUtils.createSubnet(capi, {
418+
VpcId: defaultVpc.VpcId,
419+
Zone: zone,
420+
SubnetName: subnetName,
421+
CidrBlock: cidrBlock
422+
})
423+
console.log(`Create subnet ${subnetName} (${SubnetId}) success`)
424+
return {
425+
vpcId: defaultVpc.VpcId,
426+
vpcName: defaultVpc.VpcName,
427+
subnetId: SubnetId,
428+
subnetName: subnetName
429+
}
430+
}
431+
}
432+
433+
return {
434+
vpcId: defaultVpc.VpcId,
435+
vpcName: defaultVpc.VpcName,
436+
subnetId: defaultSubnet.SubnetId,
437+
subnetName: defaultSubnet.SubnetName
438+
}
439+
}
440+
355441
async function deployByDefaultVpc({ instance, inputs, state = {} }) {
356442
const { __TmpCredentials, CONFIGS } = instance
357443
const region = inputs.region || CONFIGS.region
@@ -363,20 +449,23 @@ async function deployByDefaultVpc({ instance, inputs, state = {} }) {
363449
return item.Region === region
364450
})
365451

366-
// 尝试创建默认 VPC
367-
const vpc = new Vpc(__TmpCredentials, region)
368-
const defaultVpc = await VpcUtils.createDefaultVpc(vpc.capi, currentZone.Zone)
452+
const defaultVpc = await getDefaultVpcAndSubnet({
453+
instance,
454+
inputs,
455+
region,
456+
zone: currentZone.Zone,
457+
state
458+
})
369459

370-
const isDhcp = await VpcUtils.isDhcpEnable(vpc.capi, defaultVpc.VpcId)
371-
if (isDhcp) {
372-
console.log(`Use default VPC, vpcId: ${defaultVpc.VpcId}, subnetId: ${defaultVpc.SubnetId}`)
460+
if (defaultVpc) {
461+
console.log(`Use default VPC, vpcId: ${defaultVpc.vpcId}, subnetId: ${defaultVpc.subnetId}`)
373462
const vpcOutput = {
374463
region,
375464
zone: currentZone.Zone,
376-
vpcId: defaultVpc.VpcId,
377-
subnetId: defaultVpc.SubnetId,
378-
vpcName: defaultVpc.VpcName,
379-
subnetName: defaultVpc.SubnetName
465+
vpcId: defaultVpc.vpcId,
466+
vpcName: defaultVpc.vpcName,
467+
subnetId: defaultVpc.subnetId,
468+
subnetName: defaultVpc.subnetName
380469
}
381470
vpcOutput.isDefault = true
382471

src/utils/vpc.js

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
/**
2+
* 自然数级别的区间计算库;也可用于字符串区间(直接使用字符串对比),但此时不支持区间减法。
3+
* nature number ranges calculate library, also support string ranges without subtraction.
4+
*/
5+
// 点、区对比结果 point vs range compare result
6+
const PR_RESULT = {
7+
BEFORE: -1, // 1 vs [2,3]
8+
IN: 1, // 1 vs [1,2]
9+
AFTER: 2 // 3 vs [1,2]
10+
}
11+
// 区、区对比结果 range vs range compare result
12+
const RR_RESULT = {
13+
BEFORE: -3, // [1,2] vs [3,4]
14+
C_BEFORE: -2, // [1,3] vs [2,4]
15+
CONTAIN: -1, // [1,4] vs [2,3]
16+
IN: 1, // [2,3] vs [1,4]
17+
C_AFTER: 2, // [2,4] vs [1,3]
18+
AFTER: 3 // [3,4] vs [1,2]
19+
}
20+
// 通过点区对比结果快速查区区对比结果,即通过a与[c,d]、b与[c,d]的结果来计算[a,b]与[c,d]的对比结果
21+
// calculate ([a,b] vs [c,d]) result by results of (a vs [c,d]) and (b vs [c,d]),
22+
const RR_MAP = {}
23+
24+
// --- when a before [c,d]
25+
let o = (RR_MAP[PR_RESULT.BEFORE] = {})
26+
// when b before [c,d], [a,b] before [c,d]
27+
o[PR_RESULT.BEFORE] = RR_RESULT.BEFORE
28+
// when b in [c,d], [a,b] before and connect to [c,d]
29+
o[PR_RESULT.IN] = RR_RESULT.C_BEFORE
30+
// when b after [c,d], [a,b] contain [c,d]
31+
o[PR_RESULT.AFTER] = RR_RESULT.CONTAIN
32+
33+
// --- when a in [c,d]
34+
o = RR_MAP[PR_RESULT.IN] = {}
35+
o[PR_RESULT.IN] = RR_RESULT.IN
36+
o[PR_RESULT.AFTER] = RR_RESULT.C_AFTER
37+
38+
// --- when a after [c,d]
39+
o = RR_MAP[PR_RESULT.AFTER] = {}
40+
o[PR_RESULT.AFTER] = RR_RESULT.AFTER
41+
42+
// compare point and range, return PR_RESULT
43+
function comparePR(v, r) {
44+
return v < r[0] ? PR_RESULT.BEFORE : v <= r[1] ? PR_RESULT.IN : PR_RESULT.AFTER
45+
}
46+
// compare range and range, return RR_RESULT
47+
function compareRR(r1, r2) {
48+
return RR_MAP[comparePR(r1[0], r2)][comparePR(r1[1], r2)]
49+
}
50+
function isInt(n) {
51+
return Number(n) === n && n % 1 === 0
52+
}
53+
// 判断两个区间是否连接
54+
// if range1 and range2 are adjacent
55+
function isAdjacentRR(r1, r2) {
56+
const a = r1[1],
57+
b = r2[0]
58+
if (!isInt(a) || !isInt(b)) {
59+
return false
60+
}
61+
return a + 1 === b
62+
}
63+
// 区间相加
64+
// range addition
65+
function addRR(r1, r2) {
66+
switch (compareRR(r1, r2)) {
67+
case RR_RESULT.BEFORE:
68+
return isAdjacentRR(r1, r2) ? [[r1[0], r2[1]]] : [r1, r2]
69+
case RR_RESULT.IN:
70+
return [r2]
71+
case RR_RESULT.AFTER:
72+
return isAdjacentRR(r2, r1) ? [[r2[0], r1[1]]] : [r2, r1]
73+
case RR_RESULT.C_BEFORE:
74+
return [[r1[0], r2[1]]]
75+
case RR_RESULT.C_AFTER:
76+
return [[r2[0], r1[1]]]
77+
case RR_RESULT.CONTAIN:
78+
return [r1]
79+
}
80+
}
81+
82+
// 区间相减
83+
// range subtraction
84+
function subRR(r1, r2) {
85+
switch (compareRR(r1, r2)) {
86+
case RR_RESULT.BEFORE:
87+
return [r1]
88+
case RR_RESULT.IN:
89+
return []
90+
case RR_RESULT.AFTER:
91+
return [r1]
92+
case RR_RESULT.C_BEFORE:
93+
return [[r1[0], r2[0] - 1]]
94+
case RR_RESULT.C_AFTER:
95+
return [[r2[1] + 1, r1[1]]]
96+
case RR_RESULT.CONTAIN:
97+
return [
98+
[r1[0], r2[0] - 1],
99+
[r2[1] + 1, r1[1]]
100+
]
101+
}
102+
}
103+
104+
class Ranges {
105+
constructor(a, b) {
106+
this.ranges = a instanceof Array ? a : [[a, b]]
107+
this._connectRange()
108+
}
109+
110+
// 连接、组合相连的区间
111+
// [ [1,2], [3,4], [3,6], [8,9] ] -> [ [1,6], [8,9] ]
112+
_connectRange() {
113+
const { ranges } = this
114+
ranges.sort(compareRR)
115+
let results = [],
116+
r1 = ranges[0],
117+
i,
118+
r2,
119+
result
120+
for (i = 1; i < ranges.length; i++) {
121+
r2 = ranges[i]
122+
result = addRR(r1, r2)
123+
results = results.concat(result)
124+
r1 = results.pop()
125+
}
126+
results.push(r1)
127+
return (this.ranges = results)
128+
}
129+
130+
/**
131+
* 添加区间
132+
* @param {Array} r1 例如 [1,3]
133+
*/
134+
add(r1) {
135+
this.ranges.push(r1)
136+
this._connectRange()
137+
return this
138+
}
139+
140+
/**
141+
* 减去区间
142+
* @param {Array} r2 例如 [1,3]
143+
*/
144+
sub(r2) {
145+
if (!isInt(r2[0])) {
146+
throw new Error('Not support non integer range')
147+
}
148+
const { ranges } = this
149+
let results = [],
150+
i,
151+
result,
152+
r1
153+
for (i = 0; i < ranges.length; i++) {
154+
r1 = ranges[i]
155+
result = subRR(r1, r2)
156+
results = results.concat(result)
157+
}
158+
this.ranges = results
159+
return this
160+
}
161+
162+
/**
163+
* 判断区间是否有冲突
164+
* @param {Array} range 例如 [1,3]
165+
* @returns {boolean}
166+
*/
167+
isConflict(range) {
168+
const { ranges } = this
169+
for (let i = 0; i < ranges.length; i++) {
170+
switch (compareRR(ranges[i], range)) {
171+
case RR_RESULT.IN:
172+
case RR_RESULT.C_BEFORE:
173+
case RR_RESULT.C_AFTER:
174+
case RR_RESULT.CONTAIN:
175+
return true
176+
}
177+
}
178+
return false
179+
}
180+
181+
/**
182+
* 判断点是否在区间内
183+
* @param {Number|Array} point
184+
* @returns {boolean}
185+
*/
186+
isContain(point) {
187+
const { ranges } = this
188+
let range
189+
if (isInt(point)) {
190+
for (let i = 0; i < ranges.length; i++) {
191+
if (comparePR(point, ranges[i]) === PR_RESULT.IN) {
192+
return true
193+
}
194+
}
195+
} else {
196+
range = point
197+
for (let i = 0; i < ranges.length; i++) {
198+
if (compareRR(range, ranges[i]) === RR_RESULT.IN) {
199+
return true
200+
}
201+
}
202+
}
203+
204+
return false
205+
}
206+
}
207+
208+
function ipToUint(e) {
209+
return (((e = e.split('.'))[0] << 24) | (e[1] << 16) | (e[2] << 8) | e[3]) >>> 0
210+
}
211+
212+
function uintToIp(e) {
213+
return (e >>> 24) + '.' + ((e >>> 16) & 255) + '.' + ((e >>> 8) & 255) + '.' + (255 & e)
214+
}
215+
216+
function getIpZone(e, t) {
217+
const n = ipToUint(e)
218+
return [(n & ((Math.pow(2, t) - 1) << (32 - t))) >>> 0, (n | (Math.pow(2, 32 - t) - 1)) >>> 0]
219+
}
220+
221+
// 检查是否存在 CIDR 冲突
222+
function getAvailableCidr(
223+
vpcCIDR,
224+
existSubnetCIDRs // 已绑定的子网的 CIDR
225+
) {
226+
const [vpcNat, vpcMask] = vpcCIDR.split('/')
227+
const totalRang = new Ranges([getIpZone(vpcNat, vpcMask)]) // 可用 CIDR 范围
228+
existSubnetCIDRs.forEach((existCIDR) => {
229+
// 减去已经使用的 CIDR 范围
230+
const [subnetNat, subnetMask] = existCIDR.split('/')
231+
totalRang.sub(getIpZone(subnetNat, subnetMask))
232+
})
233+
234+
const [avalableZone] = totalRang.ranges
235+
236+
return `${uintToIp(avalableZone[0])}/29`
237+
}
238+
239+
module.exports = {
240+
getAvailableCidr
241+
}
242+
243+
// const vpcCidr = '172.17.0.0/16'
244+
// const existCidr = [
245+
// '172.17.3.0/24',
246+
// '172.17.2.0/29',
247+
// '172.17.1.0/24',
248+
// '172.17.0.0/24',
249+
// '172.17.2.8/29',
250+
// '172.17.2.16/29'
251+
// ]
252+
253+
// console.log(getAvailableCidr(vpcCidr, []))

0 commit comments

Comments
 (0)