diff --git a/lib/ovsdb.ex b/lib/ovsdb.ex new file mode 100644 index 0000000..7a4c9d0 --- /dev/null +++ b/lib/ovsdb.ex @@ -0,0 +1,22 @@ +defmodule OVSDB do + @moduledoc false + + @behaviour :supervisor + + def start_link do + :supervisor.start_link({:local, __MODULE__}, __MODULE__, []) + end + + def init([]) do + child = OVSDB.OpenvSwitch + strategy = :simple_one_for_one + max_r = 1000 + intensity = 3600 + sup_flags = {strategy, max_r, intensity} + {:ok, {sup_flags, [{child, {child, :start_link, []}, :temporary, 1000, :worker, [child]}]}} + end + + def start_child(server) do + :supervisor.start_child(__MODULE__, [server]) + end +end diff --git a/lib/ovsdb/open_vswitch.ex b/lib/ovsdb/open_vswitch.ex new file mode 100644 index 0000000..67baaa9 --- /dev/null +++ b/lib/ovsdb/open_vswitch.ex @@ -0,0 +1,145 @@ +defmodule OVSDB.OpenvSwitch do + use GenServer + + defmodule State do + defstruct [ + server: nil, + client_pid: nil, + monitor_pid: nil, + ovs_uuid: nil + ] + end + + @database "Open_vSwitch" + + @open_vswitch "Open_vSwitch" + @interface "Interface" + @port "Port" + @controller "Controller" + @bridge "Bridge" + + def start_link(server) do + GenServer.start_link(__MODULE__, [server]) + end + + def find_by_name(pid, table, name) do + GenServer.call(pid, {:find_by_name, table, name}) + end + + def add_br(pid, bridge, options \\ []) do + br_uuids = GenServer.call(pid, {:sync_get, @open_vswitch, "bridges"}) + add_br_options = [ + bridge: bridge, + controller: options[:controller] || "tcp:127.0.0.1:6653", + protocol: options[:protocol] || "OpenFlow13", + fail_mode: options[:fail_mode] || "standalone", + br_uuids: br_uuids + ] + GenServer.call(pid, {:add_br, add_br_options}) + end + + def del_br(pid, bridge) do + case find_by_name(pid, @bridge, bridge) do + %{"_uuid" => uuid} -> + new_bridges = + case GenServer.call(pid, {:sync_get, @open_vswitch, "bridges"}) do + ["set", bridges] -> %{"bridges" => ["set", bridges -- [uuid]]} + ^uuid -> %{"bridges" => ["set", []]} + curr_bridges -> %{"bridges" => curr_bridges} + end + GenServer.call(pid, {:del_br, new_bridges}) + :not_found -> + {:error, :not_found} + end + end + + def init([server]) do + state = server + |> String.to_charlist + |> init_client + {:ok, state} + end + + def handle_call({:sync_get, table, col_name}, _from, state) do + [%{"rows" => [%{^col_name => values}|_]}] = + [col_name] + |> :eovsdb_op.select(table, []) + |> xact(state.client_pid) + {:reply, values, state} + end + def handle_call({:add_br, options}, _from, state) do + %State{client_pid: pid, ovs_uuid: ovs} = state + br_iface = %{name: options[:bridge], type: :internal} + br_port = %{name: options[:bridge], interfaces: ["named-uuid", "brinterface"]} + controller = %{target: options[:controller]} + new_bridge = %{ + name: options[:bridge], + ports: ["named-uuid", "brport"], + controller: ["named-uuid", "brcontroller"], + fail_mode: options[:fail_mode], + protocols: options[:protocol] + } + named_uuid = ["named-uuid", "bridge"] + new_bridges = + case options[:br_uuids] do + ["set", []] -> %{bridges: named_uuid} + ["set", bridges] -> %{bridges: ["set", bridges ++ [named_uuid]]} + ["uuid", _] = bridge -> %{bridges: ["set", [bridge] ++ [named_uuid]]} + end + next_config = [{"next_cfg", "+=", 1}] + eq_ovs_uuid = [{"_uuid", "==", ovs}] + replies = xact([ + :eovsdb_op.insert(@interface, br_iface, "brinterface"), + :eovsdb_op.insert(@port, br_port, "brport"), + :eovsdb_op.insert(@controller, controller, "brcontroller"), + :eovsdb_op.insert(@bridge, new_bridge, "bridge"), + :eovsdb_op.update(@open_vswitch, eq_ovs_uuid, new_bridges), + :eovsdb_op.mutate(@open_vswitch, eq_ovs_uuid, next_config) + ], pid) + {:reply, replies, state} + end + def handle_call({:del_br, new_bridges}, _from, state) do + %State{client_pid: pid, ovs_uuid: ovs} = state + eq_ovs_uuid = [{"_uuid", "==", ovs}] + next_config = [{"next_cfg", "+=", 1}] + replies = xact([ + :eovsdb_op.update(@open_vswitch, eq_ovs_uuid, new_bridges), + :eovsdb_op.mutate(@open_vswitch, eq_ovs_uuid, next_config) + ], pid) + {:reply, replies, state} + end + def handle_call({:find_by_name, table, name}, _from, state) do + %State{client_pid: pid} = state + query = :eovsdb_op.select('*', table, [{"name", "==", name}]) + reply = case xact(query, pid) do + [%{"rows" => []}] -> :not_found + [%{"rows" => [row]}] -> row + end + {:reply, reply, state} + end + + def handle_cast({:async_get, "_uuid"}, state) do + [%{"rows" => [%{"_uuid" => values}|_]}] = + ["_uuid"] + |> :eovsdb_op.select(@open_vswitch, []) + |> xact(state.client_pid) + {:noreply, %{state|ovs_uuid: values}} + end + + # private functions + + defp init_client(server) do + {:ok, pid} = :eovsdb_client.connect(server, [database: @database]) + :eovsdb_client.regist_schema(pid) + :ok = GenServer.cast(self(), {:async_get, "_uuid"}) + %State{server: server, client_pid: pid} + end + + defp xact(query, pid) when is_list(query) do + {:ok, res} = :eovsdb_client.transaction(pid, query) + res + end + defp xact(query, pid) when is_map(query) do + xact([query], pid) + end +end diff --git a/lib/tres/application.ex b/lib/tres/application.ex index 3186d9d..5af2b67 100644 --- a/lib/tres/application.ex +++ b/lib/tres/application.ex @@ -10,7 +10,8 @@ defmodule Tres.Application do {cb_mod, _cb_args} = Tres.Utils.get_callback_module children = [worker(Registry, [[keys: :unique, name: SwitchRegistry]], id: SwitchRegistry), - supervisor(Tres.MessageHandlerSup, [cb_mod], id: MessageHandlerSup)] + supervisor(Tres.MessageHandlerSup, [cb_mod], id: MessageHandlerSup), + supervisor(OVSDB, [], id: OVSDB)] opts = [strategy: :one_for_one, name: Tres.Supervisor] {:ok, _connection_pid} = Tres.Utils.start_openflow_listener Supervisor.start_link(children, opts)