UNPKG

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