From 51729542f3b33814e258ca301c47891fca2867da Mon Sep 17 00:00:00 2001 From: Eishun Kondoh Date: Sun, 25 Feb 2018 23:44:58 +0900 Subject: [PATCH] Add patch_panel example --- examples/patch_panel/.formatter.exs | 4 + examples/patch_panel/.gitignore | 24 ++++++ examples/patch_panel/README.md | 16 ++++ examples/patch_panel/config/config.exs | 7 ++ examples/patch_panel/lib/patch_panel.ex | 18 +++++ .../lib/patch_panel/application.ex | 20 +++++ .../lib/patch_panel/openflow/controller.ex | 81 +++++++++++++++++++ .../lib/patch_panel/openflow/registry.ex | 16 ++++ examples/patch_panel/mix.exs | 26 ++++++ examples/patch_panel/mix.lock | 6 ++ .../patch_panel/test/patch_panel_test.exs | 8 ++ examples/patch_panel/test/test_helper.exs | 1 + 12 files changed, 227 insertions(+) create mode 100644 examples/patch_panel/.formatter.exs create mode 100644 examples/patch_panel/.gitignore create mode 100644 examples/patch_panel/README.md create mode 100644 examples/patch_panel/config/config.exs create mode 100644 examples/patch_panel/lib/patch_panel.ex create mode 100644 examples/patch_panel/lib/patch_panel/application.ex create mode 100644 examples/patch_panel/lib/patch_panel/openflow/controller.ex create mode 100644 examples/patch_panel/lib/patch_panel/openflow/registry.ex create mode 100644 examples/patch_panel/mix.exs create mode 100644 examples/patch_panel/mix.lock create mode 100644 examples/patch_panel/test/patch_panel_test.exs create mode 100644 examples/patch_panel/test/test_helper.exs diff --git a/examples/patch_panel/.formatter.exs b/examples/patch_panel/.formatter.exs new file mode 100644 index 0000000..525446d --- /dev/null +++ b/examples/patch_panel/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/examples/patch_panel/.gitignore b/examples/patch_panel/.gitignore new file mode 100644 index 0000000..b22822d --- /dev/null +++ b/examples/patch_panel/.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"). +patch_panel-*.tar + diff --git a/examples/patch_panel/README.md b/examples/patch_panel/README.md new file mode 100644 index 0000000..ce2181c --- /dev/null +++ b/examples/patch_panel/README.md @@ -0,0 +1,16 @@ +# PatchPanel + +openflow controller that emulates a software patch panel + +## prerequisites + + - Erlang 20 or higher + - Elixir 1.6.1 or higher + - OpenFlow switch supports version 1.3 + +```iex +# To add a patch link +iex > :ok = PatchPanel.Openflow.Controller.create_patch("de780562fb45, 1, 2) +# To delete a patch link +iex > :ok = PatchPanel.Openflow.Controller.delete_patch("de780562fb45, 1, 2) +``` diff --git a/examples/patch_panel/config/config.exs b/examples/patch_panel/config/config.exs new file mode 100644 index 0000000..ce4f069 --- /dev/null +++ b/examples/patch_panel/config/config.exs @@ -0,0 +1,7 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Mix.Config module. +use Mix.Config + +config :tres, + callback_module: PatchPanel.Openflow.Controller, + callback_args: [] diff --git a/examples/patch_panel/lib/patch_panel.ex b/examples/patch_panel/lib/patch_panel.ex new file mode 100644 index 0000000..f6e0dbe --- /dev/null +++ b/examples/patch_panel/lib/patch_panel.ex @@ -0,0 +1,18 @@ +defmodule PatchPanel do + @moduledoc """ + Documentation for PatchPanel. + """ + + @doc """ + Hello world. + + ## Examples + + iex> PatchPanel.hello + :world + + """ + def hello do + :world + end +end diff --git a/examples/patch_panel/lib/patch_panel/application.ex b/examples/patch_panel/lib/patch_panel/application.ex new file mode 100644 index 0000000..b156391 --- /dev/null +++ b/examples/patch_panel/lib/patch_panel/application.ex @@ -0,0 +1,20 @@ +defmodule PatchPanel.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + alias PatchPanel.Openflow + + def start(_type, _args) do + import Supervisor.Spec + + children = [ + worker(Registry, [[keys: :unique, name: Openflow.Registry]], id: Openflow.Registry), + ] + + opts = [strategy: :one_for_one, name: PatchPanel.Supervisor] + Supervisor.start_link(children, opts) + end +end diff --git a/examples/patch_panel/lib/patch_panel/openflow/controller.ex b/examples/patch_panel/lib/patch_panel/openflow/controller.ex new file mode 100644 index 0000000..bbecf04 --- /dev/null +++ b/examples/patch_panel/lib/patch_panel/openflow/controller.ex @@ -0,0 +1,81 @@ +defmodule PatchPanel.Openflow.Controller do + use GenServer + use Tres.Controller + + import Logger + + defmodule State do + defstruct [:datapath_id] + end + + def create_patch(datapath_id, port_a, port_b) do + case PatchPanel.Openflow.Registry.lookup_pid(datapath_id) do + nil -> + {:error, :not_found} + pid when is_pid(pid) -> + GenServer.call(pid, {:create, port_a, port_b}) + end + end + + def delete_patch(datapath_id, port_a, port_b) do + case PatchPanel.Openflow.Registry.lookup_pid(datapath_id) do + nil -> + {:error, :not_found} + pid when is_pid(pid) -> + GenServer.call(pid, {:delete, port_a, port_b}) + end + end + + def start_link({datapath_id, _aux_id}, _start_args) do + GenServer.start_link(__MODULE__, [datapath_id]) + end + + def init([datapath_id]) do + :ok = info("Switch Connected: datapath_id = #{datapath_id}") + {:ok, _} = PatchPanel.Openflow.Registry.register(datapath_id) + {:ok, %State{datapath_id: datapath_id}} + end + + def handle_call({:create, port_a, port_b}, _from, %State{datapath_id: datapath_id} = state) do + :ok = add_flow_entries(datapath_id, port_a, port_b) + {:reply, :ok, state} + end + def handle_call({:delete, port_a, port_b}, _from, %State{datapath_id: datapath_id} = state) do + :ok = del_flow_entries(datapath_id, port_a, port_b) + {:reply, :ok, state} + end + + def handle_info({:switch_disconnected, reason}, %State{datapath_id: datapath_id} = state) do + :ok = warn("#{datapath_id} disconnected: reason = #{inspect(reason)}") + :ok = PatchPanel.Openflow.Registry.unregister(datapath_id) + {:stop, :normal, state} + end + def handle_info(_info, state) do + {:noreply, state} + end + + def terminate(reason, state) do + {reason, state} + end + + # private functions + + defp add_flow_entries(datapath_id, port_a, port_b) do + :ok = send_flow_mod_add( + datapath_id, + match: Match.new(in_port: port_a), + instructions: [ApplyActions.new(Output.new(port_b))] + ) + + :ok = send_flow_mod_add( + datapath_id, + match: Match.new(in_port: port_b), + instructions: [ApplyActions.new(Output.new(port_a))] + ) + end + + defp del_flow_entries(datapath_id, port_a, port_b) do + :ok = send_flow_mod_delete(datapath_id, match: Match.new(in_port: port_a)) + :ok = send_flow_mod_delete(datapath_id, match: Match.new(in_port: port_b)) + end +end diff --git a/examples/patch_panel/lib/patch_panel/openflow/registry.ex b/examples/patch_panel/lib/patch_panel/openflow/registry.ex new file mode 100644 index 0000000..a24ba0b --- /dev/null +++ b/examples/patch_panel/lib/patch_panel/openflow/registry.ex @@ -0,0 +1,16 @@ +defmodule PatchPanel.Openflow.Registry do + def register(datapath_id) do + {:ok, _} = Registry.register(__MODULE__, datapath_id, []) + end + + def unregister(datapath_id) do + :ok = Registry.unregister(__MODULE__, datapath_id) + end + + def lookup_pid(datapath_id) do + case Registry.lookup(__MODULE__, datapath_id) do + [{pid, _}] -> pid + [] -> nil + end + end +end diff --git a/examples/patch_panel/mix.exs b/examples/patch_panel/mix.exs new file mode 100644 index 0000000..91b4af6 --- /dev/null +++ b/examples/patch_panel/mix.exs @@ -0,0 +1,26 @@ +defmodule PatchPanel.MixProject do + use Mix.Project + + def project do + [ + app: :patch_panel, + version: "0.1.0", + elixir: "~> 1.6", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger], + mod: {PatchPanel.Application, []} + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [{:tres, path: "../../../tres"}] + end +end diff --git a/examples/patch_panel/mix.lock b/examples/patch_panel/mix.lock new file mode 100644 index 0000000..b51b792 --- /dev/null +++ b/examples/patch_panel/mix.lock @@ -0,0 +1,6 @@ +%{ + "eovsdb": {:git, "https://github.com/shun159/eovsdb.git", "1ff1572708d72fd25631c681f2102407903252a3", [branch: "master"]}, + "jsone": {:git, "https://github.com/sile/jsone.git", "eecc9666c7165e1870b78a7a762549ae8d1c391b", [tag: "1.2.1"]}, + "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/patch_panel/test/patch_panel_test.exs b/examples/patch_panel/test/patch_panel_test.exs new file mode 100644 index 0000000..d6f8a89 --- /dev/null +++ b/examples/patch_panel/test/patch_panel_test.exs @@ -0,0 +1,8 @@ +defmodule PatchPanelTest do + use ExUnit.Case + doctest PatchPanel + + test "greets the world" do + assert PatchPanel.hello() == :world + end +end diff --git a/examples/patch_panel/test/test_helper.exs b/examples/patch_panel/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/examples/patch_panel/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()