Repository: openfun/marsha

Issue:
Live remains open if there is no activity at all
## Bug Report

**Expected behavior/code**
When a live has no activity after 20 min it should be closed and all resources deleted.

**Actual Behavior**
We want to delete all aws medialive/mediapackage resources not used. To do that we created a management command responsible to look medialive channel activity through cloudwatch logs. This work fine when a live has been started. 

When a live is only configured and never started, there no activity at all, no log in cloudwatch. In this case the management command do nothing and resource remain open.

**Steps to Reproduce**
1. Create a new video
2. click button `configure a live`
3. Do nothing more

**Possible Solution**

include in the management command this case. If there is no log, there is no activity and closes it after a delay of 20 min.



Discussion:

Resolving patch:
diff --git a/src/backend/marsha/core/management/commands/check_live_idle.py b/src/backend/marsha/core/management/commands/check_live_idle.py
new file mode 100644
--- /dev/null
+++ b/src/backend/marsha/core/management/commands/check_live_idle.py
@@ -0,0 +1,36 @@
+"""Check live in idle state management command."""
+from datetime import timedelta
+
+from django.conf import settings
+from django.core.management.base import BaseCommand
+from django.utils import timezone
+
+from marsha.core.defaults import DELETED, IDLE
+from marsha.core.models import Video
+from marsha.core.utils.medialive_utils import delete_aws_element_stack
+
+
+def generate_expired_date():
+    """Generate a datetime object NB_DAYS_KEEPING_LIVE_IDLE days in the past."""
+    return timezone.now() - timedelta(days=settings.NB_DAYS_KEEPING_LIVE_IDLE)
+
+
+class Command(BaseCommand):
+    """Check every live streaming in idle state."""
+
+    help = "Check all live in IDLE state and close them if there are with no activity"
+
+    def handle(self, *args, **options):
+        """Execute management command."""
+        videos = Video.objects.filter(
+            live_state=IDLE,
+            updated_on__lte=generate_expired_date(),
+        )
+
+        for video in videos:
+            """Stop and delete the video."""
+            self.stdout.write(f"Deleting video {video.id}")
+            delete_aws_element_stack(video)
+            video.upload_state = DELETED
+            video.live_state = None
+            video.save()
diff --git a/src/backend/marsha/settings.py b/src/backend/marsha/settings.py
--- a/src/backend/marsha/settings.py
+++ b/src/backend/marsha/settings.py
@@ -260,6 +260,7 @@ class Base(Configuration):
 
     MAINTENANCE_MODE = values.BooleanValue(False)
     NB_DAYS_BEFORE_DELETING_LIVE_RECORDINGS = values.Value(14)
+    NB_DAYS_KEEPING_LIVE_IDLE = values.Value(7)
 
     # pylint: disable=invalid-name
     @property


Test patch:
diff --git a/src/backend/marsha/core/tests/test_command_check_live_idle.py b/src/backend/marsha/core/tests/test_command_check_live_idle.py
new file mode 100644
--- /dev/null
+++ b/src/backend/marsha/core/tests/test_command_check_live_idle.py
@@ -0,0 +1,47 @@
+"""Tests for check_live_idle command."""
+from datetime import timedelta
+from io import StringIO
+from unittest import mock
+
+from django.core.management import call_command
+from django.test import TestCase
+from django.utils import timezone
+
+from ..defaults import DELETED, IDLE
+from ..factories import VideoFactory
+
+
+class CheckLiveIdleTest(TestCase):
+    """Test check_live_idle command."""
+
+    def test_check_live_idle_no_video_to_process(self):
+        """Command should do nothing when there is no video to process."""
+        out = StringIO()
+        with mock.patch(
+            "marsha.core.management.commands.check_live_idle.delete_aws_element_stack"
+        ) as mock_delete_aws_element_stack:
+            call_command("check_live_idle", stdout=out)
+            mock_delete_aws_element_stack.assert_not_called()
+
+        self.assertEqual("", out.getvalue())
+        out.close()
+
+    def test_check_live_idle_video_to_delete(self):
+        """Command should delete a video in idle state too long."""
+        video = VideoFactory(id="36b82a5e-f2a6-4c11-bdf4-aa435a5dffc2", live_state=IDLE)
+        out = StringIO()
+        with mock.patch(
+            "marsha.core.management.commands.check_live_idle.delete_aws_element_stack"
+        ) as mock_delete_aws_element_stack, mock.patch(
+            "marsha.core.management.commands.check_live_idle.generate_expired_date"
+        ) as generate_expired_date_mock:
+            generate_expired_date_mock.return_value = timezone.now() + timedelta(days=1)
+            call_command("check_live_idle", stdout=out)
+            mock_delete_aws_element_stack.assert_called_once()
+
+        video.refresh_from_db()
+        self.assertEqual(video.upload_state, DELETED)
+        self.assertIn(
+            "Deleting video 36b82a5e-f2a6-4c11-bdf4-aa435a5dffc2", out.getvalue()
+        )
+        out.close()