From ebcfabdbd2e7e85911ad088e6d4bcbef6414213c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Wed, 23 Feb 2022 17:53:00 +0100 Subject: [PATCH] Set editorconfig, format whole codebase --- .editorconfig | 12 ++ lib/ha_handler.ex | 5 +- lib/ha_handler/application.ex | 3 +- lib/ha_handler/control.ex | 150 ++++++++------ lib/ha_handler/ha_proxy.ex | 61 +++--- lib/ha_handler/pgsql.ex | 85 ++++---- lib/ha_handler/pgsql/supervisor.ex | 33 +-- lib/ha_handler/pgsql/watcher.ex | 44 ++-- lib/ha_handler/web/controller.ex | 49 +++-- lib/ha_handler/web/router.ex | 51 ++--- lib/ha_handler/web/templates/index.html.eex | 216 ++++++++++---------- 11 files changed, 380 insertions(+), 329 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..888d407 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*.ex] +indent_style = space +indent_size = 2 + +[*.eex] +indent_style = space +indent_size = 2 diff --git a/lib/ha_handler.ex b/lib/ha_handler.ex index c2252c6..056d944 100644 --- a/lib/ha_handler.ex +++ b/lib/ha_handler.ex @@ -6,8 +6,8 @@ defmodule HAHandler do # Mix is not available in releases, and these things are static # anyway (@variables are evaluated at compile time). @otp_app Mix.Project.config()[:app] - @version Mix.Project.config[:version] - @env Mix.env + @version Mix.Project.config()[:version] + @env Mix.env() def http_port, do: Application.get_env(@otp_app, :http_port) def haproxy_socket, do: Application.get_env(@otp_app, :haproxy_socket) @@ -26,6 +26,7 @@ defmodule HAHandler do from: acme_challenge_path() ] end + def assets_static_config() do [ at: "/static", diff --git a/lib/ha_handler/application.ex b/lib/ha_handler/application.ex index 725967f..139126d 100644 --- a/lib/ha_handler/application.ex +++ b/lib/ha_handler/application.ex @@ -10,7 +10,8 @@ defmodule HAHandler.Application do @impl true def start(_type, _args) do children = [ - {Plug.Cowboy, scheme: :http, plug: HAHandler.Web.Router, options: [port: HAHandler.http_port()]}, + {Plug.Cowboy, + scheme: :http, plug: HAHandler.Web.Router, options: [port: HAHandler.http_port()]}, {HAHandler.PGSQL.Supervisor, HAHandler.pgsql_instances()}, {HAHandler.Control, []} ] diff --git a/lib/ha_handler/control.ex b/lib/ha_handler/control.ex index a602734..20ec667 100644 --- a/lib/ha_handler/control.ex +++ b/lib/ha_handler/control.ex @@ -1,82 +1,104 @@ defmodule HAHandler.Control do - @moduledoc """ - This module handles the decision-logic and actions to be - taken regarding the current state of the infrastructure. - """ + @moduledoc """ + This module handles the decision-logic and actions to be + taken regarding the current state of the infrastructure. + """ - @haproxy_pgsql_backend "pgsql" + @haproxy_pgsql_backend "pgsql" - use GenServer + use GenServer - require Logger + require Logger - alias HAHandler.{PGSQL, HAProxy} + alias HAHandler.{PGSQL, HAProxy} - # How much do we wait (ms) between each check/decision-making round? - @refresh 15_000 + # How much do we wait (ms) between each check/decision-making round? + @refresh 15_000 - def start_link(opts) do - GenServer.start_link(__MODULE__, opts, name: __MODULE__) - end + def start_link(opts) do + GenServer.start_link(__MODULE__, opts, name: __MODULE__) + end - @impl true - def init(_opts) do - state = [] + @impl true + def init(_opts) do + state = [] - # Let's skip the initial startup round so that other components are all up - # and running. - Process.send_after self(), :sync, @refresh + # Let's skip the initial startup round so that other components are all up + # and running. + Process.send_after(self(), :sync, @refresh) - {:ok, state} - end + {:ok, state} + end - @impl true - def handle_info(:sync, state) do - Logger.debug("Executing control logic.") + @impl true + def handle_info(:sync, state) do + Logger.debug("Executing control logic.") - # Fetch PGSQL state, make sure HAProxy routes to the master - # process. - pgsql_state = PGSQL.get_instances() - |> Enum.map(fn {hostname, pid} = instance-> - haproxy_server = HAHandler.pgsql_instances() - |> Enum.filter(fn opts -> Keyword.get(opts, :hostname) == hostname end) - |> Enum.at(0) - |> Keyword.get(:haproxy_server) + # Fetch PGSQL state, make sure HAProxy routes to the master + # process. + pgsql_state = + PGSQL.get_instances() + |> Enum.map(fn {hostname, pid} = instance -> + haproxy_server = + HAHandler.pgsql_instances() + |> Enum.filter(fn opts -> Keyword.get(opts, :hostname) == hostname end) + |> Enum.at(0) + |> Keyword.get(:haproxy_server) - %{ - haproxy_server: haproxy_server, - pgsql_watcher_pid: pid, - pgsql_operation_mode: PGSQL.get_operation_mode(instance) - } - end) - haproxy_state = HAProxy.get_stats() - |> Map.get("Server", []) - |> Enum.filter(fn mapping -> mapping["pxname"] == @haproxy_pgsql_backend end) - |> Enum.map(fn mapping -> %{mapping["svname"] => mapping["status"]} end) - |> Enum.reduce(&Map.merge/2) + %{ + haproxy_server: haproxy_server, + pgsql_watcher_pid: pid, + pgsql_operation_mode: PGSQL.get_operation_mode(instance) + } + end) - for pgsql_instance <- pgsql_state do - haproxy_state = Map.get(haproxy_state, pgsql_instance.haproxy_server) + haproxy_state = + HAProxy.get_stats() + |> Map.get("Server", []) + |> Enum.filter(fn mapping -> mapping["pxname"] == @haproxy_pgsql_backend end) + |> Enum.map(fn mapping -> %{mapping["svname"] => mapping["status"]} end) + |> Enum.reduce(&Map.merge/2) - case {pgsql_instance.pgsql_operation_mode, haproxy_state} do - {:primary, "UP"} -> - :noop - {:primary, "MAINT"} -> - Logger.info("Enabling routing PGSQL to (now) primary #{pgsql_instance.haproxy_server}.") - HAProxy.set_server(@haproxy_pgsql_backend, pgsql_instance.haproxy_server, "state", "ready") - {:secondary, "UP"} -> - Logger.info("Disabling routing PGSQL to (now) secondary #{pgsql_instance.haproxy_server}.") - HAProxy.set_server(@haproxy_pgsql_backend, pgsql_instance.haproxy_server, "state", "maint") - {:secondary, "MAINT"} -> - :noop - unknown -> - Logger.warning("Unhandled PGSQL/HAProxy state: #{inspect(unknown)}") - end - end + for pgsql_instance <- pgsql_state do + haproxy_state = Map.get(haproxy_state, pgsql_instance.haproxy_server) - # Schedule next round. - Process.send_after self(), :sync, @refresh + case {pgsql_instance.pgsql_operation_mode, haproxy_state} do + {:primary, "UP"} -> + :noop - {:noreply, state} - end + {:primary, "MAINT"} -> + Logger.info("Enabling routing PGSQL to (now) primary #{pgsql_instance.haproxy_server}.") + + HAProxy.set_server( + @haproxy_pgsql_backend, + pgsql_instance.haproxy_server, + "state", + "ready" + ) + + {:secondary, "UP"} -> + Logger.info( + "Disabling routing PGSQL to (now) secondary #{pgsql_instance.haproxy_server}." + ) + + HAProxy.set_server( + @haproxy_pgsql_backend, + pgsql_instance.haproxy_server, + "state", + "maint" + ) + + {:secondary, "MAINT"} -> + :noop + + unknown -> + Logger.warning("Unhandled PGSQL/HAProxy state: #{inspect(unknown)}") + end + end + + # Schedule next round. + Process.send_after(self(), :sync, @refresh) + + {:noreply, state} + end end diff --git a/lib/ha_handler/ha_proxy.ex b/lib/ha_handler/ha_proxy.ex index d753585..cfb313c 100644 --- a/lib/ha_handler/ha_proxy.ex +++ b/lib/ha_handler/ha_proxy.ex @@ -33,28 +33,31 @@ defmodule HAHandler.HAProxy do # run out - which will block all operations. close_socket(fd) data + {:error, err} -> {:error, err} end end defp extract_stats(data) do - extracted = for entry <- data do - for mapping <- entry do - case mapping do - %{ - "id" => id, - "proxyId" => proxy_id, - "objType" => type, - "field" => %{"name" => name}, - "value" => %{"value" => value}, - } -> - %{:id => id, :proxy_id => proxy_id, :type => type, :field => name, :value => value} - _ -> - nil + extracted = + for entry <- data do + for mapping <- entry do + case mapping do + %{ + "id" => id, + "proxyId" => proxy_id, + "objType" => type, + "field" => %{"name" => name}, + "value" => %{"value" => value} + } -> + %{:id => id, :proxy_id => proxy_id, :type => type, :field => name, :value => value} + + _ -> + nil + end end end - end extracted |> List.flatten() @@ -62,18 +65,14 @@ defmodule HAHandler.HAProxy do fn mapping -> {mapping.type, mapping.id, mapping.proxy_id} end, fn mapping -> %{mapping.field => mapping.value} end ) - |> Enum.map( - fn {{type, id, proxy_id}, grouped_mappings} -> - grouped_mappings - |> Enum.reduce(fn l, r -> Map.merge(l,r) end) - |> Map.put("type", type) - |> Map.put("id", id) - |> Map.put("proxy_id", proxy_id) - end - ) - |> Enum.group_by( - fn entry -> Map.get(entry, "type") end - ) + |> Enum.map(fn {{type, id, proxy_id}, grouped_mappings} -> + grouped_mappings + |> Enum.reduce(fn l, r -> Map.merge(l, r) end) + |> Map.put("type", type) + |> Map.put("id", id) + |> Map.put("proxy_id", proxy_id) + end) + |> Enum.group_by(fn entry -> Map.get(entry, "type") end) end @doc """ @@ -81,7 +80,8 @@ defmodule HAHandler.HAProxy do a list of Maps. """ def get_stats(opts \\ []) - def get_stats([hide_error: true]) do + + def get_stats(hide_error: true) do case get_stats() do {:error, _err} -> %{ @@ -89,9 +89,12 @@ defmodule HAHandler.HAProxy do "Backend" => %{}, "Server" => %{} } - stats -> stats + + stats -> + stats end end + def get_stats(_opts) do case execute("show stat json") do {:ok, raw} -> @@ -117,8 +120,10 @@ defmodule HAHandler.HAProxy do case execute("set server #{backend}/#{server} #{key} #{value}") do {:ok, ""} -> :ok + {:ok, err} -> {:error, err} + {:error, err} -> {:error, err} end diff --git a/lib/ha_handler/pgsql.ex b/lib/ha_handler/pgsql.ex index c982de1..d8c131a 100644 --- a/lib/ha_handler/pgsql.ex +++ b/lib/ha_handler/pgsql.ex @@ -1,45 +1,52 @@ defmodule HAHandler.PGSQL do - @supervisor HAHandler.PGSQL.Supervisor - @version_query "SELECT version();" - @is_in_recovery_query "SELECT pg_is_in_recovery();" + @supervisor HAHandler.PGSQL.Supervisor + @version_query "SELECT version();" + @is_in_recovery_query "SELECT pg_is_in_recovery();" - def get_instances() do - watchers = Supervisor.which_children(@supervisor) - for {hostname, pid, _type, _modules} <- watchers do - {hostname, pid} - end - end + def get_instances() do + watchers = Supervisor.which_children(@supervisor) - def get_version({hostname, pid}) do - case GenServer.call(pid, {:execute, @version_query, []}) 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: :unknown} - end - end + for {hostname, pid, _type, _modules} <- watchers do + {hostname, pid} + end + end - def get_operation_mode({_hostname, pid}) do - case GenServer.call(pid, {:execute, @is_in_recovery_query, []}) do - {:ok, %Postgrex.Result{rows: [[false]]}} -> - :primary - {:ok, %Postgrex.Result{rows: [[true]]}} -> - :secondary - _ -> - :unknown - end - end + def get_version({hostname, pid}) do + case GenServer.call(pid, {:execute, @version_query, []}) 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 - def get_stats() do - get_instances() - |> Enum.map(fn instance -> - get_version(instance) |> Map.put(:mode, get_operation_mode(instance)) - end) - 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: :unknown} + end + end + + def get_operation_mode({_hostname, pid}) do + case GenServer.call(pid, {:execute, @is_in_recovery_query, []}) do + {:ok, %Postgrex.Result{rows: [[false]]}} -> + :primary + + {:ok, %Postgrex.Result{rows: [[true]]}} -> + :secondary + + _ -> + :unknown + end + end + + def get_stats() do + get_instances() + |> Enum.map(fn instance -> + get_version(instance) |> Map.put(:mode, get_operation_mode(instance)) + end) + end end diff --git a/lib/ha_handler/pgsql/supervisor.ex b/lib/ha_handler/pgsql/supervisor.ex index 6d7a1d0..89cca46 100644 --- a/lib/ha_handler/pgsql/supervisor.ex +++ b/lib/ha_handler/pgsql/supervisor.ex @@ -1,22 +1,23 @@ defmodule HAHandler.PGSQL.Supervisor do - use Supervisor + use Supervisor - alias HAHandler.PGSQL.Watcher, as: PGSQLWatcher + alias HAHandler.PGSQL.Watcher, as: PGSQLWatcher - def start_link(opts) do - Supervisor.start_link(__MODULE__, opts, name: __MODULE__) - end + 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 -> - %{ - id: Keyword.get(conf, :hostname), - start: {PGSQLWatcher, :start_link, [conf]} - } - end) + @impl true + def init(instances) do + children = + Enum.map(instances, fn conf -> + %{ + id: Keyword.get(conf, :hostname), + start: {PGSQLWatcher, :start_link, [conf]} + } + end) - opts = [ strategy: :one_for_one ] - Supervisor.init(children, opts) - 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 index b532851..ef17bd6 100644 --- a/lib/ha_handler/pgsql/watcher.ex +++ b/lib/ha_handler/pgsql/watcher.ex @@ -1,29 +1,29 @@ defmodule HAHandler.PGSQL.Watcher do - use GenServer - require Logger + use GenServer + require Logger - def start_link(opts) do - GenServer.start_link(__MODULE__, opts) - end + def start_link(opts) do + GenServer.start_link(__MODULE__, opts) + 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) + @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) - } + state = %{ + backend: pid, + hostname: Keyword.get(opts, :hostname) + } - {:ok, state} - end + {:ok, state} + end - @impl true - def handle_call({:execute, query, params}, _from, %{backend: backend} = state) do - {:reply, Postgrex.query(backend, query, params), state} - end + @impl true + def handle_call({:execute, query, params}, _from, %{backend: backend} = state) do + {:reply, Postgrex.query(backend, query, params), state} + end end diff --git a/lib/ha_handler/web/controller.ex b/lib/ha_handler/web/controller.ex index 68bcf1c..48756e8 100644 --- a/lib/ha_handler/web/controller.ex +++ b/lib/ha_handler/web/controller.ex @@ -1,35 +1,34 @@ defmodule HAHandler.Web.Controller do - import Plug.Conn + import Plug.Conn - alias HAHandler.{HAProxy, PGSQL} + alias HAHandler.{HAProxy, PGSQL} @template_dir "lib/ha_handler/web/templates" - @index_template EEx.compile_file( - Path.join(@template_dir, "index.html.eex") - ) + @index_template EEx.compile_file(Path.join(@template_dir, "index.html.eex")) - defp render(conn, template, assigns) do - {body, _binding} = Code.eval_quoted(template, assigns) + defp render(conn, template, assigns) do + {body, _binding} = Code.eval_quoted(template, assigns) - conn - |> put_resp_content_type("text/html") - |> send_resp(200, body) - end + conn + |> put_resp_content_type("text/html") + |> send_resp(200, body) + end - def index(conn) do - {:ok, hostname} = :net_adm.dns_hostname(:net_adm.localhost) + def index(conn) do + {:ok, hostname} = :net_adm.dns_hostname(:net_adm.localhost()) - haproxy_stats = HAProxy.get_stats([hide_error: true]) - pgsql_stats = PGSQL.get_stats() + haproxy_stats = HAProxy.get_stats(hide_error: true) + pgsql_stats = PGSQL.get_stats() - assigns = [ - haproxy_stats: haproxy_stats, - pgsql_status: pgsql_stats, - hostname: hostname, - otp_app: HAHandler.otp_app(), - version: HAHandler.version(), - env: HAHandler.env() - ] - render(conn, @index_template, assigns) - end + assigns = [ + haproxy_stats: haproxy_stats, + pgsql_status: pgsql_stats, + hostname: hostname, + otp_app: HAHandler.otp_app(), + version: HAHandler.version(), + env: HAHandler.env() + ] + + render(conn, @index_template, assigns) + end end diff --git a/lib/ha_handler/web/router.ex b/lib/ha_handler/web/router.ex index 1572c14..d5c2d13 100644 --- a/lib/ha_handler/web/router.ex +++ b/lib/ha_handler/web/router.ex @@ -1,35 +1,38 @@ defmodule HAHandler.Web.Router do - @moduledoc """ - This module dispatch incoming HTTP requests to their - related logic. Please refer to [1] for details. + @moduledoc """ + This module dispatch incoming HTTP requests to their + related logic. Please refer to [1] for details. - [1] https://hexdocs.pm/plug/Plug.Router.html#content - """ + [1] https://hexdocs.pm/plug/Plug.Router.html#content + """ - use Plug.Router + use Plug.Router - alias HAHandler.Web.Controller + alias HAHandler.Web.Controller - # Note for plugs: oder is important, as a plug may stop - # want to stop the pipeline! + # Note for plugs: oder is important, as a plug may stop + # want to stop the pipeline! - plug Plug.Logger, log: :debug + plug(Plug.Logger, log: :debug) - # We use replug to allow for runtime configuration in release (as macros such - # as the `plug` call ae evaluated are compile-time). - plug Replug, - plug: Plug.Static, - opts: {HAHandler, :acme_challenges_static_config} - plug Replug, - plug: Plug.Static, - opts: {HAHandler, :assets_static_config} + # We use replug to allow for runtime configuration in release (as macros such + # as the `plug` call ae evaluated are compile-time). + plug(Replug, + plug: Plug.Static, + opts: {HAHandler, :acme_challenges_static_config} + ) - plug :match - plug :dispatch + plug(Replug, + plug: Plug.Static, + opts: {HAHandler, :assets_static_config} + ) - get "/", do: Controller.index(conn) + plug(:match) + plug(:dispatch) - match _ do - send_resp(conn, 404, "Not found") - end + get("/", do: Controller.index(conn)) + + match _ do + send_resp(conn, 404, "Not found") + end end diff --git a/lib/ha_handler/web/templates/index.html.eex b/lib/ha_handler/web/templates/index.html.eex index 4d2bbf4..28fcb32 100644 --- a/lib/ha_handler/web/templates/index.html.eex +++ b/lib/ha_handler/web/templates/index.html.eex @@ -1,124 +1,124 @@ - - [HA] <%= hostname %> - - - -
-
- -
+ + [HA] <%= hostname %> + + + +
+
+ +
-

