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 find_by_name(pid, table, name) do GenServer.call(pid, {:find_by_name, table, name}) end # API functions @doc """ Create a bridge: ## options: iex> OpenvSwitch.add_br( client_pid, name: "br0", fail_mode: "standalone", datapath_id: "0000000000000001", disable_in_band: "true" ) """ def add_br(pid, [_ | _] = options) do br_uuids = GenServer.call(pid, {:sync_get, @open_vswitch, "bridges"}) add_br_options = [ bridge: options[:name], fail_mode: options[:fail_mode] || "standalone", datapath_id: options[:datapath_id], disable_in_band: options[:disable_in_band], br_uuids: br_uuids ] GenServer.call(pid, {:add_br, add_br_options}) end @doc """ Delete a bridge iex> OpenvSwitch.del_br(client_pid, "br0") """ 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 @doc """ Set a controller to the switch iex> OpenvSwitch.set_controller( client_pid, bridge: "br0", target: "tcp:127.0.0.1:6653", connection_mode: "out-of-band", controller_rate_limit: 100, controller_burst_limit: 25, protocol: "OpenFlow13" ) """ def set_controller(pid, [_ | _] = options) do case find_by_name(pid, @bridge, options[:bridge]) do :not_found -> {:error, :not_found} %{"_uuid" => _uuid} -> set_ctl_opts = [ bridge: options[:bridge], target: options[:target] || "tcp:127.0.0.1:6653", connection_mode: options[:connection_mode] || "out-of-band", controller_rate_limit: options[:controller_rate_limit] || 1000, controller_burst_limit: options[:controller_burst_limit] || 100, protocol: options[:protocol] || "OpenFlow13" ] GenServer.call(pid, {:set_controller, set_ctl_opts}) end end @doc """ iex> OpenvSwitch.add_port( client_pid, bridge: "br0", name: "vxlan5", type: "vxlan", remote_ip: "flow", ofport_request: 99 ) """ def add_port(pid, [_ | _] = options) do case find_by_name(pid, @bridge, options[:bridge]) do :not_found -> {:error, :not_found} %{"_uuid" => _uuid, "ports" => ports} -> port_opts = [ bridge: options[:bridge], name: options[:name], type: options[:type] || "system", ofport_request: options[:ofport_request], remote_ip: options[:remote_ip], local_ip: options[:local_ip], in_key: options[:in_key], out_key: options[:out_key], key: options[:key], tos: options[:tos], ttl: options[:ttl], df_default: options[:df_default], csum: options[:csum], peer_cert: options[:peer_cert], certificate: options[:certificate], private_key: options[:private_key], psk: options[:psk], ingress_policing_rate: options[:ingress_policing_rate] || 0, ingress_policing_burst: options[:ingress_policing_burst] || 0, ports: ports ] GenServer.call(pid, {:add_port, port_opts}) end end @doc """ iex> OpenvSwitch.del_port(client_pid, bridge: "br0", port: "vxlan5") """ def del_port(pid, [_ | _] = options) do case find_by_name(pid, @bridge, options[:bridge]) do :not_found -> {:error, :not_found} %{"_uuid" => _uuid, "ports" => ports} -> case find_by_name(pid, @port, options[:port]) do :not_found -> {:error, :not_found} %{"_uuid" => port_uuid} -> new_ports = del_elem_from_set(ports, port_uuid) port_opts = [ bridge: options[:bridge], ports: new_ports ] GenServer.call(pid, {:del_port, port_opts}) end end end # GenServer callback functions def start_link(server) do GenServer.start_link(__MODULE__, [server]) 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", "interface"]} other_config = [ ["datapath-id", options[:datapath_id]], ["dp-desc", options[:dp_desc]], ["disable-in-band", options[:disable_in_band]], ["in-band-queue", options[:in_band_queue]] ] new_bridge = %{ name: options[:bridge], ports: ["named-uuid", "port"], fail_mode: options[:fail_mode], other_config: make_ovsdb_map(other_config) } named_uuid = ["named-uuid", "bridge"] new_bridges = %{bridges: add_elem_to_set(options[:br_uuids], named_uuid)} next_config = [{"next_cfg", "+=", 1}] eq_ovs_uuid = [{"_uuid", "==", ovs}] replies = xact( [ :eovsdb_op.insert(@interface, br_iface, "interface"), :eovsdb_op.insert(@port, br_port, "port"), :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({:del_port, options}, _from, state) do %State{client_pid: pid, ovs_uuid: ovs} = state eq_br_name = [{"name", "==", options[:bridge]}] eq_ovs_uuid = [{"_uuid", "==", ovs}] next_config = [{"next_cfg", "+=", 1}] bridge = %{ports: options[:ports]} replies = xact( [ :eovsdb_op.update(@bridge, eq_br_name, bridge), :eovsdb_op.mutate(@open_vswitch, eq_ovs_uuid, next_config) ], pid ) {:reply, replies, state} end def handle_call({:set_controller, options}, _from, state) do %State{client_pid: pid, ovs_uuid: ovs} = state controller = %{ target: options[:target], connection_mode: options[:connection_mode], controller_rate_limit: options[:controller_rate_limit], controller_burst_limit: options[:controller_burst_limit] } bridge = %{ protocols: options[:protocol], controller: ["named-uuid", "controller"] } replies = xact( [ :eovsdb_op.insert(@controller, controller, "controller"), :eovsdb_op.update(@bridge, [{"name", "==", options[:bridge]}], bridge), :eovsdb_op.mutate(@open_vswitch, [{"_uuid", "==", ovs}], [{"next_cfg", "+=", 1}]) ], pid ) {:reply, replies, state} end def handle_call({:add_port, options}, _from, state) do %State{client_pid: pid, ovs_uuid: ovs} = state port = %{ name: options[:name], interfaces: ["named-uuid", "interface"] } iface_options = [ ["remote_ip", options[:remote_ip]], ["local_ip", options[:local_ip]], ["in_key", options[:in_key]], ["out_key", options[:out_key]], ["key", options[:key]], ["tos", options[:tos]], ["ttl", options[:ttl]], ["df_default", options[:df_default]], ["csum", options[:csum]], ["peer_cert", options[:peer_cert]], ["certificate", options[:certificate]], ["private_key", options[:private_key]], ["psk", options[:psk]] ] interface = %{ name: options[:name], type: options[:type], ingress_policing_rate: options[:ingress_policing_rate], ingress_policing_burst: options[:ingress_policing_burst], ofport_request: make_ovsdb_set(options[:ofport_request]), options: make_ovsdb_map(iface_options) } bridge = %{ports: add_elem_to_set(options[:ports], ["named-uuid", "port"])} next_config = [{"next_cfg", "+=", 1}] eq_br_name = [{"name", "==", options[:bridge]}] eq_ovs_uuid = [{"_uuid", "==", ovs}] replies = xact( [ :eovsdb_op.insert(@interface, interface, "interface"), :eovsdb_op.insert(@port, port, "port"), :eovsdb_op.update(@bridge, eq_br_name, bridge), :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 reply = do_find_by_name(pid, table, name) {: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 defp do_find_by_name(pid, table, name) do query = :eovsdb_op.select('*', table, [{"name", "==", name}]) case xact(query, pid) do [%{"rows" => []}] -> :not_found [%{"rows" => [row]}] -> row end end defp make_ovsdb_map(map), do: make_ovsdb_map([], map) defp make_ovsdb_map(acc, []), do: ["map", Enum.reverse(acc)] defp make_ovsdb_map(acc, [[_key, nil] | rest]), do: make_ovsdb_map(acc, rest) defp make_ovsdb_map(acc, [attr | rest]), do: make_ovsdb_map([attr | acc], rest) defp make_ovsdb_set(nil), do: ["set", []] defp make_ovsdb_set([_ | _] = list), do: ["set", list] defp make_ovsdb_set(value), do: value defp add_elem_to_set(["set", []], value), do: value defp add_elem_to_set(["set", values], value), do: ["set", values ++ [value]] defp add_elem_to_set(["uuid", _] = uuid, value), do: ["set", [uuid] ++ [value]] defp del_elem_from_set(["set", values], value), do: ["set", values -- [value]] defp del_elem_from_set(["uuid", _], _value), do: ["set", []] end