Skip to content

Commit

Permalink
Add a scheduled software updates discovery job (#2376)
Browse files Browse the repository at this point in the history
* Add a discovery function that gets relevant patches for all hosts and potentially changes health

* Add discovery to software updates context and register a quantum process

* fix suma genserver setup handle continue

* Add missing url composition when getting relevant patches

* Different approach to incrementing relevant patches counters

* simplify tuples returned by softwre updates discovery
  • Loading branch information
nelsonkopliku authored Mar 4, 2024
1 parent 984b36e commit 75776e9
Show file tree
Hide file tree
Showing 11 changed files with 458 additions and 42 deletions.
7 changes: 7 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ config :trento, Trento.Scheduler,
task: {Trento.Hosts, :request_hosts_checks_execution, []},
run_strategy: {Quantum.RunStrategy.Random, :cluster},
overlap: false
],
discover_software_updates: [
# Runs every 12 hours. At 00:00 and 12:00
schedule: "0 */12 * * *",
task: {Trento.SoftwareUpdates, :run_discovery, []},
run_strategy: {Quantum.RunStrategy.Random, :cluster},
overlap: false
]
],
debug_logging: false
Expand Down
5 changes: 3 additions & 2 deletions lib/trento/infrastructure/software_updates/suma.ex
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,9 @@ defmodule Trento.Infrastructure.SoftwareUpdates.Suma do
Process.send(self(), {previous_message, reply_to: from}, [:nosuspend, :noconnect])
{:noreply, new_state}

{:error, reason} ->
{:stop, reason}
{:error, _} = error ->
GenServer.reply(from, error)
{:noreply, state}
end
end

Expand Down
11 changes: 9 additions & 2 deletions lib/trento/infrastructure/software_updates/suma_api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule Trento.Infrastructure.SoftwareUpdates.SumaApi do
SUMA API client supporting software updates discovery.
"""

require Trento.SoftwareUpdates.Enums.AdvisoryType, as: AdvisoryType
require Logger

@login_retries 5
Expand Down Expand Up @@ -34,11 +35,17 @@ defmodule Trento.Infrastructure.SoftwareUpdates.SumaApi do
end

def get_relevant_patches(url, auth, system_id) do
response = http_executor().get_relevant_patches(url, auth, system_id)
response =
url
|> get_suma_api_url()
|> http_executor().get_relevant_patches(auth, system_id)

with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <- response,
{:ok, %{success: true, result: result}} <- Jason.decode(body, keys: :atoms) do
{:ok, result}
{:ok,
Enum.map(result, fn %{advisory_type: advisory_type} = advisory ->
%{advisory | advisory_type: AdvisoryType.from_string(advisory_type)}
end)}
else
error ->
Logger.error("Failed to get errata for system ID #{system_id}. Error: #{inspect(error)}")
Expand Down
15 changes: 15 additions & 0 deletions lib/trento/software_updates.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ defmodule Trento.SoftwareUpdates do
alias Trento.Support.DateService

alias Trento.Repo
alias Trento.SoftwareUpdates.Discovery
alias Trento.SoftwareUpdates.Settings

@type software_update_settings_save_submission :: %{
Expand Down Expand Up @@ -70,6 +71,20 @@ defmodule Trento.SoftwareUpdates do
:ok
end

@spec run_discovery :: :ok | {:error, :settings_not_configured}
def run_discovery do
case get_settings() do
{:ok, _} ->
Discovery.discover_software_updates()
:ok

error ->
Logger.error("Software updates settings not configured. Skipping discovery.")

error
end
end

defp has_settings?(%Settings{url: url, username: username, password: password}),
do: url != nil and username != nil and password != nil

Expand Down
90 changes: 90 additions & 0 deletions lib/trento/software_updates/discovery.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ defmodule Trento.SoftwareUpdates.Discovery do
Software updates integration service
"""

alias Trento.Hosts
alias Trento.Hosts.Commands.CompleteSoftwareUpdatesDiscovery
alias Trento.Hosts.Projections.HostReadModel

require Trento.SoftwareUpdates.Enums.AdvisoryType, as: AdvisoryType

require Logger

@behaviour Trento.SoftwareUpdates.Discovery.Gen

@impl true
Expand All @@ -13,5 +21,87 @@ defmodule Trento.SoftwareUpdates.Discovery do
def get_relevant_patches(system_id),
do: adapter().get_relevant_patches(system_id)

@spec discover_software_updates :: {:ok, {list(), list()}}
def discover_software_updates,
do:
{:ok,
Hosts.get_all_hosts()
|> Enum.map(&discover_host_software_updates/1)
|> Enum.split_with(fn
{:ok, _, _, _} -> true
_ -> false
end)}

