diff --git a/config/config.exs b/config/config.exs index 0df80bc..768a042 100644 --- a/config/config.exs +++ b/config/config.exs @@ -3,5 +3,5 @@ import Config config :ha_handler, http_port: 4000, acme_challenge_path: "acme-challenge", - haproxy_socket: System.get_env("HAPROXY_SOCKET") || "/var/run/haproxy.sock" - + haproxy_socket: System.get_env("HAPROXY_SOCKET") || "/var/run/haproxy.sock", + pgsql_instances: [] diff --git a/lib/ha_handler.ex b/lib/ha_handler.ex index 8ab75e7..2700fc0 100644 --- a/lib/ha_handler.ex +++ b/lib/ha_handler.ex @@ -11,6 +11,8 @@ defmodule HAHandler do def http_port, do: Application.get_env(@otp_app, :http_port) def haproxy_socket, do: Application.get_env(@otp_app, :haproxy_socket) + def pgsql_instances, do: Application.get_env(@otp_app, :pgsql_instances, []) + def acme_challenge_path, do: Application.get_env(@otp_app, :acme_challenge_path) def static_path(), do: Application.app_dir(@otp_app, "priv/static/") diff --git a/lib/ha_handler/application.ex b/lib/ha_handler/application.ex index bb02cbf..d928238 100644 --- a/lib/ha_handler/application.ex +++ b/lib/ha_handler/application.ex @@ -5,12 +5,13 @@ defmodule HAHandler.Application do use Application - import HAHandler + alias HAHandler @impl true def start(_type, _args) do children = [ - {Plug.Cowboy, scheme: :http, plug: HAHandler.Web.Router, options: [port: http_port()]} + {Plug.Cowboy, scheme: :http, plug: HAHandler.Web.Router, options: [port: HAHandler.http_port()]}, + {HAHandler.PGSQL.Supervisor, HAHandler.pgsql_instances()} ] # See https://hexdocs.pm/elixir/Supervisor.html diff --git a/lib/ha_handler/pgsql.ex b/lib/ha_handler/pgsql.ex new file mode 100644 index 0000000..3107d83 --- /dev/null +++ b/lib/ha_handler/pgsql.ex @@ -0,0 +1,10 @@ +defmodule HAHandler.PGSQL do + @supervisor HAHandler.PGSQL.Supervisor + + def status() do + watchers = Supervisor.which_children(@supervisor) + for {_id, pid, _type, _modules} <- watchers do + GenServer.call(pid, :status) + end + end +end diff --git a/lib/ha_handler/pgsql/supervisor.ex b/lib/ha_handler/pgsql/supervisor.ex new file mode 100644 index 0000000..8c826ac --- /dev/null +++ b/lib/ha_handler/pgsql/supervisor.ex @@ -0,0 +1,17 @@ +defmodule HAHandler.PGSQL.Supervisor do + use Supervisor + + alias HAHandler.PGSQL.Watcher, as: PGSQLWatcher + + def start_link(opts) do + Supervisor.start_link(__MODULE__, opts, name: __MODULE__) + end + + @impl true + def init(instances) do + children = Enum.map(instances, fn conf -> {PGSQLWatcher, conf} end) + + opts = [ strategy: :one_for_one ] + Supervisor.init(children, opts) + end +end diff --git a/lib/ha_handler/pgsql/watcher.ex b/lib/ha_handler/pgsql/watcher.ex new file mode 100644 index 0000000..e3ce328 --- /dev/null +++ b/lib/ha_handler/pgsql/watcher.ex @@ -0,0 +1,42 @@ +defmodule HAHandler.PGSQL.Watcher do + use GenServer + require Logger + + def start_link(opts) do + GenServer.start_link(__MODULE__, opts, name: __MODULE__) + end + + @impl true + def init(opts) do + # Starts a Postgrex child but does not means the connection was + # successful. + # TODO: set dbconnections backoff and connect hooks + # See https://github.com/elixir-ecto/db_connection/blob/master/lib/db_connection.ex#L343 + {:ok, pid} = Postgrex.start_link(opts) + + state = %{ + backend: pid, + hostname: Keyword.get(opts, :hostname) + } + + {:ok, state} + end + + @impl true + def handle_call(:status, _from, %{backend: backend, hostname: hostname} = state) do + result = case Postgrex.query(backend, "SELECT version();", []) do + {:ok, %Postgrex.Result{rows: [[raw_version_string]]}} -> + version = case Regex.run(~r/^PostgreSQL (\d+\.\d+)/, raw_version_string) do + [_, version_number] -> version_number + _ -> "unknown" + end + %{hostname: hostname, version: version, status: "up"} + {:error, %DBConnection.ConnectionError{message: _msg, reason: err}} -> + %{hostname: hostname, version: "unknown", status: err} + _ -> + %{hostname: hostname, version: "unknown", status: "Unhandled error"} + end + + {:reply, result, state} + end +end diff --git a/lib/ha_handler/web/controller.ex b/lib/ha_handler/web/controller.ex index 01b7d4f..448af9c 100644 --- a/lib/ha_handler/web/controller.ex +++ b/lib/ha_handler/web/controller.ex @@ -1,7 +1,7 @@ defmodule HAHandler.Web.Controller do import Plug.Conn - alias HAHandler.HAProxy + alias HAHandler.{HAProxy, PGSQL} @templates_dir "lib/ha_handler/web/templates" @@ -22,6 +22,7 @@ defmodule HAHandler.Web.Controller do assigns = [ haproxy_stats: stats, + pgsql_status: PGSQL.status(), hostname: hostname, otp_app: HAHandler.otp_app(), version: HAHandler.version(), diff --git a/lib/ha_handler/web/templates/index.html.eex b/lib/ha_handler/web/templates/index.html.eex index 3e62aa7..629d172 100644 --- a/lib/ha_handler/web/templates/index.html.eex +++ b/lib/ha_handler/web/templates/index.html.eex @@ -98,6 +98,29 @@ <% end %> + +
+ +

