Skip to content

Commit

Permalink
Track software update discoveries (#2540)
Browse files Browse the repository at this point in the history
* Add a DiscoveryResult schema in order to keep track of software updates discovery results

* Store software updates discovery results on successful discovery

* Add tracking of both successful and failing software updates discoveries

* Integrate clearup of tracked software updates discoveries

* Properly track discoveries failing due to authentication issues
  • Loading branch information
nelsonkopliku committed Apr 29, 2024
1 parent 1c00920 commit 4ddc8af
Show file tree
Hide file tree
Showing 7 changed files with 572 additions and 144 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ defmodule Trento.Infrastructure.Commanded.EventHandlers.SoftwareUpdatesDiscovery
application: Trento.Commanded,
name: "software_updates_discovery_event_handler"

alias Trento.Hosts.Events.SoftwareUpdatesDiscoveryRequested
alias Trento.Hosts.Events.{
SoftwareUpdatesDiscoveryCleared,
SoftwareUpdatesDiscoveryRequested
}

alias Trento.SoftwareUpdates.Discovery

Expand All @@ -23,4 +26,12 @@ defmodule Trento.Infrastructure.Commanded.EventHandlers.SoftwareUpdatesDiscovery

:ok
end

def handle(
%SoftwareUpdatesDiscoveryCleared{
host_id: host_id
},
_
),
do: Discovery.clear_tracked_discovery_result(host_id)
end
135 changes: 101 additions & 34 deletions lib/trento/software_updates/discovery.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ defmodule Trento.SoftwareUpdates.Discovery do
Software updates integration service
"""

import Ecto.Query

alias Trento.Repo

alias Ecto.Multi

alias Trento.Hosts

alias Trento.Hosts.Commands.{
Expand All @@ -11,6 +17,7 @@ defmodule Trento.SoftwareUpdates.Discovery do
}

alias Trento.Hosts.Projections.HostReadModel
alias Trento.SoftwareUpdates.Discovery.DiscoveryResult

require Trento.SoftwareUpdates.Enums.SoftwareUpdatesHealth, as: SoftwareUpdatesHealth
require Trento.SoftwareUpdates.Enums.AdvisoryType, as: AdvisoryType
Expand Down Expand Up @@ -49,12 +56,12 @@ defmodule Trento.SoftwareUpdates.Discovery do
{:error, error} ->
{:error, host_id, error}

{:ok, _, _, _} = success ->
{:ok, _, _, _, _} = success ->
success
end
end)
|> Enum.split_with(fn
{:ok, _, _, _} -> true
{:ok, _, _, _, _} -> true
_ -> false
end)}
end
Expand All @@ -74,8 +81,14 @@ defmodule Trento.SoftwareUpdates.Discovery do
:ok
end

@spec clear_tracked_discovery_result(String.t()) :: :ok
def clear_tracked_discovery_result(host_id) do
Repo.delete_all(from d in DiscoveryResult, where: d.host_id == ^host_id)
:ok
end

@spec discover_host_software_updates(String.t(), String.t()) ::
{:ok, String.t(), String.t(), any()} | {:error, any()}
{:ok, String.t(), String.t(), any(), any()} | {:error, any()}
def discover_host_software_updates(host_id, nil) do
Logger.info("Host #{host_id} does not have an fqdn. Skipping software updates discovery")
{:error, :host_without_fqdn}
Expand All @@ -84,57 +97,111 @@ defmodule Trento.SoftwareUpdates.Discovery do
def discover_host_software_updates(host_id, fully_qualified_domain_name) do
with {:ok, system_id} <- get_system_id(fully_qualified_domain_name),
{:ok, relevant_patches} <- get_relevant_patches(system_id),
:ok <-
host_id
|> build_discovery_completion_command(relevant_patches)
|> commanded().dispatch() do
{:ok, host_id, system_id, relevant_patches}
{:ok, upgradable_packages} <- get_upgradable_packages(system_id),
{:ok, _} <-
finalize_successful_discovery(
host_id,
system_id,
relevant_patches,
upgradable_packages
) do
{:ok, host_id, system_id, relevant_patches, upgradable_packages}
else
{:error, :settings_not_configured} ->
{:error, :settings_not_configured}

{:error, discovery_error} = error ->
{:error, _} = error ->
Logger.error(
"An error occurred during software updates discovery for host #{host_id}: #{inspect(error)}"
)

commanded().dispatch(
CompleteSoftwareUpdatesDiscovery.new!(%{
host_id: host_id,
health: SoftwareUpdatesHealth.unknown()
})
)
finalize_failed_discovery(host_id, error)

{:error, discovery_error}
error
end
end

defp discover_host_software_updates(_, _, {:error, :settings_not_configured} = error),
do: error

defp discover_host_software_updates(host_id, _, {:error, error}) do
commanded().dispatch(
CompleteSoftwareUpdatesDiscovery.new!(%{
host_id: host_id,
health: SoftwareUpdatesHealth.unknown()
})
)

{:error, error}
defp discover_host_software_updates(host_id, _, {:error, _} = error) do
finalize_failed_discovery(host_id, error)
error
end

defp discover_host_software_updates(host_id, fully_qualified_domain_name, _),
do: discover_host_software_updates(host_id, fully_qualified_domain_name)

defp build_discovery_completion_command(host_id, relevant_patches),
do:
CompleteSoftwareUpdatesDiscovery.new!(%{
host_id: host_id,
health:
relevant_patches
|> track_relevant_patches
|> compute_software_updates_discovery_health
})
defp finalize_failed_discovery(host_id, {:error, reason}) do
%DiscoveryResult{}
|> DiscoveryResult.changeset(%{
host_id: host_id,
system_id: nil,
relevant_patches: nil,
upgradable_packages: nil,
failure_reason: Atom.to_string(reason)
})
|> finalize_discovery(host_id, SoftwareUpdatesHealth.unknown())
end

defp finalize_successful_discovery(host_id, system_id, relevant_patches, upgradable_packages) do
%DiscoveryResult{}
|> DiscoveryResult.changeset(%{
host_id: host_id,
system_id: "#{system_id}",
relevant_patches: relevant_patches,
upgradable_packages: upgradable_packages
})
|> finalize_discovery(
host_id,
relevant_patches
|> track_relevant_patches
|> compute_software_updates_discovery_health
)
end

defp finalize_discovery(discovery_result, host_id, discovered_health) do
transaction_result =
Multi.new()
|> Multi.insert(:insert, discovery_result,
conflict_target: :host_id,
on_conflict: :replace_all
)
|> Multi.run(:command_dispatching, fn _, _ ->
dispatch_completion_command(host_id, discovered_health)
end)
|> Repo.transaction()

case transaction_result do
{:ok, _} = success ->
success

{:error, :command_dispatching, dispatching_error, _} ->
{:error, dispatching_error}

{:error, _} = error ->
Logger.error(
"Error while finalizing software updates discovery for host #{host_id}, error: #{inspect(error)}"
)

error
end
end

defp dispatch_completion_command(host_id, discovered_health) do
case %{
host_id: host_id,
health: discovered_health
}
|> CompleteSoftwareUpdatesDiscovery.new!()
|> commanded().dispatch() do
:ok ->
{:ok, :dispatched}

{:error, _} = error ->
error
end
end

defp track_relevant_patches(relevant_patches),
do:
Expand Down
22 changes: 22 additions & 0 deletions lib/trento/software_updates/discovery/discovery_result.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
defmodule Trento.SoftwareUpdates.Discovery.DiscoveryResult do
@moduledoc """
This is the schema used to store the results of the software updates discovery process.
"""

use Ecto.Schema
import Ecto.Changeset

@primary_key {:host_id, :binary_id, autogenerate: false}
schema "software_updates_discovery_result" do
field :system_id, :string
field :relevant_patches, Trento.Support.Ecto.Payload
field :upgradable_packages, Trento.Support.Ecto.Payload
field :failure_reason, :string

timestamps(type: :utc_datetime_usec)
end

def changeset(discovery_result, attrs) do
cast(discovery_result, attrs, __MODULE__.__schema__(:fields))
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
defmodule Trento.Repo.Migrations.CreateSoftwareUpdatesDiscoveryResult do
use Ecto.Migration

def change do
create table(:software_updates_discovery_result, primary_key: false) do
add :host_id, :uuid, primary_key: true
add :system_id, :string
add :relevant_patches, :map
add :upgradable_packages, :map
add :failure_reason, :string

timestamps(type: :utc_datetime_usec)
end
end
end
18 changes: 18 additions & 0 deletions test/support/factory.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ defmodule Trento.Factory do
HostTombstoned,
SaptuneStatusUpdated,
SlesSubscriptionsUpdated,
SoftwareUpdatesDiscoveryCleared,
SoftwareUpdatesDiscoveryRequested,
SoftwareUpdatesHealthChanged
}
Expand Down Expand Up @@ -114,6 +115,7 @@ defmodule Trento.Factory do
DiscoveryEvent
}

alias Trento.SoftwareUpdates.Discovery.DiscoveryResult
alias Trento.SoftwareUpdates.Settings

alias Trento.Settings.{
Expand Down Expand Up @@ -805,6 +807,12 @@ defmodule Trento.Factory do
}
end

def software_updates_discovery_cleared_event_factory do
SoftwareUpdatesDiscoveryCleared.new!(%{
host_id: Faker.UUID.v4()
})
end

def host_health_changed_event_factory do
HostHealthChanged.new!(%{
host_id: Faker.UUID.v4(),
Expand Down Expand Up @@ -884,4 +892,14 @@ defmodule Trento.Factory do
to_package_id: "#{RandomElixir.random_between(0, 1000)}"
}
end

def software_updates_discovery_result_factory do
%DiscoveryResult{
host_id: Faker.UUID.v4(),
system_id: Faker.UUID.v4(),
relevant_patches: build_list(2, :relevant_patch),
upgradable_packages: build_list(2, :upgradable_package),
failure_reason: Faker.Lorem.word()
}
end
end
Loading

0 comments on commit 4ddc8af

Please sign in to comment.