defp discover_host_software_updates(%HostReadModel{
id: host_id,
fully_qualified_domain_name: nil
}) do
Logger.info("Host #{host_id} does not have an fqdn. Skipping software updates discovery")
{:error, host_id, :host_without_fqdn}
end

defp discover_host_software_updates(%HostReadModel{
id: host_id,
fully_qualified_domain_name: 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}
else
error ->
Logger.error(
"An error occurred during software updates discovery for host #{host_id}: #{inspect(error)}"
)

{:error, host_id, error}
end
end

defp build_discovery_completion_command(host_id, relevant_patches),
do:
CompleteSoftwareUpdatesDiscovery.new!(%{
host_id: host_id,
relevant_patches:
Enum.reduce(
relevant_patches,
%{
security_advisories: 0,
bug_fixes: 0,
software_enhancements: 0
},
&track_relevant_patches/2
)
})

defp track_relevant_patches(
%{advisory_type: AdvisoryType.security_advisory()},
%{
security_advisories: security_advisories
} = patches
),
do: %{patches | security_advisories: security_advisories + 1}

defp track_relevant_patches(
%{advisory_type: AdvisoryType.bugfix()},
%{
bug_fixes: bug_fixes
} = patches
),
do: %{patches | bug_fixes: bug_fixes + 1}

defp track_relevant_patches(
%{advisory_type: AdvisoryType.enhancement()},
%{
software_enhancements: software_enhancements
} = patches
),
do: %{patches | software_enhancements: software_enhancements + 1}

defp adapter, do: Application.fetch_env!(:trento, __MODULE__)[:adapter]

defp commanded, do: Application.fetch_env!(:trento, Trento.Commanded)[:adapter]
end
15 changes: 15 additions & 0 deletions lib/trento/software_updates/enums/advisory_type.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
defmodule Trento.SoftwareUpdates.Enums.AdvisoryType do
@moduledoc """
Enum representing possible advisory types.
"""

@security_advisory "Security Advisory"
@bugfix "Bug Fix Advisory"
@enhancement "Product Enhancement Advisory"

use Trento.Support.Enum, values: [:security_advisory, :bugfix, :enhancement]

def from_string(@security_advisory), do: security_advisory()
def from_string(@bugfix), do: bugfix()
def from_string(@enhancement), do: enhancement()
end
10 changes: 10 additions & 0 deletions test/support/factory.ex
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ defmodule Trento.Factory do
%HostReadModel{
id: Faker.UUID.v4(),
hostname: Faker.StarWars.character(),
fully_qualified_domain_name: Faker.Internet.domain_name(),
ip_addresses: [Faker.Internet.ip_v4_address()],
agent_version: Faker.StarWars.planet(),
cluster_id: Faker.UUID.v4(),
Expand Down Expand Up @@ -806,4 +807,13 @@ defmodule Trento.Factory do
eula_accepted: true
}
end

def insert_software_updates_settings(attrs \\ []) do
insert(
:software_updates_settings,
attrs,
conflict_target: :id,
on_conflict: :replace_all
)
end
end
6 changes: 6 additions & 0 deletions test/test_helper.exs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ Application.put_env(:trento, Trento.Infrastructure.SoftwareUpdates.SumaApi,
executor: Trento.Infrastructure.SoftwareUpdates.Suma.HttpExecutor.Mock
)

Mox.defmock(Trento.SoftwareUpdates.Discovery.Mock, for: Trento.SoftwareUpdates.Discovery.Gen)

Application.put_env(:trento, Trento.SoftwareUpdates.Discovery,
adapter: Trento.SoftwareUpdates.Discovery.Mock
)

Mox.defmock(Trento.Infrastructure.Messaging.Adapter.Mock,
for: Trento.Infrastructure.Messaging.Adapter.Gen
)
Expand Down
22 changes: 21 additions & 1 deletion test/trento/infrastructure/software_updates/suma_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,27 @@ defmodule Trento.Infrastructure.SoftwareUpdates.SumaTest do
{:ok, %HTTPoison.Response{status_code: 200, body: Jason.encode!(suma_response_body)}}
end)

assert {:ok, ^patches} =
assert {:ok,
[
%{
date: "2024-02-27",
advisory_name: "SUSE-15-SP4-2024-630",
advisory_type: :bugfix,
advisory_status: "stable",
id: 4182,
advisory_synopsis: "Recommended update for cloud-netconfig",
update_date: "2024-02-27"
},
%{
date: "2024-02-26",
advisory_name: "SUSE-15-SP4-2024-619",
advisory_type: :security_advisory,
advisory_status: "stable",
id: 4174,
advisory_synopsis: "important: Security update for java-1_8_0-ibm",
update_date: "2024-02-26"
}
]} =
Suma.get_relevant_patches(system_id, @test_integration_name)
end
end
Expand Down
Loading

0 comments on commit 75776e9

Please sign in to comment.