Skip to content
This repository has been archived by the owner on Jun 11, 2023. It is now read-only.

Commit

Permalink
Add LogRecoverRequest
Browse files Browse the repository at this point in the history
  • Loading branch information
renatomassaro committed Aug 29, 2018
1 parent 241fb6c commit 2a215ea
Show file tree
Hide file tree
Showing 13 changed files with 1,081 additions and 21 deletions.
96 changes: 96 additions & 0 deletions lib/log/action/flow/recover.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# credo:disable-for-this-file Credo.Check.Refactor.FunctionArity
defmodule Helix.Log.Action.Flow.Recover do

alias Helix.Event
alias Helix.Entity.Model.Entity
alias Helix.Network.Model.Connection
alias Helix.Network.Model.Tunnel
alias Helix.Server.Model.Server
alias Helix.Software.Model.File
alias Helix.Log.Model.Log

alias Helix.Log.Process.Recover, as: LogRecoverProcess

@spec global(
Server.t,
Server.t,
File.t,
Entity.id,
{Tunnel.t, Connection.ssh} | nil,
Event.relay
) ::
term
def global(
gateway = %Server{},
endpoint = %Server{},
recover = %File{software_type: :log_recover},
entity_id = %Entity.ID{},
conn,
relay
) do
start_process(gateway, endpoint, nil, recover, entity_id, conn, relay)
end

@spec custom(
Server.t,
Server.t,
Log.t,
File.t,
Entity.id,
{Tunnel.t, Connection.ssh} | nil,
Event.relay
) ::
term
def custom(
gateway = %Server{},
endpoint = %Server{},
log = %Log{},
recover = %File{software_type: :log_recover},
entity_id = %Entity.ID{},
conn,
relay
) do
start_process(
gateway, endpoint, log, recover, entity_id, conn, relay
)
end

defp start_process(
gateway = %Server{},
endpoint = %Server{},
log,
recover = %File{software_type: :log_recover},
entity_id = %Entity.ID{},
conn_info,
relay
) do
method =
if is_nil(log) do
:global
else
:custom
end

{network_id, ssh} =
if is_nil(conn_info) do
{nil, nil}
else
{tunnel, ssh} = conn_info
{tunnel.network_id, ssh}
end

params = %{}

meta =
%{
recover: recover,
log: log,
method: method,
ssh: ssh,
entity_id: entity_id,
network_id: network_id
}

LogRecoverProcess.execute(gateway, endpoint, params, meta, relay)
end
end
6 changes: 3 additions & 3 deletions lib/log/event/log.ex
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,12 @@ defmodule Helix.Log.Event.Log do

publish do

alias Helix.Log.Public.Index, as: LogIndex

@event :log_recovered

def generate_payload(event, _socket) do
data = %{
log_id: to_string(event.log.log_id)
}
data = LogIndex.render_log(event.log)

{:ok, data}
end
Expand Down
104 changes: 104 additions & 0 deletions lib/log/henforcer/log/recover.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
defmodule Helix.Log.Henforcer.Log.Recover do

import Helix.Henforcer

alias Helix.Server.Henforcer.Server, as: ServerHenforcer
alias Helix.Server.Model.Server
alias Helix.Software.Henforcer.File, as: FileHenforcer
alias Helix.Software.Model.File
alias Helix.Log.Henforcer.Log, as: LogHenforcer
alias Helix.Log.Model.Log

@type can_recover_global_relay :: %{gateway: Server.t, recover: File.t}
@type can_recover_global_relay_partial :: map
@type can_recover_global_error ::
ServerHenforcer.server_exists_error
| exists_recover_error

@spec can_recover_global?(Server.id) ::
{true, can_recover_global_relay}
| can_recover_global_error
@doc """
Henforces that the player can start a global LogRecover process.
In order to recover globally, all a user needs to have is:
- SSH access to the server
- a valid LogRecover file on his gateway filesystem
"""
def can_recover_global?(gateway_id) do
with \
{true, r1} <- ServerHenforcer.server_exists?(gateway_id),
r1 = replace(r1, :server, :gateway),
gateway = r1.gateway,
{true, r2} <- exists_recover?(gateway),
r2 = replace(r2, :file, :recover, only: true)
do
[r1, r2]
|> relay()
|> reply_ok()
end
end

@type can_recover_custom_relay ::
%{log: Log.t, gateway: Server.t, recover: File.t}
@type can_recover_custom_relay_partial :: map
@type can_recover_custom_error ::
LogHenforcer.log_exists_error
| LogHenforcer.belongs_to_server_error
| ServerHenforcer.server_exists_error
| exists_recover_error

@spec can_recover_custom?(Log.id, Server.id, Server.id) ::
{true, can_recover_custom_relay}
| can_recover_custom_error
@doc """
Henforces that the player can start a custom LogRecover process.
In order to recover a specific (custom) log, the player must have the same
requirements of a `global` recover (SSH access and valid LogRecover file), and
also the given `log_id` must exist, and it must exist on the target server.
"""
def can_recover_custom?(log_id = %Log.ID{}, gateway_id, target_id) do
with \
{true, r1} <- LogHenforcer.log_exists?(log_id),
log = r1.log,

{true, _} <- LogHenforcer.belongs_to_server?(log, target_id),

{true, r2} <- ServerHenforcer.server_exists?(gateway_id),
r2 = replace(r2, :server, :gateway),
gateway = r2.gateway,

