From 83a215a0aeb5ccb5c8de527747f9eeb256e6f32a Mon Sep 17 00:00:00 2001 From: Luca Zaninotto Date: Thu, 12 Dec 2024 16:10:46 +0100 Subject: [PATCH] chore(tenants): tenant cleanup Closes #504. Manages resource cleanup on tenant deletion, deleting base images, ephemeral images and system models images on remote storage. Signed-off-by: Luca Zaninotto --- .../lib/edgehog/base_images/base_images.ex | 7 +- backend/lib/edgehog/devices/devices.ex | 8 +- .../edgehog/os_management/os_management.ex | 1 + .../ota_operation/ota_operation.ex | 12 ++- .../tenants/tenant/changes/handle_cleanup.ex | 85 +++++++++++++++++++ backend/lib/edgehog/tenants/tenant/tenant.ex | 10 ++- backend/test/edgehog/tenants_test.exs | 21 ++++- 7 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 backend/lib/edgehog/tenants/tenant/changes/handle_cleanup.ex diff --git a/backend/lib/edgehog/base_images/base_images.ex b/backend/lib/edgehog/base_images/base_images.ex index 3e07d0ba9..b5f8b33a1 100644 --- a/backend/lib/edgehog/base_images/base_images.ex +++ b/backend/lib/edgehog/base_images/base_images.ex @@ -1,7 +1,7 @@ # # This file is part of Edgehog. # -# Copyright 2022-2024 SECO Mind Srl +# Copyright 2022-2025 SECO Mind Srl # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -67,7 +67,10 @@ defmodule Edgehog.BaseImages do end resources do - resource BaseImage + resource BaseImage do + define :delete_base_image, action: :destroy + end + resource BaseImageCollection end end diff --git a/backend/lib/edgehog/devices/devices.ex b/backend/lib/edgehog/devices/devices.ex index 05448cd4b..f2e38eea6 100644 --- a/backend/lib/edgehog/devices/devices.ex +++ b/backend/lib/edgehog/devices/devices.ex @@ -1,7 +1,7 @@ # # This file is part of Edgehog. # -# Copyright 2021-2024 SECO Mind Srl +# Copyright 2021-2025 SECO Mind Srl # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -86,7 +86,11 @@ defmodule Edgehog.Devices do resource Device resource HardwareType resource Edgehog.Devices.HardwareTypePartNumber - resource SystemModel + + resource SystemModel do + define :delete_system_model, action: :destroy + end + resource Edgehog.Devices.SystemModelPartNumber end end diff --git a/backend/lib/edgehog/os_management/os_management.ex b/backend/lib/edgehog/os_management/os_management.ex index 9785401fd..b599849a2 100644 --- a/backend/lib/edgehog/os_management/os_management.ex +++ b/backend/lib/edgehog/os_management/os_management.ex @@ -44,6 +44,7 @@ defmodule Edgehog.OSManagement do define :mark_ota_operation_as_timed_out, action: :mark_as_timed_out define :update_ota_operation_status, action: :update_status, args: [:status] define :send_update_request, args: [:ota_operation] + define :delete_ota_operation, action: :destroy end end end diff --git a/backend/lib/edgehog/os_management/ota_operation/ota_operation.ex b/backend/lib/edgehog/os_management/ota_operation/ota_operation.ex index 30208ac35..c8543f577 100644 --- a/backend/lib/edgehog/os_management/ota_operation/ota_operation.ex +++ b/backend/lib/edgehog/os_management/ota_operation/ota_operation.ex @@ -1,7 +1,7 @@ # # This file is part of Edgehog. # -# Copyright 2022-2024 SECO Mind Srl +# Copyright 2022-2025 SECO Mind Srl # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -47,7 +47,7 @@ defmodule Edgehog.OSManagement.OTAOperation do end actions do - defaults [:read, :destroy] + defaults [:read] create :create_fixture do accept [ @@ -98,6 +98,14 @@ defmodule Edgehog.OSManagement.OTAOperation do change {PublishNotification, event_type: :ota_operation_created} end + destroy :destroy do + require_atomic? false + + change Changes.HandleEphemeralImageDeletion do + where attribute_equals(:manual?, true) + end + end + update :mark_as_timed_out do # Needed because PublishNotification and HandleEphemeralImageDeletion are not atomic require_atomic? false diff --git a/backend/lib/edgehog/tenants/tenant/changes/handle_cleanup.ex b/backend/lib/edgehog/tenants/tenant/changes/handle_cleanup.ex new file mode 100644 index 000000000..7c7ae8744 --- /dev/null +++ b/backend/lib/edgehog/tenants/tenant/changes/handle_cleanup.ex @@ -0,0 +1,85 @@ +# +# This file is part of Edgehog. +# +# Copyright 2025 SECO Mind Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +defmodule Edgehog.Tenants.Tenant.Changes.HandleCleanup do + @moduledoc false + use Ash.Resource.Change + + alias Edgehog.BaseImages + alias Edgehog.BaseImages.BaseImage + alias Edgehog.Devices + alias Edgehog.Devices.SystemModel + alias Edgehog.OSManagement + alias Edgehog.OSManagement.OTAOperation + + require Ash.Query + require Logger + + @impl Ash.Resource.Change + def change(changeset, _opts, _context) do + tenant = changeset.data + + system_models = Ash.read!(SystemModel, tenant: tenant) + + base_images = Ash.read!(BaseImage, tenant: tenant) + + manual_otas = + OTAOperation + |> Ash.Query.filter(manual?) + |> Ash.read!(tenant: tenant) + + Ash.Changeset.after_transaction(changeset, fn _changeset, result -> + with {:ok, tenant} <- result do + try do + cleanup_system_models(system_models, tenant) + cleanup_base_images(base_images, tenant) + cleanup_ephimeral_images(manual_otas, tenant) + catch + signal, error -> + Logger.error(""" + Tenant cleanup was not completed: + recived signal #{inspect(signal)} with the following error: #{inspect({error})} + """) + end + + # Return :ok if the cleanup does not fully succeeds + {:ok, tenant} + end + end) + end + + defp cleanup_system_models(system_models, tenant) do + for system_model <- system_models do + Devices.delete_system_model!(system_model, tenant: tenant) + end + end + + defp cleanup_base_images(base_images, tenant) do + for image <- base_images do + BaseImages.delete_base_image!(image, tenant: tenant) + end + end + + defp cleanup_ephimeral_images(manual_otas, tenant) do + for ota <- manual_otas do + OSManagement.delete_ota_operation!(ota, tenant: tenant) + end + end +end diff --git a/backend/lib/edgehog/tenants/tenant/tenant.ex b/backend/lib/edgehog/tenants/tenant/tenant.ex index 47d591151..dd95ada8a 100644 --- a/backend/lib/edgehog/tenants/tenant/tenant.ex +++ b/backend/lib/edgehog/tenants/tenant/tenant.ex @@ -31,6 +31,7 @@ defmodule Edgehog.Tenants.Tenant do alias Ash.Error.Invalid.TenantRequired alias Edgehog.Tenants.AstarteConfig alias Edgehog.Tenants.Tenant + alias Edgehog.Tenants.Tenant.Changes alias Edgehog.Validations require Ash.Query @@ -60,7 +61,7 @@ defmodule Edgehog.Tenants.Tenant do end actions do - defaults [:read, :destroy] + defaults [:read] create :create do primary? true @@ -99,6 +100,13 @@ defmodule Edgehog.Tenants.Tenant do run Tenant.ManualActions.ReconcilerAction end + + destroy :destroy do + description "Destroy tenant handling resource cleanup." + + require_atomic? false + change Changes.HandleCleanup + end end validations do diff --git a/backend/test/edgehog/tenants_test.exs b/backend/test/edgehog/tenants_test.exs index 2910e7d66..9c9b7191a 100644 --- a/backend/test/edgehog/tenants_test.exs +++ b/backend/test/edgehog/tenants_test.exs @@ -1,7 +1,7 @@ # # This file is part of Edgehog. # -# Copyright 2021-2024 SECO Mind Srl +# Copyright 2021-2025 SECO Mind Srl # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -28,6 +28,8 @@ defmodule Edgehog.TenantsTest do alias Ash.Error.Invalid alias Ash.Error.Query.NotFound alias Edgehog.Astarte + alias Edgehog.BaseImages.StorageMock + alias Edgehog.OSManagement.EphemeralImageMock alias Edgehog.Tenants alias Edgehog.Tenants.ReconcilerMock alias Edgehog.Tenants.Tenant @@ -326,8 +328,21 @@ defmodule Edgehog.TenantsTest do manual_ota_operation = manual_ota_operation_fixture(device_id: device.id, tenant: tenant) update_channel = update_channel_fixture(tenant: tenant) - update_campaign = update_campaign_fixture(tenant: tenant) - update_target = target_fixture(tenant: tenant) + update_campaign = update_campaign_fixture(base_image_id: base_image.id, tenant: tenant) + update_target = target_fixture(base_image_id: base_image.id, tenant: tenant) + + expect(StorageMock, :delete, fn to_delete -> + assert to_delete.id == base_image.id + :ok + end) + + expect(EphemeralImageMock, :delete, fn tenant_id, ota_operation_id, url -> + assert tenant_id == manual_ota_operation.tenant_id + assert ota_operation_id == manual_ota_operation.id + assert url == manual_ota_operation.base_image_url + + :ok + end) assert :ok = Tenants.destroy_tenant(tenant)