From ce56d2b7befa444b808e005874a5985706d80d59 Mon Sep 17 00:00:00 2001 From: Eishun Kondoh Date: Fri, 9 Feb 2018 00:49:57 +0900 Subject: [PATCH] Add an example for leader election based multiple controller --- examples/leader_example/.formatter.exs | 4 + examples/leader_example/.gitignore | 24 ++++++ examples/leader_example/README.md | 21 +++++ examples/leader_example/config/config.exs | 21 +++++ examples/leader_example/config/dev.exs | 3 + examples/leader_example/config/node1.exs | 9 +++ examples/leader_example/config/node2.exs | 9 +++ .../lib/leader_example/application.ex | 20 +++++ .../lib/leader_example/leader.ex | 77 +++++++++++++++++++ examples/leader_example/mix.exs | 25 ++++++ examples/leader_example/mix.lock | 10 +++ .../test/leader_example_test.exs | 8 ++ examples/leader_example/test/test_helper.exs | 1 + lib/tres/message_helper.ex | 10 +++ 14 files changed, 242 insertions(+) create mode 100644 examples/leader_example/.formatter.exs create mode 100644 examples/leader_example/.gitignore create mode 100644 examples/leader_example/README.md create mode 100644 examples/leader_example/config/config.exs create mode 100644 examples/leader_example/config/dev.exs create mode 100644 examples/leader_example/config/node1.exs create mode 100644 examples/leader_example/config/node2.exs create mode 100644 examples/leader_example/lib/leader_example/application.ex create mode 100644 examples/leader_example/lib/leader_example/leader.ex create mode 100644 examples/leader_example/mix.exs create mode 100644 examples/leader_example/mix.lock create mode 100644 examples/leader_example/test/leader_example_test.exs create mode 100644 examples/leader_example/test/test_helper.exs diff --git a/examples/leader_example/.formatter.exs b/examples/leader_example/.formatter.exs new file mode 100644 index 0000000..525446d --- /dev/null +++ b/examples/leader_example/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/examples/leader_example/.gitignore b/examples/leader_example/.gitignore new file mode 100644 index 0000000..28bf3b2 --- /dev/null +++ b/examples/leader_example/.gitignore @@ -0,0 +1,24 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where 3rd-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +leader_example-*.tar + diff --git a/examples/leader_example/README.md b/examples/leader_example/README.md new file mode 100644 index 0000000..dfed475 --- /dev/null +++ b/examples/leader_example/README.md @@ -0,0 +1,21 @@ +# LeaderExample + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `leader_example` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:leader_example, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at [https://hexdocs.pm/leader_example](https://hexdocs.pm/leader_example). + diff --git a/examples/leader_example/config/config.exs b/examples/leader_example/config/config.exs new file mode 100644 index 0000000..ce42502 --- /dev/null +++ b/examples/leader_example/config/config.exs @@ -0,0 +1,21 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Mix.Config module. +use Mix.Config + +config :libcluster, + topologies: [ + gossip_example: [ + strategy: Cluster.Strategy.Gossip, + config: [ + port: 45_892, + if_addr: {0, 0, 0, 0}, + multicast_addr: {230, 1, 1, 251}, + multicast_ttl: 1]]] + +config :logger, + level: :debug, + format: "$date $time [$level] $metadata$message\n", + metadata: [:application], + handle_otp_reports: true + +import_config "#{Mix.env}.exs" diff --git a/examples/leader_example/config/dev.exs b/examples/leader_example/config/dev.exs new file mode 100644 index 0000000..0d0917d --- /dev/null +++ b/examples/leader_example/config/dev.exs @@ -0,0 +1,3 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Mix.Config module. +use Mix.Config diff --git a/examples/leader_example/config/node1.exs b/examples/leader_example/config/node1.exs new file mode 100644 index 0000000..52a9823 --- /dev/null +++ b/examples/leader_example/config/node1.exs @@ -0,0 +1,9 @@ +use Mix.Config + +config :tres, + protocol: :tcp, + port: 6653, + max_connections: 10, + num_acceptors: 10, + callback_module: LeaderExample.Leader, + callback_args: [] diff --git a/examples/leader_example/config/node2.exs b/examples/leader_example/config/node2.exs new file mode 100644 index 0000000..bbc9aef --- /dev/null +++ b/examples/leader_example/config/node2.exs @@ -0,0 +1,9 @@ +use Mix.Config + +config :tres, + protocol: :tcp, + port: 6633, + max_connections: 10, + num_acceptors: 10, + callback_module: LeaderExample.Leader, + callback_args: [] diff --git a/examples/leader_example/lib/leader_example/application.ex b/examples/leader_example/lib/leader_example/application.ex new file mode 100644 index 0000000..89c4ca6 --- /dev/null +++ b/examples/leader_example/lib/leader_example/application.ex @@ -0,0 +1,20 @@ +defmodule LeaderExample.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + def start(_type, _args) do + # List all child processes to be supervised + children = [ + # Starts a worker by calling: LeaderExample.Worker.start_link(arg) + # {LeaderExample.Worker, arg}, + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: LeaderExample.Supervisor] + Supervisor.start_link(children, opts) + end +end diff --git a/examples/leader_example/lib/leader_example/leader.ex b/examples/leader_example/lib/leader_example/leader.ex new file mode 100644 index 0000000..9fb0e69 --- /dev/null +++ b/examples/leader_example/lib/leader_example/leader.ex @@ -0,0 +1,77 @@ +defmodule LeaderExample.Leader do + use Tres.Controller + + @behaviour :locks_leader + + require Logger + + def start_link(datapath_id, args) do + :locks_leader.start_link(__MODULE__, [datapath_id, args], []) + end + + def init([{datapath_id, _aux_id}, _args]) do + :ok = Logger.debug("Switch Ready: datapath_id: #{inspect(datapath_id)}") + :ok = role_request(datapath_id, role: :slave, generation_id: 0) + {:ok, %{am_leader: false, gen_id: 0, datapath_id: datapath_id}} + end + + def elected(state, _info, _cand) do + :ok = Logger.info("Elected: #{inspect(Node.self)} for #{state.datapath_id} gen_id: #{state.gen_id}") + :ok = role_request(state.datapath_id, role: :master, generation_id: state.gen_id) + {:ok, {:elected, {Node.self(), state.gen_id + 1}}, %{state|am_leader: true}} + end + + def surrendered(%{am_leader: true} = state, {:elected, {_node, next_gen_id}}, _info) do + :ok = Logger.info("network split possible detected") + :ok = role_request(state.datapath_id, role: :slave, generation_id: state.gen_id) + {:ok, %{state|am_leader: false, gen_id: next_gen_id}} + end + + def surrendered(state, {:elected, {node, next_gen_id}}, _info) do + :ok = Logger.info("Surrendered: elected node is #{node} for #{state.datapath_id}") + :ok = role_request(state.datapath_id, role: :slave, generation_id: state.gen_id) + {:ok, %{state|am_leader: false, gen_id: next_gen_id}} + end + + def handle_DOWN(_pid, state, _info) do + :ok = Logger.warn("DOWN detected") + {:ok, state} + end + + def handle_leader_call(msg, from, state, _info) do + :ok = Logger.info("leader call with: #{inspect(msg)} from: #{inspect(from)} on #{Node.self()}") + {:reply, :ok, state} + end + + def handle_leader_cast(_msg, state, _info) do + {:ok, state} + end + + def from_leader(_from_leader, state, _info) do + {:ok, state} + end + + def handle_call(_msg, _from, state, _info) do + {:reply, :ok, state} + end + + def handle_cast(_msg, state, _info) do + {:noreply, state} + end + + def handle_info(%Role.Reply{datapath_id: dpid} = role, %{datapath_id: dpid} = state, _info) do + :ok = Logger.info("#{Node.self()} is on #{role.role} for #{dpid}") + {:noreply, state} + end + def handle_info(_msg, state, _info) do + {:noreply, state} + end + + def code_change(_from_vsn, state, _info, _extra) do + {:ok, state} + end + + def terminate(_reason, _state) do + :ok + end +end diff --git a/examples/leader_example/mix.exs b/examples/leader_example/mix.exs new file mode 100644 index 0000000..864f603 --- /dev/null +++ b/examples/leader_example/mix.exs @@ -0,0 +1,25 @@ +defmodule LeaderExample.MixProject do + use Mix.Project + + def project do + [ + app: :leader_example, + version: "0.1.0", + elixir: "~> 1.6", + deps: deps() + ] + end + + def application do + [ + extra_applications: [:logger, :libcluster, :locks, :tres], + mod: {LeaderExample.Application, []} + ] + end + + defp deps do + [{:locks, "~> 0.2.0"}, + {:libcluster, "~> 2.3.0"}, + {:tres, path: "../../../tres"}] + end +end diff --git a/examples/leader_example/mix.lock b/examples/leader_example/mix.lock new file mode 100644 index 0000000..6318ab4 --- /dev/null +++ b/examples/leader_example/mix.lock @@ -0,0 +1,10 @@ +%{ + "eovsdb": {:git, "https://github.com/shun159/eovsdb.git", "1ff1572708d72fd25631c681f2102407903252a3", [branch: "master"]}, + "jsone": {:git, "https://github.com/sile/jsone.git", "eecc9666c7165e1870b78a7a762549ae8d1c391b", [tag: "1.2.1"]}, + "libcluster": {:hex, :libcluster, "2.3.0", "d2fb8e8b2054a4a0c7faf7ced7c41dd13f7b49cce689590d37ecf30098e1b344", [:mix], [{:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, + "locks": {:hex, :locks, "0.2.0", "c8a5db52bdbfacfcc979b38920adb2ab8909fa7fb36015ddff6b753d1c950b92", [:rebar3], [{:plain_fsm, "1.4.1", [hex: :plain_fsm, repo: "hexpm", optional: false]}], "hexpm"}, + "plain_fsm": {:hex, :plain_fsm, "1.4.1", "47e9bf6ac9322fc7586fb6df8de7198391e93764571c75165f2c45b27acde1d0", [:rebar3], [], "hexpm"}, + "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, + "ranch": {:hex, :ranch, "1.4.0", "10272f95da79340fa7e8774ba7930b901713d272905d0012b06ca6d994f8826b", [:rebar3], [], "hexpm"}, + "uuid": {:git, "https://github.com/avtobiff/erlang-uuid.git", "585c2474afb4a597ae8c8bf6d21e5a9c73f18e0b", [tag: "v0.5.0"]}, +} diff --git a/examples/leader_example/test/leader_example_test.exs b/examples/leader_example/test/leader_example_test.exs new file mode 100644 index 0000000..e7d1d8d --- /dev/null +++ b/examples/leader_example/test/leader_example_test.exs @@ -0,0 +1,8 @@ +defmodule LeaderExampleTest do + use ExUnit.Case + doctest LeaderExample + + test "greets the world" do + assert LeaderExample.hello() == :world + end +end diff --git a/examples/leader_example/test/test_helper.exs b/examples/leader_example/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/examples/leader_example/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/lib/tres/message_helper.ex b/lib/tres/message_helper.ex index 87d6a00..41c571b 100644 --- a/lib/tres/message_helper.ex +++ b/lib/tres/message_helper.ex @@ -93,6 +93,16 @@ defmodule Tres.MessageHelper do send_message(group_mod, datapath_id) end + + defp role_request(datapath_id, options) do + role_request = + Openflow.Role.Request.new( + role: options[:role] || :nochange, + generation_id: options[:generation_id] || 0 + ) + + send_message(role_request, datapath_id) + end end end end