diff --git a/.travis.yml b/.travis.yml index d458396..097035a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,9 @@ before_install: - sudo ovs-vsctl add-br br0 - sudo ovs-vsctl set bridge br0 datapath_type=netdev - sudo ovs-vsctl set bridge br0 protocols=OpenFlow13 + - sudo ovs-vsctl set bridge br0 other-config:datapath-id=0000000000000001 - sudo ovs-vsctl set-controller br0 tcp:127.0.0.1:6653 + - sudo ovs-vsctl set-manager ptcp:6640 - sudo ovs-vsctl show - cd - diff --git a/lib/openflow/packet_in.ex b/lib/openflow/packet_in.ex index c73b2c3..ea4a738 100644 --- a/lib/openflow/packet_in.ex +++ b/lib/openflow/packet_in.ex @@ -43,29 +43,4 @@ defmodule Openflow.PacketIn do data: data } end - - def to_binary(%PacketIn{} = packet_in) do - %PacketIn{ - buffer_id: buffer_id, - total_len: total_len, - reason: reason, - table_id: table_id, - cookie: cookie, - in_port: in_port, - match: match_fields, - data: data - } = packet_in - - buffer_id_int = Openflow.Utils.get_enum(buffer_id, :buffer_id) - reason_int = Openflow.Utils.get_enum(reason, :packet_in_reason) - table_id_int = Openflow.Utils.get_enum(table_id, :table_id) - - match_fields_bin = - [{:in_port, in_port} | match_fields] - |> Openflow.Match.new() - |> Openflow.Match.to_binary() - - <> - end end diff --git a/lib/openflow/role/reply.ex b/lib/openflow/role/reply.ex index 0480a0f..24e6f73 100644 --- a/lib/openflow/role/reply.ex +++ b/lib/openflow/role/reply.ex @@ -14,19 +14,8 @@ defmodule Openflow.Role.Reply do def ofp_type, do: 25 - def new(options \\ []) do - role = Keyword.get(options, :role, :nochange) - generation_id = Keyword.get(options, :generation_id, 0) - %Reply{role: role, generation_id: generation_id} - end - def read(<>) do role = Openflow.Enums.to_atom(role_int, :controller_role) %Reply{role: role, generation_id: generation_id} end - - def to_binary(%Reply{role: role, generation_id: generation_id}) do - role_int = Openflow.Enums.to_int(role, :controller_role) - <> - end end diff --git a/lib/openflow/role/request.ex b/lib/openflow/role/request.ex index 3bacde8..ee96941 100644 --- a/lib/openflow/role/request.ex +++ b/lib/openflow/role/request.ex @@ -21,11 +21,6 @@ defmodule Openflow.Role.Request do %Request{xid: xid, role: role, generation_id: generation_id} end - def read(<>) do - role = Openflow.Enums.to_atom(role_int, :controller_role) - %Request{role: role, generation_id: generation_id} - end - def to_binary(%Request{role: role, generation_id: generation_id}) do role_int = Openflow.Enums.to_int(role, :controller_role) <> diff --git a/lib/tres/example_handler.ex b/lib/tres/example_handler.ex index 88436ca..ae96954 100644 --- a/lib/tres/example_handler.ex +++ b/lib/tres/example_handler.ex @@ -7,33 +7,50 @@ defmodule Tres.ExampleHandler do defmodule State do defstruct datapath_id: nil, aux_id: nil, - conn_ref: nil + queue: :queue.new(), + client: nil + end + + # API functions + + @spec send(String.t(), map()) :: :ok + def send(datapath_id, msg), do: send_message(msg, datapath_id) + + @spec get(datapath_id :: String.t()) :: map() | nil + def get(datapath_id) do + datapath_id + |> lookup_handler_pid() + |> GenServer.call(:get) end def start_link(datapath, args) do GenServer.start_link(__MODULE__, [datapath, args]) end + # GenServer callbacks + def init([{datapath_id, aux_id}, _args]) do - info( - "Switch Ready: " <> - "datapath_id: #{datapath_id} " <> "aux_id: #{aux_id} " <> "on #{inspect(self())}" - ) - - _ = send_desc_stats_request(datapath_id) - _ = send_port_desc_stats_request(datapath_id) - state = %State{datapath_id: datapath_id, aux_id: aux_id} - {:ok, state} + :ok = info("datapath connected: #{datapath_id}") + :ok = send_flow_mod_add(datapath_id, priority: 0) + {:ok, %State{datapath_id: datapath_id, aux_id: aux_id}} end - def handle_info(%PortDesc.Reply{datapath_id: datapath_id} = desc, state) do - handle_port_desc_stats_reply(desc, datapath_id) - {:noreply, state} + def handle_call(:get, from, %State{queue: {[], []}} = state) do + {:noreply, %{state | client: from}} end - def handle_info(%Desc.Reply{datapath_id: datapath_id} = desc, state) do - handle_desc_stats_reply(desc, datapath_id) - {:noreply, state} + def handle_call(:get, _from, %State{} = state) do + {{:value, msg}, new_queue} = :queue.out(state.queue) + {:reply, msg, %{state | queue: new_queue}} + end + + def handle_info(%{datapath_id: _datapath_id} = msg, %State{client: nil} = state) do + {:noreply, %{state | queue: :queue.in(msg, state.queue)}} + end + + def handle_info(%{datapath_id: _datapath_id} = msg, state) do + GenServer.reply(state.client, msg) + {:noreply, %{state | client: nil}} end def handle_info({:switch_disconnected, _reason}, state) do @@ -41,47 +58,10 @@ defmodule Tres.ExampleHandler do {:stop, :normal, state} end - def handle_info({:switch_hang, _datapath_id}, state) do - :ok = warn("Switch possible hang: datapath_id: #{state.datapath_id}") - {:noreply, state} - end - - # `Catch all` function is required. def handle_info(info, state) do :ok = warn("unhandled message #{inspect(info)}: #{state.datapath_id}") {:noreply, state} end # private functions - - defp send_desc_stats_request(datapath_id) do - Desc.Request.new() - |> send_message(datapath_id) - end - - defp send_port_desc_stats_request(datapath_id) do - PortDesc.Request.new() - |> send_message(datapath_id) - end - - defp handle_desc_stats_reply(desc, datapath_id) do - info( - "Switch Desc: " <> - "mfr = #{desc.mfr_desc} " <> - "hw = #{desc.hw_desc} " <> "sw = #{desc.sw_desc} " <> "for #{datapath_id}" - ) - end - - defp handle_port_desc_stats_reply(port_desc, datapath_id) do - for port <- port_desc.ports do - info( - "Switch has port: " <> - "number = #{port.number} " <> - "hw_addr = #{port.hw_addr} " <> - "name = #{port.name} " <> - "config = #{inspect(port.config)} " <> - "current_speed = #{port.current_speed} " <> "on #{datapath_id}" - ) - end - end end diff --git a/test/lib/openflow/ofp_packet_in2_test.exs b/test/lib/openflow/ofp_packet_in2_test.exs index 32d6e49..addb797 100644 --- a/test/lib/openflow/ofp_packet_in2_test.exs +++ b/test/lib/openflow/ofp_packet_in2_test.exs @@ -28,8 +28,14 @@ defmodule OfpPacketIn2Test do |> Kernel.elem(1) assert pktin.continuation_action_set == nil - assert pktin.continuation_actions == [%Openflow.Action.NxResubmitTable{in_port: :in_port, table_id: 5}] - assert pktin.continuation_bridge == <<8, 137, 105, 58, 5, 77, 183, 237, 163, 58, 25, 166, 212, 167, 209, 11>> + + assert pktin.continuation_actions == [ + %Openflow.Action.NxResubmitTable{in_port: :in_port, table_id: 5} + ] + + assert pktin.continuation_bridge == + <<8, 137, 105, 58, 5, 77, 183, 237, 163, 58, 25, 166, 212, 167, 209, 11>> + assert pktin.continuation_conntracked == nil assert pktin.continuation_cookie == nil assert pktin.continuation_mirrors == nil diff --git a/test/lib/openflow/ofp_resume_test.exs b/test/lib/openflow/ofp_resume_test.exs index 3fdbe03..494d30e 100644 --- a/test/lib/openflow/ofp_resume_test.exs +++ b/test/lib/openflow/ofp_resume_test.exs @@ -11,8 +11,14 @@ defmodule OfpResumeTest do |> Kernel.elem(1) assert pktin.continuation_action_set == nil - assert pktin.continuation_actions == [%Openflow.Action.NxResubmitTable{in_port: :in_port, table_id: 5}] - assert pktin.continuation_bridge == <<8, 137, 105, 58, 5, 77, 183, 237, 163, 58, 25, 166, 212, 167, 209, 11>> + + assert pktin.continuation_actions == [ + %Openflow.Action.NxResubmitTable{in_port: :in_port, table_id: 5} + ] + + assert pktin.continuation_bridge == + <<8, 137, 105, 58, 5, 77, 183, 237, 163, 58, 25, 166, 212, 167, 209, 11>> + assert pktin.continuation_conntracked == nil assert pktin.continuation_cookie == nil assert pktin.continuation_mirrors == nil diff --git a/test/lib/ovsdb/openvswitch_test.exs b/test/lib/ovsdb/openvswitch_test.exs new file mode 100644 index 0000000..d28f0ba --- /dev/null +++ b/test/lib/ovsdb/openvswitch_test.exs @@ -0,0 +1,44 @@ +defmodule OVSDB.OpenvSwitchTest do + use ExUnit.Case, async: false + + setup_all do + {:ok, pid} = OVSDB.start_child("127.0.0.1:6640") + {:ok, pid: pid} + end + + describe "OVSDB.OpenvSwitch.find_by_name/3" do + test "with pid, table and name", context do + %{"datapath_id" => "0000000000000001"} = OVSDB.OpenvSwitch.find_by_name(context.pid, "Bridge", "br0") + end + end + + describe "OVSDB.OpenvSwitch.add_br/2" do + test "with options", context do + OVSDB.OpenvSwitch.add_br( + context.pid, + name: "brx", + datapath_id: "0000000000000003" + ) + end + end + + describe "OVSDB.OpenvSwitch.set_controller/2" do + test "with options", context do + OVSDB.OpenvSwitch.add_br( + context.pid, + name: "brx", + target: "tcp:127.0.0.1:6653", + connection_mode: "out-of-band", + controller_rate_limit: 100, + controller_burst_limit: 25, + protocol: "OpenFlow13" + ) + end + end + + describe "OVSDB.OpenvSwitch.del_br/2" do + test "with pid and name", context do + OVSDB.OpenvSwitch.del_br(context.pid, "brx") + end + end +end diff --git a/test/lib/tres/handler_test.exs b/test/lib/tres/handler_test.exs new file mode 100644 index 0000000..b811c74 --- /dev/null +++ b/test/lib/tres/handler_test.exs @@ -0,0 +1,178 @@ +defmodule Tres.HanderTest do + use ExUnit.Case + + @datapath_id "0000000000000001" + + setup_all do + :ok = wait_datapath_is_connected() + :ok = send_message(Openflow.FlowMod.new(command: :delete, table_id: :all)) + :ok = send_message(Openflow.GroupMod.new(command: :delete, group_id: :all)) + :ok = send_message(Openflow.MeterMod.new(command: :delete, meter_id: :all)) + end + + describe "Openflow RoleRequest message" do + test "with role and generation_id" do + send_message(Openflow.Role.Request.new(role: :nochange, generation_id: 1)) + %Openflow.Role.Reply{} = get_message() + end + end + + describe "Openflow standard PacketIn message" do + test "with arp_packet" do + send_message( + Openflow.PacketOut.new( + buffer_id: :no_buffer, + in_port: :controller, + actions: [Openflow.Action.Output.new(:controller)], + data: File.read!("test/packet_data/arp_packet.raw") + ) + ) + + %Openflow.PacketIn{} = get_message() + end + end + + describe "Openflow Nicira PacketIn2 message" do + test "with arp_packet" do + send_message(Openflow.NxSetPacketInFormat.new(:nxt_packet_in2)) + + send_message( + Openflow.FlowMod.new( + instructions: Openflow.Instruction.ApplyActions.new(Openflow.Action.NxController2.new()) + ) + ) + + send_message( + Openflow.PacketOut.new( + buffer_id: :no_buffer, + in_port: :controller, + actions: [Openflow.Action.Output.new(:controller)], + data: File.read!("test/packet_data/arp_packet.raw") + ) + ) + + %Openflow.NxPacketIn2{} = get_message() + end + end + + describe "Openflow FlowRemoved message" do + test "with a flow" do + send_message( + Openflow.FlowMod.new(flags: [:send_flow_rem], match: Openflow.Match.new(reg0: 99)) + ) + + send_message(Openflow.FlowMod.new(command: :delete, match: Openflow.Match.new(reg0: 99))) + %Openflow.FlowRemoved{} = get_message() + end + end + + describe "Openflow AggregateStats Reply message" do + test "with no option" do + send_message(Openflow.Multipart.Aggregate.Request.new()) + %Openflow.Multipart.Aggregate.Reply{} = get_message() + end + end + + describe "Openflow DescStats Reply message" do + test "with no option" do + send_message(Openflow.Multipart.Desc.Request.new()) + %Openflow.Multipart.Desc.Reply{} = get_message() + end + end + + describe "Openflow FlowStats Reply message" do + test "with no option" do + send_message(Openflow.Multipart.Flow.Request.new()) + %Openflow.Multipart.Flow.Reply{} = get_message() + end + + test "with 3000 flows" do + Enum.each(1..3000, fn n -> + send_message(Openflow.FlowMod.new(match: Openflow.Match.new(reg0: n))) + end) + + send_message(Openflow.Multipart.Flow.Request.new()) + %Openflow.Multipart.Flow.Reply{flags: [:more]} = get_message() + end + end + + describe "Openflow GroupStats Reply message" do + test "with no option" do + send_message(Openflow.Multipart.Group.Request.new()) + %Openflow.Multipart.Group.Reply{} = get_message() + end + + test "with 3000 groups" do + Enum.each(1..3000, fn n -> send_message(Openflow.GroupMod.new(group_id: n)) end) + send_message(Openflow.Multipart.Group.Request.new()) + %Openflow.Multipart.Group.Reply{flags: [:more]} = get_message() + end + end + + describe "Openflow GroupDesc Reply message" do + test "with no option" do + send_message(Openflow.Multipart.GroupDesc.Request.new()) + %Openflow.Multipart.GroupDesc.Reply{} = get_message() + end + end + + describe "Openflow GroupFeatures Reply message" do + test "with no option" do + send_message(Openflow.Multipart.GroupFeatures.Request.new()) + %Openflow.Multipart.GroupFeatures.Reply{} = get_message() + end + end + + describe "Openflow MeterStats Reply message" do + test "with no option" do + send_message(Openflow.Multipart.Meter.Request.new()) + %Openflow.Multipart.Meter.Reply{} = get_message() + end + + test "with 3000 meters" do + Enum.each(1..3000, fn n -> + send_message( + Openflow.MeterMod.new( + meter_id: n, + flags: [:pktps, :burst, :stats], + bands: [Openflow.MeterBand.Drop.new(rate: 1000, burst_size: 10)] + ) + ) + end) + + send_message(Openflow.Multipart.Meter.Request.new()) + %Openflow.Multipart.Meter.Reply{flags: [:more]} = get_message() + end + end + + describe "Openflow PortDescStats Reply message" do + test "with no option" do + send_message(Openflow.Multipart.PortDesc.Request.new()) + %Openflow.Multipart.PortDesc.Reply{} = get_message() + end + end + + describe "Openflow PortStats Reply message" do + test "with no option" do + send_message(Openflow.Multipart.Port.Request.new()) + %Openflow.Multipart.Port.Reply{} = get_message() + end + end + + # helper + + def wait_datapath_is_connected do + case Tres.SwitchRegistry.lookup_handler_pid(@datapath_id) do + nil -> wait_datapath_is_connected() + pid when is_pid(pid) -> :ok + end + end + + def send_message(msg) do + :ok = Tres.ExampleHandler.send(@datapath_id, msg) + end + + def get_message do + Tres.ExampleHandler.get(@datapath_id) + end +end