UNPKG

10.1 kBJavaScriptView Raw
1'use strict';
2
3const getProfile = require('./profile').getProfile;
4const { getNasPopClient } = require('./client');
5const _ = require('lodash');
6const { isNasAutoConfig } = require('./definition');
7const debug = require('debug')('fun:nas');
8const { green } = require('colors');
9const { sleep } = require('./time');
10const { throwProcessedException } = require('./error-message');
11const fs = require('fs-extra');
12const definition = require('./definition');
13const path = require('path');
14const constants = require('./nas/constants');
15const tpl = require('./tpl');
16const requestOption = {
17 method: 'POST'
18};
19
20const NAS_DEFAULT_DESCRIPTION = 'default_nas_created_by_fc_fun';
21
22async function createMountTarget(nasClient, region, fileSystemId, vpcId, vswitchId) {
23 const params = {
24 'RegionId': region,
25 'NetworkType': 'Vpc',
26 'FileSystemId': fileSystemId,
27 'AccessGroupName': 'DEFAULT_VPC_GROUP_NAME',
28 'VpcId': vpcId,
29 'VSwitchId': vswitchId
30 };
31
32 const rs = await nasClient.request('CreateMountTarget', params, requestOption);
33
34 const mountTargetDomain = rs.MountTargetDomain;
35
36 debug('create mount target rs: %s', mountTargetDomain);
37
38 await waitMountPointUntilAvaliable(nasClient, region, fileSystemId, mountTargetDomain);
39
40 return mountTargetDomain;
41}
42
43async function waitMountPointUntilAvaliable(nasClient, region, fileSystemId, mountTargetDomain) {
44 let count = 0;
45 let status;
46
47 do {
48 count++;
49
50 var params = {
51 'RegionId': region,
52 'FileSystemId': fileSystemId,
53 'MountTargetDomain': mountTargetDomain
54 };
55
56 await sleep(800);
57
58 const rs = await nasClient.request('DescribeMountTargets', params, requestOption);
59 status = rs.MountTargets.MountTarget[0].Status;
60
61 debug('nas status is: ' + status);
62
63 console.log(`\t\tnas mount target domain already created, waiting for status to be 'Active', now is ${status}`);
64 } while (count < 15 && status !== 'Active');
65
66 if (status !== 'Active') { throw new Error(`Timeout while waiting for MountPoint ${mountTargetDomain} status to be 'Active'`); }
67}
68
69async function createDefaultNasIfNotExist(vpcId, vswitchId) {
70 const nasClient = await getNasPopClient();
71
72 const profile = await getProfile();
73 const region = profile.defaultRegion;
74
75 const fileSystemId = await createNasFileSystemIfNotExist(nasClient, region);
76
77 debug('fileSystemId: %s', fileSystemId);
78
79 return await createMountTargetIfNotExist(nasClient, region, fileSystemId, vpcId, vswitchId);
80}
81
82async function findMountTarget(nasClient, region, fileSystemId, vpcId, vswitchId) {
83 var params = {
84 'RegionId': region,
85 'FileSystemId': fileSystemId
86 };
87
88 const rs = await nasClient.request('DescribeMountTargets', params, requestOption);
89
90 const mountTargets = rs.MountTargets.MountTarget;
91
92 // todo: 检查 mountTargets 的 vswitch 是否与函数计算的一致?
93
94 if (!_.isEmpty(mountTargets)) {
95
96 const mountTarget = _.find(mountTargets, {
97 'VpcId': vpcId,
98 'VswId': vswitchId
99 });
100
101 if (mountTarget) {
102 return mountTarget.MountTargetDomain;
103 }
104 }
105
106 return null;
107}
108
109async function createMountTargetIfNotExist(nasClient, region, fileSystemId, vpcId, vswitchId) {
110 let mountTargetDomain = await findMountTarget(nasClient, region, fileSystemId, vpcId, vswitchId);
111
112 if (mountTargetDomain) {
113 console.log(green('\t\tnas file system mount target is already created, mountTargetDomain is: ' + mountTargetDomain));
114
115 return mountTargetDomain;
116 }
117
118 // create mountTarget if not exist
119
120 console.log('\t\tcould not find default nas file system mount target, ready to generate one');
121
122 mountTargetDomain = await createMountTarget(nasClient, region, fileSystemId, vpcId, vswitchId);
123
124 console.log(green('\t\tdefault nas file system mount target has been generated, mount domain is: ' + mountTargetDomain));
125
126 return mountTargetDomain;
127}
128
129async function createNasFileSystemIfNotExist(nasClient, region) {
130 let fileSystemId = await findNasFileSystem(nasClient, region, NAS_DEFAULT_DESCRIPTION);
131
132 if (!fileSystemId) {
133 console.log('\t\tcould not find default nas file system, ready to generate one');
134
135 fileSystemId = await createNasFileSystem(nasClient, region);
136
137 console.log(green('\t\tdefault nas file system has been generated, fileSystemId is: ' + fileSystemId));
138 } else {
139 console.log(green('\t\tnas file system already generated, fileSystemId is: ' + fileSystemId));
140 }
141
142 return fileSystemId;
143}
144
145async function findNasFileSystem(nasClient, region, description) {
146
147 const pageSize = 50;
148 let requestPageNumber = 0;
149 let totalCount;
150 let pageNumber;
151
152 let fileSystem;
153
154 do {
155 const params = {
156 'RegionId': region,
157 'PageSize': pageSize,
158 'PageNumber': ++requestPageNumber
159 };
160
161 var rs;
162 try {
163 rs = await nasClient.request('DescribeFileSystems', params, requestOption);
164 } catch (ex) {
165 throwProcessedException(ex, 'AliyunNASFullAccess');
166 }
167
168 totalCount = rs.TotalCount;
169 pageNumber = rs.PageNumber;
170
171 const fileSystems = rs.FileSystems.FileSystem;
172
173 fileSystem = _.find(fileSystems, { Description: description });
174
175 debug('find filesystem: ' + JSON.stringify(fileSystem));
176
177 } while (!fileSystem && totalCount && pageNumber && pageNumber * pageSize < totalCount);
178
179 return (fileSystem || {}).FileSystemId;
180}
181
182async function createNasFileSystem(nasClient, region) {
183 const params = {
184 'RegionId': region,
185 'ProtocolType': 'NFS',
186 'StorageType': 'Performance',
187 'Description': NAS_DEFAULT_DESCRIPTION
188 };
189
190 const rs = await nasClient.request('CreateFileSystem', params, requestOption);
191
192 return rs.FileSystemId;
193}
194
195async function generateAutoNasConfig(serviceName, vpcId, vswitchId) {
196 const mountPointDomain = await createDefaultNasIfNotExist(vpcId, vswitchId);
197
198 //fun nas 创建的服务名比其对应的服务多了 '_FUN_NAS_' 前缀
199 //对于 nas 的挂载目录,要去掉这个前缀,保证 fun nas 的服务与对应的服务使用的是同样的挂载目录
200 if (serviceName.startsWith(constants.FUN_NAS_SERVICE_PREFIX)) {
201 serviceName = serviceName.substring(constants.FUN_NAS_SERVICE_PREFIX.length);
202 }
203 return {
204 UserId: 10003,
205 GroupId: 10003,
206 MountPoints: [
207 {
208 ServerAddr: `${mountPointDomain}:/${serviceName}`,
209 MountDir: '/mnt/auto'
210 }
211 ]
212 };
213}
214
215const serverAddrReGe = /^[a-z0-9-.]*.nas.[a-z]+.com:\//;
216
217function resolveMountPoint(mountPoint) {
218 // '012194b28f-ujc20.cn-hangzhou.nas.aliyuncs.com:/'
219 const serverAddr = mountPoint.ServerAddr;
220 const mountDir = mountPoint.MountDir;
221
222 // valid serverAddr
223 if (!serverAddrReGe.test(serverAddr)) {
224 throw new Error(`NasConfig's nas server address '${serverAddr}' doesn't match expected format (allowed: '^[a-z0-9-.]*.nas.[a-z]+.com:/')`);
225 }
226
227 const suffix = '.com:';
228 const index = serverAddr.lastIndexOf(suffix);
229
230 // /
231 let mountSource = serverAddr.substr(index + suffix.length);
232 // 012194b28f-ujc20.cn-hangzhou.nas.aliyuncs.com
233 let serverPath = serverAddr.substr(0, serverAddr.length - mountSource.length - 1);
234
235 return {
236 serverPath,
237 mountSource,
238 mountDir,
239 serverAddr
240 };
241}
242
243async function convertMountPointToNasMapping(nasBaseDir, mountPoint) {
244 const { mountSource, mountDir, serverPath } = resolveMountPoint(mountPoint);
245
246 const nasDir = path.join(nasBaseDir, serverPath);
247
248 if (!(await fs.pathExists(nasDir))) {
249 await fs.ensureDir(nasDir);
250 }
251
252 const localNasDir = path.join(nasDir, mountSource);
253
254 // The mounted nas directory must exist.
255 if (!(await fs.pathExists(localNasDir))) {
256 await fs.ensureDir(localNasDir);
257 }
258
259 return {
260 localNasDir,
261 remoteNasDir: mountDir
262 };
263}
264
265async function convertMountPointsToNasMappings(nasBaseDir, mountPoints) {
266 if (!mountPoints) { return []; }
267
268 const nasMappings = [];
269
270 for (let mountPoint of mountPoints) {
271 const nasMapping = await convertMountPointToNasMapping(nasBaseDir, mountPoint);
272
273 nasMappings.push(nasMapping);
274 }
275
276 return nasMappings;
277}
278
279async function convertNasConfigToNasMappings(nasBaseDir, nasConfig, serviceName) {
280 if (!nasConfig) { return []; }
281
282 const isNasAuto = definition.isNasAutoConfig(nasConfig);
283
284 if (isNasAuto) { // support 'NasConfig: Auto'
285 const nasDir = path.join(nasBaseDir, 'auto-default');
286
287 const localNasDir = path.join(nasDir, serviceName);
288
289 if (!(await fs.pathExists(localNasDir))) {
290 await fs.ensureDir(localNasDir);
291 }
292
293 return [{
294 localNasDir,
295 remoteNasDir: '/mnt/auto'
296 }];
297 }
298 const mountPoints = nasConfig.MountPoints;
299
300 return await convertMountPointsToNasMappings(nasBaseDir, mountPoints);
301
302}
303
304async function convertTplToServiceNasMappings(nasBaseDir, tpl) {
305 const serviceNasMappings = {};
306
307 for (const { serviceName, serviceRes } of definition.findServices(tpl.Resources)) {
308 const nasConfig = (serviceRes.Properties || {}).NasConfig;
309
310 const nasMappings = await convertNasConfigToNasMappings(nasBaseDir, nasConfig, serviceName);
311
312 serviceNasMappings[serviceName] = nasMappings;
313 }
314
315 return serviceNasMappings;
316}
317
318function convertTplToServiceNasIdMappings(tpl) {
319 const serviceNasIdMappings = {};
320
321 for (const { serviceName, serviceRes } of definition.findServices(tpl.Resources)) {
322 const nasConfig = (serviceRes.Properties || {}).NasConfig;
323
324 const nasId = getNasIdFromNasConfig(nasConfig);
325
326 serviceNasIdMappings[serviceName] = nasId;
327 }
328
329 return serviceNasIdMappings;
330}
331
332function getNasIdFromNasConfig(nasConfig) {
333 if (isNasAutoConfig(nasConfig)) {
334 return {
335 UserId: 10003,
336 GroupId: 10003
337 };
338 }
339 return {
340 UserId: nasConfig.UserId,
341 GroupId: nasConfig.GroupId
342 };
343}
344
345function getDefaultNasDir(baseDir) {
346 return path.join(baseDir, tpl.DEFAULT_NAS_PATH_SUFFIX);
347}
348
349module.exports = {
350 findNasFileSystem,
351 findMountTarget,
352 createMountTarget,
353 generateAutoNasConfig,
354 resolveMountPoint,
355 convertMountPointToNasMapping,
356 convertNasConfigToNasMappings,
357 convertTplToServiceNasMappings,
358 convertTplToServiceNasIdMappings,
359 getNasIdFromNasConfig,
360 getDefaultNasDir
361};
\No newline at end of file