Packages

Aerospike driver for Elixir with an OTP-native cluster runtime

Current section

Files

Jump to
aerospike_driver lib aerospike protocol peers.ex
Raw

lib/aerospike/protocol/peers.ex

defmodule Aerospike.Protocol.Peers do
@moduledoc false
# Parses the `peers-clear-std` info response used for cluster node discovery.
# The server returns a generation counter, default port, and a nested list of
# peer entries. The Cluster module uses this to discover and verify new nodes.
# Matches individual peer entries: [node_name,tls_name,[addr1,addr2,...]]
# Captures: 1=node_name, 2=tls_name (may be empty), 3=comma-separated addresses.
@peer_regex ~r/\[([^,\[\]]+),([^,\[\]]*),\[([^\]]*)\]\]/
@doc """
Parses `peers-clear-std` info value.
Format: `generation,default_port,[[node_name,tls_name,[addr1,addr2,...]],...]`
Empty peer list: `0,3000,[]`
"""
@spec parse_peers_clear_std(String.t()) ::
{:ok,
%{
generation: non_neg_integer(),
default_port: :inet.port_number(),
peers: [%{node_name: String.t(), host: String.t(), port: :inet.port_number()}]
}}
| :error
def parse_peers_clear_std(s) when is_binary(s) do
s = String.trim(s)
case String.split(s, ",", parts: 3) do
[gen_s, port_s, rest] ->
with {gen, ""} <- Integer.parse(gen_s),
{port, ""} <- Integer.parse(port_s),
true <- gen >= 0 and port > 0 do
peers = parse_peer_entries(rest, port)
{:ok, %{generation: gen, default_port: port, peers: peers}}
else
_ -> :error
end
_ ->
:error
end
end
defp parse_peer_entries("[]", _default_port), do: []
defp parse_peer_entries("", _default_port), do: []
# Scans the nested bracket structure with regex rather than writing a recursive
# parser — the format is regular enough that this is reliable.
defp parse_peer_entries(rest, default_port) do
@peer_regex
|> Regex.scan(rest)
|> Enum.flat_map(fn
[_, node_name, _tls, addrs_str] ->
case first_address(addrs_str, default_port) do
{:ok, host, port} -> [%{node_name: node_name, host: host, port: port}]
:error -> []
end
_ ->
[]
end)
end
# Uses only the first address from the peer's address list.
# Peers may advertise multiple addresses (e.g. for different networks).
defp first_address("", _default_port), do: :error
defp first_address(addrs_str, default_port) do
addr =
addrs_str
|> String.split(",", trim: true)
|> List.first()
case addr do
nil ->
:error
addr ->
{host, port} = parse_host_port(addr, default_port)
{:ok, host, port}
end
end
# Splits "host:port"; falls back to default_port when the port is missing or invalid.
defp parse_host_port(addr, default_port) do
case String.split(addr, ":", parts: 2) do
[host, port_s] ->
case Integer.parse(port_s) do
{port, ""} when port > 0 -> {host, port}
_ -> {addr, default_port}
end
[host] ->
{host, default_port}
end
end
@doc """
Parses a simple integer info field (e.g. `peers-generation`, `partition-generation`).
"""
@spec parse_generation(String.t()) :: {:ok, non_neg_integer()} | :error
def parse_generation(s) when is_binary(s) do
case Integer.parse(String.trim(s)) do
{n, ""} when n >= 0 -> {:ok, n}
_ -> :error
end
end
end