{true, r3} <- exists_recover?(gateway),
r3 = replace(r3, :file, :recover, only: true)
do
[r1, r2, r3]
|> relay()
|> reply_ok()
end
end

@type exists_recover_relay :: FileHenforcer.exists_software_module_relay
@type exists_recover_relay_partial ::
FileHenforcer.exists_software_module_relay_partial
@type exists_recover_error ::
{false, {:recover, :not_found}, exists_recover_relay_partial}

@spec exists_recover?(Server.t) ::
{true, exists_recover_relay}
| exists_recover_error
@doc """
Ensures that exists a Recover file on `server`, sorting the result by `module`
(only `:log_recover` in this context).
It's simply a wrapper over `FileHenforcer.exists_software_module?` used to
generate a more meaningful error message ("recover_not_found") instead of
"module_not_found".
"""
def exists_recover?(server = %Server{}) do
henforce_else(
FileHenforcer.exists_software_module?(:log_recover, server),
{:recover, :not_found}
)
end
end
8 changes: 5 additions & 3 deletions lib/log/process/recover.ex
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,12 @@ process Helix.Log.Process.Recover do

on_retarget(process, _data) do
{new_log, method} =
# On `log_recover_global`, we must select a new log on each iteration
# On `log_recover_global`, we must select a new log on each iteration.
if process.type == :log_recover_global do
log = LogRecoverProcess.find_next_target(process.target_id)
{log, :global}

# On `log_recover_custom` we always work at the same log
# On `log_recover_custom` we always work at the same log. May be `nil`.
else
log = LogQuery.fetch(process.tgt_log_id)
{log, :custom}
Expand Down Expand Up @@ -231,7 +231,9 @@ process Helix.Log.Process.Recover do
if f.log.revisions.extra == 0 do
999_999_999_999
else
(f.log.revisions.total * multiplier) / f.recover.version.log_recover
t = (f.log.revisions.total * multiplier) / f.recover.version.log_recover

t + 5000
end
end

Expand Down
10 changes: 10 additions & 0 deletions lib/log/public/recover.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
defmodule Helix.Log.Public.Recover do

alias Helix.Log.Action.Flow.Recover, as: RecoverFlow

defdelegate global(gateway, endpoint, recover, entity, conn_info, relay),
to: RecoverFlow

defdelegate custom(gateway, endpoint, log, recover, entity, conn_info, relay),
to: RecoverFlow
end
120 changes: 120 additions & 0 deletions lib/log/websocket/requests/recover.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import Helix.Websocket.Request

request Helix.Log.Websocket.Requests.Recover do
@moduledoc """
`LogRecoverRequest` is called when the player wants to recover a log. It may
either be a `global` recovery, in which case a recoverable log is randomly
selected from all logs within the server, or it may be a `custom` recovery,
in which case a specific log to be recovered is defined by the player.
"""

import HELL.Macros

alias Helix.Server.Query.Server, as: ServerQuery
alias Helix.Log.Henforcer.Log.Recover, as: LogRecoverHenforcer
alias Helix.Log.Model.Log
alias Helix.Log.Public.Recover, as: RecoverPublic

def check_params(request, socket) do
case request.unsafe["method"] do
"global" ->
check_params_global(request, socket)

"custom" ->
check_params_custom(request, socket)

_ ->
reply_error(request, "bad_method")
end
end

defp check_params_global(request, _socket) do
with \
true <- is_nil(request.unsafe["log_id"])
do
update_params(request, %{method: :global}, reply: true)
else
_ ->
bad_request(request)
end
end

defp check_params_custom(request, _socket) do
with \
{:ok, log_id} <- Log.ID.cast(request.unsafe["log_id"])
do
params = %{method: :custom, log_id: log_id}

update_params(request, params, reply: true)
else
_ ->
bad_request(request)
end
end

def check_permissions(request = %{params: %{method: :global}}, socket) do
gateway_id = socket.assigns.gateway.server_id

case LogRecoverHenforcer.can_recover_global?(gateway_id) do
{true, relay} ->
meta = %{gateway: relay.gateway, recover: relay.recover}
update_meta(request, meta, reply: true)

{false, reason, _} ->
reply_error(request, reason)
end
end

def check_permissions(request = %{params: %{method: :custom}}, socket) do
log_id = request.params.log_id
gateway_id = socket.assigns.gateway.server_id
target_id = socket.assigns.destination.server_id

can_recover? =
LogRecoverHenforcer.can_recover_custom?(log_id, gateway_id, target_id)

case can_recover? do
{true, relay} ->
meta = %{gateway: relay.gateway, recover: relay.recover, log: relay.log}
update_meta(request, meta, reply: true)

{false, reason, _} ->
reply_error(request, reason)
end
end

def handle_request(request, socket) do
entity_id = socket.assigns.entity_id
recover = request.meta.recover
gateway = request.meta.gateway
relay = request.relay

{target, conn_info} =
if socket.assigns.meta.access == :local do
{gateway, nil}
else
{
ServerQuery.fetch(socket.assigns.destination.server_id),
{socket.assigns.tunnel, socket.assigns.ssh}
}
end

hespawn fn ->
if request.params.method == :global do
RecoverPublic.global(
gateway, target, recover, entity_id, conn_info, relay
)
else
log = request.meta.log

RecoverPublic.custom(
gateway, target, log, recover, entity_id, conn_info, relay
)
end
end

reply_ok(request)
end

render_empty()
end
Loading

0 comments on commit 2a215ea

Please sign in to comment.