diff --git a/apis/v1alpha1/ack-generate-metadata.yaml b/apis/v1alpha1/ack-generate-metadata.yaml index 8a3d008f..815e7202 100755 --- a/apis/v1alpha1/ack-generate-metadata.yaml +++ b/apis/v1alpha1/ack-generate-metadata.yaml @@ -1,13 +1,13 @@ ack_generate_info: - build_date: "2023-07-17T23:54:05Z" + build_date: "2023-07-24T18:19:28Z" build_hash: e9b68590da73ce9143ba1e4361cebdc1d876c81e - go_version: go1.20.5 + go_version: go1.19 version: v0.26.1-7-ge9b6859 -api_directory_checksum: 5d5c7aea8863c47e7303cc870aad4250267d93d2 +api_directory_checksum: 8f80588f678cbcff979aa67cf82e547315fdb2b8 api_version: v1alpha1 aws_sdk_go_version: v1.44.181 generator_config_info: - file_checksum: a3a0fb7f87067244de99c0b37aaaf917b026f03d + file_checksum: 8b0c7bd625c74c9a471db45bbf8697069d35b218 original_file_name: generator.yaml last_modification: reason: API generation diff --git a/apis/v1alpha1/alias.go b/apis/v1alpha1/alias.go index 4705d029..2b1ecc73 100644 --- a/apis/v1alpha1/alias.go +++ b/apis/v1alpha1/alias.go @@ -64,6 +64,12 @@ type AliasSpec struct { // The name of the alias. // +kubebuilder:validation:Required Name *string `json:"name"` + // Configures provisioned concurrency to a function's alias + // + // - ProvisionedConcurrentExecutions + // The amount of provisioned concurrency to allocate for the version or alias. + // Minimum value of 1 is required + ProvisionedConcurrencyConfig *PutProvisionedConcurrencyConfigInput `json:"provisionedConcurrencyConfig,omitempty"` // The routing configuration (https://docs.aws.amazon.com/lambda/latest/dg/configuration-aliases.html#configuring-alias-routing) // of the alias. RoutingConfig *AliasRoutingConfiguration `json:"routingConfig,omitempty"` diff --git a/apis/v1alpha1/generator.yaml b/apis/v1alpha1/generator.yaml index 193114cc..81f252ab 100644 --- a/apis/v1alpha1/generator.yaml +++ b/apis/v1alpha1/generator.yaml @@ -108,6 +108,10 @@ resources: from: operation: PutFunctionEventInvokeConfig path: . + ProvisionedConcurrencyConfig: + from: + operation: PutProvisionedConcurrencyConfig + path: . hooks: sdk_update_pre_build_request: template_path: hooks/alias/sdk_update_pre_build_request.go.tpl diff --git a/apis/v1alpha1/types.go b/apis/v1alpha1/types.go index a32c3620..43a87004 100644 --- a/apis/v1alpha1/types.go +++ b/apis/v1alpha1/types.go @@ -381,9 +381,10 @@ type OnSuccess struct { // Details about the provisioned concurrency configuration for a function alias // or version. type ProvisionedConcurrencyConfigListItem struct { - FunctionARN *string `json:"functionARN,omitempty"` - LastModified *string `json:"lastModified,omitempty"` - StatusReason *string `json:"statusReason,omitempty"` + FunctionARN *string `json:"functionARN,omitempty"` + LastModified *string `json:"lastModified,omitempty"` + RequestedProvisionedConcurrentExecutions *int64 `json:"requestedProvisionedConcurrentExecutions,omitempty"` + StatusReason *string `json:"statusReason,omitempty"` } type PutFunctionConcurrencyOutput struct { @@ -400,6 +401,12 @@ type PutFunctionEventInvokeConfigInput struct { Qualifier *string `json:"qualifier,omitempty"` } +type PutProvisionedConcurrencyConfigInput struct { + FunctionName *string `json:"functionName,omitempty"` + ProvisionedConcurrentExecutions *int64 `json:"provisionedConcurrentExecutions,omitempty"` + Qualifier *string `json:"qualifier,omitempty"` +} + // (Amazon SQS only) The scaling configuration for the event source. To remove // the configuration, pass an empty value. type ScalingConfig struct { diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go index 9c957bb8..a4ea8e65 100644 --- a/apis/v1alpha1/zz_generated.deepcopy.go +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -247,6 +247,11 @@ func (in *AliasSpec) DeepCopyInto(out *AliasSpec) { *out = new(string) **out = **in } + if in.ProvisionedConcurrencyConfig != nil { + in, out := &in.ProvisionedConcurrencyConfig, &out.ProvisionedConcurrencyConfig + *out = new(PutProvisionedConcurrencyConfigInput) + (*in).DeepCopyInto(*out) + } if in.RoutingConfig != nil { in, out := &in.RoutingConfig, &out.RoutingConfig *out = new(AliasRoutingConfiguration) @@ -2666,6 +2671,11 @@ func (in *ProvisionedConcurrencyConfigListItem) DeepCopyInto(out *ProvisionedCon *out = new(string) **out = **in } + if in.RequestedProvisionedConcurrentExecutions != nil { + in, out := &in.RequestedProvisionedConcurrentExecutions, &out.RequestedProvisionedConcurrentExecutions + *out = new(int64) + **out = **in + } if in.StatusReason != nil { in, out := &in.StatusReason, &out.StatusReason *out = new(string) @@ -2743,6 +2753,36 @@ func (in *PutFunctionEventInvokeConfigInput) DeepCopy() *PutFunctionEventInvokeC return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PutProvisionedConcurrencyConfigInput) DeepCopyInto(out *PutProvisionedConcurrencyConfigInput) { + *out = *in + if in.FunctionName != nil { + in, out := &in.FunctionName, &out.FunctionName + *out = new(string) + **out = **in + } + if in.ProvisionedConcurrentExecutions != nil { + in, out := &in.ProvisionedConcurrentExecutions, &out.ProvisionedConcurrentExecutions + *out = new(int64) + **out = **in + } + if in.Qualifier != nil { + in, out := &in.Qualifier, &out.Qualifier + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PutProvisionedConcurrencyConfigInput. +func (in *PutProvisionedConcurrencyConfigInput) DeepCopy() *PutProvisionedConcurrencyConfigInput { + if in == nil { + return nil + } + out := new(PutProvisionedConcurrencyConfigInput) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ScalingConfig) DeepCopyInto(out *ScalingConfig) { *out = *in diff --git a/config/crd/bases/lambda.services.k8s.aws_aliases.yaml b/config/crd/bases/lambda.services.k8s.aws_aliases.yaml index 2726d5f6..0d935087 100644 --- a/config/crd/bases/lambda.services.k8s.aws_aliases.yaml +++ b/config/crd/bases/lambda.services.k8s.aws_aliases.yaml @@ -104,6 +104,19 @@ spec: name: description: The name of the alias. type: string + provisionedConcurrencyConfig: + description: "Configures provisioned concurrency to a function's alias + \n - ProvisionedConcurrentExecutions The amount of provisioned concurrency + to allocate for the version or alias. Minimum value of 1 is required" + properties: + functionName: + type: string + provisionedConcurrentExecutions: + format: int64 + type: integer + qualifier: + type: string + type: object routingConfig: description: The routing configuration (https://docs.aws.amazon.com/lambda/latest/dg/configuration-aliases.html#configuring-alias-routing) of the alias. diff --git a/documentation.yaml b/documentation.yaml index 7b7ebd37..605ca8e1 100644 --- a/documentation.yaml +++ b/documentation.yaml @@ -39,3 +39,11 @@ resources: - MaximumRetryAttempts The maximum number of times to retry when the function returns an error. + + ProvisionedConcurrencyConfig: + prepend: | + Configures provisioned concurrency to a function's alias + + - ProvisionedConcurrentExecutions + The amount of provisioned concurrency to allocate for the version or alias. + Minimum value of 1 is required \ No newline at end of file diff --git a/generator.yaml b/generator.yaml index 193114cc..81f252ab 100644 --- a/generator.yaml +++ b/generator.yaml @@ -108,6 +108,10 @@ resources: from: operation: PutFunctionEventInvokeConfig path: . + ProvisionedConcurrencyConfig: + from: + operation: PutProvisionedConcurrencyConfig + path: . hooks: sdk_update_pre_build_request: template_path: hooks/alias/sdk_update_pre_build_request.go.tpl diff --git a/helm/crds/lambda.services.k8s.aws_aliases.yaml b/helm/crds/lambda.services.k8s.aws_aliases.yaml index de2fe2c9..828854b5 100644 --- a/helm/crds/lambda.services.k8s.aws_aliases.yaml +++ b/helm/crds/lambda.services.k8s.aws_aliases.yaml @@ -104,6 +104,19 @@ spec: name: description: The name of the alias. type: string + provisionedConcurrencyConfig: + description: "Configures provisioned concurrency to a function's alias + \n - ProvisionedConcurrentExecutions The amount of provisioned concurrency + to allocate for the version or alias. Minimum value of 1 is required" + properties: + functionName: + type: string + provisionedConcurrentExecutions: + format: int64 + type: integer + qualifier: + type: string + type: object routingConfig: description: The routing configuration (https://docs.aws.amazon.com/lambda/latest/dg/configuration-aliases.html#configuring-alias-routing) of the alias. diff --git a/pkg/resource/alias/delta.go b/pkg/resource/alias/delta.go index a380116c..ca1fed58 100644 --- a/pkg/resource/alias/delta.go +++ b/pkg/resource/alias/delta.go @@ -132,6 +132,31 @@ func newResourceDelta( delta.Add("Spec.Name", a.ko.Spec.Name, b.ko.Spec.Name) } } + if ackcompare.HasNilDifference(a.ko.Spec.ProvisionedConcurrencyConfig, b.ko.Spec.ProvisionedConcurrencyConfig) { + delta.Add("Spec.ProvisionedConcurrencyConfig", a.ko.Spec.ProvisionedConcurrencyConfig, b.ko.Spec.ProvisionedConcurrencyConfig) + } else if a.ko.Spec.ProvisionedConcurrencyConfig != nil && b.ko.Spec.ProvisionedConcurrencyConfig != nil { + if ackcompare.HasNilDifference(a.ko.Spec.ProvisionedConcurrencyConfig.FunctionName, b.ko.Spec.ProvisionedConcurrencyConfig.FunctionName) { + delta.Add("Spec.ProvisionedConcurrencyConfig.FunctionName", a.ko.Spec.ProvisionedConcurrencyConfig.FunctionName, b.ko.Spec.ProvisionedConcurrencyConfig.FunctionName) + } else if a.ko.Spec.ProvisionedConcurrencyConfig.FunctionName != nil && b.ko.Spec.ProvisionedConcurrencyConfig.FunctionName != nil { + if *a.ko.Spec.ProvisionedConcurrencyConfig.FunctionName != *b.ko.Spec.ProvisionedConcurrencyConfig.FunctionName { + delta.Add("Spec.ProvisionedConcurrencyConfig.FunctionName", a.ko.Spec.ProvisionedConcurrencyConfig.FunctionName, b.ko.Spec.ProvisionedConcurrencyConfig.FunctionName) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.ProvisionedConcurrencyConfig.ProvisionedConcurrentExecutions, b.ko.Spec.ProvisionedConcurrencyConfig.ProvisionedConcurrentExecutions) { + delta.Add("Spec.ProvisionedConcurrencyConfig.ProvisionedConcurrentExecutions", a.ko.Spec.ProvisionedConcurrencyConfig.ProvisionedConcurrentExecutions, b.ko.Spec.ProvisionedConcurrencyConfig.ProvisionedConcurrentExecutions) + } else if a.ko.Spec.ProvisionedConcurrencyConfig.ProvisionedConcurrentExecutions != nil && b.ko.Spec.ProvisionedConcurrencyConfig.ProvisionedConcurrentExecutions != nil { + if *a.ko.Spec.ProvisionedConcurrencyConfig.ProvisionedConcurrentExecutions != *b.ko.Spec.ProvisionedConcurrencyConfig.ProvisionedConcurrentExecutions { + delta.Add("Spec.ProvisionedConcurrencyConfig.ProvisionedConcurrentExecutions", a.ko.Spec.ProvisionedConcurrencyConfig.ProvisionedConcurrentExecutions, b.ko.Spec.ProvisionedConcurrencyConfig.ProvisionedConcurrentExecutions) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.ProvisionedConcurrencyConfig.Qualifier, b.ko.Spec.ProvisionedConcurrencyConfig.Qualifier) { + delta.Add("Spec.ProvisionedConcurrencyConfig.Qualifier", a.ko.Spec.ProvisionedConcurrencyConfig.Qualifier, b.ko.Spec.ProvisionedConcurrencyConfig.Qualifier) + } else if a.ko.Spec.ProvisionedConcurrencyConfig.Qualifier != nil && b.ko.Spec.ProvisionedConcurrencyConfig.Qualifier != nil { + if *a.ko.Spec.ProvisionedConcurrencyConfig.Qualifier != *b.ko.Spec.ProvisionedConcurrencyConfig.Qualifier { + delta.Add("Spec.ProvisionedConcurrencyConfig.Qualifier", a.ko.Spec.ProvisionedConcurrencyConfig.Qualifier, b.ko.Spec.ProvisionedConcurrencyConfig.Qualifier) + } + } + } if ackcompare.HasNilDifference(a.ko.Spec.RoutingConfig, b.ko.Spec.RoutingConfig) { delta.Add("Spec.RoutingConfig", a.ko.Spec.RoutingConfig, b.ko.Spec.RoutingConfig) } else if a.ko.Spec.RoutingConfig != nil && b.ko.Spec.RoutingConfig != nil { diff --git a/pkg/resource/alias/hooks.go b/pkg/resource/alias/hooks.go index 3e02f4ab..b5f75078 100644 --- a/pkg/resource/alias/hooks.go +++ b/pkg/resource/alias/hooks.go @@ -16,6 +16,7 @@ package alias import ( "context" + ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors" ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" "github.com/aws/aws-sdk-go/aws" svcsdk "github.com/aws/aws-sdk-go/service/lambda" @@ -70,14 +71,71 @@ func (rm *resourceManager) syncEventInvokeConfig( return r, nil } -func (rm *resourceManager) setResourceAdditionalFields( +func (rm *resourceManager) updateProvisionedConcurrency( ctx context.Context, - ko *svcapitypes.Alias, -) (err error) { + desired *resource, +) error { + var err error rlog := ackrtlog.FromContext(ctx) - exit := rlog.Trace("rm.setResourceAdditionalFields") + exit := rlog.Trace("rm.updateProvisionedConcurrency") defer exit(err) + dspec := desired.ko.Spec + input := &svcsdk.PutProvisionedConcurrencyConfigInput{ + FunctionName: aws.String(*dspec.FunctionName), + Qualifier: aws.String(*dspec.Name), + } + + if desired.ko.Spec.ProvisionedConcurrencyConfig != nil { + if desired.ko.Spec.ProvisionedConcurrencyConfig.ProvisionedConcurrentExecutions != nil { + input.ProvisionedConcurrentExecutions = aws.Int64(*desired.ko.Spec.ProvisionedConcurrencyConfig.ProvisionedConcurrentExecutions) + } else { + input.ProvisionedConcurrentExecutions = aws.Int64(0) + } + } else { + input.ProvisionedConcurrentExecutions = aws.Int64(0) + } + + _, err = rm.sdkapi.PutProvisionedConcurrencyConfigWithContext(ctx, input) + rm.metrics.RecordAPICall("UPDATE", "UpdateProvisionedConcurrency", err) + if err != nil { + return err + } + return nil +} + +func (rm *resourceManager) getProvisionedConcurrencyConfig( + ctx context.Context, + ko *svcapitypes.Alias, +) (err error) { + + var getProvisionedConcurrencyConfigOutput *svcsdk.GetProvisionedConcurrencyConfigOutput + getProvisionedConcurrencyConfigOutput, err = rm.sdkapi.GetProvisionedConcurrencyConfigWithContext( + ctx, + &svcsdk.GetProvisionedConcurrencyConfigInput{ + FunctionName: ko.Spec.FunctionName, + Qualifier: ko.Spec.Name, + }, + ) + rm.metrics.RecordAPICall("GET", "GetProvisionedConcurrencyConfig", err) + + if err != nil { + if awserr, ok := ackerr.AWSError(err); ok && (awserr.Code() == "ProvisionedConcurrencyConfigNotFoundException" || awserr.Code() == "ResourceNotFoundException") { + ko.Spec.ProvisionedConcurrencyConfig = nil + } else { + return err + } + } else { + ko.Spec.ProvisionedConcurrencyConfig.ProvisionedConcurrentExecutions = getProvisionedConcurrencyConfigOutput.RequestedProvisionedConcurrentExecutions + } + + return nil +} + +func (rm *resourceManager) getFunctionEventInvokeConfig( + ctx context.Context, + ko *svcapitypes.Alias, +) (err error) { var getFunctionEventInvokeConfigOutput *svcsdk.GetFunctionEventInvokeConfigOutput getFunctionEventInvokeConfigOutput, err = rm.sdkapi.GetFunctionEventInvokeConfigWithContext( ctx, @@ -88,8 +146,13 @@ func (rm *resourceManager) setResourceAdditionalFields( ) rm.metrics.RecordAPICall("GET", "GetFunctionEventInvokeConfig", err) + if err != nil { - ko.Spec.FunctionEventInvokeConfig = nil + if awserr, ok := ackerr.AWSError(err); ok && (awserr.Code() == "EventInvokeConfigNotFoundException" || awserr.Code() == "ResourceNotFoundException") { + ko.Spec.FunctionEventInvokeConfig = nil + } else { + return err + } } else { if getFunctionEventInvokeConfigOutput.DestinationConfig != nil { if getFunctionEventInvokeConfigOutput.DestinationConfig.OnFailure != nil { @@ -116,5 +179,27 @@ func (rm *resourceManager) setResourceAdditionalFields( ko.Spec.FunctionEventInvokeConfig.MaximumRetryAttempts = nil } } + + return nil +} + +func (rm *resourceManager) setResourceAdditionalFields( + ctx context.Context, + ko *svcapitypes.Alias, +) (err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.setResourceAdditionalFields") + defer exit(err) + + eic_err := rm.getFunctionEventInvokeConfig(ctx, ko) + if eic_err != nil { + return eic_err + } + + pc_err := rm.getProvisionedConcurrencyConfig(ctx, ko) + if pc_err != nil { + return pc_err + } + return nil } diff --git a/pkg/resource/alias/sdk.go b/pkg/resource/alias/sdk.go index 1f941c0a..09885435 100644 --- a/pkg/resource/alias/sdk.go +++ b/pkg/resource/alias/sdk.go @@ -245,6 +245,13 @@ func (rm *resourceManager) sdkCreate( return nil, err } } + + if ko.Spec.ProvisionedConcurrencyConfig != nil { + err = rm.updateProvisionedConcurrency(ctx, desired) + if err != nil { + return nil, err + } + } return &resource{ko}, nil } @@ -304,7 +311,13 @@ func (rm *resourceManager) sdkUpdate( return nil, err } } - if !delta.DifferentExcept("Spec.FunctionEventInvokeConfig") { + if delta.DifferentAt("Spec.ProvisionedConcurrencyConfig") { + err = rm.updateProvisionedConcurrency(ctx, desired) + if err != nil { + return nil, err + } + } + if !delta.DifferentExcept("Spec.ProvisionedConcurrencyConfig", "Spec.FunctionEventInvokeConfig") { return desired, nil } input, err := rm.newUpdateRequestPayload(ctx, desired, delta) diff --git a/pkg/resource/function/hooks.go b/pkg/resource/function/hooks.go index 2dba93bd..451c7288 100644 --- a/pkg/resource/function/hooks.go +++ b/pkg/resource/function/hooks.go @@ -623,7 +623,11 @@ func (rm *resourceManager) setResourceAdditionalFields( ) rm.metrics.RecordAPICall("GET", "GetFunctionEventInvokeConfig", err) if err != nil { - ko.Spec.FunctionEventInvokeConfig = nil + if awserr, ok := ackerr.AWSError(err); ok && (awserr.Code() == "EventInvokeConfigNotFoundException" || awserr.Code() == "ResourceNotFoundException") { + ko.Spec.FunctionEventInvokeConfig = nil + } else { + return err + } } else { if getFunctionEventInvokeConfigOutput.DestinationConfig != nil { if getFunctionEventInvokeConfigOutput.DestinationConfig.OnFailure != nil { diff --git a/templates/hooks/alias/sdk_create_post_set_output.go.tpl b/templates/hooks/alias/sdk_create_post_set_output.go.tpl index b5e036e3..b53086f6 100644 --- a/templates/hooks/alias/sdk_create_post_set_output.go.tpl +++ b/templates/hooks/alias/sdk_create_post_set_output.go.tpl @@ -3,4 +3,11 @@ if ko.Spec.FunctionEventInvokeConfig != nil { if err != nil{ return nil, err } +} + +if ko.Spec.ProvisionedConcurrencyConfig != nil { + err = rm.updateProvisionedConcurrency(ctx,desired) + if err != nil{ + return nil, err + } } \ No newline at end of file diff --git a/templates/hooks/alias/sdk_update_pre_build_request.go.tpl b/templates/hooks/alias/sdk_update_pre_build_request.go.tpl index 44c22fd1..4ac2740b 100644 --- a/templates/hooks/alias/sdk_update_pre_build_request.go.tpl +++ b/templates/hooks/alias/sdk_update_pre_build_request.go.tpl @@ -4,6 +4,12 @@ if delta.DifferentAt("Spec.FunctionEventInvokeConfig"){ return nil, err } } -if !delta.DifferentExcept("Spec.FunctionEventInvokeConfig"){ +if delta.DifferentAt("Spec.ProvisionedConcurrencyConfig"){ + err = rm.updateProvisionedConcurrency(ctx, desired) + if err != nil { + return nil, err + } +} +if !delta.DifferentExcept("Spec.ProvisionedConcurrencyConfig","Spec.FunctionEventInvokeConfig"){ return desired, nil } \ No newline at end of file diff --git a/test/e2e/resources/alias_provisioned_concurrency.yaml b/test/e2e/resources/alias_provisioned_concurrency.yaml new file mode 100644 index 00000000..a67c8a2c --- /dev/null +++ b/test/e2e/resources/alias_provisioned_concurrency.yaml @@ -0,0 +1,13 @@ +apiVersion: lambda.services.k8s.aws/v1alpha1 +kind: Alias +metadata: + name: $ALIAS_NAME + annotations: + services.k8s.aws/region: $AWS_REGION +spec: + name: $ALIAS_NAME + functionName: $FUNCTION_NAME + functionVersion: $FUNCTION_VERSION + description: alias created by ACK lambda-controller e2e tests + provisionedConcurrencyConfig: + provisionedConcurrentExecutions: $PROVISIONED_CONCURRENT_EXECUTIONS \ No newline at end of file diff --git a/test/e2e/tests/helper.py b/test/e2e/tests/helper.py index 1f3caa3a..f4c2986b 100644 --- a/test/e2e/tests/helper.py +++ b/test/e2e/tests/helper.py @@ -160,4 +160,15 @@ def get_function_event_invoke_config_alias(self, function_name:str, qualifier:st return None def function_event_invoke_config_exists(self, function_name: str) -> bool: - return self.get_function_event_invoke_config(function_name) is not None \ No newline at end of file + return self.get_function_event_invoke_config(function_name) is not None + + def get_provisioned_concurrency_config(self, function_name: str, qualifier:str) -> dict: + try: + resp = self.lambda_client.get_provisioned_concurrency_config( + FunctionName = function_name, + Qualifier = qualifier + ) + return resp + except Exception as e: + logging.debug(e) + return None \ No newline at end of file diff --git a/test/e2e/tests/test_alias.py b/test/e2e/tests/test_alias.py index 03c3b16d..df33bd96 100644 --- a/test/e2e/tests/test_alias.py +++ b/test/e2e/tests/test_alias.py @@ -44,7 +44,7 @@ def lambda_function(): replacements["BUCKET_NAME"] = resources.FunctionsBucket.name replacements["LAMBDA_ROLE"] = resources.EICRole.arn replacements["LAMBDA_FILE_NAME"] = LAMBDA_FUNCTION_FILE_ZIP - replacements["RESERVED_CONCURRENT_EXECUTIONS"] = "0" + replacements["RESERVED_CONCURRENT_EXECUTIONS"] = "3" replacements["CODE_SIGNING_CONFIG_ARN"] = "" replacements["AWS_REGION"] = get_region() @@ -196,6 +196,75 @@ def test_smoke_ref(self, lambda_client, lambda_function): # Check alias doesn't exist assert not lambda_validator.alias_exists(resource_name, function_resource_name) + def test_provisioned_concurrency_config(self, lambda_client, lambda_function): + (_, function_resource) = lambda_function + lambda_function_name = function_resource["spec"]["name"] + + resource_name = random_suffix_name("lambda-alias", 24) + + resources = get_bootstrap_resources() + logging.debug(resources) + + resp = lambda_client.publish_version( + FunctionName = lambda_function_name + ) + version = resp['Version'] + + replacements = REPLACEMENT_VALUES.copy() + replacements["AWS_REGION"] = get_region() + replacements["ALIAS_NAME"] = resource_name + replacements["FUNCTION_NAME"] = lambda_function_name + replacements["FUNCTION_VERSION"] = f"\'{version}\'" + replacements["PROVISIONED_CONCURRENT_EXECUTIONS"] = "1" + + # Load alias CR + resource_data = load_lambda_resource( + "alias_provisioned_concurrency", + additional_replacements=replacements, + ) + logging.debug(resource_data) + + # Create k8s resource + ref = k8s.CustomResourceReference( + CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL, + resource_name, namespace="default", + ) + + k8s.create_custom_resource(ref, resource_data) + cr = k8s.wait_resource_consumed_by_controller(ref) + + assert cr is not None + assert k8s.get_resource_exists(ref) + + time.sleep(CREATE_WAIT_AFTER_SECONDS) + + cr = k8s.wait_resource_consumed_by_controller(ref) + + lambda_validator = LambdaValidator(lambda_client) + + # Check alias exists + assert lambda_validator.alias_exists(resource_name, lambda_function_name) + + # Update cr + cr["spec"]["provisionedConcurrencyConfig"]["provisionedConcurrentExecutions"] = 2 + + # Patch k8s resource + k8s.patch_custom_resource(ref, cr) + time.sleep(UPDATE_WAIT_AFTER_SECONDS) + + #Check function_event_invoke_config update fields + provisioned_concurrency_config = lambda_validator.get_provisioned_concurrency_config(lambda_function_name,resource_name) + assert provisioned_concurrency_config["RequestedProvisionedConcurrentExecutions"] == 2 + + # Delete k8s resource + _, deleted = k8s.delete_custom_resource(ref) + assert deleted + + time.sleep(DELETE_WAIT_AFTER_SECONDS) + + # Check alias doesn't exist + assert not lambda_validator.alias_exists(resource_name, lambda_function_name) + def test_function_event_invoke_config(self, lambda_client, lambda_function): (_, function_resource) = lambda_function lambda_function_name = function_resource["spec"]["name"]