Recycled Cloud HA handler

+

Recycled Cloud HA handler

-

- This service supervises the various components of - the Recycled Cloud's High Availability - infrastruture. Documentation and source code can be - found on our - software forge. -

+

+ This service supervises the various components of + the Recycled Cloud's High Availability + infrastruture. Documentation and source code can be + found on our + software forge. +

-
+
-

Handler

+

Handler

- <%= otp_app %> v<%= version %> (<%= env %>) running on <%= hostname %> + <%= otp_app %> v<%= version %> (<%= env %>) running on <%= hostname %> -
+
-

HAProxy

+

HAProxy

-

Frontends

- - - - - - - - - - - <%= for entry <- Map.get(haproxy_stats, "Frontend") do %> - - - - - - - <% end %> - -
NameStatusBytes inBytes out
<%= entry["pxname"] %><%= entry["status"] %><%= entry["bin"] %><%= entry["bout"] %>
+

Frontends

+ + + + + + + + + + + <%= for entry <- Map.get(haproxy_stats, "Frontend") do %> + + + + + + + <% end %> + +
NameStatusBytes inBytes out
<%= entry["pxname"] %><%= entry["status"] %><%= entry["bin"] %><%= entry["bout"] %>
-

Backends

- - - - - - - - - - <%= for entry <- Map.get(haproxy_stats, "Backend") do %> - - - - - - <% end %> - -
NameStatusalgo
<%= entry["pxname"] %><%= entry["status"] %><%= entry["algo"] %>
+

