Add an example for leader election based multiple controller

This commit is contained in:
Eishun Kondoh 2018-02-09 00:49:57 +09:00
parent 658b7448da
commit ce56d2b7be
14 changed files with 242 additions and 0 deletions

View file

@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

24
examples/leader_example/.gitignore vendored Normal file
View file

@ -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

View file

@ -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).

View file

@ -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"

View file

@ -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

View file

@ -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: []

View file

@ -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: []

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"]},
}

View file

@ -0,0 +1,8 @@
defmodule LeaderExampleTest do
use ExUnit.Case
doctest LeaderExample
test "greets the world" do
assert LeaderExample.hello() == :world
end
end

View file

@ -0,0 +1 @@
ExUnit.start()

View file

@ -93,6 +93,16 @@ defmodule Tres.MessageHelper do
send_message(group_mod, datapath_id) send_message(group_mod, datapath_id)
end 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 end
end end