Repository: serverless/serverless (JavaScript)
Date: 2018-01-30 08:29:01+00:00
Task type: Feature

Issue:
Feature Request: Set Concurrency Limits on Individual AWS Lambda 
<!--
1. If you have a question and not a bug/feature request please ask it at http://forum.serverless.com
2. Please check if an issue already exists so there are no duplicates
3. Check out and follow our Guidelines: https://github.com/serverless/serverless/blob/master/CONTRIBUTING.md
4. Fill out the whole template so we have a good overview on the issue
5. Do not remove any section of the template. If something is not applicable leave it empty but leave it in the Issue
6. Please follow the template, otherwise we'll have to ask you to update it
-->

# This is a Feature Proposal

## Description

Following AWS announcement of the ability to set the concurrency limit per each Lambda function (https://aws.amazon.com/about-aws/whats-new/2017/11/set-concurrency-limits-on-individual-aws-lambda-functions/), it would be nice to get this ability through the serverless framework.

For bug reports:
* What went wrong?
* What did you expect should have happened?
* What was the config you used?
* What stacktrace or error message from your provider did you see?

For feature proposals:
* What is the use case that should be solved. The more detail you describe this in the easier it is to understand for us.
* If there is additional config how would it look

```
functions:
  hello:
    handler: handler.hello # required, handler set in AWS Lambda
    name: ${self:provider.stage}-lambdaName # optional, Deployed Lambda name
    description: Description of what the lambda function does 
    concurrency: 5
```



Similar or dependent issues:


## Additional Data

* ***Serverless Framework Version you're using***:
* ***Operating System***:
* ***Stack Trace***:
* ***Provider Error messages***:



Issue discussion:
Hi @itzaliza , great catch!

However, we have to check if CloudFormation already offers support for the new feature. Only if it is supported, we can make use of it.
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html
CloudFormation has not supported the feature yet. So current status for this issue is that waiting for support.
+1 on this
+1
+1 on this
But CloudFormation is still not updated... so we have to wait...
Totes need this +100500
+1, Why it is not ReservedConcurrentExecutions ? I think CloudFormation has this feature yet
+100
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-reservedconcurrentexecutions
@fantapop Cool. It is supported now, so we can continue here.

Open for implementation 💥 

Solution patch:
diff --git a/docs/providers/aws/guide/functions.md b/docs/providers/aws/guide/functions.md
index 2d6a970ed68..468701dce7f 100644
--- a/docs/providers/aws/guide/functions.md
+++ b/docs/providers/aws/guide/functions.md
@@ -37,6 +37,7 @@ functions:
     runtime: python2.7 # optional overwrite, default is provider runtime
     memorySize: 512 # optional, in MB, default is 1024
     timeout: 10 # optional, in seconds, default is 6
+    reservedConcurrency: 5 # optional, reserved concurrency limit for this function. By default, AWS uses account concurrency limit
 ```
 
 The `handler` property points to the file and module containing the code you want to run in your function.
diff --git a/lib/plugins/aws/package/compile/functions/index.js b/lib/plugins/aws/package/compile/functions/index.js
index 971bf1e2bd6..d880d0f1115 100644
--- a/lib/plugins/aws/package/compile/functions/index.js
+++ b/lib/plugins/aws/package/compile/functions/index.js
@@ -279,6 +279,19 @@ class AwsCompileFunctions {
       delete newFunction.Properties.VpcConfig;
     }
 
+    if (functionObject.reservedConcurrency) {
+      if (_.isInteger(functionObject.reservedConcurrency)) {
+        newFunction.Properties.ReservedConcurrentExecutions = functionObject.reservedConcurrency;
+      } else {
+        const errorMessage = [
+          'You should use integer as reservedConcurrency value on function: ',
+          `${newFunction.Properties.FunctionName}`,
+        ].join('');
+
+        return BbPromise.reject(new this.serverless.classes.Error(errorMessage));
+      }
+    }
+
     newFunction.DependsOn = [this.provider.naming.getLogGroupLogicalId(functionName)]
       .concat(newFunction.DependsOn || []);
 


Test patch:
diff --git a/lib/plugins/aws/package/compile/functions/index.test.js b/lib/plugins/aws/package/compile/functions/index.test.js
index b5e18c68424..d170bdeb217 100644
--- a/lib/plugins/aws/package/compile/functions/index.test.js
+++ b/lib/plugins/aws/package/compile/functions/index.test.js
@@ -25,6 +25,7 @@ describe('AwsCompileFunctions', () => {
     };
     serverless = new Serverless(options);
     serverless.setProvider('aws', new AwsProvider(serverless, options));
+    serverless.cli = new serverless.classes.CLI();
     awsCompileFunctions = new AwsCompileFunctions(serverless, options);
     awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate = {
       Resources: {},
@@ -1730,6 +1731,65 @@ describe('AwsCompileFunctions', () => {
         );
       });
     });
+
+    it('should set function declared reserved concurrency limit', () => {
+      const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName;
+      const s3FileName = awsCompileFunctions.serverless.service.package.artifact
+        .split(path.sep).pop();
+      awsCompileFunctions.serverless.service.functions = {
+        func: {
+          handler: 'func.function.handler',
+          name: 'new-service-dev-func',
+          reservedConcurrency: 5,
+        },
+      };
+      const compiledFunction = {
+        Type: 'AWS::Lambda::Function',
+        DependsOn: [
+          'FuncLogGroup',
+          'IamRoleLambdaExecution',
+        ],
+        Properties: {
+          Code: {
+            S3Key: `${s3Folder}/${s3FileName}`,
+            S3Bucket: { Ref: 'ServerlessDeploymentBucket' },
+          },
+          FunctionName: 'new-service-dev-func',
+          Handler: 'func.function.handler',
+          MemorySize: 1024,
+          ReservedConcurrentExecutions: 5,
+          Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] },
+          Runtime: 'nodejs4.3',
+          Timeout: 6,
+        },
+      };
+
+      return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled
+        .then(() => {
+          expect(
+            awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate
+              .Resources.FuncLambdaFunction
+          ).to.deep.equal(compiledFunction);
+        });
+    });
+
+    it('should throw an informative error message if non-integer reserved concurrency limit set ' +
+      'on function', () => {
+      awsCompileFunctions.serverless.service.functions = {
+        func: {
+          handler: 'func.function.handler',
+          name: 'new-service-dev-func',
+          reservedConcurrency: '1',
+        },
+      };
+
+      const errorMessage = [
+        'You should use integer as reservedConcurrency value on function: ',
+        'new-service-dev-func',
+      ].join('');
+
+      return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith(errorMessage);
+    });
   });
 
   describe('#compileRole()', () => {