Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(containers): refactor deployment #754

Draft
wants to merge 8 commits into
base: feature/application-management
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#
# 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.Container.Calculations.ContainerReady do
@moduledoc false

use Ash.Resource.Calculation

alias Ash.Resource.Calculation
alias Edgehog.Containers

@impl Calculation
def load(_query, _opts, _context) do
[container: [:image, :networks, :volumes], device: []]
end

@impl Calculation
def calculate(records, _opts, context) do
%{tenant: tenant} = context
Enum.map(records, &compute_ready(&1, tenant))
end

defp compute_ready(deployment, tenant) do
container = deployment.container
device = deployment.device

image =
Containers.fetch_image_deployment!(container.image.id, device.id,
tenant: tenant,
load: [:ready?]
)

networks =
Enum.map(
container.networks,
&Containers.fetch_network_deployment!(&1.id, device.id, tenant: tenant, load: [:ready?])
)

volumes =
Enum.map(
container.volumes,
&Containers.fetch_volume_deployment!(&1.id, device.id, tenant: tenant, load: [:ready?])
)

resources = [image | networks ++ volumes]

Enum.reduce_while(resources, true, fn resource, _ ->
if resource.ready? do
{:cont, true}
else
{:halt, false}
end
end)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#
# This file is part of Edgehog.
#
# Copyright 2024 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.Container.Changes.DeployContainerOnDevice do
@moduledoc false
use Ash.Resource.Change

alias Ash.Error.Query.NotFound
alias Edgehog.Containers
alias Edgehog.Devices

@impl Ash.Resource.Change
def change(changeset, _opts, context) do
%{tenant: tenant} = context

Ash.Changeset.after_action(changeset, fn _changeset, deployment ->
with {:ok, deployment} <-
Ash.load(deployment, device: [], container: [:image, :networks, :volumes]),
{:ok, _image_deployment} <- deploy_image(deployment, tenant),
:ok <- deploy_networks(deployment, tenant),
:ok <- deploy_volumes(deployment, tenant),
{:ok, _device} <-
Devices.send_create_container_request(deployment.device, deployment.container, tenant: tenant) do
Containers.container_deployment_sent(deployment, tenant: tenant)
end
end)
end

def deploy_image(deployment, tenant) do
image = deployment.container.image
device = deployment.device
Containers.deploy_image(image.id, device.id, tenant: tenant)
end

def deploy_networks(deployment, tenant) do
device = deployment.device

networks =
deployment.container.networks
|> Enum.uniq_by(& &1.id)
|> Enum.reject(&deployed?(&1, device, tenant))

Enum.reduce_while(networks, :ok, fn network, _acc ->
case Containers.deploy_network(network.id, device.id, tenant: tenant) do
{:ok, _network_deployment} -> {:cont, :ok}
error -> {:halt, error}
end
end)
end

def deploy_volumes(deployment, tenant) do
device = deployment.device

volumes =
deployment.container.volumes
|> Enum.uniq_by(& &1.id)
|> Enum.reject(&deployed?(&1, device, tenant))

Enum.reduce_while(volumes, :ok, fn volume, _acc ->
case Containers.deploy_volume(volume.id, device.id, tenant: tenant) do
{:ok, _volume_deployment} -> {:cont, :ok}
error -> {:halt, error}
end
end)
end

defp deployed?(%Containers.Network{} = network, device, tenant) do
case Containers.network_is_deployed?(network.id, device.id, tenant: tenant) do
{:ok, _network} -> true
{:error, %NotFound{}} -> false
end
end

defp deployed?(%Containers.Volume{} = volume, device, tenant) do
case Containers.volume_is_deployed?(volume.id, device.id, tenant: tenant) do
{:ok, _volume} -> true
{:error, %NotFound{}} -> false
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ defmodule Edgehog.Containers.Container do
through Edgehog.Containers.ContainerNetwork
public? true
end

many_to_many :devices, Edgehog.Devices.Device do
through Edgehog.Containers.Container.Deployment
join_relationship :container_deployments
end
end

calculations do
Expand Down
121 changes: 121 additions & 0 deletions backend/lib/edgehog/containers/container/deployment.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#
# This file is part of Edgehog.
#
# Copyright 2024 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.Container.Deployment do
@moduledoc false
use Edgehog.MultitenantResource,
domain: Edgehog.Containers,
extensions: [AshGraphql.Resource]

alias Edgehog.Containers.Container.Calculations
alias Edgehog.Containers.Container.Changes

graphql do
type :container_deployment
end

actions do
defaults [:read, :destroy]

create :deploy do
description """
Deploys an image on a device, the status according to device triggers.
"""

accept [:container_id]

argument :device_id, :id do
allow_nil? false
end

change set_attribute(:state, :created)
change manage_relationship(:device_id, :device, type: :append)
change Changes.DeployContainerOnDevice
end

update :sent do
change set_attribute(:state, :sent)
end

update :received do
change set_attribute(:state, :received)
end

update :created do
change set_attribute(:state, :device_created)
end

update :stopped do
change set_attribute(:state, :stopped)
end

update :running do
change set_attribute(:state, :running)
end

update :errored do
argument :message, :string do
allow_nil? false
end

change set_attribute(:last_message, arg(:message))
change set_attribute(:state, :error)
end
end

attributes do
uuid_primary_key :id

attribute :last_message, :string

attribute :state, :atom,
constraints: [
one_of: [:created, :sent, :received, :device_created, :stopped, :running, :error]
]

timestamps()
end

relationships do
belongs_to :container, Edgehog.Containers.Container do
attribute_type :uuid
public? true
end

belongs_to :device, Edgehog.Devices.Device
end

calculations do
calculate :ready?, :boolean, Calculations.ContainerReady
end

identities do
identity :container_instance, [:container_id, :device_id]
end

postgres do
table "container_deployments"

references do
reference :container, on_delete: :delete
reference :device, on_delete: :delete
end
end
end
26 changes: 26 additions & 0 deletions backend/lib/edgehog/containers/container/status.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#
# This file is part of Edgehog.
#
# Copyright 2024 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.Container.Status do
@moduledoc false
use Ash.Type.Enum, values: [:received, :created, :running, :stopped]

def graphql_type(_), do: :application_container_status
end
Loading