Packages

HAProxy Agent Check protocol implementation for Elixir/Phoenix apps. Allows for easy rolling restarts and dynamic backpressure to your HAProxy loadbalancer.

Current section

Files

Jump to
agent_check lib agent_check.ex
Raw

lib/agent_check.ex

defmodule AgentCheck do
@moduledoc """
Implementation of the Agent Check protocol for your Elixir/Phoenix app.
Also see - https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#5.2-agent-check
It allows for easy rolling restarts and dynamic backpressure to your loadbalancer.
"""
require Logger
@doc """
Starts accepting connections on the given `port`.
"""
def accept(port) when is_integer(port) do
{:ok, socket} =
:gen_tcp.listen(port, [:binary, packet: :line, active: false, reuseaddr: true])
Logger.info("Agent Check - accepting connections on port #{port}")
loop_acceptor(socket)
end
def accept(port) when is_binary(port) do
port
|> Integer.parse()
|> elem(0)
|> accept
end
@doc """
Wait for incomming socket connection (blocking) and spawn of a Task when one comes in.
"""
def loop_acceptor(socket) do
{:ok, client} = :gen_tcp.accept(socket)
{:ok, pid} = Task.Supervisor.start_child(AgentCheck.TaskSupervisor, fn -> serve(client) end)
:gen_tcp.controlling_process(client, pid)
loop_acceptor(socket)
end
@doc """
Connect the socket and wait for a command.
"""
def serve(socket) do
reply =
socket
|> read_line()
|> String.trim("\n")
|> String.trim("\r")
|> handle_command
:gen_tcp.send(socket, "#{reply}\n")
:gen_tcp.close(socket)
end
@doc """
Try to handle the received commandline.
"""
def handle_command("state"), do: AgentCheck.GlobalState.get_stats() |> format_haproxy_state # Used by Haproxy
def handle_command("stats"), do: inspect(AgentCheck.GlobalState.get_stats())
def handle_command("ready"), do: AgentCheck.GlobalState.ready()
def handle_command("maint"), do: AgentCheck.GlobalState.maint()
def handle_command("stop"), do: AgentCheck.GlobalState.stop("reason")
def handle_command("drain"), do: AgentCheck.GlobalState.drain()
def handle_command("up"), do: AgentCheck.GlobalState.up()
def handle_command("down"), do: AgentCheck.GlobalState.down()
def handle_command(_), do: "Unknown command"
@doc """
Reformat the stats struct into a haproxy state format.
"""
def format_haproxy_state(stats_struct) do
state = Map.get(stats_struct, :state)
capacity = Map.get(stats_struct, :capacity)
"#{state} #{capacity}%"
end
@doc """
Read a single line from the connected socket.
"""
def read_line(socket) do
{:ok, data} = :gen_tcp.recv(socket, 0)
data
end
end