UNPKG

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