Skip to content

Commit fc50cef

Browse files
author
Adriano Santos
committed
feat: added clustering for spawn monitor
1 parent 54afd57 commit fc50cef

File tree

24 files changed

+511
-0
lines changed

24 files changed

+511
-0
lines changed

spawn_monitor/.formatter.exs

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Used by "mix format"
2+
[
3+
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
4+
]

spawn_monitor/.gitignore

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# The directory Mix will write compiled artifacts to.
2+
/_build/
3+
4+
# If you run "mix test --cover", coverage assets end up here.
5+
/cover/
6+
7+
# The directory Mix downloads your dependencies sources to.
8+
/deps/
9+
10+
# Where third-party dependencies like ExDoc output generated docs.
11+
/doc/
12+
13+
# If the VM crashes, it generates a dump, let's ignore it too.
14+
erl_crash.dump
15+
16+
# Also ignore archive artifacts (built via "mix archive.build").
17+
*.ez
18+
19+
# Ignore package tarball (built via "mix hex.build").
20+
spawn_monitor-*.tar
21+
22+
# Temporary files, for example, from tests.
23+
/tmp/

spawn_monitor/README.md

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# SpawnMonitor
2+
3+
**TODO: Add description**
4+
5+
## Installation
6+
7+
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
8+
by adding `spawn_monitor` to your list of dependencies in `mix.exs`:
9+
10+
```elixir
11+
def deps do
12+
[
13+
{:spawn_monitor, "~> 0.1.0"}
14+
]
15+
end
16+
```
17+
18+
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
19+
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
20+
be found at <https://hexdocs.pm/spawn_monitor>.
21+

spawn_monitor/config/config.exs

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import Config
2+
3+
config :spawn_monitor,
4+
halt_on_abort: true,
5+
namespace: SpawnMonitor
6+
7+
# Configures the endpoint
8+
config :spawn_monitor, SpawnMonitorWeb.Endpoint,
9+
url: [host: "localhost"],
10+
render_errors: [view: SpawnMonitorWeb.ErrorView, accepts: ~w(json), layout: false],
11+
pubsub_server: SpawnMonitor.PubSub,
12+
live_view: [signing_salt: "kA47bW1N"]
13+
14+
# Configures Elixir's Logger
15+
config :logger, :console,
16+
format: "$time $metadata[$level] $message\n",
17+
metadata: [:request_id]
18+
19+
# Use Jason for JSON parsing in Phoenix
20+
config :phoenix, :json_library, Jason
21+
22+
# Import environment specific config. This must remain at the bottom
23+
# of this file so it overrides the configuration defined above.
24+
import_config "#{config_env()}.exs"

spawn_monitor/config/dev.exs

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import Config
2+
3+
# For development, we disable any cache and enable
4+
# debugging and code reloading.
5+
#
6+
# The watchers configuration can be used to run external
7+
# watchers to your application. For example, we use it
8+
# with esbuild to bundle .js and .css sources.
9+
config :spawn_monitor, SpawnMonitorWeb.Endpoint,
10+
debug_errors: true,
11+
code_reloader: true,
12+
check_origin: false,
13+
secret_key_base: "1lFra0dpD7ayAn4I3NANdZTKZyd2ecunwvTQzKw+dIBsDZElo3i4cvRLhee3F/VL",
14+
watchers: []
15+
16+
config :spawn_monitor, :cookie, :my_cookie
17+
18+
# Do not include metadata nor timestamps in development logs
19+
config :logger, :console, format: "[$level] $message\n"
20+
21+
# Set a higher stacktrace during development. Avoid configuring such
22+
# in production as building large stacktraces may be expensive.
23+
config :phoenix, :stacktrace_depth, 20
24+
25+
# Initialize plugs at runtime for faster development compilation
26+
config :phoenix, :plug_init_mode, :runtime

spawn_monitor/config/prod.exs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import Config
2+
3+
# For production, don't forget to configure the url host
4+
# to something meaningful, Phoenix uses this information
5+
# when generating URLs.
6+
#
7+
# Note we also include the path to a cache manifest
8+
# containing the digested version of static files. This
9+
# manifest is generated by the `mix phx.digest` task,
10+
# which you should run after static files are built and
11+
# before starting your production server.
12+
config :spawn_monitor, SpawnMonitorWeb.Endpoint, server: true
13+
14+
config :logger, level: :info

