Hemdal.Host behaviour (Hemdal v1.2.1)
View SourceHost is spawning processes to run tasks for the corresponding host. It's a way of limiting the amount of commands triggered against a host and ensure the rest of the commands stay in the queue.
It's also the base for the implementation of the way of running commands. At the moment the available way to run commands are:
Hemdal.Host.Local
intended to run local commands.Hemdal.Host.Trooper
intended to run SSH commands. It's not included in the main repository, you can find more information here.
If you want to know more about what could be included in a command or to be run in a host you can review the following modules:
Hemdal.Config.Command
where you can check what's included inside of the command.Hemdal.Config.Host
where you can find information about the host.
Implement a new Host
If you need to implement a new way to run external commands, you can
create a new Hemdal.Host.XXX
module which will be using the module
Hemdal.Host
inside. For example, if you need to implement a telnet
way to access to the remote hosts, you can implement a module as follows:
defmodule Hemdal.Host.Telnet do
use Hemdal.Host
@impl Hemdal.Host
def exec(host, command) do
# do your stuff
{:ok, errorlevel, output}
end
@impl Hemdal.Host
def write_file(host, filename, content) do
raise "Impossible to transfer files"
end
@impl Hemdal.Host
def delete(host, filename) do
raise "Impossible to remove files"
end
end
While telnet isn't prepared to transfer files, it's raising an error which is telling the system that's impossible to use the script way for running commands.
Summary
Types
The command to be executed.
The command arguments to be passed with the execution command.
The errorlevel is shell concept. In the shell every command is returning an integer as the errorlevel after the running, if that's 0 (zero), it's meaning the execution was fine, otherwise a positive or negative number means the running was wrong and the number could means something different depending on the command.
The options for exec_interactive/4
and shell/2
let us define if we want to
accumulate the output (it's usually not desirable for shell sessions) and the
timeout for idle. It means the time it wait between interactions to close the
communication.
The options for exec are providing to the execution valid information, and sometimes required information about the running process.
Handler is used by the backend implementation of the host, it could be whatever depending on the needs of the backend implementation. For example, it could be the SSH connection or the credentials, or the way to access to the PTY or TTY. See the implementations for further details.
Information about the status of the host. The host is limiting the number of running workers so we can get the information of the following data
The console output. It will be decoded using JSON to determine what's the information processed and generated by the command to be processed.
The reason of the failure. It's usually an atom, but it could be whatever.
Callbacks
Remove a file which was created with write_file/3
when the execution of
the script was finalized.
Exec a command using the method implemented by the module where it's
implemented. The exec/2
command is getting a handler from the transaction
and the command to be executed as a string.
Exec an interactive command implemented by the module where it's
implemented. The c:exec_interactive/3
command is getting a handler from the
transaction and the command to be executed as a string.
Start a shell and connect to it. The shell usually has specific features that let us to do more than in normal or usual exec commands.
The transaction is an optional callback which is implemented by default if
you are using use Hemdal.Host
and it's ensuring you can provide to the
write_file/3
, exec/2
and delete/2
the same handler which must be
provided to the running function. As an example, the default implementation
of this callback is as follows
Write a file in the remote (or local) host. It's intended to write the
scripts which will be needed to be executed after that with exec/2
.
Functions
Returns a specification to start this module under a supervisor.
Run or execute the command passed as parameter. It's needed to pass the host ID to find the process where to send the request, and the the command and the arguments to run the command.
Run or execute the command passed as parameter. It's needed to pass the host ID to find the process where to send the request, and the the command and the arguments to run the command.
Check if the host is started based on the host ID passed as parameter.
Get all the host IDs that are running at the moment of calling this function.
Retrieve stats for all of the hosts. See get_stats/1
.
Retrieve the PID providing the host ID.
Retrieve stats for a specific host.
Performs a reload for all of the hosts. It's retrieving the configuration for
all of the hosts and applying it for each host. It's reloading the
configuration for each host and applying it through the update_host/1
function.
Start a new host under the Hemdal.Host.Supervisor
module.
Start all of the hosts based on the configuration. It's retrieving all of
the hosts from the configuration and the using each host with the start/1
function.
Start a new host directly. It's intended to be in use from the supervisor, but it could be used for test purposes.
Terminate the processes related to a process ID.
Update the host configuration. If the host isn't running it's starting it
and passing it the configuration provided as the host
parameter. In a
nutshell, it's: if exist host ID then perform an update of the host
information, else start the process with the host information.
Types
@type command() :: String.t()
The command to be executed.
@type command_args() :: [String.t()]
The command arguments to be passed with the execution command.
@type errorlevel() :: integer()
The errorlevel is shell concept. In the shell every command is returning an integer as the errorlevel after the running, if that's 0 (zero), it's meaning the execution was fine, otherwise a positive or negative number means the running was wrong and the number could means something different depending on the command.
The options for exec_interactive/4
and shell/2
let us define if we want to
accumulate the output (it's usually not desirable for shell sessions) and the
timeout for idle. It means the time it wait between interactions to close the
communication.
The options for exec are providing to the execution valid information, and sometimes required information about the running process.
For example, if we want to run an interactive command but using a
non-background execution, then we will need to provide caller
option.
In addition, args
are a list of arguments we pass to a script usually,
and timeout
makes sense mainly for non-interactive executions. Indeed,
timeout
is only applicable for the calling of the function, because
it returns usually fast (depending on the loaded queue), it's not very
useful for execution in background mode, and it could be dangerous in
interactive and non-background mode.
@type handler() :: any()
Handler is used by the backend implementation of the host, it could be whatever depending on the needs of the backend implementation. For example, it could be the SSH connection or the credentials, or the way to access to the PTY or TTY. See the implementations for further details.
@type host_stats() :: %{ :status => :up | :down, optional(:waiting_workers) => non_neg_integer(), optional(:running_workers) => non_neg_integer(), optional(:max_running_workers) => non_neg_integer() }
Information about the status of the host. The host is limiting the number of running workers so we can get the information of the following data:
waiting_workers
the current number of requests awaiting in the queue.running_workers
the number of running workers.max_running_workers
the max number of workers that could be run at the same time.
@type output() :: String.t()
The console output. It will be decoded using JSON to determine what's the information processed and generated by the command to be processed.
@type reason() :: any()
The reason of the failure. It's usually an atom, but it could be whatever.
Callbacks
Remove a file which was created with write_file/3
when the execution of
the script was finalized.
@callback exec(handler(), command()) :: {:ok, errorlevel(), output()} | {:error, reason()}
Exec a command using the method implemented by the module where it's
implemented. The exec/2
command is getting a handler from the transaction
and the command to be executed as a string.
@callback exec_interactive(handler(), command(), pid(), exec_mod_opts()) :: {:ok, pid()} | {:ok, errorlevel(), output()} | {:error, reason()}
Exec an interactive command implemented by the module where it's
implemented. The c:exec_interactive/3
command is getting a handler from the
transaction and the command to be executed as a string.
@callback shell(handler(), pid(), exec_mod_opts()) :: {:ok, pid()} | {:ok, errorlevel(), output()} | {:error, reason()}
Start a shell and connect to it. The shell usually has specific features that let us to do more than in normal or usual exec commands.
@callback transaction(Hemdal.Config.Host.t(), (handler() -> any())) :: any()
The transaction is an optional callback which is implemented by default if
you are using use Hemdal.Host
and it's ensuring you can provide to the
write_file/3
, exec/2
and delete/2
the same handler which must be
provided to the running function. As an example, the default implementation
of this callback is as follows:
@impl Hemdal.Host
def transaction(host, f), do: f.(host)
@callback write_file(handler(), tmp_file :: String.t(), content :: String.t()) :: :ok | {:error, reason()}
Write a file in the remote (or local) host. It's intended to write the
scripts which will be needed to be executed after that with exec/2
.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor
.
@spec exec(host_id(), Hemdal.Config.Command.t(), exec_opts()) :: {:ok, map()} | {:error, map()}
Run or execute the command passed as parameter. It's needed to pass the host ID to find the process where to send the request, and the the command and the arguments to run the command.
It's returning a tuple for :ok
or :error
and a set of data which depends on
if it was success or not.
The success return data is usually including the following keys (all of them as strings):
status
which could beOK
,FAIL
,WARN
orUNKNOWN
.message
which is a string containing a message to show.
The failure return data is usually similar to the previous one but it could be
containing something different depending on the return of the remote command.
If the JSON sent back from the command is valid, it's usually using that as
data and it's marked as UNKNOWN
status if there is no status defined.
@spec exec_background(host_id(), Hemdal.Config.Command.t(), exec_opts()) :: {:ok, pid()} | {:error, any()}
Run or execute the command passed as parameter. It's needed to pass the host ID to find the process where to send the request, and the the command and the arguments to run the command.
It's returning a tuple for :ok
or :error
. The :ok
tuple have a PID to know
which process is in charge of running the background process and to know if it's
still running or not.
@spec exists?(host_id()) :: boolean()
Check if the host is started based on the host ID passed as parameter.
@spec get_all() :: [host_id()]
Get all the host IDs that are running at the moment of calling this function.
@spec get_all_stats() :: %{required(host_id()) => host_stats()}
Retrieve stats for all of the hosts. See get_stats/1
.
@spec get_pid(host_id()) :: pid() | nil
Retrieve the PID providing the host ID.
@spec get_stats(host_id()) :: host_stats()
Retrieve stats for a specific host.
@spec reload_all() :: :ok
Performs a reload for all of the hosts. It's retrieving the configuration for
all of the hosts and applying it for each host. It's reloading the
configuration for each host and applying it through the update_host/1
function.
@spec start(Hemdal.Config.Host.t()) :: :ok
Start a new host under the Hemdal.Host.Supervisor
module.
@spec start_all() :: :ok
Start all of the hosts based on the configuration. It's retrieving all of
the hosts from the configuration and the using each host with the start/1
function.
@spec start_link([Hemdal.Config.Host.t()]) :: {:ok, pid()}
Start a new host directly. It's intended to be in use from the supervisor, but it could be used for test purposes.
@spec stop(host_id()) :: :ok
Terminate the processes related to a process ID.
@spec update_host(Hemdal.Config.Host.t()) :: {:ok, pid()}
Update the host configuration. If the host isn't running it's starting it
and passing it the configuration provided as the host
parameter. In a
nutshell, it's: if exist host ID then perform an update of the host
information, else start the process with the host information.