Repository: serverless/serverless (JavaScript)
Date: 2019-12-06 16:53:35+00:00
Task type: Bug Fix

Issue:
Provisioned Concurrency Does Not Work As Described
# Bug Report

Provisioned Concurrency was recently added in Serverless v1.59 and described in detail in this blog post: https://serverless.com/blog/aws-lambda-provisioned-concurrency/

The blog post makes the claim that enabling provisioned concurrency is as easy as:

```
functions:
  hello:
    handler: handler.hello
      events:
        - http:
            path: /hello
            method: get
    provisionedConcurrency: 5
```

This is not true.

What this configuration will do is assign provisioned concurrency to the version number that was deployed, but API Gateway will continue invoking the Lambda with the $LATEST alias (no version). What effectively ends up happening is every deploy keeps assigning provisioned concurrency to each new version and never actually utilizes it. If a developer does not use any version pruning they will quickly rack up an expensive bill paying for new provisioned capacity on each version, and potentially worse, eventually exhaust their reserved (or unreserved) concurrency on their AWS account.

I think this is a critical issue and needs to, at the very least, be addressed in the blog post. Without some form of version cleanup or aliasing I don't think there's much Serverless can do to make this work in a simple configuration like that.



Issue discussion:
@AndrewBarba great thanks for reporting that issue

We will shortly (hopefully tomorrow) publish an update that fix the behavior in a framework (to make blog post accurate).
Thank you really appreciate it
@medikoo Would the fix you guys are making need to be applied/supported on all event types? Example, the API for specifying an alias/version for APIG is different than the api for ALB
Current idea is to create an alias `latest` (or similar) onto which provisioned concurrency will be setup, and ensure that APIGW endpoints will run against that.

If you have any better proposal, we'll definitely open to consider, any help is appreciated.
Definitely sounds right to me. 

Solution patch:
diff --git a/.travis.yml b/.travis.yml
index 09728c6d6d3..35cf4ce9d65 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -44,6 +44,8 @@ before_script:
   # Fail build right after first script fails. Travis doesn't ensure that: https://github.com/travis-ci/travis-ci/issues/1066
   # More info on below line: https://www.davidpashley.com/articles/writing-robust-shell-scripts/#idm5413512
   - set -e
+  - git config --global user.email "platform@serverless.com"
+  - git config --global user.name "Serverless CI"
 
 script:
   - npm test -- -b # Bail after first fail
@@ -154,21 +156,24 @@ jobs:
           if [ -n "$tagName" ]
           then
             git tag v$tagName
-            git push https://$GITHUB_TOKEN@github.com/serverless/serverless --tags
-            if [ $TRAVIS_BRANCH = master ]
+            if git push https://$GITHUB_TOKEN@github.com/serverless/serverless --tags
             then
-              # Ensure `release-fast-track` branch points latest release
-              git push -f https://$GITHUB_TOKEN@github.com/serverless/serverless master:release-fast-track
-            elif [ $TRAVIS_BRANCH = release-fast-track ]
-            then
-              # Fast forward to `master`
-              git fetch --unshallow
-              git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
-              git fetch origin master
-              git rebase origin/master
-              git checkout master
-              git merge release-fast-track
-              git push https://$GITHUB_TOKEN@github.com/serverless/serverless
+              if [ $TRAVIS_BRANCH = master ]
+              then
+                # Ensure `release-fast-track` branch points latest release
+                git push -f https://$GITHUB_TOKEN@github.com/serverless/serverless master:release-fast-track
+              elif [ $TRAVIS_BRANCH = release-fast-track ]
+              then
+                # Fast forward to `master`
+                git fetch --unshallow
+                git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
+                git fetch origin master
+                git checkout -b rebase-target
+                git rebase origin/master
+                git checkout master
+                git merge rebase-target
+                git push https://$GITHUB_TOKEN@github.com/serverless/serverless
+              fi
             fi
           fi
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7447e6c87ca..2bb7a91d8cb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,13 @@
 
 All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
 
