diff --git a/moto/elbv2/exceptions.py b/moto/elbv2/exceptions.py index dd2fa6ee9..469758e7b 100644 --- a/moto/elbv2/exceptions.py +++ b/moto/elbv2/exceptions.py @@ -187,3 +187,8 @@ class InvalidLoadBalancerActionException(ELBClientError): class ValidationError(ELBClientError): def __init__(self, msg: str): super().__init__("ValidationError", msg) + + +class InvalidConfigurationRequest(ELBClientError): + def __init__(self, msg: str): + super().__init__("InvalidConfigurationRequest", msg) diff --git a/moto/elbv2/models.py b/moto/elbv2/models.py index 70c83fd7f..11c31ce39 100644 --- a/moto/elbv2/models.py +++ b/moto/elbv2/models.py @@ -36,6 +36,7 @@ from .exceptions import ( InvalidStatusCodeActionTypeError, InvalidLoadBalancerActionException, ValidationError, + InvalidConfigurationRequest, ) ALLOWED_ACTIONS = [ @@ -1257,6 +1258,72 @@ Member must satisfy regular expression pattern: {expression}" if not target_group: raise TargetGroupNotFoundError() + deregistration_delay_timeout_seconds = attributes.get( + "deregistration_delay.timeout_seconds" + ) + if deregistration_delay_timeout_seconds: + if int(deregistration_delay_timeout_seconds) not in range(0, 3600): + raise ValidationError( + f"'deregistration_delay.timeout_seconds' value '{deregistration_delay_timeout_seconds}' must be between '0-3600' inclusive" + ) + if target_group.target_type == "lambda": + raise InvalidConfigurationRequest( + "A target group with target type 'lambda' does not support the attribute deregistration_delay.timeout_seconds" + ) + + stickiness_type = attributes.get("stickiness.type") + # TODO: strict type checking for app_cookie + stickiness_cookie_name = attributes.get("stickiness.app_cookie.cookie_name") + if stickiness_type: + if target_group.protocol == "GENEVE": + if stickiness_cookie_name: + # TODO: generalise error message + raise ValidationError( + "Target group attribute key 'stickiness.app_cookie.cookie_name' is not recognized" + ) + elif stickiness_type not in [ + "source_ip_dest_ip_proto", + "source_ip_dest_ip", + ]: + raise ValidationError( + f"'{stickiness_type}' must be one of [source_ip_dest_ip_proto, source_ip_dest_ip]" + ) + if stickiness_type == "source_ip": + if target_group.protocol in ["HTTP", "HTTPS"]: + raise InvalidConfigurationRequest( + f"Stickiness type 'source_ip' is not supported for target groups with the {target_group.protocol} protocol" + ) + elif target_group.protocol == "TLS": + raise InvalidConfigurationRequest( + "You cannot enable stickiness on target groups with the TLS protocol" + ) + elif stickiness_type == "lb_cookie": + if target_group.protocol in ["TCP", "TLS", "UDP", "TCP_UDP"]: + raise InvalidConfigurationRequest( + f"Stickiness type 'lb_cookie' is not supported for target groups with the {target_group.protocol} protocol" + ) + elif stickiness_type == "app_cookie": + if not stickiness_cookie_name: + raise InvalidConfigurationRequest( + "You must set an application cookie name to enable stickiness of type 'app_cookie'" + ) + if target_group.protocol in ["TCP", "TLS", "UDP", "TCP_UDP", "GENEVE"]: + raise InvalidConfigurationRequest( + f"Stickiness type 'app_cookie' is not supported for target groups with the {target_group.protocol} protocol" + ) + elif stickiness_type in ["source_ip_dest_ip", "source_ip_dest_ip_proto"]: + if target_group.protocol in [ + "HTTP", + "HTTPS", + "TCP", + "TLS", + "UDP", + "TCP_UDP", + ]: + raise ValidationError( + "'Stickiness type' must be one of [app_cookie, lb_cookie, source_ip]" + ) + target_group.attributes.update(attributes) def convert_and_validate_certificates( diff --git a/moto/elbv2/responses.py b/moto/elbv2/responses.py index c65754e0c..2cfc49c7b 100644 --- a/moto/elbv2/responses.py +++ b/moto/elbv2/responses.py @@ -844,7 +844,8 @@ CREATE_TARGET_GROUP_TEMPLATE = """{{ target_group.matcher['HttpCode'] }}{% endif %} + {% if target_group.matcher.get("GrpcCode") %}{{ target_group.matcher['GrpcCode'] }}{% endif %} {% endif %} {% if target_group.target_type %} @@ -1098,7 +1099,8 @@ DESCRIBE_TARGET_GROUPS_TEMPLATE = """{{ target_group.matcher['HttpCode'] }}{% endif %} + {% if target_group.matcher.get("GrpcCode") %}{{ target_group.matcher['GrpcCode'] }}{% endif %} {% endif %} {% if target_group.target_type %} diff --git a/tests/terraformtests/bin/run_go_test b/tests/terraformtests/bin/run_go_test index 19727426b..6593ecb21 100755 --- a/tests/terraformtests/bin/run_go_test +++ b/tests/terraformtests/bin/run_go_test @@ -19,5 +19,9 @@ PATCH="etc/0001-Patch-Hardcode-endpoints-to-local-server.patch" ( cd terraform-provider-aws || exit echo "Running tests $2 for service $1..." -AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test TF_ACC=true go test ./internal/service/$1/ -v -timeout 60m -run $2 +TF_ACC=1 \ + AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test \ + AWS_ALTERNATE_ACCESS_KEY_ID=test AWS_ALTERNATE_SECRET_ACCESS_KEY=test \ + AWS_THIRD_SECRET_ACCESS_KEY=test AWS_THIRD_ACCESS_KEY_ID=test \ + go test ./internal/service/$1/ -v -timeout 60m -run $2 ) diff --git a/tests/terraformtests/terraform-tests.failures.txt b/tests/terraformtests/terraform-tests.failures.txt index accba96d1..9bd6b2873 100644 --- a/tests/terraformtests/terraform-tests.failures.txt +++ b/tests/terraformtests/terraform-tests.failures.txt @@ -57,5 +57,8 @@ TestAccLambdaFunctionURL_Cors TestAccVPCNATGateway_privateIP TestAccVPCSecurityGroupRule_multiDescription +TestAccELBV2TargetGroup_ProtocolVersion_basic +TestAccELBV2TargetGroup_ForceNew_port +TestAccELBV2TargetGroup_ForceNew_protocol # TF expects a wrong error message, which is not in-line with what AWS returns TestAccEFSFileSystemDataSource_nonExistent_fileSystemID diff --git a/tests/terraformtests/terraform-tests.success.txt b/tests/terraformtests/terraform-tests.success.txt index 6a2020df2..23229dfca 100644 --- a/tests/terraformtests/terraform-tests.success.txt +++ b/tests/terraformtests/terraform-tests.success.txt @@ -267,14 +267,58 @@ elb: - TestAccELBSSLNegotiationPolicy elbv2: - TestAccELBV2ListenerCertificate - - TestAccELBV2TargetGroupAttachment - - TestAccELBV2TargetGroupDataSource - - TestAccELBV2TargetGroup_ALBAlias - - TestAccELBV2TargetGroup_networkLB - - TestAccELBV2TargetGroup_NetworkLB + - TestAccELBV2TargetGroup_backwardsCompatibility + - TestAccELBV2TargetGroup_ProtocolVersion_grpcHealthCheck + - TestAccELBV2TargetGroup_ProtocolVersion_grpcUpdate + - TestAccELBV2TargetGroup_ipAddressType + - TestAccELBV2TargetGroup_tls + - TestAccELBV2TargetGroup_HealthCheck_tcpHTTPS + - TestAccELBV2TargetGroup_attrsOnCreate + - TestAccELBV2TargetGroup_basic + - TestAccELBV2TargetGroup_udp + - TestAccELBV2TargetGroup_ForceNew_name + - TestAccELBV2TargetGroup_ForceNew_vpc + - TestAccELBV2TargetGroup_Defaults_application + - TestAccELBV2TargetGroup_Defaults_network + - TestAccELBV2TargetGroup_HealthCheck_enable + - TestAccELBV2TargetGroup_Name_generated + - TestAccELBV2TargetGroup_Name_prefix + - TestAccELBV2TargetGroup_NetworkLB_tcpHealthCheckUpdated + - TestAccELBV2TargetGroup_networkLB_TargetGroupWithConnectionTermination + - TestAccELBV2TargetGroup_NetworkLB_targetGroupWithProxy + - TestAccELBV2TargetGroup_preserveClientIPValid + - TestAccELBV2TargetGroup_Geneve_basic + - TestAccELBV2TargetGroup_Geneve_notSticky + - TestAccELBV2TargetGroup_Geneve_Sticky + - TestAccELBV2TargetGroup_Geneve_targetFailover - TestAccELBV2TargetGroup_Stickiness_defaultALB - - TestAccELBV2TargetGroup_Stickiness_valid - - TestAccELBV2TargetGroup_Stickiness_update + - TestAccELBV2TargetGroup_Stickiness_defaultNLB + - TestAccELBV2TargetGroup_Stickiness_invalidALB + - TestAccELBV2TargetGroup_Stickiness_invalidNLB + - TestAccELBV2TargetGroup_Stickiness_validALB + - TestAccELBV2TargetGroup_Stickiness_validNLB + - TestAccELBV2TargetGroup_tags + - TestAccELBV2TargetGroup_Stickiness_updateAppEnabled + - TestAccELBV2TargetGroup_HealthCheck_update + - TestAccELBV2TargetGroup_Stickiness_updateEnabled + - TestAccELBV2TargetGroup_HealthCheck_without + - TestAccELBV2TargetGroup_ALBAlias_basic + - TestAccELBV2TargetGroup_ALBAlias_changeNameForceNew + - TestAccELBV2TargetGroup_ALBAlias_changePortForceNew + - TestAccELBV2TargetGroup_ALBAlias_changeProtocolForceNew + - TestAccELBV2TargetGroup_ALBAlias_changeVPCForceNew + - TestAccELBV2TargetGroup_ALBAlias_generatedName + - TestAccELBV2TargetGroup_ALBAlias_lambda + - TestAccELBV2TargetGroup_ALBAlias_lambdaMultiValueHeadersEnabled + - TestAccELBV2TargetGroup_ALBAlias_missingPortProtocolVPC + - TestAccELBV2TargetGroup_ALBAlias_namePrefix + - TestAccELBV2TargetGroup_ALBAlias_setAndUpdateSlowStart + - TestAccELBV2TargetGroup_ALBAlias_tags + - TestAccELBV2TargetGroup_ALBAlias_updateHealthCheck + - TestAccELBV2TargetGroup_ALBAlias_updateLoadBalancingAlgorithmType + - TestAccELBV2TargetGroup_ALBAlias_updateLoadBalancingCrossZoneEnabled + - TestAccELBV2TargetGroup_ALBAlias_updateStickinessEnabled + - TestAccELBV2TargetGroup_Name_noDuplicates events: - TestAccEventsAPIDestination - TestAccEventsArchive diff --git a/tests/test_elbv2/test_elbv2_target_groups.py b/tests/test_elbv2/test_elbv2_target_groups.py index 0fea02be4..512f1f118 100644 --- a/tests/test_elbv2/test_elbv2_target_groups.py +++ b/tests/test_elbv2/test_elbv2_target_groups.py @@ -398,21 +398,24 @@ def test_target_group_attributes(): Attributes=[ {"Key": "stickiness.enabled", "Value": "true"}, {"Key": "stickiness.type", "Value": "app_cookie"}, + {"Key": "stickiness.app_cookie.cookie_name", "Value": "my_cookie"}, ], ) # The response should have only the keys updated - assert len(response["Attributes"]) == 2 + assert len(response["Attributes"]) == 3 attributes = {attr["Key"]: attr["Value"] for attr in response["Attributes"]} assert attributes["stickiness.type"] == "app_cookie" assert attributes["stickiness.enabled"] == "true" + assert attributes["stickiness.app_cookie.cookie_name"] == "my_cookie" # These new values should be in the full attribute list response = conn.describe_target_group_attributes(TargetGroupArn=target_group_arn) - assert len(response["Attributes"]) == 7 + assert len(response["Attributes"]) == 8 attributes = {attr["Key"]: attr["Value"] for attr in response["Attributes"]} assert attributes["stickiness.type"] == "app_cookie" assert attributes["stickiness.enabled"] == "true" + assert attributes["stickiness.app_cookie.cookie_name"] == "my_cookie" @mock_elbv2 @@ -993,3 +996,213 @@ def test_create_target_group_healthcheck_validation(protocol_name, should_raise) assert exc.value.response["Error"]["Message"] == _get_error_message( protocol_name, 5, 5 ) + + +@mock_ec2 +@mock_elbv2 +@pytest.mark.parametrize( + "protocol, should_raise, stickiness_type, error_message", + [ + # stickiness.type = "source_ip" + ( + "HTTP", + True, + "source_ip", + "Stickiness type 'source_ip' is not supported for target groups with the HTTP protocol", + ), + ( + "HTTPS", + True, + "source_ip", + "Stickiness type 'source_ip' is not supported for target groups with the HTTPS protocol", + ), + ("TCP", False, "source_ip", ""), + ( + "TLS", + True, + "source_ip", + "You cannot enable stickiness on target groups with the TLS protocol", + ), + ("UDP", False, "source_ip", ""), + ("TCP_UDP", False, "source_ip", ""), + ( + "GENEVE", + True, + "source_ip", + "'source_ip' must be one of [source_ip_dest_ip_proto, source_ip_dest_ip]", + ), + # stickiness.type = "lb_cookie" + ("HTTP", False, "lb_cookie", ""), + ("HTTPS", False, "lb_cookie", ""), + ( + "TCP", + True, + "lb_cookie", + "Stickiness type 'lb_cookie' is not supported for target groups with the TCP protocol", + ), + ( + "TLS", + True, + "lb_cookie", + "Stickiness type 'lb_cookie' is not supported for target groups with the TLS protocol", + ), + ( + "UDP", + True, + "lb_cookie", + "Stickiness type 'lb_cookie' is not supported for target groups with the UDP protocol", + ), + ( + "TCP_UDP", + True, + "lb_cookie", + "Stickiness type 'lb_cookie' is not supported for target groups with the TCP_UDP protocol", + ), + ( + "GENEVE", + True, + "lb_cookie", + "'lb_cookie' must be one of [source_ip_dest_ip_proto, source_ip_dest_ip]", + ), + # stickiness.type = "app_cookie" + ("HTTP", False, "app_cookie", ""), + ("HTTPS", False, "app_cookie", ""), + ( + "TCP", + True, + "app_cookie", + "Stickiness type 'app_cookie' is not supported for target groups with the TCP protocol", + ), + ( + "TLS", + True, + "app_cookie", + "Stickiness type 'app_cookie' is not supported for target groups with the TLS protocol", + ), + ( + "UDP", + True, + "app_cookie", + "Stickiness type 'app_cookie' is not supported for target groups with the UDP protocol", + ), + ( + "TCP_UDP", + True, + "app_cookie", + "Stickiness type 'app_cookie' is not supported for target groups with the TCP_UDP protocol", + ), + ( + "GENEVE", + True, + "app_cookie", + "Target group attribute key 'stickiness.app_cookie.cookie_name' is not recognized", + ), + # stickiness.type = "source_ip_dest_ip" + ( + "HTTP", + True, + "source_ip_dest_ip", + "'Stickiness type' must be one of [app_cookie, lb_cookie, source_ip]", + ), + ( + "HTTPS", + True, + "source_ip_dest_ip", + "'Stickiness type' must be one of [app_cookie, lb_cookie, source_ip]", + ), + ( + "TCP", + True, + "source_ip_dest_ip", + "'Stickiness type' must be one of [app_cookie, lb_cookie, source_ip]", + ), + ( + "TLS", + True, + "source_ip_dest_ip", + "'Stickiness type' must be one of [app_cookie, lb_cookie, source_ip]", + ), + ( + "UDP", + True, + "source_ip_dest_ip", + "'Stickiness type' must be one of [app_cookie, lb_cookie, source_ip]", + ), + ( + "TCP_UDP", + True, + "source_ip_dest_ip", + "'Stickiness type' must be one of [app_cookie, lb_cookie, source_ip]", + ), + ("GENEVE", False, "source_ip_dest_ip", ""), + # stickiness.type = "source_ip_dest_ip_proto" + ( + "HTTPS", + True, + "source_ip_dest_ip_proto", + "'Stickiness type' must be one of [app_cookie, lb_cookie, source_ip]", + ), + ( + "HTTP", + True, + "source_ip_dest_ip_proto", + "'Stickiness type' must be one of [app_cookie, lb_cookie, source_ip]", + ), + ( + "TCP", + True, + "source_ip_dest_ip_proto", + "'Stickiness type' must be one of [app_cookie, lb_cookie, source_ip]", + ), + ( + "TLS", + True, + "source_ip_dest_ip_proto", + "'Stickiness type' must be one of [app_cookie, lb_cookie, source_ip]", + ), + ( + "UDP", + True, + "source_ip_dest_ip_proto", + "'Stickiness type' must be one of [app_cookie, lb_cookie, source_ip]", + ), + ( + "TCP_UDP", + True, + "source_ip_dest_ip_proto", + "'Stickiness type' must be one of [app_cookie, lb_cookie, source_ip]", + ), + ("GENEVE", False, "source_ip_dest_ip_proto", ""), + ], +) +def test_target_group_attributes_stickiness( + protocol, should_raise, stickiness_type, error_message +): + elbv2 = boto3.client("elbv2", region_name="us-east-1") + _, vpc, _, _, _, _ = create_load_balancer() + + response = elbv2.create_target_group( + Name="a-target", + Protocol=protocol, + Port=6081 if protocol == "GENEVE" else 80, + VpcId=vpc.id, + ) + target_group_arn = response["TargetGroups"][0]["TargetGroupArn"] + attributes = [ + {"Key": "stickiness.enabled", "Value": "true"}, + {"Key": "stickiness.type", "Value": stickiness_type}, + ] + if stickiness_type == "app_cookie": + attributes.append( + {"Key": "stickiness.app_cookie.cookie_name", "Value": "localstack"} + ) + if should_raise: + with pytest.raises(ClientError) as exc: + elbv2.modify_target_group_attributes( + TargetGroupArn=target_group_arn, Attributes=attributes + ) + assert exc.value.response["Error"]["Message"] == error_message + else: + elbv2.modify_target_group_attributes( + TargetGroupArn=target_group_arn, Attributes=attributes + )