ha-handler/lib/ha_handler/drbd.ex
Timothée Floure b9aa3eeb98
Add initial plumbing for DRBD
This is 'quickly-hacked-together' and needs some love - it's working,
but is ways to fragile. It's no more than a POC atm.
2022-02-25 13:39:58 +01:00

74 lines
2.7 KiB
Elixir

defmodule HAHandler.DRBD do
@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/
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
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)
%{
hostname: hostname,
version: Map.get(version, "full"),
mode: Map.get(default_resource, "ro"),
status: Map.get(default_resource, "ds"),
data: resources
}
end
_ ->
{:error, "could not parse /proc/drbd"}
end
{:ok, _, posix_err} ->
{:error, posix_err}
{:error, _err} = reply ->
reply
end
end
end