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

Sap system restoration #1545

Merged
merged 9 commits into from
Jun 29, 2023
37 changes: 36 additions & 1 deletion lib/trento/application/projectors/database_projector.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ defmodule Trento.DatabaseProjector do
DatabaseInstanceHealthChanged,
DatabaseInstanceRegistered,
DatabaseInstanceSystemReplicationChanged,
DatabaseRegistered
DatabaseRegistered,
DatabaseRestored
}

alias Trento.Repo
Expand Down Expand Up @@ -160,6 +161,25 @@ defmodule Trento.DatabaseProjector do
end
)

project(
%DatabaseRestored{
sap_system_id: sap_system_id,
sid: sid,
health: health
},
fn multi ->
db = Repo.get!(DatabaseReadModel, sap_system_id)

changeset =
DatabaseReadModel.changeset(
db,
%{deregistered_at: nil, sid: sid, health: health}
)

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

project(
%DatabaseInstanceDeregistered{
instance_number: instance_number,
Expand Down Expand Up @@ -279,6 +299,21 @@ defmodule Trento.DatabaseProjector do
)
end

@impl true
def after_update(
%DatabaseRestored{sap_system_id: sap_system_id},
_,
_
) do
database = Repo.get!(DatabaseReadModel, sap_system_id)
CDimonaco marked this conversation as resolved.
Show resolved Hide resolved

TrentoWeb.Endpoint.broadcast(
@databases_topic,
"database_registered",
SapSystemView.render("database_registered.json", database: database)
)
end

@impl true
def after_update(
%DatabaseDeregistered{
Expand Down
41 changes: 41 additions & 0 deletions lib/trento/application/projectors/sap_system_projector.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ defmodule Trento.SapSystemProjector do
SapSystemDeregistered,
SapSystemHealthChanged,
SapSystemRegistered,
SapSystemRestored,
SapSystemUpdated
}

Expand Down Expand Up @@ -134,6 +135,30 @@ defmodule Trento.SapSystemProjector do
end
)

project(
%SapSystemRestored{
sap_system_id: sap_system_id,
sid: sid,
tenant: tenant,
db_host: db_host,
health: health
},
fn multi ->
sap_system = Repo.get!(SapSystemReadModel, sap_system_id)

changeset =
SapSystemReadModel.changeset(sap_system, %{
sid: sid,
tenant: tenant,
db_host: db_host,
health: health,
deregistered_at: nil
})

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

project(
%ApplicationInstanceDeregistered{
instance_number: instance_number,
Expand Down Expand Up @@ -170,6 +195,7 @@ defmodule Trento.SapSystemProjector do
@sap_systems_topic "monitoring:sap_systems"

@impl true
@spec after_update(any, any, any) :: :ok | {:error, any}
def after_update(
%SapSystemRegistered{},
_,
Expand Down Expand Up @@ -258,6 +284,21 @@ defmodule Trento.SapSystemProjector do
)
end

@impl true
def after_update(
%SapSystemRestored{sap_system_id: sap_system_id},
_,
_
) do
sap_system = Repo.get!(SapSystemReadModel, sap_system_id)
CDimonaco marked this conversation as resolved.
Show resolved Hide resolved

TrentoWeb.Endpoint.broadcast(
@sap_systems_topic,
"sap_system_registered",
SapSystemView.render("sap_system_registered.json", sap_system: sap_system)
)
end

@impl true
def after_update(
%ApplicationInstanceDeregistered{
Expand Down
15 changes: 15 additions & 0 deletions lib/trento/domain/sap_system/events/database_restored.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
defmodule Trento.Domain.Events.DatabaseRestored do
@moduledoc """
This event is emitted when a database is restored.
"""

use Trento.Event

require Trento.Domain.Enums.Health, as: Health

defevent do
field :sap_system_id, Ecto.UUID
field :sid, :string
CDimonaco marked this conversation as resolved.
Show resolved Hide resolved
field :health, Ecto.Enum, values: Health.values()
end
end
17 changes: 17 additions & 0 deletions lib/trento/domain/sap_system/events/sap_system_restored.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
defmodule Trento.Domain.Events.SapSystemRestored do
@moduledoc """
This event is emitted when a sap system is restored.
"""

use Trento.Event

require Trento.Domain.Enums.Health, as: Health

defevent do
field :sap_system_id, Ecto.UUID
field :sid, :string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sid won't b e updated, that's for sure. About the others, I can give you the benefit of the doubt XD
Why didn't you add the ensa_version if you added everything else?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to mimic the registration as soon as possible, I noticed the updated event for ensa so I tought that was not really necessary, but we can add it if we want

field :tenant, :string
field :db_host, :string
field :health, Ecto.Enum, values: Health.values()
end
end
131 changes: 131 additions & 0 deletions lib/trento/domain/sap_system/sap_system.ex
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,11 @@ defmodule Trento.Domain.SapSystem do
DatabaseInstanceRegistered,
DatabaseInstanceSystemReplicationChanged,
DatabaseRegistered,
DatabaseRestored,
SapSystemDeregistered,
SapSystemHealthChanged,
SapSystemRegistered,
SapSystemRestored,
SapSystemRolledUp,
SapSystemRollUpRequested,
SapSystemTombstoned,
Expand Down Expand Up @@ -161,6 +163,61 @@ defmodule Trento.Domain.SapSystem do
]
end

# Database restore
def execute(
%SapSystem{database: %Database{deregistered_at: deregistered_at}},
%RegisterDatabaseInstance{
system_replication: "Secondary"
}
)
when not is_nil(deregistered_at),
do: {:error, :sap_system_not_registered}

# When a deregistered database is present, we add the new database instance
# and restore the database, the conditions are the same as registration
def execute(
%SapSystem{database: %Database{deregistered_at: deregistered_at}},
%RegisterDatabaseInstance{
sap_system_id: sap_system_id,
sid: sid,
tenant: tenant,
host_id: host_id,
instance_number: instance_number,
instance_hostname: instance_hostname,
features: features,
http_port: http_port,
https_port: https_port,
start_priority: start_priority,
system_replication: system_replication,
system_replication_status: system_replication_status,
health: health
}
)
when not is_nil(deregistered_at) do
[
%DatabaseRestored{
sap_system_id: sap_system_id,
health: health,
sid: sid
},
%DatabaseInstanceRegistered{
CDimonaco marked this conversation as resolved.
Show resolved Hide resolved
sap_system_id: sap_system_id,
sid: sid,
tenant: tenant,
instance_number: instance_number,
instance_hostname: instance_hostname,
features: features,
http_port: http_port,
https_port: https_port,
start_priority: start_priority,
host_id: host_id,
system_replication: system_replication,
system_replication_status: system_replication_status,
health: health
}
]
end

# When a RegisterDatabaseInstance command is received by an existing SAP System aggregate,
# the SAP System aggregate registers the Database instance if it is not already registered
# and updates the health when needed.
Expand All @@ -185,6 +242,27 @@ defmodule Trento.Domain.SapSystem do
|> Multi.execute(&maybe_emit_sap_system_health_changed_event/1)
end

# Restore sap system
# Same registration rules
def execute(
%SapSystem{deregistered_at: deregistered_at} = sap_system,
%RegisterApplicationInstance{} = instance
)
when not is_nil(deregistered_at) do
sap_system
|> Multi.new()
|> Multi.execute(fn sap_system ->
emit_application_instance_registered_or_application_instance_health_changed(
CDimonaco marked this conversation as resolved.
Show resolved Hide resolved
sap_system,
instance
)
end)
|> Multi.execute(fn sap_system ->
maybe_emit_sap_system_restored_event(sap_system, instance)
end)
|> Multi.execute(&maybe_emit_sap_system_health_changed_event/1)
CDimonaco marked this conversation as resolved.
Show resolved Hide resolved
end

# SAP system not registered, application already present
# If the instance is not one of MESSAGESERVER or ABAP we discard.
# Otherwise if the instance we want register together with already present instances
Expand Down Expand Up @@ -615,6 +693,26 @@ defmodule Trento.Domain.SapSystem do
}
end

def apply(
%SapSystem{database: database} = sap_system,
%DatabaseRestored{
sap_system_id: sap_system_id,
health: health,
sid: sid
}
) do
%SapSystem{
sap_system
| sap_system_id: sap_system_id,
CDimonaco marked this conversation as resolved.
Show resolved Hide resolved
database: %Database{
database
| health: health,
sid: sid,
deregistered_at: nil
}
}
end

def apply(
%SapSystem{} = sap_system,
%SapSystemDeregistered{
Expand All @@ -627,6 +725,18 @@ defmodule Trento.Domain.SapSystem do
}
end

def apply(%SapSystem{} = sap_system, %SapSystemRestored{
sid: sid,
health: health
}) do
%SapSystem{
sap_system
| sid: sid,
health: health,
deregistered_at: nil
}
end

def apply(%SapSystem{} = sap_system, %SapSystemTombstoned{}), do: sap_system

defp maybe_emit_database_instance_registered_event(
Expand Down Expand Up @@ -829,6 +939,27 @@ defmodule Trento.Domain.SapSystem do
end
end

defp maybe_emit_sap_system_restored_event(
CDimonaco marked this conversation as resolved.
Show resolved Hide resolved
%SapSystem{application: %Application{instances: instances}},
%RegisterApplicationInstance{
sap_system_id: sap_system_id,
sid: sid,
tenant: tenant,
db_host: db_host,
health: health
}
) do
if instances_have_abap?(instances) and instances_have_messageserver?(instances) do
%SapSystemRestored{
db_host: db_host,
health: health,
sap_system_id: sap_system_id,
sid: sid,
tenant: tenant
}
end
end

defp maybe_emit_sap_system_registered_or_updated_event(
%SapSystem{sid: nil, application: %Application{instances: instances}},
%RegisterApplicationInstance{
Expand Down
19 changes: 19 additions & 0 deletions test/support/factory.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ defmodule Trento.Factory do
}

alias Trento.Domain.Events.{
ApplicationInstanceDeregistered,
ApplicationInstanceRegistered,
ClusterDeregistered,
ClusterRegistered,
Expand All @@ -29,6 +30,7 @@ defmodule Trento.Factory do
DatabaseInstanceDeregistered,
DatabaseInstanceRegistered,
DatabaseRegistered,
DatabaseRestored,
HostAddedToCluster,
HostDetailsUpdated,
HostRegistered,
Expand Down Expand Up @@ -267,6 +269,14 @@ defmodule Trento.Factory do
})
end

def database_restored_event_factory do
DatabaseRestored.new!(%{
sap_system_id: Faker.UUID.v4(),
sid: Faker.UUID.v4(),
health: Health.passing()
})
end

def deregister_database_instance_command_factory do
DeregisterDatabaseInstance.new!(%{
sap_system_id: Faker.UUID.v4(),
Expand Down Expand Up @@ -298,6 +308,15 @@ defmodule Trento.Factory do
}
end

def application_instance_deregistered_event_factory do
ApplicationInstanceDeregistered.new!(%{
sap_system_id: Faker.UUID.v4(),
deregistered_at: DateTime.utc_now(),
instance_number: "00",
host_id: Faker.UUID.v4()
})
end

def deregister_application_instance_command_factory do
DeregisterApplicationInstance.new!(%{
sap_system_id: Faker.UUID.v4(),
Expand Down
Loading