diff --git a/lib/trento/application/integration/discovery/protocol/enrich_register_application_instance.ex b/lib/trento/application/integration/discovery/protocol/enrich_register_application_instance.ex index 173820bed4..c3c4cae54e 100644 --- a/lib/trento/application/integration/discovery/protocol/enrich_register_application_instance.ex +++ b/lib/trento/application/integration/discovery/protocol/enrich_register_application_instance.ex @@ -14,7 +14,7 @@ defimpl Trento.Support.Middleware.Enrichable, from d in DatabaseInstanceReadModel, join: h in HostReadModel, on: d.host_id == h.id, - where: ^db_host in h.ip_addresses and ^tenant == d.tenant + where: ^db_host in h.ip_addresses and ^tenant == d.tenant and is_nil(h.deregistered_at) case Repo.one(query) do %DatabaseInstanceReadModel{sap_system_id: sap_system_id} -> diff --git a/lib/trento/application/projectors/host_projector.ex b/lib/trento/application/projectors/host_projector.ex index ac5c64f7f0..67a38142a8 100644 --- a/lib/trento/application/projectors/host_projector.ex +++ b/lib/trento/application/projectors/host_projector.ex @@ -16,6 +16,7 @@ defmodule Trento.HostProjector do HeartbeatFailed, HeartbeatSucceded, HostAddedToCluster, + HostDeregistered, HostDetailsUpdated, HostRegistered, ProviderUpdated @@ -47,6 +48,21 @@ defmodule Trento.HostProjector do end ) + project( + %HostDeregistered{ + host_id: id, + deregistered_at: deregistered_at + }, + fn multi -> + changeset = + HostReadModel.changeset(%HostReadModel{id: id}, %{ + deregistered_at: deregistered_at + }) + + Ecto.Multi.update(multi, :host, changeset) + end + ) + project( %HostAddedToCluster{ host_id: id, @@ -144,6 +160,23 @@ defmodule Trento.HostProjector do ) end + def after_update( + %HostDeregistered{host_id: id}, + _, + _ + ) do + %HostReadModel{hostname: hostname} = Repo.get!(HostReadModel, id) + + TrentoWeb.Endpoint.broadcast( + "monitoring:hosts", + "host_deregistered", + %{ + id: id, + hostname: hostname + } + ) + end + def after_update( %HostAddedToCluster{host_id: id, cluster_id: cluster_id}, _, diff --git a/lib/trento/application/read_models/application_instance_read_model.ex b/lib/trento/application/read_models/application_instance_read_model.ex index dde2c376b8..562cd4c0e1 100644 --- a/lib/trento/application/read_models/application_instance_read_model.ex +++ b/lib/trento/application/read_models/application_instance_read_model.ex @@ -27,7 +27,10 @@ defmodule Trento.ApplicationInstanceReadModel do field :host_id, Ecto.UUID, primary_key: true field :health, Ecto.Enum, values: Health.values() - has_one :host, HostReadModel, references: :host_id, foreign_key: :id + has_one :host, HostReadModel, + references: :host_id, + foreign_key: :id, + where: [deregistered_at: nil] end @spec changeset(t() | Ecto.Changeset.t(), map) :: Ecto.Changeset.t() diff --git a/lib/trento/application/read_models/database_instance_read_model.ex b/lib/trento/application/read_models/database_instance_read_model.ex index c5f38c5aa0..bdf767baa5 100644 --- a/lib/trento/application/read_models/database_instance_read_model.ex +++ b/lib/trento/application/read_models/database_instance_read_model.ex @@ -30,7 +30,10 @@ defmodule Trento.DatabaseInstanceReadModel do field :system_replication_status, :string, default: "" field :health, Ecto.Enum, values: Health.values() - has_one :host, HostReadModel, references: :host_id, foreign_key: :id + has_one :host, HostReadModel, + references: :host_id, + foreign_key: :id, + where: [deregistered_at: nil] end @spec changeset(t() | Ecto.Changeset.t(), map) :: Ecto.Changeset.t() diff --git a/lib/trento/application/read_models/host_read_model.ex b/lib/trento/application/read_models/host_read_model.ex index 0333807e78..cb10589927 100644 --- a/lib/trento/application/read_models/host_read_model.ex +++ b/lib/trento/application/read_models/host_read_model.ex @@ -31,6 +31,8 @@ defmodule Trento.HostReadModel do references: :id, foreign_key: :host_id, preload_order: [desc: :identifier] + + field :deregistered_at, :utc_datetime_usec end @spec changeset(t() | Ecto.Changeset.t(), map) :: Ecto.Changeset.t() diff --git a/lib/trento/application/usecases/hosts/hosts.ex b/lib/trento/application/usecases/hosts/hosts.ex index 1e709dac14..d91e6e566a 100644 --- a/lib/trento/application/usecases/hosts/hosts.ex +++ b/lib/trento/application/usecases/hosts/hosts.ex @@ -15,7 +15,7 @@ defmodule Trento.Hosts do @spec get_all_hosts :: [HostReadModel.t()] def get_all_hosts do HostReadModel - |> where([h], not is_nil(h.hostname)) + |> where([h], not is_nil(h.hostname) and is_nil(h.deregistered_at)) |> order_by(asc: :hostname) |> Repo.all() |> Repo.preload([:sles_subscriptions, :tags]) diff --git a/priv/repo/migrations/20230309094053_add_deregistered_at_to_host_read_model.exs b/priv/repo/migrations/20230309094053_add_deregistered_at_to_host_read_model.exs new file mode 100644 index 0000000000..a46850a063 --- /dev/null +++ b/priv/repo/migrations/20230309094053_add_deregistered_at_to_host_read_model.exs @@ -0,0 +1,9 @@ +defmodule Trento.Repo.Migrations.AddDeregisteredAtToHostReadModel do + use Ecto.Migration + + def change do + alter table(:hosts) do + add :deregistered_at, :utc_datetime_usec + end + end +end diff --git a/test/trento/application/integration/discovery/protocol/enrich_register_application_instance_test.exs b/test/trento/application/integration/discovery/protocol/enrich_register_application_instance_test.exs index 1baeeacedd..e7a2b9c721 100644 --- a/test/trento/application/integration/discovery/protocol/enrich_register_application_instance_test.exs +++ b/test/trento/application/integration/discovery/protocol/enrich_register_application_instance_test.exs @@ -31,6 +31,34 @@ defmodule Trento.EnrichRegisterApplicationInstanceTest do Enrichable.enrich(command, %{}) end + test "should return a database not found error if the database instance host has been deregistered" do + deregistered_host = insert(:host, deregistered_at: DateTime.utc_now()) + + %{ + tenant: tenant, + host: %{ip_addresses: [ip]} + } = + insert(:database_instance_without_host, + host_id: deregistered_host.id, + host: deregistered_host + ) + + command = + build( + :register_application_instance_command, + sap_system_id: nil, + sid: Faker.StarWars.planet(), + db_host: ip, + tenant: tenant, + instance_number: "00", + features: Faker.Pokemon.name(), + host_id: Faker.UUID.v4(), + health: :passing + ) + + assert {:error, :database_not_found} = Enrichable.enrich(command, %{}) + end + test "should return an error if the database was not found" do %{ tenant: tenant diff --git a/test/trento/application/projectors/host_projector_test.exs b/test/trento/application/projectors/host_projector_test.exs index c3e363cb94..14ff0e0093 100644 --- a/test/trento/application/projectors/host_projector_test.exs +++ b/test/trento/application/projectors/host_projector_test.exs @@ -24,6 +24,7 @@ defmodule Trento.HostProjectorTest do HeartbeatFailed, HeartbeatSucceded, HostAddedToCluster, + HostDeregistered, HostDetailsUpdated, ProviderUpdated } @@ -45,9 +46,9 @@ defmodule Trento.HostProjectorTest do end setup do - %HostReadModel{id: host_id} = insert(:host) + %HostReadModel{id: host_id, hostname: hostname} = insert(:host) - %{host_id: host_id} + %{host_id: host_id, hostname: hostname} end test "should project a new host when HostRegistered event is received" do @@ -344,4 +345,29 @@ defmodule Trento.HostProjectorTest do }, 1000 end + + test "should update the deregistered_at field when HostDeregistered is received", + %{ + host_id: host_id, + hostname: hostname + } do + timestamp = DateTime.utc_now() + + event = %HostDeregistered{ + host_id: host_id, + deregistered_at: timestamp + } + + ProjectorTestHelper.project(HostProjector, event, "host_projector") + host_projection = Repo.get!(HostReadModel, host_id) + + assert timestamp == host_projection.deregistered_at + + assert_broadcast "host_deregistered", + %{ + id: ^host_id, + hostname: ^hostname + }, + 1000 + end end diff --git a/test/trento/application/usecases/hosts_test.exs b/test/trento/application/usecases/hosts_test.exs index b021429ceb..9bf9732a50 100644 --- a/test/trento/application/usecases/hosts_test.exs +++ b/test/trento/application/usecases/hosts_test.exs @@ -25,4 +25,17 @@ defmodule Trento.HostsTest do assert 6 = Hosts.get_all_sles_subscriptions() end end + + describe "get_all_hosts/0" do + test "should list all hosts except the deregistered ones" do + registered_hosts = Enum.map(0..9, fn i -> insert(:host, hostname: "hostname_#{i}") end) + deregistered_host = insert(:host, deregistered_at: DateTime.utc_now()) + + hosts = Hosts.get_all_hosts() + hosts_ids = Enum.map(hosts, & &1.id) + + assert Enum.map(registered_hosts, & &1.id) == hosts_ids + refute deregistered_host.id in hosts_ids + end + end end