Skip to content

Commit

Permalink
feat!(containers): deployment caching
Browse files Browse the repository at this point in the history
- Decouples the deployment state (created, sent, stopped, stopping, ...)
  and the underlying resources states (created images, created networks,
  ...) from each other.
- The deployment state gets updated when the device publishes on the
  appropriate interfaces. Its state is cached and persisted in the DB so
  we dont have to query the device every time we want to update the
  deployment status.

Signed-off-by: Luca Zaninotto <luca.zaninotto@secomind.com>
  • Loading branch information
lusergit committed Feb 10, 2025
1 parent 9648338 commit 333070b
Show file tree
Hide file tree
Showing 16 changed files with 516 additions and 122 deletions.
10 changes: 8 additions & 2 deletions backend/lib/edgehog/containers/containers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
72 changes: 62 additions & 10 deletions backend/lib/edgehog/containers/deployment.ex
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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

Expand All @@ -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 """
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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)
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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

Expand All @@ -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
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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 =
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 333070b

Please sign in to comment.