PostgreSQL

+ + + + + + + + + + + <%= for entry <- pgsql_status do %> + + + + + + <% end %> + +
HostnameVersionStatus
<%= entry[:hostname] %><%= entry[:version] %><%= entry[:status] %>
diff --git a/mix.exs b/mix.exs index a2f0e67..acce1c6 100644 --- a/mix.exs +++ b/mix.exs @@ -26,7 +26,8 @@ defmodule HAHandler.MixProject do {:plug, "~> 1.12"}, {:plug_cowboy, "~> 2.5"}, {:procket, "~> 0.9"}, - {:poison, "~> 5.0"} + {:poison, "~> 5.0"}, + {:postgrex, "~> 0.16.1"} ] end diff --git a/mix.lock b/mix.lock index 387ec64..f69c45a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,13 +1,17 @@ %{ "afunix": {:git, "https://github.com/tonyrog/afunix.git", "d7baab77d741d49bce08de2aeb28c5f192ab13d8", []}, + "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, + "db_connection": {:hex, :db_connection, "2.4.1", "6411f6e23f1a8b68a82fa3a36366d4881f21f47fc79a9efb8c615e62050219da", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ea36d226ec5999781a9a8ad64e5d8c4454ecedc7a4d643e4832bf08efca01f00"}, + "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, "mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"}, "plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"}, "plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"}, "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"}, "poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"}, + "postgrex": {:hex, :postgrex, "0.16.1", "f94628a32c571266f53cd1e5fca705e626e2417bf1eee6f868985d14e874160a", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "6b225df32c857b9430619dbe30200a7ae664e23415a771ae9209396ee8eeee64"}, "procket": {:hex, :procket, "0.9.5", "ce91c00cb3f4d627fb3827f46ce68e6a969ea265df1afb349c300a07be4bb16c", [:rebar3], [], "hexpm", "1288bba5eb6f08ea5df3dda9e051bdac8f4b1e50adcca5cf4d726e825ad81bed"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "sweet_xml": {:hex, :sweet_xml, "0.7.2", "4729f997286811fabdd8288f8474e0840a76573051062f066c4b597e76f14f9f", [:mix], [], "hexpm", "6894e68a120f454534d99045ea3325f7740ea71260bc315f82e29731d570a6e8"},