Repository: getmoto/moto

Issue:
Cannot create ELBV2 listener default rule with ForwardConfig
Python version: 3.6
Moto version: 2.2.18

I'm trying to create an application load balancer with a single listener.
That lister should have a default rule with weighted target groups.
This is how the default rule config format looks.
```
"DefaultActions": [
          {
            "Type": "forward",
            "ForwardConfig": {
              "TargetGroups": [
                {
                  "TargetGroupArn": "myapp-blue-tg",
                  "Weight": 100
                },
                {
                  "TargetGroupArn": "myapp-green-tg",
                  "Weight": 0
                }
              ],
              "TargetGroupStickinessConfig": {
                "Enabled": false,
                "DurationSeconds": 300
              }
            }
          }
        ]
```

The error I get is 
File "C:\Users\**\AppData\Roaming\Python\Python36\site-packages\moto\elbv2\models.py", line 1001, in create_listener
    target_group = self.target_groups[action.data["TargetGroupArn"]]
KeyError: 'TargetGroupArn'

Looking at the models.py create_listener code, it fails in this loop, where it attempts to get the TargetGroupArn attribute from action.data.
However when I step through and look at action.data it does not contain a TargetGroupArn key it contains the entire default actions list. 
```
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)
```



Discussion:
Hi @rickbowden, thanks for raising this! Looks like we only support sending a single TargetGroupArn - will mark it as an enhancement to also support the `ForwardConfig`-parameter.
Many thanks @bblommers 

Resolving patch:
diff --git a/moto/elbv2/models.py b/moto/elbv2/models.py
--- a/moto/elbv2/models.py
+++ b/moto/elbv2/models.py
@@ -821,6 +821,17 @@ def _validate_query_string_condition(self, condition):
                 "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 @@ def _validate_actions(self, actions):
         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 @@ def create_listener(
         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 @@ def _any_listener_using(self, target_group_arn):
             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
--- a/moto/elbv2/responses.py
+++ b/moto/elbv2/responses.py
@@ -187,7 +187,7 @@ def create_target_group(self):
         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(


Test patch:
diff --git a/tests/test_elbv2/test_elbv2_target_groups.py b/tests/test_elbv2/test_elbv2_target_groups.py
--- 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)