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

Saptune status projection #1821

Merged
merged 3 commits into from
Sep 14, 2023
Merged
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
39 changes: 34 additions & 5 deletions lib/trento/application/projectors/host_projector.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ defmodule Trento.HostProjector do
HostRegistered,
HostRemovedFromCluster,
HostRestored,
ProviderUpdated
ProviderUpdated,
SaptuneStatusUpdated
}

alias Trento.HostReadModel
Expand Down Expand Up @@ -201,18 +202,32 @@ defmodule Trento.HostProjector do
|> Repo.get!(id)
|> HostReadModel.changeset(%{
provider: provider,
provider_data: handle_provider_data(provider_data)
provider_data: map_from_struct(provider_data)
})

Ecto.Multi.update(multi, :host, changeset)
end
)

def handle_provider_data(provider_data) when is_map(provider_data) do
Map.from_struct(provider_data)
project(
%SaptuneStatusUpdated{host_id: id, status: status},
fn multi ->
changeset =
HostReadModel
|> Repo.get!(id)
|> HostReadModel.changeset(%{
saptune_status: map_from_struct(status)
})

Ecto.Multi.update(multi, :host, changeset)
end
)

def map_from_struct(struct) when is_struct(struct) do
Map.from_struct(struct)
end

def handle_provider_data(_), do: nil
def map_from_struct(_), do: nil

@impl true
@spec after_update(any, any, any) :: :ok | {:error, any}
Expand Down Expand Up @@ -363,5 +378,19 @@ defmodule Trento.HostProjector do
TrentoWeb.Endpoint.broadcast("monitoring:hosts", "host_details_updated", message)
end

def after_update(
%SaptuneStatusUpdated{},
_,
%{host: %HostReadModel{} = host}
) do
message =
HostView.render(
"saptune_status_updated.json",
%{host: host}
)

TrentoWeb.Endpoint.broadcast("monitoring:hosts", "saptune_status_updated", message)
end

def after_update(_, _, _), do: :ok
end
1 change: 1 addition & 0 deletions lib/trento/application/read_models/host_read_model.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ defmodule Trento.HostReadModel do
field :selected_checks, {:array, :string}, default: []
field :provider, Ecto.Enum, values: Provider.values()
field :provider_data, :map
field :saptune_status, :map

has_many :tags, Trento.Tag, foreign_key: :resource_id

Expand Down
3 changes: 2 additions & 1 deletion lib/trento_web/openapi/v1/schema/host.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule TrentoWeb.OpenApi.V1.Schema.Host do
require OpenApiSpex
alias OpenApiSpex.Schema

alias TrentoWeb.OpenApi.V1.Schema.{Provider, SlesSubscription, Tags}
alias TrentoWeb.OpenApi.V1.Schema.{Provider, SaptuneStatus, SlesSubscription, Tags}

