ha-handler/lib/ha_handler/drbd.ex

92 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