diff --git a/backend/lib/edgehog/containers/containers.ex b/backend/lib/edgehog/containers/containers.ex index bc8e9396f..8dd8b59f7 100644 --- a/backend/lib/edgehog/containers/containers.ex +++ b/backend/lib/edgehog/containers/containers.ex @@ -100,12 +100,18 @@ defmodule Edgehog.Containers do define :deploy, action: :deploy, args: [:release_id, :device_id] define :send_deploy_request, action: :send_deploy_request, args: [:deployment] define :fetch_deployment, action: :read, get_by: [:id] - define :deployment_set_status, action: :set_status, args: [:status, :message] - define :delete_deployment, action: :destroy define :deployment_update_status, action: :update_status define :deployments_with_release, action: :filter_by_release, args: [:release_id] define :run_ready_actions, action: :run_ready_actions + + define :mark_deployment_as_sent, action: :mark_as_sent + define :mark_deployment_as_deleting, action: :mark_as_deleting + define :mark_deployment_as_errored, action: :mark_as_errored, args: [:message] + define :mark_deployment_as_started, action: :mark_as_started + define :mark_deployment_as_starting, action: :mark_as_starting + define :mark_deployment_as_stopped, action: :mark_as_stopped + define :mark_deployment_as_stopping, action: :mark_as_stopping end resource Edgehog.Containers.Image do diff --git a/backend/lib/edgehog/containers/deployment.ex b/backend/lib/edgehog/containers/deployment.ex index d0e4e1000..d97a121b2 100644 --- a/backend/lib/edgehog/containers/deployment.ex +++ b/backend/lib/edgehog/containers/deployment.ex @@ -1,7 +1,7 @@ # # This file is part of Edgehog. # -# Copyright 2024 SECO Mind Srl +# Copyright 2024 - 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. @@ -25,9 +25,9 @@ defmodule Edgehog.Containers.Deployment do extensions: [AshGraphql.Resource] alias Edgehog.Containers.Deployment.Changes + alias Edgehog.Containers.Deployment.Types.ResourcesState alias Edgehog.Containers.ManualActions alias Edgehog.Containers.Release - alias Edgehog.Containers.Types.DeploymentStatus alias Edgehog.Containers.Validations.IsUpgrade alias Edgehog.Containers.Validations.SameApplication @@ -36,7 +36,7 @@ defmodule Edgehog.Containers.Deployment do end actions do - defaults [:read, :destroy, create: [:device_id, :release_id, :status, :message]] + defaults [:read, :destroy, create: [:device_id, :release_id, :state, :last_message]] create :deploy do description """ @@ -107,15 +107,48 @@ defmodule Edgehog.Containers.Deployment do manual ManualActions.SendDeploymentUpgrade end - update :set_status do - accept [:status, :message] + update :mark_as_sent do + change set_attribute(:state, :sent) + end + + update :mark_as_started do + change set_attribute(:state, :started) + end + + update :mark_as_starting do + require_atomic? false + + change Changes.MarkAsStarting + end + + update :mark_as_stopped do + change set_attribute(:state, :stopped) + end + + update :mark_as_stopping do + require_atomic? false + + change Changes.MarkAsStopping + end + + update :mark_as_errored do + argument :message, :string do + allow_nil? false + end + + change set_attribute(:last_message, arg(:message)) + change set_attribute(:state, :error) + end + + update :mark_as_deleting do + change set_attribute(:state, :deleting) end update :update_status do change Changes.CheckImages change Changes.CheckNetworks change Changes.CheckContainers - change Changes.CheckDeployments + change Changes.CheckDeployment require_atomic? false end @@ -130,13 +163,27 @@ defmodule Edgehog.Containers.Deployment do attributes do uuid_primary_key :id - attribute :status, DeploymentStatus do - allow_nil? false - default :created + attribute :resources_state, ResourcesState do + default nil public? true end - attribute :message, :string do + attribute :state, :atom do + constraints one_of: [ + :created, + :sent, + :deleting, + :error, + :started, + :starting, + :stopped, + :stopping + ] + + public? true + end + + attribute :last_message, :string do public? true end @@ -158,6 +205,11 @@ defmodule Edgehog.Containers.Deployment do end end + calculations do + calculate :ready?, :boolean, expr(state in [:started, :starting, :stopped, :stopping]) + calculate :resources_ready?, :boolean, expr(resources_state == :ready) + end + identities do identity :release_instance, [:device_id, :release_id] end diff --git a/backend/lib/edgehog/containers/deployment/changes/check_containers.ex b/backend/lib/edgehog/containers/deployment/changes/check_containers.ex index 2139500e8..2d8e0b011 100644 --- a/backend/lib/edgehog/containers/deployment/changes/check_containers.ex +++ b/backend/lib/edgehog/containers/deployment/changes/check_containers.ex @@ -1,7 +1,7 @@ # # This file is part of Edgehog. # -# Copyright 2024 SECO Mind Srl +# Copyright 2024 - 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. @@ -26,7 +26,8 @@ defmodule Edgehog.Containers.Deployment.Changes.CheckContainers do def change(changeset, _opts, _context) do deployment = changeset.data - with {:ok, :created_networks} <- Ash.Changeset.fetch_argument_or_change(changeset, :status), + with {:ok, :created_networks} <- + Ash.Changeset.fetch_argument_or_change(changeset, :resources_state), {:ok, deployment} <- Ash.load(deployment, device: :available_containers, release: [:containers]) do available_container_ids = Enum.map(deployment.device.available_containers, & &1.id) @@ -35,7 +36,7 @@ defmodule Edgehog.Containers.Deployment.Changes.CheckContainers do Enum.reject(deployment.release.containers, &(&1.id in available_container_ids)) if missing_containers == [] do - Ash.Changeset.change_attribute(changeset, :status, :created_containers) + Ash.Changeset.change_attribute(changeset, :resources_state, :created_containers) else changeset end diff --git a/backend/lib/edgehog/containers/deployment/changes/check_deployments.ex b/backend/lib/edgehog/containers/deployment/changes/check_deployment.ex similarity index 50% rename from backend/lib/edgehog/containers/deployment/changes/check_deployments.ex rename to backend/lib/edgehog/containers/deployment/changes/check_deployment.ex index 66fbaf58a..f21168c4e 100644 --- a/backend/lib/edgehog/containers/deployment/changes/check_deployments.ex +++ b/backend/lib/edgehog/containers/deployment/changes/check_deployment.ex @@ -1,7 +1,7 @@ # # This file is part of Edgehog. # -# Copyright 2024 SECO Mind Srl +# Copyright 2024 - 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. @@ -18,7 +18,7 @@ # SPDX-License-Identifier: Apache-2.0 # -defmodule Edgehog.Containers.Deployment.Changes.CheckDeployments do +defmodule Edgehog.Containers.Deployment.Changes.CheckDeployment do @moduledoc false use Ash.Resource.Change @@ -28,25 +28,19 @@ defmodule Edgehog.Containers.Deployment.Changes.CheckDeployments do def change(changeset, _opts, _context) do deployment = changeset.data - with {:ok, :created_containers} <- Ash.Changeset.fetch_argument_or_change(changeset, :status), - {:ok, deployment} <- Ash.load(deployment, device: :available_deployments) do - available_deployment = - Enum.find(deployment.device.available_deployments, &(&1.id == deployment.id)) - - if available_deployment do - changeset - |> Ash.Changeset.change_attribute(:status, available_deployment.status) - |> Ash.Changeset.after_transaction(fn _changeset, transaction_result -> - with {:ok, deployment} <- transaction_result do - Containers.run_ready_actions(deployment) - end - end) - else - changeset - end + with {:ok, :created_containers} <- + Ash.Changeset.fetch_argument_or_change(changeset, :resources_state), + {:ok, deployment} <- Ash.load(deployment, :ready?), + true <- deployment.ready? do + changeset + |> Ash.Changeset.change_attribute(:resources_state, :ready) + |> Ash.Changeset.after_transaction(fn _changeset, transaction_result -> + with {:ok, deployment} <- transaction_result do + Containers.run_ready_actions(deployment) + end + end) else - _ -> - changeset + _ -> changeset end end end diff --git a/backend/lib/edgehog/containers/deployment/changes/check_images.ex b/backend/lib/edgehog/containers/deployment/changes/check_images.ex index 0761c6aec..6f422b896 100644 --- a/backend/lib/edgehog/containers/deployment/changes/check_images.ex +++ b/backend/lib/edgehog/containers/deployment/changes/check_images.ex @@ -1,7 +1,7 @@ # # This file is part of Edgehog. # -# Copyright 2024 SECO Mind Srl +# Copyright 2024 - 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. @@ -26,7 +26,7 @@ defmodule Edgehog.Containers.Deployment.Changes.CheckImages do def change(changeset, _opts, _context) do deployment = changeset.data - with :sent <- deployment.status, + with nil <- deployment.resources_state, {:ok, deployment} <- Ash.load(deployment, device: :available_images, release: [containers: [:image]]) do available_images_ids = @@ -38,7 +38,7 @@ defmodule Edgehog.Containers.Deployment.Changes.CheckImages do |> Enum.reject(&(&1 in available_images_ids)) if missing_images == [] do - Ash.Changeset.change_attribute(changeset, :status, :created_images) + Ash.Changeset.change_attribute(changeset, :resources_state, :created_images) else changeset end diff --git a/backend/lib/edgehog/containers/deployment/changes/check_networks.ex b/backend/lib/edgehog/containers/deployment/changes/check_networks.ex index 9f32d0069..b7b93e70e 100644 --- a/backend/lib/edgehog/containers/deployment/changes/check_networks.ex +++ b/backend/lib/edgehog/containers/deployment/changes/check_networks.ex @@ -28,7 +28,8 @@ defmodule Edgehog.Containers.Deployment.Changes.CheckNetworks do def change(changeset, _opts, _context) do deployment = changeset.data - with {:ok, :created_images} <- Ash.Changeset.fetch_argument_or_change(changeset, :status), + with {:ok, :created_images} <- + Ash.Changeset.fetch_argument_or_change(changeset, :resources_state), {:ok, deployment} <- Ash.load(deployment, device: [], release: [containers: [:networks]]) do device = deployment.device @@ -46,7 +47,7 @@ defmodule Edgehog.Containers.Deployment.Changes.CheckNetworks do |> Enum.all?(& &1.ready?) if networks_ready?, - do: Ash.Changeset.change_attribute(changeset, :status, :created_networks), + do: Ash.Changeset.change_attribute(changeset, :resources_state, :created_networks), else: changeset else _ -> changeset diff --git a/backend/lib/edgehog/containers/deployment/changes/mark_as_starting.ex b/backend/lib/edgehog/containers/deployment/changes/mark_as_starting.ex new file mode 100644 index 000000000..57ef10ad9 --- /dev/null +++ b/backend/lib/edgehog/containers/deployment/changes/mark_as_starting.ex @@ -0,0 +1,34 @@ +# +# 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.Containers.Deployment.Changes.MarkAsStarting do + @moduledoc false + + use Ash.Resource.Change + + @impl Ash.Resource.Change + def change(changeset, _opts, _context) do + deployment = changeset.data + + if deployment.state == :started, + do: changeset, + else: Ash.Changeset.force_change_attribute(changeset, :state, :starting) + end +end diff --git a/backend/lib/edgehog/containers/deployment/changes/mark_as_stopping.ex b/backend/lib/edgehog/containers/deployment/changes/mark_as_stopping.ex new file mode 100644 index 000000000..5be0eab26 --- /dev/null +++ b/backend/lib/edgehog/containers/deployment/changes/mark_as_stopping.ex @@ -0,0 +1,34 @@ +# +# 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.Containers.Deployment.Changes.MarkAsStopping do + @moduledoc false + + use Ash.Resource.Change + + @impl Ash.Resource.Change + def change(changeset, _opts, _context) do + deployment = changeset.data + + if deployment.state == :stopped, + do: changeset, + else: Ash.Changeset.force_change_attribute(changeset, :state, :stopping) + end +end diff --git a/backend/lib/edgehog/containers/types/deployment_status.ex b/backend/lib/edgehog/containers/deployment/types/resources_state.ex similarity index 57% rename from backend/lib/edgehog/containers/types/deployment_status.ex rename to backend/lib/edgehog/containers/deployment/types/resources_state.ex index 484086b26..12083ff4f 100644 --- a/backend/lib/edgehog/containers/types/deployment_status.ex +++ b/backend/lib/edgehog/containers/deployment/types/resources_state.ex @@ -1,7 +1,7 @@ # # This file is part of Edgehog. # -# Copyright 2024 SECO Mind Srl +# Copyright 2024 - 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. @@ -18,24 +18,14 @@ # SPDX-License-Identifier: Apache-2.0 # -defmodule Edgehog.Containers.Types.DeploymentStatus do +defmodule Edgehog.Containers.Deployment.Types.ResourcesState do @moduledoc false use Ash.Type.Enum, values: [ - deleting: "The deployment is being deleted", - error: "The deployment process entered an error state.", - started: "The deployment is running.", - starting: "The deployment is starting.", - stopped: "The deployment has stopped.", - stopping: "The deploymen is stopping.", - created: "The deployment has been received by the backend and will be sent to the device.", - sent: "All the necessary resources have been sent to the device.", - # TODO: these are internal states that should not be exposed. - # Remove when reimplementing the deployment and its status as a state machine created_images: "The device has received the necessary image descriptions for the deployment.", created_networks: "The device has received all the network descriptions necessary for the deployment.", created_containers: "The device has received all the container descriptions necessary for the deployment.", - created_deployment: "The device has received the release description of the the deployment." + ready: "All the underlying resources needed for the deploymes have been received by the device." ] def graphql_type(_), do: :application_deployment_status diff --git a/backend/lib/edgehog/containers/manual_actions/send_deploy_request.ex b/backend/lib/edgehog/containers/manual_actions/send_deploy_request.ex index 56df14e2f..695221d93 100644 --- a/backend/lib/edgehog/containers/manual_actions/send_deploy_request.ex +++ b/backend/lib/edgehog/containers/manual_actions/send_deploy_request.ex @@ -54,7 +54,7 @@ defmodule Edgehog.Containers.ManualActions.SendDeployRequest do :ok <- send_create_container_requests(device, containers), :ok <- deploy_networks(device, networks), {:ok, _device} <- Devices.send_create_deployment_request(device, deployment) do - Containers.deployment_set_status(deployment, :sent, nil, tenant: tenant) + Containers.mark_deployment_as_sent(deployment, tenant: tenant) end end end diff --git a/backend/lib/edgehog/triggers/handler/manual_actions/handle_trigger.ex b/backend/lib/edgehog/triggers/handler/manual_actions/handle_trigger.ex index bebfa603b..b5c203b9a 100644 --- a/backend/lib/edgehog/triggers/handler/manual_actions/handle_trigger.ex +++ b/backend/lib/edgehog/triggers/handler/manual_actions/handle_trigger.ex @@ -42,15 +42,6 @@ defmodule Edgehog.Triggers.Handler.ManualActions.HandleTrigger do @ota_response "io.edgehog.devicemanager.OTAResponse" @system_info "io.edgehog.devicemanager.SystemInfo" - @initial_statuses [ - :created, - :sent, - :created_images, - :created_networks, - :created_containers, - :created_deployment - ] - @impl Ash.Resource.Actions.Implementation def run(input, _opts, _context) do realm_name = input.arguments.realm_name @@ -203,28 +194,24 @@ defmodule Edgehog.Triggers.Handler.ManualActions.HandleTrigger do "/" <> deployment_id = event.path %{ - "status" => status, + "status" => state, "message" => message } = event.value with {:ok, deployment} <- Containers.fetch_deployment(deployment_id, tenant: tenant) do - case {deployment.status, status} do - {:started, "Starting"} -> - # Skip Starting if already Started - {:ok, deployment} - - {:stopped, "Stopping"} -> - # Skip Stopping if already Stopped - {:ok, deployment} - - {_, "Error"} -> - # Errors have precedence - Containers.deployment_set_status(deployment, status, message, tenant: tenant) - - _ -> - if deployment.status in @initial_statuses, - do: Containers.deployment_update_status(deployment, tenant: tenant), - else: Containers.deployment_set_status(deployment, status, message, tenant: tenant) + case state do + "Starting" -> + deployment + |> Containers.mark_deployment_as_starting!(tenant: tenant) + |> Containers.deployment_update_status(tenant: tenant) + + "Stopping" -> + deployment + |> Containers.mark_deployment_as_stopping!(tenant: tenant) + |> Containers.deployment_update_status(tenant: tenant) + + "Error" -> + Containers.mark_deployment_as_errored(deployment, message, tenant: tenant) end end end @@ -232,18 +219,20 @@ defmodule Edgehog.Triggers.Handler.ManualActions.HandleTrigger do defp handle_event(%IncomingData{interface: @available_deployments} = event, tenant, _realm_id, _device_id, _timestamp) do case String.split(event.path, "/") do ["", deployment_id, "status"] -> - status = event.value + state = event.value with {:ok, deployment} <- Containers.fetch_deployment(deployment_id, tenant: tenant) do - cond do - status == nil -> + case state do + nil -> Containers.delete_deployment(deployment) - deployment.status in @initial_statuses -> + "Started" -> + Containers.mark_deployment_as_started(deployment, tenant: tenant) Containers.deployment_update_status(deployment, tenant: tenant) - true -> - Containers.deployment_set_status(deployment, status, deployment.message, tenant: tenant) + "Stopped" -> + Containers.mark_deployment_as_stopped(deployment, tenant: tenant) + Containers.deployment_update_status(deployment, tenant: tenant) end end diff --git a/backend/priv/repo/migrations/20250210140512_deployment_caching_attributes.exs b/backend/priv/repo/migrations/20250210140512_deployment_caching_attributes.exs new file mode 100644 index 000000000..34632c9fc --- /dev/null +++ b/backend/priv/repo/migrations/20250210140512_deployment_caching_attributes.exs @@ -0,0 +1,53 @@ +# +# 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.Repo.Migrations.DeploymentCachingAttributes do + @moduledoc """ + Updates resources based on their most recent snapshots. + + This file was autogenerated with `mix ash_postgres.generate_migrations` + """ + + use Ecto.Migration + + def up do + rename table(:application_deployments), :status, to: :state + + rename table(:application_deployments), :message, to: :last_message + + alter table(:application_deployments) do + modify :last_message, :text + modify :state, :text, null: true, default: nil + add :resources_state, :text + end + end + + def down do + alter table(:application_deployments) do + remove :resources_state + modify :status, :text, null: false, default: "created" + modify :message, :text + end + + rename table(:application_deployments), :last_message, to: :message + + rename table(:application_deployments), :state, to: :status + end +end diff --git a/backend/priv/resource_snapshots/repo/application_deployments/20250210140512.json b/backend/priv/resource_snapshots/repo/application_deployments/20250210140512.json new file mode 100644 index 000000000..9b5c0f06b --- /dev/null +++ b/backend/priv/resource_snapshots/repo/application_deployments/20250210140512.json @@ -0,0 +1,235 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "resources_state", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "state", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "last_message", + "type": "text" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "tenant_id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "application_deployments_tenant_id_fkey", + "on_delete": "delete", + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "tenants" + }, + "size": null, + "source": "tenant_id", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": "tenant_id", + "global": false, + "strategy": "attribute" + }, + "name": "application_deployments_device_id_fkey", + "on_delete": "delete", + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "devices" + }, + "size": null, + "source": "device_id", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": "tenant_id", + "global": false, + "strategy": "attribute" + }, + "name": "application_deployments_release_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "application_releases" + }, + "size": null, + "source": "release_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [ + { + "all_tenants?": true, + "concurrently": false, + "error_fields": [ + "id", + "tenant_id" + ], + "fields": [ + { + "type": "atom", + "value": "id" + }, + { + "type": "atom", + "value": "tenant_id" + } + ], + "include": null, + "message": null, + "name": null, + "nulls_distinct": true, + "prefix": null, + "table": null, + "unique": true, + "using": null, + "where": null + }, + { + "all_tenants?": true, + "concurrently": false, + "error_fields": [ + "tenant_id" + ], + "fields": [ + { + "type": "atom", + "value": "tenant_id" + } + ], + "include": null, + "message": null, + "name": null, + "nulls_distinct": true, + "prefix": null, + "table": null, + "unique": false, + "using": null, + "where": null + } + ], + "custom_statements": [], + "has_create_action": true, + "hash": "1582C86E3BAB8346B3020B3D3AB5DB069EBEC4EFBCC02C7F4E3F5D9A0B1AB35D", + "identities": [ + { + "all_tenants?": false, + "base_filter": null, + "index_name": "application_deployments_release_instance_index", + "keys": [ + { + "type": "atom", + "value": "device_id" + }, + { + "type": "atom", + "value": "release_id" + } + ], + "name": "release_instance", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": "tenant_id", + "global": false, + "strategy": "attribute" + }, + "repo": "Elixir.Edgehog.Repo", + "schema": null, + "table": "application_deployments" +} \ No newline at end of file diff --git a/backend/test/edgehog_web/controllers/astarte_trigger_controller_test.exs b/backend/test/edgehog_web/controllers/astarte_trigger_controller_test.exs index 924a73178..0eb6ce6f9 100644 --- a/backend/test/edgehog_web/controllers/astarte_trigger_controller_test.exs +++ b/backend/test/edgehog_web/controllers/astarte_trigger_controller_test.exs @@ -411,7 +411,7 @@ defmodule EdgehogWeb.Controllers.AstarteTriggerControllerTest do {:ok, cluster: cluster, realm: realm, device: device} end - test "updates the deployment status", context do + test "deployment events update the state", context do %{ conn: conn, realm: realm, @@ -445,8 +445,8 @@ defmodule EdgehogWeb.Controllers.AstarteTriggerControllerTest do # Deployment must be reloaded from the db deployment = Ash.get!(Edgehog.Containers.Deployment, deployment.id, tenant: tenant) - assert deployment.status == :error - assert deployment.message == "error message" + assert deployment.state == :error + assert deployment.last_message == "error message" deployment_event = %{ device_id: device.device_id, @@ -464,15 +464,17 @@ defmodule EdgehogWeb.Controllers.AstarteTriggerControllerTest do path = Routes.astarte_trigger_path(conn, :process_event, tenant.slug) + set_resource_expectations([deployment]) + conn |> put_req_header("astarte-realm", realm.name) |> post(path, deployment_event) |> response(200) - deployment = Ash.get!(Edgehog.Containers.Deployment, deployment.id, tenant: tenant) + deployment = Edgehog.Containers.fetch_deployment!(deployment.id, tenant: tenant) - assert deployment.status == :starting - assert deployment.message == nil + assert deployment.state == :starting + assert deployment.last_message == "error message" end test "Starting status does not update a Started deployment", context do @@ -483,7 +485,7 @@ defmodule EdgehogWeb.Controllers.AstarteTriggerControllerTest do tenant: tenant } = context - deployment = deployment_fixture(tenant: tenant, device_id: device.id, status: :started) + deployment = deployment_fixture(tenant: tenant, device_id: device.id, state: :started) deployment_event = %{ device_id: device.device_id, @@ -501,6 +503,8 @@ defmodule EdgehogWeb.Controllers.AstarteTriggerControllerTest do path = Routes.astarte_trigger_path(conn, :process_event, tenant.slug) + set_resource_expectations([deployment]) + conn |> put_req_header("astarte-realm", realm.name) |> post(path, deployment_event) @@ -509,8 +513,8 @@ defmodule EdgehogWeb.Controllers.AstarteTriggerControllerTest do # Deployment must be reloaded from the db deployment = Ash.get!(Edgehog.Containers.Deployment, deployment.id, tenant: tenant) - assert deployment.status == :started - assert deployment.message == nil + assert deployment.state == :started + assert deployment.last_message == nil end test "Stopping status does not update a Stopped deployment", context do @@ -521,7 +525,7 @@ defmodule EdgehogWeb.Controllers.AstarteTriggerControllerTest do tenant: tenant } = context - deployment = deployment_fixture(tenant: tenant, device_id: device.id, status: :stopped) + deployment = deployment_fixture(tenant: tenant, device_id: device.id, state: :stopped) deployment_event = %{ device_id: device.device_id, @@ -537,6 +541,8 @@ defmodule EdgehogWeb.Controllers.AstarteTriggerControllerTest do timestamp: DateTime.to_iso8601(DateTime.utc_now()) } + set_resource_expectations([deployment]) + path = Routes.astarte_trigger_path(conn, :process_event, tenant.slug) conn @@ -547,8 +553,8 @@ defmodule EdgehogWeb.Controllers.AstarteTriggerControllerTest do # Deployment must be reloaded from the db deployment = Ash.get!(Edgehog.Containers.Deployment, deployment.id, tenant: tenant) - assert deployment.status == :stopped - assert deployment.message == nil + assert deployment.state == :stopped + assert deployment.last_message == nil end test "AvailableImages triggers update deployment status", context do @@ -567,7 +573,7 @@ defmodule EdgehogWeb.Controllers.AstarteTriggerControllerTest do tenant: tenant, device_id: device.id, release_id: release.id, - status: :sent + state: :stopped ) network_deployment_fixture( @@ -599,7 +605,7 @@ defmodule EdgehogWeb.Controllers.AstarteTriggerControllerTest do |> response(200) deployment = Ash.get!(Deployment, deployment.id, tenant: tenant) - assert deployment.status == :stopped + assert deployment.resources_state == :ready end test "AvailableContainers triggers update deployment status", context do @@ -614,10 +620,12 @@ defmodule EdgehogWeb.Controllers.AstarteTriggerControllerTest do [network] = container.networks deployment = - [tenant: tenant, device_id: device.id, release_id: release.id] - |> deployment_fixture() - |> Ash.Changeset.for_update(:set_status, %{status: :sent}, tenant: tenant) - |> Ash.update!() + deployment_fixture( + tenant: tenant, + device_id: device.id, + release_id: release.id, + state: :sent + ) network_deployment_fixture( network_id: network.id, @@ -648,7 +656,7 @@ defmodule EdgehogWeb.Controllers.AstarteTriggerControllerTest do |> response(200) deployment = Ash.get!(Deployment, deployment.id, tenant: tenant) - assert deployment.status == :stopped + assert deployment.resources_state == :created_containers end test "AvailableDeployments triggers update deployment status", context do @@ -663,10 +671,12 @@ defmodule EdgehogWeb.Controllers.AstarteTriggerControllerTest do [network] = container.networks deployment = - [tenant: tenant, device_id: device.id, release_id: release.id] - |> deployment_fixture() - |> Ash.Changeset.for_update(:set_status, %{status: :sent}, tenant: tenant) - |> Ash.update!() + deployment_fixture( + tenant: tenant, + device_id: device.id, + release_id: release.id, + state: :sent + ) network_deployment_fixture( network_id: network.id, @@ -697,7 +707,7 @@ defmodule EdgehogWeb.Controllers.AstarteTriggerControllerTest do |> response(200) deployment = Ash.get!(Deployment, deployment.id, tenant: tenant) - assert deployment.status == :stopped + assert deployment.state == :stopped end test "unset AvailableDeployments deletes an existing deployment", context do diff --git a/backend/test/edgehog_web/schema/mutation/send_deployment_upgrade_test.exs b/backend/test/edgehog_web/schema/mutation/send_deployment_upgrade_test.exs index f0607dfcf..e007b4b04 100644 --- a/backend/test/edgehog_web/schema/mutation/send_deployment_upgrade_test.exs +++ b/backend/test/edgehog_web/schema/mutation/send_deployment_upgrade_test.exs @@ -1,7 +1,7 @@ # # This file is part of Edgehog. # -# Copyright 2024 SECO Mind Srl +# Copyright 2024 - 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. @@ -83,7 +83,11 @@ defmodule EdgehogWeb.Schema.Mutation.SendDeploymentUpgradeTest do {:ok, %{id: deployment_id}} = AshGraphql.Resource.decode_relay_id(result["id"]) - deployment = Ash.get!(Deployment, deployment_id, tenant: tenant) + deployment = + Deployment + |> Ash.get!(deployment_id, tenant: tenant) + |> Containers.mark_deployment_as_stopped!(tenant: tenant) + set_resource_expectations([deployment_0_0_1, deployment]) Containers.deployment_update_status!(deployment) diff --git a/backend/test/support/fixtures/containers_fixtures.ex b/backend/test/support/fixtures/containers_fixtures.ex index 6bc64d5ac..e852ad9b2 100644 --- a/backend/test/support/fixtures/containers_fixtures.ex +++ b/backend/test/support/fixtures/containers_fixtures.ex @@ -25,8 +25,6 @@ defmodule Edgehog.ContainersFixtures do """ alias Edgehog.Astarte.Device.AvailableContainers.ContainerStatus alias Edgehog.Astarte.Device.AvailableContainersMock - alias Edgehog.Astarte.Device.AvailableDeployments.DeploymentStatus - alias Edgehog.Astarte.Device.AvailableDeploymentsMock alias Edgehog.Astarte.Device.AvailableImages.ImageStatus alias Edgehog.Astarte.Device.AvailableImagesMock alias Edgehog.AstarteFixtures @@ -265,17 +263,10 @@ defmodule Edgehog.ContainersFixtures do |> Enum.map(&%ImageStatus{id: &1.image_id, pulled: false}) |> Enum.uniq() - available_deployments = - Enum.map(deployments, &%DeploymentStatus{id: &1.id, status: :stopped}) - Mox.expect(AvailableImagesMock, :get, new_deployments, fn _, _ -> {:ok, available_images} end) Mox.expect(AvailableContainersMock, :get, new_deployments, fn _, _ -> {:ok, available_containers} end) - - Mox.expect(AvailableDeploymentsMock, :get, new_deployments, fn _, _ -> - {:ok, available_deployments} - end) end end