spawn_monitor/config/runtime.exs

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import Config
2+
3+
# config/runtime.exs is executed for all environments, including
4+
# during releases. It is executed after compilation and before the
5+
# system starts, so it is typically used to load production configuration
6+
# and secrets from environment variables or elsewhere. Do not define
7+
# any compile-time configuration in here, as it won't be applied.
8+
# The block below contains prod specific runtime configuration.
9+
if config_env() == :prod do
10+
secret_key_base =
11+
System.get_env("SPAWN_MONITOR_SECRET_KEY_BASE") ||
12+
Base.encode64(:crypto.strong_rand_bytes(48))
13+
14+
config :spawn_monitor, SpawnMonitorWeb.Endpoint,
15+
http: [
16+
# Enable IPv6 and bind on all interfaces.
17+
# Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access.
18+
# See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html
19+
# for details about using IPv6 vs IPv4 and loopback vs public addresses.
20+
ip: {0, 0, 0, 0, 0, 0, 0, 0},
21+
port: 8090
22+
],
23+
secret_key_base: secret_key_base
24+
25+
# ## Using releases
26+
#
27+
# If you are doing OTP releases, you need to instruct Phoenix
28+
# to start each relevant endpoint:
29+
#
30+
# config :spawn_monitor, SpawnMonitorWeb.Endpoint, server: true
31+
#
32+
# Then you can assemble a release by calling `mix release`.
33+
# See `mix help release` for more information.
34+
end

spawn_monitor/config/test.exs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import Config
2+
3+
# We don't run a server during test. If one is required,
4+
# you can enable the server option below.
5+
config :spawn_monitor, SpawnMonitorWeb.Endpoint,
6+
http: [ip: {127, 0, 0, 1}, port: 4002],
7+
secret_key_base: "4uFwNlOnyppVcDWeVcNUgGPOYQD+y7F4mTkconBPSqAleqXvd2wmmcLCqXJanfon",
8+
server: false
9+
10+
config :spawn_monitor,
11+
cookie: :"my-plds-test-cookie",
12+
ensure_distribution?: false,
13+
halt_on_abort: false
14+
15+
# Print only warnings and errors during test
16+
config :logger, level: :warning
17+
18+
# Initialize plugs at runtime for faster test compilation
19+
config :phoenix, :plug_init_mode, :runtime

