Repository: getmoto/moto

Issue:
mock_elbv2: ResourceInUseException after trying to delete a target group that was a listener DefaultAction
**how to reproduce the issue**

When changing the DefaultAction of a loadbalancer's listener, the old TargetGroup cannot be deleted.

**code sample (simplified)**
```
target_group_arn_1 = client.create_target_group(...)["TargetGroups"][0]["TargetGroupArn"]
target_group_arn_2 = client.create_target_group(...)["TargetGroups"][0]["TargetGroupArn"]
listener_arn = client.create_listener(
        LoadBalancerArn=load_balancer_arn,
        Protocol='HTTP',
        Port=80,
        DefaultActions=[{'Type': 'forward', 'TargetGroupArn': target_group_arn_1}])["Listeners"][0]["LoadBalancerArn"]
client.modify_listener(
                            ListenerArn=listener_arn,
                            DefaultActions=[{
                                'Type': 'forward',
                                'TargetGroupArn': target_group_arn_2,
                            }])
client.delete_target_group(TargetGroupArn=target_group_arn_1)
```

**what you expected to happen**.

I would expect to delete the target group, because it is not attached to the listener anymore.

**what actually happens**

The following error is raised:

`botocore.errorfactory.ResourceInUseException: An error occurred (ResourceInUse) when calling the DeleteTargetGroup operation: The target group 'arn:aws:elasticloadbalancing:eu-west-3:1:targetgroup/TEST/50dc6c495c0c9188' is currently in use by a listener or a rule`

**analysis**

I've walked throught the moto's source code and I believe to have found rootcause (but feel free to tell me if not !)
In the file `moto/moto/elbv2/models.py` the ResourceInUseError is thrown because moto detects that the target group is in use in any of the `rules`:

```
    def _any_listener_using(self, target_group_arn):
        for load_balancer in self.load_balancers.values():
            for listener in load_balancer.listeners.values():
                for rule in listener.rules:
                    for action in rule.actions:
                        if action.data.get("target_group_arn") == target_group_arn:
                            return True
        return False
```


The `listener.rules` object is build like that:
```
    @property
    def rules(self):
        return self._non_default_rules + [self._default_rule]
```

The `self._default_rule` is created only when the listener is created:
```
        self._default_rule = FakeRule(
            listener_arn=self.arn,
            conditions=[],
            priority="default",
            actions=default_actions,
            is_default=True,
        )
```

But, when updating default actions, the object reference is replaced, without updating _default_rule:
```
        if default_actions is not None and default_actions != []:
            # Is currently not validated
            listener.default_actions = default_actions
```

Consequently, the `_default_rule` is never updated, the `rules` property still return a stale reference, and moto believes that the TargetGroup is still attached to the listener.



Discussion:
Thanks for raising and debugging this, @npellegrin! Much appreciated.
Marking it as a bug.

Resolving patch:
diff --git a/moto/elbv2/models.py b/moto/elbv2/models.py
--- a/moto/elbv2/models.py
+++ b/moto/elbv2/models.py
@@ -1423,6 +1423,7 @@ def modify_listener(
         if default_actions is not None and default_actions != []:
             # Is currently not validated
             listener.default_actions = default_actions
+            listener._default_rule[0].actions = default_actions
 
         return listener
 
diff --git a/moto/elbv2/responses.py b/moto/elbv2/responses.py
--- a/moto/elbv2/responses.py
+++ b/moto/elbv2/responses.py
@@ -607,7 +607,7 @@ def modify_listener(self):
         protocol = self._get_param("Protocol")
         ssl_policy = self._get_param("SslPolicy")
         certificates = self._get_list_prefix("Certificates.member")
-        default_actions = self._get_list_prefix("DefaultActions.member")
+        default_actions = self._get_params().get("DefaultActions", [])
 
         # Should really move SSL Policies to models
         if ssl_policy is not None and ssl_policy not in [


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
@@ -521,3 +521,52 @@ def test_create_target_group_with_target_type(target_type):
     group.should.have.key("TargetType").equal(target_type)
     group.shouldnt.have.key("Protocol")
     group.shouldnt.have.key("VpcId")
+
+
+@mock_elbv2
+@mock_ec2
+def test_delete_target_group_after_modifying_listener():
+    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"]
+
+    response = conn.create_listener(
+        LoadBalancerArn=load_balancer_arn,
+        Protocol="HTTP",
+        Port=80,
+        DefaultActions=[{"Type": "forward", "TargetGroupArn": target_group_arn1}],
+    )
+    listener_arn = response["Listeners"][0].get("ListenerArn")
+
+    client.modify_listener(
+        ListenerArn=listener_arn,
+        DefaultActions=[{"Type": "forward", "TargetGroupArn": target_group_arn2,}],
+    )
+
+    response = conn.describe_listeners(LoadBalancerArn=load_balancer_arn)
+    default_actions = response["Listeners"][0]["DefaultActions"]
+    default_actions.should.equal(
+        [{"Type": "forward", "TargetGroupArn": target_group_arn2}]
+    )
+
+    # Target Group 1 can now be deleted, as the LB points to group 2
+    client.delete_target_group(TargetGroupArn=target_group_arn1)
+
+    # Sanity check - we're still pointing to group 2
+    response = conn.describe_listeners(LoadBalancerArn=load_balancer_arn)
+    default_actions = response["Listeners"][0]["DefaultActions"]
+    default_actions.should.equal(
+        [{"Type": "forward", "TargetGroupArn": target_group_arn2}]
+    )