+### [1.59.3](https://github.com/serverless/serverless/compare/v1.59.2...v1.59.3) (2019-12-09)
+
+### Bug Fixes
+
+- Do not set optional ParallelizationFactor when not explicitly set ([e74d1a0](https://github.com/serverless/serverless/commit/e74d1a0a6486fba1ca09c5eb54b36fcf552d60f4)), closes [#7049](https://github.com/serverless/serverless/issues/7049)
+- Fix provisioned concurrency support ([be0ebb7](https://github.com/serverless/serverless/commit/be0ebb76e7d3860587a986c9da48209870e7990d)), closes [#7059](https://github.com/serverless/serverless/issues/7059)
+
 ### [1.59.2](https://github.com/serverless/serverless/compare/v1.59.1...v1.59.2) (2019-12-06)
 
 ### Bug Fixes
diff --git a/lib/plugins/aws/lib/naming.js b/lib/plugins/aws/lib/naming.js
index 39f200b8dd5..2c1f5dfdc80 100644
--- a/lib/plugins/aws/lib/naming.js
+++ b/lib/plugins/aws/lib/naming.js
@@ -163,6 +163,9 @@ module.exports = {
       ''
     )}`;
   },
+  getLambdaOnlyVersionLogicalId(functionName) {
+    return `${this.getNormalizedFunctionName(functionName)}LambdaVersion`;
+  },
   getLambdaVersionOutputLogicalId(functionName) {
     return `${this.getLambdaLogicalId(functionName)}QualifiedArn`;
   },
diff --git a/lib/plugins/aws/package/compile/events/stream/index.js b/lib/plugins/aws/package/compile/events/stream/index.js
index 6ae838eda85..9b84b37e830 100644
--- a/lib/plugins/aws/package/compile/events/stream/index.js
+++ b/lib/plugins/aws/package/compile/events/stream/index.js
@@ -42,7 +42,7 @@ class AwsCompileStreamEvents {
           if (event.stream) {
             let EventSourceArn;
             let BatchSize = 10;
-            let ParallelizationFactor = 1;
+            let ParallelizationFactor;
             let StartingPosition = 'TRIM_HORIZON';
             let Enabled = 'True';
 
@@ -89,7 +89,9 @@ class AwsCompileStreamEvents {
               }
               EventSourceArn = event.stream.arn;
               BatchSize = event.stream.batchSize || BatchSize;
-              ParallelizationFactor = event.stream.parallelizationFactor || ParallelizationFactor;
+              if (event.stream.parallelizationFactor) {
+                ParallelizationFactor = event.stream.parallelizationFactor;
+              }
               StartingPosition = event.stream.startingPosition || StartingPosition;
               if (typeof event.stream.enabled !== 'undefined') {
                 Enabled = event.stream.enabled ? 'True' : 'False';
@@ -133,14 +135,14 @@ class AwsCompileStreamEvents {
             );
 
             const funcRole = functionObj.role || this.serverless.service.provider.role;
-            let dependsOn = '"IamRoleLambdaExecution"';
+            let dependsOn = 'IamRoleLambdaExecution';
             if (funcRole) {
               if (
                 // check whether the custom role is an ARN
                 typeof funcRole === 'string' &&
                 funcRole.indexOf(':') !== -1
               ) {
-                dependsOn = '[]';
+                dependsOn = [];
               } else if (
                 // otherwise, check if we have an in-service reference to a role ARN
                 typeof funcRole === 'object' &&
@@ -151,36 +153,31 @@ class AwsCompileStreamEvents {
                 typeof funcRole['Fn::GetAtt'][1] === 'string' &&
                 funcRole['Fn::GetAtt'][1] === 'Arn'
               ) {
-                dependsOn = `"${funcRole['Fn::GetAtt'][0]}"`;
+                dependsOn = funcRole['Fn::GetAtt'][0];
               } else if (
                 // otherwise, check if we have an import or parameters ref
                 typeof funcRole === 'object' &&
                 ('Fn::ImportValue' in funcRole || 'Ref' in funcRole)
               ) {
-                dependsOn = '[]';
+                dependsOn = [];
               } else if (typeof funcRole === 'string') {
-                dependsOn = `"${funcRole}"`;
+                dependsOn = funcRole;
               }
             }
-            const streamTemplate = `
-              {
-                "Type": "AWS::Lambda::EventSourceMapping",
-                "DependsOn": ${dependsOn},
-                "Properties": {
-                  "BatchSize": ${BatchSize},
-                  "ParallelizationFactor": ${ParallelizationFactor},
-                  "EventSourceArn": ${JSON.stringify(EventSourceArn)},
-                  "FunctionName": {
-                    "Fn::GetAtt": [
-                      "${lambdaLogicalId}",
-                      "Arn"
-                    ]
-                  },
-                  "StartingPosition": "${StartingPosition}",
-                  "Enabled": "${Enabled}"
-                }
-              }
-            `;
+            const streamResource = {
+              Type: 'AWS::Lambda::EventSourceMapping',
+              DependsOn: dependsOn,
+              Properties: {
+                BatchSize,
+                ParallelizationFactor,
+                EventSourceArn,
+                FunctionName: {
+                  'Fn::GetAtt': [lambdaLogicalId, 'Arn'],
+                },
+                StartingPosition,
+                Enabled,
+              },
+            };
 
             // add event source ARNs to PolicyDocument statements
             if (streamType === 'dynamodb') {
@@ -198,8 +195,6 @@ class AwsCompileStreamEvents {
               );
             }
 
-            const streamResource = JSON.parse(streamTemplate);
-
             if (event.stream.batchWindow) {
               streamResource.Properties.MaximumBatchingWindowInSeconds = event.stream.batchWindow;
             }
diff --git a/lib/plugins/aws/package/compile/functions/index.js b/lib/plugins/aws/package/compile/functions/index.js
index 7cb925d0617..3e6498ad4af 100644
--- a/lib/plugins/aws/package/compile/functions/index.js
+++ b/lib/plugins/aws/package/compile/functions/index.js
@@ -117,166 +117,211 @@ class AwsCompileFunctions {
   }
 
   compileFunction(functionName) {
-    const newFunction = this.cfLambdaFunctionTemplate();
-    const functionObject = this.serverless.service.getFunction(functionName);
-    functionObject.package = functionObject.package || {};
+    return BbPromise.try(() => {
+      const newFunction = this.cfLambdaFunctionTemplate();
+      const functionObject = this.serverless.service.getFunction(functionName);
+      functionObject.package = functionObject.package || {};
+
+      const serviceArtifactFileName = this.provider.naming.getServiceArtifactName();
+      const functionArtifactFileName = this.provider.naming.getFunctionArtifactName(functionName);
+
+      let artifactFilePath =
+        functionObject.package.artifact || this.serverless.service.package.artifact;
+
+      if (
+        !artifactFilePath ||
+        (this.serverless.service.artifact && !functionObject.package.artifact)
+      ) {
+        let artifactFileName = serviceArtifactFileName;
+        if (this.serverless.service.package.individually || functionObject.package.individually) {
+          artifactFileName = functionArtifactFileName;
+        }
 
-    const serviceArtifactFileName = this.provider.naming.getServiceArtifactName();
-    const functionArtifactFileName = this.provider.naming.getFunctionArtifactName(functionName);
+        artifactFilePath = path.join(
+          this.serverless.config.servicePath,
+          '.serverless',
+          artifactFileName
+        );
+      }
 
-    let artifactFilePath =
-      functionObject.package.artifact || this.serverless.service.package.artifact;
+      if (this.serverless.service.package.deploymentBucket) {
+        newFunction.Properties.Code.S3Bucket = this.serverless.service.package.deploymentBucket;
+      }
 
-    if (
-      !artifactFilePath ||
-      (this.serverless.service.artifact && !functionObject.package.artifact)
-    ) {
-      let artifactFileName = serviceArtifactFileName;
-      if (this.serverless.service.package.individually || functionObject.package.individually) {
-        artifactFileName = functionArtifactFileName;
+      const s3Folder = this.serverless.service.package.artifactDirectoryName;
+      const s3FileName = artifactFilePath.split(path.sep).pop();
+      newFunction.Properties.Code.S3Key = `${s3Folder}/${s3FileName}`;
+
+      if (!functionObject.handler) {
+        const errorMessage = [
+          `Missing "handler" property in function "${functionName}".`,
+          ' Please make sure you point to the correct lambda handler.',
+          ' For example: handler.hello.',
+          ' Please check the docs for more info',
+        ].join('');
+        throw new this.serverless.classes.Error(errorMessage);
       }
 
-      artifactFilePath = path.join(
-        this.serverless.config.servicePath,
-        '.serverless',
-        artifactFileName
-      );
-    }
+      const Handler = functionObject.handler;
+      const FunctionName = functionObject.name;
+      const MemorySize =
+        Number(functionObject.memorySize) ||
+        Number(this.serverless.service.provider.memorySize) ||
+        1024;
+      const Timeout =
+        Number(functionObject.timeout) || Number(this.serverless.service.provider.timeout) || 6;
+      const Runtime =
+        functionObject.runtime || this.serverless.service.provider.runtime || 'nodejs12.x';
+
+      newFunction.Properties.Handler = Handler;
+      newFunction.Properties.FunctionName = FunctionName;
+      newFunction.Properties.MemorySize = MemorySize;
+      newFunction.Properties.Timeout = Timeout;
+      newFunction.Properties.Runtime = Runtime;
+
+      // publish these properties to the platform
+      this.serverless.service.functions[functionName].memory = MemorySize;
+      this.serverless.service.functions[functionName].timeout = Timeout;
+      this.serverless.service.functions[functionName].runtime = Runtime;
 
-    if (this.serverless.service.package.deploymentBucket) {
-      newFunction.Properties.Code.S3Bucket = this.serverless.service.package.deploymentBucket;
-    }
+      if (functionObject.description) {
+        newFunction.Properties.Description = functionObject.description;
+      }
 
-    const s3Folder = this.serverless.service.package.artifactDirectoryName;
-    const s3FileName = artifactFilePath.split(path.sep).pop();
-    newFunction.Properties.Code.S3Key = `${s3Folder}/${s3FileName}`;
-
-    if (!functionObject.handler) {
-      const errorMessage = [
-        `Missing "handler" property in function "${functionName}".`,
-        ' Please make sure you point to the correct lambda handler.',
-        ' For example: handler.hello.',
-        ' Please check the docs for more info',
-      ].join('');
-      return BbPromise.reject(new this.serverless.classes.Error(errorMessage));
-    }
+      if (functionObject.condition) {
+        newFunction.Condition = functionObject.condition;
+      }
 
-    const Handler = functionObject.handler;
-    const FunctionName = functionObject.name;
-    const MemorySize =
-      Number(functionObject.memorySize) ||
-      Number(this.serverless.service.provider.memorySize) ||
-      1024;
-    const Timeout =
-      Number(functionObject.timeout) || Number(this.serverless.service.provider.timeout) || 6;
-    const Runtime =
-      functionObject.runtime || this.serverless.service.provider.runtime || 'nodejs12.x';
-
-    newFunction.Properties.Handler = Handler;
-    newFunction.Properties.FunctionName = FunctionName;
-    newFunction.Properties.MemorySize = MemorySize;
-    newFunction.Properties.Timeout = Timeout;
-    newFunction.Properties.Runtime = Runtime;
-
-    // publish these properties to the platform
-    this.serverless.service.functions[functionName].memory = MemorySize;
-    this.serverless.service.functions[functionName].timeout = Timeout;
-    this.serverless.service.functions[functionName].runtime = Runtime;
-
-    if (functionObject.description) {
-      newFunction.Properties.Description = functionObject.description;
-    }
+      if (functionObject.dependsOn) {
+        newFunction.DependsOn = (newFunction.DependsOn || []).concat(functionObject.dependsOn);
+      }
 
-    if (functionObject.condition) {
-      newFunction.Condition = functionObject.condition;
-    }
+      if (functionObject.tags || this.serverless.service.provider.tags) {
+        const tags = Object.assign({}, this.serverless.service.provider.tags, functionObject.tags);
+        newFunction.Properties.Tags = [];
+        _.forEach(tags, (Value, Key) => {
+          newFunction.Properties.Tags.push({ Key, Value });
+        });
+      }
 
-    if (functionObject.dependsOn) {
-      newFunction.DependsOn = (newFunction.DependsOn || []).concat(functionObject.dependsOn);
-    }
+      if (functionObject.onError) {
+        const arn = functionObject.onError;
 
-    if (functionObject.tags || this.serverless.service.provider.tags) {
-      const tags = Object.assign({}, this.serverless.service.provider.tags, functionObject.tags);
-      newFunction.Properties.Tags = [];
-      _.forEach(tags, (Value, Key) => {
-        newFunction.Properties.Tags.push({ Key, Value });
-      });
-    }
+        if (typeof arn === 'string') {
+          const splittedArn = arn.split(':');
+          if (splittedArn[0] === 'arn' && (splittedArn[2] === 'sns' || splittedArn[2] === 'sqs')) {
+            const dlqType = splittedArn[2];
+            const iamRoleLambdaExecution = this.serverless.service.provider
+              .compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution;
+            let stmt;
 
-    if (functionObject.onError) {
-      const arn = functionObject.onError;
+            newFunction.Properties.DeadLetterConfig = {
+              TargetArn: arn,
+            };
 
-      if (typeof arn === 'string') {
-        const splittedArn = arn.split(':');
-        if (splittedArn[0] === 'arn' && (splittedArn[2] === 'sns' || splittedArn[2] === 'sqs')) {
-          const dlqType = splittedArn[2];
-          const iamRoleLambdaExecution = this.serverless.service.provider
-            .compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution;
-          let stmt;
+            if (dlqType === 'sns') {
+              stmt = {
+                Effect: 'Allow',
+                Action: ['sns:Publish'],
+                Resource: [arn],
+              };
+            } else if (dlqType === 'sqs') {
+              const errorMessage = [
+                'onError currently only supports SNS topic arns due to a',
+                ' race condition when using SQS queue arns and updating the IAM role.',
+                ' Please check the docs for more info.',
+              ].join('');
+              throw new this.serverless.classes.Error(errorMessage);
+            }
 
+            // update the PolicyDocument statements (if default policy is used)
+            if (iamRoleLambdaExecution) {
+              iamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement.push(stmt);
+            }
+          } else {
+            const errorMessage = 'onError config must be a SNS topic arn or SQS queue arn';
+            throw new this.serverless.classes.Error(errorMessage);
+          }
+        } else if (this.isArnRefGetAttOrImportValue(arn)) {
           newFunction.Properties.DeadLetterConfig = {
             TargetArn: arn,
           };
+        } else {
+          const errorMessage = [
+            'onError config must be provided as an arn string,',
+            ' Ref, Fn::GetAtt or Fn::ImportValue',
+          ].join('');
+          throw new this.serverless.classes.Error(errorMessage);
+        }
+      }
 
-          if (dlqType === 'sns') {
-            stmt = {
+      let kmsKeyArn;
+      const serviceObj = this.serverless.service.serviceObject;
+      if ('awsKmsKeyArn' in functionObject) {
+        kmsKeyArn = functionObject.awsKmsKeyArn;
+      } else if (serviceObj && 'awsKmsKeyArn' in serviceObj) {
+        kmsKeyArn = serviceObj.awsKmsKeyArn;
+      }
+
+      if (kmsKeyArn) {
+        const arn = kmsKeyArn;
+
+        if (typeof arn === 'string') {
+          const splittedArn = arn.split(':');
+          if (splittedArn[0] === 'arn' && splittedArn[2] === 'kms') {
+            const iamRoleLambdaExecution = this.serverless.service.provider
+              .compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution;
+
+            newFunction.Properties.KmsKeyArn = arn;
+
+            const stmt = {
               Effect: 'Allow',
-              Action: ['sns:Publish'],
+              Action: ['kms:Decrypt'],
               Resource: [arn],
             };
-          } else if (dlqType === 'sqs') {
-            const errorMessage = [
-              'onError currently only supports SNS topic arns due to a',
-              ' race condition when using SQS queue arns and updating the IAM role.',
-              ' Please check the docs for more info.',
-            ].join('');
-            return BbPromise.reject(new this.serverless.classes.Error(errorMessage));
-          }
 
-          // update the PolicyDocument statements (if default policy is used)
-          if (iamRoleLambdaExecution) {
-            iamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement.push(stmt);
+            // update the PolicyDocument statements (if default policy is used)
+            if (iamRoleLambdaExecution) {
+              iamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement = _.unionWith(
+                iamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement,
+                [stmt],
+                _.isEqual
+              );
+            }
+          } else {
+            const errorMessage = 'awsKmsKeyArn config must be a KMS key arn';
+            throw new this.serverless.classes.Error(errorMessage);
           }
         } else {
-          const errorMessage = 'onError config must be a SNS topic arn or SQS queue arn';
-          return BbPromise.reject(new this.serverless.classes.Error(errorMessage));
+          const errorMessage = 'awsKmsKeyArn config must be provided as a string';
+          throw new this.serverless.classes.Error(errorMessage);
         }
-      } else if (this.isArnRefGetAttOrImportValue(arn)) {
-        newFunction.Properties.DeadLetterConfig = {
-          TargetArn: arn,
-        };
-      } else {
-        const errorMessage = [
-          'onError config must be provided as an arn string,',
-          ' Ref, Fn::GetAtt or Fn::ImportValue',
-        ].join('');
-        return BbPromise.reject(new this.serverless.classes.Error(errorMessage));
       }
-    }
 
-    let kmsKeyArn;
-    const serviceObj = this.serverless.service.serviceObject;
-    if ('awsKmsKeyArn' in functionObject) {
-      kmsKeyArn = functionObject.awsKmsKeyArn;
-    } else if (serviceObj && 'awsKmsKeyArn' in serviceObj) {
-      kmsKeyArn = serviceObj.awsKmsKeyArn;
-    }
+      const tracing =
+        functionObject.tracing ||
+        (this.serverless.service.provider.tracing &&
+          this.serverless.service.provider.tracing.lambda);
+
+      if (tracing) {
+        if (typeof tracing === 'boolean' || typeof tracing === 'string') {
+          let mode = tracing;
 
-    if (kmsKeyArn) {
-      const arn = kmsKeyArn;
+          if (typeof tracing === 'boolean') {
+            mode = 'Active';
+          }
 
-      if (typeof arn === 'string') {
-        const splittedArn = arn.split(':');
-        if (splittedArn[0] === 'arn' && splittedArn[2] === 'kms') {
           const iamRoleLambdaExecution = this.serverless.service.provider
             .compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution;
 
-          newFunction.Properties.KmsKeyArn = arn;
+          newFunction.Properties.TracingConfig = {
+            Mode: mode,
+          };
 
           const stmt = {
             Effect: 'Allow',
-            Action: ['kms:Decrypt'],
-            Resource: [arn],
+            Action: ['xray:PutTraceSegments', 'xray:PutTelemetryRecords'],
+            Resource: ['*'],
           };
 
           // update the PolicyDocument statements (if default policy is used)
@@ -288,253 +333,221 @@ class AwsCompileFunctions {
             );
           }
         } else {
-          const errorMessage = 'awsKmsKeyArn config must be a KMS key arn';
-          return BbPromise.reject(new this.serverless.classes.Error(errorMessage));
+          const errorMessage =
+            'tracing requires a boolean value or the "mode" provided as a string';
+          throw new this.serverless.classes.Error(errorMessage);
         }
-      } else {
-        const errorMessage = 'awsKmsKeyArn config must be provided as a string';
-        return BbPromise.reject(new this.serverless.classes.Error(errorMessage));
       }
-    }
 
-    const tracing =
-      functionObject.tracing ||
-      (this.serverless.service.provider.tracing && this.serverless.service.provider.tracing.lambda);
+      if (functionObject.environment || this.serverless.service.provider.environment) {
+        newFunction.Properties.Environment = {};
+        newFunction.Properties.Environment.Variables = Object.assign(
+          {},
+          this.serverless.service.provider.environment,
+          functionObject.environment
+        );
+
+        let invalidEnvVar = null;
+        _.forEach(_.keys(newFunction.Properties.Environment.Variables), key => {
+          // taken from the bash man pages
+          if (!key.match(/^[A-Za-z_][a-zA-Z0-9_]*$/)) {
+            invalidEnvVar = `Invalid characters in environment variable ${key}`;
+            return false; // break loop with lodash
+          }
+          const value = newFunction.Properties.Environment.Variables[key];
+          if (_.isObject(value)) {
+            const isCFRef =
+              _.isObject(value) &&
+              !_.some(value, (v, k) => k !== 'Ref' && !_.startsWith(k, 'Fn::'));
+            if (!isCFRef) {
+              invalidEnvVar = `Environment variable ${key} must contain string`;
+              return false;
+            }
+          }
+          return true;
+        });
 
-    if (tracing) {
-      if (typeof tracing === 'boolean' || typeof tracing === 'string') {
-        let mode = tracing;
+        if (invalidEnvVar) throw new this.serverless.classes.Error(invalidEnvVar);
+      }
 
-        if (typeof tracing === 'boolean') {
-          mode = 'Active';
-        }
+      if ('role' in functionObject) {
+        this.compileRole(newFunction, functionObject.role);
+      } else if ('role' in this.serverless.service.provider) {
+        this.compileRole(newFunction, this.serverless.service.provider.role);
+      } else {
+        this.compileRole(newFunction, 'IamRoleLambdaExecution');
+      }
 
-        const iamRoleLambdaExecution = this.serverless.service.provider
-          .compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution;
+      if (!functionObject.vpc) functionObject.vpc = {};
+      if (!this.serverless.service.provider.vpc) this.serverless.service.provider.vpc = {};
 
-        newFunction.Properties.TracingConfig = {
-          Mode: mode,
-        };
+      newFunction.Properties.VpcConfig = {
+        SecurityGroupIds:
+          functionObject.vpc.securityGroupIds ||
+          this.serverless.service.provider.vpc.securityGroupIds,
+        SubnetIds: functionObject.vpc.subnetIds || this.serverless.service.provider.vpc.subnetIds,
+      };
 
-        const stmt = {
-          Effect: 'Allow',
-          Action: ['xray:PutTraceSegments', 'xray:PutTelemetryRecords'],
-          Resource: ['*'],
-        };
+      if (
+        !newFunction.Properties.VpcConfig.SecurityGroupIds ||
+        !newFunction.Properties.VpcConfig.SubnetIds
+      ) {
+        delete newFunction.Properties.VpcConfig;
+      }
 
-        // update the PolicyDocument statements (if default policy is used)
-        if (iamRoleLambdaExecution) {
-          iamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement = _.unionWith(
-            iamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement,
-            [stmt],
-            _.isEqual
-          );
+      if (functionObject.reservedConcurrency || functionObject.reservedConcurrency === 0) {
+        // Try convert reservedConcurrency to integer
+        const reservedConcurrency = _.parseInt(functionObject.reservedConcurrency);
+
+        if (_.isInteger(reservedConcurrency)) {
+          newFunction.Properties.ReservedConcurrentExecutions = reservedConcurrency;
+        } else {
+          const errorMessage = [
+            'You should use integer as reservedConcurrency value on function: ',
+            `${newFunction.Properties.FunctionName}`,
+          ].join('');
+
+          throw new this.serverless.classes.Error(errorMessage);
         }
-      } else {
-        const errorMessage = 'tracing requires a boolean value or the "mode" provided as a string';
-        throw new this.serverless.classes.Error(errorMessage);
       }
-    }
 
-    if (functionObject.environment || this.serverless.service.provider.environment) {
-      newFunction.Properties.Environment = {};
-      newFunction.Properties.Environment.Variables = Object.assign(
-        {},
-        this.serverless.service.provider.environment,
-        functionObject.environment
+      newFunction.DependsOn = [this.provider.naming.getLogGroupLogicalId(functionName)].concat(
+        newFunction.DependsOn || []
       );
 
-      let invalidEnvVar = null;
-      _.forEach(_.keys(newFunction.Properties.Environment.Variables), key => {
-        // taken from the bash man pages
-        if (!key.match(/^[A-Za-z_][a-zA-Z0-9_]*$/)) {
-          invalidEnvVar = `Invalid characters in environment variable ${key}`;
-          return false; // break loop with lodash
-        }
-        const value = newFunction.Properties.Environment.Variables[key];
-        if (_.isObject(value)) {
-          const isCFRef =
-            _.isObject(value) && !_.some(value, (v, k) => k !== 'Ref' && !_.startsWith(k, 'Fn::'));
-          if (!isCFRef) {
-            invalidEnvVar = `Environment variable ${key} must contain string`;
-            return false;
-          }
-        }
-        return true;
-      });
-
-      if (invalidEnvVar) {
-        return BbPromise.reject(new this.serverless.classes.Error(invalidEnvVar));
+      if (functionObject.layers && _.isArray(functionObject.layers)) {
+        newFunction.Properties.Layers = functionObject.layers;
+      } else if (
+        this.serverless.service.provider.layers &&
+        _.isArray(this.serverless.service.provider.layers)
+      ) {
+        newFunction.Properties.Layers = this.serverless.service.provider.layers;
       }
-    }
 
-    if ('role' in functionObject) {
-      this.compileRole(newFunction, functionObject.role);
-    } else if ('role' in this.serverless.service.provider) {
-      this.compileRole(newFunction, this.serverless.service.provider.role);
-    } else {
-      this.compileRole(newFunction, 'IamRoleLambdaExecution');
-    }
+      const functionLogicalId = this.provider.naming.getLambdaLogicalId(functionName);
+      const newFunctionObject = {
+        [functionLogicalId]: newFunction,
+      };
 
-    if (!functionObject.vpc) functionObject.vpc = {};
-    if (!this.serverless.service.provider.vpc) this.serverless.service.provider.vpc = {};
+      _.merge(
+        this.serverless.service.provider.compiledCloudFormationTemplate.Resources,
+        newFunctionObject
+      );
 
-    newFunction.Properties.VpcConfig = {
-      SecurityGroupIds:
-        functionObject.vpc.securityGroupIds ||
-        this.serverless.service.provider.vpc.securityGroupIds,
-      SubnetIds: functionObject.vpc.subnetIds || this.serverless.service.provider.vpc.subnetIds,
-    };
+      const versionFunction =
+        functionObject.versionFunction != null
+          ? functionObject.versionFunction
+          : this.serverless.service.provider.versionFunctions;
 
-    if (
-      !newFunction.Properties.VpcConfig.SecurityGroupIds ||
-      !newFunction.Properties.VpcConfig.SubnetIds
-    ) {
-      delete newFunction.Properties.VpcConfig;
-    }
+      if (functionObject.provisionedConcurrency) {
+        if (versionFunction) {
+          const errorMessage = [
+            'Sorry, at this point,provisioned conncurrency for ' +
+              `${newFunction.Properties.FunctionName}\n`,
+            'cannot be setup together with lambda versioning enabled.\n\n',
+            'If you want to take advantage of provisioned concurrency, please turn off lambda versioning.\n\n',
+            "We're working on improving that experience, stay tuned for upcoming updates.",
+          ].join('');
+          throw new this.serverless.classes.Error(errorMessage);
+        }
 
-    if (functionObject.reservedConcurrency || functionObject.reservedConcurrency === 0) {
-      // Try convert reservedConcurrency to integer
-      const reservedConcurrency = _.parseInt(functionObject.reservedConcurrency);
+        const provisionedConcurrency = _.parseInt(functionObject.provisionedConcurrency);
 
-      if (_.isInteger(reservedConcurrency)) {
-        newFunction.Properties.ReservedConcurrentExecutions = reservedConcurrency;
-      } else {
-        const errorMessage = [
-          'You should use integer as reservedConcurrency value on function: ',
-          `${newFunction.Properties.FunctionName}`,
-        ].join('');
+        if (!_.isInteger(provisionedConcurrency)) {
+          throw new this.serverless.classes.Error(
+            'You should use integer as provisionedConcurrency value on function: ' +
+              `${newFunction.Properties.FunctionName}`
+          );
+        }
 
-        return BbPromise.reject(new this.serverless.classes.Error(errorMessage));
+        this.serverless.service.provider.compiledCloudFormationTemplate.Resources[
+          this.provider.naming.getLambdaOnlyVersionLogicalId(functionName)
+        ] = {
+          Type: 'AWS::Lambda::Version',
+          Properties: {
+            FunctionName: { Ref: functionLogicalId },
+            ProvisionedConcurrencyConfig: {
+              ProvisionedConcurrentExecutions: provisionedConcurrency,
+            },
+          },
+        };
       }
-    }
-
-    const versionFunction =
-      functionObject.versionFunction != null
-        ? functionObject.versionFunction
-        : this.serverless.service.provider.versionFunctions;
-
-    if (functionObject.provisionedConcurrency && !versionFunction) {
-      return BbPromise.reject(
-        new this.serverless.classes.Error(
-          'Cannot setup provisioned conncurrency with lambda versioning disabled on function: ' +
-            `${newFunction.Properties.FunctionName}`
-        )
-      );
-    }
 
-    newFunction.DependsOn = [this.provider.naming.getLogGroupLogicalId(functionName)].concat(
-      newFunction.DependsOn || []
-    );
+      if (!versionFunction) return null;
 
-    if (functionObject.layers && _.isArray(functionObject.layers)) {
-      newFunction.Properties.Layers = functionObject.layers;
-    } else if (
-      this.serverless.service.provider.layers &&
-      _.isArray(this.serverless.service.provider.layers)
-    ) {
-      newFunction.Properties.Layers = this.serverless.service.provider.layers;
-    }
+      // Create hashes for the artifact and the logical id of the version resource
+      // The one for the version resource must include the function configuration
+      // to make sure that a new version is created on configuration changes and
+      // not only on source changes.
+      const fileHash = crypto.createHash('sha256');
+      const versionHash = crypto.createHash('sha256');
+      fileHash.setEncoding('base64');
+      versionHash.setEncoding('base64');
 
-    const functionLogicalId = this.provider.naming.getLambdaLogicalId(functionName);
-    const newFunctionObject = {
-      [functionLogicalId]: newFunction,
-    };
-
-    _.merge(
-      this.serverless.service.provider.compiledCloudFormationTemplate.Resources,
-      newFunctionObject
-    );
+      // Read the file in chunks and add them to the hash (saves memory and performance)
+      return BbPromise.fromCallback(cb => {
+        const readStream = fs.createReadStream(artifactFilePath);
 
-    // Create hashes for the artifact and the logical id of the version resource
-    // The one for the version resource must include the function configuration
-    // to make sure that a new version is created on configuration changes and
-    // not only on source changes.
-    const fileHash = crypto.createHash('sha256');
-    const versionHash = crypto.createHash('sha256');
-    fileHash.setEncoding('base64');
-    versionHash.setEncoding('base64');
-
-    // Read the file in chunks and add them to the hash (saves memory and performance)
-    return BbPromise.fromCallback(cb => {
-      const readStream = fs.createReadStream(artifactFilePath);
-
-      readStream
-        .on('data', chunk => {
-          fileHash.write(chunk);
-          versionHash.write(chunk);
-        })
-        .on('close', () => {
-          cb();
-        })
-        .on('error', error => {
-          cb(error);
+        readStream
+          .on('data', chunk => {
+            fileHash.write(chunk);
+            versionHash.write(chunk);
+          })
+          .on('close', () => {
+            cb();
+          })
+          .on('error', error => {
+            cb(error);
+          });
+      }).then(() => {
+        // Include function configuration in version id hash (without the Code part)
+        const properties = _.omit(_.get(newFunction, 'Properties', {}), 'Code');
+        _.forOwn(properties, value => {
+          const hashedValue = _.isObject(value) ? JSON.stringify(value) : _.toString(value);
+          versionHash.write(hashedValue);
         });
-    }).then(() => {
-      // Include function configuration in version id hash (without the Code part)
-      const properties = _.omit(_.get(newFunction, 'Properties', {}), 'Code');
-      _.forOwn(properties, value => {
-        const hashedValue = _.isObject(value) ? JSON.stringify(value) : _.toString(value);
-        versionHash.write(hashedValue);
-      });
-
-      // Finalize hashes
-      fileHash.end();
-      versionHash.end();
 
-      const fileDigest = fileHash.read();
-      const versionDigest = versionHash.read();
+        // Finalize hashes
+        fileHash.end();
+        versionHash.end();
 
-      if (!versionFunction) return;
-
-      const newVersion = this.cfLambdaVersionTemplate();
-
-      newVersion.Properties.CodeSha256 = fileDigest;
-      newVersion.Properties.FunctionName = { Ref: functionLogicalId };
-      if (functionObject.description) {
-        newVersion.Properties.Description = functionObject.description;
-      }
+        const fileDigest = fileHash.read();
+        const versionDigest = versionHash.read();
 
-      if (functionObject.provisionedConcurrency) {
-        const provisionedConcurrency = _.parseInt(functionObject.provisionedConcurrency);
+        const newVersion = this.cfLambdaVersionTemplate();
 
-        if (_.isInteger(provisionedConcurrency)) {
-          newVersion.Properties.ProvisionedConcurrencyConfig = {
-            ProvisionedConcurrentExecutions: provisionedConcurrency,
-          };
-        } else {
-          throw new this.serverless.classes.Error(
-            'You should use integer as provisionedConcurrency value on function: ' +
-              `${newFunction.Properties.FunctionName}`
-          );
+        newVersion.Properties.CodeSha256 = fileDigest;
+        newVersion.Properties.FunctionName = { Ref: functionLogicalId };
+        if (functionObject.description) {
+          newVersion.Properties.Description = functionObject.description;
         }
-      }
 
-      // use the version SHA in the logical resource ID of the version because
-      // AWS::Lambda::Version resource will not support updates
-      const versionLogicalId = this.provider.naming.getLambdaVersionLogicalId(
-        functionName,
-        versionDigest
-      );
-      const newVersionObject = {
-        [versionLogicalId]: newVersion,
-      };
+        // use the version SHA in the logical resource ID of the version because
+        // AWS::Lambda::Version resource will not support updates
+        const versionLogicalId = this.provider.naming.getLambdaVersionLogicalId(
+          functionName,
+          versionDigest
+        );
+        const newVersionObject = {
+          [versionLogicalId]: newVersion,
+        };
 
-      _.merge(
-        this.serverless.service.provider.compiledCloudFormationTemplate.Resources,
-        newVersionObject
-      );
+        _.merge(
+          this.serverless.service.provider.compiledCloudFormationTemplate.Resources,
+          newVersionObject
+        );
 
-      // Add function versions to Outputs section
-      const functionVersionOutputLogicalId = this.provider.naming.getLambdaVersionOutputLogicalId(
-        functionName
-      );
-      const newVersionOutput = this.cfOutputLatestVersionTemplate();
+        // Add function versions to Outputs section
+        const functionVersionOutputLogicalId = this.provider.naming.getLambdaVersionOutputLogicalId(
+          functionName
+        );
+        const newVersionOutput = this.cfOutputLatestVersionTemplate();
 
-      newVersionOutput.Value = { Ref: versionLogicalId };
+        newVersionOutput.Value = { Ref: versionLogicalId };
 
-      _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Outputs, {
-        [functionVersionOutputLogicalId]: newVersionOutput,
+        _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Outputs, {
+          [functionVersionOutputLogicalId]: newVersionOutput,
+        });
       });
     });
   }
diff --git a/package.json b/package.json
index 846cbec979d..9c1e3c1f781 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "serverless",
-  "version": "1.59.2",
+  "version": "1.59.3",
   "engines": {
     "node": ">=6.0"
   },


Test patch:
diff --git a/lib/plugins/aws/package/compile/events/stream/index.test.js b/lib/plugins/aws/package/compile/events/stream/index.test.js
index f3c129491dd..8ab31a7f881 100644
--- a/lib/plugins/aws/package/compile/events/stream/index.test.js
+++ b/lib/plugins/aws/package/compile/events/stream/index.test.js
@@ -807,7 +807,7 @@ describe('AwsCompileStreamEvents', () => {
         expect(
           awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate
             .Resources.FirstEventSourceMappingKinesisBar.Properties.ParallelizationFactor
-        ).to.equal(1);
+        ).to.equal(undefined);
         expect(
           awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate
             .Resources.FirstEventSourceMappingKinesisBar.Properties.StartingPosition
diff --git a/lib/plugins/aws/package/compile/functions/index.test.js b/lib/plugins/aws/package/compile/functions/index.test.js
index 45c27e33594..e9f094d8e68 100644
--- a/lib/plugins/aws/package/compile/functions/index.test.js
+++ b/lib/plugins/aws/package/compile/functions/index.test.js
@@ -2206,17 +2206,14 @@ describe('AwsCompileFunctions', () => {
           handler: 'func.function.handler',
           name: 'new-service-dev-func',
           provisionedConcurrency: 5,
+          versionFunction: false,
         },
       };
 
       return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => {
-        const resourceId =
-          awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Outputs
-            .FuncLambdaFunctionQualifiedArn.Value.Ref;
         expect(
-          awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources[
-            resourceId
-          ].Properties.ProvisionedConcurrencyConfig
+          awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources
+            .FuncLambdaVersion.Properties.ProvisionedConcurrencyConfig
         ).to.deep.equal({ ProvisionedConcurrentExecutions: 5 });
       });
     });