defmodule HAHandler.DRBD.Watcher do # TODO: add support for SSH public keys authentication. use GenServer require Logger def start_link(opts) do GenServer.start_link(__MODULE__, opts) end defp connect(hostname, password) do case :inet.gethostbyname(to_charlist(hostname), :inet6) do {:ok, {:hostent, _name, _aliases, _addrtype, _length, [addr]}} -> SSHEx.connect( ip: addr, user: 'root', password: password, silently_accept_hosts: true ) err -> err end end @impl true def init(opts) do # Configures this worker's jobs to report in the "drbd" namespace Appsignal.Span.set_namespace(Appsignal.Tracer.root_span(), "drbd") state = %{ backend: nil, last_reconnect: nil, hostname: Keyword.get(opts, :hostname), password: Keyword.get(opts, :password), } # This action will be processed once the GenServer is fully # started/operational. This process handle connection failures by itself, # as we don't want to crash loop into supervisor logic (which is only there # to handle unexpected failure). send self(), :reconnect {:ok, state} end @impl true def handle_info(:reconnect, state = %{hostname: hostname, password: password}) do case connect(hostname, password) do {:ok, pid} -> {:noreply, %{state | backend: pid}} {:error, _err} -> # Nothing to do, as the next request will trigger the reconnect logic # (see :execute call). {:noreply, state} end end @impl true def handle_call({:execute, cmd}, _from, %{backend: backend} = state) do case SSHEx.run(backend, cmd) do {:ok, _output, _status} = reply-> {:reply, reply, state} {:error, :closed} = reply -> # Asynchroneously tries to reopen the connection to the backend. send self(), :reconnect {:reply, reply, state} {:error, _err} = reply -> # Do not take action on unknown error. {:reply, reply, state} end end end