Repository: aliyun/fun
Pull request #264 (2019-07-04 16:26:35)

add nas auto config support
1. add support for nas auto and vpc auto
2. add tips for enable vpc feature when using nas auto and vpc auto
3. checking if the directory exists, if not, it will be created automatically
4. fix fun local uid and gid not match with prod bug
5. refactor related code


Patch:
diff --git a/.gitignore b/.gitignore
index 55f9be1..0d99672 100644
--- a/.gitignore
+++ b/.gitignore
@@ -92,3 +92,7 @@ output
 *.class
 .DS_Store
 .fun
+.classpath
+.project
+event.dat
+__pycache__/
\ No newline at end of file
diff --git a/examples/nas_http/template.yml b/examples/nas_http/template.yml
index 3e1ad5f..51bb25e 100644
--- a/examples/nas_http/template.yml
+++ b/examples/nas_http/template.yml
@@ -11,7 +11,7 @@ Resources:
       VpcConfig:
         VpcId: 'vpc-bp12hm92gdpcjtai7ua82'
         VSwitchIds: [ 'vsw-bp1gitru7oicyyb4uiylj' ]
-        SecurityGroupId: 'sg-bp1243pi65bw4cjj4bks'
+        SecurityGroupId: 'sg-bp1hxp0cmozjepfiy9iu'
       NasConfig: 
         UserId: -1
         GroupId: -1
@@ -34,7 +34,7 @@ Resources:
           Properties:
               AuthType: ANONYMOUS
               Methods: ['GET', 'POST', 'PUT']
-
+              
     writeNas:
       Type: 'Aliyun::Serverless::Function'
       Properties:
diff --git a/lib/client.js b/lib/client.js
new file mode 100644
index 0000000..1e8dfe2
--- /dev/null
+++ b/lib/client.js
@@ -0,0 +1,135 @@
+'use strict';
+
+const osLocale = require('os-locale');
+const MNSClient = require('@alicloud/mns');
+const hashedMachineId = require('node-machine-id').machineId;
+const pkg = require('../package.json');
+const CloudAPI = require('@alicloud/cloudapi');
+const TableStore = require('tablestore');
+const Log = require('@alicloud/log');
+const FC = require('@alicloud/fc2');
+const Pop = require('@alicloud/pop-core');
+const getProfile = require('./profile').getProfile;
+
+const getFcClient = async () => {
+  const profile = await getProfile();
+
+  const locale = await osLocale();
+
+  const mid = await hashedMachineId();
+
+  FC.prototype.getAccountSettings = function(options = {}, headers = {}) {
+    return this.get('/account-settings', options, headers);
+  };
+
+  const fc = new FC(profile.accountId, {
+    accessKeyID: profile.accessKeyId,
+    accessKeySecret: profile.accessKeySecret,
+    region: profile.defaultRegion,
+    timeout: profile.timeout * 1000,
+    secure: profile.protocol !== 'http',
+    headers: {
+      'user-agent': `${pkg.name}/v${pkg.version} ( Node.js ${process.version}; OS ${process.platform} ${process.arch}; language ${locale}; mid ${mid})`
+    }
+  });
+
+  return fc;
+};
+
+const getPopClient = async (endpoint, apiVersion) => {
+  const profile = await getProfile();
+
+  return new Pop({
+    endpoint: endpoint,
+    apiVersion: apiVersion,
+    accessKeyId: profile.accessKeyId,
+    secretAccessKey: profile.accessKeySecret,
+    opts: {
+      timeout: profile.timeout * 1000
+    }
+  });
+};
+
+const getOtsPopClient = async () => {
+  const profile = await getProfile();
+
+  return await getPopClient(`http://ots.${profile.defaultRegion}.aliyuncs.com`, '2016-06-20');
+};
+
+const getVpcPopClient = async () => {
+  return await getPopClient('https://vpc.aliyuncs.com', '2016-04-28');
+};
+
+const getEcsPopClient = async () => {
+  return await getPopClient('https://ecs.aliyuncs.com', '2014-05-26');
+};
+
+const getNasPopClient = async () => {
+
+  const profile = await getProfile();
+
+  return await getPopClient(`http://nas.${profile.defaultRegion}.aliyuncs.com`, '2017-06-26');
+};
+
+const getOtsClient = async (instanceName) => {
+  const profile = await getProfile();
+
+  var endpoint = `http://${instanceName}.${profile.defaultRegion}.ots.aliyuncs.com`;
+  return new TableStore.Client({
+    accessKeyId: profile.accessKeyId,
+    secretAccessKey: profile.accessKeySecret,
+    endpoint: endpoint,
+    instancename: instanceName
+  });
+};
+
+const getMnsClient = async (topicName, region) => {
+  const profile = await getProfile();
+
+  return new MNSClient(profile.accountId, {
+    region: region,
+    accessKeyId: profile.accessKeyId,
+    accessKeySecret: profile.accessKeySecret,
+    // optional & default
+    secure: false, // use https or http
+    internal: false, // use internal endpoint
+    vpc: false // use vpc endpoint
+  });
+
+};
+
+const getCloudApiClient = async () => {
+  const profile = await getProfile();
+
+  return new CloudAPI({
+    accessKeyId: profile.accessKeyId,
+    accessKeySecret: profile.accessKeySecret,
+    endpoint: `http://apigateway.${profile.defaultRegion}.aliyuncs.com`,
+    opts: {
+      timeout: profile.timeout * 1000
+    }
+  });
+};
+
+const getSlsClient = async () => {
+  const profile = await getProfile();
+
+  return new Log({
+    region: profile.defaultRegion,
+    accessKeyId: profile.accessKeyId,
+    accessKeySecret: profile.accessKeySecret
+  });
+};
+
+module.exports = {
+  getFcClient,
+  getOtsClient,
+  getOtsPopClient,
+  getMnsClient,
+  getCloudApiClient,
+  getSlsClient,
+  getPopClient,
+  getVpcPopClient,
+  getEcsPopClient,
+  getNasPopClient
+};
\ No newline at end of file
diff --git a/lib/deploy/deploy-by-tpl.js b/lib/deploy/deploy-by-tpl.js
index 4ed2c47..e67d0a9 100644
--- a/lib/deploy/deploy-by-tpl.js
+++ b/lib/deploy/deploy-by-tpl.js
@@ -4,9 +4,7 @@ const fs = require('fs');
 const path = require('path');
 const util = require('util');
 const yaml = require('js-yaml');
-const getProfile = require('../profile').getProfile;
-
-const { mark } = require('../profile');
+const { getProfile, mark } = require('../profile');
 
 const getTpl = require('../tpl').getTpl;
 const validate = require('../validate/validate');
@@ -17,21 +15,21 @@ const { green, yellow, red} = require('colors');
 const readFile = util.promisify(fs.readFile);
 const _ = require('lodash');
 