defmodule IPv4 do
@moduledoc false
Expand Down Expand Up @@ -70,6 +70,7 @@ defmodule TrentoWeb.OpenApi.V1.Schema.Host do
type: :array,
items: SlesSubscription
},
saptune_status: SaptuneStatus,
deregistered_at: %Schema{
title: "DeregisteredAt",
description: "Timestamp of the last deregistration of the host",
Expand Down
136 changes: 136 additions & 0 deletions lib/trento_web/openapi/v1/schema/saptune_status.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
defmodule TrentoWeb.OpenApi.V1.Schema.SaptuneStatus do
@moduledoc false

require OpenApiSpex

alias OpenApiSpex.Schema

defmodule Service do
@moduledoc false

OpenApiSpex.schema(%{
title: "Saptune service",
description: "Saptune service",
type: :object,
properties: %{
name: %Schema{
type: :string,
description: "Saptune service name"
},
enabled: %Schema{
type: :string,
description: "Enabled state as string"
},
active: %Schema{
type: :string,
description: "Active state as string"
}
}
})
end

defmodule Note do
@moduledoc false

OpenApiSpex.schema(%{
title: "Saptune note",
description: "Saptune note",
type: :object,
properties: %{
id: %Schema{
type: :string,
description: "Saptune note ID"
},
additionally_enabled: %Schema{
type: :boolean,
description: "Note is additionally enabled"
}
}
})
end

defmodule Solution do
@moduledoc false

OpenApiSpex.schema(%{
title: "Saptune solution",
description: "Saptune solution",
type: :object,
properties: %{
id: %Schema{
type: :boolean,
description: "Saptune solution ID"
},
notes: %Schema{
type: :array,
description: "Solution note IDs",
items: %Schema{type: :string}
},
partial: %Schema{
type: :boolean,
description: "Solution is partially applied"
}
}
})
end

defmodule Staging do
@moduledoc false

OpenApiSpex.schema(%{
title: "Saptune staging",
description: "Saptune staging data",
type: :object,
properties: %{
enabled: %Schema{
type: :boolean,
description: "Saptune staging is enabled"
},
notes: %Schema{
type: :array,
description: "Staged saptune note IDs",
items: %Schema{type: :string}
},
solutions_ids: %Schema{
type: :array,
description: "Staged saptune solution IDs",
items: %Schema{type: :string}
}
}
})
end

OpenApiSpex.schema(%{
title: "Saptune status",
description: "Saptune status output on the host",
type: :object,
nullable: true,
properties: %{
package_version: %Schema{type: :string, description: "Saptune package version"},
configured_version: %Schema{type: :string, description: "Saptune configure version"},
tuning_state: %Schema{type: :string, description: "Saptune tuning state"},
services: %Schema{
title: "Saptune services",
description: "A list of saptune services",
type: :array,
items: Service
},
enabled_nodes: %Schema{
title: "Enabled notes",
description: "A list of enabled notes",
type: :array,
items: Note
},
applied_notes: %Schema{
title: "Applied notes",
description: "A list of applied notes",
type: :array,
items: Note
},
enabled_solution: Solution,
applied_solution: Solution,
staging: Staging
},
required: [:package_version]
})
end
6 changes: 6 additions & 0 deletions lib/trento_web/views/v1/host_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,10 @@ defmodule TrentoWeb.V1.HostView do
def render("heartbeat_result.json", %{host: %{id: id, hostname: hostname}}) do
%{id: id, hostname: hostname}
end

def render("saptune_status_updated.json", %{
host: %{id: id, saptune_status: status, hostname: hostname}
}) do
%{id: id, status: status, hostname: hostname}
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule Trento.Repo.Migrations.AddSaptuneStatusHostReadModel do
use Ecto.Migration

def change do
alter table(:hosts) do
add :saptune_status, :map
end
end
end
3 changes: 2 additions & 1 deletion test/support/factory.ex
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ defmodule Trento.Factory do
provider: Enum.random(Provider.values()),
provider_data: nil,
deregistered_at: nil,
selected_checks: Enum.map(0..4, fn _ -> Faker.StarWars.planet() end)
selected_checks: Enum.map(0..4, fn _ -> Faker.StarWars.planet() end),
saptune_status: nil
}
end

Expand Down
33 changes: 32 additions & 1 deletion test/trento/application/projectors/host_projector_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ defmodule Trento.HostProjectorTest do
HostDetailsUpdated,
HostRemovedFromCluster,
HostRestored,
ProviderUpdated
ProviderUpdated,
SaptuneStatusUpdated
}

alias Trento.ProjectorTestHelper
alias Trento.Repo
alias Trento.Support.StructHelper

@moduletag :integration

Expand Down Expand Up @@ -511,4 +513,33 @@ defmodule Trento.HostProjectorTest do
},
1000
end

test "should update the saptune_status field when SaptuneStatusUpdated is received",
%{
host_id: host_id,
hostname: hostname
} do
saptune_status = build(:saptune_status)

event = %SaptuneStatusUpdated{
host_id: host_id,
status: saptune_status
}

expected_status = StructHelper.to_map(saptune_status)
expected_broadcast_status = Map.from_struct(saptune_status)

ProjectorTestHelper.project(HostProjector, event, "host_projector")
host_projection = Repo.get!(HostReadModel, host_id)

assert expected_status == host_projection.saptune_status

assert_broadcast "saptune_status_updated",
%{
id: ^host_id,
hostname: ^hostname,
status: ^expected_broadcast_status
},
1000
end
end