diff --git a/.gitignore b/.gitignore index 19fe3da..2f7d2d7 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ erl_crash.dump # Static artifacts /node_modules +/assets/node_modules # Since we are building assets from web/static, # we ignore priv/static. You may want to comment diff --git a/config/prod.exs b/config/prod.exs index c1570e0..e019ac2 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -73,6 +73,12 @@ config :gigalixir_getting_started, GigalixirGettingStarted.Repo, config :libcluster, topologies: [ + gigalixir_example: [ + strategy: GigalixirGettingStarted.Libcluster.Strategy, + config: [ + url: "https://bar.gigalixirapp.com/nodes" + ] + ], k8s_example: [ strategy: Cluster.Strategy.Kubernetes, config: [ diff --git a/lib/gigalixir_getting_started/libcluster/strategy.ex b/lib/gigalixir_getting_started/libcluster/strategy.ex new file mode 100644 index 0000000..d3f7959 --- /dev/null +++ b/lib/gigalixir_getting_started/libcluster/strategy.ex @@ -0,0 +1,64 @@ +defmodule GigalixirGettingStarted.Libcluster.Strategy do + use GenServer + use Cluster.Strategy + import Cluster.Logger + + alias Cluster.Strategy.State + + @default_polling_interval 5_000 + + def start_link(opts), do: GenServer.start_link(__MODULE__, opts) + def init(opts) do + state = %State{ + topology: Keyword.fetch!(opts, :topology), + connect: Keyword.fetch!(opts, :connect), + disconnect: Keyword.fetch!(opts, :disconnect), + config: Keyword.fetch!(opts, :config), + meta: MapSet.new([]) + } + {:ok, state, 0} + end + + def handle_info(:timeout, state) do + handle_info(:load, state) + end + def handle_info(:load, %State{topology: topology, connect: connect, disconnect: disconnect} = state) do + new_nodelist = MapSet.new(get_nodes(state)) + added = MapSet.difference(new_nodelist, state.meta) + removed = MapSet.difference(state.meta, new_nodelist) + new_nodelist = case Cluster.Strategy.disconnect_nodes(topology, disconnect, MapSet.to_list(removed)) do + :ok -> + new_nodelist + {:error, bad_nodes} -> + # Add back the nodes which should have been removed, but which couldn't be for some reason + Enum.reduce(bad_nodes, new_nodelist, fn {n, _}, acc -> + MapSet.put(acc, n) + end) + end + new_nodelist = case Cluster.Strategy.connect_nodes(topology, connect, MapSet.to_list(added)) do + :ok -> + new_nodelist + {:error, bad_nodes} -> + # Remove the nodes which should have been added, but couldn't be for some reason + Enum.reduce(bad_nodes, new_nodelist, fn {n, _}, acc -> + MapSet.delete(acc, n) + end) + end + Process.send_after(self(), :load, Keyword.get(state.config, :polling_interval, @default_polling_interval)) + {:noreply, %{state | :meta => new_nodelist}} + end + def handle_info(_, state) do + {:noreply, state} + end + + @spec get_nodes(State.t) :: [atom()] + defp get_nodes(%State{topology: topology, config: config}) do + config + |> Keyword.get(:url) + |> HTTPoison.get! + |> Map.get(:body) + |> Poison.decode! + |> Map.get("data") + |> Enum.map(&String.to_atom/1) + end +end diff --git a/mix.exs b/mix.exs index 846ab11..5519d69 100644 --- a/mix.exs +++ b/mix.exs @@ -19,7 +19,7 @@ defmodule GigalixirGettingStarted.Mixfile do def application do [mod: {GigalixirGettingStarted, []}, applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext, - :phoenix_ecto, :postgrex, :libcluster, :ssl, :runtime_tools]] + :phoenix_ecto, :postgrex, :libcluster, :ssl, :runtime_tools, :httpoison]] end # Specifies which paths to compile per environment. @@ -36,6 +36,7 @@ defmodule GigalixirGettingStarted.Mixfile do {:postgrex, ">= 0.13.3"}, {:phoenix_html, "~> 2.6"}, {:phoenix_live_reload, "~> 1.0", only: :dev}, + {:httpoison, "~> 0.13"}, {:gettext, "~> 0.11"}, {:cowboy, "~> 1.0"}, {:libcluster, "~> 2.0.3"}, diff --git a/mix.lock b/mix.lock index f1e0008..ba50f60 100644 --- a/mix.lock +++ b/mix.lock @@ -1,4 +1,5 @@ -%{"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], []}, +%{"certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [], [], "hexpm"}, + "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], []}, "cowboy": {:hex, :cowboy, "1.0.4", "a324a8df9f2316c833a470d918aaf73ae894278b8aa6226ce7a9bf699388f878", [:make, :rebar], [{:cowlib, "~> 1.0.0", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.0", [hex: :ranch, optional: false]}]}, "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []}, "db_connection": {:hex, :db_connection, "1.1.2", "2865c2a4bae0714e2213a0ce60a1b12d76a6efba0c51fbda59c9ab8d1accc7a8", [], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, @@ -7,8 +8,13 @@ "ecto": {:hex, :ecto, "2.1.2", "8d7bde4170b33e1049850a5dbe0112bfd232824e63c73ee63383df4c6ffd4064", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, optional: true]}]}, "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], []}, "gettext": {:hex, :gettext, "0.13.0", "daafbddc5cda12738bb93b01d84105fe75b916a302f1c50ab9fb066b95ec9db4", [:mix], []}, + "hackney": {:hex, :hackney, "1.9.0", "51c506afc0a365868469dcfc79a9d0b94d896ec741cfd5bd338f49a5ec515bfe", [], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, + "httpoison": {:hex, :httpoison, "0.13.0", "bfaf44d9f133a6599886720f3937a7699466d23bb0cd7a88b6ba011f53c6f562", [], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "idna": {:hex, :idna, "5.1.0", "d72b4effeb324ad5da3cab1767cb16b17939004e789d8c0ad5b70f3cea20c89a", [], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, "libcluster": {:hex, :libcluster, "2.0.3", "be2b31161f14ff95c092431acfa5259d8e5d98be31dab634591f0033caca0848", [:mix], [{:poison, "~> 2.2", [hex: :poison, optional: false]}]}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [], [], "hexpm"}, "mime": {:hex, :mime, "1.0.1", "05c393850524767d13a53627df71beeebb016205eb43bfbd92d14d24ec7a1b51", [:mix], []}, + "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [], [], "hexpm"}, "phoenix": {:hex, :phoenix, "1.2.1", "6dc592249ab73c67575769765b66ad164ad25d83defa3492dc6ae269bd2a68ab", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.1", [hex: :plug, optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}]}, "phoenix_ecto": {:hex, :phoenix_ecto, "3.2.1", "6cf11d590c61977f50fcb98ad8a10ee90ba8670a82cbf5eaf49cfaee2e95e8b7", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, optional: false]}, {:plug, "~> 1.0", [hex: :plug, optional: false]}]}, "phoenix_html": {:hex, :phoenix_html, "2.9.2", "371160b30cf4e10443b015efce6f03e1f19aae98ff6487620477b13a5b2ef660", [:mix], [{:plug, "~> 1.0", [hex: :plug, optional: false]}]}, @@ -18,4 +24,6 @@ "poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []}, "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], []}, "postgrex": {:hex, :postgrex, "0.13.3", "c277cfb2a9c5034d445a722494c13359e361d344ef6f25d604c2353185682bfc", [], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}, - "ranch": {:hex, :ranch, "1.2.1", "a6fb992c10f2187b46ffd17ce398ddf8a54f691b81768f9ef5f461ea7e28c762", [:make], []}} + "ranch": {:hex, :ranch, "1.2.1", "a6fb992c10f2187b46ffd17ce398ddf8a54f691b81768f9ef5f461ea7e28c762", [:make], []}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [], [], "hexpm"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [], [], "hexpm"}} diff --git a/web/controllers/page_controller.ex b/web/controllers/page_controller.ex index 77b68e1..a1d4fda 100644 --- a/web/controllers/page_controller.ex +++ b/web/controllers/page_controller.ex @@ -4,4 +4,25 @@ defmodule GigalixirGettingStarted.PageController do def index(conn, _params) do render conn, "index.html" end + + def nodes(conn, params) do + json conn, %{ + data: Node.list ++ [Node.self] + } + end + + def connect(conn, params) do + result = params + |> Map.get("url") + |> HTTPoison.get! + |> Map.get(:body) + |> Poison.decode! + |> Map.get("data") + |> Enum.map(&String.to_atom/1) + |> Enum.map(&Node.connect/1) + + json conn, %{ + data: result + } + end end diff --git a/web/router.ex b/web/router.ex index 40135e8..cc1e175 100644 --- a/web/router.ex +++ b/web/router.ex @@ -17,6 +17,8 @@ defmodule GigalixirGettingStarted.Router do pipe_through :browser # Use the default browser stack get "/", PageController, :index + get "/nodes", PageController, :nodes + get "/connect", PageController, :connect end # Other scopes may use custom stacks.