+const { makeService, makeFunction } = require('../fc');
+
+const { getTriggerNameList, makeTrigger } = require('../trigger');
+
 let {
   makeApi,
   makeApiTrigger,
-  makeFunction,
   makeGroup,
   makeOtsTable,
   makeOtsInstance,
-  makeService,
-  makeTrigger,
   makeMnsTopic,
   makeSlsProject,
   makeLogstore,
   makeLogstoreIndex,
-  makeCustomDomain,
-  getTriggerNameList
+  makeCustomDomain
 } = require('./deploy-support');
 
 let {
@@ -172,16 +170,7 @@ async function deployService(baseDir, serviceName, serviceRes) {
   const internetAccess = 'InternetAccess' in properties ? properties.InternetAccess : null;
   
   const policies = properties.Policies;
-  const defaultVpcConfig = {
-    securityGroupId: '',
-    vSwitchIds: [],
-    vpcId: ''
-  };
-  const defaultNasConfig = {
-    UserId: -1,
-    GroupId: -1,
-    MountPoints: []
-  };
+  
   const vpcConfig = properties.VpcConfig;
   const nasConfig = properties.NasConfig;
 
@@ -209,17 +198,27 @@ async function deployService(baseDir, serviceName, serviceRes) {
   // However, in some cases, users do not want to configure ram permissions for ram users.
   // https://github.com/aliyun/fun/issues/182
   // https://github.com/aliyun/fun/pull/223
-  if (!roleArn && (policies || !_.isEmpty(vpcConfig) || !_.isEmpty(logConfig))) {
+  if (!roleArn && (policies || !_.isEmpty(vpcConfig) || !_.isEmpty(logConfig) || !_.isEmpty(nasConfig))) {
     // create role
+
+    console.log(`\tmake sure role '${roleName}' is exist`);
+
     role = await makeRole(roleName, createRoleIfNotExist);
+
+    console.log(green(`\trole '${roleName}' is already exist`));
   }
 
   if (!roleArn && policies) { // if roleArn exist, then ignore polices
+    console.log('\tattaching policies ' + policies + ' to role: ' + roleName);
     await deployPolicies(serviceName, roleName, policies);
+    console.log(green('\tattached policies ' + policies + ' to role: ' + roleName));
+
   }
 
-  if (!roleArn && !_.isEmpty(vpcConfig)) {
+  if (!roleArn && (!_.isEmpty(vpcConfig) || !_.isEmpty(nasConfig))) {
+    console.log('\tattaching police \'AliyunECSNetworkInterfaceManagementAccess\' to role: ' + roleName);
     await attachPolicyToRole('AliyunECSNetworkInterfaceManagementAccess', roleName);
+    console.log(green('\tattached police \'AliyunECSNetworkInterfaceManagementAccess\' to role: ' + roleName));
   }
 
   if (logConfig.Logstore && logConfig.Project) {
@@ -246,8 +245,8 @@ async function deployService(baseDir, serviceName, serviceRes) {
     internetAccess,
     description: (serviceRes.Properties || {}).Description,
     logConfig,
-    vpcConfig: vpcConfig || defaultVpcConfig,
-    nasConfig: nasConfig || defaultNasConfig
+    vpcConfig: vpcConfig,
+    nasConfig: nasConfig
   });
 
   await deployFunctions(baseDir, serviceName, serviceRes);
diff --git a/lib/deploy/deploy-support.js b/lib/deploy/deploy-support.js
index 87a3074..ea42c0c 100644
--- a/lib/deploy/deploy-support.js
+++ b/lib/deploy/deploy-support.js
@@ -3,121 +3,24 @@
 const fs = require('fs');
 const util = require('util');
 
-const Log = require('@alicloud/log');
-const FC = require('@alicloud/fc2');
-const Pop = require('@alicloud/pop-core');
-const CloudAPI = require('@alicloud/cloudapi');
-const TableStore = require('tablestore');
+const { getCloudApiClient, getSlsClient, getMnsClient } = require('../client');
+
+const {
+  getOtsClient,
+  getOtsPopClient,
+  getFcClient } = require('../client');
 
 const getProfile = require('../profile').getProfile;
-const pkg = require('../../package.json');
-const zip = require('../package/zip');
 const debug = require('debug')('fun:deploy');
-const osLocale = require('os-locale');
-const MNSClient = require('@alicloud/mns');
-const path = require('path');
 const { processApiParameters } = require('./deploy-support-api');
 
-const hashedMachineId = require('node-machine-id').machineId;
-
 const promiseRetry = require('../retry');
 
-const funignore = require('../package/ignore');
-
 const ram = require('../ram');
 
 const readFile = util.promisify(fs.readFile);
-const readdir = util.promisify(fs.readdir);
-const lstat = util.promisify(fs.lstat);
-
-const { green, red, yellow } = require('colors');
-const { addEnv } = require('../install/env');
-
-const _ = require('lodash');
-const getFcClient = async () => {
-  const profile = await getProfile();
-
-  const locale = await osLocale();
-
-  const mid = await hashedMachineId();
-
-  return new FC(profile.accountId, {
-    accessKeyID: profile.accessKeyId,
-    accessKeySecret: profile.accessKeySecret,
-    region: profile.defaultRegion,
-    timeout: profile.timeout * 1000,
-    secure: profile.protocol !== 'http',
-    headers: {
-      'user-agent': `${pkg.name}/v${pkg.version} ( Node.js ${process.version}; OS ${process.platform} ${process.arch}; language ${locale}; mid ${mid})`
-    }
-  });
-};
-
-const getOtsClient = async (instanceName) => {
-  const profile = await getProfile();
-
-  var endpoint = `http://${instanceName}.${profile.defaultRegion}.ots.aliyuncs.com`;
-  return new TableStore.Client({
-    accessKeyId: profile.accessKeyId,
-    secretAccessKey: profile.accessKeySecret,
-    endpoint: endpoint,
-    instancename: instanceName
-  });
-};
-
-
-const getMnsClient = async (topicName, region) => {
-  const profile = await getProfile();
 
-  return new MNSClient(profile.accountId, {
-    region: region,
-    accessKeyId: profile.accessKeyId,
-    accessKeySecret: profile.accessKeySecret,
-    // optional & default
-    secure: false, // use https or http
-    internal: false, // use internal endpoint
-    vpc: false // use vpc endpoint
-  });
-
-};
-
-const getPopClient = async () => {
-  const profile = await getProfile();
-
-  return new Pop({
-    endpoint: `http://ots.${profile.defaultRegion}.aliyuncs.com`,
-    apiVersion: '2016-06-20',
-    accessKeyId: profile.accessKeyId,
-    secretAccessKey: profile.accessKeySecret,
-    opts: {
-      timeout: profile.timeout * 1000
-    }
-  });
-};
-
-const getCloudApiClient = async () => {
-  const profile = await getProfile();
-
-  return new CloudAPI({
-    accessKeyId: profile.accessKeyId,
-    accessKeySecret: profile.accessKeySecret,
-    endpoint: `http://apigateway.${profile.defaultRegion}.aliyuncs.com`,
-    opts: {
-      timeout: profile.timeout * 1000
-    }
-  });
-};
-
-
-const getSlsClient = async () => {
-  const profile = await getProfile();
-
-  return new Log({
-    region: profile.defaultRegion,
-    accessKeyId: profile.accessKeyId,
-    accessKeySecret: profile.accessKeySecret
-  });
-};
+const { green, red } = require('colors');
 
 async function makeLogstoreIndex(projectName, logstoreName) {
   const sls = await getSlsClient();
@@ -278,661 +181,6 @@ async function makeMnsTopic(topicName, properties) {
   });
 }
 
-async function makeService({
-  serviceName,
-  role,
-  description,
-  internetAccess = true,
-  logConfig,
-  vpcConfig,
-  nasConfig
-}) {
-  const fc = await getFcClient();
-
-  var service;
-  await promiseRetry(async (retry, times) => {
-    try {
-      service = await fc.getService(serviceName);
-    } catch (ex) {
-
-      if (ex.code === 'AccessDenied') {
-
-        console.log(red(`\n${ ex.message }`));
-        
-        if (ex.message.indexOf('the caller is not authorized to perform') !== -1) {
-
-          console.error(red(`\nMaybe you need grant AliyunRAMFullAccess policy to the subuser or use the primary account. You can refer to Chinese doc https://github.com/aliyun/fun/blob/master/docs/usage/faq-zh.md#nopermissionerror-you-are-not-authorized-to-do-this-action-resource-acsramxxxxxxxxxxrole-action-ramgetrole or English doc https://github.com/aliyun/fun/blob/master/docs/usage/faq.md#nopermissionerror-you-are-not-authorized-to-do-this-action-resource-acsramxxxxxxxxxxrole-action-ramgetrole for help.\n\nIf you don’t want use the AliyunRAMFullAccess policy or primary account, you can also specify the Role property for Service. You can refer to Chinease doc https://github.com/aliyun/fun/blob/master/docs/specs/2018-04-03-zh-cn.md#aliyunserverlessservice or Enaglish doc https://github.com/aliyun/fun/blob/master/docs/specs/2018-04-03.md#aliyunserverlessservice for help.\n`));
-
-        } else if (ex.message.indexOf('FC service is not enabled for current user') !== -1) {
-
-          console.error(red(`\nFC service is not enabled for current user. Please enable FC service before using fun.\nYou can enable FC service on this page https://www.aliyun.com/product/fc .\n`));
-
-        } else {
-          console.error(red(`\nThe accountId you entered is incorrect. You can only use the primary account id, whether or not you use a sub-account or a primary account ak. You can get primary account ID on this page https://account.console.aliyun.com/#/secure .\n`));
-        }
-
-        throw ex;
-      } else if (ex.code !== 'ServiceNotFound') {
-        debug('error when getService, serviceName is %s, error is: \n%O', serviceName, ex);
-
-        console.log(red(`\tretry ${times} times`));
-        retry(ex);
-      }
-    }
-  });
-
-  const options = {
-    description,
-    role,
-    logConfig: {
-      project: logConfig.Project || '',
-      logstore: logConfig.Logstore || ''
-    }
-  };
-
-  if (internetAccess !== null) {
-    // vpc feature is not supported in some region
-    Object.assign(options, {
-      internetAccess
-    });
-  }
-
-  if (vpcConfig) {
-    Object.assign(options, {
-      vpcConfig
-    });
-  }
-
-  if (nasConfig) {
-    Object.assign(options, {
-      nasConfig
-    });
-  }
-
-  await promiseRetry(async (retry, times) => {
-    try {
-      if (!service) {
-        debug('create service %s, options is %j', serviceName, options);
-        service = await fc.createService(serviceName, options);
-      } else {
-        debug('update service %s, options is %j', serviceName, options);
-        service = await fc.updateService(serviceName, options);
-      }
-    } catch (ex) {
-      debug('error when createService or updateService, serviceName is %s, options is %j, error is: \n%O', serviceName, options, ex);
-
-      console.log(red(`\tretry ${times} times`));
-      retry(ex);
-    }
-  });
-
-  return service;
-}
-
-function generateFunIngore(baseDir, codeUri) {
-  const absCodeUri = path.resolve(codeUri);
-  const absBaseDir = path.resolve(baseDir);
-
-  const relative = path.relative(absBaseDir, absCodeUri);
-
-  if (codeUri.startsWith('..') || relative.startsWith('..')) {
-    console.warn(red(`\t\twarning: funignore is not supported for your CodeUri: ${codeUri}`));
-    return null;
-  }
-
-  return funignore(baseDir);
-}
-
-async function getFunCodeAsBase64(baseDir, codeUri, runtime, functionName) {
-  if (codeUri) {
-    if (codeUri.endsWith('.zip') || codeUri.endsWith('.jar')) {
-      return {base64: Buffer.from(await readFile(codeUri)).toString('base64')};
-    }
-  } else {
-    codeUri = './';
-  }
-
-  const ignore = generateFunIngore(baseDir, codeUri);
-
-  await detectLibrary(codeUri, runtime, baseDir, '\t\t', functionName);
-  
-  return await zip.pack(codeUri, ignore);
-}
-
-function extractOssCodeUri(ossUri) {
-  const prefixLength = 'oss://'.length;
-
-  const index = ossUri.indexOf('/', prefixLength);
-
-  return {
-    ossBucketName: ossUri.substring(prefixLength, index),
-    ossObjectName: ossUri.substring(index + 1)
-  };
-}
-
-async function makeFunction(baseDir, {
-  serviceName,
-  functionName,
-  description = '',
-  handler,
-  initializer = '',
-  timeout = 3,
-  initializationTimeout = 3,
-  memorySize = 128,
-  runtime = 'nodejs6',
-  codeUri,
-  environmentVariables = {}
-}) {
-  const fc = await getFcClient();
-
-  var fn;
-  try {
-    fn = await fc.getFunction(serviceName, functionName);
-  } catch (ex) {
-    if (ex.code !== 'FunctionNotFound') {
-      throw ex;
-    }
-  }
-
-  let code;
-  // oss://my-bucket/function.zip
-  if (codeUri && codeUri.startsWith('oss://')) {
-    code = extractOssCodeUri(codeUri);
-  } else {
-    console.log(`\t\tWaiting for packaging function ${functionName} code...`);
-    const {base64, count} = await getFunCodeAsBase64(baseDir, codeUri, runtime, functionName);
-    const log = count ? ', the number of files you have packaged is：' + yellow(`${count}`) : '';
-    console.log(green(`\t\tpackage function ${functionName} code done` + log));
-    code = {
-      zipFile: base64
-    };
-  }
-
-  const params = {
-    description,
-    handler,
-    initializer,
-    timeout,
-    initializationTimeout,
-    memorySize,
-    runtime,
-    code,
-    environmentVariables: addEnv(environmentVariables)
-  };
-
-  for (let i in params.environmentVariables) {
-    if (!isNaN(params.environmentVariables[i])) {
-      debug(`the value in environmentVariables:${params.environmentVariables[i]} cast String Done`);
-      params.environmentVariables[i] = params.environmentVariables[i]+ '';
-    }
-  }
-  if (!fn) {
-    // create
-    params['functionName'] = functionName;
-    fn = await fc.createFunction(serviceName, params);
-  } else {
-    // update
-    fn = await fc.updateFunction(serviceName, functionName, params);
-  }
-
-  return fn;
-}
-
-const triggerTypeMapping = {
-  'Datahub': 'datahub',
-  'Timer': 'timer',
-  'HTTP': 'http',
-  'Log': 'log',
-  'OSS': 'oss',
-  'RDS': 'rds',
-  'MNSTopic': 'mns_topic',
-  'TableStore': 'tablestore',
-  'CDN': 'cdn_events'
-};
-
-async function getSourceArn(triggerType, triggerProperties) {
-  const profile = await getProfile();
-
-  if (triggerType === 'Log') {
-    return `acs:log:${profile.defaultRegion}:${profile.accountId}:project/${triggerProperties.LogConfig.Project}`;
-  } else if (triggerType === 'RDS') {
-    return `acs:rds:${profile.defaultRegion}:${profile.accountId}:dbinstance/${triggerProperties.InstanceId}`;
-  } else if (triggerType === 'MNSTopic') {
-    if (triggerProperties.Region !== undefined) {
-      return `acs:mns:${triggerProperties.Region}:${profile.accountId}:/topics/${triggerProperties.TopicName}`;
-    }
-    return `acs:mns:${profile.defaultRegion}:${profile.accountId}:/topics/${triggerProperties.TopicName}`;
-  } else if (triggerType === 'TableStore') {
-    return `acs:ots:${profile.defaultRegion}:${profile.accountId}:instance/${triggerProperties.InstanceName}/table/${triggerProperties.TableName}`;
-  } else if (triggerType === 'OSS') {
-    return `acs:oss:${profile.defaultRegion}:${profile.accountId}:${triggerProperties.BucketName || triggerProperties.bucketName}`;
-  } else if (triggerType === 'CDN') {
-    return `acs:cdn:*:${profile.accountId}`;
-  }
-  return;
-}
-
-function getTriggerConfig(triggerType, triggerProperties) {
-  if (triggerType === 'Timer') {
-    return {
-      payload: triggerProperties.Payload,
-      cronExpression: triggerProperties.CronExpression,
-      enable: triggerProperties.Enable
-    };
-  } else if (triggerType === 'HTTP') {
-    return {
-      authType: (triggerProperties.AuthType).toLowerCase(),
-      methods: triggerProperties.Methods
-    };
-  } else if (triggerType === 'Log') {
-    const logConfig = triggerProperties.LogConfig;
-    const jobConfig = triggerProperties.JobConfig;
-    const sourceConfig = triggerProperties.SourceConfig;
-
-    return {
-      sourceConfig: {
-        logstore: sourceConfig.Logstore
-      },
-      jobConfig: {
-        maxRetryTime: jobConfig.MaxRetryTime,
-        triggerInterval: jobConfig.TriggerInterval
-      },
-      logConfig: {
-        project: logConfig.Project,
-        logstore: logConfig.Logstore,
-        functionParameter: logConfig.FunctionParameter || {}
-      },
-      Enable: triggerProperties.Enable || true
-    };
-  } else if (triggerType === 'RDS') {
-    return {
-      subscriptionObjects: triggerProperties.SubscriptionObjects,
-      retry: triggerProperties.Retry,
-      concurrency: triggerProperties.Concurrency,
-      eventFormat: triggerProperties.EventFormat
-    };
-  } else if (triggerType === 'MNSTopic') {
-    var notifyContentFormat = 'STREAM';
-    if (triggerProperties.NotifyContentFormat !== undefined) {
-      notifyContentFormat = triggerProperties.NotifyContentFormat;
-    }
-    var notifyStrategy = 'BACKOFF_RETRY';
-    if (triggerProperties.NotifyStrategy !== undefined) {
-      notifyStrategy = triggerProperties.NotifyStrategy;
-    }
-    var triggerCfg = {
-      NotifyContentFormat: notifyContentFormat,
-      NotifyStrategy: notifyStrategy
-    };
-    if (triggerProperties.FilterTag !== undefined) {
-      triggerCfg.FilterTag = triggerProperties.FilterTag;
-    }
-    return triggerCfg;
-  } else if (triggerType === 'TableStore') {
-    return {};
-  } else if (triggerType === 'OSS') {
-    return {
-      events: triggerProperties.Events || triggerProperties.events,
-      filter: triggerProperties.Filter || triggerProperties.filter
-    };
-  } else if (triggerType === 'CDN') {
-    return {
-      eventName: triggerProperties.EventName,
-      eventVersion: triggerProperties.EventVersion,
-      notes: triggerProperties.Notes,
-      filter: _.mapKeys(triggerProperties.Filter, (value, key) => {
-        return _.lowerFirst(key);
-      })
-    };
-  }
-  console.error(`trigger type is ${triggerType} not supported.`);
-}
-
-async function makeInvocationRole(serviceName, functionName, triggerType) {
-  if (triggerType === 'Log') {
-
-    const invocationRoleName = ram.normalizeRoleOrPoliceName(`AliyunFcGeneratedInvocationRole-${serviceName}-${functionName}`);
-
-    const invocationRole = await ram.makeRole(invocationRoleName, true, 'Used for fc invocation', {
-      'Statement': [{
-        'Action': 'sts:AssumeRole',
-        'Effect': 'Allow',
-        'Principal': {
-          'Service': [
-            'log.aliyuncs.com'
-          ]
-        }
-      }],
-      'Version': '1'
-    });
-
-    const policyName = ram.normalizeRoleOrPoliceName(`AliyunFcGeneratedInvocationPolicy-${serviceName}-${functionName}`);
-
-    await ram.makePolicy(policyName, {
-      'Version': '1',
-      'Statement': [{
-        'Action': [
-          'fc:InvokeFunction'
-        ],
-        'Resource': `acs:fc:*:*:services/${serviceName}/functions/*`,
-        'Effect': 'Allow'
-      },
-      {
-        'Action': [
-          'log:Get*',
-          'log:List*',
-          'log:PostLogStoreLogs',
-          'log:CreateConsumerGroup',
-          'log:UpdateConsumerGroup',
-          'log:DeleteConsumerGroup',
-          'log:ListConsumerGroup',
-          'log:ConsumerGroupUpdateCheckPoint',
-          'log:ConsumerGroupHeartBeat',
-          'log:GetConsumerGroupCheckPoint'
-        ],
-        'Resource': '*',
-        'Effect': 'Allow'
-      }
-      ]
-    });
-
-    await ram.attachPolicyToRole(policyName, invocationRoleName, 'Custom');
-    return invocationRole.Role;
-
-  } else if (triggerType === 'RDS' || triggerType === 'MNSTopic') {
-
-    const invocationRoleName = ram.normalizeRoleOrPoliceName(`FunCreateRole-${serviceName}-${functionName}`);
-    var tMap = {
-      'RDS': 'rds',
-      'MNSTopic': 'mns'
-    };
-    var principalService = util.format('%s.aliyuncs.com', tMap[triggerType]);
-
-    const invocationRole = await ram.makeRole(invocationRoleName, true, 'Used for fc invocation', {
-      'Statement': [{
-        'Action': 'sts:AssumeRole',
-        'Effect': 'Allow',
-        'Principal': {
-          'Service': [
-            principalService
-          ]
-        }
-      }],
-      'Version': '1'
-    });
-
-    const policyName = ram.normalizeRoleOrPoliceName(`FunCreatePolicy-${serviceName}-${functionName}`);
-
-    await ram.makePolicy(policyName, {
-      'Version': '1',
-      'Statement': [{
-        'Action': [
-          'fc:InvokeFunction'
-        ],
-        'Resource': `acs:fc:*:*:services/${serviceName}/functions/*`,
-        'Effect': 'Allow'
-      }]
-    });
-
-    await ram.attachPolicyToRole(policyName, invocationRoleName, 'Custom');
-
-    return invocationRole.Role;
-
-  } else if (triggerType === 'TableStore') {
-    const invocationRoleName = ram.normalizeRoleOrPoliceName(`FunCreateRole-${serviceName}-${functionName}`);
-
-    const invocationRole = await ram.makeRole(invocationRoleName, true, 'Used for fc invocation', {
-      'Statement': [{
-        'Action': 'sts:AssumeRole',
-        'Effect': 'Allow',
-        'Principal': {
-          'RAM': [
-            'acs:ram::1604337383174619:root'
-          ]
-        }
-      }],
-      'Version': '1'
-    });
-
-    const invkPolicyName = ram.normalizeRoleOrPoliceName(`FunCreateInvkPolicy-${serviceName}-${functionName}`);
-
-    await ram.makePolicy(invkPolicyName, {
-      'Version': '1',
-      'Statement': [{
-        'Action': [
-          'fc:InvokeFunction'
-        ],
-        'Resource': '*',
-        'Effect': 'Allow'
-      }]
-    });
-
-    await ram.attachPolicyToRole(invkPolicyName, invocationRoleName, 'Custom');
-
-    const otsReadPolicyName = ram.normalizeRoleOrPoliceName(`FunCreateOtsReadPolicy-${serviceName}-${functionName}`);
-
-    await ram.makePolicy(otsReadPolicyName, {
-      'Version': '1',
-      'Statement': [{
-        'Action': [
-          'ots:BatchGet*',
-          'ots:Describe*',
-          'ots:Get*',
-          'ots:List*'
-        ],
-        'Resource': '*',
-        'Effect': 'Allow'
-      }]
-    });
-
-    await ram.attachPolicyToRole(otsReadPolicyName, invocationRoleName, 'Custom');
-
-    return invocationRole.Role;
-  } else if (triggerType === 'OSS') {
-
-    const invocationRoleName = ram.normalizeRoleOrPoliceName(`FunCreateRole-${serviceName}-${functionName}`);
-
-    const invocationRole = await ram.makeRole(invocationRoleName, true, 'Used for fc invocation', {
-      'Statement': [
-        {
-          'Action': 'sts:AssumeRole',
-          'Effect': 'Allow',
-          'Principal': {
-            'Service': [
-              'oss.aliyuncs.com'
-            ]
-          }
-        }
-      ],
-      'Version': '1'
-    });
-
-    const policyName = ram.normalizeRoleOrPoliceName(`FunCreateOSSPolicy-${serviceName}-${functionName}`);
-
-    await ram.makePolicy(policyName, {
-      'Version': '1',
-      'Statement': [{
-        'Action': [
-          'fc:InvokeFunction'
-        ],
-        'Resource': `acs:fc:*:*:services/${serviceName}/functions/*`,
-        'Effect': 'Allow'
-      }]
-    });
-
-    await ram.attachPolicyToRole(policyName, invocationRoleName, 'Custom');
-    return invocationRole.Role;
-
-  } else if (triggerType === 'CDN') {
-
-    const invocationRoleName = ram.normalizeRoleOrPoliceName(`FunCreateRole-${serviceName}-${functionName}`);
-
-    const invocationRole = await ram.makeRole(invocationRoleName, true, 'Used for fc invocation', {
-      'Statement': [
-        {
-          'Action': 'sts:AssumeRole',
-          'Effect': 'Allow',
-          'Principal': {
-            'Service': [
-              'cdn.aliyuncs.com'
-            ]
-          }
-        }
-      ],
-      'Version': '1'
-    });
-
-    const policyName = ram.normalizeRoleOrPoliceName(`FunCreateCDNPolicy-${serviceName}-${functionName}`);
-
-    await ram.makePolicy(policyName, {
-      'Version': '1',
-      'Statement': [{
-        'Action': [
-          'fc:InvokeFunction'
-        ],
-        'Resource': `acs:fc:*:*:services/${serviceName}/functions/*`,
-        'Effect': 'Allow'
-      }]
-    });
-
-    await ram.attachPolicyToRole(policyName, invocationRoleName, 'Custom');
-    return invocationRole.Role;
-  }
-  return;
-}
-
-const runtimeTypeMapping = {
-  'nodejs6': 'node_modules',
-  'nodejs8': 'node_modules',
-  'python2.7': ['.egg-info', '.dist-info', '.fun'],
-  'python3': ['.egg-info', '.dist-info', '.fun'],
-  'php7.2': ['extension', 'vendor']
-};
-
-async function detectLibraryFolders(dirName, libraryFolders, childDir, wrap, functionName) {
-  if (Array.isArray(libraryFolders)) {
-    for (const iterator of libraryFolders) {
-      for (const name of childDir) {
-        if (_.endsWith(name, iterator)) {
-          console.warn(red(`${wrap}Fun detected that the library directory '${name}' is not included in function '${functionName}' CodeUri.\n\t\tPlease make sure if it is the right configuration. if yes, ignore please.`));
-          return;
-        }
-      }
-    }
-  } else {
-    if (childDir.includes(libraryFolders)) {
-      console.warn(red(`${wrap}Fun detected that the library directory '${libraryFolders}' is not included in function '${functionName}' CodeUri.\n\t\tPlease make sure if it is the right configuration. if yes, ignore please.`));
-    } else {
-
-      const funDir = childDir.filter(p => p === '.fun');
-      if (Array.isArray(funDir) && funDir.length > 0) {
-
-        const childFun = await readdir(path.join(dirName, '.fun'));
-
-        if (childFun.includes('root')) {
-
-          console.warn(red(`${wrap}Fun detected that the library directory '.fun/root' is not included in function '${functionName}' CodeUri.\n\t\tPlease make sure if it is the right configuration. if yes, ignore please.`));
-
-        }
-      }
-    }
-  } 
-}
-
-async function detectLibrary(codeUri, runtime, baseDir, wrap = '', functionName) {
-  const stats = await lstat(codeUri);
-  if (stats.isFile()) {
-    let libraryFolders = runtimeTypeMapping[runtime];
-    let absoluteCodePath;
-
-    if (path.isAbsolute(codeUri)) {
-      absoluteCodePath = codeUri;
-    } else {
-      absoluteCodePath = path.join(baseDir, codeUri);
-    }
-
-    const dirName = path.dirname(absoluteCodePath);
-    const childDir = await readdir(dirName);
-    
-    await detectLibraryFolders(dirName, libraryFolders, childDir, wrap, functionName);
-  }
-}
-
-async function getTriggerNameList ({
-  serviceName,
-  functionName
-}) {
-  const fc = await getFcClient();
-  var listTriggerResponse = await fc.listTriggers(serviceName, functionName);
-
-  var triggerNameArray = [];
-
-  if (listTriggerResponse && listTriggerResponse.data.triggers) {
-    triggerNameArray = listTriggerResponse.data.triggers.map(p => p.triggerName);
-  }
-  return triggerNameArray;
-}
-
-async function makeTrigger({
-  serviceName,
-  functionName,
-  triggerName,
-  triggerType,
-  triggerProperties
-}) {
-  const fc = await getFcClient();
-  var trigger;
-  try {
-    trigger = await fc.getTrigger(serviceName, functionName, triggerName);
-  } catch (ex) {
-    if (ex.code !== 'TriggerNotFound') {
-      throw ex;
-    }
-  }
-
-  const params = {
-    triggerType: triggerTypeMapping[triggerType],
-    triggerConfig: getTriggerConfig(triggerType, triggerProperties)
-  };
-
-  debug('serviceName is %s, functionName is %s, trigger params is %j', serviceName, functionName, params);
-
-  let invocationRoleArn = triggerProperties.InvocationRole;
-
-  if (!invocationRoleArn) { 
-    const invocationRole = await makeInvocationRole(serviceName, functionName, triggerType);
-    invocationRoleArn = invocationRole.Arn;
-  } 
-
-  Object.assign(params, {
-    'invocationRole': invocationRoleArn
-  });
-
-  const sourceArn = await getSourceArn(triggerType, triggerProperties);
-  if (sourceArn) {
-    Object.assign(params, {
-      'sourceArn': sourceArn
-    });
-  }
-
-  if (!trigger) {
-    params.triggerName = triggerName;    
-    trigger = await fc.createTrigger(serviceName, functionName, params);
-  } else {
-    if (triggerType === 'TableStore' || triggerType === 'MNSTopic') {
-      // no triggerConfig, so no updateTrigger, first delete, then create
-      // await fc.deleteTrigger(serviceName, functionName, triggerName);
-      // params.triggerName = triggerName;
-      // trigger = await fc.createTrigger(serviceName, functionName, params);
-      console.log(red(`\t\tWarning: TableStore and MNSTopic Trigger cann't update`));
-      return;
-    }
-    trigger = await fc.updateTrigger(serviceName, functionName, triggerName, params);
-  }
-
-  return trigger;
-}
-
 async function makeCustomDomain({
   domainName,
   protocol,
@@ -970,7 +218,7 @@ async function makeCustomDomain({
 
     if (privateKey && privateKey.endsWith('.pem')) {
       certConfig.PrivateKey = await readFile(privateKey, 'utf-8');
-    }     
+    }
     if (certificate && certificate.endsWith('.pem')) {
       certConfig.Certificate = await readFile(certificate, 'utf-8');
     }
@@ -978,7 +226,7 @@ async function makeCustomDomain({
       certConfig
     });
   }
-  
+
   await promiseRetry(async (retry, times) => {
     try {
       if (!customDomain) {
@@ -1085,9 +333,9 @@ async function makeApi(group, {
         'requestPath': requestPath
       }, requestConfig);
 
-      const { apiRequestParameters, 
-        apiServiceParameters, 
-        apiServiceParametersMap} = processApiParameters(requestParameters, serviceParameters, serviceParametersMap);
+      const { apiRequestParameters,
+        apiServiceParameters,
+        apiServiceParametersMap } = processApiParameters(requestParameters, serviceParameters, serviceParametersMap);
 
       const profile = await getProfile();
       var params = {
@@ -1218,12 +466,12 @@ async function makeApiTrigger({
 }
 
 async function makeOtsInstance(instanceName, clusterType, description) {
-  const pop = await getPopClient();
+  const otsPopClient = await getOtsPopClient();
 
   await promiseRetry(async (retry, times) => {
     try {
       try {
-        await pop.request('GetInstance', {
+        await otsPopClient.request('GetInstance', {
           'InstanceName': instanceName
         });
 
@@ -1234,7 +482,7 @@ async function makeOtsInstance(instanceName, clusterType, description) {
         }
       }
 
-      await pop.request('InsertInstance', {
+      await otsPopClient.request('InsertInstance', {
         InstanceName: instanceName,
         ClusterType: clusterType,
         Description: description
@@ -1243,7 +491,7 @@ async function makeOtsInstance(instanceName, clusterType, description) {
       });
     } catch (ex) {
       if (ex.code === 'AuthFailed'
-        || ex.code === 'InvalidParameter' 
+        || ex.code === 'InvalidParameter'
         || ex.code === 'QuotaExhausted') {
         throw new Error(red(ex.message));
       } else {
@@ -1304,20 +552,12 @@ async function makeOtsTable({
 module.exports = {
   makeApi,
   makeApiTrigger,
-  makeFunction,
   makeGroup,
   makeOtsTable,
   makeOtsInstance,
-  makeService,
-  makeTrigger,
   makeSlsProject,
   makeLogstore,
   makeLogstoreIndex,
   makeCustomDomain,
-  makeMnsTopic,
-  makeInvocationRole,
-  getFunCodeAsBase64,
-  getTriggerNameList,
-  getTriggerConfig,
-  detectLibrary
+  makeMnsTopic
 };
\ No newline at end of file
diff --git a/lib/docker.js b/lib/docker.js
index a5ca6b2..b5463d6 100644
--- a/lib/docker.js
+++ b/lib/docker.js
@@ -404,8 +404,8 @@ async function run(opts, event, outputStream, errorStream) {
 }
 
 function resolveDockerUser(nasConfig) {
-  let uid = 0;
-  let gid = 0;
+  let uid = 10003;
+  let gid = 10003;
 
   if (nasConfig) {
     const userId = nasConfig.UserId;
diff --git a/lib/fc-utils/fc-nas-dir-check.js b/lib/fc-utils/fc-nas-dir-check.js
new file mode 100644
index 0000000..3320286
--- /dev/null
+++ b/lib/fc-utils/fc-nas-dir-check.js
@@ -0,0 +1,43 @@
+
+'use strict';
+
+const path = require('path');
+const fs = require('fs');
+
+function mkdirPSync(p) {
+
+  try {
+    fs.mkdirSync(p);
+  }
+  catch (err0) {
+    console.error('error: ', err0);
+
+    switch (err0.code) {
+    case 'ENOENT' :
+      mkdirPSync(path.dirname(p));
+      mkdirPSync(p);
+      break;
+
+    default:
+      var stat;
+      try {
+        stat = fs.statSync(p);
+      }
+      catch (err1) {
+        throw err0;
+      }
+      if (!stat.isDirectory()) { throw err0; }
+      break;
+    }
+  }
+}
+
+module.exports.handler = function(event, context, callback) {
+  const dirs = JSON.parse(event);
+
+  for (let dir of dirs) {
+    mkdirPSync(dir);
+  }
+
+  callback(null, 'OK');
+};
\ No newline at end of file
diff --git a/lib/fc.js b/lib/fc.js
new file mode 100644
index 0000000..c9d833e
--- /dev/null
+++ b/lib/fc.js
@@ -0,0 +1,494 @@
+'use strict';
+
+const { getFcClient } = require('./client');
+const vpc = require('./vpc');
+const nas = require('./nas');
+
+const util = require('util');
+const fs = require('fs');
+const readFile = util.promisify(fs.readFile);
+const path = require('path');
+const debug = require('debug')('fun:fc');
+const zip = require('./package/zip');
+const { green, red, yellow } = require('colors');
+const { addEnv } = require('./install/env');
+const funignore = require('./package/ignore');
+const lstat = util.promisify(fs.lstat);
+const readdir = util.promisify(fs.readdir);
+const _ = require('lodash');
+
+const promiseRetry = require('./retry');
+
+const FUN_GENERATED_SERVICE = 'fun-generated-default-service';
+
+const defaultVpcConfig = {
+  securityGroupId: '',
+  vSwitchIds: [],
+  vpcId: ''
+};
+
+const defaultNasConfig = {
+  UserId: -1,
+  GroupId: -1,
+  MountPoints: []
+};
+
+function generateFunIngore(baseDir, codeUri) {
+  const absCodeUri = path.resolve(codeUri);
+  const absBaseDir = path.resolve(baseDir);
+
+  const relative = path.relative(absBaseDir, absCodeUri);
+
+  if (codeUri.startsWith('..') || relative.startsWith('..')) {
+    console.warn(red(`\t\twarning: funignore is not supported for your CodeUri: ${codeUri}`));
+    return null;
+  }
+
+  return funignore(baseDir);
+}
+
+const runtimeTypeMapping = {
+  'nodejs6': 'node_modules',
+  'nodejs8': 'node_modules',
+  'python2.7': ['.egg-info', '.dist-info', '.fun'],
+  'python3': ['.egg-info', '.dist-info', '.fun'],
+  'php7.2': ['extension', 'vendor']
+};
+
+async function detectLibraryFolders(dirName, libraryFolders, childDir, wrap, functionName) {
+  if (Array.isArray(libraryFolders)) {
+    for (const iterator of libraryFolders) {
+      for (const name of childDir) {
+        if (_.endsWith(name, iterator)) {
+          console.warn(red(`${wrap}Fun detected that the library directory '${name}' is not included in function '${functionName}' CodeUri.\n\t\tPlease make sure if it is the right configuration. if yes, ignore please.`));
+          return;
+        }
+      }
+    }
+  } else {
+    if (childDir.includes(libraryFolders)) {
+      console.warn(red(`${wrap}Fun detected that the library directory '${libraryFolders}' is not included in function '${functionName}' CodeUri.\n\t\tPlease make sure if it is the right configuration. if yes, ignore please.`));
+    } else {
+
+      const funDir = childDir.filter(p => p === '.fun');
+      if (Array.isArray(funDir) && funDir.length > 0) {
+
+        const childFun = await readdir(path.join(dirName, '.fun'));
+
+        if (childFun.includes('root')) {
+
+          console.warn(red(`${wrap}Fun detected that the library directory '.fun/root' is not included in function '${functionName}' CodeUri.\n\t\tPlease make sure if it is the right configuration. if yes, ignore please.`));
+
+        }
+      }
+    }
+  }
+}
+
+async function detectLibrary(codeUri, runtime, baseDir, wrap = '', functionName) {
+  const stats = await lstat(codeUri);
+  if (stats.isFile()) {
+    let libraryFolders = runtimeTypeMapping[runtime];
+    let absoluteCodePath;
+
+    if (path.isAbsolute(codeUri)) {
+      absoluteCodePath = codeUri;
+    } else {
+      absoluteCodePath = path.join(baseDir, codeUri);
+    }
+
+    const dirName = path.dirname(absoluteCodePath);
+    const childDir = await readdir(dirName);
+
+    await detectLibraryFolders(dirName, libraryFolders, childDir, wrap, functionName);
+  }
+}
+
+function extractOssCodeUri(ossUri) {
+  const prefixLength = 'oss://'.length;
+
+  const index = ossUri.indexOf('/', prefixLength);
+
+  return {
+    ossBucketName: ossUri.substring(prefixLength, index),
+    ossObjectName: ossUri.substring(index + 1)
+  };
+}
+
+async function getFunCodeAsBase64(baseDir, codeUri, runtime, functionName) {
+  if (codeUri) {
+    if (codeUri.endsWith('.zip') || codeUri.endsWith('.jar')) {
+      return { base64: Buffer.from(await readFile(codeUri)).toString('base64') };
+    }
+  } else {
+    codeUri = './';
+  }
+
+  const ignore = generateFunIngore(baseDir, codeUri);
+
+  await detectLibrary(codeUri, runtime, baseDir, '\t\t', functionName);
+
+  return await zip.pack(codeUri, ignore);
+}
+
+async function makeFunction(baseDir, {
+  serviceName,
+  functionName,
+  description = '',
+  handler,
+  initializer = '',
+  timeout = 3,
+  initializationTimeout = 3,
+  memorySize = 128,
+  runtime = 'nodejs6',
+  codeUri,
+  environmentVariables = {}
+}) {
+  const fc = await getFcClient();
+
+  var fn;
+  try {
+    fn = await fc.getFunction(serviceName, functionName);
+  } catch (ex) {
+    if (ex.code !== 'FunctionNotFound') {
+      throw ex;
+    }
+  }
+
+  let code;
+  // oss://my-bucket/function.zip
+  if (codeUri && codeUri.startsWith('oss://')) {
+    code = extractOssCodeUri(codeUri);
+  } else {
+    console.log(`\t\tWaiting for packaging function ${functionName} code...`);
+    const { base64, count } = await getFunCodeAsBase64(baseDir, codeUri, runtime, functionName);
+    const log = count ? ', the number of files you have packaged is：' + yellow(`${count}`) : '';
+    console.log(green(`\t\tpackage function ${functionName} code done` + log));
+    code = {
+      zipFile: base64
+    };
+  }
+
+  const params = {
+    description,
+    handler,
+    initializer,
+    timeout,
+    initializationTimeout,
+    memorySize,
+    runtime,
+    code,
+    environmentVariables: addEnv(environmentVariables)
+  };
+
+  for (let i in params.environmentVariables) {
+    if (!isNaN(params.environmentVariables[i])) {
+      debug(`the value in environmentVariables:${params.environmentVariables[i]} cast String Done`);
+      params.environmentVariables[i] = params.environmentVariables[i] + '';
+    }
+  }
+  if (!fn) {
+    // create
+    params['functionName'] = functionName;
+    fn = await fc.createFunction(serviceName, params);
+  } else {
+    // update
+    fn = await fc.updateFunction(serviceName, functionName, params);
+  }
+
+  return fn;
+}
+
+async function makeService({
+  serviceName,
+  role,
+  description,
+  internetAccess = true,
+  logConfig = {},
+  vpcConfig,
+  nasConfig
+}) {
+  const fc = await getFcClient();
+
+  var service;
+  await promiseRetry(async (retry, times) => {
+    try {
+      service = await fc.getService(serviceName);
+    } catch (ex) {
+
+      if (ex.code === 'AccessDenied') {
+
+        console.log(red(`\n${ex.message}`));
+
+        if (ex.message.indexOf('the caller is not authorized to perform') !== -1) {
+
+          console.error(red(`\nMaybe you need grant AliyunRAMFullAccess policy to the subuser or use the primary account. You can refer to Chinese doc https://github.com/aliyun/fun/blob/master/docs/usage/faq-zh.md#nopermissionerror-you-are-not-authorized-to-do-this-action-resource-acsramxxxxxxxxxxrole-action-ramgetrole or English doc https://github.com/aliyun/fun/blob/master/docs/usage/faq.md#nopermissionerror-you-are-not-authorized-to-do-this-action-resource-acsramxxxxxxxxxxrole-action-ramgetrole for help.\n\nIf you don’t want use the AliyunRAMFullAccess policy or primary account, you can also specify the Role property for Service. You can refer to Chinease doc https://github.com/aliyun/fun/blob/master/docs/specs/2018-04-03-zh-cn.md#aliyunserverlessservice or Enaglish doc https://github.com/aliyun/fun/blob/master/docs/specs/2018-04-03.md#aliyunserverlessservice for help.\n`));
+
+        } else if (ex.message.indexOf('FC service is not enabled for current user') !== -1) {
+
+          console.error(red(`\nFC service is not enabled for current user. Please enable FC service before using fun.\nYou can enable FC service on this page https://www.aliyun.com/product/fc .\n`));
+
+        } else {
+          console.error(red(`\nThe accountId you entered is incorrect. You can only use the primary account id, whether or not you use a sub-account or a primary account ak. You can get primary account ID on this page https://account.console.aliyun.com/#/secure .\n`));
+        }
+
+        throw ex;
+      } else if (ex.code !== 'ServiceNotFound') {
+        debug('error when getService, serviceName is %s, error is: \n%O', serviceName, ex);
+
+        console.log(red(`\tretry ${times} times`));
+        retry(ex);
+      }
+    }
+  });
+
+  const options = {
+    description,
+    role,
+    logConfig: {
+      project: logConfig.Project || '',
+      logstore: logConfig.Logstore || ''
+    }
+  };
+
+  if (internetAccess !== null) {
+    // vpc feature is not supported in some region
+    Object.assign(options, {
+      internetAccess
+    });
+  }
+
+  if (!_.isEmpty(vpcConfig) || _.isEqual(nasConfig, 'Auto')) {
+
+    if (_.isEqual(vpcConfig, 'Auto') || (_.isEmpty(vpcConfig) && _.isEqual(nasConfig, 'Auto'))) {
+      console.log('\tusing \'VpcConfig: Auto\', Fun will try to generate related vpc resources automatically');
+      vpcConfig = await vpc.createDefaultVpcIfNotExist();
+      console.log(green('\tgenerated auto VpcConfig done: ', JSON.stringify(vpcConfig)));
+
+      debug('generated vpcConfig: %j', vpcConfig);
+    }
+  }
+
+  Object.assign(options, {
+    vpcConfig: vpcConfig || defaultVpcConfig
+  });
+
+  if (_.isEqual(nasConfig, 'Auto')) {
+
+    const vpcId = vpcConfig.vpcId;
+    const vswitchId = _.head(vpcConfig.vswitchIds);
+
+    console.log('\tusing \'NasConfig: Auto\', Fun will try to generate related nas file system automatically');
+    nasConfig = await nas.generateAutoNasConfig(serviceName, vpcId, vswitchId);
+    console.log(green('\tgenerated auto NasConfig done: ', JSON.stringify(nasConfig)));
+  }
+
+  Object.assign(options, {
+    nasConfig: nasConfig || defaultNasConfig
+  });
+
+  await promiseRetry(async (retry, times) => {
+    try {
+      if (!service) {
+        debug('create service %s, options is %j', serviceName, options);
+        service = await fc.createService(serviceName, options);
+      } else {
+        debug('update service %s, options is %j', serviceName, options);
+        service = await fc.updateService(serviceName, options);
+      }
+    } catch (ex) {
+      debug('error when createService or updateService, serviceName is %s, options is %j, error is: \n%O', serviceName, options, ex);
+
+      console.log(red(`\tretry ${times} times`));
+      retry(ex);
+    }
+  });
+
+  // make sure nas dir exist
+  if (serviceName !== FUN_GENERATED_SERVICE
+    && !_.isEmpty(nasConfig)
+    && !_.isEmpty(nasConfig.MountPoints)) {
+
+    await ensureNasDirExist({
+      role, vpcConfig, nasConfig
+    });
+  }
+
+  return service;
+}
+
+function mapMountPointDir(mountPoints, func) {
+  let resolvedMountPoints = _.map(mountPoints, (mountPoint) => {
+    const serverAddr = mountPoint.ServerAddr;
+
+    const index = _.lastIndexOf(serverAddr, ':');
+    if (index >= 0) {
+      const mountPointDomain = serverAddr.substring(0, index);
+      const remoteDir = serverAddr.substring(index + 1);
+      const mountDir = mountPoint.MountDir;
+
+      debug('remoteDir is: %s', remoteDir);
+
+      return func(mountPointDomain, remoteDir, mountDir);
+    }
+  });
+
+  resolvedMountPoints = _.compact(resolvedMountPoints);
+
+  return resolvedMountPoints;
+}
+
+async function ensureNasDirExist({
+  role,
+  vpcConfig,
+  nasConfig
+}) {
+  const mountPoints = nasConfig.MountPoints;
+  const modifiedNasConfig = _.cloneDeep(nasConfig);
+
+  modifiedNasConfig.MountPoints = mapMountPointDir(mountPoints, (mountPointDomain, remoteDir, mountDir) => {
+    if (remoteDir !== '/') {
+      return {
+        ServerAddr: `${mountPointDomain}:/`,
+        MountDir: `${mountDir}`
+      };
+    } return null;
+  });
+
+  const dirsNeedToCheck = mapMountPointDir(mountPoints, (mountPointDomain, remoteDir, mountDir) => {
+    if (remoteDir !== '/') {
+      return path.join(mountDir, remoteDir);
+    } return null;
+  });
+
+  debug('dirs need to check: %s', dirsNeedToCheck);
+
+  if (!_.isEmpty(dirsNeedToCheck)) {
+    console.log(`\tChecking if nas directories ${dirsNeedToCheck} exists, if not, it will be created automatically`);
+
+    const utilFunctionName = await makeFcUtilsFunctionNasDirChecker(role, vpcConfig, modifiedNasConfig);
+
+    await invokeFcUtilsFunction({
+      functionName: utilFunctionName,
+      event: JSON.stringify(dirsNeedToCheck)
+    });
+  
+    console.log(green('\tChecking nas directories done', JSON.stringify(dirsNeedToCheck)));
+  } 
+}
+
+async function makeFcUtilsService(role, vpcConfig, nasConfig) {
+  return await makeService({
+    serviceName: FUN_GENERATED_SERVICE,
+    role,
+    description: 'generated by fun',
+    vpcConfig,
+    nasConfig
+  });
+}
+
+async function makeFcUtilsFunction({
+  serviceName,
+  functionName,
+  codes,
+  description = '',
+  handler,
+  timeout = 60,
+  memorySize = 128,
+  runtime = 'nodejs8'
+}) {
+  const fc = await getFcClient();
+
+  var fn;
+  try {
+    fn = await fc.getFunction(serviceName, functionName);
+  } catch (ex) {
+    if (ex.code !== 'FunctionNotFound') {
+      throw ex;
+    }
+  }
+
+  const base64 = await zip.packFromJson(codes);
+
+  let code = {
+    zipFile: base64
+  };
+
+  const params = {
+    description,
+    handler,
+    initializer: '',
+    timeout,
+    memorySize,
+    runtime,
+    code
+  };
+
+  if (!fn) {
+    // create
+    params['functionName'] = functionName;
+    fn = await fc.createFunction(serviceName, params);
+  } else {
+    // update
+    fn = await fc.updateFunction(serviceName, functionName, params);
+  }
+
+  return fn;
+}
+
+async function invokeFcUtilsFunction({
+  functionName,
+  event
+}) {
+  const fc = await getFcClient();
+  const rs = await fc.invokeFunction(FUN_GENERATED_SERVICE, functionName, event, {
+    'X-Fc-Log-Type': 'Tail'
+  });
+
+  if (rs.data !== 'OK') {
+    const log = rs.headers['x-fc-log-result'];
+
+    if (log) {
+      const decodedLog = Buffer.from(log, 'base64');
+
+      throw new Error(`fc utils function ${functionName} invoke error, error message is: ${decodedLog}`);
+    }
+  }
+}
+
+async function getFcUtilsFunctionCode(filename) {
+  const functionCode = await readFile(path.join(__dirname, 'fc-utils', filename));
+
+  const codes = {
+    'index.js': functionCode
+  };
+
+  return codes;
+}
+
+async function makeFcUtilsFunctionNasDirChecker(role, vpcConfig, nasConfig) {
+  await makeFcUtilsService(role, vpcConfig, nasConfig);
+
+  const functionName = 'nas_dir_checker';
+
+  const codes = await getFcUtilsFunctionCode('fc-nas-dir-check.js');
+
+  await makeFcUtilsFunction({
+    serviceName: FUN_GENERATED_SERVICE,
+    functionName: 'nas_dir_checker',
+    codes,
+    description: 'used for fun to ensure nas remote dir exist',
+    handler: 'index.handler'
+  });
+
+  return functionName;
+}
+
+module.exports = {
+  invokeFcUtilsFunction,
+  makeFcUtilsFunctionNasDirChecker,
+  FUN_GENERATED_SERVICE,
+  makeService,
+  makeFunction,
+  getFunCodeAsBase64
+};
\ No newline at end of file
diff --git a/lib/nas.js b/lib/nas.js
new file mode 100644
index 0000000..9c46916
--- /dev/null
+++ b/lib/nas.js
@@ -0,0 +1,203 @@
+'use strict';
+
+const getProfile = require('./profile').getProfile;
+const { getNasPopClient } = require('./client');
+const _ = require('lodash');
+const debug = require('debug')('fun:nas');
+const { green } = require('colors');
+const { sleep } = require('./time');
+
+var requestOption = {
+  method: 'POST'
+};
+
+const NAS_DEFAULT_DESCRIPTION = 'default nas created by fc fun';
+
+async function createMountTarget(nasClient, region, fileSystemId, vpcId, vswitchId) {
+  const params = {
+    'RegionId': region,
+    'NetworkType': 'Vpc',
+    'FileSystemId': fileSystemId,
+    'AccessGroupName': 'DEFAULT_VPC_GROUP_NAME',
+    'VpcId': vpcId,
+    'VSwitchId': vswitchId
+  };
+
+  var requestOption = {
+    method: 'POST'
+  };
+
+  const rs = await nasClient.request('CreateMountTarget', params, requestOption);    
+
+  const mountTargetDomain = rs.MountTargetDomain;
+
+  debug('create mount target rs: %s', mountTargetDomain);
+
+  await waitMountPointUntilAvaliable(nasClient, region, fileSystemId, mountTargetDomain);
+
+  return mountTargetDomain;
+}
+
+async function waitMountPointUntilAvaliable(nasClient, region, fileSystemId, mountTargetDomain) {
+  let count = 0;
+  let status;
+
+  do {
+    count++;
+
+    var params = {
+      'RegionId': region, 
+      'FileSystemId': fileSystemId,
+      'MountTargetDomain': mountTargetDomain
+    };
+    
+    await sleep(800);
+
+    const rs = await nasClient.request('DescribeMountTargets', params, requestOption);
+    
+    status = rs.MountTargets.MountTarget[0].Status;
+    
+    debug('nas status is: ' + status);
+
+    console.log(`\t\tnas mount target domain already created, waiting for status to be 'Active', now is ${status}`);
+    
+  } while (count < 15 && status !== 'Active');
+
+  if (status !== 'Active') { throw new Error(`Timeout while waiting for MountPoint ${mountTargetDomain} status to be 'Active'`); }
+}
+
+async function createDefaultNasIfNotExist(vpcId, vswitchId) {
+  const nasClient = await getNasPopClient();
+
+  const profile = await getProfile();
+  const region = profile.defaultRegion;
+
+  const fileSystemId = await createNasFileSystemIfNotExist(nasClient, region);
+
+  debug('fileSystemId: %s', fileSystemId);
+
+  return await createMountTargetIfNotExist(nasClient, region, fileSystemId, vpcId, vswitchId);  
+}
+
+async function findMountTarget(nasClient, region, fileSystemId, vpcId, vswitchId) {
+  var params = {
+    'RegionId': region,
+    'FileSystemId': fileSystemId
+  };
+
+  const rs = await nasClient.request('DescribeMountTargets', params, requestOption);
+
+  const mountTargets = rs.MountTargets.MountTarget;
+
+  // todo: 检查 mountTargets 的 vswitch 是否与函数计算的一致？
+
+  if (!_.isEmpty(mountTargets)) {
+
+    const mountTarget = _.find(mountTargets, {
+      'VpcId': vpcId,
+      'VswId': vswitchId
+    });
+
+    if (mountTarget) { 
+      return mountTarget.MountTargetDomain;
+    }
+  } 
+
+  return null;
+}
+
+async function createMountTargetIfNotExist(nasClient, region, fileSystemId, vpcId, vswitchId) {
+  let mountTargetDomain = await findMountTarget(nasClient, region, fileSystemId, vpcId, vswitchId);
+  
+  if (mountTargetDomain) { 
+    console.log(green('\t\tnas file system mount target is already created, mountTargetDomain is: ' + mountTargetDomain));
+
+    return mountTargetDomain; 
+  }
+
+  // create mountTarget if not exist
+
+  console.log('\t\tcould not find default nas file system mount target, ready to generate one');
+
+  mountTargetDomain = await createMountTarget(nasClient, region, fileSystemId, vpcId, vswitchId);
+
+  console.log(green('\t\tdefault nas file system mount target has been generated, mount domain is: ' + mountTargetDomain));
+
+  return mountTargetDomain;
+}
+
+async function createNasFileSystemIfNotExist(nasClient, region) {
+  let fileSystemId = await findNasFileSystem(nasClient, region, NAS_DEFAULT_DESCRIPTION);
+
+  if (!fileSystemId) {
+    console.log('\t\tcould not find default nas file system, ready to generate one');
+
+    fileSystemId = await createNasFileSystem(nasClient, region);
+
+    console.log(green('\t\tdefault nas file system has been generated, fileSystemId is: ' + fileSystemId));
+  } else {
+    console.log(green('\t\tnas file system already generated, fileSystemId is: ' + fileSystemId));
+  }
+
+  return fileSystemId;
+}
+
+async function findNasFileSystem(nasClient, region, description) {
+
+  // todo: pageable
+  const pageSize = 1000;
+
+  var params = {
+    'RegionId': region,
+    'PageSize': pageSize
+  };
+  
+  var requestOption = {
+    method: 'POST'
+  };
+  
+  const rs = await nasClient.request('DescribeFileSystems', params, requestOption);
+
+  const fileSystems = rs.FileSystems.FileSystem;
+
+  const fileSystem = _.find(fileSystems, { Description: description });
+
+  debug('find filesystem: ' + JSON.stringify(fileSystem));
+
+  if (fileSystem) { return fileSystem.FileSystemId; }
+  return null;
+}
+
+async function createNasFileSystem(nasClient, region) {
+  var params = {
+    'RegionId': region,
+    'ProtocolType': 'NFS',
+    'StorageType': 'Performance',
+    'Description': NAS_DEFAULT_DESCRIPTION
+  };
+
+  const rs = await nasClient.request('CreateFileSystem', params, requestOption);  
+  return rs.FileSystemId;
+}
+
+async function generateAutoNasConfig(serviceName, vpcId, vswitchId) {
+  const mountPointDomain = await createDefaultNasIfNotExist(vpcId, vswitchId);
+
+  return {
+    UserId: 10003,
+    GroupId: 10003,
+    MountPoints: [
+      {
+        ServerAddr: `${mountPointDomain}:/${serviceName}`,
+        MountDir: '/mnt/auto'
+      }
+    ]
+  };
+}
+
+module.exports = {
+  findNasFileSystem,
+  findMountTarget,
+  createMountTarget,
+  generateAutoNasConfig
+};
\ No newline at end of file
diff --git a/lib/package/zip.js b/lib/package/zip.js
index be98355..a41cbe8 100644
--- a/lib/package/zip.js
+++ b/lib/package/zip.js
@@ -8,6 +8,7 @@ const util = require('util');
 const debug = require('debug')('fun:zip');
 const JSZip = require('jszip');
 const glob = require('glob');
+const _ = require('lodash');
 
 const exists = util.promisify(fs.exists);
 const readdir = util.promisify(fs.readdir);
@@ -118,6 +119,27 @@ async function zipFolder(zip, folder, folders, funignore) {
   return count;
 }
 
+async function generateAsync(zip) {
+  return await zip.generateAsync({
+    type: 'base64',
+    platform: 'UNIX',
+    compression: 'DEFLATE',
+    compressionOptions: {
+      level: 9
+    }
+  });
+}
+
+exports.packFromJson = async function (files) {
+  const zip = new JSZip();
+
+  _.forEach(files, (content, path) => {
+    zip.file(path, content);
+  });
+
+  return await generateAsync(zip);
+};
+
 exports.pack = async function (file, funignore) {
   if (!(await exists(file))) {
     throw new Error('zip file %s is not exist.', file);
@@ -145,14 +167,8 @@ exports.pack = async function (file, funignore) {
   } else {
     throw new Error('file %s must be a regular file or directory.', file);
   }
-  const base64 = await zip.generateAsync({
-    type: 'base64',
-    platform: 'UNIX',
-    compression: 'DEFLATE',
-    compressionOptions: {
-      level: 9
-    }
-  });
+  const base64 = await generateAsync(zip);
 
   return { base64, count };
 };
+
diff --git a/lib/security-group.js b/lib/security-group.js
new file mode 100644
index 0000000..cfb9403
--- /dev/null
+++ b/lib/security-group.js
@@ -0,0 +1,68 @@
+'use strict';
+
+var requestOption = {
+  method: 'POST'
+};
+
+async function describeSecurityGroups(client, region, vpcId, securityGroupName) {
+  var params = {
+    'RegionId': region,
+    'VpcId': vpcId,
+    'SecurityGroupName': securityGroupName
+  };
+
+  const describeRs = await client.request('DescribeSecurityGroups', params, requestOption);
+
+  const securityGroup = describeRs.SecurityGroups.SecurityGroup;
+
+  return securityGroup;
+}
+
+async function authSecurityGroupRule(ecsClient, region, securityGroupId, protocol, port) {
+  var params = {
+    'RegionId': region,
+    'SecurityGroupId': securityGroupId,
+    'IpProtocol': protocol,
+    'PortRange': port,
+    'Policy': 'Accept',
+    'SourceCidrIp': '0.0.0.0/0',
+    'NicType': 'intranet'
+  };
+
+  const rs = await ecsClient.request('AuthorizeSecurityGroup', params, requestOption);
+  return rs;
+}
+
+async function authDefaultSecurityGroupRules(ecsClient, region, securityGroupId) {
+
+  const sgRules = [
+    { protocol: 'TCP', port: '80/80' },
+    { protocol: 'TCP', port: '443/443' },
+    { protocol: 'ICMP', port: '-1/-1' },
+    { protocol: 'TCP', port: '22/22' }
+  ];
+
+  for (const rule of sgRules) {
+    await authSecurityGroupRule(ecsClient, region, securityGroupId, rule.protocol, rule.port);
+  }
+}
+
+async function createSecurityGroup(ecsClient, region, vpcId, securityGroupName) {
+  var params = {
+    'RegionId': region,
+    'SecurityGroupName': securityGroupName,
+    'Description': 'default security group created by fc fun',
+    'VpcId': vpcId,
+    'SecurityGroupType': 'normal'
+  };
+
+  const createRs = await ecsClient.request('CreateSecurityGroup', params, requestOption);
+
+  return createRs.SecurityGroupId;
+}
+
+module.exports = {
+  describeSecurityGroups,
+  createSecurityGroup,
+  authDefaultSecurityGroupRules
+};
\ No newline at end of file
diff --git a/lib/time.js b/lib/time.js
new file mode 100644
index 0000000..7d3bc33
--- /dev/null
+++ b/lib/time.js
@@ -0,0 +1,9 @@
+'use strict';
+
+function sleep(ms) {
+  return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+module.exports = {
+  sleep
+};
\ No newline at end of file
diff --git a/lib/trigger.js b/lib/trigger.js
new file mode 100644
index 0000000..be3980e
--- /dev/null
+++ b/lib/trigger.js
@@ -0,0 +1,419 @@
+'use strict';
+
+const getProfile = require('./profile').getProfile;
+const { getFcClient } = require('./client');
+const debug = require('debug')('fun:trigger');
+const _ = require('lodash');
+const ram = require('./ram');
+const util = require('util');
+const { red } = require('colors');
+
+const triggerTypeMapping = {
+  'Datahub': 'datahub',
+  'Timer': 'timer',
+  'HTTP': 'http',
+  'Log': 'log',
+  'OSS': 'oss',
+  'RDS': 'rds',
+  'MNSTopic': 'mns_topic',
+  'TableStore': 'tablestore',
+  'CDN': 'cdn_events'
+};
+
+async function getSourceArn(triggerType, triggerProperties) {
+  const profile = await getProfile();
+
+  if (triggerType === 'Log') {
+    return `acs:log:${profile.defaultRegion}:${profile.accountId}:project/${triggerProperties.LogConfig.Project}`;
+  } else if (triggerType === 'RDS') {
+    return `acs:rds:${profile.defaultRegion}:${profile.accountId}:dbinstance/${triggerProperties.InstanceId}`;
+  } else if (triggerType === 'MNSTopic') {
+    if (triggerProperties.Region !== undefined) {
+      return `acs:mns:${triggerProperties.Region}:${profile.accountId}:/topics/${triggerProperties.TopicName}`;
+    }
+    return `acs:mns:${profile.defaultRegion}:${profile.accountId}:/topics/${triggerProperties.TopicName}`;
+  } else if (triggerType === 'TableStore') {
+    return `acs:ots:${profile.defaultRegion}:${profile.accountId}:instance/${triggerProperties.InstanceName}/table/${triggerProperties.TableName}`;
+  } else if (triggerType === 'OSS') {
+    return `acs:oss:${profile.defaultRegion}:${profile.accountId}:${triggerProperties.BucketName || triggerProperties.bucketName}`;
+  } else if (triggerType === 'CDN') {
+    return `acs:cdn:*:${profile.accountId}`;
+  }
+  return;
+}
+
+async function getTriggerNameList({
+  serviceName,
+  functionName
+}) {
+  const fc = await getFcClient();
+  var listTriggerResponse = await fc.listTriggers(serviceName, functionName);
+
+  var triggerNameArray = [];
+
+  if (listTriggerResponse && listTriggerResponse.data.triggers) {
+    triggerNameArray = listTriggerResponse.data.triggers.map(p => p.triggerName);
+  }
+  return triggerNameArray;
+}
+
+function getTriggerConfig(triggerType, triggerProperties) {
+  if (triggerType === 'Timer') {
+    return {
+      payload: triggerProperties.Payload,
+      cronExpression: triggerProperties.CronExpression,
+      enable: triggerProperties.Enable
+    };
+  } else if (triggerType === 'HTTP') {
+    return {
+      authType: (triggerProperties.AuthType).toLowerCase(),
+      methods: triggerProperties.Methods
+    };
+  } else if (triggerType === 'Log') {
+    const logConfig = triggerProperties.LogConfig;
+    const jobConfig = triggerProperties.JobConfig;
+    const sourceConfig = triggerProperties.SourceConfig;
+
+    return {
+      sourceConfig: {
+        logstore: sourceConfig.Logstore
+      },
+      jobConfig: {
+        maxRetryTime: jobConfig.MaxRetryTime,
+        triggerInterval: jobConfig.TriggerInterval
+      },
+      logConfig: {
+        project: logConfig.Project,
+        logstore: logConfig.Logstore,
+        functionParameter: logConfig.FunctionParameter || {}
+      },
+      Enable: triggerProperties.Enable || true
+    };
+  } else if (triggerType === 'RDS') {
+    return {
+      subscriptionObjects: triggerProperties.SubscriptionObjects,
+      retry: triggerProperties.Retry,
+      concurrency: triggerProperties.Concurrency,
+      eventFormat: triggerProperties.EventFormat
+    };
+  } else if (triggerType === 'MNSTopic') {
+    var notifyContentFormat = 'STREAM';
+    if (triggerProperties.NotifyContentFormat !== undefined) {
+      notifyContentFormat = triggerProperties.NotifyContentFormat;
+    }
+    var notifyStrategy = 'BACKOFF_RETRY';
+    if (triggerProperties.NotifyStrategy !== undefined) {
+      notifyStrategy = triggerProperties.NotifyStrategy;
+    }
+    var triggerCfg = {
+      NotifyContentFormat: notifyContentFormat,
+      NotifyStrategy: notifyStrategy
+    };
+    if (triggerProperties.FilterTag !== undefined) {
+      triggerCfg.FilterTag = triggerProperties.FilterTag;
+    }
+    return triggerCfg;
+  } else if (triggerType === 'TableStore') {
+    return {};
+  } else if (triggerType === 'OSS') {
+    return {
+      events: triggerProperties.Events || triggerProperties.events,
+      filter: triggerProperties.Filter || triggerProperties.filter
+    };
+  } else if (triggerType === 'CDN') {
+    return {
+      eventName: triggerProperties.EventName,
+      eventVersion: triggerProperties.EventVersion,
+      notes: triggerProperties.Notes,
+      filter: _.mapKeys(triggerProperties.Filter, (value, key) => {
+        return _.lowerFirst(key);
+      })
+    };
+  }
+  console.error(`trigger type is ${triggerType} not supported.`);
+}
+
+async function makeInvocationRole(serviceName, functionName, triggerType) {
+  if (triggerType === 'Log') {
+
+    const invocationRoleName = ram.normalizeRoleOrPoliceName(`AliyunFcGeneratedInvocationRole-${serviceName}-${functionName}`);
+
+    const invocationRole = await ram.makeRole(invocationRoleName, true, 'Used for fc invocation', {
+      'Statement': [{
+        'Action': 'sts:AssumeRole',
+        'Effect': 'Allow',
+        'Principal': {
+          'Service': [
+            'log.aliyuncs.com'
+          ]
+        }
+      }],
+      'Version': '1'
+    });
+
+    const policyName = ram.normalizeRoleOrPoliceName(`AliyunFcGeneratedInvocationPolicy-${serviceName}-${functionName}`);
+
+    await ram.makePolicy(policyName, {
+      'Version': '1',
+      'Statement': [{
+        'Action': [
+          'fc:InvokeFunction'
+        ],
+        'Resource': `acs:fc:*:*:services/${serviceName}/functions/*`,
+        'Effect': 'Allow'
+      },
+      {
+        'Action': [
+          'log:Get*',
+          'log:List*',
+          'log:PostLogStoreLogs',
+          'log:CreateConsumerGroup',
+          'log:UpdateConsumerGroup',
+          'log:DeleteConsumerGroup',
+          'log:ListConsumerGroup',
+          'log:ConsumerGroupUpdateCheckPoint',
+          'log:ConsumerGroupHeartBeat',
+          'log:GetConsumerGroupCheckPoint'
+        ],
+        'Resource': '*',
+        'Effect': 'Allow'
+      }
+      ]
+    });
+
+    await ram.attachPolicyToRole(policyName, invocationRoleName, 'Custom');
+    return invocationRole.Role;
+
+  } else if (triggerType === 'RDS' || triggerType === 'MNSTopic') {
+
+    const invocationRoleName = ram.normalizeRoleOrPoliceName(`FunCreateRole-${serviceName}-${functionName}`);
+    var tMap = {
+      'RDS': 'rds',
+      'MNSTopic': 'mns'
+    };
+    var principalService = util.format('%s.aliyuncs.com', tMap[triggerType]);
+
+    const invocationRole = await ram.makeRole(invocationRoleName, true, 'Used for fc invocation', {
+      'Statement': [{
+        'Action': 'sts:AssumeRole',
+        'Effect': 'Allow',
+        'Principal': {
+          'Service': [
+            principalService
+          ]
+        }
+      }],
+      'Version': '1'
+    });
+
+    const policyName = ram.normalizeRoleOrPoliceName(`FunCreatePolicy-${serviceName}-${functionName}`);
+
+    await ram.makePolicy(policyName, {
+      'Version': '1',
+      'Statement': [{
+        'Action': [
+          'fc:InvokeFunction'
+        ],
+        'Resource': `acs:fc:*:*:services/${serviceName}/functions/*`,
+        'Effect': 'Allow'
+      }]
+    });
+
+    await ram.attachPolicyToRole(policyName, invocationRoleName, 'Custom');
+
+    return invocationRole.Role;
+
+  } else if (triggerType === 'TableStore') {
+    const invocationRoleName = ram.normalizeRoleOrPoliceName(`FunCreateRole-${serviceName}-${functionName}`);
+
+    const invocationRole = await ram.makeRole(invocationRoleName, true, 'Used for fc invocation', {
+      'Statement': [{
+        'Action': 'sts:AssumeRole',
+        'Effect': 'Allow',
+        'Principal': {
+          'RAM': [
+            'acs:ram::1604337383174619:root'
+          ]
+        }
+      }],
+      'Version': '1'
+    });
+
+    const invkPolicyName = ram.normalizeRoleOrPoliceName(`FunCreateInvkPolicy-${serviceName}-${functionName}`);
+
+    await ram.makePolicy(invkPolicyName, {
+      'Version': '1',
+      'Statement': [{
+        'Action': [
+          'fc:InvokeFunction'
+        ],
+        'Resource': '*',
+        'Effect': 'Allow'
+      }]
+    });
+
+    await ram.attachPolicyToRole(invkPolicyName, invocationRoleName, 'Custom');
+
+    const otsReadPolicyName = ram.normalizeRoleOrPoliceName(`FunCreateOtsReadPolicy-${serviceName}-${functionName}`);
+
+    await ram.makePolicy(otsReadPolicyName, {
+      'Version': '1',
+      'Statement': [{
+        'Action': [
+          'ots:BatchGet*',
+          'ots:Describe*',
+          'ots:Get*',
+          'ots:List*'
+        ],
+        'Resource': '*',
+        'Effect': 'Allow'
+      }]
+    });
+
+    await ram.attachPolicyToRole(otsReadPolicyName, invocationRoleName, 'Custom');
+
+    return invocationRole.Role;
+  } else if (triggerType === 'OSS') {
+
+    const invocationRoleName = ram.normalizeRoleOrPoliceName(`FunCreateRole-${serviceName}-${functionName}`);
+
+    const invocationRole = await ram.makeRole(invocationRoleName, true, 'Used for fc invocation', {
+      'Statement': [
+        {
+          'Action': 'sts:AssumeRole',
+          'Effect': 'Allow',
+          'Principal': {
+            'Service': [
+              'oss.aliyuncs.com'
+            ]
+          }
+        }
+      ],
+      'Version': '1'
+    });
+
+    const policyName = ram.normalizeRoleOrPoliceName(`FunCreateOSSPolicy-${serviceName}-${functionName}`);
+
+    await ram.makePolicy(policyName, {
+      'Version': '1',
+      'Statement': [{
+        'Action': [
+          'fc:InvokeFunction'
+        ],
+        'Resource': `acs:fc:*:*:services/${serviceName}/functions/*`,
+        'Effect': 'Allow'
+      }]
+    });
+
+    await ram.attachPolicyToRole(policyName, invocationRoleName, 'Custom');
+    return invocationRole.Role;
+
+  } else if (triggerType === 'CDN') {
+
+    const invocationRoleName = ram.normalizeRoleOrPoliceName(`FunCreateRole-${serviceName}-${functionName}`);
+
+    const invocationRole = await ram.makeRole(invocationRoleName, true, 'Used for fc invocation', {
+      'Statement': [
+        {
+          'Action': 'sts:AssumeRole',
+          'Effect': 'Allow',
+          'Principal': {
+            'Service': [
+              'cdn.aliyuncs.com'
+            ]
+          }
+        }
+      ],
+      'Version': '1'
+    });
+
+    const policyName = ram.normalizeRoleOrPoliceName(`FunCreateCDNPolicy-${serviceName}-${functionName}`);
+
+    await ram.makePolicy(policyName, {
+      'Version': '1',
+      'Statement': [{
+        'Action': [
+          'fc:InvokeFunction'
+        ],
+        'Resource': `acs:fc:*:*:services/${serviceName}/functions/*`,
+        'Effect': 'Allow'
+      }]
+    });
+
+    await ram.attachPolicyToRole(policyName, invocationRoleName, 'Custom');
+    return invocationRole.Role;
+  }
+  return;
+}
+
+async function makeTrigger({
+  serviceName,
+  functionName,
+  triggerName,
+  triggerType,
+  triggerProperties
+}) {
+  const fc = await getFcClient();
+  var trigger;
+  try {
+    trigger = await fc.getTrigger(serviceName, functionName, triggerName);
+  } catch (ex) {
+    if (ex.code !== 'TriggerNotFound') {
+      throw ex;
+    }
+  }
+
+  const params = {
+    triggerType: triggerTypeMapping[triggerType],
+    triggerConfig: getTriggerConfig(triggerType, triggerProperties)
+  };
+
+  debug('serviceName is %s, functionName is %s, trigger params is %j', serviceName, functionName, params);
+
+  let invocationRoleArn = triggerProperties.InvocationRole;
+
+  if (!invocationRoleArn) {
+    const invocationRole = await makeInvocationRole(serviceName, functionName, triggerType);
+
+    if (invocationRole) {
+      invocationRoleArn = invocationRole.Arn;
+    }
+  }
+
+  if (invocationRoleArn) {
+    Object.assign(params, {
+      'invocationRole': invocationRoleArn
+    });
+  }
+
+  const sourceArn = await getSourceArn(triggerType, triggerProperties);
+  if (sourceArn) {
+    Object.assign(params, {
+      'sourceArn': sourceArn
+    });
+  }
+
+  if (!trigger) {
+    params.triggerName = triggerName;
+    trigger = await fc.createTrigger(serviceName, functionName, params);
+  } else {
+    if (triggerType === 'TableStore' || triggerType === 'MNSTopic') {
+      // no triggerConfig, so no updateTrigger, first delete, then create
+      // await fc.deleteTrigger(serviceName, functionName, triggerName);
+      // params.triggerName = triggerName;
+      // trigger = await fc.createTrigger(serviceName, functionName, params);
+      console.log(red(`\t\tWarning: TableStore and MNSTopic Trigger cann't update`));
+      return;
+    }
+    trigger = await fc.updateTrigger(serviceName, functionName, triggerName, params);
+  }
+
+  return trigger;
+}
+
+module.exports = {
+  getTriggerNameList,
+  getTriggerConfig,
+  makeTrigger,
+  makeInvocationRole
+};
\ No newline at end of file
diff --git a/lib/validate/schema/service-resource.js b/lib/validate/schema/service-resource.js
index c57d8ee..10aa5b5 100644
--- a/lib/validate/schema/service-resource.js
+++ b/lib/validate/schema/service-resource.js
@@ -22,9 +22,9 @@ const serviceResourceSchema = {
           oneOf: [
             { 'type': 'string' },
             { '$ref': '/Resources/Service/Role' },
-            { 
-              'type': 'array', 
-              'items': { 
+            {
+              'type': 'array',
+              'items': {
                 oneOf: [
                   { 'type': 'string' },
                   { '$ref': '/Resources/Service/Role' }
@@ -37,47 +37,58 @@ const serviceResourceSchema = {
           'type': 'boolean'
         },
         'VpcConfig': {
-          'type': 'object',
-          'properties': {
-            'VpcId': {'type': 'string'},
-            'VSwitchIds': {
-              'type': 'array', 
-              'items': {'type': 'string'}
-            },
-            'SecurityGroupId': {'type': 'string'}
-          },
-          'required': ['VpcId', 'VSwitchIds', 'SecurityGroupId'],
-          'additionalProperties': false
+          oneOf: [
+            { 'type': 'string' },
+            {
+              'type': 'object',
+              'properties': {
+                'VpcId': { 'type': 'string' },
+                'VSwitchIds': {
+                  'type': 'array',
+                  'items': { 'type': 'string' }
+                },
+                'SecurityGroupId': { 'type': 'string' }
+              },
+              'required': ['VpcId', 'VSwitchIds', 'SecurityGroupId'],
+              'additionalProperties': false
+            }
+          ]
         },
         'LogConfig': {
           'type': 'object',
           'properties': {
-            'Project': {'type': 'string'},
-            'Logstore': {'type': 'string'}
+            'Project': { 'type': 'string' },
+            'Logstore': { 'type': 'string' }
           },
           'required': ['Project', 'Logstore'],
           'additionalProperties': false
         },
         'NasConfig': {
-          'type': 'object',
-          'properties': {
-            'UserId': {'type': 'integer'},
-            'GroupId': {'type': 'integer'},
-            'MountPoints': {
-              'type': 'array',
-              'items': {
-                'type': 'object',
-                'properties': {
-                  'ServerAddr': {'type': 'string'},
-                  'MountDir': {'type': 'string'}
-                },
-                'required': ['ServerAddr', 'MountDir'],
-                'additionalProperties': false
-              }
+          oneOf: [
+            { 'type': 'string' },
+            {
+              'type': 'object',
+              'properties': {
+                'UserId': { 'type': 'integer' },
+                'GroupId': { 'type': 'integer' },
+                'MountPoints': {
+                  'type': 'array',
+                  'items': {
+                    'type': 'object',
+                    'properties': {
+                      'ServerAddr': { 'type': 'string' },
+                      'MountDir': { 'type': 'string' }
+                    },
+                    'required': ['ServerAddr', 'MountDir'],
+                    'additionalProperties': false
+                  }
+                }
+              },
+              'required': ['UserId', 'GroupId', 'MountPoints'],
+              'additionalProperties': false
             }
-          },
-          'required': ['UserId', 'GroupId', 'MountPoints'],
-          'additionalProperties': false
+          ]
+
         }
       },
       'additionalProperties': false
diff --git a/lib/vpc.js b/lib/vpc.js
new file mode 100644
index 0000000..c9937f6
--- /dev/null
+++ b/lib/vpc.js
@@ -0,0 +1,197 @@
+'use strict';
+
+const getProfile = require('./profile').getProfile;
+const _ = require('lodash');
+const { getVpcPopClient, getEcsPopClient } = require('./client');
+
+const vswitch = require('./vswitch');
+const securityGroup = require('./security-group');
+const debug = require('debug')('fun:nas');
+const { green } = require('colors');
+const { sleep } = require('./time');
+
+var requestOption = {
+  method: 'POST'
+};
+
+const defaultVSwitchName = 'fc-fun-vswitch-1';
+const defaultSecurityGroupName = 'fc-fun-sg-1';
+
+async function findVpc(vpcClient, region, vpcName) {
+
+  const pageSize = 50; // max value is 50. see https://help.aliyun.com/document_detail/104577.html
+  let requestPageNumber = 0;
+  let totalCount;
+  let pageNumber;
+
+  let vpc;
+
+  do {
+    var params = {
+      'RegionId': region,
+      'PageSize': pageSize,
+      'PageNumber': ++requestPageNumber
+    };
+
+    const rs = await vpcClient.request('DescribeVpcs', params, requestOption);
+
+    totalCount = rs.TotalCount;
+    pageNumber = rs.PageNumber;
+    const vpcs = rs.Vpcs.Vpc;
+  
+    debug('find vpc rs: %s', rs);
+
+    vpc = _.find(vpcs, { VpcName: vpcName });
+  
+    debug('find default vpc: %s', vpc);
+
+  } while (!vpc && totalCount && pageNumber && pageNumber * pageSize < totalCount);
+
+  return vpc;
+}
+
+async function createVpc(vpcClient, region, vpcName) {
+  var createParams = {
+    'RegionId': region,
+    'CidrBlock': '10.0.0.0/8',
+    'EnableIpv6': false,
+    'VpcName': vpcName,
+    'Description': 'default vpc created by fc fun'
+  };
+
+  const createRs = await vpcClient.request('CreateVpc', createParams, requestOption);
+
+  const vpcId = createRs.VpcId;
+
+  debug('create vpc rs is: %j', createRs);
+
+  await waitVpcUntilAvaliable(vpcClient, region, vpcId);
+
+  return vpcId;
+}
+
+async function waitVpcUntilAvaliable(vpcClient, region, vpcId) {
+
+  let count = 0;
+  let status;
+
+  do {
+    count++;
+
+    var params = {
+      'RegionId': region,
+      'VpcId': vpcId
+    };
+    
+    await sleep(800);
+
+    const rs = await vpcClient.request('DescribeVpcs', params, requestOption);
+    
+    status = rs.Vpcs.Vpc[0].Status;
+    
+    debug('vpc status is: ' + status);
+
+    console.log(`\t\tvpc already created, waiting for status to be 'Available', now is ${status}`);
+    
+  } while (count < 15 && status !== 'Available');
+
+  if (status !== 'Available') { throw new Error(`Timeout while waiting for vpc ${vpcId} status to be 'Available'`); }
+
+}
+
+async function createDefaultVSwitchIfNotExist(vpcClient, region, vpcId, vswitchIds) {
+  let vswitchId = await vswitch.findVswitchExistByName(vpcClient, region, vswitchIds, defaultVSwitchName);
+
+  if (!vswitchId) { // create vswitch
+    console.log('\t\tcould not find default vswitch, ready to generate one');
+    vswitchId = await vswitch.createDefaultVSwitch(vpcClient, region, vpcId, defaultVSwitchName);
+    console.log(green('\t\tdefault vswitch has been generated, vswitchId is: ' + vswitchId));
+  } else {
+    console.log(green('\t\tvswitch already generated, vswitchId is: ' + vswitchId));
+  }
+
+  return vswitchId;
+}
+
+async function createDefaultSecurityGroupIfNotExist(ecsClient, region, vpcId) {
+  // check fun default security group exist?
+  let defaultSecurityGroup = await securityGroup.describeSecurityGroups(ecsClient, region, vpcId, defaultSecurityGroupName);
+  debug('default security grpup: %j', defaultSecurityGroup);
+
+  // create security group
+  if (_.isEmpty(defaultSecurityGroup)) {
+
+    console.log('\t\tcould not find default security group, ready to generate one');
+
+    const securityGroupId = await securityGroup.createSecurityGroup(ecsClient, region, vpcId, defaultSecurityGroupName);
+
+    console.log('\t\t\tsetting default security group rules');
+
+    await securityGroup.authDefaultSecurityGroupRules(ecsClient, region, securityGroupId);
+
+    console.log(green('\t\t\tdefault security group rules has been generated'));
+
+    console.log(green('\t\tdefault security group has been generated, security group is: ' + securityGroupId));
+
+    return securityGroupId;
+  }  
+  const securityGroupId = defaultSecurityGroup[0].SecurityGroupId;
+
+  console.log(green('\t\tsecurity group already generated, security group is: ' + securityGroupId));
+
+  return securityGroupId;
+   
+}
+
+async function createDefaultVpcIfNotExist() {
+
+  const profile = await getProfile();
+  const region = profile.defaultRegion;
+
+  const vpcClient = await getVpcPopClient();
+
+  const ecsClient = await getEcsPopClient();
+
+  const defaultVpcName = 'fc-fun-vpc';
+
+  let vswitchIds;
+  let vpcId;
+
+  const funDefaultVpc = await findVpc(vpcClient, region, defaultVpcName);
+
+  if (funDefaultVpc) { // update
+    vswitchIds = funDefaultVpc.VSwitchIds.VSwitchId;
+    vpcId = funDefaultVpc.VpcId;
+
+    console.log(green('\t\tvpc already generated, vpcId is: ' + vpcId));
+  } else { // create
+    console.log('\t\tcould not find default vpc, ready to generate one');
+
+    vpcId = await createVpc(vpcClient, region, defaultVpcName);
+
+    console.log(green('\t\tdefault vpc has been generated, vpcId is: ' + vpcId));
+
+  }
+
+  debug('vpcId is %s', vpcId);
+
+  const vswitchId = await createDefaultVSwitchIfNotExist(vpcClient, region, vpcId, vswitchIds);
+
+  vswitchIds = [ vswitchId ];
+
+  // create security
+
+  const securityGroupId = await createDefaultSecurityGroupIfNotExist(ecsClient, region, vpcId);
+
+  return {
+    vpcId,
+    vswitchIds,
+    securityGroupId
+  };
+}
+
+module.exports = {
+  createDefaultVpcIfNotExist,
+  findVpc,
+  createVpc
+};
\ No newline at end of file
diff --git a/lib/vswitch.js b/lib/vswitch.js
new file mode 100644
index 0000000..84d1864
--- /dev/null
+++ b/lib/vswitch.js
@@ -0,0 +1,144 @@
+'use strict';
+
+const _ = require('lodash');
+
+const { getFcClient } = require('./client');
+
+const debug = require('debug')('fun:nas');
+
+const { red } = require('colors');
+const { getProfile } = require('./profile');
+
+var requestOption = {
+  method: 'POST'
+};
+
+async function createDefaultVSwitch(vpcClient, region, vpcId, vswitchName) {
+  const vswitchZoneId = await selectAllowedVSwitchZone(vpcClient, region);
+
+  // 创建 vswitch
+  const vswitchId = await createVSwitch(vpcClient, {
+    region,
+    vpcId,
+    zoneId: vswitchZoneId,
+    vswitchName: vswitchName
+  });
+
+  return vswitchId;
+}
+
+async function getVSwitchName(vpcClient, region, vswitchId) {
+  var params = {
+    'RegionId': region,
+    'VSwitchId': vswitchId
+  };
+
+
+  const describeRs = await vpcClient.request('DescribeVSwitchAttributes', params, requestOption);
+
+  const vswitchName = (describeRs || {}).VSwitchName;
+
+  return vswitchName;
+}
+
+async function findVswitchExistByName(vpcClient, region, vswitchIds, searchVSwtichName) {
+
+  if (!_.isEmpty(vswitchIds)) {
+    for (let vswitchId of vswitchIds) {
+
+      const vswitchName = await getVSwitchName(vpcClient, region, vswitchId);
+
+      if (_.isEqual(searchVSwtichName, vswitchName)) {
+        debug('found default vswitchId: ' + vswitchId);
+
+        return vswitchId;
+      }
+    }
+  }
+
+  debug('could not find %s from %j for region %s', searchVSwtichName, vswitchIds, region);
+
+  return null;
+}
+
+async function createVSwitch(vpcClient, {
+  region,
+  vpcId,
+  zoneId,
+  vswitchName
+}) {
+  var params = {
+    'RegionId': region,
+    'VpcId': vpcId,
+    'ZoneId': zoneId,
+    'CidrBlock': '10.20.0.0/16',
+    'VSwitchName': vswitchName,
+    'Description': 'default vswitch created by fc fun'
+  };
+
+  debug('createVSwitch params is %j', params);
+
+  const createRs = await vpcClient.request('CreateVSwitch', params, requestOption);
+
+  return createRs.VSwitchId;
+}
+
+async function selectVSwitchZoneId(fcAllowedZones, zones) {
+
+  const allowedZones = _.filter(zones, z => _.includes(fcAllowedZones, z.ZoneId));
+
+  const sortedZones = _.sortBy(allowedZones, ['ZoneId']);
+  
+  return (_.head(sortedZones) || {}).ZoneId;
+}
+
+async function getFcAllowedZones() {
+  const fc = await getFcClient();
+
+  const fcRs = await fc.getAccountSettings();
+
+  const fcAllowedZones = fcRs.data.availableAZs;
+
+  debug('fc allowed zones: %j', fcAllowedZones);
+  
+  if (_.isEqual(fcAllowedZones, [''])) {
+
+    const profile = await getProfile();
+
+    throw new Error(red(`No fc vswitch zones allowed, you may need login to fc console to apply for VPC feature: https://fc.console.aliyun.com/overview/${profile.defaultRegion}`));
+  }
+
+  return fcAllowedZones;
+}
+
+async function selectAllowedVSwitchZone(vpcClient, region) {
+  const zones = await describeZones(vpcClient, region);
+
+  const fcAllowedZones = await getFcAllowedZones();
+
+  const usedZoneId = await selectVSwitchZoneId(fcAllowedZones, zones);
+
+  if (!usedZoneId) {
+    throw new Error('no availiable zone for vswitch');
+  }
+
+  debug('select allowed switch zone: ', usedZoneId);
+
+  return usedZoneId;
+}
+
+async function describeZones(vpcClient, region) {
+  const params = {
+    'RegionId': region
+  };
+
+  const zones = await vpcClient.request('DescribeZones', params, requestOption);
+  return zones.Zones.Zone;
+}
+
+module.exports = {
+  findVswitchExistByName,
+  selectVSwitchZoneId,
+  createVSwitch,
+  createDefaultVSwitch
+};
\ No newline at end of file


Test patch:
diff --git a/test/deploy/deploy-by-tpl.test.js b/test/deploy/deploy-by-tpl.test.js
index 5f6133f..8d3f2ae 100644
--- a/test/deploy/deploy-by-tpl.test.js
+++ b/test/deploy/deploy-by-tpl.test.js
@@ -1,7 +1,6 @@
 'use strict';
 
 const proxyquire = require('proxyquire');
-const expect = require('expect.js');
 const sinon = require('sinon');
 const sandbox = sinon.createSandbox();
 const assert = sinon.assert;
@@ -10,26 +9,27 @@ const deploySupport = require('../../lib/deploy/deploy-support');
 const ram = require('../../lib/ram');
 const { setProcess } = require('../test-utils');
 const { red } = require('colors');
-const defaultVpcConfig = {
-  securityGroupId: '',
-  vSwitchIds: [],
-  vpcId: ''
-};
-const defaultNasConfig = {
-  UserId: -1,
-  GroupId: -1,
-  MountPoints: []
-};
+const trigger = require('../../lib/trigger');
+const fc = require('../../lib/fc');
 
 describe('deploy service role ', () => {
   let restoreProcess;
 
   beforeEach(() => {
+
     Object.keys(deploySupport).forEach(m => {
+      sandbox.stub(deploySupport, m).resolves({});
+    });
+
+    Object.keys(fc).forEach(m => {
+      sandbox.stub(fc, m).resolves({});
+    });
+
+    Object.keys(trigger).forEach(m => {
       if (m === 'getTriggerNameList') {
-        sandbox.stub(deploySupport, m).resolves([]);
+        sandbox.stub(trigger, m).resolves([]);
       } else {
-        sandbox.stub(deploySupport, m).resolves({});
+        sandbox.stub(trigger, m).resolves({});
       }
     });
 
@@ -62,7 +62,9 @@ describe('deploy service role ', () => {
   async function deploy(example) {
     await proxyquire('../../lib/deploy/deploy-by-tpl', {
       './deploy-support': deploySupport,
-      '../ram': ram
+      '../ram': ram,
+      '../fc': fc,
+      '../trigger': trigger
     }).deploy(path.join('./examples', example, 'template.yml'));
   }
 
@@ -102,11 +104,20 @@ describe('deploy', () => {
   beforeEach(() => {
 
     sandbox.stub(console, 'warn');
+
     Object.keys(deploySupport).forEach(m => {
+      sandbox.stub(deploySupport, m).resolves({});
+    });
+
+    Object.keys(fc).forEach(m => {
+      sandbox.stub(fc, m).resolves({});
+    });
+
+    Object.keys(trigger).forEach(m => {
       if (m === 'getTriggerNameList') {
-        sandbox.stub(deploySupport, m).resolves(['my_trigger_name']);
+        sandbox.stub(trigger, m).resolves(['my_trigger_name']);
       } else {
-        sandbox.stub(deploySupport, m).resolves({});
+        sandbox.stub(trigger, m).resolves({});
       }
     });
 
@@ -139,24 +150,26 @@ describe('deploy', () => {
   async function deploy(example) {
     await proxyquire('../../lib/deploy/deploy-by-tpl', {
       './deploy-support': deploySupport,
-      '../ram': ram
+      '../ram': ram,
+      '../fc': fc,
+      '../trigger': trigger
     }).deploy(path.join('./examples', example, 'template.yml'));
   }
 
   it('deploy datahub', async () => {
     await deploy('datahub');
 
-    assert.calledWith(deploySupport.makeService, {
+    assert.calledWith(fc.makeService, {
       description: undefined,
       internetAccess: null,
       logConfig: {},
       role: '',
       serviceName: 'MyService',
-      vpcConfig: defaultVpcConfig,
-      nasConfig: defaultNasConfig
+      vpcConfig: undefined,
+      nasConfig: undefined
     });
 
-    assert.calledWith(deploySupport.makeFunction,
+    assert.calledWith(fc.makeFunction,
       path.join(process.cwd(), 'examples', 'datahub'), {
         codeUri: 'datahub.js',
         description: undefined,
@@ -175,16 +188,16 @@ describe('deploy', () => {
   it('deploy helloworld', async () => {
     await deploy('helloworld');
 
-    assert.calledWith(deploySupport.makeService, {
+    assert.calledWith(fc.makeService, {
       description: 'fc test',
       internetAccess: null,
       logConfig: {},
       role: '',
       serviceName: 'fc',
-      vpcConfig: defaultVpcConfig,
-      nasConfig: defaultNasConfig
+      vpcConfig: undefined,
+      nasConfig: undefined
     });
-    assert.calledWith(deploySupport.makeFunction,
+    assert.calledWith(fc.makeFunction,
       path.join(process.cwd(), 'examples', 'helloworld'), {
         codeUri: './',
         description: undefined,
@@ -203,16 +216,16 @@ describe('deploy', () => {
   it('deploy java', async () => {
     await deploy('java');
 
-    assert.calledWith(deploySupport.makeService, {
+    assert.calledWith(fc.makeService, {
       description: 'java demo',
       internetAccess: null,
       logConfig: {},
       role: '',
       serviceName: 'java',
-      vpcConfig: defaultVpcConfig,
-      nasConfig: defaultNasConfig
+      vpcConfig: undefined,
+      nasConfig: undefined
     });
-    assert.calledWith(deploySupport.makeFunction,
+    assert.calledWith(fc.makeFunction,
       path.join(process.cwd(), 'examples', 'java'), {
         codeUri: './demo.jar',
         description: 'Hello world!',
@@ -232,7 +245,7 @@ describe('deploy', () => {
   it('deploy nas', async () => {
     await deploy('nas');
 
-    assert.calledWith(deploySupport.makeService, {
+    assert.calledWith(fc.makeService, {
       description: 'fc nas test',
       internetAccess: null,
       logConfig: {},
@@ -253,7 +266,7 @@ describe('deploy', () => {
       }
     });
 
-    assert.calledWith(deploySupport.makeFunction.firstCall,
+    assert.calledWith(fc.makeFunction.firstCall,
       path.join(process.cwd(), 'examples', 'nas'), {
         codeUri: './read.js',
         description: undefined,
@@ -268,7 +281,7 @@ describe('deploy', () => {
         environmentVariables: { ROOT_DIR: '/mnt/test' }
       });
 
-    assert.calledWith(deploySupport.makeFunction.secondCall,
+    assert.calledWith(fc.makeFunction.secondCall,
       path.join(process.cwd(), 'examples', 'nas'), {
         codeUri: './write.py',
         description: undefined,
@@ -287,17 +300,17 @@ describe('deploy', () => {
   it('deploy openid_connect', async () => {
     await deploy('openid_connect');
 
-    assert.calledWith(deploySupport.makeService, {
+    assert.calledWith(fc.makeService, {
       description: 'fc test',
       internetAccess: null,
       logConfig: {},
       role: '',
       serviceName: 'fc',
-      vpcConfig: defaultVpcConfig,
-      nasConfig: defaultNasConfig
+      vpcConfig: undefined,
+      nasConfig: undefined
     });
 
-    assert.calledWith(deploySupport.makeFunction,
+    assert.calledWith(fc.makeFunction,
       path.join(process.cwd(), 'examples', 'openid_connect'), {
         codeUri: './',
         description: 'Hello world!',
@@ -349,16 +362,16 @@ describe('deploy', () => {
   it('deploy tablestore-trigger', async () => {
     await deploy('tablestore-trigger');
 
-    assert.calledWith(deploySupport.makeService, {
+    assert.calledWith(fc.makeService, {
       description: 'Stream trigger for TableStore',
       internetAccess: null,
       logConfig: {},
       role: '',
       serviceName: 'test-tableStore-service',
-      vpcConfig: defaultVpcConfig,
-      nasConfig: defaultNasConfig
+      vpcConfig: undefined,
+      nasConfig: undefined
     });
-    assert.calledWith(deploySupport.makeFunction,
+    assert.calledWith(fc.makeFunction,
       path.join(process.cwd(), 'examples', 'tablestore-trigger'), {
         codeUri: './',
         handler: 'main.index',
@@ -372,7 +385,7 @@ describe('deploy', () => {
         initializationTimeout: undefined,
         environmentVariables: undefined
       });
-    assert.calledWith(deploySupport.makeTrigger, {
+    assert.calledWith(trigger.makeTrigger, {
       serviceName: 'test-tableStore-service',
       functionName: 'fun-ots-func',
       triggerName: 'my-tablestore-trigger',
@@ -387,16 +400,16 @@ describe('deploy', () => {
   it('deploy sls_demo', async () => {
     await deploy('sls_demo');
 
-    assert.calledWith(deploySupport.makeService, {
+    assert.calledWith(fc.makeService, {
       description: 'sls test',
       internetAccess: null,
       logConfig: {},
       role: '',
       serviceName: 'log-compute',
-      vpcConfig: defaultVpcConfig,
-      nasConfig: defaultNasConfig
+      vpcConfig: undefined,
+      nasConfig: undefined
     });
-    assert.calledWith(deploySupport.makeFunction,
+    assert.calledWith(fc.makeFunction,
       path.join(process.cwd(), 'examples', 'sls_demo'), {
         codeUri: './',
         handler: 'index.handler',
@@ -410,7 +423,7 @@ describe('deploy', () => {
         initializationTimeout: undefined,
         environmentVariables: undefined
       });
-    assert.calledWith(deploySupport.makeTrigger, {
+    assert.calledWith(trigger.makeTrigger, {
       serviceName: 'log-compute',
       functionName: 'log-compute',
       triggerName: 'log-stream',
@@ -427,16 +440,16 @@ describe('deploy', () => {
   it('deploy rds-trigger', async () => {
     await deploy('rds-trigger');
 
-    assert.calledWith(deploySupport.makeService, {
+    assert.calledWith(fc.makeService, {
       description: 'rds trigger test',
       internetAccess: null,
       logConfig: {},
       role: '',
       serviceName: 'rds-service',
-      vpcConfig: defaultVpcConfig,
-      nasConfig: defaultNasConfig
+      vpcConfig: undefined,
+      nasConfig: undefined
     });
-    assert.calledWith(deploySupport.makeFunction,
+    assert.calledWith(fc.makeFunction,
       path.join(process.cwd(), 'examples', 'rds-trigger'), {
         codeUri: './',
         handler: 'index.handler',
@@ -450,7 +463,7 @@ describe('deploy', () => {
         initializationTimeout: undefined,
         environmentVariables: undefined
       });
-    assert.calledWith(deploySupport.makeTrigger, {
+    assert.calledWith(trigger.makeTrigger, {
       serviceName: 'rds-service',
       functionName: 'rds-function',
       triggerName: 'my-rds-trigger',
@@ -468,16 +481,16 @@ describe('deploy', () => {
   it('deploy oss-trigger', async () => {
     await deploy('oss-trigger');
 
-    assert.calledWith(deploySupport.makeService, {
+    assert.calledWith(fc.makeService, {
       description: 'oss trigger test',
       internetAccess: null,
       logConfig: {},
       role: '',
       serviceName: 'oss-test-service',
-      vpcConfig: defaultVpcConfig,
-      nasConfig: defaultNasConfig
+      vpcConfig: undefined,
+      nasConfig: undefined
     });
-    assert.calledWith(deploySupport.makeFunction,
+    assert.calledWith(fc.makeFunction,
       path.join(process.cwd(), 'examples', 'oss-trigger'), {
         codeUri: './',
         handler: 'index.handler',
@@ -491,7 +504,7 @@ describe('deploy', () => {
         initializationTimeout: undefined,
         environmentVariables: undefined
       });
-    assert.calledWith(deploySupport.makeTrigger, {
+    assert.calledWith(trigger.makeTrigger, {
       serviceName: 'oss-test-service',
       functionName: 'oss-test-function',
       triggerName: 'oss-trigger-name',
@@ -502,7 +515,7 @@ describe('deploy', () => {
         Filter: { Key: { Prefix: 'source/', Suffix: '.png' }}
       }
     });
-    assert.calledWith(deploySupport.getTriggerNameList, {
+    assert.calledWith(trigger.getTriggerNameList, {
       serviceName: 'oss-test-service',
       functionName: 'oss-test-function'
     });
@@ -511,16 +524,16 @@ describe('deploy', () => {
   it('deploy cdn-trigger', async () => {
     await deploy('cdn-trigger');
 
-    assert.calledWith(deploySupport.makeService, {
+    assert.calledWith(fc.makeService, {
       description: 'cdn trigger test',
       internetAccess: null,
       logConfig: {},
       role: '',
       serviceName: 'cdn-test-service',
-      vpcConfig: defaultVpcConfig,
-      nasConfig: defaultNasConfig
+      vpcConfig: undefined,
+      nasConfig: undefined
     });
-    assert.calledWith(deploySupport.makeFunction,
+    assert.calledWith(fc.makeFunction,
       path.join(process.cwd(), 'examples', 'cdn-trigger'), {
         codeUri: './',
         handler: 'index.handler',
@@ -534,7 +547,7 @@ describe('deploy', () => {
         initializationTimeout: undefined,
         environmentVariables: undefined
       });
-    assert.calledWith(deploySupport.makeTrigger, {
+    assert.calledWith(trigger.makeTrigger, {
       serviceName: 'cdn-test-service',
       functionName: 'cdn-test-function',
       triggerName: 'cdn-trigger-name',
@@ -549,7 +562,7 @@ describe('deploy', () => {
           ]
         }}
     });
-    assert.calledWith(deploySupport.getTriggerNameList, {
+    assert.calledWith(trigger.getTriggerNameList, {
       serviceName: 'cdn-test-service',
       functionName: 'cdn-test-function'
     });
@@ -558,16 +571,16 @@ describe('deploy', () => {
   it('deploy mnsTopic-trigger', async () => {
     await deploy('mnsTopic-trigger');
 
-    assert.calledWith(deploySupport.makeService, {
+    assert.calledWith(fc.makeService, {
       description: 'MnsTopic trigger test',
       internetAccess: null,
       logConfig: {},
       role: '',
       serviceName: 'mnsTopic-service',
-      vpcConfig: defaultVpcConfig,
-      nasConfig: defaultNasConfig
+      vpcConfig: undefined,
+      nasConfig: undefined
     });
-    assert.calledWith(deploySupport.makeFunction,
+    assert.calledWith(fc.makeFunction,
       path.join(process.cwd(), 'examples', 'mnsTopic-trigger'), {
         codeUri: './',
         handler: 'index.handler',
@@ -581,7 +594,7 @@ describe('deploy', () => {
         initializationTimeout: undefined,
         environmentVariables: undefined
       });
-    assert.calledWith(deploySupport.makeTrigger, {
+    assert.calledWith(trigger.makeTrigger, {
       serviceName: 'mnsTopic-service',
       functionName: 'mnsTopic-function',
       triggerName: 'my-mns-trigger',
@@ -598,16 +611,16 @@ describe('deploy', () => {
   it('deploy python', async () => {
     await deploy('python');
 
-    assert.calledWith(deploySupport.makeService, {
+    assert.calledWith(fc.makeService, {
       description: 'python demo',
       internetAccess: null,
       logConfig: {},
       role: '',
       serviceName: 'pythondemo',
-      vpcConfig: defaultVpcConfig,
-      nasConfig: defaultNasConfig
+      vpcConfig: undefined,
+      nasConfig: undefined
     });
-    assert.calledWith(deploySupport.makeFunction,
+    assert.calledWith(fc.makeFunction,
       path.join(process.cwd(), 'examples', 'python'), {
         codeUri: './',
         description: 'Hello world with python!',
@@ -650,17 +663,17 @@ describe('deploy', () => {
   it('deploy segment', async () => {
     await deploy('segment');
 
-    assert.calledWith(deploySupport.makeService, {
+    assert.calledWith(fc.makeService, {
       description: 'Module as a service',
       internetAccess: null,
       logConfig: {},
       role: '',
       serviceName: 'maas',
-      vpcConfig: defaultVpcConfig,
-      nasConfig: defaultNasConfig
+      vpcConfig: undefined,
+      nasConfig: undefined
     });
 
-    assert.calledWith(deploySupport.makeFunction,
+    assert.calledWith(fc.makeFunction,
       path.join(process.cwd(), 'examples', 'segment'), {
         codeUri: './',
         description: 'do segment',
@@ -701,16 +714,16 @@ describe('deploy', () => {
   it('deploy timer', async () => {
     await deploy('timer');
 
-    assert.calledWith(deploySupport.makeService, {
+    assert.calledWith(fc.makeService, {
       description: undefined,
       internetAccess: null,
       logConfig: {},
       role: '',
       serviceName: 'MyService',
-      vpcConfig: defaultVpcConfig,
-      nasConfig: defaultNasConfig
+      vpcConfig: undefined,
+      nasConfig: undefined
     });
-    assert.calledWith(deploySupport.makeFunction,
+    assert.calledWith(fc.makeFunction,
       path.join(process.cwd(), 'examples', 'timer'), {
         codeUri: './',
         description: 'send hangzhou weather',
@@ -724,7 +737,7 @@ describe('deploy', () => {
         initializationTimeout: undefined,
         environmentVariables: undefined
       });
-    assert.calledWith(deploySupport.makeTrigger, {
+    assert.calledWith(trigger.makeTrigger, {
       functionName: 'MyFunction',
       serviceName: 'MyService',
       triggerName: 'TmTrigger',
@@ -739,16 +752,16 @@ describe('deploy', () => {
   it('deploy wechat', async () => {
     await deploy('wechat');
 
-    assert.calledWith(deploySupport.makeService, {
+    assert.calledWith(fc.makeService, {
       description: 'wechat demo',
       internetAccess: null,
       logConfig: {},
       role: '',
       serviceName: 'wechat',
-      vpcConfig: defaultVpcConfig,
-      nasConfig: defaultNasConfig
+      vpcConfig: undefined,
+      nasConfig: undefined
     });
-    assert.calledWith(deploySupport.makeFunction.firstCall,
+    assert.calledWith(fc.makeFunction.firstCall,
       path.join(process.cwd(), 'examples', 'wechat'), {
         codeUri: './',
         description: 'Wechat get handler',
@@ -792,7 +805,7 @@ describe('deploy', () => {
       serviceTimeout: 3000
     });
 
-    assert.calledWith(deploySupport.makeFunction.secondCall,
+    assert.calledWith(fc.makeFunction.secondCall,
       path.join(process.cwd(), 'examples', 'wechat'), {
         codeUri: './',
         description: 'Wechat post handler',
@@ -858,16 +871,16 @@ describe('deploy', () => {
   it('deploy initializer', async () => {
     await deploy('initializer');
 
-    assert.calledWith(deploySupport.makeService, {
+    assert.calledWith(fc.makeService, {
       description: 'initializer demo',
       internetAccess: null,
       logConfig: {},
       role: '',
       serviceName: 'initializerdemo',
-      vpcConfig: defaultVpcConfig,
-      nasConfig: defaultNasConfig
+      vpcConfig: undefined,
+      nasConfig: undefined
     });
-    assert.calledWith(deploySupport.makeFunction,
+    assert.calledWith(fc.makeFunction,
       path.join(process.cwd(), 'examples', 'initializer'), {
         codeUri: './',
         description: 'Hello world with initializer!',
@@ -911,17 +924,17 @@ describe('deploy', () => {
   it('deploy service role', async () => {
     await deploy('service_role');
 
-    assert.calledWith(deploySupport.makeService, {
+    assert.calledWith(fc.makeService, {
       description: 'local invoke demo',
       internetAccess: null,
       logConfig: {},
       role: 'acs:ram::123:role/aliyunfcgeneratedrole-fc',
       serviceName: 'localdemo',
-      vpcConfig: defaultVpcConfig,
-      nasConfig: defaultNasConfig
+      vpcConfig: undefined,
+      nasConfig: undefined
     });
 
-    assert.calledWith(deploySupport.makeFunction,
+    assert.calledWith(fc.makeFunction,
       path.join(process.cwd(), 'examples', 'service_role'), {
         codeUri: 'nodejs6',
         description: 'Hello world with nodejs6!',
@@ -936,7 +949,7 @@ describe('deploy', () => {
         environmentVariables: { StringTypeValue1: 123, StringTypeValue2: 'test' }
       });
     // add test => no events on local but have onLine 
-    assert.notCalled(deploySupport.makeTrigger);
+    assert.notCalled(trigger.makeTrigger);
     assert.calledOnce(console.warn);
     assert.calledWith(console.warn, red(`\t\tThe trigger my_trigger_name you configured in fc console does not match the local configuration.\n\t\tFun will not modify this trigger. You can remove this trigger manually through fc console if necessary`));
   });
@@ -949,6 +962,7 @@ describe('custom domain', () => {
     Object.keys(deploySupport).forEach(m => {
       sandbox.stub(deploySupport, m);
     });
+
     restoreProcess = setProcess({
       ACCOUNT_ID: 'ACCOUNT_ID',
       DEFAULT_REGION: 'cn-shanghai',
@@ -956,6 +970,7 @@ describe('custom domain', () => {
       ACCESS_KEY_SECRET: 'ACCESS_KEY_SECRET'
     });
   });
+
   afterEach(() => {
     sandbox.restore();
     restoreProcess();
@@ -1088,29 +1103,3 @@ describe('custom domain', () => {
     });
   });
 });
-
-describe('cdn domain capitalization', () => {
-
-  it('Domain->domain', async() => {
-    var capitalConfig = deploySupport.getTriggerConfig('CDN', {
-      'EventName': 'CachedObjectsRefreshed',
-      'EventVersion': '1.0.0',
-      'Notes': 'cdn events trigger test',
-      'Filter': {
-        'Domain': [
-          'cdn-trigger.sunfeiyu.top'
-        ]
-      }
-    });
-    expect(capitalConfig).to.eql({
-      'eventName': 'CachedObjectsRefreshed',
-      'eventVersion': '1.0.0',
-      'notes': 'cdn events trigger test',
-      'filter': {
-        'domain': [
-          'cdn-trigger.sunfeiyu.top'
-        ]
-      }
-    });
-  });
-});
diff --git a/test/deploy/deploy-support.test.js b/test/deploy/deploy-support.test.js
index f6894e1..10ba7aa 100644
--- a/test/deploy/deploy-support.test.js
+++ b/test/deploy/deploy-support.test.js
@@ -4,18 +4,7 @@ var nock = require('nock');
 
 let deploySupport = require('../../lib/deploy/deploy-support');
 
-const ram = require('../../lib/ram');
 const { setProcess } = require('../test-utils');
-const proxyquire = require('proxyquire');
-const FC = require('@alicloud/fc2');
-const sinon = require('sinon');
-const sandbox = sinon.createSandbox();
-const assert = sinon.assert;
-const zip = require('../../lib/package/zip');
-const expect = require('expect.js');
-const path = require('path');
-const util = require('util');
-
 
 describe('make', () => {
 
@@ -309,465 +298,4 @@ describe('make', () => {
   });
 
 });
-describe('Incorrect environmental variables', ()=> {
-  let restoreProcess;
-
-  beforeEach(async () => {
-    sandbox.stub(FC.prototype, 'getFunction').resolves({});
-    sandbox.stub(FC.prototype, 'updateFunction').resolves({});
-    sandbox.stub(zip, 'pack').resolves({});
-
-    deploySupport = await proxyquire('../../lib/deploy/deploy-support', {
-      '../package/zip': zip,
-      '@alicloud/fc2': FC
-    });
-
-    restoreProcess = setProcess({
-      ACCOUNT_ID: 'ACCOUNT_ID',
-      ACCESS_KEY_ID: 'ACCESS_KEY_ID',
-      ACCESS_KEY_SECRET: 'ACCESS_KEY_SECRET',
-      DEFAULT_REGION: 'cn-shanghai'
-    });
-  });
-
-  afterEach(() => {
-    sandbox.restore();
-    restoreProcess();
-  });
-
-  it('should cast env value to String', async ()=> {
-    await deploySupport.makeFunction(path.join('examples', 'local'), {
-      serviceName: 'localdemo',
-      functionName: 'nodejs6',
-      description: 'Hello world with nodejs6!',
-      handler: 'index.handler',
-      initializer: null,
-      timeout: 3,
-      initializationTimeout: 3,
-      memorySize: 128,
-      runtime: 'nodejs6',
-      codeUri: path.join('examples', 'local', 'nodejs6'),
-      environmentVariables: {'StringTypeValue1': 123, 'StringTypeValue2': 'test'}
-    });    
-    
-    assert.calledWith(
-      FC.prototype.updateFunction,
-      'localdemo',
-      'nodejs6',
-      {
-        description: 'Hello world with nodejs6!',
-        handler: 'index.handler',
-        initializer: null,
-        timeout: 3,
-        initializationTimeout: 3,
-        memorySize: 128,
-        runtime: 'nodejs6',
-        code: {
-          zipFile: undefined
-        },
-        environmentVariables: {
-          StringTypeValue1: '123',
-          StringTypeValue2: 'test',
-          LD_LIBRARY_PATH: '/code/.fun/root/usr/lib:/code/.fun/root/usr/lib/x86_64-linux-gnu:/code:/code/lib:/usr/local/lib',
-          PATH: '/code/.fun/root/usr/local/bin:/code/.fun/root/usr/local/sbin:/code/.fun/root/usr/bin:/code/.fun/root/usr/sbin:/code/.fun/root/sbin:/code/.fun/root/bin:/code/.fun/python/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/sbin:/bin',
-          PYTHONUSERBASE: '/code/.fun/python'
-        }
-      });
-  });
-});
-
-describe('make invocation role', () => {
-
-  let restoreProcess;
-
-  beforeEach(async () => {
-
-    sandbox.stub(ram, 'makeRole').resolves({
-      Role: 'generated-role-name'
-    });
-    sandbox.stub(ram, 'makePolicy').resolves({});
-    sandbox.stub(ram, 'attachPolicyToRole').resolves({});
-
-    deploySupport = await proxyquire('../../lib/deploy/deploy-support', {
-      '../../lib/ram': ram
-    });
-
-    restoreProcess = setProcess({
-      ACCOUNT_ID: 'testAccountId',
-      ACCESS_KEY_ID: 'testKeyId',
-      ACCESS_KEY_SECRET: 'testKeySecret',
-      DEFAULT_REGION: 'cn-shanghai'
-    });
-  });
-
-  afterEach(() => {
-    sandbox.restore();
-    restoreProcess();
-  });
-
-  it('makeInvocationRole of log', async () => {
-    const role = await deploySupport.makeInvocationRole('test-service_name', 'test-function_name', 'Log');
-
-    assert.calledOnce(ram.makeRole);
-    assert.calledWith(ram.makeRole,
-      'AliyunFcGeneratedInvocationRole-test-service-name-test-function-name',
-      true,
-      'Used for fc invocation',
-      {
-        Statement: [{
-          Action: 'sts:AssumeRole',
-          Effect: 'Allow',
-          Principal: { Service: ['log.aliyuncs.com'] }
-        }],
-        Version: '1'
-      });
-
-    assert.calledOnce(ram.makePolicy);
-    assert.calledWith(ram.makePolicy, 'AliyunFcGeneratedInvocationPolicy-test-service-name-test-function-name',
-      {
-        Statement: [{
-          Action: ['fc:InvokeFunction'],
-          Effect: 'Allow',
-          Resource: 'acs:fc:*:*:services/test-service_name/functions/*'
-        }, {
-          Action: ['log:Get*', 'log:List*', 'log:PostLogStoreLogs', 'log:CreateConsumerGroup', 'log:UpdateConsumerGroup', 'log:DeleteConsumerGroup', 'log:ListConsumerGroup', 'log:ConsumerGroupUpdateCheckPoint', 'log:ConsumerGroupHeartBeat', 'log:GetConsumerGroupCheckPoint'],
-          Effect: 'Allow',
-          Resource: '*'
-        }],
-        Version: '1'
-      });
-
-    assert.calledOnce(ram.attachPolicyToRole);
-    assert.calledWith(ram.attachPolicyToRole, 'AliyunFcGeneratedInvocationPolicy-test-service-name-test-function-name', 'AliyunFcGeneratedInvocationRole-test-service-name-test-function-name', 'Custom');
-
-    expect(role).to.be('generated-role-name');
-  });
-
-  it('makeInvocationRole of rds', async () => {
-    const role = await deploySupport.makeInvocationRole('test-service_name', 'test-function_name', 'RDS');
-
-    assert.calledOnce(ram.makeRole);
-    assert.calledWith(ram.makeRole,
-      'FunCreateRole-test-service-name-test-function-name',
-      true,
-      'Used for fc invocation',
-      {
-        Statement: [{
-          Action: 'sts:AssumeRole',
-          Effect: 'Allow',
-          Principal: { Service: ['rds.aliyuncs.com'] }
-        }],
-        Version: '1'
-      });
-
-    assert.calledOnce(ram.makePolicy);
-    assert.calledWith(ram.makePolicy, 'FunCreatePolicy-test-service-name-test-function-name',
-      {
-        Statement: [{
-          Action: ['fc:InvokeFunction'],
-          Effect: 'Allow',
-          Resource: 'acs:fc:*:*:services/test-service_name/functions/*'
-        }],
-        Version: '1'
-      });
-
-    assert.calledOnce(ram.attachPolicyToRole);
-    assert.calledWith(ram.attachPolicyToRole, 'FunCreatePolicy-test-service-name-test-function-name', 'FunCreateRole-test-service-name-test-function-name', 'Custom');
-
-    expect(role).to.be('generated-role-name');
-  });
-
-  it('makeInvocationRole of mns topic', async () => {
-    const role = await deploySupport.makeInvocationRole('test-service_name', 'test-function_name', 'MNSTopic');
-
-    assert.calledOnce(ram.makeRole);
-    assert.calledWith(ram.makeRole,
-      'FunCreateRole-test-service-name-test-function-name',
-      true,
-      'Used for fc invocation',
-      {
-        Statement: [{
-          Action: 'sts:AssumeRole',
-          Effect: 'Allow',
-          Principal: { Service: ['mns.aliyuncs.com'] }
-        }],
-        Version: '1'
-      });
-
-    assert.calledOnce(ram.makePolicy);
-    assert.calledWith(ram.makePolicy, 'FunCreatePolicy-test-service-name-test-function-name',
-      {
-        Statement: [{
-          Action: ['fc:InvokeFunction'],
-          Effect: 'Allow',
-          Resource: 'acs:fc:*:*:services/test-service_name/functions/*'
-        }],
-        Version: '1'
-      });
-
-    assert.calledOnce(ram.attachPolicyToRole);
-    assert.calledWith(ram.attachPolicyToRole, 'FunCreatePolicy-test-service-name-test-function-name', 'FunCreateRole-test-service-name-test-function-name', 'Custom');
-
-    expect(role).to.be('generated-role-name');
-  });
-
-  it('makeInvocationRole of table store', async () => {
-    const role = await deploySupport.makeInvocationRole('test-service_name', 'test-function_name', 'TableStore');
-
-    assert.calledOnce(ram.makeRole);
-    assert.calledWith(ram.makeRole,
-      'FunCreateRole-test-service-name-test-function-name',
-      true,
-      'Used for fc invocation',
-      {
-        Statement: [{
-          Action: 'sts:AssumeRole',
-          Effect: 'Allow',
-          Principal: { RAM: ['acs:ram::1604337383174619:root'] } }],
-        Version: '1'
-      });
-
-    assert.calledTwice(ram.makePolicy);
-    assert.calledWith(ram.makePolicy.firstCall, 'FunCreateInvkPolicy-test-service-name-test-function-name',
-      {
-        Statement: [{ Action: ['fc:InvokeFunction'], Effect: 'Allow', Resource: '*' }],
-        Version: '1'
-      });
-    assert.calledWith(ram.makePolicy.secondCall, 'FunCreateOtsReadPolicy-test-service-name-test-function-name',
-      {
-        Statement: [{
-          Action: ['ots:BatchGet*', 'ots:Describe*', 'ots:Get*', 'ots:List*'],
-          Effect: 'Allow',
-          Resource: '*'
-        }],
-        Version: '1'
-      });
-
-    assert.calledTwice(ram.attachPolicyToRole);
-    assert.calledWith(ram.attachPolicyToRole.firstCall, 'FunCreateInvkPolicy-test-service-name-test-function-name', 'FunCreateRole-test-service-name-test-function-name', 'Custom');
-    assert.calledWith(ram.attachPolicyToRole.secondCall, 'FunCreateOtsReadPolicy-test-service-name-test-function-name', 'FunCreateRole-test-service-name-test-function-name', 'Custom');
-
-    expect(role).to.be('generated-role-name');
-  });
-
-  it('makeInvocationRole of oss', async () => {
-    const role = await deploySupport.makeInvocationRole('oss-service_name', 'oss-function_name', 'OSS');
-
-    assert.calledOnce(ram.makeRole);
-    assert.calledWith(ram.makeRole,
-      'FunCreateRole-oss-service-name-oss-function-name',
-      true,
-      'Used for fc invocation',
-      {
-        Statement: [
-          {
-            'Action': 'sts:AssumeRole',
-            'Effect': 'Allow',
-            'Principal': {
-              'Service': [
-                'oss.aliyuncs.com'
-              ]
-            }
-          }
-        ],
-        Version: '1'
-      });
-
-    assert.calledWith(ram.makePolicy, 'FunCreateOSSPolicy-oss-service-name-oss-function-name',
-      {
-        Statement: [{
-          'Action': [
-            'fc:InvokeFunction'
-          ],
-          'Resource': `acs:fc:*:*:services/oss-service_name/functions/*`,
-          'Effect': 'Allow'
-        }],
-        Version: '1'
-      });
-    assert.calledOnce(ram.attachPolicyToRole);
-    assert.calledWith(ram.attachPolicyToRole, 'FunCreateOSSPolicy-oss-service-name-oss-function-name', 'FunCreateRole-oss-service-name-oss-function-name', 'Custom');
 
-    expect(role).to.be('generated-role-name');
-  });
-
-  it('makeInvocationRole of cdn', async () => {
-    const role = await deploySupport.makeInvocationRole('cdn-service_name', 'cdn-function_name', 'CDN');
-
-    assert.calledOnce(ram.makeRole);
-    assert.calledWith(ram.makeRole,
-      'FunCreateRole-cdn-service-name-cdn-function-name',
-      true,
-      'Used for fc invocation',
-      {
-        Statement: [
-          {
-            'Action': 'sts:AssumeRole',
-            'Effect': 'Allow',
-            'Principal': {
-              'Service': [
-                'cdn.aliyuncs.com'
-              ]
-            }
-          }
-        ],
-        Version: '1'
-      });
-
-    assert.calledWith(ram.makePolicy, 'FunCreateCDNPolicy-cdn-service-name-cdn-function-name',
-      {
-        Statement: [{
-          'Action': [
-            'fc:InvokeFunction'
-          ],
-          'Resource': `acs:fc:*:*:services/cdn-service_name/functions/*`,
-          'Effect': 'Allow'
-        }],
-        Version: '1'
-      });
-    assert.calledOnce(ram.attachPolicyToRole);
-    assert.calledWith(ram.attachPolicyToRole, 'FunCreateCDNPolicy-cdn-service-name-cdn-function-name', 'FunCreateRole-cdn-service-name-cdn-function-name', 'Custom');
-
-    expect(role).to.be('generated-role-name');
-  });
-});
-
-describe('test getFunCodeAsBase64', () => {
-
-  let restoreProcess;
-  let lstat;
-
-  beforeEach(async () => {
-
-    sandbox.stub(zip, 'pack').resolves({});
-
-    lstat = sandbox.stub();
-
-    lstat.resolves({
-      isFile: function () { return false; }
-    });
-
-    sandbox.stub(util, 'promisify').returns(lstat);
-
-    deploySupport = await proxyquire('../../lib/deploy/deploy-support', {
-      '../package/zip': zip,
-      'util': util
-    });
-
-    restoreProcess = setProcess({
-      ACCOUNT_ID: 'testAccountId',
-      ACCESS_KEY_ID: 'testKeyId',
-      ACCESS_KEY_SECRET: 'testKeySecret'
-    });
-  });
-
-  afterEach(() => {
-    sandbox.restore();
-    restoreProcess();
-  });
-
-  it('test getFunCodeAsBase64: codeUri outside baseDir', async () => {
-    await deploySupport.getFunCodeAsBase64('/a/b', '/a');
-    assert.calledWith(lstat, '/a');
-    assert.calledWith(zip.pack, '/a', null);
-  });
-
-
-  it('test getFunCodeAsBase64: codeUri outside baseDir2', async () => {
-    await deploySupport.getFunCodeAsBase64('/a/b', '../');
-    assert.calledWith(lstat, '../');
-    assert.calledWith(zip.pack, '../', null);
-  });
-
-  it('test getFunCodeAsBase64: codeUri within baseDir', async () => {
-    await deploySupport.getFunCodeAsBase64('/a/b', '/a/b/c');
-    assert.calledWith(lstat, '/a/b/c');
-    assert.calledWith(zip.pack, '/a/b/c', sinon.match.func);
-  });
-
-
-  it('test getFunCodeAsBase64: absolute codeUri path', async () => {
-    await deploySupport.getFunCodeAsBase64('/a/b', process.cwd() + '/a/b/c/index.js');
-    assert.calledWith(lstat, process.cwd() + '/a/b/c/index.js');
-    assert.calledWith(zip.pack, process.cwd() + '/a/b/c/index.js', null);
-  });
-
-  it('test getFunCodeAsBase64: relative codeUri path', async () => {
-    await deploySupport.getFunCodeAsBase64('/a/b', './index.js');
-    assert.calledWith(lstat, './index.js');
-    assert.calledWith(zip.pack, './index.js', null);
-  });
-  
-});
-
-describe('test config invovationRole fot trigger', ()=> {
-  let restoreProcess;
-
-  beforeEach(async () => {
-    sandbox.stub(FC.prototype, 'getTrigger').resolves(undefined);
-    sandbox.stub(FC.prototype, 'createTrigger').resolves({});
-
-    Object.keys(ram).forEach(m => {
-      if (m === 'makeRole') {
-        sandbox.stub(ram, m).resolves({
-          'Role': {
-            'Arn': 'acs:ram::123:role/aliyunfcgeneratedrole-fc'
-          }
-        });
-      } else if (m === 'normalizeRoleOrPoliceName') {
-        sandbox.stub(ram, 'normalizeRoleOrPoliceName').returns('');
-      } else {
-        sandbox.stub(ram, m).resolves({});
-      }
-    });
-
-    deploySupport = await proxyquire('../../lib/deploy/deploy-support', {
-      '@alicloud/fc2': FC,
-      '../ram': ram
-    });
-
-    restoreProcess = setProcess({
-      ACCOUNT_ID: 'ACCOUNT_ID',
-      ACCESS_KEY_ID: 'ACCESS_KEY_ID',
-      ACCESS_KEY_SECRET: 'ACCESS_KEY_SECRET',
-      DEFAULT_REGION: 'cn-shanghai'
-    });
-  });
-
-  afterEach(() => {
-    sandbox.restore();
-    restoreProcess();
-  });
-
-  it('invocationRole  ots trigger ', async ()=> {
-    await deploySupport.makeTrigger(
-      {
-        serviceName: 'serviceName', 
-        functionName: 'functionName', 
-        triggerName: 'triggerName', 
-        triggerType: 'TableStore',
-        triggerProperties: {
-          'InstanceName': 'fc-test-inst',
-          'TableName': 'fc_test_tbl',
-          'InvocationRole': 'acs:ram::987:role/invocation-role-test'
-        }
-      });    
-
-    assert.notCalled(ram.makeRole);
-    assert.notCalled(ram.makePolicy);
-    assert.notCalled(ram.makeAndAttachPolicy);
-    assert.notCalled(ram.attachPolicyToRole);  
-
-    assert.calledWith(
-      FC.prototype.createTrigger,
-      'serviceName',
-      'functionName',
-      {
-        'triggerName': 'triggerName',
-        'triggerType': 'tablestore',
-        'triggerConfig': {},
-        'invocationRole': 'acs:ram::987:role/invocation-role-test',
-        'sourceArn': 'acs:ots:cn-shanghai:ACCOUNT_ID:instance/fc-test-inst/table/fc_test_tbl'
-
-      });
-  });
-});
\ No newline at end of file
diff --git a/test/docker.test.js b/test/docker.test.js
index 09ba009..e153eb7 100644
--- a/test/docker.test.js
+++ b/test/docker.test.js
@@ -19,10 +19,7 @@ const { hasDocker } = require('./conditions');
 
 const util = require('util');
 const path = require('path');
-
-function sleep(ms) {
-  return new Promise(resolve => setTimeout(resolve, ms));
-}
+const { sleep } = require('../lib/time');
 
 describe('test generateDockerCmd', () => {
   const functionProps = {
diff --git a/test/fc.test.js b/test/fc.test.js
new file mode 100644
index 0000000..c311605
--- /dev/null
+++ b/test/fc.test.js
@@ -0,0 +1,148 @@
+'use strict';
+
+const FC = require('@alicloud/fc2');
+
+const sinon = require('sinon');
+const sandbox = sinon.createSandbox();
+const assert = sinon.assert;
+const proxyquire = require('proxyquire');
+const { setProcess } = require('./test-utils');
+const zip = require('../lib/package/zip');
+let fc = require('../lib/fc');
+const path = require('path');
+const util = require('util');
+
+describe('test getFunCodeAsBase64', () => {
+
+  let restoreProcess;
+  let lstat;
+
+  beforeEach(async () => {
+
+    sandbox.stub(zip, 'pack').resolves({});
+
+    lstat = sandbox.stub();
+
+    lstat.resolves({
+      isFile: function () { return false; }
+    });
+
+    sandbox.stub(util, 'promisify').returns(lstat);
+
+    fc = await proxyquire('../lib/fc', {
+      './package/zip': zip,
+      'util': util
+    });
+
+    restoreProcess = setProcess({
+      ACCOUNT_ID: 'testAccountId',
+      ACCESS_KEY_ID: 'testKeyId',
+      ACCESS_KEY_SECRET: 'testKeySecret'
+    });
+  });
+
+  afterEach(() => {
+    sandbox.restore();
+    restoreProcess();
+  });
+
+  it('test getFunCodeAsBase64: codeUri outside baseDir', async () => {
+    await fc.getFunCodeAsBase64('/a/b', '/a');
+    assert.calledWith(lstat, '/a');
+    assert.calledWith(zip.pack, '/a', null);
+  });
+
+
+  it('test getFunCodeAsBase64: codeUri outside baseDir2', async () => {
+    await fc.getFunCodeAsBase64('/a/b', '../');
+    assert.calledWith(lstat, '../');
+    assert.calledWith(zip.pack, '../', null);
+  });
+
+  it('test getFunCodeAsBase64: codeUri within baseDir', async () => {
+    await fc.getFunCodeAsBase64('/a/b', '/a/b/c');
+    assert.calledWith(lstat, '/a/b/c');
+    assert.calledWith(zip.pack, '/a/b/c', sinon.match.func);
+  });
+
+
+  it('test getFunCodeAsBase64: absolute codeUri path', async () => {
+    await fc.getFunCodeAsBase64('/a/b', process.cwd() + '/a/b/c/index.js');
+    assert.calledWith(lstat, process.cwd() + '/a/b/c/index.js');
+    assert.calledWith(zip.pack, process.cwd() + '/a/b/c/index.js', null);
+  });
+
+  it('test getFunCodeAsBase64: relative codeUri path', async () => {
+    await fc.getFunCodeAsBase64('/a/b', './index.js');
+    assert.calledWith(lstat, './index.js');
+    assert.calledWith(zip.pack, './index.js', null);
+  });
+  
+});
+
+describe('Incorrect environmental variables', ()=> {
+  let restoreProcess;
+
+  beforeEach(async () => {
+    sandbox.stub(FC.prototype, 'getFunction').resolves({});
+    sandbox.stub(FC.prototype, 'updateFunction').resolves({});
+    sandbox.stub(zip, 'pack').resolves({});
+
+    fc = await proxyquire('../lib/fc', {
+      './package/zip': zip,
+      '@alicloud/fc2': FC
+    });
+
+    restoreProcess = setProcess({
+      ACCOUNT_ID: 'ACCOUNT_ID',
+      ACCESS_KEY_ID: 'ACCESS_KEY_ID',
+      ACCESS_KEY_SECRET: 'ACCESS_KEY_SECRET',
+      DEFAULT_REGION: 'cn-shanghai'
+    });
+  });
+
+  afterEach(() => {
+    sandbox.restore();
+    restoreProcess();
+  });
+
+  it('should cast env value to String', async ()=> {
+    await fc.makeFunction(path.join('examples', 'local'), {
+      serviceName: 'localdemo',
+      functionName: 'nodejs6',
+      description: 'Hello world with nodejs6!',
+      handler: 'index.handler',
+      initializer: null,
+      timeout: 3,
+      initializationTimeout: 3,
+      memorySize: 128,
+      runtime: 'nodejs6',
+      codeUri: path.join('examples', 'local', 'nodejs6'),
+      environmentVariables: {'StringTypeValue1': 123, 'StringTypeValue2': 'test'}
+    });    
+    
+    assert.calledWith(
+      FC.prototype.updateFunction,
+      'localdemo',
+      'nodejs6',
+      {
+        description: 'Hello world with nodejs6!',
+        handler: 'index.handler',
+        initializer: null,
+        timeout: 3,
+        initializationTimeout: 3,
+        memorySize: 128,
+        runtime: 'nodejs6',
+        code: {
+          zipFile: undefined
+        },
+        environmentVariables: {
+          StringTypeValue1: '123',
+          StringTypeValue2: 'test',
+          LD_LIBRARY_PATH: '/code/.fun/root/usr/lib:/code/.fun/root/usr/lib/x86_64-linux-gnu:/code:/code/lib:/usr/local/lib',
+          PATH: '/code/.fun/root/usr/local/bin:/code/.fun/root/usr/local/sbin:/code/.fun/root/usr/bin:/code/.fun/root/usr/sbin:/code/.fun/root/sbin:/code/.fun/root/bin:/code/.fun/python/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/sbin:/bin',
+          PYTHONUSERBASE: '/code/.fun/python'
+        }
+      });
+  });
+});
diff --git a/test/local/invoke.test.js b/test/local/invoke.test.js
index 9ba140b..1560c83 100644
--- a/test/local/invoke.test.js
+++ b/test/local/invoke.test.js
@@ -72,7 +72,7 @@ describe('test invoke construct and init', async () => {
     await invoke.init();
 
     expect(invoke.nasConfig).to.eql(undefined);
-    expect(invoke.dockerUser).to.eql('0:0');
+    expect(invoke.dockerUser).to.eql('10003:10003');
     expect(invoke.nasMounts).to.eql([]);
     expect(invoke.codeMount).to.eql(codeMount);
     expect(invoke.mounts).to.eql([codeMount]);
diff --git a/test/nas.test.js b/test/nas.test.js
new file mode 100644
index 0000000..251b709
--- /dev/null
+++ b/test/nas.test.js
@@ -0,0 +1,211 @@
+'use strict';
+
+const expect = require('expect.js');
+let nas = require('../lib/nas');
+const sinon = require('sinon');
+const sandbox = sinon.createSandbox();
+const assert = sinon.assert;
+
+const region = 'cn-hangzhou';
+
+var requestOption = {
+  method: 'POST'
+};
+
+describe('test findNasFileSystem', async () => {
+
+  afterEach(() => {
+    sandbox.restore();
+  });
+
+  it('test', async () => {
+    const description = 'test';
+
+    const params = {
+      'RegionId': region,
+      'PageSize': 1000
+    };
+
+    const requestStub = sandbox.stub();
+
+    requestStub.withArgs('DescribeFileSystems', params, requestOption).resolves({
+      'FileSystems': {
+        'FileSystem': [
+          {
+            'Description': description,
+            'FileSystemId': '109c042666',
+            'RegionId': 'cn-hangzhou',
+            'MeteredSize': 1611661312
+          }
+        ]
+      }
+    });
+
+    const nasPopClient = { request: requestStub };
+
+    const findSystemId = await nas.findNasFileSystem(nasPopClient, region, description);
+
+    expect(findSystemId).to.eql('109c042666');
+
+    assert.calledWith(requestStub, 'DescribeFileSystems', params, requestOption);
+  });
+
+});
+
+describe('test findMountTarget', async () => {
+
+  afterEach(() => {
+    sandbox.restore();
+  });
+
+  it('test', async () => {
+    const fileSystemId = '123';
+    const vpcId = 'vpc-bp1me4doa1zk2mwhksx4q';
+    const vswId = 'vpc-bp1me4doa1zk2mwhksx4q';
+    const mountTargetDomain = '0d2574b319-doo72.cn-hangzhou.nas.aliyuncs.com';
+
+    const params = {
+      'RegionId': region,
+      'FileSystemId': fileSystemId
+    };
+
+    const requestStub = sandbox.stub();
+
+    requestStub.withArgs('DescribeMountTargets', params, requestOption).resolves({
+      'MountTargets': {
+        'MountTarget': [
+          {
+            'VswId': vswId,
+            'VpcId': vpcId,
+            'MountTargetDomain': mountTargetDomain
+          }
+        ]
+      }
+    });
+
+    const nasPopClient = { request: requestStub };
+
+    const mountTarget = await nas.findMountTarget(nasPopClient, region, fileSystemId, vpcId, vswId);
+
+    expect(mountTarget).to.eql(mountTargetDomain);
+
+    assert.calledWith(requestStub, 'DescribeMountTargets', params, requestOption);
+  });
+});
+
+describe('test createMountTarget', async () => {
+
+  afterEach(() => {
+    sandbox.restore();
+  });
+
+  it('test createMountTarget', async () => {
+    const fileSystemId = '123';
+    const vpcId = 'vpc-bp1me4doa1zk2mwhksx4q';
+    const vswId = 'vpc-bp1me4doa1zk2mwhksx4q';
+    const mountTargetDomain = '0d2574b319-doo72.cn-hangzhou.nas.aliyuncs.com';
+
+    const params = {
+      'RegionId': region,
+      'NetworkType': 'Vpc',
+      'FileSystemId': fileSystemId,
+      'AccessGroupName': 'DEFAULT_VPC_GROUP_NAME',
+      'VpcId': vpcId,
+      'VSwitchId': vswId
+    };
+
+    const requestStub = sandbox.stub();
+
+    requestStub.withArgs('CreateMountTarget', params, requestOption).resolves({
+      'MountTargetDomain': '0d2574b319-doo72.cn-hangzhou.nas.aliyuncs.com'
+    });
+
+    const describeParms = {
+      'RegionId': region,
+      'FileSystemId': fileSystemId,
+      'MountTargetDomain': mountTargetDomain
+    };
+
+
+    requestStub.withArgs('DescribeMountTargets', describeParms, requestOption)
+      .onCall(0).resolves({
+        'MountTargets': {
+          'MountTarget': [
+            {
+              'Status': 'Pending'
+            }
+          ]
+        }
+      })
+      .onCall(1).resolves({
+        'MountTargets': {
+          'MountTarget': [
+            {
+              'Status': 'Active'
+            }
+          ]
+        }
+      });
+
+    const nasPopClient = { request: requestStub };
+
+    const mountTarget = await nas.createMountTarget(nasPopClient, region, fileSystemId, vpcId, vswId);
+
+    expect(mountTarget).to.eql(mountTargetDomain);
+
+    assert.calledWith(requestStub.firstCall, 'CreateMountTarget', params, requestOption);
+    assert.calledWith(requestStub.secondCall, 'DescribeMountTargets', describeParms, requestOption);
+    assert.calledWith(requestStub.thirdCall, 'DescribeMountTargets', describeParms, requestOption);
+  });
+
+  it('test createMountTarget timeout', async () => {
+    const fileSystemId = '123';
+    const vpcId = 'vpc-bp1me4doa1zk2mwhksx4q';
+    const vswId = 'vpc-bp1me4doa1zk2mwhksx4q';
+    const mountTargetDomain = '0d2574b319-doo72.cn-hangzhou.nas.aliyuncs.com';
+
+    const params = {
+      'RegionId': region,
+      'NetworkType': 'Vpc',
+      'FileSystemId': fileSystemId,
+      'AccessGroupName': 'DEFAULT_VPC_GROUP_NAME',
+      'VpcId': vpcId,
+      'VSwitchId': vswId
+    };
+
+    const requestStub = sandbox.stub();
+
+    requestStub.withArgs('CreateMountTarget', params, requestOption).resolves({
+      'MountTargetDomain': '0d2574b319-doo72.cn-hangzhou.nas.aliyuncs.com'
+    });
+
+    const describeParms = {
+      'RegionId': region,
+      'FileSystemId': fileSystemId,
+      'MountTargetDomain': mountTargetDomain
+    };
+
+    requestStub.withArgs('DescribeMountTargets', describeParms, requestOption)
+      .resolves({
+        'MountTargets': {
+          'MountTarget': [
+            {
+              'Status': 'Pending'
+            }
+          ]
+        }
+      });
+
+    const nasPopClient = { request: requestStub };
+
+    try {
+      await nas.createMountTarget(nasPopClient, region, fileSystemId, vpcId, vswId);
+    } catch (e) {
+      expect(e.message).to.eql(`Timeout while waiting for MountPoint 0d2574b319-doo72.cn-hangzhou.nas.aliyuncs.com status to be 'Active'`);
+
+      assert.calledWith(requestStub.firstCall, 'CreateMountTarget', params, requestOption);
+      requestStub.withArgs('DescribeMountTargets', describeParms, requestOption);
+      assert.callCount(requestStub, 1 + 15); // 1 CreateMountTarget, 15 DescribeVpcs
+    }
+  });
+});
\ No newline at end of file
diff --git a/test/trigger.test.js b/test/trigger.test.js
new file mode 100644
index 0000000..e2162ef
--- /dev/null
+++ b/test/trigger.test.js
@@ -0,0 +1,365 @@
+'use strict';
+
+let trigger = require('../lib/trigger');
+const FC = require('@alicloud/fc2');
+const ram = require('../lib/ram');
+const sinon = require('sinon');
+const sandbox = sinon.createSandbox();
+const assert = sinon.assert;
+const { setProcess } = require('./test-utils');
+const proxyquire = require('proxyquire');
+const expect = require('expect.js');
+
+describe('test config invovationRole fot trigger', ()=> {
+  let restoreProcess;
+
+  beforeEach(async () => {
+    sandbox.stub(FC.prototype, 'getTrigger').resolves(undefined);
+    sandbox.stub(FC.prototype, 'createTrigger').resolves({});
+
+    Object.keys(ram).forEach(m => {
+      if (m === 'makeRole') {
+        sandbox.stub(ram, m).resolves({
+          'Role': {
+            'Arn': 'acs:ram::123:role/aliyunfcgeneratedrole-fc'
+          }
+        });
+      } else if (m === 'normalizeRoleOrPoliceName') {
+        sandbox.stub(ram, 'normalizeRoleOrPoliceName').returns('');
+      } else {
+        sandbox.stub(ram, m).resolves({});
+      }
+    });
+
+    trigger = await proxyquire('../lib/trigger', {
+      '@alicloud/fc2': FC,
+      './ram': ram
+    });
+
+    restoreProcess = setProcess({
+      ACCOUNT_ID: 'ACCOUNT_ID',
+      ACCESS_KEY_ID: 'ACCESS_KEY_ID',
+      ACCESS_KEY_SECRET: 'ACCESS_KEY_SECRET',
+      DEFAULT_REGION: 'cn-shanghai'
+    });
+  });
+
+  afterEach(() => {
+    sandbox.restore();
+    restoreProcess();
+  });
+
+  it('invocationRole  ots trigger ', async ()=> {
+    await trigger.makeTrigger(
+      {
+        serviceName: 'serviceName', 
+        functionName: 'functionName', 
+        triggerName: 'triggerName', 
+        triggerType: 'TableStore',
+        triggerProperties: {
+          'InstanceName': 'fc-test-inst',
+          'TableName': 'fc_test_tbl',
+          'InvocationRole': 'acs:ram::987:role/invocation-role-test'
+        }
+      });    
+
+    assert.notCalled(ram.makeRole);
+    assert.notCalled(ram.makePolicy);
+    assert.notCalled(ram.makeAndAttachPolicy);
+    assert.notCalled(ram.attachPolicyToRole);  
+
+    assert.calledWith(
+      FC.prototype.createTrigger,
+      'serviceName',
+      'functionName',
+      {
+        'triggerName': 'triggerName',
+        'triggerType': 'tablestore',
+        'triggerConfig': {},
+        'invocationRole': 'acs:ram::987:role/invocation-role-test',
+        'sourceArn': 'acs:ots:cn-shanghai:ACCOUNT_ID:instance/fc-test-inst/table/fc_test_tbl'
+
+      });
+  });
+});
+
+describe('make invocation role', () => {
+
+  let restoreProcess;
+
+  beforeEach(async () => {
+
+    sandbox.stub(ram, 'makeRole').resolves({
+      Role: 'generated-role-name'
+    });
+    sandbox.stub(ram, 'makePolicy').resolves({});
+    sandbox.stub(ram, 'attachPolicyToRole').resolves({});
+
+    trigger = await proxyquire('../lib/trigger', {
+      './ram': ram
+    });
+
+    restoreProcess = setProcess({
+      ACCOUNT_ID: 'testAccountId',
+      ACCESS_KEY_ID: 'testKeyId',
+      ACCESS_KEY_SECRET: 'testKeySecret',
+      DEFAULT_REGION: 'cn-shanghai'
+    });
+  });
+
+  afterEach(() => {
+    sandbox.restore();
+    restoreProcess();
+  });
+
+  it('makeInvocationRole of log', async () => {
+    const role = await trigger.makeInvocationRole('test-service_name', 'test-function_name', 'Log');
+
+    assert.calledOnce(ram.makeRole);
+    assert.calledWith(ram.makeRole,
+      'AliyunFcGeneratedInvocationRole-test-service-name-test-function-name',
+      true,
+      'Used for fc invocation',
+      {
+        Statement: [{
+          Action: 'sts:AssumeRole',
+          Effect: 'Allow',
+          Principal: { Service: ['log.aliyuncs.com'] }
+        }],
+        Version: '1'
+      });
+
+    assert.calledOnce(ram.makePolicy);
+    assert.calledWith(ram.makePolicy, 'AliyunFcGeneratedInvocationPolicy-test-service-name-test-function-name',
+      {
+        Statement: [{
+          Action: ['fc:InvokeFunction'],
+          Effect: 'Allow',
+          Resource: 'acs:fc:*:*:services/test-service_name/functions/*'
+        }, {
+          Action: ['log:Get*', 'log:List*', 'log:PostLogStoreLogs', 'log:CreateConsumerGroup', 'log:UpdateConsumerGroup', 'log:DeleteConsumerGroup', 'log:ListConsumerGroup', 'log:ConsumerGroupUpdateCheckPoint', 'log:ConsumerGroupHeartBeat', 'log:GetConsumerGroupCheckPoint'],
+          Effect: 'Allow',
+          Resource: '*'
+        }],
+        Version: '1'
+      });
+
+    assert.calledOnce(ram.attachPolicyToRole);
+    assert.calledWith(ram.attachPolicyToRole, 'AliyunFcGeneratedInvocationPolicy-test-service-name-test-function-name', 'AliyunFcGeneratedInvocationRole-test-service-name-test-function-name', 'Custom');
+
+    expect(role).to.be('generated-role-name');
+  });
+
+  it('makeInvocationRole of rds', async () => {
+    const role = await trigger.makeInvocationRole('test-service_name', 'test-function_name', 'RDS');
+
+    assert.calledOnce(ram.makeRole);
+    assert.calledWith(ram.makeRole,
+      'FunCreateRole-test-service-name-test-function-name',
+      true,
+      'Used for fc invocation',
+      {
+        Statement: [{
+          Action: 'sts:AssumeRole',
+          Effect: 'Allow',
+          Principal: { Service: ['rds.aliyuncs.com'] }
+        }],
+        Version: '1'
+      });
+
+    assert.calledOnce(ram.makePolicy);
+    assert.calledWith(ram.makePolicy, 'FunCreatePolicy-test-service-name-test-function-name',
+      {
+        Statement: [{
+          Action: ['fc:InvokeFunction'],
+          Effect: 'Allow',
+          Resource: 'acs:fc:*:*:services/test-service_name/functions/*'
+        }],
+        Version: '1'
+      });
+
+    assert.calledOnce(ram.attachPolicyToRole);
+    assert.calledWith(ram.attachPolicyToRole, 'FunCreatePolicy-test-service-name-test-function-name', 'FunCreateRole-test-service-name-test-function-name', 'Custom');
+
+    expect(role).to.be('generated-role-name');
+  });
+
+  it('makeInvocationRole of mns topic', async () => {
+    const role = await trigger.makeInvocationRole('test-service_name', 'test-function_name', 'MNSTopic');
+
+    assert.calledOnce(ram.makeRole);
+    assert.calledWith(ram.makeRole,
+      'FunCreateRole-test-service-name-test-function-name',
+      true,
+      'Used for fc invocation',
+      {
+        Statement: [{
+          Action: 'sts:AssumeRole',
+          Effect: 'Allow',
+          Principal: { Service: ['mns.aliyuncs.com'] }
+        }],
+        Version: '1'
+      });
+
+    assert.calledOnce(ram.makePolicy);
+    assert.calledWith(ram.makePolicy, 'FunCreatePolicy-test-service-name-test-function-name',
+      {
+        Statement: [{
+          Action: ['fc:InvokeFunction'],
+          Effect: 'Allow',
+          Resource: 'acs:fc:*:*:services/test-service_name/functions/*'
+        }],
+        Version: '1'
+      });
+
+    assert.calledOnce(ram.attachPolicyToRole);
+    assert.calledWith(ram.attachPolicyToRole, 'FunCreatePolicy-test-service-name-test-function-name', 'FunCreateRole-test-service-name-test-function-name', 'Custom');
+
+    expect(role).to.be('generated-role-name');
+  });
+
+  it('makeInvocationRole of table store', async () => {
+    const role = await trigger.makeInvocationRole('test-service_name', 'test-function_name', 'TableStore');
+
+    assert.calledOnce(ram.makeRole);
+    assert.calledWith(ram.makeRole,
+      'FunCreateRole-test-service-name-test-function-name',
+      true,
+      'Used for fc invocation',
+      {
+        Statement: [{
+          Action: 'sts:AssumeRole',
+          Effect: 'Allow',
+          Principal: { RAM: ['acs:ram::1604337383174619:root'] } }],
+        Version: '1'
+      });
+
+    assert.calledTwice(ram.makePolicy);
+    assert.calledWith(ram.makePolicy.firstCall, 'FunCreateInvkPolicy-test-service-name-test-function-name',
+      {
+        Statement: [{ Action: ['fc:InvokeFunction'], Effect: 'Allow', Resource: '*' }],
+        Version: '1'
+      });
+    assert.calledWith(ram.makePolicy.secondCall, 'FunCreateOtsReadPolicy-test-service-name-test-function-name',
+      {
+        Statement: [{
+          Action: ['ots:BatchGet*', 'ots:Describe*', 'ots:Get*', 'ots:List*'],
+          Effect: 'Allow',
+          Resource: '*'
+        }],
+        Version: '1'
+      });
+
+    assert.calledTwice(ram.attachPolicyToRole);
+    assert.calledWith(ram.attachPolicyToRole.firstCall, 'FunCreateInvkPolicy-test-service-name-test-function-name', 'FunCreateRole-test-service-name-test-function-name', 'Custom');
+    assert.calledWith(ram.attachPolicyToRole.secondCall, 'FunCreateOtsReadPolicy-test-service-name-test-function-name', 'FunCreateRole-test-service-name-test-function-name', 'Custom');
+
+    expect(role).to.be('generated-role-name');
+  });
+
+  it('makeInvocationRole of oss', async () => {
+    const role = await trigger.makeInvocationRole('oss-service_name', 'oss-function_name', 'OSS');
+
+    assert.calledOnce(ram.makeRole);
+    assert.calledWith(ram.makeRole,
+      'FunCreateRole-oss-service-name-oss-function-name',
+      true,
+      'Used for fc invocation',
+      {
+        Statement: [
+          {
+            'Action': 'sts:AssumeRole',
+            'Effect': 'Allow',
+            'Principal': {
+              'Service': [
+                'oss.aliyuncs.com'
+              ]
+            }
+          }
+        ],
+        Version: '1'
+      });
+
+    assert.calledWith(ram.makePolicy, 'FunCreateOSSPolicy-oss-service-name-oss-function-name',
+      {
+        Statement: [{
+          'Action': [
+            'fc:InvokeFunction'
+          ],
+          'Resource': `acs:fc:*:*:services/oss-service_name/functions/*`,
+          'Effect': 'Allow'
+        }],
+        Version: '1'
+      });
+    assert.calledOnce(ram.attachPolicyToRole);
+    assert.calledWith(ram.attachPolicyToRole, 'FunCreateOSSPolicy-oss-service-name-oss-function-name', 'FunCreateRole-oss-service-name-oss-function-name', 'Custom');
+
+    expect(role).to.be('generated-role-name');
+  });
+
+  it('makeInvocationRole of cdn', async () => {
+    const role = await trigger.makeInvocationRole('cdn-service_name', 'cdn-function_name', 'CDN');
+
+    assert.calledOnce(ram.makeRole);
+    assert.calledWith(ram.makeRole,
+      'FunCreateRole-cdn-service-name-cdn-function-name',
+      true,
+      'Used for fc invocation',
+      {
+        Statement: [
+          {
+            'Action': 'sts:AssumeRole',
+            'Effect': 'Allow',
+            'Principal': {
+              'Service': [
+                'cdn.aliyuncs.com'
+              ]
+            }
+          }
+        ],
+        Version: '1'
+      });
+
+    assert.calledWith(ram.makePolicy, 'FunCreateCDNPolicy-cdn-service-name-cdn-function-name',
+      {
+        Statement: [{
+          'Action': [
+            'fc:InvokeFunction'
+          ],
+          'Resource': `acs:fc:*:*:services/cdn-service_name/functions/*`,
+          'Effect': 'Allow'
+        }],
+        Version: '1'
+      });
+    assert.calledOnce(ram.attachPolicyToRole);
+    assert.calledWith(ram.attachPolicyToRole, 'FunCreateCDNPolicy-cdn-service-name-cdn-function-name', 'FunCreateRole-cdn-service-name-cdn-function-name', 'Custom');
+
+    expect(role).to.be('generated-role-name');
+  });
+});
+
+describe('cdn domain capitalization', () => {
+
+  it('Domain->domain', async() => {
+    var capitalConfig = trigger.getTriggerConfig('CDN', {
+      'EventName': 'CachedObjectsRefreshed',
+      'EventVersion': '1.0.0',
+      'Notes': 'cdn events trigger test',
+      'Filter': {
+        'Domain': [
+          'cdn-trigger.sunfeiyu.top'
+        ]
+      }
+    });
+    expect(capitalConfig).to.eql({
+      'eventName': 'CachedObjectsRefreshed',
+      'eventVersion': '1.0.0',
+      'notes': 'cdn events trigger test',
+      'filter': {
+        'domain': [
+          'cdn-trigger.sunfeiyu.top'
+        ]
+      }
+    });
+  });
+});
diff --git a/test/vpc.test.js b/test/vpc.test.js
new file mode 100644
index 0000000..96ceebf
--- /dev/null
+++ b/test/vpc.test.js
@@ -0,0 +1,256 @@
+'use strict';
+
+const vpc = require('../lib/vpc');
+const expect = require('expect.js');
+const sinon = require('sinon');
+const sandbox = sinon.createSandbox();
+const assert = sinon.assert;
+
+const region = 'cn-hangzhou';
+const vpcName = 'test-vpc';
+const vpcId = 'vpc-bp1dkyqjiu7hecg8j7jhe';
+
+var requestOption = {
+  method: 'POST'
+};
+
+describe('test findVpc', () => {
+
+  afterEach(() => {
+    sandbox.restore();
+  });
+
+  it('test find in first page', async () => {
+    var params = {
+      'RegionId': region,
+      'PageSize': 50,
+      'PageNumber': 1
+    };
+
+    const requestStub = sandbox.stub();
+
+    requestStub.withArgs('DescribeVpcs', params, requestOption).resolves({
+      'PageNumber': 1,
+      'Vpcs': {
+        'Vpc': [
+          {
+            'VpcName': vpcName
+          }
+        ]
+      },
+      'TotalCount': 3,
+      'PageSize': 10
+    });
+
+    const vpcPopClient = { request: requestStub };
+
+    const v = await vpc.findVpc(vpcPopClient, region, vpcName);
+
+    expect(v).to.eql({ VpcName: 'test-vpc' });
+  });
+
+  it('test find in second page', async () => {
+    var firstPageParams = {
+      'RegionId': region,
+      'PageSize': 50,
+      'PageNumber': 1
+    };
+
+    var secondPageParams = {
+      'RegionId': region,
+      'PageSize': 50,
+      'PageNumber': 2
+    };
+
+    const requestStub = sandbox.stub();
+
+    requestStub.withArgs('DescribeVpcs', firstPageParams, requestOption).resolves({
+      'PageNumber': 1,
+      'Vpcs': {
+        'Vpc': [
+          {
+            'VpcName': 'notfound'
+          }
+        ]
+      },
+      'TotalCount': 60,
+      'PageSize': 50
+    });
+
+    requestStub.withArgs('DescribeVpcs', secondPageParams, requestOption).resolves({
+      'PageNumber': 2,
+      'Vpcs': {
+        'Vpc': [
+          {
+            'VpcName': vpcName
+          }
+        ]
+      },
+      'TotalCount': 60,
+      'PageSize': 50
+    });
+
+    const vpcPopClient = { request: requestStub };
+
+    const v = await vpc.findVpc(vpcPopClient, region, vpcName);
+
+    expect(v).to.eql({ VpcName: 'test-vpc' });
+
+    assert.calledWith(requestStub.firstCall, 'DescribeVpcs', firstPageParams, requestOption);
+
+    assert.calledWith(requestStub.secondCall, 'DescribeVpcs', secondPageParams, requestOption);
+  });
+
+  it('test not found', async () => {
+    var firstPageParams = {
+      'RegionId': region,
+      'PageSize': 50,
+      'PageNumber': 1
+    };
+
+    var secondPageParams = {
+      'RegionId': region,
+      'PageSize': 50,
+      'PageNumber': 2
+    };
+
+    const requestStub = sandbox.stub();
+
+    requestStub.withArgs('DescribeVpcs', firstPageParams, requestOption).resolves({
+      'PageNumber': 1,
+      'Vpcs': {
+        'Vpc': [
+          {
+            'VpcName': 'notfound'
+          }
+        ]
+      },
+      'TotalCount': 60,
+      'PageSize': 50
+    });
+
+    requestStub.withArgs('DescribeVpcs', secondPageParams, requestOption).resolves({
+      'PageNumber': 2,
+      'Vpcs': {
+        'Vpc': [
+          {
+            'VpcName': 'not found'
+          }
+        ]
+      },
+      'TotalCount': 60,
+      'PageSize': 50
+    });
+
+    const vpcPopClient = { request: requestStub };
+
+    const v = await vpc.findVpc(vpcPopClient, region, vpcName);
+
+    expect(v).to.be(undefined);
+
+    assert.calledWith(requestStub.firstCall, 'DescribeVpcs', firstPageParams, requestOption);
+
+    assert.calledWith(requestStub.secondCall, 'DescribeVpcs', secondPageParams, requestOption);
+
+    assert.calledTwice(requestStub);
+  });
+});
+
+describe('test createVpc', async () => {
+
+  it('test create vpc', async () => {
+    var createParams = {
+      'RegionId': region,
+      'CidrBlock': '10.0.0.0/8',
+      'EnableIpv6': false,
+      'VpcName': vpcName,
+      'Description': 'default vpc created by fc fun'
+    };
+
+    const requestStub = sandbox.stub();
+
+    requestStub.withArgs('CreateVpc', createParams, requestOption).resolves({
+      'VpcId': vpcId
+    });
+
+    const describeParms = {
+      'RegionId': region,
+      'VpcId': vpcId
+    };
+
+    requestStub.withArgs('DescribeVpcs', describeParms, requestOption)
+      .onCall(0).resolves({
+        'Vpcs': {
+          'Vpc': [
+            {
+              'Status': 'Pending'
+            }
+          ]
+        }
+      })
+      .onCall(1).resolves({
+        'Vpcs': {
+          'Vpc': [
+            {
+              'Status': 'Available'
+            }
+          ]
+        }
+      });
+
+    const vpcPopClient = { request: requestStub };
+
+    const v = await vpc.createVpc(vpcPopClient, region, vpcName);
+
+    expect(v).to.eql(vpcId);
+
+    assert.calledWith(requestStub.firstCall, 'CreateVpc', createParams, requestOption);
+    assert.calledWith(requestStub.secondCall, 'DescribeVpcs', describeParms, requestOption);
+    assert.calledWith(requestStub.thirdCall, 'DescribeVpcs', describeParms, requestOption);
+  });
+
+  it('test create vpc timeout', async () => {
+    var createParams = {
+      'RegionId': region,
+      'CidrBlock': '10.0.0.0/8',
+      'EnableIpv6': false,
+      'VpcName': vpcName,
+      'Description': 'default vpc created by fc fun'
+    };
+
+    const requestStub = sandbox.stub();
+
+    requestStub.withArgs('CreateVpc', createParams, requestOption).resolves({
+      'VpcId': vpcId
+    });
+
+    const describeParms = {
+      'RegionId': region,
+      'VpcId': vpcId
+    };
+
+    requestStub.withArgs('DescribeVpcs', describeParms, requestOption)
+      .resolves({
+        'Vpcs': {
+          'Vpc': [
+            {
+              'Status': 'Pending'
+            }
+          ]
+        }
+      });
+
+    const vpcPopClient = { request: requestStub };
+
+    try {
+      await vpc.createVpc(vpcPopClient, region, vpcName);
+      assert.fail('create vpc should not success');
+    } catch (e) {
+      expect(e.message).to.eql(`Timeout while waiting for vpc vpc-bp1dkyqjiu7hecg8j7jhe status to be 'Available'`);
+
+      assert.calledWith(requestStub.firstCall, 'CreateVpc', createParams, requestOption);
+      assert.callCount(requestStub, 1 + 15); // 1 CreateVpc, 15 DescribeVpcs
+    }
+  });
+});
+
diff --git a/test/vswitch.test.js b/test/vswitch.test.js
new file mode 100644
index 0000000..950fc80
--- /dev/null
+++ b/test/vswitch.test.js
@@ -0,0 +1,138 @@
+'use strict'; 
+
+const expect = require('expect.js');
+const sinon = require('sinon');
+const sandbox = sinon.createSandbox();
+const assert = sinon.assert;
+const vswitch = require('../lib/vswitch');
+
+const requestOption = {
+  method: 'POST'
+};
+
+const defualtVSwitchName = 'fc-fun-vswitch-1';
+const vpcId = 'vpc-bp1me4doa1zk2mwhksx4q';
+const hangzhouZoneId = 'cn-hangzhou-g';
+
+const region = 'cn-hangzhou';
+
+describe('test selectVSwitchZoneId', async () => {
+
+  afterEach(() => {
+    sandbox.restore();
+  });
+
+  it('test no allowed zone ', async () => {
+    const fcAllowedzones = ['cn-hangzhou-g'];
+    const vpcZones = [
+      {
+        'ZoneId': 'cn-hangzhou-b',
+        'LocalName': '华东 1 可用区 B'
+      },
+      {
+        'ZoneId': 'cn-hangzhou-d',
+        'LocalName': '华东 1 可用区 D'
+      }
+    ];
+
+    const rs = await vswitch.selectVSwitchZoneId(fcAllowedzones, vpcZones);
+    expect(rs).to.be(undefined);
+  });
+
+  it('test one allowed zone', async () => {
+    const fcAllowedzones = ['cn-hangzhou-g'];
+    const vpcZones = [
+      {
+        'ZoneId': 'cn-hangzhou-b',
+        'LocalName': '华东 1 可用区 B'
+      },
+      {
+        'ZoneId': 'cn-hangzhou-d',
+        'LocalName': '华东 1 可用区 D'
+      },
+      {
+        'ZoneId': 'cn-hangzhou-g',
+        'LocalName': '华东 1 可用区 G'
+      }
+    ];
+
+    const rs = await vswitch.selectVSwitchZoneId(fcAllowedzones, vpcZones);
+    expect(rs).to.eql('cn-hangzhou-g');
+  });
+});
+
+describe('test findVSwitchExistByName', async () => {
+
+  afterEach(() => {
+    sandbox.restore();
+  });
+
+  it('test none', async () => {
+    const vpcPopClient = { request: sandbox.stub() };
+
+    const searchRs = await vswitch.findVswitchExistByName(vpcPopClient, 'cn-hangzhou', [], 'test');
+    expect(searchRs).to.eql(null);
+  });
+
+  it('test findVSwitchExistByName', async () => {
+    const requestStub = sandbox.stub();
+
+    const params = {
+      'RegionId': region,
+      'VSwitchId': 'vsw-bp1h9eryqi6fjor4qvmor'
+    };
+
+    requestStub.withArgs('DescribeVSwitchAttributes',
+      params, requestOption).resolves({
+      'VSwitchName': defualtVSwitchName
+    });
+
+    const vpcPopClient = { request: requestStub };
+
+    const vswitchIds = ['vsw-bp1h9eryqi6fjor4qvmor'];
+
+    const searchRs = await vswitch.findVswitchExistByName(vpcPopClient, region, vswitchIds, defualtVSwitchName);
+    expect(searchRs).to.eql('vsw-bp1h9eryqi6fjor4qvmor');
+
+    assert.calledWith(requestStub, 'DescribeVSwitchAttributes', params, requestOption);
+  });
+});
+
+describe('test createVSwitch', async () => {
+
+  afterEach(() => {
+    sandbox.restore();
+  });
+
+  it('test createVSwitch', async () => {
+    const params = {
+      'RegionId': region,
+      'VpcId': vpcId,
+      'ZoneId': hangzhouZoneId,
+      'CidrBlock': '10.20.0.0/16',
+      'VSwitchName': defualtVSwitchName,
+      'Description': 'default vswitch created by fc fun'
+    };
+
+    const requestStub = sandbox.stub();
+
+    requestStub.withArgs('CreateVSwitch',
+      params, requestOption).resolves({
+      'VSwitchId': 'vsw-25naue4gz'
+    });
+
+    const vpcPopClient = { request: requestStub };
+
+    const vswitchId = await vswitch.createVSwitch(vpcPopClient, {
+      region,
+      vpcId,
+      zoneId: hangzhouZoneId,
+      vswitchName: defualtVSwitchName
+    });
+
+    expect(vswitchId).to.eql('vsw-25naue4gz');
+
+    assert.calledWith(requestStub, 'CreateVSwitch', params, requestOption);
+  });
+
+});
\ No newline at end of file