diff --git a/moto/elbv2/models.py b/moto/elbv2/models.py index 30f2ff240..ca7ce11ad 100644 --- a/moto/elbv2/models.py +++ b/moto/elbv2/models.py @@ -821,6 +821,17 @@ class ELBv2Backend(BaseBackend): "A 'QueryStringConfig' must be specified with 'query-string'" ) + def _get_target_group_arns_from(self, action_data): + if "TargetGroupArn" in action_data: + return [action_data["TargetGroupArn"]] + elif "ForwardConfig" in action_data: + return [ + tg["TargetGroupArn"] + for tg in action_data["ForwardConfig"].get("TargetGroups", []) + ] + else: + return [] + def _validate_actions(self, actions): # validate Actions target_group_arns = [ @@ -829,10 +840,11 @@ class ELBv2Backend(BaseBackend): for i, action in enumerate(actions): index = i + 1 action_type = action.type - if action_type == "forward" and "TargetGroupArn" in action.data: - action_target_group_arn = action.data["TargetGroupArn"] - if action_target_group_arn not in target_group_arns: - raise ActionTargetGroupNotFoundError(action_target_group_arn) + if action_type == "forward": + found_arns = self._get_target_group_arns_from(action_data=action.data) + for target_group_arn in found_arns: + if target_group_arn not in target_group_arns: + raise ActionTargetGroupNotFoundError(target_group_arn) elif action_type == "fixed-response": self._validate_fixed_response_action(action, i, index) elif action_type in ["redirect", "authenticate-cognito"]: @@ -998,8 +1010,10 @@ Member must satisfy regular expression pattern: {}".format( balancer.listeners[listener.arn] = listener for action in default_actions: if action.type == "forward": - target_group = self.target_groups[action.data["TargetGroupArn"]] - target_group.load_balancer_arns.append(load_balancer_arn) + found_arns = self._get_target_group_arns_from(action_data=action.data) + for arn in found_arns: + target_group = self.target_groups[arn] + target_group.load_balancer_arns.append(load_balancer_arn) return listener @@ -1432,7 +1446,10 @@ Member must satisfy regular expression pattern: {}".format( for listener in load_balancer.listeners.values(): for rule in listener.rules.values(): for action in rule.actions: - if action.data.get("TargetGroupArn") == target_group_arn: + found_arns = self._get_target_group_arns_from( + action_data=action.data + ) + if target_group_arn in found_arns: return True return False diff --git a/moto/elbv2/responses.py b/moto/elbv2/responses.py index 8d6a237f4..c576a6715 100644 --- a/moto/elbv2/responses.py +++ b/moto/elbv2/responses.py @@ -187,7 +187,7 @@ class ELBV2Response(BaseResponse): healthcheck_enabled = self._get_param("HealthCheckEnabled") healthy_threshold_count = self._get_param("HealthyThresholdCount") unhealthy_threshold_count = self._get_param("UnhealthyThresholdCount") - matcher = self._get_param("Matcher") + matcher = self._get_params().get("Matcher") target_type = self._get_param("TargetType") target_group = self.elbv2_backend.create_target_group( diff --git a/tests/test_elbv2/test_elbv2_target_groups.py b/tests/test_elbv2/test_elbv2_target_groups.py index 472b910c1..9c5a9ac11 100644 --- a/tests/test_elbv2/test_elbv2_target_groups.py +++ b/tests/test_elbv2/test_elbv2_target_groups.py @@ -447,10 +447,12 @@ def test_describe_target_groups_no_arguments(): HealthCheckTimeoutSeconds=5, HealthyThresholdCount=5, UnhealthyThresholdCount=2, - Matcher={"HttpCode": "200"}, + Matcher={"HttpCode": "201"}, ) - conn.describe_target_groups()["TargetGroups"].should.have.length_of(1) + groups = conn.describe_target_groups()["TargetGroups"] + groups.should.have.length_of(1) + groups[0].should.have.key("Matcher").equals({"HttpCode": "201"}) @mock_elbv2 @@ -570,3 +572,123 @@ def test_delete_target_group_after_modifying_listener(): default_actions.should.equal( [{"Type": "forward", "TargetGroupArn": target_group_arn2}] ) + + +@mock_elbv2 +@mock_ec2 +def test_create_listener_with_multiple_target_groups(): + client = boto3.client("elbv2", region_name="us-east-1") + + response, vpc, _, _, _, conn = create_load_balancer() + + load_balancer_arn = response.get("LoadBalancers")[0].get("LoadBalancerArn") + + response = client.create_target_group( + Name="a-target", Protocol="HTTP", Port=8080, VpcId=vpc.id, + ) + target_group_arn1 = response.get("TargetGroups")[0]["TargetGroupArn"] + + response = client.create_target_group( + Name="a-target-2", Protocol="HTTPS", Port=8081, VpcId=vpc.id, + ) + target_group_arn2 = response.get("TargetGroups")[0]["TargetGroupArn"] + + conn.create_listener( + LoadBalancerArn=load_balancer_arn, + Protocol="HTTP", + Port=80, + DefaultActions=[ + { + "Type": "forward", + "ForwardConfig": { + "TargetGroups": [ + {"TargetGroupArn": target_group_arn1, "Weight": 100}, + {"TargetGroupArn": target_group_arn2, "Weight": 0}, + ], + "TargetGroupStickinessConfig": { + "Enabled": False, + "DurationSeconds": 300, + }, + }, + } + ], + ) + + response = conn.describe_listeners(LoadBalancerArn=load_balancer_arn) + listener = response["Listeners"][0] + groups = listener["DefaultActions"][0]["ForwardConfig"]["TargetGroups"] + groups.should.have.length_of(2) + groups.should.contain({"TargetGroupArn": target_group_arn1, "Weight": 100}) + groups.should.contain({"TargetGroupArn": target_group_arn2, "Weight": 0}) + + +@mock_elbv2 +@mock_ec2 +def test_create_listener_with_invalid_target_group(): + response, _, _, _, _, conn = create_load_balancer() + + load_balancer_arn = response.get("LoadBalancers")[0].get("LoadBalancerArn") + + with pytest.raises(ClientError) as exc: + conn.create_listener( + LoadBalancerArn=load_balancer_arn, + Protocol="HTTP", + Port=80, + DefaultActions=[ + { + "Type": "forward", + "ForwardConfig": { + "TargetGroups": [{"TargetGroupArn": "unknown", "Weight": 100}] + }, + } + ], + ) + err = exc.value.response["Error"] + err["Code"].should.equal("TargetGroupNotFound") + err["Message"].should.equal("Target group 'unknown' not found") + + +@mock_elbv2 +@mock_ec2 +def test_delete_target_group_while_listener_still_exists(): + client = boto3.client("elbv2", region_name="us-east-1") + + response, vpc, _, _, _, conn = create_load_balancer() + + load_balancer_arn = response.get("LoadBalancers")[0].get("LoadBalancerArn") + + response = client.create_target_group( + Name="a-target", Protocol="HTTP", Port=8080, VpcId=vpc.id, + ) + target_group_arn1 = response.get("TargetGroups")[0]["TargetGroupArn"] + + response = conn.create_listener( + LoadBalancerArn=load_balancer_arn, + Protocol="HTTP", + Port=80, + DefaultActions=[ + { + "Type": "forward", + "ForwardConfig": { + "TargetGroups": [ + {"TargetGroupArn": target_group_arn1, "Weight": 100} + ] + }, + } + ], + ) + listener_arn = response["Listeners"][0]["ListenerArn"] + + # Deletion does not succeed if the Listener still exists + with pytest.raises(ClientError) as exc: + client.delete_target_group(TargetGroupArn=target_group_arn1) + err = exc.value.response["Error"] + err["Code"].should.equal("ResourceInUse") + err["Message"].should.equal( + f"The target group '{target_group_arn1}' is currently in use by a listener or a rule" + ) + + client.delete_listener(ListenerArn=listener_arn) + + # Deletion does succeed now that the listener is deleted + client.delete_target_group(TargetGroupArn=target_group_arn1)