Backends

+ + + + + + + + + + <%= for entry <- Map.get(haproxy_stats, "Backend") do %> + + + + + + <% end %> + +
NameStatusalgo
<%= entry["pxname"] %><%= entry["status"] %><%= entry["algo"] %>
-

Servers

- - - - - - - - - - - <%= for entry <- Map.get(haproxy_stats, "Server") do %> - - - - - - - <% end %> - -
NameStatusModeAddress
<%= entry["pxname"] %>/<%= entry["svname"] %><%= entry["status"] %><%= entry["mode"] %><%= entry["addr"] %>
+

Servers

+ + + + + + + + + + + <%= for entry <- Map.get(haproxy_stats, "Server") do %> + + + + + + + <% end %> + +
NameStatusModeAddress
<%= entry["pxname"] %>/<%= entry["svname"] %><%= entry["status"] %><%= entry["mode"] %><%= entry["addr"] %>
-
+
-

PostgreSQL

+

PostgreSQL

- - - - - - - - - - - <%= for entry <- pgsql_status do %> - - - - - - - <% end %> - -
HostnameVersionStatusOperation
<%= entry[:hostname] %><%= entry[:version] %><%= entry[:status] %><%= entry[:mode] %>
-
- + + + + + + + + + + + <%= for entry <- pgsql_status do %> + + + + + + + <% end %> + +
HostnameVersionStatusOperation
<%= entry[:hostname] %><%= entry[:version] %><%= entry[:status] %><%= entry[:mode] %>
+
+