spawn_monitor/lib/spawn_monitor.ex

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
defmodule SpawnMonitor do
2+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
defmodule SpawnMonitor.Application do
2+
@moduledoc false
3+
use Application
4+
require Logger
5+
6+
@impl true
7+
def start(_type, _args) do
8+
children = [
9+
SpawnMonitor.Cluster.get_spec(),
10+
{Phoenix.PubSub, name: SpawnMonitor.PubSub},
11+
SpawnMonitorWeb.Endpoint
12+
]
13+
14+
opts = [strategy: :one_for_one, name: SpawnMonitor.Supervisor]
15+
Supervisor.start_link(children, opts)
16+
end
17+
18+
@impl true
19+
def config_change(changed, _new, removed) do
20+
SpawnMonitorWeb.Endpoint.config_change(changed, removed)
21+
:ok
22+
end
23+
end
+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
defmodule SpawnMonitor.Cluster do
2+
@moduledoc false
3+
require Logger
4+
5+
import SpawnMonitor.Utils
6+
7+
def get_spec() do
8+
cluster_strategy = env("PROXY_CLUSTER_STRATEGY", "gossip")
9+
10+
topologies =
11+
case cluster_strategy do
12+
"epmd" ->
13+
get_epmd_strategy()
14+
15+
"gossip" ->
16+
get_gossip_strategy()
17+
18+
"kubernetes-dns" ->
19+
get_k8s_dns_strategy()
20+
21+
_ ->
22+
Logger.warning("Invalid Topology")
23+
end
24+
25+
if topologies && Code.ensure_compiled(Cluster.Supervisor) do
26+
Logger.debug("Cluster topology #{inspect(topologies)}")
27+
28+
{Cluster.Supervisor,
29+
[
30+
topologies,
31+
[name: String.to_atom("#{__MODULE__}.Cluster")]
32+
]}
33+
end
34+
end
35+
36+
defp get_epmd_strategy() do
37+
[
38+
proxy: [
39+
strategy: Cluster.Strategy.Epmd
40+
]
41+
]
42+
end
43+
44+
defp get_gossip_strategy() do
45+
reuseaddr =
46+
env("PROXY_CLUSTER_GOSSIP_REUSE_ADDRESS", "true")
47+
|> to_bool()
48+
49+
broadcast_only =
50+
env("PROXY_CLUSTER_GOSSIP_BROADCAST_ONLY", "true")
51+
|> to_bool()
52+
53+
[
54+
proxy: [
55+
strategy: Cluster.Strategy.Gossip,
56+
config: [
57+
reuseaddr: reuseaddr,
58+
multicast_addr: env("PROXY_CLUSTER_GOSSIP_MULTICAST_ADDRESS", "255.255.255.255"),
59+
broadcast_only: broadcast_only
60+
]
61+
]
62+
]
63+
end
64+
65+
defp get_k8s_dns_strategy() do
66+
polling_interval =
67+
env("PROXY_CLUSTER_POLLING", "3000")
68+
|> String.to_integer()
69+
70+
[
71+
proxy: [
72+
strategy: Cluster.Strategy.Kubernetes.DNS,
73+
config: [
74+
service: env("PROXY_HEADLESS_SERVICE", "proxy-headless"),
75+
application_name: "proxy",
76+
polling_interval: polling_interval
77+
]
78+
]
79+
]
80+
end
81+
end
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
defmodule SpawnMonitor.Utils do
2+
@moduledoc false
3+
4+
def env(key, default) when is_binary(key), do: System.get_env(key, default)
5+
6+
def to_bool("false"), do: false
7+
def to_bool("true"), do: true
8+
def to_bool(_), do: false
9+
end
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
defmodule SpawnMonitorWeb do
2+
@moduledoc false
3+
4+
def controller do
5+
quote do
6+
use Phoenix.Controller, namespace: SpawnMonitorWeb
7+
import Plug.Conn
8+
end
9+
end
10+
11+
def view do
12+
quote do
13+
use Phoenix.View,
14+
root: "lib/spawn_monitor_web/templates",
15+
namespace: SpawnMonitorWeb
16+
17+
import Phoenix.Controller,
18+
only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]
19+
20+
unquote(view_helpers())
21+
end
22+
end
23+
24+
def router do
25+
quote do
26+
use Phoenix.Router
27+
28+
import Plug.Conn
29+
import Phoenix.Controller
30+
end
31+
end
32+
33+
defp view_helpers do
34+
quote do
35+
import Phoenix.View
36+
end
37+
end
38+
39+
@doc """
40+
When used, dispatch to the appropriate controller/view/etc.
41+
"""
42+
defmacro __using__(which) when is_atom(which) do
43+
apply(__MODULE__, which, [])
44+
end
45+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
defmodule SpawnMonitorWeb.HealthController do
2+
@moduledoc false
3+
use SpawnMonitorWeb, :controller
4+
5+
def index(conn, _) do
6+
version = Application.spec(:spawn_monitor, :vsn) |> List.to_string()
7+
8+
json(conn, %{
9+
"application" => "spawn_monitor",
10+
"version" => version
11+
})
12+
end
13+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
defmodule SpawnMonitorWeb.Endpoint do
2+
@moduledoc false
3+
use Phoenix.Endpoint, otp_app: :spawn_monitor
4+
5+
@session_options [
6+
store: :cookie,
7+
key: "_spawn_monitor_key",
8+
signing_salt: "LVKEVz/+"
9+
]
10+
11+
socket("/live", Phoenix.LiveView.Socket,
12+
# Don't check the origin as we don't know how the web app is gonna be accessed.
13+
# It runs locally, but may be exposed via IP or domain name.
14+
# The WebSocket connection is already protected from CSWSH by using CSRF token.
15+
websocket: [check_origin: false, connect_info: [:user_agent, session: @session_options]]
16+
)
17+
18+
if code_reloading? do
19+
plug(Phoenix.CodeReloader)
20+
end
21+
22+
plug(Phoenix.LiveDashboard.RequestLogger,
23+
param_key: "request_logger",
24+
cookie_key: "request_logger"
25+
)
26+
27+
plug(Plug.MethodOverride)
28+
plug(Plug.Head)
29+
plug(Plug.Session, @session_options)
30+
plug(SpawnMonitorWeb.Router)
31+
end

0 commit comments

Comments
 (0)