91 lines
3.2 KiB
Elixir
91 lines
3.2 KiB
Elixir
defmodule HAHandler.DRBD do
|
|
require Logger
|
|
|
|
@supervisor HAHandler.DRBD.Supervisor
|
|
|
|
# There might be >1 resources configured in DRBD!
|
|
@default_resource_id "1"
|
|
|
|
# We don't support DRBD 9 for the time being, as /proc/drbd does not have a
|
|
# stable API.
|
|
@supported_drbd_major_version "8"
|
|
|
|
# Parsing of /proc/drbd, assuming DRBD 8. Splitting the regexes helps humans
|
|
# wrapping their head around what's going on. And yes, it's fragile: we need
|
|
# drbd 9 to get a JSON interface to `drbdadm status`.
|
|
@drbd_proc_cmd "cat /proc/drbd"
|
|
@block_regex ~r/(?<version_block>(.|\n)*)\n(?<resource_block>\n\s(.|\n)*)/
|
|
@version_regex ~r/version: (?<full>(?<major>\d+)\.(?<intermediate>\d+)\.(?<minor>\d))/
|
|
@resource_split_regex ~r{(\n\s(\d+)\:\s)}
|
|
@id_extraction_regex ~r/\n\s(?<id>\d+)\:\s/
|
|
@data_extraction_regex ~r/cs:(?<cs>(\w|\/)+)\sro:(?<ro>(\w|\/)+)\sds:(?<ds>(\w|\/)+)\s/
|
|
|
|
# Empty state, when backend is not queryable for some reason.
|
|
@empty_state %{
|
|
hostname: "unknown",
|
|
version: "",
|
|
mode: "",
|
|
status: "unknown",
|
|
data: ""
|
|
}
|
|
|
|
def get_instances() do
|
|
watchers = Supervisor.which_children(@supervisor)
|
|
|
|
for {hostname, pid, _type, _modules} <- watchers do
|
|
{hostname, pid}
|
|
end
|
|
end
|
|
|
|
def get_stats() do
|
|
get_instances()
|
|
|> Enum.map(fn instance -> get_state(instance) end)
|
|
end
|
|
|
|
def get_state({hostname, pid}) do
|
|
empty_reply = %{@empty_state | hostname: hostname}
|
|
|
|
case GenServer.call(pid, {:execute, @drbd_proc_cmd}) do
|
|
{:ok, raw, 0} ->
|
|
case Regex.named_captures(@block_regex, raw) do
|
|
%{"version_block" => version_block, "resource_block" => resource_block} ->
|
|
version = Regex.named_captures(@version_regex, version_block)
|
|
|
|
if Map.get(version, "major") != @supported_drbd_major_version do
|
|
{:error, "unsupported DRBD version #{inspect(version)}"}
|
|
else
|
|
resources = Regex.split( @resource_split_regex, resource_block,
|
|
[include_captures: true, trim: true])
|
|
|> Enum.chunk_every(2)
|
|
|> Enum.map(fn [raw_id, raw_data] ->
|
|
%{}
|
|
|> Map.merge(Regex.named_captures(@id_extraction_regex, raw_id))
|
|
|> Map.merge(Regex.named_captures(@data_extraction_regex, raw_data))
|
|
end)
|
|
|
|
default_resource = resources
|
|
|> Enum.filter(fn r -> r["id"] == @default_resource_id end)
|
|
|> Enum.at(0)
|
|
|
|
processed_reply = %{
|
|
version: Map.get(version, "full"),
|
|
mode: Map.get(default_resource, "ro"),
|
|
status: Map.get(default_resource, "ds"),
|
|
data: resources
|
|
}
|
|
Map.merge(empty_reply, processed_reply)
|
|
end
|
|
_ ->
|
|
Logger.warning("Failed to query DRBD backend: could not parse /proc/drbd.")
|
|
|
|
end
|
|
{:ok, _, posix_err} ->
|
|
Logger.warning("Failed to query DRBD backend: POSIX #{inspect(posix_err)}.")
|
|
empty_reply
|
|
|
|
{:error, err} ->
|
|
Logger.warning("Failed to query DRBD backend: #{inspect(err)}.")
|
|
empty_reply
|
|
end
|
|
end
|
|
end
|