From 1382995ea5bb7a009b5426fa86dde92c501f7229 Mon Sep 17 00:00:00 2001 From: Henri BAUDESSON Date: Tue, 13 Jun 2023 18:20:13 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(back)=20fix=20sync=5Fmedialive=5Fv?= =?UTF-8?q?ideo=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sync_medialive_video is failing when medialive channel exists, but its associated video does not. When a channel does not have a video, it can't be used. Therefore, we should delete the stack of this unused medialive channel. --- CHANGELOG.md | 1 + .../commands/clean_medialive_dev_stack.py | 35 +- .../commands/sync_medialive_video.py | 37 ++- .../test_clean_medialive_dev_stack.py | 298 +++++------------- .../test_sync_medialive_video.py | 38 +++ .../test_medialive_delete_utils.py | 129 ++++++++ .../medialive_utils/medialive_delete_utils.py | 26 ++ 7 files changed, 298 insertions(+), 266 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 737b76d0e5..7b33832e32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ Versioning](https://semver.org/spec/v2.0.0.html). - Blacklist the refresh token on the frontend side (#2265) - Legale pages not accessible from url (#2266) - Remove old svg from the back to the front application (#1485) +- Sync medialive command deletes medialive stack when video not found ### Changed diff --git a/src/backend/marsha/core/management/commands/clean_medialive_dev_stack.py b/src/backend/marsha/core/management/commands/clean_medialive_dev_stack.py index e009636dc1..1aedb390b8 100644 --- a/src/backend/marsha/core/management/commands/clean_medialive_dev_stack.py +++ b/src/backend/marsha/core/management/commands/clean_medialive_dev_stack.py @@ -6,10 +6,8 @@ from django.utils import timezone from marsha.core.utils.medialive_utils import ( - delete_mediapackage_channel, + delete_medialive_stack, list_medialive_channels, - medialive_client, - mediapackage_client, ) from marsha.core.utils.time_utils import to_datetime @@ -42,33 +40,4 @@ def handle(self, *args, **options): created_at + timedelta(seconds=settings.NB_SECONDS_LIVING_DEV_STACK) <= now ): - self.stdout.write( - f"Cleaning stack with name {medialive_channel['Name']}" - ) - - if medialive_channel["State"] == "RUNNING": - self.stdout.write( - "Medialive channel is running, we must stop it first." - ) - channel_waiter = medialive_client.get_waiter("channel_stopped") - medialive_client.stop_channel(ChannelId=medialive_channel["Id"]) - channel_waiter.wait(ChannelId=medialive_channel["Id"]) - - medialive_client.delete_channel(ChannelId=medialive_channel["Id"]) - input_waiter = medialive_client.get_waiter("input_detached") - for medialive_input in medialive_channel["InputAttachments"]: - input_waiter.wait(InputId=medialive_input["InputId"]) - medialive_client.delete_input( - InputId=medialive_input["InputId"] - ) - - try: - # the mediapackage channel can already be deleted when the dev stack - # have ngrok up and running. - delete_mediapackage_channel(medialive_channel["Name"]) - except mediapackage_client.exceptions.NotFoundException: - pass - - self.stdout.write( - f"Stack with name {medialive_channel['Name']} deleted" - ) + delete_medialive_stack(medialive_channel, self.stdout) diff --git a/src/backend/marsha/core/management/commands/sync_medialive_video.py b/src/backend/marsha/core/management/commands/sync_medialive_video.py index b4ee6873b9..1e9d9051c3 100644 --- a/src/backend/marsha/core/management/commands/sync_medialive_video.py +++ b/src/backend/marsha/core/management/commands/sync_medialive_video.py @@ -5,7 +5,11 @@ from marsha.core.defaults import IDLE, RUNNING, STOPPED from marsha.core.models import Video -from marsha.core.utils.medialive_utils import list_medialive_channels, update_id3_tags +from marsha.core.utils.medialive_utils import ( + delete_medialive_stack, + list_medialive_channels, + update_id3_tags, +) from marsha.core.utils.time_utils import to_timestamp @@ -85,17 +89,24 @@ def handle(self, *args, **options): # the channel name contains the environment, the primary key and the created_at # stamp. Here we want to use the primary key _environment, live_pk, _stamp = medialive_channel["Name"].split("_") - live = Video.objects.get(pk=live_pk) - self.stdout.write(f"Checking video {live.id}") - channel_state = medialive_channel.get("State").casefold() - - # If the live state in not sync with media channel state, - # we manually update to avoid soft lock - live_state = live.live_state.casefold() - if not self.is_channel_sync_with_video(live_state, channel_state): + try: + live = Video.objects.get(pk=live_pk) + self.stdout.write(f"Checking video {live.id}") + channel_state = medialive_channel.get("State").casefold() + + # If the live state in not sync with media channel state, + # we manually update to avoid soft lock + live_state = live.live_state.casefold() + if not self.is_channel_sync_with_video(live_state, channel_state): + self.stdout.write( + f"""Video {live.id} not sync: (video) """ + f"""{live_state} != {channel_state} (medialive)""" + ) + self.update_video_state(live, channel_state) + except Video.DoesNotExist: + # live exists in AWS but not our DB self.stdout.write( - f""" - Video {live.id} not sync: (video) {live_state} != {channel_state} (medialive) - """ + f"""Channel {medialive_channel["Name"]} is """ + f"""attached to a video {live_pk} that does not exist""" ) - self.update_video_state(live, channel_state) + delete_medialive_stack(medialive_channel, self.stdout) diff --git a/src/backend/marsha/core/tests/management_commands/test_clean_medialive_dev_stack.py b/src/backend/marsha/core/tests/management_commands/test_clean_medialive_dev_stack.py index 9cbe0f58da..cf8527f7c4 100644 --- a/src/backend/marsha/core/tests/management_commands/test_clean_medialive_dev_stack.py +++ b/src/backend/marsha/core/tests/management_commands/test_clean_medialive_dev_stack.py @@ -1,32 +1,38 @@ """Test clean_medialive_dev_stack command.""" from datetime import datetime, timezone -from io import StringIO from unittest import mock from django.core.management import call_command from django.test import TestCase -from botocore.stub import Stubber - -from marsha.core.management.commands import clean_medialive_dev_stack - class CleanMedialigeDevStackCommandTest(TestCase): """Test clean_medialive_dev_stack command.""" def test_clean_medialive_dev_stack_non_running_medialive_channel(self): """Dev stack is deleted and channel waiter not used when channel is not running.""" - out = StringIO() with mock.patch( "marsha.core.management.commands.clean_medialive_dev_stack.list_medialive_channels" ) as list_medialive_channels_mock, mock.patch( - "marsha.core.management.commands.clean_medialive_dev_stack.delete_mediapackage_channel" - ) as delete_mediapackage_channel_mock, mock.patch( + "marsha.core.management.commands.clean_medialive_dev_stack.delete_medialive_stack" + ) as delete_medialive_stack_mock, mock.patch( "django.utils.timezone.now", return_value=datetime(2021, 8, 26, 13, 25, tzinfo=timezone.utc), - ), Stubber( - clean_medialive_dev_stack.medialive_client - ) as medialive_client_stubber: + ): + live_to_delete = { + "Name": "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356", + "Tags": { + "app": "marsha", + "environment": "dev-bar", + }, + "Id": "562345", + "State": "IDLE", + "InputAttachments": [ + { + "InputId": "876294", + } + ], + } list_medialive_channels_mock.return_value = [ { # non dev environment are ignored @@ -40,70 +46,39 @@ def test_clean_medialive_dev_stack_non_running_medialive_channel(self): "Name": "dev-foo_2389cabd-fc46-4ead-8f82-610f35c37d63_1629984950", "Tags": {"app": "marsha", "environment": "dev-foo"}, }, - { - "Name": "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356", - "Tags": { - "app": "marsha", - "environment": "dev-bar", - }, - "Id": "562345", - "State": "IDLE", - "InputAttachments": [ - { - "InputId": "876294", - } - ], - }, + live_to_delete, ] - medialive_client_stubber.add_response( - "delete_channel", - expected_params={"ChannelId": "562345"}, - service_response={}, - ) + call_command("clean_medialive_dev_stack") - medialive_client_stubber.add_response( - "describe_input", - expected_params={"InputId": "876294"}, - service_response={"State": "DETACHED"}, + delete_medialive_stack_mock.assert_called_once_with( + live_to_delete, mock.ANY ) - medialive_client_stubber.add_response( - "delete_input", - expected_params={"InputId": "876294"}, - service_response={}, - ) - - call_command("clean_medialive_dev_stack", stdout=out) - - delete_mediapackage_channel_mock.assert_called_once_with( - "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356" - ) - medialive_client_stubber.assert_no_pending_responses() - - self.assertEqual( - ( - "Cleaning stack with name " - "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356\n" - "Stack with name " - "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356 deleted\n" - ), - out.getvalue(), - ) - out.close() def test_clean_medialive_dev_stack_running_medialive_channel(self): """Running medialive channel is stopped before deleting all the stack.""" - out = StringIO() with mock.patch( "marsha.core.management.commands.clean_medialive_dev_stack.list_medialive_channels" ) as list_medialive_channels_mock, mock.patch( - "marsha.core.management.commands.clean_medialive_dev_stack.delete_mediapackage_channel" - ) as delete_mediapackage_channel_mock, mock.patch( + "marsha.core.management.commands.clean_medialive_dev_stack.delete_medialive_stack" + ) as delete_medialive_stack_mock, mock.patch( "django.utils.timezone.now", return_value=datetime(2021, 8, 26, 13, 25, tzinfo=timezone.utc), - ), Stubber( - clean_medialive_dev_stack.medialive_client - ) as medialive_client_stubber: + ): + live_to_delete = { + "Name": "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356", + "Tags": { + "app": "marsha", + "environment": "dev-bar", + }, + "Id": "562345", + "State": "RUNNING", + "InputAttachments": [ + { + "InputId": "876294", + } + ], + } list_medialive_channels_mock.return_value = [ { # non dev environment are ignored @@ -117,81 +92,53 @@ def test_clean_medialive_dev_stack_running_medialive_channel(self): "Name": "dev-foo_2389cabd-fc46-4ead-8f82-610f35c37d63_1629984950", "Tags": {"app": "marsha", "environment": "dev-foo"}, }, - { - "Name": "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356", - "Tags": { - "app": "marsha", - "environment": "dev-bar", - }, - "Id": "562345", - "State": "RUNNING", - "InputAttachments": [ - { - "InputId": "876294", - } - ], - }, + live_to_delete, ] - medialive_client_stubber.add_response( - "stop_channel", - expected_params={"ChannelId": "562345"}, - service_response={}, - ) - medialive_client_stubber.add_response( - "describe_channel", - expected_params={"ChannelId": "562345"}, - service_response={"State": "IDLE"}, - ) - medialive_client_stubber.add_response( - "delete_channel", - expected_params={"ChannelId": "562345"}, - service_response={}, - ) - - medialive_client_stubber.add_response( - "describe_input", - expected_params={"InputId": "876294"}, - service_response={"State": "DETACHED"}, - ) - medialive_client_stubber.add_response( - "delete_input", - expected_params={"InputId": "876294"}, - service_response={}, - ) - - call_command("clean_medialive_dev_stack", stdout=out) + call_command("clean_medialive_dev_stack") - delete_mediapackage_channel_mock.assert_called_once_with( - "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356" + delete_medialive_stack_mock.assert_called_once_with( + live_to_delete, mock.ANY ) - medialive_client_stubber.assert_no_pending_responses() - - self.assertEqual( - ( - "Cleaning stack with name " - "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356\n" - "Medialive channel is running, we must stop it first.\n" - "Stack with name " - "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356 deleted\n" - ), - out.getvalue(), - ) - out.close() def test_clean_medialive_dev_stack_multiple_outdated_medialive_channels(self): """Multiple channels are outdates and must be deleted.""" - out = StringIO() with mock.patch( "marsha.core.management.commands.clean_medialive_dev_stack.list_medialive_channels" ) as list_medialive_channels_mock, mock.patch( - "marsha.core.management.commands.clean_medialive_dev_stack.delete_mediapackage_channel" - ) as delete_mediapackage_channel_mock, mock.patch( + "marsha.core.management.commands.clean_medialive_dev_stack.delete_medialive_stack" + ) as delete_medialive_stack_mock, mock.patch( "django.utils.timezone.now", return_value=datetime(2021, 8, 26, 13, 25, tzinfo=timezone.utc), - ), Stubber( - clean_medialive_dev_stack.medialive_client - ) as medialive_client_stubber: + ): + live_to_delete_1 = { + "Name": "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356", + "Tags": { + "app": "marsha", + "environment": "dev-bar", + }, + "Id": "562345", + "State": "RUNNING", + "InputAttachments": [ + { + "InputId": "876294", + } + ], + } + live_to_delete_2 = { + "Name": "dev-baz_fe8a4a85-f955-4db5-ac25-e7972c3095a4_1629982356", + "Tags": { + "app": "marsha", + "environment": "dev-baz", + }, + "Id": "194672", + "State": "IDLE", + "InputAttachments": [ + { + "InputId": "375107", + } + ], + } list_medialive_channels_mock.return_value = [ { # non dev environment are ignored @@ -205,100 +152,11 @@ def test_clean_medialive_dev_stack_multiple_outdated_medialive_channels(self): "Name": "dev-foo_2389cabd-fc46-4ead-8f82-610f35c37d63_1629984950", "Tags": {"app": "marsha", "environment": "dev-foo"}, }, - { - "Name": "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356", - "Tags": { - "app": "marsha", - "environment": "dev-bar", - }, - "Id": "562345", - "State": "RUNNING", - "InputAttachments": [ - { - "InputId": "876294", - } - ], - }, - { - "Name": "dev-baz_fe8a4a85-f955-4db5-ac25-e7972c3095a4_1629982356", - "Tags": { - "app": "marsha", - "environment": "dev-baz", - }, - "Id": "194672", - "State": "IDLE", - "InputAttachments": [ - { - "InputId": "375107", - } - ], - }, + live_to_delete_1, + live_to_delete_2, ] - medialive_client_stubber.add_response( - "stop_channel", - expected_params={"ChannelId": "562345"}, - service_response={}, - ) - medialive_client_stubber.add_response( - "describe_channel", - expected_params={"ChannelId": "562345"}, - service_response={"State": "IDLE"}, - ) - medialive_client_stubber.add_response( - "delete_channel", - expected_params={"ChannelId": "562345"}, - service_response={}, - ) - medialive_client_stubber.add_response( - "describe_input", - expected_params={"InputId": "876294"}, - service_response={"State": "DETACHED"}, - ) - medialive_client_stubber.add_response( - "delete_input", - expected_params={"InputId": "876294"}, - service_response={}, - ) - - medialive_client_stubber.add_response( - "delete_channel", - expected_params={"ChannelId": "194672"}, - service_response={}, - ) - medialive_client_stubber.add_response( - "describe_input", - expected_params={"InputId": "375107"}, - service_response={"State": "DETACHED"}, - ) - medialive_client_stubber.add_response( - "delete_input", - expected_params={"InputId": "375107"}, - service_response={}, - ) - - call_command("clean_medialive_dev_stack", stdout=out) - - delete_mediapackage_channel_mock.assert_any_call( - "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356" - ) - delete_mediapackage_channel_mock.assert_any_call( - "dev-baz_fe8a4a85-f955-4db5-ac25-e7972c3095a4_1629982356" - ) - medialive_client_stubber.assert_no_pending_responses() + call_command("clean_medialive_dev_stack") - self.assertEqual( - ( - "Cleaning stack with name " - "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356\n" - "Medialive channel is running, we must stop it first.\n" - "Stack with name " - "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356 deleted\n" - "Cleaning stack with name " - "dev-baz_fe8a4a85-f955-4db5-ac25-e7972c3095a4_1629982356\n" - "Stack with name " - "dev-baz_fe8a4a85-f955-4db5-ac25-e7972c3095a4_1629982356 deleted\n" - ), - out.getvalue(), - ) - out.close() + delete_medialive_stack_mock.assert_any_call(live_to_delete_1, mock.ANY) + delete_medialive_stack_mock.assert_any_call(live_to_delete_2, mock.ANY) diff --git a/src/backend/marsha/core/tests/management_commands/test_sync_medialive_video.py b/src/backend/marsha/core/tests/management_commands/test_sync_medialive_video.py index ce231059b1..6e7be276b5 100644 --- a/src/backend/marsha/core/tests/management_commands/test_sync_medialive_video.py +++ b/src/backend/marsha/core/tests/management_commands/test_sync_medialive_video.py @@ -412,3 +412,41 @@ def test_already_sync_medialives_and_videos(self): self.assertEqual(live4.live_state, STOPPING) out.close() + + def test_sync_medialive_when_a_video_does_not_exists(self): + """Medialive channels are sync with video and should not be updated.""" + + # Run command + out = StringIO() + with mock.patch( + "django.utils.timezone.now", + return_value=datetime(2022, 10, 14, 15, 25, tzinfo=timezone.utc), + ), mock.patch.object( + sync_medialive_video, "list_medialive_channels" + ) as list_medialive_channels_mock, mock.patch( + "marsha.core.management.commands.sync_medialive_video.delete_medialive_stack" + ) as delete_medialive_stack_mock: + live_to_delete = { + "Name": "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356", + "Tags": {"environment": "test"}, + "Id": "562345", + "State": "RUNNING", + "InputAttachments": [ + { + "InputId": "876294", + } + ], + } + list_medialive_channels_mock.return_value = [live_to_delete] + + call_command("sync_medialive_video", stdout=out) + + delete_medialive_stack_mock.assert_any_call(live_to_delete, mock.ANY) + self.assertIn( + "Channel dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356 is " + "attached to a video 99d60314-20f2-4847-84a4-d2f47bf7fe38 " + "that does not exist\n", + out.getvalue(), + ) + + out.close() diff --git a/src/backend/marsha/core/tests/utils/medialive_utils/test_medialive_delete_utils.py b/src/backend/marsha/core/tests/utils/medialive_utils/test_medialive_delete_utils.py index 21a0acd4d8..edbca59d7e 100644 --- a/src/backend/marsha/core/tests/utils/medialive_utils/test_medialive_delete_utils.py +++ b/src/backend/marsha/core/tests/utils/medialive_utils/test_medialive_delete_utils.py @@ -1,6 +1,9 @@ """Test medialive utils functions.""" +from datetime import datetime, timezone +from io import StringIO from unittest import mock +from django.core.management.base import OutputWrapper from django.test import TestCase, override_settings from botocore.stub import Stubber @@ -125,3 +128,129 @@ def test_delete_mediapackage_channel(self): deleted_endpoints = medialive_utils.delete_mediapackage_channel("1") mediapackage_client_stubber.assert_no_pending_responses() self.assertEqual(deleted_endpoints, ["1", "2"]) + + def test_delete_medialive_stack_on_running_channel(self): + """Should stop the medialive channel then delete the stack.""" + out = StringIO() + live_to_delete = { + "Name": "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356", + "Tags": {"environment": "test"}, + "Id": "562345", + "State": "Running", + "InputAttachments": [ + { + "InputId": "876294", + } + ], + } + + with mock.patch( + "django.utils.timezone.now", + return_value=datetime(2021, 8, 26, 13, 25, tzinfo=timezone.utc), + ), Stubber( + medialive_utils.medialive_client + ) as mediapackage_client_stubber, mock.patch.object( + medialive_utils.medialive_delete_utils, "delete_mediapackage_channel" + ) as delete_mediapackage_channel_mock: + mediapackage_client_stubber.add_response( + "stop_channel", + expected_params={"ChannelId": "562345"}, + service_response={}, + ) + mediapackage_client_stubber.add_response( + "describe_channel", + expected_params={"ChannelId": "562345"}, + service_response={"State": "IDLE"}, + ) + mediapackage_client_stubber.add_response( + "delete_channel", + expected_params={"ChannelId": "562345"}, + service_response={}, + ) + + mediapackage_client_stubber.add_response( + "describe_input", + expected_params={"InputId": "876294"}, + service_response={"State": "DETACHED"}, + ) + mediapackage_client_stubber.add_response( + "delete_input", + expected_params={"InputId": "876294"}, + service_response={}, + ) + + medialive_utils.delete_medialive_stack(live_to_delete, OutputWrapper(out)) + delete_mediapackage_channel_mock.assert_called_once_with( + "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356" + ) + self.assertEqual( + ( + "Cleaning stack with name " + "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356\n" + "Medialive channel is running, we must stop it first.\n" + "Stack with name " + "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356 deleted\n" + ), + out.getvalue(), + ) + out.close() + + def test_delete_medialive_stack_on_stopped_channel(self): + """Should delete a medialive stack directly.""" + out = StringIO() + live_to_delete = { + "Name": "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356", + "Tags": {"environment": "test"}, + "Id": "562345", + "State": "STOPPED", + "InputAttachments": [ + { + "InputId": "876294", + } + ], + } + + with mock.patch( + "django.utils.timezone.now", + return_value=datetime(2021, 8, 26, 13, 25, tzinfo=timezone.utc), + ), Stubber( + medialive_utils.medialive_client + ) as mediapackage_client_stubber, mock.patch.object( + medialive_utils.medialive_delete_utils, "delete_mediapackage_channel" + ) as delete_mediapackage_channel_mock: + mediapackage_client_stubber.add_response( + "delete_channel", + expected_params={"ChannelId": "562345"}, + service_response={}, + ) + mediapackage_client_stubber.add_response( + "describe_input", + expected_params={"InputId": "876294"}, + service_response={"State": "DETACHED"}, + ) + mediapackage_client_stubber.add_response( + "delete_input", + expected_params={"InputId": "876294"}, + service_response={}, + ) + + mediapackage_client_stubber.add_response( + "delete_input", + expected_params={"InputId": "876294"}, + service_response={}, + ) + + medialive_utils.delete_medialive_stack(live_to_delete, OutputWrapper(out)) + delete_mediapackage_channel_mock.assert_called_once_with( + "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356" + ) + self.assertEqual( + ( + "Cleaning stack with name " + "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356\n" + "Stack with name " + "dev-bar_99d60314-20f2-4847-84a4-d2f47bf7fe38_1629982356 deleted\n" + ), + out.getvalue(), + ) + out.close() diff --git a/src/backend/marsha/core/utils/medialive_utils/medialive_delete_utils.py b/src/backend/marsha/core/utils/medialive_utils/medialive_delete_utils.py index d0274a831f..524b088320 100644 --- a/src/backend/marsha/core/utils/medialive_utils/medialive_delete_utils.py +++ b/src/backend/marsha/core/utils/medialive_utils/medialive_delete_utils.py @@ -43,3 +43,29 @@ def delete_mediapackage_channel(channel_id): deleted_endpoints.append(origin_endpoint.get("Id")) mediapackage_client.delete_channel(Id=channel_id) return deleted_endpoints + + +def delete_medialive_stack(medialive_channel, stdout): + """Delete a medialive stack and related mediapackage.""" + stdout.write(f"Cleaning stack with name {medialive_channel['Name']}") + + if medialive_channel["State"].casefold() == "running": + stdout.write("Medialive channel is running, we must stop it first.") + channel_waiter = medialive_client.get_waiter("channel_stopped") + medialive_client.stop_channel(ChannelId=medialive_channel["Id"]) + channel_waiter.wait(ChannelId=medialive_channel["Id"]) + + medialive_client.delete_channel(ChannelId=medialive_channel["Id"]) + input_waiter = medialive_client.get_waiter("input_detached") + for medialive_input in medialive_channel["InputAttachments"]: + input_waiter.wait(InputId=medialive_input["InputId"]) + medialive_client.delete_input(InputId=medialive_input["InputId"]) + + try: + # the mediapackage channel can already be deleted when the dev stack + # have ngrok up and running. + delete_mediapackage_channel(medialive_channel["Name"]) + except mediapackage_client.exceptions.NotFoundException: + pass + + stdout.write(f"Stack with name {medialive_channel['Name']} deleted")